@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.cjs CHANGED
@@ -26,12 +26,32 @@ __export(index_exports, {
26
26
  NotConnectedError: () => NotConnectedError,
27
27
  OwneyError: () => OwneyError,
28
28
  OwneySDK: () => OwneySDK,
29
+ YieldseekerAgent: () => YieldseekerAgent,
29
30
  createOwneySIWX: () => createOwneySIWX,
30
- listPendingSwaps: () => listOrders,
31
31
  setOwneyDebug: () => setOwneyDebug
32
32
  });
33
33
  module.exports = __toCommonJS(index_exports);
34
34
 
35
+ // src/lib/deposit-batch-callback.ts
36
+ var batchCallbacks = /* @__PURE__ */ new WeakMap();
37
+ var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
38
+ function toBatchTransfer(to, amount, verification) {
39
+ return {
40
+ to,
41
+ amount,
42
+ ...verification ? {
43
+ yieldseeker: {
44
+ signature: verification.signature,
45
+ userId: verification.userId,
46
+ agentId: verification.yieldseekerAgentId
47
+ }
48
+ } : {}
49
+ };
50
+ }
51
+ function registerDepositBatch(callback, transfer) {
52
+ batchCallbacks.set(callback, transfer);
53
+ }
54
+
35
55
  // src/errors.ts
36
56
  var OwneyError = class extends Error {
37
57
  code;
@@ -311,18 +331,18 @@ function tokenDecimals(symbol, explicit) {
311
331
  return explicit;
312
332
  return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
313
333
  }
314
- function mapDeposit(raw) {
334
+ function mapDeposit(raw2) {
315
335
  return {
316
- txHash: raw.txHash,
317
- smartWallet: raw.smartWallet,
318
- amount: raw.amount
336
+ txHash: raw2.txHash,
337
+ smartWallet: raw2.smartWallet,
338
+ amount: raw2.amount
319
339
  };
320
340
  }
321
- function mapWithdraw(raw) {
341
+ function mapWithdraw(raw2) {
322
342
  return {
323
- txHash: raw.txHash,
324
- type: raw.type,
325
- amount: raw.amount
343
+ txHash: raw2.txHash,
344
+ type: raw2.type,
345
+ amount: raw2.amount
326
346
  };
327
347
  }
328
348
  var CHAIN_ID_TO_NAME = {
@@ -341,10 +361,10 @@ function resolveChainId(chain) {
341
361
  if (Number.isFinite(asNum) && asNum > 0) return asNum;
342
362
  return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
343
363
  }
344
- function mapPendingAllocations(raw) {
345
- if (!Array.isArray(raw)) return void 0;
364
+ function mapPendingAllocations(raw2) {
365
+ if (!Array.isArray(raw2)) return void 0;
346
366
  const pending = [];
347
- for (const entry of raw) {
367
+ for (const entry of raw2) {
348
368
  if (typeof entry !== "object" || entry === null) continue;
349
369
  const e = entry;
350
370
  if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
@@ -367,8 +387,8 @@ function mapPendingAllocations(raw) {
367
387
  }
368
388
  return pending.length > 0 ? pending : void 0;
369
389
  }
370
- function mapBalances(raw, _chainId, smartWallet) {
371
- const portfolio = raw.portfolio;
390
+ function mapBalances(raw2, _chainId, smartWallet) {
391
+ const portfolio = raw2.portfolio;
372
392
  const portfolioByChain = portfolio.portfolioByChain ?? {};
373
393
  let totalBalance = 0;
374
394
  const tokens = [];
@@ -437,8 +457,8 @@ function sumTokenValues(tokens) {
437
457
  function sumTokenEarnings(tokens) {
438
458
  return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
439
459
  }
440
- function mapEarnings(raw, smartWallet) {
441
- const totalEarningsByChain = raw.data.totalEarningsByChainWithFee ?? raw.data.totalEarningsByChain ?? {};
460
+ function mapEarnings(raw2, smartWallet) {
461
+ const totalEarningsByChain = raw2.data.totalEarningsByChainWithFee ?? raw2.data.totalEarningsByChain ?? {};
442
462
  const tokens = [];
443
463
  for (const [chainIdKey, tokensBySymbol] of Object.entries(
444
464
  totalEarningsByChain
@@ -457,15 +477,15 @@ function mapEarnings(raw, smartWallet) {
457
477
  return {
458
478
  smartWallet,
459
479
  lifetimeEarnings: sumTokenEarnings(
460
- raw.data.totalEarningsByTokenWithFee ?? raw.data.totalEarningsByToken
480
+ raw2.data.totalEarningsByTokenWithFee ?? raw2.data.totalEarningsByToken
461
481
  ),
462
482
  tokens
463
483
  };
464
484
  }
465
- function mapWeightedApyByChain(raw) {
466
- if (!raw) return void 0;
485
+ function mapWeightedApyByChain(raw2) {
486
+ if (!raw2) return void 0;
467
487
  const out = {};
468
- for (const [chainKey, tokenApy] of Object.entries(raw)) {
488
+ for (const [chainKey, tokenApy] of Object.entries(raw2)) {
469
489
  const chainId = Number(chainKey);
470
490
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
471
491
  const perAsset = {};
@@ -505,8 +525,8 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
505
525
  }
506
526
  return totalBalance > 0 ? weightedSum / totalBalance : null;
507
527
  }
508
- function mapApyHistory(raw, chainId, tokenSymbol) {
509
- const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
528
+ function mapApyHistory(raw2, chainId, tokenSymbol) {
529
+ const history = Object.entries(raw2.history ?? {}).map(([date, entry]) => ({
510
530
  date,
511
531
  apy: rawPoolApyForChain(entry, chainId, tokenSymbol),
512
532
  // Provider position balances are treated as decimal amounts of the
@@ -522,9 +542,9 @@ function mapApyHistory(raw, chainId, tokenSymbol) {
522
542
  } : {}
523
543
  })).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
524
544
  return {
525
- walletAddress: raw.walletAddress,
526
- weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
527
- apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain),
545
+ walletAddress: raw2.walletAddress,
546
+ weightedApyAfterFee: raw2.weightedApyAfterFee ? sumTokenValues(raw2.weightedApyAfterFee) : void 0,
547
+ apyByChainAndAsset: mapWeightedApyByChain(raw2.weightedApyAfterFeeByChain),
528
548
  history
529
549
  };
530
550
  }
@@ -620,23 +640,23 @@ function mapEntries(rawEntries, chainId) {
620
640
  };
621
641
  });
622
642
  }
623
- function mapUserProfile(raw, userAddress) {
643
+ function mapUserProfile(raw2, userAddress) {
624
644
  return {
625
645
  address: userAddress,
626
- smartWallet: raw.smartWallet || "",
627
- chains: raw.chains || [],
628
- strategy: raw.strategy,
629
- hasActiveSessionKey: raw.hasActiveSessionKey || false,
630
- protocols: raw.protocols || [],
631
- splitting: raw.splitting,
632
- minSplits: raw.minSplits
646
+ smartWallet: raw2.smartWallet || "",
647
+ chains: raw2.chains || [],
648
+ strategy: raw2.strategy,
649
+ hasActiveSessionKey: raw2.hasActiveSessionKey || false,
650
+ protocols: raw2.protocols || [],
651
+ splitting: raw2.splitting,
652
+ minSplits: raw2.minSplits
633
653
  };
634
654
  }
635
- function mapApyByStrategy(raw) {
655
+ function mapApyByStrategy(raw2) {
636
656
  const apyPerAsset = {};
637
657
  let apySum = 0;
638
658
  let apyCount = 0;
639
- for (const entry of raw.data) {
659
+ for (const entry of raw2.data) {
640
660
  const supported = SupportedAssets.find(
641
661
  (asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
642
662
  );
@@ -748,9 +768,9 @@ function netDeltaForSnapshot(entry, chainId, asset) {
748
768
  debugLog("zyfai:earnings", "gross fallback for daily earnings", { gross });
749
769
  return gross;
750
770
  }
751
- function mapDailyEarnings(raw, chainId, tokenSymbol) {
771
+ function mapDailyEarnings(raw2, chainId, tokenSymbol) {
752
772
  const wanted = tokenSymbol?.toUpperCase();
753
- const snapshots = [...raw.data ?? []].sort(
773
+ const snapshots = [...raw2.data ?? []].sort(
754
774
  (a, b) => a.snapshot_date.localeCompare(b.snapshot_date)
755
775
  );
756
776
  const byAsset = /* @__PURE__ */ new Map();
@@ -765,7 +785,7 @@ function mapDailyEarnings(raw, chainId, tokenSymbol) {
765
785
  }
766
786
  }
767
787
  const assets = [...byAsset.entries()].map(([asset, series]) => ({ asset, points: series.points })).sort((a, b) => a.asset.localeCompare(b.asset));
768
- return { walletAddress: raw.walletAddress, chainId, assets };
788
+ return { walletAddress: raw2.walletAddress, chainId, assets };
769
789
  }
770
790
 
771
791
  // src/agents/zyfai/zyfai.withdraw-amount.ts
@@ -876,15 +896,15 @@ var readSession = (address, _chainId) => {
876
896
  if (typeof window === "undefined") return null;
877
897
  const key2 = buildKey(address);
878
898
  const store = storage();
879
- let raw = null;
899
+ let raw2 = null;
880
900
  try {
881
- raw = store?.getItem(key2) ?? null;
901
+ raw2 = store?.getItem(key2) ?? null;
882
902
  } catch {
883
- raw = null;
903
+ raw2 = null;
884
904
  }
885
- if (raw) {
905
+ if (raw2) {
886
906
  try {
887
- const parsed = JSON.parse(raw);
907
+ const parsed = JSON.parse(raw2);
888
908
  if (isFreshSession(parsed)) return parsed;
889
909
  } catch {
890
910
  }
@@ -1052,8 +1072,8 @@ function buildChainsByProtocol(protocols, dashboardChainIds) {
1052
1072
  }
1053
1073
  return result;
1054
1074
  }
1055
- function flattenAvailablePools(raw) {
1056
- const byChain = raw ?? {};
1075
+ function flattenAvailablePools(raw2) {
1076
+ const byChain = raw2 ?? {};
1057
1077
  const names = [];
1058
1078
  for (const byToken of Object.values(byChain ?? {})) {
1059
1079
  for (const entry of Object.values(byToken ?? {})) {
@@ -1556,8 +1576,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
1556
1576
  const poolResults = await Promise.all(
1557
1577
  universe.map(async (protocol) => {
1558
1578
  try {
1559
- const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
1560
- return [protocol.id, flattenAvailablePools(raw)];
1579
+ const raw2 = await this.sdk.getAvailablePools(protocol.id, strategy);
1580
+ return [protocol.id, flattenAvailablePools(raw2)];
1561
1581
  } catch (error) {
1562
1582
  console.warn(
1563
1583
  `[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
@@ -1623,14 +1643,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1623
1643
  async readWalletState(ownerAddress) {
1624
1644
  try {
1625
1645
  const { portfolio } = await this.sdk.getPositions(ownerAddress);
1626
- const raw = portfolio;
1646
+ const raw2 = portfolio;
1627
1647
  debugLog("zyfai:onboard", "wallet state from getPositions", {
1628
- predeployed: raw?.predeployed,
1629
- hasActiveSessionKey: raw?.hasActiveSessionKey
1648
+ predeployed: raw2?.predeployed,
1649
+ hasActiveSessionKey: raw2?.hasActiveSessionKey
1630
1650
  });
1631
1651
  return {
1632
- predeployed: raw?.predeployed,
1633
- hasActiveSessionKey: raw?.hasActiveSessionKey
1652
+ predeployed: raw2?.predeployed,
1653
+ hasActiveSessionKey: raw2?.hasActiveSessionKey
1634
1654
  };
1635
1655
  } catch (error) {
1636
1656
  console.warn(
@@ -1916,14 +1936,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1916
1936
  return { txHash, smartWallet, amount };
1917
1937
  }
1918
1938
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
1919
- const raw = await this.sdk.depositFunds(
1939
+ const raw2 = await this.sdk.depositFunds(
1920
1940
  this.getAddress(),
1921
1941
  validChainId,
1922
1942
  amount,
1923
1943
  asset,
1924
1944
  "aggressive"
1925
1945
  );
1926
- return mapDeposit(raw);
1946
+ return mapDeposit(raw2);
1927
1947
  } catch (error) {
1928
1948
  throw error;
1929
1949
  }
@@ -1932,27 +1952,27 @@ var ZyfaiAgent = class _ZyfaiAgent {
1932
1952
  async withdraw(state, chainId, token, amount) {
1933
1953
  const validChainId = isValidChainId(chainId);
1934
1954
  await this.ensureConnected(state, validChainId);
1935
- const raw = await this.sdk.withdrawFunds(
1955
+ const raw2 = await this.sdk.withdrawFunds(
1936
1956
  this.getAddress(),
1937
1957
  validChainId,
1938
1958
  amount,
1939
1959
  token
1940
1960
  );
1941
- if (!raw.success) {
1961
+ if (!raw2.success) {
1942
1962
  throw new OwneyError(
1943
1963
  "WITHDRAW_FAILED",
1944
- raw.message || "Zyfai withdraw failed.",
1945
- { chainId: validChainId, token, amount, response: raw },
1964
+ raw2.message || "Zyfai withdraw failed.",
1965
+ { chainId: validChainId, token, amount, response: raw2 },
1946
1966
  this.id
1947
1967
  );
1948
1968
  }
1949
- return mapWithdraw(raw);
1969
+ return mapWithdraw(raw2);
1950
1970
  }
1951
1971
  // --- IAgent: Portfolio reads ---
1952
1972
  async getBalances(state, chainId) {
1953
1973
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
1954
- const raw = await this.sdk.getPortfolio(this.getAddress());
1955
- return mapBalances(raw, validChainId, smartWallet);
1974
+ const raw2 = await this.sdk.getPortfolio(this.getAddress());
1975
+ return mapBalances(raw2, validChainId, smartWallet);
1956
1976
  }
1957
1977
  earningsKey(state, chainId, smartWallet) {
1958
1978
  return JSON.stringify([
@@ -1965,11 +1985,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
1965
1985
  const existing = this.earningsReads.get(key2);
1966
1986
  if (existing) return existing;
1967
1987
  const generation = this.earningsGeneration;
1968
- const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw) => {
1988
+ const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw2) => {
1969
1989
  if (generation === this.earningsGeneration) {
1970
- this.earningsSnapshot = { key: key2, raw, at: Date.now() };
1990
+ this.earningsSnapshot = { key: key2, raw: raw2, at: Date.now() };
1971
1991
  }
1972
- return raw;
1992
+ return raw2;
1973
1993
  }).finally(() => {
1974
1994
  if (this.earningsReads.get(key2) === pending)
1975
1995
  this.earningsReads.delete(key2);
@@ -1979,11 +1999,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
1979
1999
  }
1980
2000
  async getEarnings(state, chainId) {
1981
2001
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1982
- const raw = await this.readEarnings(
2002
+ const raw2 = await this.readEarnings(
1983
2003
  this.earningsKey(state, chainId, smartWallet),
1984
2004
  smartWallet
1985
2005
  );
1986
- return mapEarnings(raw, smartWallet);
2006
+ return mapEarnings(raw2, smartWallet);
1987
2007
  }
1988
2008
  async refreshEarnings(state, chainId) {
1989
2009
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -2010,17 +2030,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
2010
2030
  }
2011
2031
  async getAccountApy(state, chainId, days, tokenSymbol) {
2012
2032
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
2013
- const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
2014
- return mapApyHistory(raw, chainId, tokenSymbol);
2033
+ const raw2 = await this.sdk.getDailyApyHistory(smartWallet, days);
2034
+ return mapApyHistory(raw2, chainId, tokenSymbol);
2015
2035
  }
2016
2036
  async getDailyEarnings(state, chainId, days, tokenSymbol) {
2017
2037
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
2018
2038
  const start = new Date(Date.now() - (DayFilterMapping[days] + 1) * 864e5);
2019
- const raw = await this.sdk.getDailyEarnings(
2039
+ const raw2 = await this.sdk.getDailyEarnings(
2020
2040
  smartWallet,
2021
2041
  start.toISOString().slice(0, 10)
2022
2042
  );
2023
- return mapDailyEarnings(raw, chainId, tokenSymbol);
2043
+ return mapDailyEarnings(raw2, chainId, tokenSymbol);
2024
2044
  }
2025
2045
  /**
2026
2046
  * Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
@@ -2055,7 +2075,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2055
2075
  const matched = [];
2056
2076
  let backendExhausted = false;
2057
2077
  for (let i = 0; i < MAX_BACKEND_CALLS && matched.length < limit; i++) {
2058
- const raw = await this.sdk.getHistory(smartWallet, validChainId, {
2078
+ const raw2 = await this.sdk.getHistory(smartWallet, validChainId, {
2059
2079
  limit: backendPageSize,
2060
2080
  offset,
2061
2081
  fromDate: options?.fromDate,
@@ -2066,13 +2086,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
2066
2086
  // asset's rows and handing back a page that filters to nothing.
2067
2087
  assetType
2068
2088
  });
2069
- raw.data.forEach((entry, idx) => {
2089
+ raw2.data.forEach((entry, idx) => {
2070
2090
  if (entry.chainId === validChainId) {
2071
2091
  matched.push({ entry, rawIdx: offset + idx });
2072
2092
  }
2073
2093
  });
2074
- offset += raw.data.length;
2075
- if (raw.data.length < backendPageSize) {
2094
+ offset += raw2.data.length;
2095
+ if (raw2.data.length < backendPageSize) {
2076
2096
  backendExhausted = true;
2077
2097
  break;
2078
2098
  }
@@ -2094,18 +2114,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
2094
2114
  }
2095
2115
  async getUserProfile(state, chainId) {
2096
2116
  await this.connectAuth(state, chainId);
2097
- const raw = await this.sdk.getUserDetails();
2117
+ const raw2 = await this.sdk.getUserDetails();
2098
2118
  debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
2099
2119
  asset: "USDC (default \u2014 no asset passed)",
2100
- splitting: raw.splitting,
2101
- minSplits: raw.minSplits,
2102
- strategy: raw.strategy,
2103
- chains: raw.chains,
2104
- protocolCount: raw.protocols?.length,
2105
- hasActiveSessionKey: raw.hasActiveSessionKey,
2106
- smartWallet: raw.smartWallet
2120
+ splitting: raw2.splitting,
2121
+ minSplits: raw2.minSplits,
2122
+ strategy: raw2.strategy,
2123
+ chains: raw2.chains,
2124
+ protocolCount: raw2.protocols?.length,
2125
+ hasActiveSessionKey: raw2.hasActiveSessionKey,
2126
+ smartWallet: raw2.smartWallet
2107
2127
  });
2108
- return mapUserProfile(raw, this.connectedAddress);
2128
+ return mapUserProfile(raw2, this.connectedAddress);
2109
2129
  }
2110
2130
  async ensureAutoSelectProtocols(state, chainId, asset) {
2111
2131
  await this.connectAuth(state, chainId);
@@ -2124,80 +2144,20 @@ var ZyfaiAgent = class _ZyfaiAgent {
2124
2144
  }
2125
2145
  // --- IAgent: Discovery (no wallet required) ---
2126
2146
  async getAgentApy(days, options) {
2127
- const raw = await this.sdk.getAPYPerStrategy(
2147
+ const raw2 = await this.sdk.getAPYPerStrategy(
2128
2148
  false,
2129
2149
  DayFilterMapping[days],
2130
2150
  "aggressive",
2131
2151
  options?.chainId,
2132
2152
  options?.tokenSymbol
2133
2153
  );
2134
- return mapApyByStrategy(raw);
2154
+ return mapApyByStrategy(raw2);
2135
2155
  }
2136
2156
  };
2137
2157
 
2138
- // src/lib/routing-api.ts
2139
- var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2140
- async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2141
- const url = `${baseUrl}/api/v1/agent/org-config`;
2142
- try {
2143
- const res = await fetch(url, {
2144
- method: "GET",
2145
- headers: {
2146
- "Content-Type": "application/json",
2147
- "x-owney-api-key": `${apiKey}`
2148
- }
2149
- });
2150
- if (!res.ok) {
2151
- if (res.status !== 404) {
2152
- console.warn(
2153
- `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
2154
- );
2155
- }
2156
- return null;
2157
- }
2158
- const json = await res.json();
2159
- const policy = json.success ? json.data ?? null : null;
2160
- debugLog(
2161
- "owney-sdk",
2162
- policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
2163
- policy ?? void 0
2164
- );
2165
- return policy;
2166
- } catch (error) {
2167
- console.warn(
2168
- "[owney-sdk] Could not read org agent config (non-fatal):",
2169
- error instanceof Error ? error.message : String(error)
2170
- );
2171
- return null;
2172
- }
2173
- }
2174
- async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2175
- const url = `${baseUrl}/api/v1/agent/keys`;
2176
- const res = await fetch(url, {
2177
- method: "GET",
2178
- headers: {
2179
- "Content-Type": "application/json",
2180
- "x-owney-api-key": `${apiKey}`
2181
- }
2182
- });
2183
- if (!res.ok) {
2184
- const text = await res.text().catch(() => "");
2185
- throw new OwneyError(
2186
- "API_ROUTING_ERROR",
2187
- `Routing API error ${res.status}: ${text}`,
2188
- { statusCode: res.status, responseBody: text }
2189
- );
2190
- }
2191
- const json = await res.json();
2192
- if (!json.success) {
2193
- throw new OwneyError(
2194
- "API_ROUTING_FAILED",
2195
- `Routing API request failed: ${json.message}`,
2196
- { message: json.message }
2197
- );
2198
- }
2199
- return json.data;
2200
- }
2158
+ // src/agents/yieldseeker/yieldseeker.agent.ts
2159
+ var import_viem6 = require("viem");
2160
+ var import_chains3 = require("viem/chains");
2201
2161
 
2202
2162
  // src/lib/chain-guard.ts
2203
2163
  var CHAIN_NAMES = {
@@ -2234,148 +2194,131 @@ async function ensureWalletOnChain(pub, wallet, expected) {
2234
2194
  }
2235
2195
  }
2236
2196
 
2237
- // src/lib/swap/swap-api.ts
2238
- async function request(baseUrl, apiKey, path, init) {
2239
- const url = `${baseUrl}/api/v1/swap${path}`;
2240
- const res = await fetch(url, {
2241
- method: init?.method ?? "GET",
2242
- headers: {
2243
- "Content-Type": "application/json",
2244
- "x-owney-api-key": apiKey
2245
- },
2246
- ...init ? { body: JSON.stringify(init.body) } : {}
2247
- });
2248
- if (!res.ok) {
2249
- const text = await res.text().catch(() => "");
2250
- if (res.status === 429) {
2251
- throw new OwneyError(
2252
- "SWAP_RATE_LIMITED",
2253
- "Swap provider is rate limiting, retry shortly",
2254
- { statusCode: res.status }
2255
- );
2256
- }
2257
- if (res.status === 403) {
2258
- throw new OwneyError(
2259
- "SWAP_DISABLED",
2260
- "Swap is not enabled for this organization",
2261
- { statusCode: res.status }
2262
- );
2263
- }
2197
+ // src/lib/transfer-auth.ts
2198
+ var import_viem2 = require("viem");
2199
+
2200
+ // src/lib/sponsor-client.ts
2201
+ var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2202
+ async function postPaymasterIntent(input) {
2203
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2204
+ let res;
2205
+ try {
2206
+ res = await fetch(`${base5}/api/v1/sponsor/paymaster-intent`, {
2207
+ method: "POST",
2208
+ headers: {
2209
+ "content-type": "application/json",
2210
+ "x-owney-api-key": input.apiKey,
2211
+ Authorization: `Signature ${input.yieldseekerSignature}`
2212
+ },
2213
+ body: JSON.stringify(input.body)
2214
+ });
2215
+ } catch (networkError) {
2264
2216
  throw new OwneyError(
2265
- "SWAP_REQUEST_FAILED",
2266
- `Swap API error ${res.status}: ${text}`,
2267
- { statusCode: res.status, responseBody: text }
2217
+ "SPONSOR_REQUEST_FAILED",
2218
+ `Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2219
+ { cause: String(networkError), safeToFallback: true }
2268
2220
  );
2269
2221
  }
2270
- const json = await res.json();
2271
- if (!json.success) {
2222
+ const text = await res.text();
2223
+ let parsed = null;
2224
+ try {
2225
+ parsed = JSON.parse(text);
2226
+ } catch {
2227
+ }
2228
+ if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
2272
2229
  throw new OwneyError(
2273
- "SWAP_REQUEST_FAILED",
2274
- `Swap API request failed: ${json.message ?? "unknown error"}`,
2275
- { message: json.message }
2230
+ "SPONSOR_REQUEST_FAILED",
2231
+ `Paymaster intent API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2232
+ {
2233
+ statusCode: res.status,
2234
+ responseBody: text.slice(0, 500),
2235
+ safeToFallback: true
2236
+ }
2276
2237
  );
2277
2238
  }
2278
- return json.data;
2239
+ return parsed.data;
2279
2240
  }
2280
- function createSwapApi(baseUrl, apiKey) {
2281
- return {
2282
- /** Source assets the user may pay with, and each chain's deposit targets. */
2283
- listTokens: () => request(baseUrl, apiKey, "/tokens"),
2284
- /**
2285
- * `walletAddress` is required even though the routing API could not infer
2286
- * it: the Fusion+ quoter binds a quote to whoever will sign the order and
2287
- * rejects the request without it.
2288
- */
2289
- quote: (params) => request(baseUrl, apiKey, "/quote", {
2290
- method: "POST",
2291
- body: {
2292
- srcChainId: params.from.chainId,
2293
- srcSymbol: params.from.symbol,
2294
- dstChainId: params.to.chainId,
2295
- dstSymbol: params.to.symbol,
2296
- amount: params.from.amount,
2297
- walletAddress: params.walletAddress,
2298
- ...params.direction ? { direction: params.direction } : {}
2299
- }
2300
- }),
2301
- /** Ready-to-send calldata for a same-chain swap. */
2302
- swapTx: (params) => request(baseUrl, apiKey, "/tx", {
2303
- method: "POST",
2304
- body: {
2305
- srcChainId: params.from.chainId,
2306
- srcSymbol: params.from.symbol,
2307
- dstChainId: params.to.chainId,
2308
- dstSymbol: params.to.symbol,
2309
- amount: params.from.amount,
2310
- walletAddress: params.walletAddress,
2311
- slippage: params.slippage,
2312
- ...params.direction ? { direction: params.direction } : {}
2241
+ async function getSponsorRelayerAddress(input) {
2242
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2243
+ let res;
2244
+ try {
2245
+ res = await fetch(
2246
+ `${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2247
+ {
2248
+ headers: { "x-owney-api-key": input.apiKey }
2313
2249
  }
2314
- }),
2315
- /**
2316
- * Builds a Fusion+ order server-side and returns EIP-712 typed data.
2317
- *
2318
- * Only HASHES go over the wire. The preimages never leave the browser —
2319
- * see swap.secrets.
2320
- */
2321
- buildOrder: (params) => request(baseUrl, apiKey, "/order/build", {
2322
- method: "POST",
2323
- body: {
2324
- srcChainId: params.from.chainId,
2325
- srcSymbol: params.from.symbol,
2326
- dstChainId: params.to.chainId,
2327
- dstSymbol: params.to.symbol,
2328
- amount: params.from.amount,
2329
- walletAddress: params.walletAddress,
2330
- secretHashes: params.secretHashes,
2331
- ...params.direction ? { direction: params.direction } : {},
2332
- ...params.receiver ? { receiver: params.receiver } : {}
2250
+ );
2251
+ } catch (networkError) {
2252
+ throw new OwneyError(
2253
+ "SPONSOR_REQUEST_FAILED",
2254
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2255
+ { cause: String(networkError), safeToFallback: true }
2256
+ );
2257
+ }
2258
+ const text = await res.text();
2259
+ let parsed = null;
2260
+ try {
2261
+ parsed = JSON.parse(text);
2262
+ } catch {
2263
+ }
2264
+ if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
2265
+ throw new OwneyError(
2266
+ "SPONSOR_REQUEST_FAILED",
2267
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2268
+ {
2269
+ statusCode: res.status,
2270
+ responseBody: text.slice(0, 500),
2271
+ safeToFallback: true
2333
2272
  }
2334
- }),
2335
- submitOrder: (body) => request(baseUrl, apiKey, "/order", { method: "POST", body }),
2336
- /**
2337
- * Only call once `readyForSecrets` reports the escrow deployed. Publishing
2338
- * earlier hands a resolver the preimage while the user's funds are locked
2339
- * and nothing has been posted on the destination chain.
2340
- */
2341
- submitSecret: (orderHash, secret) => request(baseUrl, apiKey, "/order/secret", {
2342
- method: "POST",
2343
- body: { orderHash, secret }
2344
- }),
2345
- orderStatus: (orderHash) => request(baseUrl, apiKey, `/order/${orderHash}`),
2346
- readyForSecrets: (orderHash) => request(
2347
- baseUrl,
2348
- apiKey,
2349
- `/order/${orderHash}/ready-for-secrets`
2350
- )
2351
- };
2352
- }
2353
-
2354
- // src/lib/swap/swap.rpc.ts
2355
- var import_viem2 = require("viem");
2356
- var DEFAULT_RPC_URLS = {
2357
- 1: ["https://cloudflare-eth.com", "https://ethereum-rpc.publicnode.com"],
2358
- 8453: ["https://mainnet.base.org", "https://base-rpc.publicnode.com"],
2359
- 42161: [
2360
- "https://arb1.arbitrum.io/rpc",
2361
- "https://arbitrum-one-rpc.publicnode.com"
2362
- ]
2363
- };
2364
- function swapReadTransport(chainId, overrides) {
2365
- const override = overrides?.[chainId];
2366
- if (override) return (0, import_viem2.http)(override);
2367
- const urls = DEFAULT_RPC_URLS[chainId];
2368
- if (!urls || urls.length === 0) return (0, import_viem2.http)();
2369
- return (0, import_viem2.fallback)(urls.map((url) => (0, import_viem2.http)(url)));
2273
+ );
2274
+ }
2275
+ return parsed.data.relayer;
2370
2276
  }
2371
- function receiptTimeoutMs(chainId) {
2372
- return chainId === 1 ? 6e5 : 18e4;
2277
+ async function postSponsorBatchTransfer(input) {
2278
+ let res;
2279
+ try {
2280
+ res = await fetch(
2281
+ `${input.baseUrl ?? ROUTING_API_BASE_URL}/api/v1/sponsor/permit2-batch`,
2282
+ {
2283
+ method: "POST",
2284
+ headers: {
2285
+ "content-type": "application/json",
2286
+ "x-owney-api-key": input.apiKey
2287
+ },
2288
+ body: JSON.stringify(input.body)
2289
+ }
2290
+ );
2291
+ } catch {
2292
+ throw new OwneyError(
2293
+ "SPONSOR_REQUEST_FAILED",
2294
+ "Deposit status is unknown. Retry the same amount to check it.",
2295
+ { safeToFallback: false }
2296
+ );
2297
+ }
2298
+ const parsed = await res.json().catch(() => null);
2299
+ if (!res.ok || !parsed?.success || !/^0x[0-9a-fA-F]{64}$/.test(parsed.data?.txHash ?? "")) {
2300
+ throw new OwneyError(
2301
+ "SPONSOR_REQUEST_FAILED",
2302
+ "Deposit could not be confirmed. Retry the same amount to check its status.",
2303
+ {
2304
+ statusCode: res.status,
2305
+ safeToFallback: false,
2306
+ notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
2307
+ }
2308
+ );
2309
+ }
2310
+ return parsed.data;
2373
2311
  }
2374
2312
 
2375
2313
  // src/lib/permit2.ts
2376
2314
  var import_viem3 = require("viem");
2377
2315
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2378
- var MAX_UINT256 = 2n ** 256n - 1n;
2316
+ function permit2ApprovalAmount(requiredAmount) {
2317
+ if (requiredAmount <= 0n) {
2318
+ throw new Error("Permit2 approval requires a positive deposit amount");
2319
+ }
2320
+ return requiredAmount;
2321
+ }
2379
2322
  var ERC20_ALLOWANCE_ABI = [
2380
2323
  {
2381
2324
  type: "function",
@@ -2405,29 +2348,6 @@ var ERC20_ALLOWANCE_ABI = [
2405
2348
  outputs: [{ name: "", type: "uint256" }]
2406
2349
  }
2407
2350
  ];
2408
- function buildPermitTransferFromTypedData(input) {
2409
- return {
2410
- domain: {
2411
- name: "Permit2",
2412
- chainId: input.chainId,
2413
- verifyingContract: PERMIT2_ADDRESS
2414
- },
2415
- types: {
2416
- PermitTransferFrom: [
2417
- { name: "permitted", type: "TokenPermissions" },
2418
- { name: "spender", type: "address" },
2419
- { name: "nonce", type: "uint256" },
2420
- { name: "deadline", type: "uint256" }
2421
- ],
2422
- TokenPermissions: [
2423
- { name: "token", type: "address" },
2424
- { name: "amount", type: "uint256" }
2425
- ]
2426
- },
2427
- primaryType: "PermitTransferFrom",
2428
- message: input.message
2429
- };
2430
- }
2431
2351
  function randomPermit2Nonce() {
2432
2352
  const bytes = new Uint8Array(32);
2433
2353
  globalThis.crypto.getRandomValues(bytes);
@@ -2450,122 +2370,39 @@ async function readErc20Balance(publicClient, token, owner) {
2450
2370
  });
2451
2371
  }
2452
2372
 
2453
- // src/lib/swap/swap.secrets.ts
2454
- var import_viem4 = require("viem");
2455
- var SECRET_BYTES = 32;
2456
- function randomBytes(length) {
2457
- const bytes = new Uint8Array(length);
2458
- const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
2459
- if (!cryptoObj?.getRandomValues) {
2460
- throw new Error(
2461
- "[owney-sdk] Secure randomness is unavailable, so a swap secret cannot be generated safely."
2462
- );
2463
- }
2464
- cryptoObj.getRandomValues(bytes);
2465
- return bytes;
2466
- }
2467
- function mintSecrets(count) {
2468
- if (!Number.isInteger(count) || count < 1) {
2469
- throw new Error(
2470
- `[owney-sdk] A swap needs at least one secret, got ${String(count)}.`
2471
- );
2472
- }
2473
- const secrets = [];
2474
- const secretHashes = [];
2475
- for (let i = 0; i < count; i++) {
2476
- const secret = (0, import_viem4.toHex)(randomBytes(SECRET_BYTES));
2477
- secrets.push(secret);
2478
- secretHashes.push((0, import_viem4.keccak256)(secret));
2479
- }
2480
- return { secrets, secretHashes };
2373
+ // src/lib/sponsored-deposit.ts
2374
+ var AUTH_WINDOW_SECONDS = 15 * 60;
2375
+ var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
2376
+ function provideDepositVerificationContext(callback, context) {
2377
+ callback[verificationSetter]?.(context);
2481
2378
  }
2482
-
2483
- // src/lib/swap/swap.types.ts
2484
- var SWAP_TERMINAL_STATUSES = [
2485
- "executed",
2486
- "expired",
2487
- "cancelled",
2488
- "refunded"
2489
- ];
2490
- var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
2491
-
2492
- // src/lib/swap/swap.order-runner.ts
2493
- var DEFAULT_POLL_MS = 5e3;
2494
- var MAX_BACKOFF_MS = 3e4;
2495
- var backoffFor = (failures, base3) => Math.min(base3 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
2496
- var DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
2497
- async function runFusionOrder(deps, options) {
2498
- const {
2499
- orderHash,
2500
- secrets,
2501
- onStage,
2502
- pollIntervalMs = DEFAULT_POLL_MS,
2503
- timeoutMs = DEFAULT_TIMEOUT_MS
2504
- } = options;
2505
- const deadline = deps.now() + timeoutMs;
2506
- let failures = 0;
2507
- const published = /* @__PURE__ */ new Set();
2508
- onStage?.("swapping");
2509
- for (; ; ) {
2510
- if (deps.now() >= deadline) {
2511
- throw new OwneyError(
2512
- "SWAP_REQUEST_FAILED",
2513
- "Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
2514
- { orderHash }
2515
- );
2516
- }
2517
- let ready;
2518
- try {
2519
- ready = await deps.readyForSecrets(orderHash);
2520
- } catch {
2521
- ready = {};
2522
- }
2523
- for (const fill of ready.fills ?? []) {
2524
- if (published.has(fill.idx)) continue;
2525
- const secret = secrets[fill.idx];
2526
- if (secret === void 0) {
2527
- throw new OwneyError(
2528
- "SWAP_REQUEST_FAILED",
2529
- `Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
2530
- { orderHash, fillIndex: fill.idx }
2531
- );
2532
- }
2533
- try {
2534
- await deps.submitSecret(orderHash, secret);
2535
- published.add(fill.idx);
2536
- } catch {
2537
- failures += 1;
2538
- }
2539
- }
2540
- let status;
2541
- try {
2542
- ({ status } = await deps.orderStatus(orderHash));
2543
- failures = 0;
2544
- } catch {
2545
- failures += 1;
2546
- await deps.sleep(backoffFor(failures, pollIntervalMs));
2547
- continue;
2548
- }
2549
- if (status === "refunding") onStage?.("refunding");
2550
- if (isSwapTerminal(status)) {
2551
- if (status === "executed") {
2552
- onStage?.("swapped");
2553
- return { status, filled: true };
2554
- }
2555
- if (status === "refunded") onStage?.("refunded");
2556
- throw new OwneyError(
2557
- status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
2558
- 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.",
2559
- { orderHash, status }
2560
- );
2379
+ function makeVerificationAwareDepositCallback(implementation) {
2380
+ let nextVerification;
2381
+ const callback = async (smartWallet, chainId, amount) => {
2382
+ const verification = nextVerification;
2383
+ nextVerification = void 0;
2384
+ return implementation(smartWallet, chainId, amount, verification);
2385
+ };
2386
+ Object.defineProperty(callback, verificationSetter, {
2387
+ value: (context) => {
2388
+ nextVerification = context;
2561
2389
  }
2562
- await deps.sleep(pollIntervalMs);
2563
- }
2390
+ });
2391
+ return callback;
2564
2392
  }
2565
2393
 
2566
- // src/lib/swap/swap.secret-store.ts
2567
- var KEY_PREFIX2 = "owney.swap.order";
2568
- var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
2394
+ // src/agents/yieldseeker/yieldseeker.auth.ts
2395
+ var import_siwe = require("siwe");
2396
+ var import_viem4 = require("viem");
2397
+ var import_chains2 = require("viem/chains");
2398
+
2399
+ // src/agents/yieldseeker/yieldseeker.auth-cache.ts
2400
+ var KEY_PREFIX2 = "owney.yieldseeker.session.v5";
2401
+ var INVALIDATED_KEY_PREFIXES = [
2402
+ "owney.yieldseeker.session",
2403
+ "owney.yieldseeker.session.v3",
2404
+ "owney.yieldseeker.session.v4"
2405
+ ];
2569
2406
  var storage2 = () => {
2570
2407
  if (typeof window === "undefined") return null;
2571
2408
  try {
@@ -2574,281 +2411,1649 @@ var storage2 = () => {
2574
2411
  return null;
2575
2412
  }
2576
2413
  };
2577
- var keyFor = (orderHash) => `${KEY_PREFIX2}.${orderHash}`;
2578
- function saveOrder(order) {
2579
- const store = storage2();
2580
- if (!store) return;
2414
+ var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
2415
+ var invalidatedKeys = (address, chainId) => INVALIDATED_KEY_PREFIXES.map(
2416
+ (prefix) => `${prefix}:${address.toLowerCase()}:${chainId}`
2417
+ );
2418
+ var clearInvalidatedSessions = (store, address, chainId) => {
2419
+ for (const key2 of invalidatedKeys(address, chainId)) {
2420
+ memorySessions2.delete(key2);
2421
+ try {
2422
+ store?.removeItem(key2);
2423
+ } catch {
2424
+ }
2425
+ }
2426
+ };
2427
+ var memorySessions2 = /* @__PURE__ */ new Map();
2428
+ var isValidSession = (session) => {
2429
+ if (!session?.token) return false;
2581
2430
  try {
2582
- store.setItem(keyFor(order.orderHash), JSON.stringify(order));
2431
+ const parsed = JSON.parse(atob(session.token));
2432
+ return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
2583
2433
  } catch {
2434
+ return false;
2584
2435
  }
2585
- }
2586
- function clearOrder(orderHash) {
2436
+ };
2437
+ var readYieldseekerSession = (address, chainId) => {
2438
+ if (typeof window === "undefined") return null;
2439
+ const key2 = buildKey2(address, chainId);
2587
2440
  const store = storage2();
2588
- if (!store) return;
2441
+ clearInvalidatedSessions(store, address, chainId);
2442
+ let raw2 = null;
2589
2443
  try {
2590
- store.removeItem(keyFor(orderHash));
2444
+ raw2 = store?.getItem(key2) ?? null;
2591
2445
  } catch {
2446
+ raw2 = null;
2592
2447
  }
2593
- }
2594
- function listOrders(now = Date.now()) {
2448
+ if (raw2) {
2449
+ try {
2450
+ const parsed = JSON.parse(raw2);
2451
+ if (isValidSession(parsed)) return parsed.token;
2452
+ } catch {
2453
+ }
2454
+ memorySessions2.delete(key2);
2455
+ try {
2456
+ store?.removeItem(key2);
2457
+ } catch {
2458
+ }
2459
+ return null;
2460
+ }
2461
+ const cached = memorySessions2.get(key2);
2462
+ if (isValidSession(cached)) return cached.token;
2463
+ if (cached) memorySessions2.delete(key2);
2464
+ return null;
2465
+ };
2466
+ var writeYieldseekerSession = (address, chainId, token) => {
2467
+ if (typeof window === "undefined") return;
2468
+ const session = { token };
2469
+ if (!isValidSession(session)) return;
2470
+ const key2 = buildKey2(address, chainId);
2471
+ memorySessions2.set(key2, session);
2595
2472
  const store = storage2();
2596
- if (!store) return [];
2597
- const out = [];
2598
2473
  try {
2599
- const keys = [];
2600
- for (let i = 0; i < store.length; i++) {
2601
- const key2 = store.key(i);
2602
- if (key2?.startsWith(`${KEY_PREFIX2}.`)) keys.push(key2);
2474
+ store?.setItem(key2, JSON.stringify(session));
2475
+ } catch {
2476
+ }
2477
+ };
2478
+ var clearYieldseekerSession = (address, chainId) => {
2479
+ const key2 = buildKey2(address, chainId);
2480
+ memorySessions2.delete(key2);
2481
+ const store = storage2();
2482
+ clearInvalidatedSessions(store, address, chainId);
2483
+ try {
2484
+ store?.removeItem(key2);
2485
+ } catch {
2486
+ }
2487
+ };
2488
+
2489
+ // src/agents/yieldseeker/yieldseeker.auth.ts
2490
+ function resolveSiweOrigin(override) {
2491
+ const origin = override?.trim() || (typeof window !== "undefined" ? window.location?.origin : void 0);
2492
+ if (!origin || origin === "null") {
2493
+ throw new Error("Yieldseeker sign-in requires the requesting app's browser origin.");
2494
+ }
2495
+ const url = new URL(origin);
2496
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
2497
+ throw new Error("Yieldseeker sign-in requires an HTTP or HTTPS app origin.");
2498
+ }
2499
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
2500
+ throw new Error("Yieldseeker SIWE override must be an origin without credentials, path, query, or fragment.");
2501
+ }
2502
+ return url;
2503
+ }
2504
+ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
2505
+ const url = resolveSiweOrigin(dependencies.origin);
2506
+ return new import_siwe.SiweMessage({
2507
+ scheme: url.protocol.slice(0, -1),
2508
+ domain: url.host,
2509
+ address: (0, import_viem4.getAddress)(address),
2510
+ uri: url.origin,
2511
+ version: "1",
2512
+ chainId,
2513
+ nonce: (dependencies.nonce ?? import_siwe.generateNonce)(),
2514
+ issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
2515
+ }).prepareMessage();
2516
+ }
2517
+ function encodeYieldseekerAuthToken(token) {
2518
+ const bytes = new TextEncoder().encode(JSON.stringify(token));
2519
+ let binary = "";
2520
+ for (const byte of bytes) binary += String.fromCharCode(byte);
2521
+ return btoa(binary);
2522
+ }
2523
+ var YieldseekerAuth = class {
2524
+ constructor(dependencies = {}) {
2525
+ this.dependencies = dependencies;
2526
+ }
2527
+ dependencies;
2528
+ tokens = /* @__PURE__ */ new Map();
2529
+ pending = /* @__PURE__ */ new Map();
2530
+ scopes = /* @__PURE__ */ new Map();
2531
+ key(state, chainId) {
2532
+ return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
2533
+ }
2534
+ async getToken(state, chainId) {
2535
+ const key2 = this.key(state, chainId);
2536
+ const scope = { address: state.walletAddress, chainId };
2537
+ this.scopes.set(key2, scope);
2538
+ const cached = this.tokens.get(key2);
2539
+ if (cached) return cached;
2540
+ const persisted = readYieldseekerSession(scope.address, scope.chainId);
2541
+ if (persisted && this.matchesOrigin(persisted)) {
2542
+ this.tokens.set(key2, persisted);
2543
+ return persisted;
2544
+ }
2545
+ if (persisted) clearYieldseekerSession(scope.address, scope.chainId);
2546
+ const inFlight = this.pending.get(key2);
2547
+ if (inFlight) return inFlight;
2548
+ const request = this.sign(state, chainId).then((token) => {
2549
+ if (this.pending.get(key2) !== request) throw new Error("Wallet sign-in session changed. Please try again.");
2550
+ this.tokens.set(key2, token);
2551
+ writeYieldseekerSession(scope.address, scope.chainId, token);
2552
+ return token;
2553
+ });
2554
+ this.pending.set(key2, request);
2555
+ try {
2556
+ return await request;
2557
+ } finally {
2558
+ if (this.pending.get(key2) === request) this.pending.delete(key2);
2603
2559
  }
2604
- for (const key2 of keys) {
2605
- const raw = store.getItem(key2);
2606
- if (!raw) continue;
2607
- try {
2608
- const parsed = JSON.parse(raw);
2609
- if (now - parsed.createdAt > MAX_AGE_MS) {
2610
- store.removeItem(key2);
2611
- continue;
2612
- }
2613
- if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
2614
- out.push(parsed);
2615
- }
2616
- } catch {
2617
- store.removeItem(key2);
2560
+ }
2561
+ async refreshToken(state, chainId, rejectedToken) {
2562
+ const key2 = this.key(state, chainId);
2563
+ if (this.tokens.get(key2) === rejectedToken) {
2564
+ this.tokens.delete(key2);
2565
+ clearYieldseekerSession(state.walletAddress, chainId);
2566
+ }
2567
+ return this.getToken(state, chainId);
2568
+ }
2569
+ matchesOrigin(token) {
2570
+ try {
2571
+ const message = new import_siwe.SiweMessage(JSON.parse(atob(token)).message);
2572
+ const url = resolveSiweOrigin(this.dependencies.origin);
2573
+ return message.domain === url.host && message.uri === url.origin && message.scheme === url.protocol.slice(0, -1);
2574
+ } catch {
2575
+ return false;
2576
+ }
2577
+ }
2578
+ clear(state, chainId) {
2579
+ if (!state || chainId === void 0) {
2580
+ for (const scope of this.scopes.values()) {
2581
+ clearYieldseekerSession(scope.address, scope.chainId);
2618
2582
  }
2583
+ this.tokens.clear();
2584
+ this.pending.clear();
2585
+ this.scopes.clear();
2586
+ return;
2619
2587
  }
2588
+ const key2 = this.key(state, chainId);
2589
+ this.tokens.delete(key2);
2590
+ this.pending.delete(key2);
2591
+ this.scopes.delete(key2);
2592
+ clearYieldseekerSession(state.walletAddress, chainId);
2593
+ }
2594
+ async sign(state, chainId) {
2595
+ const account = (0, import_viem4.getAddress)(state.walletAddress);
2596
+ const publicClient = (0, import_viem4.createPublicClient)({
2597
+ chain: import_chains2.base,
2598
+ transport: (0, import_viem4.custom)(state.provider)
2599
+ });
2600
+ const walletClient = (0, import_viem4.createWalletClient)({
2601
+ account,
2602
+ chain: import_chains2.base,
2603
+ transport: (0, import_viem4.custom)(state.provider)
2604
+ });
2605
+ await ensureWalletOnChain(
2606
+ publicClient,
2607
+ walletClient,
2608
+ 8453
2609
+ );
2610
+ const message = createYieldseekerSiweMessage(
2611
+ account,
2612
+ chainId,
2613
+ this.dependencies
2614
+ );
2615
+ const signature = await walletClient.signMessage({ account, message });
2616
+ return encodeYieldseekerAuthToken({ message, signature });
2617
+ }
2618
+ };
2619
+
2620
+ // src/agents/yieldseeker/yieldseeker.identity-cache.ts
2621
+ var KEY_PREFIX3 = "owney.yieldseeker.identity.v1";
2622
+ var YIELDSEEKER_IDENTITY_TTL_MS = 24 * 60 * 60 * 1e3;
2623
+ var memoryIdentities = /* @__PURE__ */ new Map();
2624
+ var storage3 = () => {
2625
+ if (typeof window === "undefined") return null;
2626
+ try {
2627
+ return window.localStorage;
2628
+ } catch {
2629
+ return null;
2630
+ }
2631
+ };
2632
+ var keyFor = (walletAddress, chainId) => `${KEY_PREFIX3}:${walletAddress.toLowerCase()}:${chainId}`;
2633
+ function valid(value, walletAddress, chainId, now) {
2634
+ return Boolean(
2635
+ 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
2636
+ );
2637
+ }
2638
+ function readYieldseekerIdentity(walletAddress, chainId, now = Date.now()) {
2639
+ if (typeof window === "undefined") return null;
2640
+ const key2 = keyFor(walletAddress, chainId);
2641
+ const store = storage3();
2642
+ let parsed = null;
2643
+ try {
2644
+ const raw2 = store?.getItem(key2);
2645
+ parsed = raw2 ? JSON.parse(raw2) : null;
2646
+ } catch {
2647
+ parsed = null;
2648
+ }
2649
+ const candidate = parsed ?? memoryIdentities.get(key2);
2650
+ if (valid(candidate, walletAddress, chainId, now)) {
2651
+ memoryIdentities.set(key2, candidate);
2652
+ return { userId: candidate.userId };
2653
+ }
2654
+ memoryIdentities.delete(key2);
2655
+ try {
2656
+ store?.removeItem(key2);
2657
+ } catch {
2658
+ }
2659
+ return null;
2660
+ }
2661
+ function writeYieldseekerIdentity(walletAddress, chainId, userId, now = Date.now()) {
2662
+ if (typeof window === "undefined") return;
2663
+ const identity = {
2664
+ userId,
2665
+ walletAddress,
2666
+ chainId,
2667
+ expiresAt: now + YIELDSEEKER_IDENTITY_TTL_MS
2668
+ };
2669
+ if (!valid(identity, walletAddress, chainId, now)) return;
2670
+ const key2 = keyFor(walletAddress, chainId);
2671
+ memoryIdentities.set(key2, identity);
2672
+ try {
2673
+ storage3()?.setItem(key2, JSON.stringify(identity));
2674
+ } catch {
2675
+ }
2676
+ }
2677
+ function clearYieldseekerIdentity(walletAddress, chainId) {
2678
+ const key2 = keyFor(walletAddress, chainId);
2679
+ memoryIdentities.delete(key2);
2680
+ try {
2681
+ storage3()?.removeItem(key2);
2620
2682
  } catch {
2621
- return out;
2622
2683
  }
2623
- return out.sort((a, b) => b.createdAt - a.createdAt);
2624
2684
  }
2625
2685
 
2626
- // src/lib/swap/swap.executor.ts
2627
- var DEFAULT_SLIPPAGE = 1;
2628
- async function affordableAmount(deps, quoted) {
2629
- const balance = await deps.readSourceBalance();
2630
- if (balance >= quoted) return quoted;
2631
- debugLog("owney-sdk", "swap: trimming to the current source balance", {
2632
- quoted: quoted.toString(),
2633
- balance: balance.toString(),
2634
- short: (quoted - balance).toString()
2635
- });
2636
- return balance;
2686
+ // src/agents/yieldseeker/yieldseeker.client.ts
2687
+ var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2688
+ function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
2689
+ return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
2637
2690
  }
2638
- async function executeSwap(deps, options) {
2639
- const { quote, walletAddress, onStage } = options;
2640
- debugLog("owney-sdk", "swap: start", {
2641
- rail: quote.rail,
2642
- from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
2643
- to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
2644
- expected: quote.dst.amount,
2645
- floor: quote.dstAmountMin
2646
- });
2647
- const before = await deps.readTargetBalance();
2648
- debugLog("owney-sdk", "swap: target balance before", before.toString());
2649
- const result = quote.rail === "classic" ? await runClassic(deps, options) : await runFusion(deps, options, walletAddress);
2650
- const after = await deps.readTargetBalance();
2651
- const received = after - before;
2652
- debugLog("owney-sdk", "swap: target balance after", {
2653
- after: after.toString(),
2654
- received: received.toString()
2655
- });
2656
- if (received <= 0n) {
2657
- throw new OwneyError(
2658
- "SWAP_REQUEST_FAILED",
2659
- "The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
2660
- { rail: quote.rail, ...result }
2691
+ var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
2692
+ var YieldseekerApiError = class extends Error {
2693
+ constructor(status, providerCode, responseFields) {
2694
+ super(`Yieldseeker request failed (${status}): ${providerCode}`);
2695
+ this.status = status;
2696
+ this.providerCode = providerCode;
2697
+ this.responseFields = responseFields;
2698
+ this.name = "YieldseekerApiError";
2699
+ }
2700
+ status;
2701
+ providerCode;
2702
+ responseFields;
2703
+ get isAuthenticationError() {
2704
+ return this.status === 401 || this.status === 403;
2705
+ }
2706
+ };
2707
+ function providerError(body, fallback) {
2708
+ if (!body || typeof body !== "object") return { code: fallback };
2709
+ const record = body;
2710
+ return {
2711
+ code: typeof record.message === "string" ? record.message : fallback,
2712
+ fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
2713
+ };
2714
+ }
2715
+ var YieldseekerApiClient = class {
2716
+ constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch.bind(globalThis)) {
2717
+ this.owneyApiKey = owneyApiKey;
2718
+ this.baseUrl = baseUrl;
2719
+ this.fetchFn = fetchFn;
2720
+ }
2721
+ owneyApiKey;
2722
+ baseUrl;
2723
+ fetchFn;
2724
+ async request(path, options = {}) {
2725
+ const controller = new AbortController();
2726
+ const timer = setTimeout(
2727
+ () => controller.abort(),
2728
+ options.timeoutMs ?? 15e3
2661
2729
  );
2730
+ try {
2731
+ const response = await this.fetchFn(`${this.baseUrl}${path}`, {
2732
+ method: options.method ?? "GET",
2733
+ headers: {
2734
+ "Content-Type": "application/json",
2735
+ "x-owney-api-key": this.owneyApiKey,
2736
+ ...options.signature ? { Authorization: `Signature ${options.signature}` } : {}
2737
+ },
2738
+ body: options.body ? JSON.stringify(options.body) : void 0,
2739
+ signal: controller.signal
2740
+ });
2741
+ const payload = await response.json().catch(() => null);
2742
+ if (!response.ok) {
2743
+ const error = providerError(payload, `HTTP_${response.status}`);
2744
+ throw new YieldseekerApiError(
2745
+ response.status,
2746
+ error.code,
2747
+ error.fields
2748
+ );
2749
+ }
2750
+ if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
2751
+ return payload.data;
2752
+ }
2753
+ return payload;
2754
+ } catch (error) {
2755
+ if (error instanceof YieldseekerApiError) throw error;
2756
+ if (error instanceof DOMException && error.name === "AbortError") {
2757
+ throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
2758
+ }
2759
+ throw new YieldseekerApiError(0, "NETWORK_ERROR", {
2760
+ cause: error instanceof Error ? error.message : String(error)
2761
+ });
2762
+ } finally {
2763
+ clearTimeout(timer);
2764
+ }
2662
2765
  }
2663
- return { received: received.toString(), ...result };
2766
+ };
2767
+
2768
+ // src/agents/yieldseeker/yieldseeker.mapper.ts
2769
+ var import_viem5 = require("viem");
2770
+
2771
+ // src/lib/helpers/snapshot-apy.ts
2772
+ var DAY_MS = 864e5;
2773
+ function snapshotTime(date) {
2774
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
2775
+ const time = Date.parse(date);
2776
+ return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === date ? time : void 0;
2664
2777
  }
2665
- async function runClassic(deps, options) {
2666
- const {
2667
- quote,
2668
- walletAddress,
2669
- slippage = DEFAULT_SLIPPAGE,
2670
- direction,
2671
- onStage
2672
- } = options;
2673
- const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
2674
- const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
2675
- onStage?.("quoting");
2676
- debugLog("owney-sdk", "swap: fetching classic calldata");
2677
- const { tx } = await deps.api.swapTx({
2678
- from: {
2679
- chainId: quote.src.chainId,
2680
- symbol: quote.src.symbol,
2681
- amount: amount.toString()
2682
- },
2683
- to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
2778
+ function returnFactor(value) {
2779
+ if (typeof value !== "number" && typeof value !== "string") return void 0;
2780
+ if (typeof value === "string" && value.trim() === "") return void 0;
2781
+ const factor = Number(value);
2782
+ return Number.isFinite(factor) && factor >= 0 ? factor : void 0;
2783
+ }
2784
+ function calculateSnapshotApy(snapshots, lookbackDays, now = Date.now()) {
2785
+ if (!Number.isFinite(lookbackDays) || lookbackDays <= 0 || !Number.isFinite(now)) {
2786
+ return void 0;
2787
+ }
2788
+ const points = snapshots.flatMap((snapshot) => {
2789
+ const time = snapshotTime(snapshot.date);
2790
+ return time !== void 0 && time <= now ? [{ snapshot, time }] : [];
2791
+ }).sort((a, b) => a.time - b.time);
2792
+ const end = points.at(-1);
2793
+ if (!end) return void 0;
2794
+ const cutoff = end.time - lookbackDays * DAY_MS;
2795
+ const start = points.find((point) => point.time >= cutoff);
2796
+ const actualDays = (end.time - start.time) / DAY_MS;
2797
+ if (actualDays <= 0) return void 0;
2798
+ const startFactor = returnFactor(start.snapshot.cumulativeReturnFactor);
2799
+ const endFactor = returnFactor(end.snapshot.cumulativeReturnFactor);
2800
+ if (startFactor === void 0 || startFactor === 0 || endFactor === void 0) {
2801
+ return void 0;
2802
+ }
2803
+ const periodReturn = endFactor / startFactor - 1;
2804
+ const apy = Math.pow(1 + periodReturn, 365 / actualDays) - 1;
2805
+ return Number.isFinite(apy) ? apy : void 0;
2806
+ }
2807
+
2808
+ // src/agents/yieldseeker/yieldseeker.types.ts
2809
+ var YIELDSEEKER_ASSET_METADATA = {
2810
+ USDC: {
2811
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2812
+ decimals: 6
2813
+ },
2814
+ WETH: {
2815
+ address: "0x4200000000000000000000000000000000000006",
2816
+ decimals: 18
2817
+ }
2818
+ };
2819
+
2820
+ // src/agents/yieldseeker/yieldseeker.mapper.ts
2821
+ function invalid(endpoint, detail) {
2822
+ throw new OwneyError(
2823
+ "AGENT_INVALID_RESPONSE",
2824
+ `Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
2825
+ { endpoint, detail },
2826
+ "yieldseeker"
2827
+ );
2828
+ }
2829
+ function raw(value, endpoint) {
2830
+ if (typeof value !== "string" || !/^-?[0-9]+$/.test(value)) {
2831
+ return invalid(endpoint, "expected a base-10 integer string");
2832
+ }
2833
+ return BigInt(value);
2834
+ }
2835
+ function decimal(value, decimals, endpoint) {
2836
+ return (0, import_viem5.formatUnits)(raw(value, endpoint), decimals);
2837
+ }
2838
+ function usd(rawAmount, decimals, price) {
2839
+ return Number((0, import_viem5.formatUnits)(rawAmount, decimals)) * price;
2840
+ }
2841
+ function percent(value) {
2842
+ const result = Number(value);
2843
+ return Number.isFinite(result) ? result * 100 : 0;
2844
+ }
2845
+ var YIELDSEEKER_POSITIVE_YIELD_FEE_RATE = 0.1;
2846
+ function publicApyAfterYieldseekerFee(value) {
2847
+ const grossPercent = percent(value);
2848
+ if (grossPercent <= 0) return grossPercent;
2849
+ const netPercent = grossPercent * (1 - YIELDSEEKER_POSITIVE_YIELD_FEE_RATE);
2850
+ return Math.round(netPercent * 1e12) / 1e12;
2851
+ }
2852
+ function riskAdjustedApyForDays(option, days) {
2853
+ if (days === "7D") return option.riskAdjustedApy7dAverage;
2854
+ if (days === "30D") return option.riskAdjustedApy30dAverage;
2855
+ return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
2856
+ }
2857
+ function assetAddressValue(record, address) {
2858
+ const entry = Object.entries(record).find(
2859
+ ([key2]) => key2.toLowerCase() === address.toLowerCase()
2860
+ );
2861
+ return entry?.[1] ?? "0";
2862
+ }
2863
+ function position(value, asset, baseAssetDecimals) {
2864
+ const option = value?.yieldOption;
2865
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem5.isAddress)(option.address)) {
2866
+ return invalid("yield positions", "missing vault metadata");
2867
+ }
2868
+ return {
2869
+ chain: "BASE",
2870
+ protocol: option.provider,
2871
+ protocolId: option.address,
2872
+ pool: option.name,
2873
+ asset,
2874
+ // `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
2875
+ // differ from the underlying asset. Yieldseeker already converts it to
2876
+ // underlying base-asset units in `assetsBase`; pair that value with the
2877
+ // snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
2878
+ // share quantity separately because withdraw-from-position expects it.
2879
+ amount: decimal(
2880
+ value.assetsBase,
2881
+ baseAssetDecimals,
2882
+ "yield positions"
2883
+ ),
2884
+ amountRaw: String(value.assetsRaw),
2885
+ apy: percent(option.riskAdjustedApy),
2886
+ tvl: Number(option.totalDepositsUsd),
2887
+ liquidity: Number(option.withdrawableDepositsUsd)
2888
+ };
2889
+ }
2890
+ function mapYieldseekerBalances(contexts) {
2891
+ const tokens = [];
2892
+ const assetBalances = [];
2893
+ const positions = [];
2894
+ let totalUsd = 0;
2895
+ for (const context of contexts) {
2896
+ const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
2897
+ assetBalances.push({
2898
+ chain: "BASE",
2899
+ chainId: 8453,
2900
+ asset: context.asset,
2901
+ amount: decimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
2902
+ });
2903
+ const idle = assetAddressValue(
2904
+ context.snapshot.tokenBalances,
2905
+ metadata.address
2906
+ );
2907
+ tokens.push({
2908
+ chain: "BASE",
2909
+ chainId: 8453,
2910
+ asset: context.asset,
2911
+ amount: decimal(idle, metadata.decimals, "snapshot")
2912
+ });
2913
+ positions.push(
2914
+ ...context.positions.map(
2915
+ (entry) => position(
2916
+ entry,
2917
+ context.asset,
2918
+ context.snapshot.baseAssetDecimals
2919
+ )
2920
+ )
2921
+ );
2922
+ totalUsd += usd(
2923
+ raw(context.snapshot.totalValueBase, "snapshot"),
2924
+ context.snapshot.baseAssetDecimals,
2925
+ context.snapshot.baseAssetPriceUsd
2926
+ );
2927
+ }
2928
+ return {
2929
+ ...contexts.length === 1 ? { smartWallet: contexts[0].wallet.walletAddress } : {},
2930
+ totalBalance: String(totalUsd),
2931
+ totalBalanceAsset: "usdc",
2932
+ assetBalances,
2933
+ tokens,
2934
+ positions
2935
+ };
2936
+ }
2937
+ function mapYieldseekerEarnings(contexts) {
2938
+ const tokens = [];
2939
+ let lifetimeEarnings = 0;
2940
+ for (const context of contexts) {
2941
+ const amount = raw(context.snapshot.cumulativeYieldBase, "snapshot");
2942
+ tokens.push({
2943
+ chain: "BASE",
2944
+ chainId: 8453,
2945
+ asset: context.asset,
2946
+ amount: (0, import_viem5.formatUnits)(amount, context.snapshot.baseAssetDecimals)
2947
+ });
2948
+ lifetimeEarnings += usd(
2949
+ amount,
2950
+ context.snapshot.baseAssetDecimals,
2951
+ context.snapshot.baseAssetPriceUsd
2952
+ );
2953
+ }
2954
+ return {
2955
+ smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
2956
+ lifetimeEarnings,
2957
+ tokens
2958
+ };
2959
+ }
2960
+ function apyForDays(context, days, now) {
2961
+ if (days === "7D") return percent(context.snapshot.apy7d);
2962
+ if (days === "30D") return percent(context.snapshot.apy30d);
2963
+ const apy = calculateSnapshotApy(context.historic?.dailyYieldSnapshots ?? [], 14, now);
2964
+ const apyPercent = apy === void 0 ? void 0 : apy * 100;
2965
+ return apyPercent !== void 0 && Number.isFinite(apyPercent) ? apyPercent : void 0;
2966
+ }
2967
+ function dailyApy(point) {
2968
+ const total = raw(point.totalValueBase, "historic position");
2969
+ const earned = raw(point.dailyYieldBase, "historic position");
2970
+ const principal = total - earned;
2971
+ if (principal <= 0n || earned === 0n) return 0;
2972
+ return Number(earned) / Number(principal) * 365 * 100;
2973
+ }
2974
+ function aggregateHistory(contexts, dayCount, now) {
2975
+ const today = new Date(now).toISOString().slice(0, 10);
2976
+ const cutoff = new Date(Date.parse(today) - (dayCount - 1) * 864e5).toISOString().slice(0, 10);
2977
+ const assets = new Set(contexts.map((context) => context.asset.toUpperCase()));
2978
+ const unit = assets.size === 1 ? [...assets][0] : "USD";
2979
+ const byDate = /* @__PURE__ */ new Map();
2980
+ for (const context of contexts) {
2981
+ const points = context.historic?.dailyYieldSnapshots ?? [];
2982
+ for (const point of points) {
2983
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(point.date) || !Number.isFinite(Date.parse(point.date)) || point.date < cutoff || point.date > today) continue;
2984
+ const tokens = Number(decimal(point.totalValueBase, point.baseAssetDecimals, "historic position"));
2985
+ const amount = assets.size === 1 ? tokens : tokens * point.baseAssetPriceUsd;
2986
+ if (!Number.isFinite(amount) || amount < 0) {
2987
+ invalid("historic position", "expected a finite non-negative balance");
2988
+ }
2989
+ const current = byDate.get(point.date) ?? { weighted: 0, amount: 0 };
2990
+ current.weighted += dailyApy(point) * amount;
2991
+ current.amount += amount;
2992
+ byDate.set(point.date, current);
2993
+ }
2994
+ }
2995
+ return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
2996
+ date,
2997
+ apy: value.amount > 0 ? value.weighted / value.amount : 0,
2998
+ historicalBalance: { amount: value.amount, unit }
2999
+ }));
3000
+ }
3001
+ function mapYieldseekerApy(walletAddress, contexts, days, now = Date.now()) {
3002
+ let weighted = 0;
3003
+ let totalUsd = 0;
3004
+ const byAsset = {};
3005
+ for (const context of contexts) {
3006
+ const valueUsd = usd(
3007
+ raw(context.snapshot.totalValueBase, "snapshot"),
3008
+ context.snapshot.baseAssetDecimals,
3009
+ context.snapshot.baseAssetPriceUsd
3010
+ );
3011
+ const apy = apyForDays(context, days, now);
3012
+ if (apy === void 0) continue;
3013
+ weighted += apy * valueUsd;
3014
+ totalUsd += valueUsd;
3015
+ byAsset[context.asset] = apy;
3016
+ }
3017
+ const dayCount = Number(days.slice(0, -1));
3018
+ return {
2684
3019
  walletAddress,
2685
- slippage,
2686
- ...direction ? { direction } : {}
2687
- });
2688
- const isNative = BigInt(tx.value ?? "0") > 0n;
2689
- if (!isNative) {
2690
- const needed = amount;
2691
- const current = await deps.readAllowance(tx.to);
2692
- debugLog("owney-sdk", "swap: allowance", {
2693
- spender: tx.to,
2694
- current: current.toString(),
2695
- needed: needed.toString()
3020
+ ...days !== "14D" || Object.keys(byAsset).length > 0 ? { weightedApyAfterFee: totalUsd > 0 ? weighted / totalUsd : 0 } : {},
3021
+ apyByChainAndAsset: { 8453: byAsset },
3022
+ history: aggregateHistory(contexts, dayCount, now)
3023
+ };
3024
+ }
3025
+ function actionType(value) {
3026
+ const normalized = value.toLowerCase();
3027
+ if (normalized.includes("deposit")) return "Deposit";
3028
+ if (normalized.includes("withdraw")) return "Withdraw";
3029
+ if (normalized.includes("yield") || normalized.includes("earn"))
3030
+ return "Earned";
3031
+ return "Rebalance";
3032
+ }
3033
+ function transactionHashes(details) {
3034
+ if (!details) return [];
3035
+ const values = [
3036
+ details.transactionHash,
3037
+ details.txHash,
3038
+ ...Array.isArray(details.transactionHashes) ? details.transactionHashes : [],
3039
+ ...Array.isArray(details.txHashes) ? details.txHashes : []
3040
+ ];
3041
+ return values.filter(
3042
+ (value) => typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value)
3043
+ ).filter((value, index, all) => all.indexOf(value) === index);
3044
+ }
3045
+ function actionEntry(action) {
3046
+ if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
3047
+ return {
3048
+ agent: "yieldseeker",
3049
+ action: actionType(action.actionType),
3050
+ date: action.createdDate,
3051
+ oldApy: null,
3052
+ newApy: null,
3053
+ transactions: [
3054
+ {
3055
+ txHashes: transactionHashes(action.details),
3056
+ chainId: 8453
3057
+ }
3058
+ ],
3059
+ rebalanceLog: []
3060
+ };
3061
+ }
3062
+ function depositDestination(context, movement) {
3063
+ const to = movement.toAddress.toLowerCase();
3064
+ const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
3065
+ if (option) return { protocol: option.provider || "Vault", pool: option.name, tokenSymbol: context.asset };
3066
+ const receipt = context.historic?.assets.find((asset) => asset.chainId === movement.chainId && !asset.isSpam && asset.address.toLowerCase() === to && asset.address.toLowerCase() !== context.agent.assetAddress.toLowerCase());
3067
+ 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);
3068
+ if (receipt && minted) return { protocol: "Vault", pool: receipt.name || receipt.symbol, tokenSymbol: context.asset };
3069
+ return void 0;
3070
+ }
3071
+ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses, destination) {
3072
+ const from = movement.fromAddress.toLowerCase();
3073
+ const to = movement.toAddress.toLowerCase();
3074
+ const owner = ownerAddress.toLowerCase();
3075
+ const agentWallet = wallet.walletAddress.toLowerCase();
3076
+ const baseAsset = agent.assetAddress.toLowerCase();
3077
+ if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
3078
+ return void 0;
3079
+ }
3080
+ let action;
3081
+ if (to === agentWallet && !vaultAddresses.has(from)) {
3082
+ action = "Top up";
3083
+ } else if (from === agentWallet && to === owner) {
3084
+ action = "Withdraw";
3085
+ } else if (from === agentWallet && destination) {
3086
+ action = "Deposit";
3087
+ }
3088
+ if (!action) return void 0;
3089
+ return {
3090
+ agent: "yieldseeker",
3091
+ action,
3092
+ ...action === "Deposit" && destination ? { positions: [{
3093
+ ...destination,
3094
+ amount: decimal(movement.assetAmount, YIELDSEEKER_ASSET_METADATA[asset].decimals, "historic position")
3095
+ }] } : {},
3096
+ date: movement.blockDate,
3097
+ oldApy: null,
3098
+ newApy: null,
3099
+ transactions: [
3100
+ {
3101
+ txHashes: [movement.transactionHash],
3102
+ chainId: agent.chainId,
3103
+ tokenSymbol: asset,
3104
+ amount: decimal(
3105
+ movement.assetAmount,
3106
+ YIELDSEEKER_ASSET_METADATA[asset].decimals,
3107
+ "historic position"
3108
+ )
3109
+ }
3110
+ ],
3111
+ rebalanceLog: []
3112
+ };
3113
+ }
3114
+ function mapYieldseekerHistory(contexts, options) {
3115
+ const entries = contexts.flatMap((context) => {
3116
+ const seenMovements = /* @__PURE__ */ new Set();
3117
+ const movements = (context.historic?.movements ?? []).filter((movement) => {
3118
+ const key2 = `${movement.chainId}:${movement.transactionHash.toLowerCase()}:${movement.logIndex}`;
3119
+ if (seenMovements.has(key2)) return false;
3120
+ seenMovements.add(key2);
3121
+ return true;
2696
3122
  });
2697
- if (current < needed) {
2698
- onStage?.("approving");
2699
- await deps.ensureChain(quote.src.chainId);
2700
- await deps.approve(tx.to, MAX_UINT256);
2701
- }
2702
- }
2703
- onStage?.("signing");
2704
- await deps.ensureChain(quote.src.chainId);
2705
- debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
2706
- const txHash = await deps.sendTransaction({
2707
- to: tx.to,
2708
- data: tx.data,
2709
- value: tx.value ?? "0"
3123
+ return [
3124
+ ...movements.map(
3125
+ (movement) => movementEntry(
3126
+ movement,
3127
+ context.wallet,
3128
+ context.agent,
3129
+ context.asset,
3130
+ options.ownerAddress,
3131
+ options.vaultAddresses,
3132
+ depositDestination(context, movement)
3133
+ )
3134
+ ),
3135
+ ...(context.actions ?? []).map(actionEntry)
3136
+ ].filter((entry) => entry !== void 0);
2710
3137
  });
2711
- onStage?.("swapped");
2712
- return { txHash };
3138
+ const grouped = /* @__PURE__ */ new Map();
3139
+ const ungrouped = [];
3140
+ for (const entry of entries) {
3141
+ const tx = entry.transactions[0];
3142
+ const hash = tx?.txHashes[0];
3143
+ if (!hash) {
3144
+ ungrouped.push(entry);
3145
+ continue;
3146
+ }
3147
+ const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}`;
3148
+ const previous = grouped.get(key2);
3149
+ if (!previous) {
3150
+ grouped.set(key2, entry);
3151
+ continue;
3152
+ }
3153
+ if (entry.action === "Deposit" && entry.positions?.length) {
3154
+ if (!previous.positions?.length) {
3155
+ grouped.set(key2, entry);
3156
+ continue;
3157
+ }
3158
+ previous.positions.push(...entry.positions);
3159
+ previous.transactions.push(...entry.transactions);
3160
+ }
3161
+ }
3162
+ 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));
3163
+ return {
3164
+ data: filtered.slice(0, options.limit),
3165
+ // v1 returns the whole action/movement collection and defines no cursor.
3166
+ // Report a terminal page so callers never loop over the same prefix.
3167
+ hasMore: false
3168
+ };
3169
+ }
3170
+ function mapYieldseekerProfile(address, contexts) {
3171
+ const protocols = /* @__PURE__ */ new Set();
3172
+ for (const context of contexts) {
3173
+ for (const current of context.positions) {
3174
+ if (current.yieldOption?.provider) {
3175
+ protocols.add(String(current.yieldOption.provider));
3176
+ }
3177
+ }
3178
+ }
3179
+ return {
3180
+ address,
3181
+ smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
3182
+ chains: contexts.length > 0 ? [8453] : [],
3183
+ hasActiveSessionKey: contexts.some(
3184
+ (context) => context.wallet.initializedDate != null
3185
+ ),
3186
+ protocols: [...protocols]
3187
+ };
3188
+ }
3189
+ function mapYieldseekerAgentApy(options, days) {
3190
+ const perAsset = {};
3191
+ const all = [];
3192
+ for (const entry of options) {
3193
+ const apys = entry.yieldOptions.map(
3194
+ (option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
3195
+ ).filter(Number.isFinite);
3196
+ if (apys.length === 0) continue;
3197
+ const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
3198
+ perAsset[entry.asset] = average;
3199
+ all.push(average);
3200
+ }
3201
+ return {
3202
+ averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
3203
+ detailedApys: { apyPerAsset: { 8453: perAsset } }
3204
+ };
3205
+ }
3206
+
3207
+ // src/agents/yieldseeker/yieldseeker.agent.ts
3208
+ var OWNEY_AGENT_NAME = "owney";
3209
+ var YIELDSEEKER_USERNAME_PREFIX = "owney_";
3210
+ var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
3211
+ var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
3212
+ var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
3213
+ function generateYieldseekerUsername() {
3214
+ const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
3215
+ return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
3216
+ }
3217
+ function isUsernameConflict(error) {
3218
+ if (!(error instanceof YieldseekerApiError)) return false;
3219
+ const code = error.providerCode.toUpperCase();
3220
+ return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
3221
+ }
3222
+ var YIELDSEEKER_AGENT_WALLET_ABI = [
3223
+ {
3224
+ type: "function",
3225
+ name: "withdrawAssetToUser",
3226
+ stateMutability: "nonpayable",
3227
+ inputs: [
3228
+ { name: "recipient", type: "address" },
3229
+ { name: "asset", type: "address" },
3230
+ { name: "amount", type: "uint256" }
3231
+ ],
3232
+ outputs: []
3233
+ },
3234
+ {
3235
+ type: "function",
3236
+ name: "withdrawAllAssetToUser",
3237
+ stateMutability: "nonpayable",
3238
+ inputs: [
3239
+ { name: "recipient", type: "address" },
3240
+ { name: "asset", type: "address" }
3241
+ ],
3242
+ outputs: []
3243
+ }
3244
+ ];
3245
+ function query(params) {
3246
+ const search = new URLSearchParams();
3247
+ for (const [key2, value] of Object.entries(params)) {
3248
+ if (value !== void 0) search.set(key2, String(value));
3249
+ }
3250
+ const encoded = search.toString();
3251
+ return encoded ? `?${encoded}` : "";
2713
3252
  }
2714
- async function runFusion(deps, options, walletAddress) {
2715
- const { quote, direction, onStage } = options;
2716
- const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
2717
- const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
2718
- if (quote.spender && !isNativeSource) {
2719
- const needed = amount;
2720
- const current = await deps.readAllowance(quote.spender);
2721
- debugLog("owney-sdk", "swap: fusion allowance", {
2722
- spender: quote.spender,
2723
- current: current.toString(),
2724
- needed: needed.toString()
3253
+ var YieldseekerAgent = class {
3254
+ id = "yieldseeker";
3255
+ balanceComposition = "tokens-plus-positions";
3256
+ supportedChainIds = [8453];
3257
+ supportedAssets = [
3258
+ {
3259
+ chainId: 8453,
3260
+ chain: "BASE",
3261
+ assets: [
3262
+ { symbol: "USDC", minDepositAmount: "10000000" },
3263
+ { symbol: "WETH", minDepositAmount: "1" }
3264
+ ]
3265
+ }
3266
+ ];
3267
+ api;
3268
+ auth;
3269
+ transactionExecutor;
3270
+ unwindReceiptWaiter;
3271
+ agentContexts = /* @__PURE__ */ new Map();
3272
+ users = /* @__PURE__ */ new Map();
3273
+ pendingAgents = /* @__PURE__ */ new Map();
3274
+ yieldOptions = /* @__PURE__ */ new Map();
3275
+ pendingYieldOptions = /* @__PURE__ */ new Map();
3276
+ constructor(owneyApiKey, options = {}) {
3277
+ this.api = new YieldseekerApiClient(
3278
+ owneyApiKey,
3279
+ options.baseUrl ?? getYieldseekerProxyBaseUrl(),
3280
+ options.fetchFn
3281
+ );
3282
+ this.auth = new YieldseekerAuth(options.auth);
3283
+ this.transactionExecutor = options.transactionExecutor;
3284
+ this.unwindReceiptWaiter = options.unwindReceiptWaiter;
3285
+ }
3286
+ async disconnect() {
3287
+ this.auth.clear();
3288
+ for (const key2 of this.users.keys()) {
3289
+ const [walletAddress, chainId] = key2.split(":");
3290
+ clearYieldseekerIdentity(walletAddress, Number(chainId));
3291
+ }
3292
+ this.users.clear();
3293
+ this.agentContexts.clear();
3294
+ this.pendingAgents.clear();
3295
+ }
3296
+ async activateAgent(state, chainId, asset) {
3297
+ this.assertChain(chainId);
3298
+ const targetAsset = asset ?? "USDC";
3299
+ this.assertAsset(targetAsset);
3300
+ await this.ensureAgent(state, chainId, targetAsset);
3301
+ }
3302
+ async deposit(state, chainId, amount, asset, depositCallback) {
3303
+ this.assertChain(chainId);
3304
+ this.assertAsset(asset);
3305
+ if (BigInt(amount) <= 0n) {
3306
+ throw new OwneyError(
3307
+ "DEPOSIT_AMOUNT_BELOW_MINIMUM",
3308
+ "Yieldseeker deposits must be greater than zero.",
3309
+ { amount, minDepositAmount: "1" },
3310
+ this.id
3311
+ );
3312
+ }
3313
+ const context = await this.ensureAgent(state, chainId, asset);
3314
+ let txHash;
3315
+ try {
3316
+ if (depositCallback) {
3317
+ provideDepositVerificationContext(depositCallback, {
3318
+ agentId: "yieldseeker",
3319
+ signature: await this.auth.getToken(state, chainId),
3320
+ userId: context.user.userId,
3321
+ yieldseekerAgentId: context.agent.agentId
3322
+ });
3323
+ txHash = await depositCallback(
3324
+ context.wallet.walletAddress,
3325
+ chainId,
3326
+ amount
3327
+ );
3328
+ await this.waitForReceipt(state, chainId, txHash);
3329
+ } else {
3330
+ txHash = await this.submitTransaction(state, chainId, {
3331
+ from: (0, import_viem6.getAddress)(state.walletAddress),
3332
+ to: YIELDSEEKER_ASSET_METADATA[asset].address,
3333
+ data: (0, import_viem6.encodeFunctionData)({
3334
+ abi: import_viem6.erc20Abi,
3335
+ functionName: "transfer",
3336
+ args: [(0, import_viem6.getAddress)(context.wallet.walletAddress), BigInt(amount)]
3337
+ }),
3338
+ value: "0",
3339
+ chainId
3340
+ });
3341
+ }
3342
+ } finally {
3343
+ await this.refreshSnapshotAfterMovement(
3344
+ state,
3345
+ chainId,
3346
+ context,
3347
+ "deposit"
3348
+ );
3349
+ }
3350
+ return {
3351
+ txHash,
3352
+ smartWallet: context.wallet.walletAddress,
3353
+ amount
3354
+ };
3355
+ }
3356
+ async withdraw(state, chainId, asset, amount) {
3357
+ this.assertChain(chainId);
3358
+ this.assertAsset(asset);
3359
+ if (amount !== void 0 && BigInt(amount) <= 0n) {
3360
+ throw new OwneyError(
3361
+ "WITHDRAW_FAILED",
3362
+ "Yieldseeker withdrawals must be greater than zero.",
3363
+ { amount },
3364
+ this.id
3365
+ );
3366
+ }
3367
+ const context = await this.findAgent(state, chainId, asset);
3368
+ if (!context) {
3369
+ throw new OwneyError(
3370
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3371
+ `No Yieldseeker ${asset} agent exists for this wallet.`,
3372
+ { asset, available: "0" },
3373
+ this.id
3374
+ );
3375
+ }
3376
+ try {
3377
+ const portfolio = await this.loadPortfolioContext(
3378
+ state,
3379
+ chainId,
3380
+ context
3381
+ );
3382
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3383
+ const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
3384
+ ([address]) => address.toLowerCase() === metadata.address.toLowerCase()
3385
+ );
3386
+ const idle = BigInt(idleEntry?.[1] ?? "0");
3387
+ const deployed = portfolio.positions.reduce(
3388
+ (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
3389
+ 0n
3390
+ );
3391
+ const totalAvailable = idle + deployed;
3392
+ const requested = amount === void 0 ? totalAvailable : BigInt(amount);
3393
+ if (requested > totalAvailable) {
3394
+ throw new OwneyError(
3395
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3396
+ `Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
3397
+ {
3398
+ asset,
3399
+ requested: requested.toString(),
3400
+ available: totalAvailable.toString()
3401
+ },
3402
+ this.id
3403
+ );
3404
+ }
3405
+ let remaining = requested > idle ? requested - idle : 0n;
3406
+ for (const position2 of portfolio.positions) {
3407
+ if (remaining === 0n) break;
3408
+ const available = BigInt(position2.withdrawableAssetsRaw);
3409
+ if (available <= 0n) continue;
3410
+ const assetsRaw = available < remaining ? available : remaining;
3411
+ const response = await this.walletRequest(
3412
+ state,
3413
+ chainId,
3414
+ this.agentPath(context, "withdraw-from-position"),
3415
+ {
3416
+ method: "POST",
3417
+ body: {
3418
+ chainId,
3419
+ vaultAddress: position2.yieldOption.address,
3420
+ assetsRaw: assetsRaw.toString()
3421
+ }
3422
+ }
3423
+ );
3424
+ if (!this.isTransactionHash(response?.transactionHash)) {
3425
+ throw this.invalidResponse("position withdrawal");
3426
+ }
3427
+ await this.waitForReceipt(state, chainId, response.transactionHash);
3428
+ remaining -= assetsRaw;
3429
+ }
3430
+ if (remaining > 0n) {
3431
+ throw this.invalidResponse("yield positions", {
3432
+ reason: "Withdrawable positions could not cover the request.",
3433
+ remaining: remaining.toString()
3434
+ });
3435
+ }
3436
+ const account = (0, import_viem6.getAddress)(state.walletAddress);
3437
+ const txHash = await this.submitTransaction(state, chainId, {
3438
+ from: account,
3439
+ to: (0, import_viem6.getAddress)(context.wallet.walletAddress),
3440
+ data: amount === void 0 ? (0, import_viem6.encodeFunctionData)({
3441
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
3442
+ functionName: "withdrawAllAssetToUser",
3443
+ args: [account, metadata.address]
3444
+ }) : (0, import_viem6.encodeFunctionData)({
3445
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
3446
+ functionName: "withdrawAssetToUser",
3447
+ args: [account, metadata.address, requested]
3448
+ }),
3449
+ value: "0",
3450
+ chainId
3451
+ });
3452
+ return {
3453
+ txHash,
3454
+ type: amount === void 0 ? "full" : "partial",
3455
+ amount: requested.toString()
3456
+ };
3457
+ } finally {
3458
+ await this.refreshSnapshotAfterMovement(
3459
+ state,
3460
+ chainId,
3461
+ context,
3462
+ "withdrawal"
3463
+ );
3464
+ }
3465
+ }
3466
+ async getBalances(state, chainId) {
3467
+ this.assertChain(chainId);
3468
+ return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
3469
+ }
3470
+ async getEarnings(state, chainId) {
3471
+ this.assertChain(chainId);
3472
+ return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
3473
+ }
3474
+ async getAccountApy(state, chainId, days, tokenSymbol) {
3475
+ this.assertChain(chainId);
3476
+ const asset = tokenSymbol?.toUpperCase();
3477
+ if (asset !== void 0) this.assertAsset(asset);
3478
+ const contexts = await this.loadPortfolio(state, chainId, {
3479
+ ...asset ? { asset } : {},
3480
+ historic: true
3481
+ });
3482
+ return mapYieldseekerApy(state.walletAddress, contexts, days);
3483
+ }
3484
+ async getHistory(state, chainId, options) {
3485
+ this.assertChain(chainId);
3486
+ const asset = options?.tokenSymbol?.toUpperCase();
3487
+ if (asset !== void 0) this.assertAsset(asset);
3488
+ const contexts = await this.loadPortfolio(state, chainId, {
3489
+ ...asset ? { asset } : {},
3490
+ historic: true,
3491
+ actions: true
3492
+ });
3493
+ const catalog = await Promise.all(
3494
+ [...new Set(contexts.map((context) => context.asset))].map(
3495
+ (contextAsset) => this.loadYieldOptions(contextAsset)
3496
+ )
3497
+ );
3498
+ const vaultAddresses = new Set(
3499
+ catalog.flat().filter(
3500
+ (yieldOption) => yieldOption.chainId === chainId && (0, import_viem6.isAddress)(yieldOption.address)
3501
+ ).map((yieldOption) => yieldOption.address.toLowerCase())
3502
+ );
3503
+ return mapYieldseekerHistory(contexts, {
3504
+ limit: options?.limit ?? 10,
3505
+ ownerAddress: state.walletAddress,
3506
+ vaultAddresses,
3507
+ ...options?.fromDate ? { fromDate: options.fromDate } : {},
3508
+ ...options?.toDate ? { toDate: options.toDate } : {}
3509
+ });
3510
+ }
3511
+ async getUserProfile(state, chainId) {
3512
+ this.assertChain(chainId);
3513
+ return mapYieldseekerProfile(
3514
+ state.walletAddress,
3515
+ await this.loadPortfolio(state, chainId, {})
3516
+ );
3517
+ }
3518
+ async getAgentApy(days, options) {
3519
+ this.assertOptionalChain(options?.chainId);
3520
+ const requested = options?.tokenSymbol?.toUpperCase();
3521
+ if (requested !== void 0) this.assertAsset(requested);
3522
+ const assets = requested ? [requested] : ["USDC", "WETH"];
3523
+ const values = await Promise.all(
3524
+ assets.map(async (asset) => {
3525
+ return { asset, yieldOptions: await this.loadYieldOptions(asset) };
3526
+ })
3527
+ );
3528
+ return mapYieldseekerAgentApy(values, days);
3529
+ }
3530
+ async loadYieldOptions(asset) {
3531
+ const cached = this.yieldOptions.get(asset);
3532
+ if (cached && cached.expiresAt > Date.now()) return cached.value;
3533
+ const pending = this.pendingYieldOptions.get(asset);
3534
+ if (pending) return pending;
3535
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3536
+ const request = this.api.request(
3537
+ `/chains/8453/assets/${metadata.address}/yield-options`
3538
+ ).then((response) => {
3539
+ if (!Array.isArray(response?.yieldOptions)) {
3540
+ throw this.invalidResponse("yield options");
3541
+ }
3542
+ this.yieldOptions.set(asset, {
3543
+ expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
3544
+ value: response.yieldOptions
3545
+ });
3546
+ return response.yieldOptions;
3547
+ }).finally(() => this.pendingYieldOptions.delete(asset));
3548
+ this.pendingYieldOptions.set(asset, request);
3549
+ return request;
3550
+ }
3551
+ userKey(state, chainId) {
3552
+ return `${state.walletAddress.toLowerCase()}:${chainId}`;
3553
+ }
3554
+ contextKey(state, chainId, asset) {
3555
+ return `${this.userKey(state, chainId)}:${asset}`;
3556
+ }
3557
+ async resolveUser(state, chainId) {
3558
+ const key2 = this.userKey(state, chainId);
3559
+ const inMemory = this.users.get(key2);
3560
+ if (inMemory) return inMemory;
3561
+ const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
3562
+ if (persisted) {
3563
+ this.users.set(key2, persisted);
3564
+ return persisted;
3565
+ }
3566
+ const walletAddress = (0, import_viem6.getAddress)(state.walletAddress);
3567
+ let user = null;
3568
+ try {
3569
+ const login = await this.providerRequest(
3570
+ state,
3571
+ chainId,
3572
+ "/users/login-with-wallet",
3573
+ { method: "POST", body: { walletAddress } }
3574
+ );
3575
+ user = login?.user ?? null;
3576
+ if (!user) {
3577
+ throw this.invalidResponse("wallet login", {
3578
+ reason: "A successful login returned no user."
3579
+ });
3580
+ }
3581
+ } catch (error) {
3582
+ if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
3583
+ if (error instanceof OwneyError) throw error;
3584
+ throw this.mapApiError(error);
3585
+ }
3586
+ let created;
3587
+ for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
3588
+ try {
3589
+ created = await this.providerRequest(
3590
+ state,
3591
+ chainId,
3592
+ "/users",
3593
+ {
3594
+ method: "POST",
3595
+ body: {
3596
+ walletAddress,
3597
+ username: generateYieldseekerUsername()
3598
+ }
3599
+ }
3600
+ );
3601
+ break;
3602
+ } catch (createError) {
3603
+ const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
3604
+ if (canRetry) continue;
3605
+ throw this.mapApiError(createError);
3606
+ }
3607
+ }
3608
+ user = created?.user ?? null;
3609
+ }
3610
+ if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
3611
+ throw this.invalidResponse("wallet identity");
3612
+ }
3613
+ const resolved = { userId: user.userId };
3614
+ this.users.set(key2, resolved);
3615
+ writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
3616
+ return resolved;
3617
+ }
3618
+ forgetUser(state, chainId) {
3619
+ this.users.delete(this.userKey(state, chainId));
3620
+ clearYieldseekerIdentity(state.walletAddress, chainId);
3621
+ }
3622
+ async ensureAgent(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 pending = this.pendingAgents.get(key2);
3627
+ if (pending) return pending;
3628
+ const request = this.resolveAgent(state, chainId, asset, true).then(
3629
+ async (context) => {
3630
+ if (!context) throw this.invalidResponse("agent creation");
3631
+ await this.deployAgent(state, chainId, context);
3632
+ this.agentContexts.set(key2, context);
3633
+ return context;
3634
+ }
3635
+ );
3636
+ this.pendingAgents.set(key2, request);
3637
+ try {
3638
+ return await request;
3639
+ } finally {
3640
+ this.pendingAgents.delete(key2);
3641
+ }
3642
+ }
3643
+ async findAgent(state, chainId, asset) {
3644
+ const key2 = this.contextKey(state, chainId, asset);
3645
+ const cached = this.agentContexts.get(key2);
3646
+ if (cached) return cached;
3647
+ const context = await this.resolveAgent(state, chainId, asset, false);
3648
+ if (context) this.agentContexts.set(key2, context);
3649
+ return context;
3650
+ }
3651
+ async resolveAgent(state, chainId, asset, createIfMissing) {
3652
+ const user = await this.resolveUser(state, chainId);
3653
+ const response = await this.walletRequest(
3654
+ state,
3655
+ chainId,
3656
+ `/users/${user.userId}/agents`
3657
+ );
3658
+ if (!Array.isArray(response?.agents)) {
3659
+ throw this.invalidResponse("agent list");
3660
+ }
3661
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3662
+ let agent = response.agents.find(
3663
+ (candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
3664
+ );
3665
+ if (!agent && createIfMissing) {
3666
+ const created = await this.walletRequest(
3667
+ state,
3668
+ chainId,
3669
+ `/users/${user.userId}/agents`,
3670
+ {
3671
+ method: "POST",
3672
+ body: {
3673
+ name: OWNEY_AGENT_NAME,
3674
+ emoji: "\u{1F989}",
3675
+ chainId,
3676
+ assetAddress: metadata.address,
3677
+ type: "vault",
3678
+ rulePreset: null
3679
+ }
3680
+ }
3681
+ );
3682
+ agent = created?.agent;
3683
+ }
3684
+ if (!agent) return null;
3685
+ this.assertAgent(agent);
3686
+ const walletResponse = await this.walletRequest(
3687
+ state,
3688
+ chainId,
3689
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3690
+ );
3691
+ if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
3692
+ throw this.invalidResponse("agent wallet");
3693
+ }
3694
+ return { user, agent, wallet: walletResponse.agentWallet, asset };
3695
+ }
3696
+ async loadPortfolio(state, chainId, options) {
3697
+ const user = await this.resolveUser(state, chainId);
3698
+ const response = await this.walletRequest(
3699
+ state,
3700
+ chainId,
3701
+ `/users/${user.userId}/agents`
3702
+ );
3703
+ if (!Array.isArray(response?.agents)) {
3704
+ throw this.invalidResponse("agent list");
3705
+ }
3706
+ const contexts = [];
3707
+ for (const agent of response.agents) {
3708
+ const asset = this.assetForAgent(agent);
3709
+ if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
3710
+ continue;
3711
+ }
3712
+ this.assertAgent(agent);
3713
+ const walletResponse = await this.walletRequest(
3714
+ state,
3715
+ chainId,
3716
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3717
+ );
3718
+ if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
3719
+ throw this.invalidResponse("agent wallet");
3720
+ }
3721
+ const context = {
3722
+ user,
3723
+ agent,
3724
+ wallet: walletResponse.agentWallet,
3725
+ asset
3726
+ };
3727
+ this.agentContexts.set(this.contextKey(state, chainId, asset), context);
3728
+ contexts.push(context);
3729
+ }
3730
+ return Promise.all(
3731
+ contexts.map(
3732
+ (context) => this.loadPortfolioContext(state, chainId, context, options)
3733
+ )
3734
+ );
3735
+ }
3736
+ async loadPortfolioContext(state, chainId, context, options = {}) {
3737
+ const [snapshot, positions, historic, actions] = await Promise.all([
3738
+ this.walletRequest(
3739
+ state,
3740
+ chainId,
3741
+ `${this.agentPath(context, "snapshot")}${query({
3742
+ shouldOnlyUseRecentValue: true,
3743
+ shouldAllowStaleOnError: true
3744
+ })}`
3745
+ ),
3746
+ this.walletRequest(
3747
+ state,
3748
+ chainId,
3749
+ this.agentPath(context, "yield-positions")
3750
+ ),
3751
+ options.historic ? this.walletRequest(
3752
+ state,
3753
+ chainId,
3754
+ this.agentPath(context, "wallet/historic-position")
3755
+ ) : Promise.resolve(void 0),
3756
+ options.actions ? this.walletRequest(
3757
+ state,
3758
+ chainId,
3759
+ this.agentPath(context, "actions")
3760
+ ) : Promise.resolve(void 0)
3761
+ ]);
3762
+ if (!snapshot?.agentSnapshot) {
3763
+ throw this.invalidResponse("agent snapshot");
3764
+ }
3765
+ if (!Array.isArray(positions?.yieldPositions)) {
3766
+ throw this.invalidResponse("yield positions");
3767
+ }
3768
+ return {
3769
+ ...context,
3770
+ snapshot: snapshot.agentSnapshot,
3771
+ positions: positions.yieldPositions,
3772
+ ...historic?.position ? { historic: historic.position } : {},
3773
+ ...actions?.actions ? { actions: actions.actions } : {}
3774
+ };
3775
+ }
3776
+ async deployAgent(state, chainId, context) {
3777
+ if (context.wallet.initializedDate != null) return;
3778
+ const walletAddress = context.wallet.walletAddress.toLowerCase();
3779
+ const deployed = await this.walletRequest(
3780
+ state,
3781
+ chainId,
3782
+ this.agentPath(context, "deploy"),
3783
+ { method: "POST", body: {} }
3784
+ );
3785
+ if (!deployed?.agentWallet || !(0, import_viem6.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
3786
+ throw this.invalidResponse("agent deployment", {
3787
+ reason: "Deploy did not return the expected Agent Wallet."
3788
+ });
3789
+ }
3790
+ context.wallet = deployed.agentWallet;
3791
+ }
3792
+ async refreshSnapshotAfterMovement(state, chainId, context, movement) {
3793
+ try {
3794
+ const response = await this.walletRequest(
3795
+ state,
3796
+ chainId,
3797
+ `${this.agentPath(context, "snapshot")}${query({
3798
+ shouldForceRefresh: true
3799
+ })}`
3800
+ );
3801
+ if (!response?.agentSnapshot) {
3802
+ throw this.invalidResponse("agent snapshot refresh");
3803
+ }
3804
+ } catch (error) {
3805
+ console.warn(
3806
+ `[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
3807
+ error
3808
+ );
3809
+ }
3810
+ }
3811
+ agentPath(context, suffix) {
3812
+ return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
3813
+ }
3814
+ async walletRequest(state, chainId, path, options = {}) {
3815
+ try {
3816
+ return await this.providerRequest(state, chainId, path, options);
3817
+ } catch (error) {
3818
+ throw this.mapApiError(error);
3819
+ }
3820
+ }
3821
+ async providerRequest(state, chainId, path, options = {}) {
3822
+ this.assertChain(chainId);
3823
+ const request = (signature2) => this.api.request(path, {
3824
+ ...options,
3825
+ signature: signature2
2725
3826
  });
2726
- if (current < needed) {
2727
- onStage?.("approving");
2728
- await deps.ensureChain(quote.src.chainId);
2729
- await deps.approve(quote.spender, MAX_UINT256);
2730
- debugLog("owney-sdk", "swap: approved limit order protocol");
3827
+ let signature = await this.auth.getToken(state, chainId);
3828
+ try {
3829
+ return await request(signature);
3830
+ } catch (error) {
3831
+ if (!(error instanceof YieldseekerApiError)) throw error;
3832
+ if (error.providerCode === "NO_USER") throw error;
3833
+ if (!error.isAuthenticationError) throw error;
3834
+ signature = await this.auth.refreshToken(state, chainId, signature);
3835
+ try {
3836
+ return await request(signature);
3837
+ } catch (retryError) {
3838
+ if (retryError instanceof YieldseekerApiError && retryError.isAuthenticationError) {
3839
+ this.forgetUser(state, chainId);
3840
+ }
3841
+ throw retryError;
3842
+ }
3843
+ }
3844
+ }
3845
+ mapApiError(error) {
3846
+ if (!(error instanceof YieldseekerApiError)) {
3847
+ return new OwneyError(
3848
+ "AGENT_API_ERROR",
3849
+ "Yieldseeker request failed.",
3850
+ { cause: error instanceof Error ? error.message : String(error) },
3851
+ this.id
3852
+ );
3853
+ }
3854
+ const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
3855
+ return new OwneyError(
3856
+ code,
3857
+ `Yieldseeker request failed: ${error.providerCode}.`,
3858
+ {
3859
+ statusCode: error.status,
3860
+ providerCode: error.providerCode,
3861
+ ...error.responseFields ? { fields: error.responseFields } : {}
3862
+ },
3863
+ this.id
3864
+ );
3865
+ }
3866
+ async submitTransaction(state, chainId, transaction) {
3867
+ if (this.transactionExecutor) {
3868
+ return this.transactionExecutor(state, chainId, transaction);
3869
+ }
3870
+ this.assertTransaction(transaction, state, chainId);
3871
+ const account = (0, import_viem6.getAddress)(state.walletAddress);
3872
+ const walletClient = (0, import_viem6.createWalletClient)({
3873
+ account,
3874
+ chain: import_chains3.base,
3875
+ transport: (0, import_viem6.custom)(state.provider)
3876
+ });
3877
+ const publicClient = (0, import_viem6.createPublicClient)({
3878
+ chain: import_chains3.base,
3879
+ transport: (0, import_viem6.custom)(state.provider)
3880
+ });
3881
+ await ensureWalletOnChain(
3882
+ publicClient,
3883
+ walletClient,
3884
+ 8453
3885
+ );
3886
+ const hash = await walletClient.sendTransaction({
3887
+ account,
3888
+ chain: import_chains3.base,
3889
+ to: (0, import_viem6.getAddress)(transaction.to),
3890
+ data: transaction.data,
3891
+ value: BigInt(transaction.value)
3892
+ });
3893
+ const receipt = await publicClient.waitForTransactionReceipt({
3894
+ hash,
3895
+ confirmations: 1
3896
+ });
3897
+ if (receipt.status !== "success") {
3898
+ throw new OwneyError(
3899
+ "AGENT_TRANSACTION_REVERTED",
3900
+ `Yieldseeker transaction reverted (${hash}).`,
3901
+ { transactionHash: hash },
3902
+ this.id
3903
+ );
3904
+ }
3905
+ return hash;
3906
+ }
3907
+ async waitForReceipt(state, chainId, transactionHash) {
3908
+ if (this.unwindReceiptWaiter) {
3909
+ await this.unwindReceiptWaiter(state, chainId, transactionHash);
3910
+ return;
3911
+ }
3912
+ const publicClient = (0, import_viem6.createPublicClient)({
3913
+ chain: import_chains3.base,
3914
+ transport: (0, import_viem6.custom)(state.provider)
3915
+ });
3916
+ const receipt = await publicClient.waitForTransactionReceipt({
3917
+ hash: transactionHash,
3918
+ confirmations: 1
3919
+ });
3920
+ if (receipt.status !== "success") {
3921
+ throw new OwneyError(
3922
+ "AGENT_TRANSACTION_REVERTED",
3923
+ `Yieldseeker transaction reverted (${transactionHash}).`,
3924
+ { transactionHash },
3925
+ this.id
3926
+ );
3927
+ }
3928
+ }
3929
+ assertTransaction(transaction, state, chainId) {
3930
+ if (!transaction || typeof transaction.from !== "string" || !(0, import_viem6.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem6.isAddress)(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || (0, import_viem6.getAddress)(transaction.from) !== (0, import_viem6.getAddress)(state.walletAddress)) {
3931
+ throw this.invalidResponse("transaction");
3932
+ }
3933
+ }
3934
+ assertAgent(agent) {
3935
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem6.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
3936
+ throw this.invalidResponse("agent");
3937
+ }
3938
+ }
3939
+ isOwneyAgent(agent) {
3940
+ return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
3941
+ }
3942
+ assetForAgent(agent) {
3943
+ for (const asset of ["USDC", "WETH"]) {
3944
+ if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
3945
+ return asset;
3946
+ }
3947
+ }
3948
+ return null;
3949
+ }
3950
+ isTransactionHash(value) {
3951
+ return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
3952
+ }
3953
+ assertChain(chainId) {
3954
+ if (chainId !== 8453) {
3955
+ throw new OwneyError(
3956
+ "CHAIN_UNSUPPORTED",
3957
+ `Yieldseeker does not support chain ${chainId}.`,
3958
+ { chainId, supportedChainIds: [8453] },
3959
+ this.id
3960
+ );
3961
+ }
3962
+ }
3963
+ assertOptionalChain(chainId) {
3964
+ if (chainId !== void 0) this.assertChain(chainId);
3965
+ }
3966
+ assertAsset(asset) {
3967
+ if (asset !== "USDC" && asset !== "WETH") {
3968
+ throw new OwneyError(
3969
+ "ASSET_UNSUPPORTED",
3970
+ `Yieldseeker does not support asset ${asset} in the Owney rollout.`,
3971
+ {
3972
+ asset,
3973
+ supportedAssets: ["USDC", "WETH"],
3974
+ providerAlsoAdvertises: ["cbBTC"]
3975
+ },
3976
+ this.id
3977
+ );
2731
3978
  }
2732
3979
  }
2733
- const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
2734
- onStage?.("quoting");
2735
- debugLog("owney-sdk", "swap: building fusion order", {
2736
- secrets: secretHashes.length
2737
- });
2738
- const built = await deps.api.buildOrder({
2739
- from: {
2740
- chainId: quote.src.chainId,
2741
- symbol: quote.src.symbol,
2742
- // The trimmed amount — the order is re-quoted at this size server-side.
2743
- amount: amount.toString()
2744
- },
2745
- to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
2746
- walletAddress,
2747
- secretHashes,
2748
- ...direction ? { direction } : {}
2749
- });
2750
- saveOrder({
2751
- orderHash: built.orderHash,
2752
- secrets,
2753
- srcChainId: quote.src.chainId,
2754
- srcSymbol: quote.src.symbol,
2755
- dstChainId: quote.dst.chainId,
2756
- dstSymbol: quote.dst.symbol,
2757
- amount: amount.toString(),
2758
- createdAt: Date.now()
2759
- });
2760
- debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
2761
- onStage?.("signing");
2762
- await deps.ensureChain(quote.src.chainId);
2763
- debugLog("owney-sdk", "swap: awaiting signature in wallet", {
2764
- signingOnChain: quote.src.chainId
2765
- });
2766
- const signature = await deps.signTypedData(built.typedData);
2767
- debugLog("owney-sdk", "swap: signed, submitting to relayer");
2768
- await deps.api.submitOrder({
2769
- srcChainId: quote.src.chainId,
2770
- // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
2771
- // the envelope here gets a bare 500 from the relayer.
2772
- order: built.order,
2773
- signature,
2774
- quoteId: built.quoteId,
2775
- // Single-fill orders must NOT carry secretHashes — the relayer rejects
2776
- // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
2777
- // order's hashlock, so repeating it here is redundant, and only a
2778
- // multi-fill order (a Merkle tree of hashes) needs them listed.
2779
- ...secretHashes.length > 1 ? { secretHashes } : {},
2780
- ...built.extension ? { extension: built.extension } : {}
2781
- });
2782
- debugLog("owney-sdk", "swap: order submitted, polling escrows");
3980
+ invalidResponse(operation, details = {}) {
3981
+ return new OwneyError(
3982
+ "AGENT_INVALID_RESPONSE",
3983
+ `Yieldseeker returned an invalid ${operation} response.`,
3984
+ details,
3985
+ this.id
3986
+ );
3987
+ }
3988
+ };
3989
+
3990
+ // src/lib/routing-api.ts
3991
+ var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3992
+ async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
3993
+ const url = `${baseUrl}/api/v1/agent/org-config`;
2783
3994
  try {
2784
- await runFusionOrder(deps.runner, {
2785
- orderHash: built.orderHash,
2786
- secrets,
2787
- ...onStage ? { onStage } : {}
3995
+ const res = await fetch(url, {
3996
+ method: "GET",
3997
+ headers: {
3998
+ "Content-Type": "application/json",
3999
+ "x-owney-api-key": `${apiKey}`
4000
+ }
2788
4001
  });
2789
- } catch (error) {
2790
- if (error instanceof OwneyError && (error.code === "SWAP_ORDER_REFUNDED" || error.code === "SWAP_ORDER_CANCELLED")) {
2791
- clearOrder(built.orderHash);
4002
+ if (!res.ok) {
4003
+ if (res.status !== 404) {
4004
+ console.warn(
4005
+ `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
4006
+ );
4007
+ }
4008
+ return null;
2792
4009
  }
2793
- throw error;
4010
+ const json = await res.json();
4011
+ const policy = json.success ? json.data ?? null : null;
4012
+ debugLog(
4013
+ "owney-sdk",
4014
+ policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
4015
+ policy ?? void 0
4016
+ );
4017
+ return policy;
4018
+ } catch (error) {
4019
+ console.warn(
4020
+ "[owney-sdk] Could not read org agent config (non-fatal):",
4021
+ error instanceof Error ? error.message : String(error)
4022
+ );
4023
+ return null;
2794
4024
  }
2795
- clearOrder(built.orderHash);
2796
- return { orderHash: built.orderHash };
2797
4025
  }
2798
-
2799
- // src/lib/swap/swap.arrival.ts
2800
- var DEFAULT_TIMEOUT_MS2 = 18e4;
2801
- var DEFAULT_POLL_MS2 = 4e3;
2802
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2803
- async function awaitWithdrawalArrival(options) {
2804
- const {
2805
- readBalance,
2806
- baseline,
2807
- timeoutMs = DEFAULT_TIMEOUT_MS2,
2808
- pollMs = DEFAULT_POLL_MS2
2809
- } = options;
2810
- const deadline = Date.now() + timeoutMs;
2811
- debugLog("owney-sdk", "withdraw: waiting for funds to land", {
2812
- baseline: baseline.toString(),
2813
- timeoutMs
2814
- });
2815
- let lastError;
2816
- for (; ; ) {
2817
- try {
2818
- const balance = await readBalance();
2819
- if (balance > baseline) {
2820
- const arrived = balance - baseline;
2821
- debugLog("owney-sdk", "withdraw: funds landed", {
2822
- arrived: arrived.toString()
2823
- });
2824
- return arrived;
2825
- }
2826
- } catch (error) {
2827
- lastError = error;
2828
- debugLog("owney-sdk", "withdraw: balance read failed, retrying", {
2829
- message: error instanceof Error ? error.message : String(error)
2830
- });
2831
- }
2832
- if (Date.now() >= deadline) {
2833
- throw new OwneyError(
2834
- "WITHDRAW_ARRIVAL_TIMEOUT",
2835
- "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.",
2836
- {
2837
- baseline: baseline.toString(),
2838
- waitedMs: timeoutMs,
2839
- ...lastError ? {
2840
- lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
2841
- } : {}
2842
- }
2843
- );
4026
+ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
4027
+ const url = `${baseUrl}/api/v1/agent/keys`;
4028
+ const res = await fetch(url, {
4029
+ method: "GET",
4030
+ headers: {
4031
+ "Content-Type": "application/json",
4032
+ "x-owney-api-key": `${apiKey}`
2844
4033
  }
2845
- await sleep(pollMs);
4034
+ });
4035
+ if (!res.ok) {
4036
+ const text = await res.text().catch(() => "");
4037
+ throw new OwneyError(
4038
+ "API_ROUTING_ERROR",
4039
+ `Routing API error ${res.status}: ${text}`,
4040
+ { statusCode: res.status, responseBody: text }
4041
+ );
4042
+ }
4043
+ const json = await res.json();
4044
+ if (!json.success) {
4045
+ throw new OwneyError(
4046
+ "API_ROUTING_FAILED",
4047
+ `Routing API request failed: ${json.message}`,
4048
+ { message: json.message }
4049
+ );
2846
4050
  }
4051
+ return json.data;
2847
4052
  }
2848
4053
 
2849
4054
  // src/lib/health-report.ts
2850
- var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2851
- async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
4055
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
4056
+ async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
2852
4057
  try {
2853
4058
  await fetch(`${baseUrl}/api/v1/agent/health-report`, {
2854
4059
  method: "POST",
@@ -2880,7 +4085,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
2880
4085
  }
2881
4086
 
2882
4087
  // src/lib/helpers/withdraw-helper.ts
2883
- var import_viem5 = require("viem");
4088
+ var import_viem7 = require("viem");
2884
4089
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
2885
4090
  const target = asset.toUpperCase();
2886
4091
  return agents.map((agent) => {
@@ -2888,8 +4093,29 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
2888
4093
  const tokenBalance = agentBalance?.tokens.find(
2889
4094
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
2890
4095
  );
2891
- if (!tokenBalance) return { agent, balance: 0n };
2892
- return { agent, balance: (0, import_viem5.parseUnits)(tokenBalance.amount, decimals) };
4096
+ let balance = tokenBalance ? (0, import_viem7.parseUnits)(tokenBalance.amount, decimals) : 0n;
4097
+ if (agent.balanceComposition === "tokens-plus-positions") {
4098
+ const chainNameById = {
4099
+ 1: "ETHEREUM",
4100
+ 8453: "BASE",
4101
+ 42161: "ARBITRUM"
4102
+ };
4103
+ const targetChain = chainNameById[chainId];
4104
+ for (const position2 of agentBalance?.positions ?? []) {
4105
+ const positionChain = position2.chain.trim().toUpperCase();
4106
+ const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
4107
+ if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
4108
+ if (position2.amountRaw !== void 0) {
4109
+ try {
4110
+ balance += BigInt(position2.amountRaw);
4111
+ continue;
4112
+ } catch {
4113
+ }
4114
+ }
4115
+ balance += (0, import_viem7.parseUnits)(position2.amount, decimals);
4116
+ }
4117
+ }
4118
+ return { agent, balance };
2893
4119
  });
2894
4120
  }
2895
4121
  function planProportionalShares(balances, requested, totalAvailable) {
@@ -2915,7 +4141,9 @@ function planProportionalShares(balances, requested, totalAvailable) {
2915
4141
  return plans;
2916
4142
  }
2917
4143
  function planDisabledDrain(disabled, requested) {
2918
- const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
4144
+ const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
4145
+ (a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
4146
+ );
2919
4147
  const plans = [];
2920
4148
  let remaining = requested;
2921
4149
  for (const { agent, balance } of sorted) {
@@ -2966,6 +4194,13 @@ function balanceForApyScope(balance, chainId, tokenSymbol) {
2966
4194
  return Number.isFinite(total) && total > 0 ? total : 0;
2967
4195
  }
2968
4196
  const normalizedToken = tokenSymbol.toUpperCase();
4197
+ const snapshots = balance.assetBalances?.filter(
4198
+ (token) => Number(token.chainId) === chainId && String(token.asset).toUpperCase() === normalizedToken
4199
+ );
4200
+ if (snapshots?.length) {
4201
+ const amount = snapshots.reduce((sum, token) => sum + Number(token.amount), 0);
4202
+ if (Number.isFinite(amount)) return Math.max(0, amount);
4203
+ }
2969
4204
  return balance.tokens.reduce((total, token) => {
2970
4205
  if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
2971
4206
  return total;
@@ -3036,325 +4271,305 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
3036
4271
  }
3037
4272
 
3038
4273
  // src/client.ts
3039
- var import_viem8 = require("viem");
3040
- var import_chains2 = require("viem/chains");
4274
+ var import_viem11 = require("viem");
4275
+ var import_chains4 = require("viem/chains");
3041
4276
 
3042
- // src/lib/transfer-auth.ts
3043
- var import_viem6 = require("viem");
3044
- var ERC20_META_ABI = [
3045
- { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
3046
- { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
3047
- ];
3048
- function buildTransferWithAuthorizationTypedData(input) {
4277
+ // src/lib/sponsored-token-batch.ts
4278
+ var import_viem9 = require("viem");
4279
+
4280
+ // src/lib/permit2-batch.ts
4281
+ var import_viem8 = require("viem");
4282
+ var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
4283
+ var PERMIT_BATCH_TYPES = {
4284
+ PermitBatchWitnessTransferFrom: [
4285
+ { name: "permitted", type: "TokenPermissions[]" },
4286
+ { name: "spender", type: "address" },
4287
+ { name: "nonce", type: "uint256" },
4288
+ { name: "deadline", type: "uint256" },
4289
+ { name: "witness", type: "Deposit" }
4290
+ ],
4291
+ Deposit: [{ name: "recipients", type: "address[]" }],
4292
+ TokenPermissions: [
4293
+ { name: "token", type: "address" },
4294
+ { name: "amount", type: "uint256" }
4295
+ ]
4296
+ };
4297
+ var PERMIT2_BATCH_ABI = (0, import_viem8.parseAbi)([
4298
+ "struct TokenPermissions { address token; uint256 amount; }",
4299
+ "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
4300
+ "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
4301
+ "function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
4302
+ "function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
4303
+ ]);
4304
+ function batchPermit(b) {
3049
4305
  return {
3050
- domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
3051
- types: {
3052
- TransferWithAuthorization: [
3053
- { name: "from", type: "address" },
3054
- { name: "to", type: "address" },
3055
- { name: "value", type: "uint256" },
3056
- { name: "validAfter", type: "uint256" },
3057
- { name: "validBefore", type: "uint256" },
3058
- { name: "nonce", type: "bytes32" }
3059
- ]
3060
- },
3061
- primaryType: "TransferWithAuthorization",
3062
- message: input.message
4306
+ permitted: b.transfers.map((t) => ({
4307
+ token: b.token,
4308
+ amount: BigInt(t.amount)
4309
+ })),
4310
+ nonce: BigInt(b.nonce),
4311
+ deadline: BigInt(b.deadline)
3063
4312
  };
3064
4313
  }
3065
- async function readTokenMeta(publicClient, token) {
3066
- const [tokenName, tokenVersion] = await Promise.all([
3067
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
3068
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
3069
- ]);
3070
- return { tokenName, tokenVersion };
3071
- }
3072
- function randomAuthNonce() {
3073
- const bytes = new Uint8Array(32);
3074
- globalThis.crypto.getRandomValues(bytes);
3075
- return (0, import_viem6.bytesToHex)(bytes);
4314
+ function batchTypedData(b, spender) {
4315
+ return {
4316
+ domain: {
4317
+ name: "Permit2",
4318
+ chainId: b.chainId,
4319
+ verifyingContract: BATCH_PERMIT2_ADDRESS
4320
+ },
4321
+ types: PERMIT_BATCH_TYPES,
4322
+ primaryType: "PermitBatchWitnessTransferFrom",
4323
+ message: {
4324
+ ...batchPermit(b),
4325
+ spender,
4326
+ witness: { recipients: b.transfers.map((t) => t.to) }
4327
+ }
4328
+ };
3076
4329
  }
3077
4330
 
3078
- // src/lib/sponsor-client.ts
3079
- var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3080
- async function postSponsorTransferAuth(input) {
3081
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
3082
- let res;
3083
- try {
3084
- res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
3085
- method: "POST",
3086
- headers: {
3087
- "content-type": "application/json",
3088
- "x-owney-api-key": input.apiKey
3089
- },
3090
- body: JSON.stringify(input.body)
3091
- });
3092
- } catch (networkError) {
3093
- throw new OwneyError(
3094
- "SPONSOR_REQUEST_FAILED",
3095
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
3096
- { cause: String(networkError) }
3097
- );
3098
- }
3099
- const text = await res.text();
3100
- let parsed = null;
3101
- try {
3102
- parsed = JSON.parse(text);
3103
- } catch {
3104
- }
3105
- if (!res.ok || !parsed?.success || !parsed.data) {
3106
- throw new OwneyError(
3107
- "SPONSOR_REQUEST_FAILED",
3108
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
3109
- {
3110
- statusCode: res.status,
3111
- responseBody: text.slice(0, 500),
3112
- // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
3113
- // before broadcast, so it is safe to fall back to a user-paid deposit.
3114
- safeToFallback: res.status === 503
3115
- }
3116
- );
3117
- }
3118
- return parsed.data;
4331
+ // src/lib/sponsored-token-batch.ts
4332
+ var memory = /* @__PURE__ */ new Map();
4333
+ var inflight = /* @__PURE__ */ new Map();
4334
+ var planOf = (transfers) => JSON.stringify(
4335
+ transfers.map((t) => ({
4336
+ to: t.to.toLowerCase(),
4337
+ amount: BigInt(t.amount).toString()
4338
+ }))
4339
+ );
4340
+ function read(key2) {
4341
+ return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
3119
4342
  }
3120
- async function postSponsorPermit2Transfer(input) {
3121
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
3122
- let res;
3123
- try {
3124
- res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
3125
- method: "POST",
3126
- headers: {
3127
- "content-type": "application/json",
3128
- "x-owney-api-key": input.apiKey
3129
- },
3130
- body: JSON.stringify(input.body)
3131
- });
3132
- } catch (networkError) {
3133
- throw new OwneyError(
3134
- "SPONSOR_REQUEST_FAILED",
3135
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
3136
- { cause: String(networkError), safeToFallback: false }
3137
- );
3138
- }
3139
- const text = await res.text();
3140
- let parsed = null;
3141
- try {
3142
- parsed = JSON.parse(text);
3143
- } catch {
3144
- }
3145
- if (!res.ok || !parsed?.success || !parsed.data) {
3146
- throw new OwneyError(
3147
- "SPONSOR_REQUEST_FAILED",
3148
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
3149
- {
3150
- statusCode: res.status,
3151
- responseBody: text.slice(0, 500),
3152
- safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
3153
- }
3154
- );
3155
- }
3156
- return parsed.data;
4343
+ function save(key2, body) {
4344
+ const value = JSON.stringify({
4345
+ ...body,
4346
+ transfers: body.transfers.map(({ to, amount }) => ({ to, amount }))
4347
+ });
4348
+ if (typeof window === "undefined") memory.set(key2, value);
4349
+ else window.localStorage.setItem(key2, value);
3157
4350
  }
3158
- async function getSponsorRelayerAddress(input) {
3159
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
3160
- let res;
3161
- try {
3162
- res = await fetch(
3163
- `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
3164
- {
3165
- headers: { "x-owney-api-key": input.apiKey }
3166
- }
3167
- );
3168
- } catch (networkError) {
3169
- throw new OwneyError(
3170
- "SPONSOR_REQUEST_FAILED",
3171
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
3172
- { cause: String(networkError), safeToFallback: true }
3173
- );
3174
- }
3175
- const text = await res.text();
3176
- let parsed = null;
3177
- try {
3178
- parsed = JSON.parse(text);
3179
- } catch {
3180
- }
3181
- if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
3182
- throw new OwneyError(
3183
- "SPONSOR_REQUEST_FAILED",
3184
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
3185
- {
3186
- statusCode: res.status,
3187
- responseBody: text.slice(0, 500),
3188
- safeToFallback: true
3189
- }
3190
- );
3191
- }
3192
- return parsed.data.relayer;
4351
+ function clear(key2) {
4352
+ if (typeof window === "undefined") memory.delete(key2);
4353
+ else window.localStorage.removeItem(key2);
3193
4354
  }
3194
-
3195
- // src/lib/sponsored-deposit.ts
3196
- var AUTH_WINDOW_SECONDS = 15 * 60;
3197
- function makeSponsoredDepositCallback(deps) {
3198
- const post = deps.httpPost ?? postSponsorTransferAuth;
3199
- return async (smartWallet, chainId, amount) => {
3200
- const cid = chainId;
3201
- const token = deps.tokenAddressByChain[cid];
3202
- if (!token) {
3203
- throw new OwneyError(
3204
- "CHAIN_UNSUPPORTED",
3205
- `No sponsored token configured for chain ${chainId}`
4355
+ function sponsorTokenBatch(i) {
4356
+ const key2 = `owney.token-batch.v1:${(0, import_viem9.keccak256)((0, import_viem9.toBytes)(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
4357
+ const plan = planOf(i.transfers);
4358
+ const active = inflight.get(key2);
4359
+ if (active) {
4360
+ if (active.plan !== plan)
4361
+ return Promise.reject(
4362
+ new Error(
4363
+ "A token deposit is already in progress. Wait for its result before depositing again."
4364
+ )
3206
4365
  );
3207
- }
3208
- const pub = deps.getPublicClient(cid);
3209
- const wallet = deps.getWalletClient(cid);
3210
- await ensureWalletOnChain(pub, wallet, cid);
4366
+ return active.promise;
4367
+ }
4368
+ const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
4369
+ inflight.set(key2, { plan, promise });
4370
+ return promise;
4371
+ }
4372
+ async function execute(i, key2, plan) {
4373
+ if (!i.transfers.length || i.transfers.length > 16 || i.transfers.some(
4374
+ (t) => BigInt(t.amount) <= 0n || BigInt(t.amount) >= 1n << 256n
4375
+ ) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
4376
+ throw new Error("Invalid token deposit shares.");
4377
+ const send = async (initial) => {
4378
+ let body = initial;
4379
+ save(key2, body);
3211
4380
  try {
3212
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
3213
- if (balance < BigInt(amount)) {
3214
- throw new OwneyError(
3215
- "DEPOSIT_INSUFFICIENT_BALANCE",
3216
- "Insufficient balance for this deposit.",
3217
- { token, chainId: cid, balance: balance.toString(), amount }
3218
- );
4381
+ if (!body.serializedTransaction) {
4382
+ const prepared = await postSponsorBatchTransfer({
4383
+ apiKey: i.apiKey,
4384
+ baseUrl: i.baseUrl,
4385
+ body
4386
+ });
4387
+ if (!prepared.serializedTransaction || (0, import_viem9.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
4388
+ throw new Error(
4389
+ "Sponsorship API did not return a valid prepared transaction."
4390
+ );
4391
+ body = {
4392
+ ...body,
4393
+ serializedTransaction: prepared.serializedTransaction
4394
+ };
4395
+ save(key2, body);
3219
4396
  }
3220
- } catch (err) {
3221
- if (err instanceof OwneyError) throw err;
3222
- console.warn(
3223
- "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
3224
- err instanceof Error ? err.message : String(err)
3225
- );
4397
+ const result = await postSponsorBatchTransfer({
4398
+ apiKey: i.apiKey,
4399
+ baseUrl: i.baseUrl,
4400
+ body
4401
+ });
4402
+ if (result.txHash !== (0, import_viem9.keccak256)(body.serializedTransaction))
4403
+ throw new Error(
4404
+ "Sponsorship receipt does not match the pending transaction."
4405
+ );
4406
+ clear(key2);
4407
+ return result.txHash;
4408
+ } catch (error) {
4409
+ if (!body.serializedTransaction || error instanceof OwneyError && error.details?.notSubmitted === true)
4410
+ clear(key2);
4411
+ throw error;
3226
4412
  }
3227
- const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
3228
- const validAfter = 0n;
3229
- const validBefore = BigInt(
3230
- Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
3231
- );
3232
- const nonce = randomAuthNonce();
3233
- const typedData = buildTransferWithAuthorizationTypedData({
3234
- token,
3235
- chainId: cid,
3236
- tokenName,
3237
- tokenVersion,
3238
- message: {
3239
- from: deps.ownerAddress,
3240
- to: smartWallet,
3241
- value: BigInt(amount),
3242
- validAfter,
3243
- validBefore,
3244
- nonce
3245
- }
3246
- });
3247
- const authSignature = await wallet.signTypedData({
3248
- account: deps.ownerAddress,
3249
- ...typedData
3250
- });
3251
- deps.onApproved?.();
3252
- const result = await post({
3253
- baseUrl: deps.baseUrl,
3254
- apiKey: deps.apiKey,
3255
- body: {
3256
- chainId: cid,
3257
- token,
3258
- from: deps.ownerAddress,
3259
- to: smartWallet,
3260
- value: amount,
3261
- validAfter: validAfter.toString(),
3262
- validBefore: validBefore.toString(),
3263
- nonce,
3264
- authSignature,
3265
- tokenName,
3266
- tokenVersion
3267
- }
3268
- });
3269
- return result.txHash;
3270
4413
  };
4414
+ const saved = read(key2);
4415
+ if (saved) {
4416
+ const previous = JSON.parse(saved);
4417
+ if (previous.chainId !== i.chainId || !(0, import_viem9.isAddressEqual)(previous.from, i.owner) || !(0, import_viem9.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
4418
+ throw new Error(
4419
+ "Retry the previous token deposit and agent split first to reconcile its status."
4420
+ );
4421
+ i.onApproved?.();
4422
+ return send({ ...previous, transfers: i.transfers });
4423
+ }
4424
+ const total = i.transfers.reduce((sum, t) => sum + BigInt(t.amount), 0n);
4425
+ const [balance, allowance] = await Promise.all([
4426
+ readErc20Balance(i.pub, i.token, i.owner),
4427
+ readPermit2Allowance(i.pub, i.token, i.owner)
4428
+ ]);
4429
+ if (balance < total)
4430
+ throw new OwneyError(
4431
+ "DEPOSIT_INSUFFICIENT_BALANCE",
4432
+ "Insufficient token balance for this deposit."
4433
+ );
4434
+ if (allowance < total)
4435
+ throw new OwneyError(
4436
+ "PERMIT2_APPROVAL_REQUIRED",
4437
+ "token deposits need a one-time Permit2 approval."
4438
+ );
4439
+ const relayer = await getSponsorRelayerAddress({
4440
+ apiKey: i.apiKey,
4441
+ baseUrl: i.baseUrl,
4442
+ chainId: i.chainId
4443
+ });
4444
+ const now = (await i.pub.getBlock()).timestamp;
4445
+ const unsigned = {
4446
+ chainId: i.chainId,
4447
+ token: i.token,
4448
+ from: i.owner,
4449
+ transfers: i.transfers,
4450
+ nonce: randomPermit2Nonce().toString(),
4451
+ deadline: (now + 900n).toString()
4452
+ };
4453
+ const signature = await i.wallet.signTypedData({
4454
+ account: i.owner,
4455
+ ...batchTypedData(unsigned, relayer)
4456
+ });
4457
+ i.onApproved?.();
4458
+ return send({ ...unsigned, signature });
3271
4459
  }
3272
4460
 
3273
- // src/lib/sponsored-weth-deposit.ts
3274
- var PERMIT_WINDOW_SECONDS = 15 * 60;
3275
- function makeSponsoredWethCallback(deps) {
3276
- const get = deps.httpGet ?? getSponsorRelayerAddress;
3277
- const post = deps.httpPost ?? postSponsorPermit2Transfer;
3278
- return async (smartWallet, chainId, amount) => {
3279
- const cid = chainId;
3280
- const token = deps.tokenAddressByChain[cid];
3281
- if (!token) {
4461
+ // src/lib/sponsored-token-deposit.ts
4462
+ function makeSponsoredTokenCallback(deps) {
4463
+ const batch = async (chainId, transfers) => {
4464
+ if (chainId !== 8453 && chainId !== 42161 && chainId !== 1)
3282
4465
  throw new OwneyError(
3283
4466
  "CHAIN_UNSUPPORTED",
3284
- `No sponsored WETH configured for chain ${chainId}`
3285
- );
3286
- }
3287
- const amountWei = BigInt(amount);
3288
- const pub = deps.getPublicClient(cid);
3289
- const wallet = deps.getWalletClient(cid);
3290
- await ensureWalletOnChain(pub, wallet, cid);
3291
- try {
3292
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
3293
- if (balance < amountWei) {
3294
- throw new OwneyError(
3295
- "DEPOSIT_INSUFFICIENT_BALANCE",
3296
- "Insufficient WETH balance for this deposit.",
3297
- { token, chainId: cid, balance: balance.toString(), amount }
3298
- );
3299
- }
3300
- } catch (err) {
3301
- if (err instanceof OwneyError) throw err;
3302
- console.warn(
3303
- "[owney-sdk] WETH balance pre-check failed (non-fatal):",
3304
- err instanceof Error ? err.message : String(err)
4467
+ `No sponsored token configured for chain ${chainId}`
3305
4468
  );
3306
- }
3307
- const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
3308
- if (allowance < amountWei) {
4469
+ const token = deps.tokenAddressByChain[chainId];
4470
+ if (!token)
3309
4471
  throw new OwneyError(
3310
- "PERMIT2_APPROVAL_REQUIRED",
3311
- "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
3312
- { token, chainId: cid, allowance: allowance.toString(), amount }
4472
+ "CHAIN_UNSUPPORTED",
4473
+ `No sponsored token configured for chain ${chainId}`
3313
4474
  );
3314
- }
3315
- const relayer = await get({
3316
- baseUrl: deps.baseUrl,
4475
+ const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
4476
+ await ensureWalletOnChain(pub, wallet, chainId);
4477
+ return sponsorTokenBatch({
3317
4478
  apiKey: deps.apiKey,
3318
- chainId: cid
3319
- });
3320
- const nonce = randomPermit2Nonce();
3321
- const deadline = BigInt(
3322
- Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
3323
- );
3324
- const typedData = buildPermitTransferFromTypedData({
3325
- chainId: cid,
3326
- message: {
3327
- permitted: { token, amount: amountWei },
3328
- spender: relayer,
3329
- nonce,
3330
- deadline
3331
- }
3332
- });
3333
- const signature = await wallet.signTypedData({
3334
- account: deps.ownerAddress,
3335
- ...typedData
3336
- });
3337
- deps.onApproved?.();
3338
- const result = await post({
3339
4479
  baseUrl: deps.baseUrl,
3340
- apiKey: deps.apiKey,
3341
- body: {
3342
- chainId: cid,
3343
- token,
3344
- from: deps.ownerAddress,
3345
- to: smartWallet,
3346
- amount,
3347
- nonce: nonce.toString(),
3348
- deadline: deadline.toString(),
3349
- signature
3350
- }
4480
+ owner: deps.ownerAddress,
4481
+ token,
4482
+ chainId,
4483
+ transfers,
4484
+ pub,
4485
+ wallet,
4486
+ onApproved: deps.onApproved
3351
4487
  });
3352
- return result.txHash;
3353
4488
  };
4489
+ const callback = makeVerificationAwareDepositCallback(
4490
+ (to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
4491
+ );
4492
+ registerDepositBatch(callback, batch);
4493
+ return callback;
4494
+ }
4495
+
4496
+ // src/lib/agent-deposit-batch.ts
4497
+ function deferred() {
4498
+ let resolve, reject;
4499
+ const promise = new Promise((yes, no) => {
4500
+ resolve = yes;
4501
+ reject = no;
4502
+ });
4503
+ void promise.catch(() => {
4504
+ });
4505
+ return { promise, resolve, reject };
4506
+ }
4507
+ async function runAgentDepositBatch(chainId, legs, transfer) {
4508
+ const funding = deferred();
4509
+ const tasks = [];
4510
+ const transfers = [];
4511
+ try {
4512
+ for (const leg of legs) {
4513
+ const ready = deferred();
4514
+ let entered = false;
4515
+ const callback = makeVerificationAwareDepositCallback(
4516
+ (to, cid, amount, verification) => {
4517
+ if (entered || cid !== chainId || BigInt(amount) !== BigInt(leg.amount)) {
4518
+ const error = new Error(
4519
+ "Agent changed its prepared deposit share."
4520
+ );
4521
+ ready.reject(error);
4522
+ throw error;
4523
+ }
4524
+ entered = true;
4525
+ ready.resolve(toBatchTransfer(to, amount, verification));
4526
+ return funding.promise;
4527
+ }
4528
+ );
4529
+ const task = Promise.resolve().then(() => leg.run(callback));
4530
+ tasks.push(task);
4531
+ void task.then(
4532
+ () => {
4533
+ if (!entered)
4534
+ ready.reject(
4535
+ new Error("Agent did not prepare a deposit transfer.")
4536
+ );
4537
+ },
4538
+ (error) => ready.reject(error)
4539
+ );
4540
+ transfers.push(await ready.promise);
4541
+ }
4542
+ const txHash = await transfer(chainId, transfers);
4543
+ funding.resolve(txHash);
4544
+ const settled = await Promise.allSettled(tasks);
4545
+ const agentResults = {};
4546
+ const failures = [];
4547
+ for (const [index, result] of settled.entries()) {
4548
+ if (result.status === "fulfilled")
4549
+ agentResults[legs[index].id] = result.value;
4550
+ else failures.push(legs[index].id);
4551
+ }
4552
+ if (failures.length)
4553
+ throw new OwneyError(
4554
+ "DEPOSIT_PARTIAL_FAILURE",
4555
+ "The deposit was sent to all agents, but some agent updates could not be confirmed. Check activity before depositing again.",
4556
+ {
4557
+ txHash,
4558
+ fundsSubmitted: true,
4559
+ agentResults,
4560
+ failedAgentIds: failures
4561
+ }
4562
+ );
4563
+ return { agentResults };
4564
+ } catch (error) {
4565
+ funding.reject(error);
4566
+ await Promise.allSettled(tasks);
4567
+ throw error;
4568
+ }
3354
4569
  }
3355
4570
 
3356
4571
  // src/lib/sponsored-calls-deposit.ts
3357
- var import_viem7 = require("viem");
4572
+ var import_viem10 = require("viem");
3358
4573
  var DEFAULT_POLL_INTERVAL_MS = 1500;
3359
4574
  var DEFAULT_MAX_POLLS = 30;
3360
4575
  async function paymasterSupported(provider, owner, chainId) {
@@ -3362,7 +4577,7 @@ async function paymasterSupported(provider, owner, chainId) {
3362
4577
  method: "wallet_getCapabilities",
3363
4578
  params: [owner]
3364
4579
  });
3365
- const forChain = caps?.[(0, import_viem7.toHex)(chainId)] ?? caps?.[String(chainId)];
4580
+ const forChain = caps?.[(0, import_viem10.toHex)(chainId)] ?? caps?.[String(chainId)];
3366
4581
  return Boolean(forChain?.paymasterService?.supported);
3367
4582
  }
3368
4583
  function makeSponsoredCallsCallback(deps) {
@@ -3380,7 +4595,7 @@ function makeSponsoredCallsCallback(deps) {
3380
4595
  }
3381
4596
  return new URL(configured, origin).toString();
3382
4597
  };
3383
- return async (smartWallet, chainId, amount) => {
4598
+ const batch = async (chainId, transfers) => {
3384
4599
  const cid = chainId;
3385
4600
  const token = deps.tokenAddressByChain[cid];
3386
4601
  if (!token) {
@@ -3396,22 +4611,53 @@ function makeSponsoredCallsCallback(deps) {
3396
4611
  { chainId }
3397
4612
  );
3398
4613
  }
3399
- const data = (0, import_viem7.encodeFunctionData)({
3400
- abi: import_viem7.erc20Abi,
3401
- functionName: "transfer",
3402
- args: [smartWallet, BigInt(amount)]
3403
- });
4614
+ const calls = transfers.map((transfer) => ({
4615
+ to: token,
4616
+ value: "0x0",
4617
+ data: (0, import_viem10.encodeFunctionData)({
4618
+ abi: import_viem10.erc20Abi,
4619
+ functionName: "transfer",
4620
+ args: [transfer.to, BigInt(transfer.amount)]
4621
+ })
4622
+ }));
4623
+ let paymasterUrl = absolutePaymasterUrl();
4624
+ for (const transfer of transfers) {
4625
+ const verification = transfer.yieldseeker;
4626
+ if (!verification) continue;
4627
+ if (chainId !== 8453)
4628
+ throw new OwneyError(
4629
+ "CHAIN_UNSUPPORTED",
4630
+ `Yieldseeker sponsorship is not available on chain ${chainId}.`
4631
+ );
4632
+ const { intent } = await postPaymasterIntent({
4633
+ baseUrl: deps.routingApiBaseUrl,
4634
+ apiKey: deps.apiKey,
4635
+ yieldseekerSignature: verification.signature,
4636
+ body: {
4637
+ chainId,
4638
+ token,
4639
+ from: deps.ownerAddress,
4640
+ to: transfer.to,
4641
+ amount: transfer.amount,
4642
+ yieldseekerUserId: verification.userId,
4643
+ yieldseekerAgentId: verification.agentId
4644
+ }
4645
+ });
4646
+ const url = new URL(paymasterUrl);
4647
+ url.searchParams.append("owneyIntent", intent);
4648
+ paymasterUrl = url.toString();
4649
+ }
3404
4650
  const sendResult = await deps.provider.request({
3405
4651
  method: "wallet_sendCalls",
3406
4652
  params: [
3407
4653
  {
3408
4654
  version: "2.0.0",
3409
4655
  from: deps.ownerAddress,
3410
- chainId: (0, import_viem7.toHex)(chainId),
3411
- atomicRequired: false,
3412
- calls: [{ to: token, value: "0x0", data }],
4656
+ chainId: (0, import_viem10.toHex)(chainId),
4657
+ atomicRequired: transfers.length > 1,
4658
+ calls,
3413
4659
  capabilities: {
3414
- paymasterService: { url: absolutePaymasterUrl() }
4660
+ paymasterService: { url: paymasterUrl }
3415
4661
  }
3416
4662
  }
3417
4663
  ]
@@ -3431,7 +4677,24 @@ function makeSponsoredCallsCallback(deps) {
3431
4677
  params: [callsId]
3432
4678
  });
3433
4679
  const txHash = status?.receipts?.[0]?.transactionHash;
3434
- if (txHash) return txHash;
4680
+ if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
4681
+ throw new OwneyError(
4682
+ "SPONSOR_REQUEST_FAILED",
4683
+ "The sponsored deposit did not complete successfully.",
4684
+ { chainId, callsId, safeToFallback: false }
4685
+ );
4686
+ }
4687
+ if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
4688
+ if (status?.receipts?.some(
4689
+ (receipt) => receipt.transactionHash !== txHash
4690
+ ))
4691
+ throw new OwneyError(
4692
+ "SPONSORED_CALLS_NO_RECEIPT",
4693
+ "The wallet returned multiple transactions for an atomic deposit. Check activity before trying again.",
4694
+ { chainId, callsId }
4695
+ );
4696
+ return txHash;
4697
+ }
3435
4698
  if (pollIntervalMs > 0) {
3436
4699
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
3437
4700
  }
@@ -3442,6 +4705,11 @@ function makeSponsoredCallsCallback(deps) {
3442
4705
  { chainId, callsId }
3443
4706
  );
3444
4707
  };
4708
+ const callback = makeVerificationAwareDepositCallback(
4709
+ (to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
4710
+ );
4711
+ registerDepositBatch(callback, batch);
4712
+ return callback;
3445
4713
  }
3446
4714
 
3447
4715
  // src/client.ts
@@ -3471,15 +4739,20 @@ var SPONSORED_USDC_BY_CHAIN = {
3471
4739
  1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
3472
4740
  };
3473
4741
  var VIEM_CHAIN2 = {
3474
- 8453: import_chains2.base,
3475
- 42161: import_chains2.arbitrum,
3476
- 1: import_chains2.mainnet
4742
+ 8453: import_chains4.base,
4743
+ 42161: import_chains4.arbitrum,
4744
+ 1: import_chains4.mainnet
3477
4745
  };
3478
4746
  var SPONSORED_WETH_BY_CHAIN = {
3479
4747
  8453: "0x4200000000000000000000000000000000000006",
3480
4748
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
3481
4749
  1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
3482
4750
  };
4751
+ var SPONSORED_TOKENS_BY_ASSET = { USDC: SPONSORED_USDC_BY_CHAIN, WETH: SPONSORED_WETH_BY_CHAIN };
4752
+ function sponsoredTokensFor(asset) {
4753
+ if (!Object.prototype.hasOwnProperty.call(SPONSORED_TOKENS_BY_ASSET, asset)) throw new OwneyError("ASSET_UNSUPPORTED", `No sponsored token configured for ${asset}`);
4754
+ return SPONSORED_TOKENS_BY_ASSET[asset];
4755
+ }
3483
4756
  function shouldFallbackToUserPaid(error, asset, appCallback) {
3484
4757
  return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
3485
4758
  }
@@ -3501,6 +4774,8 @@ var OwneySDK = class {
3501
4774
  orgAgentConfig;
3502
4775
  orgAgentConfigPromise = null;
3503
4776
  zyfaiRpcUrls;
4777
+ yieldseekerApiBaseUrl;
4778
+ yieldseekerSiweOrigin;
3504
4779
  routingApiBaseUrl;
3505
4780
  referralSource;
3506
4781
  cachedSponsoredCallback = null;
@@ -3523,6 +4798,8 @@ var OwneySDK = class {
3523
4798
  this.apiKey = config.apiKey;
3524
4799
  if (config.debug) setOwneyDebug(true);
3525
4800
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
4801
+ this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
4802
+ this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
3526
4803
  this.routingApiBaseUrl = config.routingApiBaseUrl;
3527
4804
  this.paymasterServiceUrl = config.paymasterServiceUrl;
3528
4805
  this.referralSource = config.referralSource;
@@ -3556,6 +4833,7 @@ var OwneySDK = class {
3556
4833
  * After calling this, `connect()` must be called again before using agent methods.
3557
4834
  */
3558
4835
  async disconnect() {
4836
+ this.state = null;
3559
4837
  for (const agent of this.agents.values()) {
3560
4838
  await agent.disconnect();
3561
4839
  }
@@ -3609,18 +4887,13 @@ var OwneySDK = class {
3609
4887
  }
3610
4888
  return this.state.provider;
3611
4889
  }
3612
- /**
3613
- * Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
3614
- * used when the caller omits `depositCallback`. Wraps the connected EIP-1193
3615
- * provider with viem `custom(provider)` to read token meta and sign the
3616
- * `TransferWithAuthorization`, then POSTs to the sponsor API.
3617
- */
4890
+ /** Builds the default USDC batch callback for the connected wallet. */
3618
4891
  getDefaultSponsoredCallback(onApproved) {
3619
4892
  if (!onApproved && this.cachedSponsoredCallback)
3620
4893
  return this.cachedSponsoredCallback;
3621
4894
  const provider = this.requireConnectedProvider();
3622
4895
  const owner = this.state.walletAddress;
3623
- const callback = makeSponsoredDepositCallback({
4896
+ const callback = makeSponsoredTokenCallback({
3624
4897
  apiKey: this.apiKey,
3625
4898
  baseUrl: this.routingApiBaseUrl,
3626
4899
  ownerAddress: owner,
@@ -3629,35 +4902,32 @@ var OwneySDK = class {
3629
4902
  // Casts work around viem's chain-narrowed Client vs the generic
3630
4903
  // PublicClient/WalletClient param types — structurally identical at
3631
4904
  // runtime, but the two share a name TS treats as unrelated.
3632
- getPublicClient: (cid) => (0, import_viem8.createPublicClient)({
4905
+ getPublicClient: (cid) => (0, import_viem11.createPublicClient)({
3633
4906
  chain: VIEM_CHAIN2[cid],
3634
- transport: (0, import_viem8.custom)(provider)
4907
+ transport: (0, import_viem11.custom)(provider)
3635
4908
  }),
3636
- getWalletClient: (cid) => (0, import_viem8.createWalletClient)({
4909
+ getWalletClient: (cid) => (0, import_viem11.createWalletClient)({
3637
4910
  account: owner,
3638
4911
  chain: VIEM_CHAIN2[cid],
3639
- transport: (0, import_viem8.custom)(provider)
4912
+ transport: (0, import_viem11.custom)(provider)
3640
4913
  })
3641
4914
  });
3642
4915
  if (!onApproved) this.cachedSponsoredCallback = callback;
3643
4916
  return callback;
3644
4917
  }
3645
- /**
3646
- * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
3647
- * callback used when the caller omits `depositCallback` for a WETH
3648
- * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
3649
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
3650
- */
4918
+ /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
3651
4919
  getDefaultSponsoredCallsCallback(asset, onApproved) {
3652
4920
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
3653
4921
  if (!onApproved && cached) return cached;
3654
4922
  const provider = this.requireConnectedProvider();
3655
4923
  const callback = makeSponsoredCallsCallback({
4924
+ apiKey: this.apiKey,
4925
+ routingApiBaseUrl: this.routingApiBaseUrl,
3656
4926
  provider,
3657
4927
  ownerAddress: this.state.walletAddress,
3658
4928
  paymasterServiceUrl: this.paymasterServiceUrl,
3659
4929
  onApproved,
3660
- tokenAddressByChain: asset === "WETH" ? SPONSORED_WETH_BY_CHAIN : SPONSORED_USDC_BY_CHAIN
4930
+ tokenAddressByChain: sponsoredTokensFor(asset)
3661
4931
  });
3662
4932
  if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
3663
4933
  return callback;
@@ -3666,14 +4936,14 @@ var OwneySDK = class {
3666
4936
  * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
3667
4937
  * callback used when the caller omits `depositCallback` for a WETH deposit.
3668
4938
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
3669
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
4939
+ * single-use batch authorization instead of an EIP-3009 authorization.
3670
4940
  */
3671
4941
  getDefaultWethSponsoredCallback(onApproved) {
3672
4942
  if (!onApproved && this.cachedWethSponsoredCallback)
3673
4943
  return this.cachedWethSponsoredCallback;
3674
4944
  const provider = this.requireConnectedProvider();
3675
4945
  const owner = this.state.walletAddress;
3676
- const callback = makeSponsoredWethCallback({
4946
+ const callback = makeSponsoredTokenCallback({
3677
4947
  apiKey: this.apiKey,
3678
4948
  baseUrl: this.routingApiBaseUrl,
3679
4949
  ownerAddress: owner,
@@ -3682,14 +4952,14 @@ var OwneySDK = class {
3682
4952
  // Casts work around viem's chain-narrowed Client vs the generic
3683
4953
  // PublicClient/WalletClient param types — structurally identical at
3684
4954
  // runtime, but the two share a name TS treats as unrelated.
3685
- getPublicClient: (cid) => (0, import_viem8.createPublicClient)({
4955
+ getPublicClient: (cid) => (0, import_viem11.createPublicClient)({
3686
4956
  chain: VIEM_CHAIN2[cid],
3687
- transport: (0, import_viem8.custom)(provider)
4957
+ transport: (0, import_viem11.custom)(provider)
3688
4958
  }),
3689
- getWalletClient: (cid) => (0, import_viem8.createWalletClient)({
4959
+ getWalletClient: (cid) => (0, import_viem11.createWalletClient)({
3690
4960
  account: owner,
3691
4961
  chain: VIEM_CHAIN2[cid],
3692
- transport: (0, import_viem8.custom)(provider)
4962
+ transport: (0, import_viem11.custom)(provider)
3693
4963
  })
3694
4964
  });
3695
4965
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -3763,7 +5033,14 @@ var OwneySDK = class {
3763
5033
  this.routingApiBaseUrl
3764
5034
  );
3765
5035
  this.disabledAgents.clear();
3766
- for (const { key: key2, agent_type, is_enabled } of agentKeys) {
5036
+ for (const {
5037
+ key: key2,
5038
+ agent_type,
5039
+ is_enabled,
5040
+ is_configured
5041
+ } of agentKeys) {
5042
+ const configured = is_configured ?? Boolean(key2);
5043
+ if (!configured) continue;
3767
5044
  const agent = this.createAgent(agent_type, key2);
3768
5045
  if (!agent) continue;
3769
5046
  this.agents.set(agent_type, agent);
@@ -3787,8 +5064,15 @@ var OwneySDK = class {
3787
5064
  }
3788
5065
  createAgent(agentId, key2) {
3789
5066
  if (agentId === "zyfai") {
5067
+ if (!key2) return null;
3790
5068
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
3791
5069
  }
5070
+ if (agentId === "yieldseeker") {
5071
+ return new YieldseekerAgent(this.apiKey, {
5072
+ auth: { origin: this.yieldseekerSiweOrigin },
5073
+ baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
5074
+ });
5075
+ }
3792
5076
  return null;
3793
5077
  }
3794
5078
  /**
@@ -3833,9 +5117,10 @@ var OwneySDK = class {
3833
5117
  * If provided, ALL specified agents must support the chainId or the call
3834
5118
  * throws before activating any agent.
3835
5119
  */
3836
- async activateAgent(chainId, agentId) {
5120
+ async activateAgent(chainId, agentId, asset) {
3837
5121
  const state = this.requireState();
3838
5122
  await this.ensureAgentsInitialized();
5123
+ this.assertActivationSession(state);
3839
5124
  if (agentId !== void 0) {
3840
5125
  if (agentId.length === 0) {
3841
5126
  throw new OwneyError(
@@ -3869,7 +5154,7 @@ var OwneySDK = class {
3869
5154
  this.activeAgents.add(id);
3870
5155
  }
3871
5156
  state.chainId = chainId;
3872
- await this.activateAgentsInTurn(agents, state, chainId);
5157
+ await this.activateAgentsInTurn(agents, state, chainId, asset);
3873
5158
  return;
3874
5159
  }
3875
5160
  const compatible = [...this.agents.values()].filter(
@@ -3890,7 +5175,12 @@ var OwneySDK = class {
3890
5175
  const enabledCompatible = compatible.filter(
3891
5176
  (agent) => !this.isAgentDisabled(agent.id)
3892
5177
  );
3893
- await this.activateAgentsInTurn(enabledCompatible, state, chainId);
5178
+ await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
5179
+ }
5180
+ assertActivationSession(state) {
5181
+ if (this.state !== state) {
5182
+ throw new OwneyError("NOT_CONNECTED", "Wallet connection changed. Please try again.");
5183
+ }
3894
5184
  }
3895
5185
  /**
3896
5186
  * Activate agents ONE AT A TIME, each followed by its org policy.
@@ -3905,26 +5195,51 @@ var OwneySDK = class {
3905
5195
  * Serializing costs no real wall-clock: the user can only approve one prompt
3906
5196
  * at a time anyway.
3907
5197
  *
3908
- * Every agent is attempted even if an earlier one fails, so one declined
3909
- * signature can't deny the remaining agents their turn. The first failure is
3910
- * rethrown (matching the previous `Promise.all` rejection) once all agents
3911
- * have had a chance to activate.
5198
+ * Stop at the first failure so a canceled sign-in does not open another
5199
+ * agent's wallet prompt. Report any earlier successes for diagnostics; the
5200
+ * app discards the session when the complete sign-in does not succeed.
3912
5201
  */
3913
- async activateAgentsInTurn(agents, state, chainId) {
5202
+ async activateAgentsInTurn(agents, state, chainId, asset) {
3914
5203
  let firstError = null;
5204
+ const activatedAgentIds = [];
5205
+ const failedAgents = [];
3915
5206
  for (const agent of agents) {
5207
+ this.assertActivationSession(state);
3916
5208
  try {
3917
- await agent.activateAgent(state, chainId);
5209
+ await agent.activateAgent(state, chainId, asset);
5210
+ this.assertActivationSession(state);
3918
5211
  await this.applyOrgPolicyTo(agent, state, chainId);
5212
+ this.assertActivationSession(state);
5213
+ activatedAgentIds.push(agent.id);
3919
5214
  } catch (error) {
5215
+ this.assertActivationSession(state);
5216
+ 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.";
5217
+ failedAgents.push({
5218
+ agentId: agent.id,
5219
+ code: error instanceof OwneyError ? error.code : void 0,
5220
+ message,
5221
+ ...error instanceof OwneyError && error.details ? { details: error.details } : {}
5222
+ });
3920
5223
  if (firstError === null) {
3921
5224
  firstError = error;
3922
5225
  } else {
3923
5226
  console.error(`activateAgent(${agent.id}) failed:`, error);
3924
5227
  }
5228
+ break;
3925
5229
  }
3926
5230
  }
3927
- if (firstError !== null) throw firstError;
5231
+ if (firstError === null) return;
5232
+ if (activatedAgentIds.length === 0) throw firstError;
5233
+ const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
5234
+ const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
5235
+ const failureMessages = failedAgents.map(
5236
+ ({ agentId, message }) => `${this.formatAgentName(agentId)} activation failed: ${message}`
5237
+ ).join(" ");
5238
+ throw new OwneyError(
5239
+ "AGENT_ACTIVATION_PARTIAL_FAILURE",
5240
+ `${activeNames} activated. ${failureMessages}`,
5241
+ { activatedAgentIds, failedAgentIds, failures: failedAgents }
5242
+ );
3928
5243
  }
3929
5244
  /**
3930
5245
  * Deposit into a specific agent, or distribute across all eligible agents.
@@ -3934,7 +5249,8 @@ var OwneySDK = class {
3934
5249
  * @param options.asset - Asset symbol to deposit (e.g. "USDC")
3935
5250
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
3936
5251
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
3937
- * split amount and smart wallet address — expect multiple wallet prompts.
5252
+ * split amount and smart wallet address. Default sponsored deposits batch
5253
+ * all shares into one signature; custom callbacks still run once per agent.
3938
5254
  * @param options.agentId - Optional explicit target. Otherwise split equally,
3939
5255
  * or fund remaining agents when a recovery deposit cannot meet every minimum.
3940
5256
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
@@ -4019,6 +5335,39 @@ var OwneySDK = class {
4019
5335
  }
4020
5336
  );
4021
5337
  }
5338
+ const batchTransfer = getDepositBatchTransfer(effectiveCallback);
5339
+ if (!depositCallback && batchTransfer) {
5340
+ return runAgentDepositBatch(
5341
+ chainId,
5342
+ agentAmounts.map(({ agent, amount: amount2 }) => ({
5343
+ id: agent.id,
5344
+ amount: amount2,
5345
+ run: (callback) => withFailureReporting(
5346
+ this.apiKey,
5347
+ agent.id,
5348
+ () => agent.deposit(state, chainId, amount2, asset, callback),
5349
+ this.routingApiBaseUrl
5350
+ )
5351
+ })),
5352
+ async (cid, transfers) => {
5353
+ try {
5354
+ return await batchTransfer(cid, transfers);
5355
+ } catch (error) {
5356
+ if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
5357
+ throw error;
5358
+ const requiredAmount = transfers.reduce(
5359
+ (sum, transfer) => sum + BigInt(transfer.amount),
5360
+ 0n
5361
+ );
5362
+ await this.approvePermit2(
5363
+ asset,
5364
+ requiredAmount
5365
+ );
5366
+ return batchTransfer(cid, transfers);
5367
+ }
5368
+ }
5369
+ );
5370
+ }
4022
5371
  const agentResults = {};
4023
5372
  for (const [
4024
5373
  index,
@@ -4058,7 +5407,7 @@ var OwneySDK = class {
4058
5407
  *
4059
5408
  * 1. Missing Permit2 allowance: when the app did not supply its own
4060
5409
  * callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
4061
- * WETH deposit, this is the wallet's first gasless WETH deposit. We send
5410
+ * token deposit, this is the wallet's first Permit2 deposit for that token. We send
4062
5411
  * the one-time (user-paid) Permit2 approval via `approvePermit2()` and
4063
5412
  * retry the SAME sponsored attempt once. Bounded to one approval attempt
4064
5413
  * per call so a wallet/agent that keeps reporting the allowance as
@@ -4095,12 +5444,15 @@ var OwneySDK = class {
4095
5444
  try {
4096
5445
  return await attempt(effectiveCallback);
4097
5446
  } catch (error) {
4098
- if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
5447
+ if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
4099
5448
  approvalAttempted = true;
4100
5449
  console.warn(
4101
- "[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
5450
+ "[owney-sdk] First token deposit: sending one-time Permit2 approval..."
5451
+ );
5452
+ await this.approvePermit2(
5453
+ asset,
5454
+ BigInt(amount)
4102
5455
  );
4103
- await this.approvePermit2();
4104
5456
  continue;
4105
5457
  }
4106
5458
  if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
@@ -4138,10 +5490,10 @@ var OwneySDK = class {
4138
5490
  agent,
4139
5491
  amount: i === agents.length - 1 ? perAgent + remainder : perAgent
4140
5492
  }));
4141
- const valid = splits.filter(
5493
+ const valid2 = splits.filter(
4142
5494
  (s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
4143
5495
  );
4144
- if (valid.length === agents.length) {
5496
+ if (valid2.length === agents.length) {
4145
5497
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
4146
5498
  }
4147
5499
  const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
@@ -4160,6 +5512,11 @@ var OwneySDK = class {
4160
5512
  )
4161
5513
  }));
4162
5514
  }
5515
+ formatAgentName(agentId) {
5516
+ if (agentId === "zyfai") return "Zyfai";
5517
+ if (agentId === "yieldseeker") return "Yieldseeker";
5518
+ return agentId;
5519
+ }
4163
5520
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
4164
5521
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
4165
5522
  const parsedAmount = BigInt(amount);
@@ -4191,12 +5548,12 @@ var OwneySDK = class {
4191
5548
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
4192
5549
  );
4193
5550
  const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
4194
- const position = (balance.positions ?? []).find((p) => {
5551
+ const position2 = (balance.positions ?? []).find((p) => {
4195
5552
  const positionChain = p.chain.trim().toUpperCase();
4196
5553
  const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
4197
5554
  return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
4198
5555
  });
4199
- return !!token && Number(token.amount) > 0 || !!position;
5556
+ return !!token && Number(token.amount) > 0 || !!position2;
4200
5557
  } catch (error) {
4201
5558
  if (requireReliableRead) {
4202
5559
  throw new OwneyError(
@@ -4242,330 +5599,6 @@ var OwneySDK = class {
4242
5599
  return eligible;
4243
5600
  }
4244
5601
  // --- Fund operations ---
4245
- // --- Swap to yield (ROUT-242) ---
4246
- /** Lazily built so an app that never swaps pays nothing for it. */
4247
- swapApiClient;
4248
- swapApi() {
4249
- this.swapApiClient ??= createSwapApi(
4250
- this.routingApiBaseUrl ?? ROUTING_API_BASE_URL,
4251
- this.apiKey
4252
- );
4253
- return this.swapApiClient;
4254
- }
4255
- /**
4256
- * Put the wallet on `chainId`, or fail with something actionable.
4257
- *
4258
- * Reuses the same guard the deposit rail uses, which re-reads the chain after
4259
- * switching — some wallets resolve wallet_switchEthereumChain before the
4260
- * network has actually changed.
4261
- */
4262
- async ensureSwapChain(chainId) {
4263
- const provider = this.requireConnectedProvider();
4264
- const state = this.requireState();
4265
- const chain = VIEM_CHAIN2[chainId];
4266
- if (!chain) {
4267
- throw new OwneyError(
4268
- "CHAIN_UNSUPPORTED",
4269
- `Chain ${chainId} is not supported`,
4270
- { chainId }
4271
- );
4272
- }
4273
- await ensureWalletOnChain(
4274
- (0, import_viem8.createPublicClient)({ chain, transport: (0, import_viem8.custom)(provider) }),
4275
- (0, import_viem8.createWalletClient)({
4276
- account: state.walletAddress,
4277
- chain,
4278
- transport: (0, import_viem8.custom)(provider)
4279
- }),
4280
- chainId
4281
- );
4282
- }
4283
- /**
4284
- * Binds the executor's abstract deps to this client's wallet.
4285
- *
4286
- * Kept as a builder rather than baked into the executor so the whole swap
4287
- * flow stays testable without a provider — the executor never imports viem.
4288
- */
4289
- buildSwapDeps(quote) {
4290
- const state = this.requireState();
4291
- const provider = this.requireConnectedProvider();
4292
- const srcChain = VIEM_CHAIN2[quote.src.chainId];
4293
- const dstChain = VIEM_CHAIN2[quote.dst.chainId];
4294
- const wallet = (0, import_viem8.createWalletClient)({
4295
- account: state.walletAddress,
4296
- chain: srcChain,
4297
- transport: (0, import_viem8.custom)(provider)
4298
- });
4299
- const srcPublic = (0, import_viem8.createPublicClient)({
4300
- chain: srcChain,
4301
- transport: swapReadTransport(quote.src.chainId, this.zyfaiRpcUrls)
4302
- });
4303
- const dstPublic = (0, import_viem8.createPublicClient)({
4304
- chain: dstChain,
4305
- transport: swapReadTransport(quote.dst.chainId, this.zyfaiRpcUrls)
4306
- });
4307
- return {
4308
- api: this.swapApi(),
4309
- // Native-aware, like readSourceBalance below. Native ETH is never a
4310
- // DEPOSIT target, so this only ever mattered once withdrawal shipped —
4311
- // and there it is the headline case. balanceOf() on the 0xEeee sentinel
4312
- // reverts, which would have read as "the swap landed nothing".
4313
- readTargetBalance: async () => {
4314
- const dst = quote.dst.address;
4315
- if (dst.toLowerCase().startsWith("0xeeee")) {
4316
- return dstPublic.getBalance({ address: state.walletAddress });
4317
- }
4318
- return dstPublic.readContract({
4319
- address: dst,
4320
- abi: import_viem8.erc20Abi,
4321
- functionName: "balanceOf",
4322
- args: [state.walletAddress]
4323
- });
4324
- },
4325
- sendTransaction: async (tx) => {
4326
- const hash = await wallet.sendTransaction({
4327
- to: tx.to,
4328
- data: tx.data,
4329
- value: BigInt(tx.value || "0"),
4330
- account: state.walletAddress,
4331
- chain: srcChain
4332
- });
4333
- const receipt = await srcPublic.waitForTransactionReceipt({
4334
- timeout: receiptTimeoutMs(quote.src.chainId),
4335
- hash,
4336
- confirmations: 1
4337
- });
4338
- if (receipt.status !== "success") {
4339
- throw new OwneyError(
4340
- "SWAP_REQUEST_FAILED",
4341
- `Swap transaction reverted (tx ${hash})`,
4342
- { hash }
4343
- );
4344
- }
4345
- return hash;
4346
- },
4347
- signTypedData: (typedData) => wallet.signTypedData({
4348
- account: state.walletAddress,
4349
- ...typedData
4350
- }),
4351
- // Chain-bound like every other read here: the wallet provider's chain is
4352
- // not ours to rely on mid-swap.
4353
- readSourceBalance: async () => {
4354
- const src = quote.src.address;
4355
- if (src.toLowerCase().startsWith("0xeeee")) {
4356
- return srcPublic.getBalance({ address: state.walletAddress });
4357
- }
4358
- return srcPublic.readContract({
4359
- address: src,
4360
- abi: ERC20_ALLOWANCE_ABI,
4361
- functionName: "balanceOf",
4362
- args: [state.walletAddress]
4363
- });
4364
- },
4365
- readAllowance: (spender) => srcPublic.readContract({
4366
- address: quote.src.address,
4367
- abi: ERC20_ALLOWANCE_ABI,
4368
- functionName: "allowance",
4369
- args: [state.walletAddress, spender]
4370
- }),
4371
- approve: async (spender, amount) => {
4372
- const hash = await wallet.writeContract({
4373
- address: quote.src.address,
4374
- abi: ERC20_ALLOWANCE_ABI,
4375
- functionName: "approve",
4376
- args: [spender, amount],
4377
- account: state.walletAddress,
4378
- chain: srcChain
4379
- });
4380
- await srcPublic.waitForTransactionReceipt({
4381
- hash,
4382
- confirmations: 1,
4383
- timeout: receiptTimeoutMs(quote.src.chainId)
4384
- });
4385
- return hash;
4386
- },
4387
- ensureChain: (chainId) => this.ensureSwapChain(chainId),
4388
- runner: {
4389
- readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
4390
- submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
4391
- orderStatus: (h) => this.swapApi().orderStatus(h),
4392
- sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
4393
- now: () => Date.now()
4394
- }
4395
- };
4396
- }
4397
- /**
4398
- * Assets the user may pay with, and what each chain deposits into.
4399
- *
4400
- * The source list is deliberately wider than the deposit list: it includes
4401
- * native ETH and USDT, which Owney never holds but users often do.
4402
- */
4403
- async getSwapTokens() {
4404
- return this.swapApi().listTokens();
4405
- }
4406
- /**
4407
- * Price a swap without committing to it.
4408
- *
4409
- * `dstAmountMin` is the number to validate against a deposit minimum —
4410
- * `dst.amount` is an estimate that a decaying auction or slippage can undercut,
4411
- * and a swap landing below the floor leaves the user swapped but not
4412
- * deposited.
4413
- */
4414
- async getSwapQuote(params) {
4415
- const state = this.requireState();
4416
- return this.swapApi().quote({
4417
- ...params,
4418
- walletAddress: state.walletAddress
4419
- });
4420
- }
4421
- /**
4422
- * Swap an asset the user holds into a deposit asset, then deposit it.
4423
- *
4424
- * Kept separate from `deposit()` rather than bolted on as an option: the
4425
- * return shape differs, the staging callback is meaningless on the plain
4426
- * path, and integrators who never swap should not have to reason about any
4427
- * of it.
4428
- *
4429
- * The deposit runs on the MEASURED arrival, not the quote. A quote is an
4430
- * estimate, so depositing the quoted figure would either strand dust or try
4431
- * to move funds that never came.
4432
- *
4433
- * Failure modes differ in a way callers must respect. A same-chain swap is
4434
- * atomic — if it fails, nothing moved. A cross-chain swap escrows the user's
4435
- * funds first, so SWAP_ORDER_EXPIRED / REFUNDED / CANCELLED all mean the
4436
- * money left the wallet. Only the former can honestly say "nothing has left
4437
- * your wallet".
4438
- */
4439
- async swapAndDeposit(options) {
4440
- const state = this.requireState();
4441
- const api = this.swapApi();
4442
- const quote = await api.quote({
4443
- from: options.from,
4444
- to: options.to,
4445
- walletAddress: state.walletAddress
4446
- });
4447
- await this.ensureSwapChain(quote.src.chainId);
4448
- const swap = await executeSwap(this.buildSwapDeps(quote), {
4449
- quote,
4450
- walletAddress: state.walletAddress,
4451
- ...options.slippage === void 0 ? {} : { slippage: options.slippage },
4452
- ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
4453
- });
4454
- options.onSwapProgress?.("depositing");
4455
- await this.ensureSwapChain(quote.dst.chainId);
4456
- const deposit = await this.deposit({
4457
- amount: swap.received,
4458
- asset: options.to.symbol,
4459
- ...options.agentId ? { agentId: options.agentId } : {}
4460
- });
4461
- return { swap, deposit };
4462
- }
4463
- /**
4464
- * Withdraw from an agent and swap the proceeds into whatever the user wants
4465
- * to hold, delivered to their own wallet.
4466
- *
4467
- * The mirror of `swapAndDeposit()`, with one structural difference that
4468
- * drives the whole implementation: a deposit swap starts from funds already
4469
- * sitting in the wallet, but a withdrawal has to wait for them. The agent's
4470
- * provider acknowledges a withdrawal and *then* queues the on-chain transfer
4471
- * to the EOA, so `withdraw()` resolving means "accepted", not "arrived".
4472
- * Quoting before the tokens land would size the swap against a balance that
4473
- * is not there yet.
4474
- *
4475
- * The swap is therefore sized from the MEASURED arrival, exactly as the
4476
- * deposit path sizes its deposit from the measured swap output. On a full
4477
- * withdrawal there is no other number available — "MAX" has no figure until
4478
- * the agent picks one.
4479
- *
4480
- * **Failure here is not symmetrical with the deposit path.** A failed
4481
- * deposit-swap leaves the user holding what they started with. A failed
4482
- * withdrawal-swap leaves them holding the AGENT'S asset in their own wallet:
4483
- * the money is out, safe, and in the wrong denomination. Both
4484
- * `WITHDRAW_ARRIVAL_TIMEOUT` and `WITHDRAW_SWAP_FAILED` carry `withdrawn` for
4485
- * that reason — the UI has to tell the user where their money actually is,
4486
- * and must never present either as a lost withdrawal.
4487
- */
4488
- async withdrawAndSwap(options) {
4489
- const state = this.requireState();
4490
- const activeChainId = this.requireChainId();
4491
- if (options.from.chainId !== activeChainId) {
4492
- throw new OwneyError(
4493
- "CHAIN_MISMATCH",
4494
- `Cannot withdraw from chain ${options.from.chainId} while the active chain is ${activeChainId}. Activate on that chain first.`,
4495
- { requested: options.from.chainId, active: activeChainId }
4496
- );
4497
- }
4498
- const asset = SupportedAssets.find(
4499
- (a) => a.chainId === options.from.chainId && a.symbol === options.from.symbol.toUpperCase()
4500
- );
4501
- if (!asset) {
4502
- throw new OwneyError(
4503
- "WITHDRAW_NO_PERMITTED_TOKENS",
4504
- `${options.from.symbol} on chain ${options.from.chainId} is not an asset Owney holds`,
4505
- { ...options.from }
4506
- );
4507
- }
4508
- const srcChain = VIEM_CHAIN2[options.from.chainId];
4509
- const srcPublic = (0, import_viem8.createPublicClient)({
4510
- chain: srcChain,
4511
- transport: swapReadTransport(options.from.chainId, this.zyfaiRpcUrls)
4512
- });
4513
- const readWalletBalance = () => srcPublic.readContract({
4514
- address: asset.address,
4515
- abi: import_viem8.erc20Abi,
4516
- functionName: "balanceOf",
4517
- args: [state.walletAddress]
4518
- });
4519
- const baseline = await readWalletBalance();
4520
- debugLog("owney-sdk", "withdrawAndSwap: baseline", {
4521
- asset: `${asset.symbol}@${asset.chainId}`,
4522
- baseline: baseline.toString()
4523
- });
4524
- options.onSwapProgress?.("withdrawing");
4525
- const withdraw = await this.withdraw({
4526
- asset: options.from.symbol,
4527
- ...options.amount === void 0 ? {} : { amount: options.amount },
4528
- ...options.agentId ? { agentId: options.agentId } : {}
4529
- });
4530
- const arrived = await awaitWithdrawalArrival({
4531
- readBalance: readWalletBalance,
4532
- baseline,
4533
- ...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs }
4534
- });
4535
- const withdrawn = arrived.toString();
4536
- options.onSwapProgress?.("withdrawn");
4537
- try {
4538
- const quote = await this.swapApi().quote({
4539
- from: { ...options.from, amount: withdrawn },
4540
- to: options.to,
4541
- direction: "withdraw",
4542
- walletAddress: state.walletAddress
4543
- });
4544
- await this.ensureSwapChain(quote.src.chainId);
4545
- const swap = await executeSwap(this.buildSwapDeps(quote), {
4546
- quote,
4547
- walletAddress: state.walletAddress,
4548
- direction: "withdraw",
4549
- ...options.slippage === void 0 ? {} : { slippage: options.slippage },
4550
- ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
4551
- });
4552
- return { withdraw, withdrawn, swap };
4553
- } catch (error) {
4554
- throw new OwneyError(
4555
- "WITHDRAW_SWAP_FAILED",
4556
- `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}.`,
4557
- {
4558
- withdrawn,
4559
- asset: asset.symbol,
4560
- chainId: asset.chainId,
4561
- intendedSymbol: options.to.symbol,
4562
- intendedChainId: options.to.chainId,
4563
- cause: error instanceof Error ? error.message : String(error),
4564
- ...error instanceof OwneyError ? { causeCode: error.code } : {}
4565
- }
4566
- );
4567
- }
4568
- }
4569
5602
  /**
4570
5603
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
4571
5604
  * Validates that the asset is supported by the target agent(s) on the active chain.
@@ -4635,6 +5668,10 @@ var OwneySDK = class {
4635
5668
  }
4636
5669
  const requested = BigInt(amount);
4637
5670
  const aggregated = await this.getBalances();
5671
+ const unavailableAgents = eligibleAgents.filter(
5672
+ (agent) => !(agent.id in aggregated.agentBalances)
5673
+ );
5674
+ const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
4638
5675
  const balances = projectAgentBalancesForAsset(
4639
5676
  eligibleAgents,
4640
5677
  aggregated.agentBalances,
@@ -4643,7 +5680,18 @@ var OwneySDK = class {
4643
5680
  assetInfo.decimals
4644
5681
  );
4645
5682
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
4646
- if (totalAvailable < requested) {
5683
+ if (totalAvailable === 0n && unavailableAgents.length > 0) {
5684
+ throw new OwneyError(
5685
+ "WITHDRAW_BALANCE_UNAVAILABLE",
5686
+ `Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
5687
+ {
5688
+ asset,
5689
+ unavailableAgents: unavailableAgentIds,
5690
+ agentErrors: aggregated.agentErrors
5691
+ }
5692
+ );
5693
+ }
5694
+ if (totalAvailable < requested && unavailableAgents.length === 0) {
4647
5695
  throw new OwneyError(
4648
5696
  "WITHDRAW_INSUFFICIENT_BALANCE",
4649
5697
  `Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
@@ -4654,6 +5702,7 @@ var OwneySDK = class {
4654
5702
  }
4655
5703
  );
4656
5704
  }
5705
+ const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
4657
5706
  const disabledBalances = balances.filter(
4658
5707
  (b) => this.isAgentDisabled(b.agent.id)
4659
5708
  );
@@ -4662,7 +5711,7 @@ var OwneySDK = class {
4662
5711
  );
4663
5712
  const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
4664
5713
  disabledBalances,
4665
- requested
5714
+ plannedTarget
4666
5715
  );
4667
5716
  const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
4668
5717
  const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
@@ -4672,7 +5721,9 @@ var OwneySDK = class {
4672
5721
  }));
4673
5722
  const plans = [...disabledPlans, ...enabledPlans];
4674
5723
  const results = {};
4675
- const agentErrors = {};
5724
+ const agentErrors = {
5725
+ ...aggregated.agentErrors ?? {}
5726
+ };
4676
5727
  for (let i = 0; i < plans.length; i++) {
4677
5728
  const p = plans[i];
4678
5729
  if (p.planned === 0n) continue;
@@ -4719,7 +5770,8 @@ var OwneySDK = class {
4719
5770
  requested: amount,
4720
5771
  withdrawn: withdrawn.toString(),
4721
5772
  partialResults: results,
4722
- agentErrors
5773
+ agentErrors,
5774
+ ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
4723
5775
  }
4724
5776
  );
4725
5777
  }
@@ -4736,24 +5788,25 @@ var OwneySDK = class {
4736
5788
  const chainId = this.requireChainId();
4737
5789
  if (agentId) {
4738
5790
  const agent = this.getAgent(agentId);
4739
- const result = await this.readAgent(
4740
- agent,
4741
- "balances",
4742
- () => agent.getBalances(state, chainId)
4743
- );
4744
- return result;
5791
+ const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
5792
+ return {
5793
+ ...result,
5794
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5795
+ };
4745
5796
  }
4746
5797
  let totalBalance = 0;
4747
5798
  const results = {};
4748
5799
  const entries = [...this.getActiveAgents().entries()];
4749
5800
  const balanceResults = await Promise.allSettled(
4750
5801
  entries.map(async ([id, agent]) => {
4751
- const b = await this.readAgent(
4752
- agent,
4753
- "balances",
4754
- () => agent.getBalances(state, chainId)
4755
- );
4756
- return [id, b];
5802
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
5803
+ return [
5804
+ id,
5805
+ {
5806
+ ...b,
5807
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5808
+ }
5809
+ ];
4757
5810
  })
4758
5811
  );
4759
5812
  let successCount = 0;
@@ -4773,9 +5826,9 @@ var OwneySDK = class {
4773
5826
  const reason = settledResult.reason;
4774
5827
  agentFailures.push(reason);
4775
5828
  const retryDelay = rateLimitDelay(reason);
4776
- if (retryDelay !== void 0)
4777
- agentRetryAt[agentId2] = Date.now() + retryDelay;
5829
+ if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
4778
5830
  agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
5831
+ console.error(`[owney-sdk] Balance fetch failed for agent "${agentId2}":`, reason);
4779
5832
  }
4780
5833
  if (successCount === 0) {
4781
5834
  throw new OwneyError(
@@ -4801,22 +5854,14 @@ var OwneySDK = class {
4801
5854
  const chainId = this.requireChainId();
4802
5855
  if (agentId) {
4803
5856
  const agent = this.getAgent(agentId);
4804
- return this.readAgent(
4805
- agent,
4806
- "earnings",
4807
- () => agent.getEarnings(state, chainId)
4808
- );
5857
+ return this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
4809
5858
  }
4810
5859
  let totalEarnings = 0;
4811
5860
  const results = {};
4812
5861
  const entries = [...this.getActiveAgents().entries()];
4813
5862
  const earningsResults = await Promise.all(
4814
5863
  entries.map(async ([id, agent]) => {
4815
- const e = await this.readAgent(
4816
- agent,
4817
- "earnings",
4818
- () => agent.getEarnings(state, chainId)
4819
- );
5864
+ const e = await this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
4820
5865
  return [id, e];
4821
5866
  })
4822
5867
  );
@@ -4961,12 +6006,11 @@ var OwneySDK = class {
4961
6006
  ),
4962
6007
  Promise.all(
4963
6008
  entries.map(async ([id, agent]) => {
4964
- const b = await this.readAgent(
4965
- agent,
4966
- "balances",
4967
- () => agent.getBalances(state, chainId)
4968
- );
4969
- return [id, balanceForApyScope(b, chainId, tokenSymbol)];
6009
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
6010
+ return [
6011
+ id,
6012
+ balanceForApyScope(b, chainId, tokenSymbol)
6013
+ ];
4970
6014
  })
4971
6015
  )
4972
6016
  ]);
@@ -5025,12 +6069,7 @@ var OwneySDK = class {
5025
6069
  const { agentId, filters } = options ?? {};
5026
6070
  if (agentId) {
5027
6071
  const agent = this.getAgent(agentId);
5028
- return this.readAgent(
5029
- agent,
5030
- "history",
5031
- () => agent.getHistory(state, chainId, filters),
5032
- filters
5033
- );
6072
+ return this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
5034
6073
  }
5035
6074
  const activeAgents = [...this.getActiveAgents().values()];
5036
6075
  const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
@@ -5087,21 +6126,13 @@ var OwneySDK = class {
5087
6126
  const chainId = this.requireChainId();
5088
6127
  if (agentId) {
5089
6128
  const agent = this.getAgent(agentId);
5090
- return this.readAgent(
5091
- agent,
5092
- "profile",
5093
- () => agent.getUserProfile(state, chainId)
5094
- );
6129
+ return this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
5095
6130
  }
5096
6131
  const results = {};
5097
6132
  const entries = [...this.getActiveAgents().entries()];
5098
6133
  const profileResults = await Promise.all(
5099
6134
  entries.map(async ([id, agent]) => {
5100
- const p = await this.readAgent(
5101
- agent,
5102
- "profile",
5103
- () => agent.getUserProfile(state, chainId)
5104
- );
6135
+ const p = await this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
5105
6136
  return [id, p];
5106
6137
  })
5107
6138
  );
@@ -5140,43 +6171,44 @@ var OwneySDK = class {
5140
6171
  return pending;
5141
6172
  }
5142
6173
  /**
5143
- * One-time, user-paid approval of Permit2 on the sponsored WETH token for
5144
- * the active chain. Required once per wallet per chain before gasless WETH
5145
- * deposits; afterwards deposit() is signature-only. Resolves only after the
5146
- * approval transaction is mined (1 confirmation), so a subsequent deposit()
5147
- * will see the new allowance; throws if the transaction reverted.
6174
+ * User-paid approval of Permit2 on the selected token for the active chain.
6175
+ * Approves exactly the pending deposit amount. Another approval is required
6176
+ * for a later deposit once this allowance has been consumed. Resolves after
6177
+ * one confirmation so the subsequent deposit attempt sees the new allowance.
6178
+ *
6179
+ * @param requiredAmount Raw base-unit amount the pending deposit must cover.
5148
6180
  * @returns the approval transaction hash.
5149
6181
  */
5150
- async approvePermit2(asset = "WETH") {
5151
- void asset;
6182
+ async approvePermit2(asset = "WETH", requiredAmount = 0n) {
5152
6183
  const state = this.requireState();
5153
6184
  const chainId = this.requireChainId();
5154
- this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
5155
- const token = SPONSORED_WETH_BY_CHAIN[chainId];
6185
+ this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
6186
+ const token = sponsoredTokensFor(asset)[chainId];
5156
6187
  if (!token) {
5157
6188
  throw new OwneyError(
5158
6189
  "CHAIN_UNSUPPORTED",
5159
- `No sponsored WETH on chain ${chainId}`
6190
+ `No sponsored token on chain ${chainId}`
5160
6191
  );
5161
6192
  }
5162
6193
  const provider = this.requireConnectedProvider();
5163
- const wallet = (0, import_viem8.createWalletClient)({
6194
+ const publicClient = (0, import_viem11.createPublicClient)({
6195
+ chain: VIEM_CHAIN2[chainId],
6196
+ transport: (0, import_viem11.custom)(provider)
6197
+ });
6198
+ const approvalAmount = permit2ApprovalAmount(requiredAmount);
6199
+ const wallet = (0, import_viem11.createWalletClient)({
5164
6200
  account: state.walletAddress,
5165
6201
  chain: VIEM_CHAIN2[chainId],
5166
- transport: (0, import_viem8.custom)(provider)
6202
+ transport: (0, import_viem11.custom)(provider)
5167
6203
  });
5168
6204
  const hash = await wallet.writeContract({
5169
6205
  address: token,
5170
6206
  abi: ERC20_ALLOWANCE_ABI,
5171
6207
  functionName: "approve",
5172
- args: [PERMIT2_ADDRESS, MAX_UINT256],
6208
+ args: [PERMIT2_ADDRESS, approvalAmount],
5173
6209
  account: state.walletAddress,
5174
6210
  chain: VIEM_CHAIN2[chainId]
5175
6211
  });
5176
- const publicClient = (0, import_viem8.createPublicClient)({
5177
- chain: VIEM_CHAIN2[chainId],
5178
- transport: (0, import_viem8.custom)(provider)
5179
- });
5180
6212
  const receipt = await publicClient.waitForTransactionReceipt({
5181
6213
  hash,
5182
6214
  confirmations: 1
@@ -5209,23 +6241,15 @@ var OwneySDK = class {
5209
6241
  const agentOptions = { tokenSymbol, chainId };
5210
6242
  if (agentId) {
5211
6243
  const agent = this.getAgent(agentId);
5212
- return this.readAgent(
5213
- agent,
5214
- "agentApy",
5215
- () => agent.getAgentApy(days, agentOptions),
5216
- { days, ...agentOptions }
5217
- );
6244
+ return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
5218
6245
  }
5219
6246
  const results = {};
5220
- const agentEntries = [...this.agents.entries()];
6247
+ const agentEntries = [...this.agents.entries()].filter(
6248
+ ([id]) => !this.isAgentDisabled(id)
6249
+ );
5221
6250
  const apyResults = await Promise.all(
5222
6251
  agentEntries.map(async ([id, agent]) => {
5223
- const apy = await this.readAgent(
5224
- agent,
5225
- "agentApy",
5226
- () => agent.getAgentApy(days, agentOptions),
5227
- { days, ...agentOptions }
5228
- );
6252
+ const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
5229
6253
  return [id, apy];
5230
6254
  })
5231
6255
  );
@@ -5252,11 +6276,7 @@ var OwneySDK = class {
5252
6276
  const entries = [...activeAgents.entries()];
5253
6277
  const balanceResults = await Promise.allSettled(
5254
6278
  entries.map(async ([id, agent]) => {
5255
- const b = await this.readAgent(
5256
- agent,
5257
- "balances",
5258
- () => agent.getBalances(state, chainId)
5259
- );
6279
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
5260
6280
  return [id, b.positions ?? []];
5261
6281
  })
5262
6282
  );
@@ -5301,13 +6321,13 @@ var OwneySDK = class {
5301
6321
  };
5302
6322
 
5303
6323
  // src/agents/zyfai/zyfai.siwx.ts
5304
- var import_viem9 = require("viem");
5305
- var import_siwe = require("siwe");
6324
+ var import_viem12 = require("viem");
6325
+ var import_siwe2 = require("siwe");
5306
6326
  var import_sdk2 = require("@zyfai/sdk");
5307
6327
 
5308
6328
  // src/agents/zyfai/zyfai.siwx-cache.ts
5309
- var KEY_PREFIX3 = "owney.siwx.session";
5310
- var storage3 = () => {
6329
+ var KEY_PREFIX4 = "owney.siwx.session";
6330
+ var storage4 = () => {
5311
6331
  if (typeof window === "undefined") return null;
5312
6332
  try {
5313
6333
  return window.localStorage;
@@ -5315,8 +6335,8 @@ var storage3 = () => {
5315
6335
  return null;
5316
6336
  }
5317
6337
  };
5318
- var buildKey2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}`;
5319
- var legacyKeyPrefix2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}:`;
6338
+ var buildKey3 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
6339
+ var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
5320
6340
  var memorySiwxSessions = /* @__PURE__ */ new Map();
5321
6341
  var readLegacySiwxSession = (store, address) => {
5322
6342
  if (!store) return null;
@@ -5347,17 +6367,17 @@ var readLegacySiwxSession = (store, address) => {
5347
6367
  };
5348
6368
  var readSiwxSession = (address, chainId) => {
5349
6369
  if (typeof window === "undefined") return null;
5350
- const key2 = buildKey2(address);
5351
- const store = storage3();
5352
- let raw = null;
6370
+ const key2 = buildKey3(address);
6371
+ const store = storage4();
6372
+ let raw2 = null;
5353
6373
  try {
5354
- raw = store?.getItem(key2) ?? null;
6374
+ raw2 = store?.getItem(key2) ?? null;
5355
6375
  } catch {
5356
- raw = null;
6376
+ raw2 = null;
5357
6377
  }
5358
- if (raw) {
6378
+ if (raw2) {
5359
6379
  try {
5360
- return JSON.parse(raw);
6380
+ return JSON.parse(raw2);
5361
6381
  } catch {
5362
6382
  memorySiwxSessions.delete(key2);
5363
6383
  try {
@@ -5376,18 +6396,18 @@ var readSiwxSession = (address, chainId) => {
5376
6396
  };
5377
6397
  var writeSiwxSession = (address, _chainId, session) => {
5378
6398
  if (typeof window === "undefined") return;
5379
- const key2 = buildKey2(address);
6399
+ const key2 = buildKey3(address);
5380
6400
  memorySiwxSessions.set(key2, session);
5381
- const store = storage3();
6401
+ const store = storage4();
5382
6402
  try {
5383
6403
  store?.setItem(key2, JSON.stringify(session));
5384
6404
  } catch {
5385
6405
  }
5386
6406
  };
5387
6407
  var clearSiwxSession = (address, _chainId) => {
5388
- const key2 = buildKey2(address);
6408
+ const key2 = buildKey3(address);
5389
6409
  memorySiwxSessions.delete(key2);
5390
- const store = storage3();
6410
+ const store = storage4();
5391
6411
  try {
5392
6412
  store?.removeItem(key2);
5393
6413
  } catch {
@@ -5427,8 +6447,8 @@ function buildSIWXConfig(deps) {
5427
6447
  statement: STATEMENT,
5428
6448
  issuedAt,
5429
6449
  toString() {
5430
- return new import_siwe.SiweMessage({
5431
- address: (0, import_viem9.getAddress)(accountAddress),
6450
+ return new import_siwe2.SiweMessage({
6451
+ address: (0, import_viem12.getAddress)(accountAddress),
5432
6452
  chainId: numericChainId(chainId),
5433
6453
  domain,
5434
6454
  uri,
@@ -5470,7 +6490,7 @@ function buildSIWXConfig(deps) {
5470
6490
  const persistSession = async (session) => {
5471
6491
  const address = session.data.accountAddress;
5472
6492
  const id = numericChainId(session.data.chainId);
5473
- const message = new import_siwe.SiweMessage(session.message);
6493
+ const message = new import_siwe2.SiweMessage(session.message);
5474
6494
  const login = await post("/auth/login", {
5475
6495
  message,
5476
6496
  signature: session.signature,
@@ -5506,9 +6526,9 @@ function buildSIWXConfig(deps) {
5506
6526
  }
5507
6527
  function createOwneySIWX(config) {
5508
6528
  const zyfai = new import_sdk2.ZyfaiSDK({ apiKey: config.apiKey });
5509
- const http4 = zyfai.httpClient;
6529
+ const http2 = zyfai.httpClient;
5510
6530
  return buildSIWXConfig({
5511
- post: (url, data) => http4.post(url, data),
6531
+ post: (url, data) => http2.post(url, data),
5512
6532
  referralSource: config.referralSource
5513
6533
  });
5514
6534
  }
@@ -5520,7 +6540,7 @@ function createOwneySIWX(config) {
5520
6540
  NotConnectedError,
5521
6541
  OwneyError,
5522
6542
  OwneySDK,
6543
+ YieldseekerAgent,
5523
6544
  createOwneySIWX,
5524
- listPendingSwaps,
5525
6545
  setOwneyDebug
5526
6546
  });