@owney/sdk 0.7.16-beta.7 → 0.7.17-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,29 +1,164 @@
1
- import {
2
- debugLog,
3
- fetchAgentKeys,
4
- fetchOrgAgentConfig,
5
- setOwneyDebug
6
- } from "./chunk-QA7JUSDB.js";
7
- import {
8
- buildTransferWithAuthorizationTypedData,
9
- randomAuthNonce,
10
- readTokenMeta
11
- } from "./chunk-5LU2SHO7.js";
12
- import {
13
- ERC20_ALLOWANCE_ABI,
14
- MAX_UINT256,
15
- PERMIT2_ADDRESS,
16
- buildPermitTransferFromTypedData,
17
- randomPermit2Nonce,
18
- readErc20Balance,
19
- readPermit2Allowance
20
- } from "./chunk-DBRXNDSB.js";
21
- import {
22
- AgentChainIncompatibleError,
23
- AgentNotFoundError,
24
- NotConnectedError,
25
- OwneyError
26
- } from "./chunk-GMNQBWUB.js";
1
+ // src/errors.ts
2
+ var OwneyError = class extends Error {
3
+ code;
4
+ details;
5
+ agentId;
6
+ constructor(code, message, details, agentId) {
7
+ const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
8
+ super(`${prefix} ${message}`);
9
+ this.name = "OwneyError";
10
+ this.code = code;
11
+ this.details = details;
12
+ this.agentId = agentId;
13
+ }
14
+ };
15
+ var AgentNotFoundError = class extends OwneyError {
16
+ constructor(agentId, available) {
17
+ super(
18
+ "AGENT_NOT_FOUND",
19
+ `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
20
+ { agentId, available },
21
+ agentId
22
+ );
23
+ this.name = "AgentNotFoundError";
24
+ }
25
+ };
26
+ var NotConnectedError = class extends OwneyError {
27
+ constructor() {
28
+ super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
29
+ this.name = "NotConnectedError";
30
+ }
31
+ };
32
+ var AgentChainIncompatibleError = class extends OwneyError {
33
+ incompatibleAgents;
34
+ connectedChainId;
35
+ constructor(incompatibleAgents, connectedChainId) {
36
+ const details = incompatibleAgents.map(
37
+ ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
38
+ ).join("; ");
39
+ super(
40
+ "AGENT_CHAIN_INCOMPATIBLE",
41
+ `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
42
+ { incompatibleAgents, connectedChainId }
43
+ );
44
+ this.name = "AgentChainIncompatibleError";
45
+ this.incompatibleAgents = incompatibleAgents;
46
+ this.connectedChainId = connectedChainId;
47
+ }
48
+ };
49
+
50
+ // src/lib/rate-limit.ts
51
+ function rateLimitDelay(error, now = Date.now()) {
52
+ const seen = /* @__PURE__ */ new Set();
53
+ let limited = false;
54
+ let delay = 0;
55
+ function visit(value, depth = 0) {
56
+ if (depth > 6 || value == null) return;
57
+ if (typeof value === "string") {
58
+ if (/rate[ _-]?limit|too many requests|HTTP_429|\b429\b/i.test(value))
59
+ limited = true;
60
+ return;
61
+ }
62
+ if (typeof value !== "object" || seen.has(value)) return;
63
+ seen.add(value);
64
+ const record = value;
65
+ if ([record.status, record.statusCode, record.code].some(
66
+ (code) => String(code) === "429"
67
+ )) {
68
+ limited = true;
69
+ }
70
+ for (const key2 of ["retryAfterSeconds", "retryAfter"]) {
71
+ const seconds = Number(record[key2]);
72
+ if (Number.isFinite(seconds) && seconds > 0)
73
+ delay = Math.max(delay, seconds * 1e3);
74
+ }
75
+ const retryAt = Number(record.retryAt);
76
+ if (Number.isFinite(retryAt) && retryAt > now)
77
+ delay = Math.max(delay, retryAt - now);
78
+ const headers = record.headers;
79
+ const header = typeof headers?.get === "function" ? headers.get("Retry-After") : headers?.["retry-after"] ?? headers?.["Retry-After"];
80
+ if (typeof header === "string" || typeof header === "number") {
81
+ const seconds = Number(header);
82
+ const ms = Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(String(header)) - now;
83
+ if (Number.isFinite(ms) && ms > 0) delay = Math.max(delay, ms);
84
+ }
85
+ for (const key2 of [
86
+ "message",
87
+ "code",
88
+ "cause",
89
+ "details",
90
+ "response",
91
+ "data",
92
+ "fields"
93
+ ]) {
94
+ visit(record[key2], depth + 1);
95
+ }
96
+ for (const key2 of ["agentErrors", "failures"]) {
97
+ const entries = record[key2];
98
+ if (entries && typeof entries === "object") {
99
+ for (const entry of Object.values(entries)) visit(entry, depth + 1);
100
+ }
101
+ }
102
+ }
103
+ visit(error);
104
+ return limited ? delay : void 0;
105
+ }
106
+
107
+ // src/lib/agent-reads.ts
108
+ var AgentReads = class {
109
+ inFlight = /* @__PURE__ */ new Map();
110
+ cooldowns = /* @__PURE__ */ new Map();
111
+ // A reconnect must not reuse work started by the previous connection.
112
+ clearInFlight() {
113
+ this.inFlight.clear();
114
+ }
115
+ limited(agentId, until) {
116
+ return new OwneyError(
117
+ "AGENT_RATE_LIMITED",
118
+ "Too many requests. Please wait before trying again.",
119
+ {
120
+ statusCode: 429,
121
+ retryAt: until,
122
+ retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3))
123
+ },
124
+ agentId
125
+ );
126
+ }
127
+ run(agentId, key2, fetch2) {
128
+ const cooldown = this.cooldowns.get(agentId);
129
+ if (cooldown && cooldown.until > Date.now()) {
130
+ return Promise.reject(this.limited(agentId, cooldown.until));
131
+ }
132
+ const requestKey = JSON.stringify([agentId, key2]);
133
+ const existing = this.inFlight.get(requestKey);
134
+ if (existing) return existing;
135
+ const promise = Promise.resolve().then(fetch2).then(
136
+ (value) => {
137
+ if (this.cooldowns.get(agentId) === cooldown)
138
+ this.cooldowns.delete(agentId);
139
+ return value;
140
+ },
141
+ (error) => {
142
+ const requestedDelay = rateLimitDelay(error);
143
+ if (requestedDelay === void 0) throw error;
144
+ const previous = this.cooldowns.get(agentId);
145
+ const failures = previous && previous.until > Date.now() ? previous.failures : Math.min((previous?.failures ?? 0) + 1, 5);
146
+ const delay = Math.max(
147
+ requestedDelay,
148
+ Math.min(3e4 * 2 ** (failures - 1), 3e5)
149
+ );
150
+ const until = Math.max(previous?.until ?? 0, Date.now() + delay);
151
+ this.cooldowns.set(agentId, { until, failures });
152
+ throw this.limited(agentId, until);
153
+ }
154
+ ).finally(() => {
155
+ if (this.inFlight.get(requestKey) === promise)
156
+ this.inFlight.delete(requestKey);
157
+ });
158
+ this.inFlight.set(requestKey, promise);
159
+ return promise;
160
+ }
161
+ };
27
162
 
28
163
  // src/agents/zyfai/zyfai.agent.ts
29
164
  import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
@@ -77,6 +212,23 @@ var SupportedAssets = [
77
212
  }
78
213
  ];
79
214
 
215
+ // src/lib/debug.ts
216
+ var configuredDebug = false;
217
+ function setOwneyDebug(enabled) {
218
+ configuredDebug = enabled;
219
+ }
220
+ function isOwneyDebug() {
221
+ return globalThis.__OWNEY_DEBUG__ === true || configuredDebug;
222
+ }
223
+ function debugLog(scope, message, data) {
224
+ if (!isOwneyDebug()) return;
225
+ if (data === void 0) {
226
+ console.log(`[${scope}] ${message}`);
227
+ } else {
228
+ console.log(`[${scope}] ${message}`, data);
229
+ }
230
+ }
231
+
80
232
  // src/lib/utils.ts
81
233
  var isValidChainId = (chainId) => {
82
234
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
@@ -88,12 +240,6 @@ var isValidChainId = (chainId) => {
88
240
  }
89
241
  return chainId;
90
242
  };
91
- function isSameAsset(tokenSymbol, asset) {
92
- const token = tokenSymbol.toLowerCase();
93
- const target = asset.toLowerCase();
94
- if (token === target) return true;
95
- return target === "weth" && token === "eth" || target === "eth" && token === "weth";
96
- }
97
243
  function hexToDecimal(hex, decimals = 6) {
98
244
  const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
99
245
  const parsed = BigInt(normalized);
@@ -886,7 +1032,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
886
1032
  // successful decode is cached forever; failures are NOT cached so a transient
887
1033
  // RPC error retries on the next fetch.
888
1034
  withdrawAmountCache = /* @__PURE__ */ new Map();
889
- earningsRefreshInFlight = null;
1035
+ earningsReads = /* @__PURE__ */ new Map();
1036
+ earningsRefreshes = /* @__PURE__ */ new Map();
1037
+ earningsSnapshot = null;
1038
+ earningsGeneration = 0;
890
1039
  constructor(apiKey, rpcUrls, referralSource) {
891
1040
  this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
892
1041
  this.sdk = new ZyfaiSDK({
@@ -1131,6 +1280,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
1131
1280
  }
1132
1281
  // --- IAgent: Connection lifecycle ---
1133
1282
  async disconnect() {
1283
+ this.earningsGeneration++;
1284
+ this.earningsReads.clear();
1285
+ this.earningsRefreshes.clear();
1286
+ this.earningsSnapshot = null;
1134
1287
  if (this.connectedAddress && this.connectedChainId !== null) {
1135
1288
  clearSession(this.connectedAddress, this.connectedChainId);
1136
1289
  }
@@ -1327,37 +1480,6 @@ var ZyfaiAgent = class _ZyfaiAgent {
1327
1480
  );
1328
1481
  }
1329
1482
  }
1330
- /**
1331
- * Records the deposit with Zyfai, retrying transient failures.
1332
- *
1333
- * This runs AFTER the transfer has already landed on-chain, so it must never
1334
- * fail the deposit — the funds moved. But it is also the ONLY source of the
1335
- * "Top up wallet" entry the history is built from: Zyfai auto-deploys the
1336
- * balance it detects either way, so when this call is lost the user's deposit
1337
- * never appears in Activity (an earlier withdrawal stays the newest row) and
1338
- * nothing ever backfills it. Retry, then log loudly enough to be recoverable.
1339
- */
1340
- async logDepositWithRetry(chainId, txHash, amount, tokenAddress) {
1341
- const ATTEMPTS = 3;
1342
- const RETRY_DELAY_MS = 1e3;
1343
- for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
1344
- try {
1345
- await (tokenAddress ? this.sdk.logDeposit(chainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(chainId, txHash, amount));
1346
- return;
1347
- } catch (logError) {
1348
- if (attempt === ATTEMPTS) {
1349
- console.error(
1350
- "[owney-sdk] Deposit landed on-chain but logDeposit failed \u2014 it will be missing from Zyfai history:",
1351
- { txHash, chainId, amount, tokenAddress, error: logError }
1352
- );
1353
- return;
1354
- }
1355
- await new Promise(
1356
- (resolve) => setTimeout(resolve, RETRY_DELAY_MS * attempt)
1357
- );
1358
- }
1359
- }
1360
- }
1361
1483
  /**
1362
1484
  * True when `smartWallet` is the backend-managed pool wallet Zyfai assigned
1363
1485
  * to this EOA — the "new wallet" kind that is predeployed on the chains it
@@ -1661,12 +1783,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1661
1783
  );
1662
1784
  const txHash = await depositCallback(smartWallet, validChainId, amount);
1663
1785
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
1664
- await this.logDepositWithRetry(
1665
- validChainId,
1666
- txHash,
1667
- amount,
1668
- tokenAddress
1669
- );
1786
+ try {
1787
+ await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
1788
+ } catch (logError) {
1789
+ console.warn(
1790
+ "[owney-sdk] Deposit landed on-chain but logDeposit failed (non-fatal):",
1791
+ logError
1792
+ );
1793
+ }
1670
1794
  return { txHash, smartWallet, amount };
1671
1795
  }
1672
1796
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
@@ -1708,26 +1832,59 @@ var ZyfaiAgent = class _ZyfaiAgent {
1708
1832
  const raw = await this.sdk.getPortfolio(this.getAddress());
1709
1833
  return mapBalances(raw, validChainId, smartWallet);
1710
1834
  }
1835
+ earningsKey(state, chainId, smartWallet) {
1836
+ return JSON.stringify([
1837
+ state.walletAddress?.toLowerCase(),
1838
+ chainId,
1839
+ smartWallet.toLowerCase()
1840
+ ]);
1841
+ }
1842
+ readEarnings(key2, smartWallet) {
1843
+ const existing = this.earningsReads.get(key2);
1844
+ if (existing) return existing;
1845
+ const generation = this.earningsGeneration;
1846
+ const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw) => {
1847
+ if (generation === this.earningsGeneration) {
1848
+ this.earningsSnapshot = { key: key2, raw, at: Date.now() };
1849
+ }
1850
+ return raw;
1851
+ }).finally(() => {
1852
+ if (this.earningsReads.get(key2) === pending)
1853
+ this.earningsReads.delete(key2);
1854
+ });
1855
+ this.earningsReads.set(key2, pending);
1856
+ return pending;
1857
+ }
1711
1858
  async getEarnings(state, chainId) {
1712
1859
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1713
- const raw = await this.sdk.getOnchainEarnings(smartWallet);
1860
+ const raw = await this.readEarnings(
1861
+ this.earningsKey(state, chainId, smartWallet),
1862
+ smartWallet
1863
+ );
1714
1864
  return mapEarnings(raw, smartWallet);
1715
1865
  }
1716
1866
  async refreshEarnings(state, chainId) {
1717
- if (this.earningsRefreshInFlight) return this.earningsRefreshInFlight;
1718
- this.earningsRefreshInFlight = (async () => {
1719
- const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1720
- const current = await this.sdk.getOnchainEarnings(smartWallet);
1867
+ const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1868
+ const key2 = this.earningsKey(state, chainId, smartWallet);
1869
+ const existing = this.earningsRefreshes.get(key2);
1870
+ if (existing) return existing;
1871
+ const generation = this.earningsGeneration;
1872
+ const pending = (async () => {
1873
+ const recent = this.earningsSnapshot;
1874
+ const current = recent?.key === key2 && Date.now() - recent.at < 5e3 ? recent.raw : await this.readEarnings(key2, smartWallet);
1721
1875
  const lastCheck = current.data.lastCheckTimestamp ? Date.parse(current.data.lastCheckTimestamp) : Number.NaN;
1722
1876
  const isFresh = Number.isFinite(lastCheck) && Date.now() - lastCheck < EARNINGS_REFRESH_COOLDOWN_MS;
1723
1877
  const earnings = isFresh ? current : await this.sdk.calculateOnchainEarnings(smartWallet);
1878
+ if (!isFresh && generation === this.earningsGeneration) {
1879
+ this.earningsSnapshot = { key: key2, raw: earnings, at: Date.now() };
1880
+ }
1724
1881
  return mapEarnings(earnings, smartWallet);
1725
- })();
1726
- try {
1727
- return await this.earningsRefreshInFlight;
1728
- } finally {
1729
- this.earningsRefreshInFlight = null;
1730
- }
1882
+ })().finally(() => {
1883
+ if (this.earningsRefreshes.get(key2) === pending)
1884
+ this.earningsRefreshes.delete(key2);
1885
+ });
1886
+ this.earningsRefreshes.set(key2, pending);
1887
+ return pending;
1731
1888
  }
1732
1889
  async getAccountApy(state, chainId, days, tokenSymbol) {
1733
1890
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -1847,9 +2004,73 @@ var ZyfaiAgent = class _ZyfaiAgent {
1847
2004
  }
1848
2005
  };
1849
2006
 
1850
- // src/lib/health-report.ts
2007
+ // src/lib/routing-api.ts
1851
2008
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1852
- async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL) {
2009
+ async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2010
+ const url = `${baseUrl}/api/v1/agent/org-config`;
2011
+ try {
2012
+ const res = await fetch(url, {
2013
+ method: "GET",
2014
+ headers: {
2015
+ "Content-Type": "application/json",
2016
+ "x-owney-api-key": `${apiKey}`
2017
+ }
2018
+ });
2019
+ if (!res.ok) {
2020
+ if (res.status !== 404) {
2021
+ console.warn(
2022
+ `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
2023
+ );
2024
+ }
2025
+ return null;
2026
+ }
2027
+ const json = await res.json();
2028
+ const policy = json.success ? json.data ?? null : null;
2029
+ debugLog(
2030
+ "owney-sdk",
2031
+ policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
2032
+ policy ?? void 0
2033
+ );
2034
+ return policy;
2035
+ } catch (error) {
2036
+ console.warn(
2037
+ "[owney-sdk] Could not read org agent config (non-fatal):",
2038
+ error instanceof Error ? error.message : String(error)
2039
+ );
2040
+ return null;
2041
+ }
2042
+ }
2043
+ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2044
+ const url = `${baseUrl}/api/v1/agent/keys`;
2045
+ const res = await fetch(url, {
2046
+ method: "GET",
2047
+ headers: {
2048
+ "Content-Type": "application/json",
2049
+ "x-owney-api-key": `${apiKey}`
2050
+ }
2051
+ });
2052
+ if (!res.ok) {
2053
+ const text = await res.text().catch(() => "");
2054
+ throw new OwneyError(
2055
+ "API_ROUTING_ERROR",
2056
+ `Routing API error ${res.status}: ${text}`,
2057
+ { statusCode: res.status, responseBody: text }
2058
+ );
2059
+ }
2060
+ const json = await res.json();
2061
+ if (!json.success) {
2062
+ throw new OwneyError(
2063
+ "API_ROUTING_FAILED",
2064
+ `Routing API request failed: ${json.message}`,
2065
+ { message: json.message }
2066
+ );
2067
+ }
2068
+ return json.data;
2069
+ }
2070
+
2071
+ // src/lib/health-report.ts
2072
+ var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2073
+ async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
1853
2074
  try {
1854
2075
  await fetch(`${baseUrl}/api/v1/agent/health-report`, {
1855
2076
  method: "POST",
@@ -1883,10 +2104,11 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
1883
2104
  // src/lib/helpers/withdraw-helper.ts
1884
2105
  import { parseUnits } from "viem";
1885
2106
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
2107
+ const target = asset.toUpperCase();
1886
2108
  return agents.map((agent) => {
1887
2109
  const agentBalance = aggregated[agent.id];
1888
2110
  const tokenBalance = agentBalance?.tokens.find(
1889
- (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
2111
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
1890
2112
  );
1891
2113
  if (!tokenBalance) return { agent, balance: 0n };
1892
2114
  return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
@@ -2003,10 +2225,46 @@ import {
2003
2225
  } from "viem";
2004
2226
  import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
2005
2227
 
2228
+ // src/lib/transfer-auth.ts
2229
+ import { bytesToHex } from "viem";
2230
+ var ERC20_META_ABI = [
2231
+ { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
2232
+ { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
2233
+ ];
2234
+ function buildTransferWithAuthorizationTypedData(input) {
2235
+ return {
2236
+ domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
2237
+ types: {
2238
+ TransferWithAuthorization: [
2239
+ { name: "from", type: "address" },
2240
+ { name: "to", type: "address" },
2241
+ { name: "value", type: "uint256" },
2242
+ { name: "validAfter", type: "uint256" },
2243
+ { name: "validBefore", type: "uint256" },
2244
+ { name: "nonce", type: "bytes32" }
2245
+ ]
2246
+ },
2247
+ primaryType: "TransferWithAuthorization",
2248
+ message: input.message
2249
+ };
2250
+ }
2251
+ async function readTokenMeta(publicClient, token) {
2252
+ const [tokenName, tokenVersion] = await Promise.all([
2253
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
2254
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
2255
+ ]);
2256
+ return { tokenName, tokenVersion };
2257
+ }
2258
+ function randomAuthNonce() {
2259
+ const bytes = new Uint8Array(32);
2260
+ globalThis.crypto.getRandomValues(bytes);
2261
+ return bytesToHex(bytes);
2262
+ }
2263
+
2006
2264
  // src/lib/sponsor-client.ts
2007
- var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2265
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2008
2266
  async function postSponsorTransferAuth(input) {
2009
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL2;
2267
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2010
2268
  let res;
2011
2269
  try {
2012
2270
  res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
@@ -2046,7 +2304,7 @@ async function postSponsorTransferAuth(input) {
2046
2304
  return parsed.data;
2047
2305
  }
2048
2306
  async function postSponsorPermit2Transfer(input) {
2049
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL2;
2307
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2050
2308
  let res;
2051
2309
  try {
2052
2310
  res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
@@ -2084,7 +2342,7 @@ async function postSponsorPermit2Transfer(input) {
2084
2342
  return parsed.data;
2085
2343
  }
2086
2344
  async function getSponsorRelayerAddress(input) {
2087
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL2;
2345
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2088
2346
  let res;
2089
2347
  try {
2090
2348
  res = await fetch(
@@ -2120,6 +2378,84 @@ async function getSponsorRelayerAddress(input) {
2120
2378
  return parsed.data.relayer;
2121
2379
  }
2122
2380
 
2381
+ // src/lib/permit2.ts
2382
+ import { bytesToHex as bytesToHex2 } from "viem";
2383
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2384
+ var MAX_UINT256 = 2n ** 256n - 1n;
2385
+ var ERC20_ALLOWANCE_ABI = [
2386
+ {
2387
+ type: "function",
2388
+ name: "allowance",
2389
+ stateMutability: "view",
2390
+ inputs: [
2391
+ { name: "owner", type: "address" },
2392
+ { name: "spender", type: "address" }
2393
+ ],
2394
+ outputs: [{ name: "", type: "uint256" }]
2395
+ },
2396
+ {
2397
+ type: "function",
2398
+ name: "approve",
2399
+ stateMutability: "nonpayable",
2400
+ inputs: [
2401
+ { name: "spender", type: "address" },
2402
+ { name: "amount", type: "uint256" }
2403
+ ],
2404
+ outputs: [{ name: "", type: "bool" }]
2405
+ },
2406
+ {
2407
+ type: "function",
2408
+ name: "balanceOf",
2409
+ stateMutability: "view",
2410
+ inputs: [{ name: "account", type: "address" }],
2411
+ outputs: [{ name: "", type: "uint256" }]
2412
+ }
2413
+ ];
2414
+ function buildPermitTransferFromTypedData(input) {
2415
+ return {
2416
+ domain: {
2417
+ name: "Permit2",
2418
+ chainId: input.chainId,
2419
+ verifyingContract: PERMIT2_ADDRESS
2420
+ },
2421
+ types: {
2422
+ PermitTransferFrom: [
2423
+ { name: "permitted", type: "TokenPermissions" },
2424
+ { name: "spender", type: "address" },
2425
+ { name: "nonce", type: "uint256" },
2426
+ { name: "deadline", type: "uint256" }
2427
+ ],
2428
+ TokenPermissions: [
2429
+ { name: "token", type: "address" },
2430
+ { name: "amount", type: "uint256" }
2431
+ ]
2432
+ },
2433
+ primaryType: "PermitTransferFrom",
2434
+ message: input.message
2435
+ };
2436
+ }
2437
+ function randomPermit2Nonce() {
2438
+ const bytes = new Uint8Array(32);
2439
+ globalThis.crypto.getRandomValues(bytes);
2440
+ return BigInt(bytesToHex2(bytes));
2441
+ }
2442
+ async function readPermit2Allowance(publicClient, token, owner) {
2443
+ return publicClient.readContract({
2444
+ address: token,
2445
+ abi: ERC20_ALLOWANCE_ABI,
2446
+ functionName: "allowance",
2447
+ args: [owner, PERMIT2_ADDRESS]
2448
+ });
2449
+ }
2450
+ async function readErc20Balance(publicClient, token, owner) {
2451
+ return publicClient.readContract({
2452
+ address: token,
2453
+ abi: ERC20_ALLOWANCE_ABI,
2454
+ functionName: "balanceOf",
2455
+ args: [owner]
2456
+ });
2457
+ }
2458
+
2123
2459
  // src/lib/chain-guard.ts
2124
2460
  var CHAIN_NAMES = {
2125
2461
  1: "Ethereum",
@@ -2438,7 +2774,6 @@ var VIEM_CHAIN2 = {
2438
2774
  42161: arbitrum2,
2439
2775
  1: mainnet2
2440
2776
  };
2441
- var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai"];
2442
2777
  var SPONSORED_WETH_BY_CHAIN = {
2443
2778
  8453: "0x4200000000000000000000000000000000000006",
2444
2779
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -2467,11 +2802,21 @@ var OwneySDK = class {
2467
2802
  zyfaiRpcUrls;
2468
2803
  routingApiBaseUrl;
2469
2804
  referralSource;
2470
- surfliquidApiBaseUrl;
2471
2805
  cachedSponsoredCallback = null;
2472
2806
  cachedWethSponsoredCallback = null;
2473
2807
  paymasterServiceUrl;
2474
2808
  cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
2809
+ agentReads = new AgentReads();
2810
+ activationChecks = /* @__PURE__ */ new Map();
2811
+ readAgent(agent, method, fetch2, params = null) {
2812
+ const key2 = JSON.stringify([
2813
+ this.state?.walletAddress.toLowerCase() ?? null,
2814
+ this.state?.chainId ?? null,
2815
+ method,
2816
+ params
2817
+ ]);
2818
+ return this.agentReads.run(agent.id, key2, fetch2);
2819
+ }
2475
2820
  initializingAgentsPromise = null;
2476
2821
  constructor(config) {
2477
2822
  this.apiKey = config.apiKey;
@@ -2480,7 +2825,6 @@ var OwneySDK = class {
2480
2825
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2481
2826
  this.paymasterServiceUrl = config.paymasterServiceUrl;
2482
2827
  this.referralSource = config.referralSource;
2483
- this.surfliquidApiBaseUrl = config.surfliquidApiBaseUrl;
2484
2828
  }
2485
2829
  /**
2486
2830
  * Establish connection state for all subsequent SDK calls.
@@ -2499,6 +2843,8 @@ var OwneySDK = class {
2499
2843
  "No accounts found. Ensure the wallet is unlocked and connected."
2500
2844
  );
2501
2845
  }
2846
+ this.agentReads.clearInFlight();
2847
+ this.activationChecks.clear();
2502
2848
  this.state = { provider, walletAddress, chainId: null };
2503
2849
  this.cachedSponsoredCallback = null;
2504
2850
  this.cachedWethSponsoredCallback = null;
@@ -2514,6 +2860,8 @@ var OwneySDK = class {
2514
2860
  }
2515
2861
  this.activeAgents.clear();
2516
2862
  this.disabledAgents.clear();
2863
+ this.agentReads.clearInFlight();
2864
+ this.activationChecks.clear();
2517
2865
  this.state = null;
2518
2866
  this.cachedSponsoredCallback = null;
2519
2867
  this.cachedWethSponsoredCallback = null;
@@ -2716,8 +3064,8 @@ var OwneySDK = class {
2716
3064
  this.routingApiBaseUrl
2717
3065
  );
2718
3066
  this.disabledAgents.clear();
2719
- for (const { key: key2, agent_type, project_name, is_enabled } of agentKeys) {
2720
- const agent = await this.createAgent(agent_type, key2, project_name);
3067
+ for (const { key: key2, agent_type, is_enabled } of agentKeys) {
3068
+ const agent = this.createAgent(agent_type, key2);
2721
3069
  if (!agent) continue;
2722
3070
  this.agents.set(agent_type, agent);
2723
3071
  if (is_enabled === false) {
@@ -2738,25 +3086,42 @@ var OwneySDK = class {
2738
3086
  this.initializingAgentsPromise = null;
2739
3087
  }
2740
3088
  }
2741
- async createAgent(agentId, key2, projectName) {
3089
+ createAgent(agentId, key2) {
2742
3090
  if (agentId === "zyfai") {
2743
3091
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
2744
3092
  }
2745
- if (agentId === "surfliquid") {
2746
- if (!projectName) return null;
2747
- const { SurfLiquidAgent } = await import("./surfliquid.agent-5GY7A6HY.js");
2748
- return new SurfLiquidAgent({
2749
- projectName,
2750
- appId: key2,
2751
- // Same-origin proxy override — SurfLiquid auth is an httpOnly cookie,
2752
- // which WebKit (iOS) discards when it is third-party. See
2753
- // OwneySDKConfig.surfliquidApiBaseUrl.
2754
- apiBaseUrl: this.surfliquidApiBaseUrl,
2755
- apiKey: this.apiKey,
2756
- routingApiBaseUrl: this.routingApiBaseUrl
3093
+ return null;
3094
+ }
3095
+ /**
3096
+ * Discover the agents actually returned by routing for this organization.
3097
+ * Consumers should use this instead of hardcoding a global agent roster.
3098
+ */
3099
+ async getAvailableAgents(options = {}) {
3100
+ await this.ensureAgentsInitialized();
3101
+ const { chainId, asset, includeDisabled = false } = options;
3102
+ const available = [];
3103
+ for (const [id, agent] of this.agents) {
3104
+ const isEnabled = !this.isAgentDisabled(id);
3105
+ if (!includeDisabled && !isEnabled) continue;
3106
+ if (chainId !== void 0 && !agent.supportedChainIds.includes(chainId)) {
3107
+ continue;
3108
+ }
3109
+ if (asset !== void 0) {
3110
+ const supportsAsset = agent.supportedAssets.some(
3111
+ (entry) => (chainId === void 0 || entry.chainId === chainId) && entry.assets.some((candidate) => candidate.symbol === asset)
3112
+ );
3113
+ if (!supportsAsset) {
3114
+ continue;
3115
+ }
3116
+ }
3117
+ available.push({
3118
+ id,
3119
+ isEnabled,
3120
+ supportedChainIds: agent.supportedChainIds,
3121
+ supportedAssets: agent.supportedAssets
2757
3122
  });
2758
3123
  }
2759
- return null;
3124
+ return available;
2760
3125
  }
2761
3126
  // --- Account lifecycle ---
2762
3127
  /**
@@ -2805,9 +3170,7 @@ var OwneySDK = class {
2805
3170
  this.activeAgents.add(id);
2806
3171
  }
2807
3172
  state.chainId = chainId;
2808
- this.activateAgentsInTurn(agents, state, chainId).catch((error) => {
2809
- console.error("activateAgent background init failed:", error);
2810
- });
3173
+ await this.activateAgentsInTurn(agents, state, chainId);
2811
3174
  return;
2812
3175
  }
2813
3176
  const compatible = [...this.agents.values()].filter(
@@ -2828,11 +3191,7 @@ var OwneySDK = class {
2828
3191
  const enabledCompatible = compatible.filter(
2829
3192
  (agent) => !this.isAgentDisabled(agent.id)
2830
3193
  );
2831
- this.activateAgentsInTurn(enabledCompatible, state, chainId).catch(
2832
- (error) => {
2833
- console.error("activateAgent background init failed:", error);
2834
- }
2835
- );
3194
+ await this.activateAgentsInTurn(enabledCompatible, state, chainId);
2836
3195
  }
2837
3196
  /**
2838
3197
  * Activate agents ONE AT A TIME, each followed by its org policy.
@@ -2869,7 +3228,7 @@ var OwneySDK = class {
2869
3228
  if (firstError !== null) throw firstError;
2870
3229
  }
2871
3230
  /**
2872
- * Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
3231
+ * Deposit into a specific agent, or distribute across all eligible agents.
2873
3232
  * Validates that the asset is supported and amount meets minimums for the target agent(s).
2874
3233
  * @param options - Deposit parameters
2875
3234
  * @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
@@ -2877,7 +3236,8 @@ var OwneySDK = class {
2877
3236
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
2878
3237
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
2879
3238
  * split amount and smart wallet address — expect multiple wallet prompts.
2880
- * @param options.agentId - Optional. Target agent. Omit to split equally across all agents.
3239
+ * @param options.agentId - Optional explicit target. Otherwise split equally,
3240
+ * or fund remaining agents when a recovery deposit cannot meet every minimum.
2881
3241
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
2882
3242
  */
2883
3243
  async deposit(options) {
@@ -2915,7 +3275,7 @@ var OwneySDK = class {
2915
3275
  });
2916
3276
  const exemptFlags = await Promise.all(
2917
3277
  eligibleAgents.map(
2918
- (a) => this.hasExistingBalance(a, state, chainId, asset)
3278
+ (a) => this.hasExistingBalance(a, state, chainId, asset, true)
2919
3279
  )
2920
3280
  );
2921
3281
  const exempt = new Set(
@@ -2929,15 +3289,42 @@ var OwneySDK = class {
2929
3289
  exempt
2930
3290
  );
2931
3291
  if (agentAmounts.length === 0) {
3292
+ const agentsRequiringActivation = eligibleAgents.filter(
3293
+ (agent) => !exempt.has(agent.id)
3294
+ );
3295
+ const perAgentMinimum = agentsRequiringActivation.reduce(
3296
+ (highest, agent) => {
3297
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
3298
+ return minimum > highest ? minimum : highest;
3299
+ },
3300
+ 0n
3301
+ );
3302
+ const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
2932
3303
  throw new OwneyError(
2933
3304
  "DEPOSIT_AMOUNT_BELOW_MINIMUM",
2934
- `Amount "${amount}" cannot satisfy minimum deposit requirements for any eligible agent.`,
2935
- { amount }
3305
+ `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
3306
+ {
3307
+ amount,
3308
+ asset,
3309
+ chainId,
3310
+ minDepositAmount: minimumRequired.toString(),
3311
+ perAgentDepositAmount: perAgentMinimum.toString(),
3312
+ agentMinimums: agentsRequiringActivation.map((agent) => ({
3313
+ agentId: agent.id,
3314
+ minDepositAmount: this.getMinDepositAmount(
3315
+ agent,
3316
+ chainId,
3317
+ asset
3318
+ ).toString()
3319
+ }))
3320
+ }
2936
3321
  );
2937
3322
  }
2938
3323
  const agentResults = {};
2939
- const agentErrors = {};
2940
- for (const { agent, amount: agentAmount } of agentAmounts) {
3324
+ for (const [
3325
+ index,
3326
+ { agent, amount: agentAmount }
3327
+ ] of agentAmounts.entries()) {
2941
3328
  try {
2942
3329
  agentResults[agent.id] = await this.depositWithFallback(
2943
3330
  agent,
@@ -2949,17 +3336,22 @@ var OwneySDK = class {
2949
3336
  depositCallback
2950
3337
  );
2951
3338
  } catch (error) {
2952
- agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
3339
+ if (Object.keys(agentResults).length === 0) throw error;
3340
+ throw new OwneyError(
3341
+ "DEPOSIT_PARTIAL_FAILURE",
3342
+ "Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
3343
+ {
3344
+ agentResults,
3345
+ failedAgentId: agent.id,
3346
+ failedAmount: agentAmount,
3347
+ unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
3348
+ cause: error
3349
+ },
3350
+ agent.id
3351
+ );
2953
3352
  }
2954
3353
  }
2955
- if (Object.keys(agentResults).length === 0) {
2956
- throw new OwneyError(
2957
- "DEPOSIT_ALL_FAILED",
2958
- `All ${agentAmounts.length} agent deposit(s) failed.`,
2959
- { agentErrors }
2960
- );
2961
- }
2962
- return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
3354
+ return { agentResults };
2963
3355
  }
2964
3356
  /**
2965
3357
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -3030,6 +3422,17 @@ var OwneySDK = class {
3030
3422
  }
3031
3423
  splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
3032
3424
  if (agents.length === 0) return [];
3425
+ const agentsRequiringActivation = agents.filter(
3426
+ (agent) => !exempt.has(agent.id)
3427
+ );
3428
+ if (agentsRequiringActivation.length === agents.length) {
3429
+ const highestMinimum2 = agents.reduce((highest, agent) => {
3430
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
3431
+ return minimum > highest ? minimum : highest;
3432
+ }, 0n);
3433
+ const minimumRequired2 = (highestMinimum2 > 0n ? highestMinimum2 : 1n) * BigInt(agents.length);
3434
+ if (totalAmount < minimumRequired2) return [];
3435
+ }
3033
3436
  const perAgent = totalAmount / BigInt(agents.length);
3034
3437
  const remainder = totalAmount % BigInt(agents.length);
3035
3438
  const splits = agents.map((agent, i) => ({
@@ -3037,18 +3440,26 @@ var OwneySDK = class {
3037
3440
  amount: i === agents.length - 1 ? perAgent + remainder : perAgent
3038
3441
  }));
3039
3442
  const valid = splits.filter(
3040
- (s) => exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset)
3443
+ (s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
3041
3444
  );
3042
3445
  if (valid.length === agents.length) {
3043
3446
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
3044
3447
  }
3045
- return this.splitDepositAmount(
3046
- totalAmount,
3047
- valid.map((s) => s.agent),
3048
- chainId,
3049
- asset,
3050
- exempt
3051
- );
3448
+ const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
3449
+ const highestMinimum = targets.reduce((highest, agent) => {
3450
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
3451
+ return minimum > highest ? minimum : highest;
3452
+ }, 0n);
3453
+ const minimumRequired = (highestMinimum > 0n ? highestMinimum : 1n) * BigInt(targets.length);
3454
+ if (totalAmount < minimumRequired) return [];
3455
+ const targetShare = totalAmount / BigInt(targets.length);
3456
+ const targetRemainder = totalAmount % BigInt(targets.length);
3457
+ return targets.map((agent, index) => ({
3458
+ agent,
3459
+ amount: String(
3460
+ targetShare + (index === targets.length - 1 ? targetRemainder : 0n)
3461
+ )
3462
+ }));
3052
3463
  }
3053
3464
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
3054
3465
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
@@ -3073,14 +3484,29 @@ var OwneySDK = class {
3073
3484
  * per-agent minimum. Fails closed: any error reading balances returns
3074
3485
  * false, so the minimum is enforced as today.
3075
3486
  */
3076
- async hasExistingBalance(agent, state, chainId, asset) {
3487
+ async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
3077
3488
  try {
3078
3489
  const balance = await agent.getBalances(state, chainId);
3490
+ const target = asset.toLowerCase();
3079
3491
  const token = balance.tokens.find(
3080
- (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
3492
+ (t) => t.chainId === chainId && t.asset.toLowerCase() === target
3081
3493
  );
3082
- return !!token && Number(token.amount) > 0;
3083
- } catch {
3494
+ const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
3495
+ const position = (balance.positions ?? []).find((p) => {
3496
+ const positionChain = p.chain.trim().toUpperCase();
3497
+ const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
3498
+ return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
3499
+ });
3500
+ return !!token && Number(token.amount) > 0 || !!position;
3501
+ } catch (error) {
3502
+ if (requireReliableRead) {
3503
+ throw new OwneyError(
3504
+ "DEPOSIT_BALANCE_UNAVAILABLE",
3505
+ "Could not verify every agent balance. No deposit was submitted. Try again once balances are available.",
3506
+ { agentId: agent.id, chainId, asset, cause: error },
3507
+ agent.id
3508
+ );
3509
+ }
3084
3510
  return false;
3085
3511
  }
3086
3512
  }
@@ -3116,34 +3542,6 @@ var OwneySDK = class {
3116
3542
  }
3117
3543
  return eligible;
3118
3544
  }
3119
- /**
3120
- * Agent ids the routing API provisioned for this org that support the given
3121
- * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
3122
- * surfliquid first). Returns `[]` when the org has no compatible agent — never
3123
- * throws on an empty org. Loads agent keys on first call (apiKey only, no
3124
- * wallet), so the UI can resolve which agent to use before the user connects.
3125
- *
3126
- * This is the source of truth for agent availability: an agent appears here
3127
- * iff the routing API returned its key. No per-app feature flags.
3128
- */
3129
- async getEligibleAgentIds(chainId, asset) {
3130
- try {
3131
- await this.ensureAgentsInitialized();
3132
- } catch (error) {
3133
- if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
3134
- return [];
3135
- }
3136
- throw error;
3137
- }
3138
- return [...this.agents.values()].filter((agent) => {
3139
- const chainAssets = agent.supportedAssets.find(
3140
- (sa) => sa.chainId === chainId
3141
- );
3142
- return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
3143
- }).map((agent) => agent.id).sort(
3144
- (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
3145
- );
3146
- }
3147
3545
  // --- Fund operations ---
3148
3546
  /**
3149
3547
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -3180,17 +3578,9 @@ var OwneySDK = class {
3180
3578
  }
3181
3579
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
3182
3580
  if (!amount) {
3183
- const aggregated2 = await this.getBalances();
3184
- const funded = projectAgentBalancesForAsset(
3185
- eligibleAgents,
3186
- aggregated2.agentBalances,
3187
- chainId,
3188
- asset,
3189
- assetInfo.decimals
3190
- ).filter((b) => b.balance > 0n);
3191
3581
  const results2 = {};
3192
3582
  const agentErrors2 = {};
3193
- for (const { agent } of funded) {
3583
+ for (const agent of eligibleAgents) {
3194
3584
  try {
3195
3585
  results2[agent.id] = await agent.withdraw(state, chainId, token);
3196
3586
  } catch (err) {
@@ -3212,10 +3602,9 @@ var OwneySDK = class {
3212
3602
  { asset, agentErrors: agentErrors2 }
3213
3603
  );
3214
3604
  }
3215
- const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
3216
3605
  return {
3217
3606
  agentResult: results2,
3218
- totalWithdrawn: totalWithdrawn.toString()
3607
+ totalWithdrawn: sumWithdrawnAmount(results2).toString()
3219
3608
  };
3220
3609
  }
3221
3610
  const requested = BigInt(amount);
@@ -3320,7 +3709,8 @@ var OwneySDK = class {
3320
3709
  const state = this.requireState();
3321
3710
  const chainId = this.requireChainId();
3322
3711
  if (agentId) {
3323
- const result = await this.getAgent(agentId).getBalances(state, chainId);
3712
+ const agent = this.getAgent(agentId);
3713
+ const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3324
3714
  return result;
3325
3715
  }
3326
3716
  let totalBalance = 0;
@@ -3328,12 +3718,13 @@ var OwneySDK = class {
3328
3718
  const entries = [...this.getActiveAgents().entries()];
3329
3719
  const balanceResults = await Promise.allSettled(
3330
3720
  entries.map(async ([id, agent]) => {
3331
- const b = await agent.getBalances(state, chainId);
3721
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3332
3722
  return [id, b];
3333
3723
  })
3334
3724
  );
3335
3725
  let successCount = 0;
3336
3726
  const agentErrors = {};
3727
+ const agentFailures = [];
3337
3728
  for (let i = 0; i < balanceResults.length; i++) {
3338
3729
  const settledResult = balanceResults[i];
3339
3730
  const [agentId2] = entries[i];
@@ -3345,13 +3736,14 @@ var OwneySDK = class {
3345
3736
  continue;
3346
3737
  }
3347
3738
  const reason = settledResult.reason;
3739
+ agentFailures.push(reason);
3348
3740
  agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
3349
3741
  }
3350
3742
  if (successCount === 0) {
3351
3743
  throw new OwneyError(
3352
3744
  "BALANCE_ALL_FAILED",
3353
3745
  "Failed to fetch balances for all active agents.",
3354
- { agentErrors }
3746
+ { agentErrors, failures: agentFailures }
3355
3747
  );
3356
3748
  }
3357
3749
  return {
@@ -3369,14 +3761,15 @@ var OwneySDK = class {
3369
3761
  const state = this.requireState();
3370
3762
  const chainId = this.requireChainId();
3371
3763
  if (agentId) {
3372
- return this.getAgent(agentId).getEarnings(state, chainId);
3764
+ const agent = this.getAgent(agentId);
3765
+ return this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
3373
3766
  }
3374
3767
  let totalEarnings = 0;
3375
3768
  const results = {};
3376
3769
  const entries = [...this.getActiveAgents().entries()];
3377
3770
  const earningsResults = await Promise.all(
3378
3771
  entries.map(async ([id, agent]) => {
3379
- const e = await agent.getEarnings(state, chainId);
3772
+ const e = await this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
3380
3773
  return [id, e];
3381
3774
  })
3382
3775
  );
@@ -3396,7 +3789,11 @@ var OwneySDK = class {
3396
3789
  async refreshEarnings(agentId) {
3397
3790
  const state = this.requireState();
3398
3791
  const chainId = this.requireChainId();
3399
- const refreshAgent = (agent) => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId);
3792
+ const refreshAgent = (agent) => this.readAgent(
3793
+ agent,
3794
+ agent.refreshEarnings ? "refreshEarnings" : "earnings",
3795
+ () => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId)
3796
+ );
3400
3797
  if (agentId) return refreshAgent(this.getAgent(agentId));
3401
3798
  let totalEarnings = 0;
3402
3799
  const agentEarnings = {};
@@ -3427,11 +3824,12 @@ var OwneySDK = class {
3427
3824
  const state = this.requireState();
3428
3825
  const chainId = this.requireChainId();
3429
3826
  if (agentId) {
3430
- return this.getAgent(agentId).getAccountApy(
3431
- state,
3432
- chainId,
3433
- days,
3434
- tokenSymbol
3827
+ const agent = this.getAgent(agentId);
3828
+ return this.readAgent(
3829
+ agent,
3830
+ "accountApy",
3831
+ () => agent.getAccountApy(state, chainId, days, tokenSymbol),
3832
+ { days, tokenSymbol }
3435
3833
  );
3436
3834
  }
3437
3835
  const activeAgents = this.getActiveAgents();
@@ -3439,18 +3837,18 @@ var OwneySDK = class {
3439
3837
  const [apyResults, balanceResults] = await Promise.all([
3440
3838
  Promise.all(
3441
3839
  entries.map(async ([id, agent]) => {
3442
- const apy = await agent.getAccountApy(
3443
- state,
3444
- chainId,
3445
- days,
3446
- tokenSymbol
3840
+ const apy = await this.readAgent(
3841
+ agent,
3842
+ "accountApy",
3843
+ () => agent.getAccountApy(state, chainId, days, tokenSymbol),
3844
+ { days, tokenSymbol }
3447
3845
  );
3448
3846
  return [id, apy];
3449
3847
  })
3450
3848
  ),
3451
3849
  Promise.all(
3452
3850
  entries.map(async ([id, agent]) => {
3453
- const b = await agent.getBalances(state, chainId);
3851
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3454
3852
  return [id, Number(b.totalBalance)];
3455
3853
  })
3456
3854
  )
@@ -3507,7 +3905,8 @@ var OwneySDK = class {
3507
3905
  const chainId = this.requireChainId();
3508
3906
  const { agentId, filters } = options ?? {};
3509
3907
  if (agentId) {
3510
- return this.getAgent(agentId).getHistory(state, chainId, filters);
3908
+ const agent = this.getAgent(agentId);
3909
+ return this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
3511
3910
  }
3512
3911
  const activeAgents = [...this.getActiveAgents().values()];
3513
3912
  const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
@@ -3517,10 +3916,13 @@ var OwneySDK = class {
3517
3916
  if (filters?.cursor && agentCursor === void 0) {
3518
3917
  return { agentId: agent.id, page: null };
3519
3918
  }
3520
- const page = await agent.getHistory(state, chainId, {
3521
- ...filters,
3522
- cursor: agentCursor
3523
- });
3919
+ const agentFilters = { ...filters, cursor: agentCursor };
3920
+ const page = await this.readAgent(
3921
+ agent,
3922
+ "history",
3923
+ () => agent.getHistory(state, chainId, agentFilters),
3924
+ agentFilters
3925
+ );
3524
3926
  return { agentId: agent.id, page };
3525
3927
  })
3526
3928
  );
@@ -3560,13 +3962,14 @@ var OwneySDK = class {
3560
3962
  const state = this.requireState();
3561
3963
  const chainId = this.requireChainId();
3562
3964
  if (agentId) {
3563
- return this.getAgent(agentId).getUserProfile(state, chainId);
3965
+ const agent = this.getAgent(agentId);
3966
+ return this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
3564
3967
  }
3565
3968
  const results = {};
3566
3969
  const entries = [...this.getActiveAgents().entries()];
3567
3970
  const profileResults = await Promise.all(
3568
3971
  entries.map(async ([id, agent]) => {
3569
- const p = await agent.getUserProfile(state, chainId);
3972
+ const p = await this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
3570
3973
  return [id, p];
3571
3974
  })
3572
3975
  );
@@ -3589,7 +3992,20 @@ var OwneySDK = class {
3589
3992
  const chainId = this.requireChainId();
3590
3993
  const agent = this.getAgent(agentId);
3591
3994
  if (typeof agent.ensureAutoSelectProtocols !== "function") return false;
3592
- return agent.ensureAutoSelectProtocols(state, chainId, asset);
3995
+ const key2 = JSON.stringify([
3996
+ state.walletAddress.toLowerCase(),
3997
+ agentId,
3998
+ chainId,
3999
+ asset
4000
+ ]);
4001
+ const existing = this.activationChecks.get(key2);
4002
+ if (existing) return existing;
4003
+ const pending = Promise.resolve().then(() => agent.ensureAutoSelectProtocols(state, chainId, asset)).finally(() => {
4004
+ if (this.activationChecks.get(key2) === pending)
4005
+ this.activationChecks.delete(key2);
4006
+ });
4007
+ this.activationChecks.set(key2, pending);
4008
+ return pending;
3593
4009
  }
3594
4010
  /**
3595
4011
  * One-time, user-paid approval of Permit2 on the sponsored WETH token for
@@ -3660,13 +4076,14 @@ var OwneySDK = class {
3660
4076
  await this.ensureAgentsInitialized();
3661
4077
  const agentOptions = { tokenSymbol, chainId };
3662
4078
  if (agentId) {
3663
- return this.getAgent(agentId).getAgentApy(days, agentOptions);
4079
+ const agent = this.getAgent(agentId);
4080
+ return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
3664
4081
  }
3665
4082
  const results = {};
3666
4083
  const agentEntries = [...this.agents.entries()];
3667
4084
  const apyResults = await Promise.all(
3668
4085
  agentEntries.map(async ([id, agent]) => {
3669
- const apy = await agent.getAgentApy(days, agentOptions);
4086
+ const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
3670
4087
  return [id, apy];
3671
4088
  })
3672
4089
  );
@@ -3693,7 +4110,7 @@ var OwneySDK = class {
3693
4110
  const entries = [...activeAgents.entries()];
3694
4111
  const balanceResults = await Promise.allSettled(
3695
4112
  entries.map(async ([id, agent]) => {
3696
- const b = await agent.getBalances(state, chainId);
4113
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3697
4114
  return [id, b.positions ?? []];
3698
4115
  })
3699
4116
  );
@@ -3701,6 +4118,7 @@ var OwneySDK = class {
3701
4118
  const allPositions = [];
3702
4119
  let successCount = 0;
3703
4120
  const agentErrors = {};
4121
+ const agentFailures = [];
3704
4122
  for (let i = 0; i < balanceResults.length; i++) {
3705
4123
  const settled = balanceResults[i];
3706
4124
  const [aid] = entries[i];
@@ -3715,6 +4133,7 @@ var OwneySDK = class {
3715
4133
  successCount += 1;
3716
4134
  } else {
3717
4135
  const reason = settled.reason;
4136
+ agentFailures.push(reason);
3718
4137
  agentErrors[aid] = reason instanceof Error ? reason.message : String(reason);
3719
4138
  console.error(`getAllocationApy agent "${aid}" failed:`, reason);
3720
4139
  }
@@ -3723,7 +4142,7 @@ var OwneySDK = class {
3723
4142
  throw new OwneyError(
3724
4143
  "ALLOCATION_ALL_FAILED",
3725
4144
  "Failed to fetch allocation APY: all agents failed.",
3726
- { agentErrors }
4145
+ { agentErrors, failures: agentFailures }
3727
4146
  );
3728
4147
  }
3729
4148
  const overall = computeAllocationApy(allPositions);