@owney/sdk 0.7.16 → 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,3 +1,165 @@
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
+ };
162
+
1
163
  // src/agents/zyfai/zyfai.agent.ts
2
164
  import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
3
165
  import { createPublicClient, http, parseAbi } from "viem";
@@ -67,55 +229,6 @@ function debugLog(scope, message, data) {
67
229
  }
68
230
  }
69
231
 
70
- // src/errors.ts
71
- var OwneyError = class extends Error {
72
- code;
73
- details;
74
- agentId;
75
- constructor(code, message, details, agentId) {
76
- const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
77
- super(`${prefix} ${message}`);
78
- this.name = "OwneyError";
79
- this.code = code;
80
- this.details = details;
81
- this.agentId = agentId;
82
- }
83
- };
84
- var AgentNotFoundError = class extends OwneyError {
85
- constructor(agentId, available) {
86
- super(
87
- "AGENT_NOT_FOUND",
88
- `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
89
- { agentId, available },
90
- agentId
91
- );
92
- this.name = "AgentNotFoundError";
93
- }
94
- };
95
- var NotConnectedError = class extends OwneyError {
96
- constructor() {
97
- super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
98
- this.name = "NotConnectedError";
99
- }
100
- };
101
- var AgentChainIncompatibleError = class extends OwneyError {
102
- incompatibleAgents;
103
- connectedChainId;
104
- constructor(incompatibleAgents, connectedChainId) {
105
- const details = incompatibleAgents.map(
106
- ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
107
- ).join("; ");
108
- super(
109
- "AGENT_CHAIN_INCOMPATIBLE",
110
- `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
111
- { incompatibleAgents, connectedChainId }
112
- );
113
- this.name = "AgentChainIncompatibleError";
114
- this.incompatibleAgents = incompatibleAgents;
115
- this.connectedChainId = connectedChainId;
116
- }
117
- };
118
-
119
232
  // src/lib/utils.ts
120
233
  var isValidChainId = (chainId) => {
121
234
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
@@ -919,7 +1032,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
919
1032
  // successful decode is cached forever; failures are NOT cached so a transient
920
1033
  // RPC error retries on the next fetch.
921
1034
  withdrawAmountCache = /* @__PURE__ */ new Map();
922
- earningsRefreshInFlight = null;
1035
+ earningsReads = /* @__PURE__ */ new Map();
1036
+ earningsRefreshes = /* @__PURE__ */ new Map();
1037
+ earningsSnapshot = null;
1038
+ earningsGeneration = 0;
923
1039
  constructor(apiKey, rpcUrls, referralSource) {
924
1040
  this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
925
1041
  this.sdk = new ZyfaiSDK({
@@ -1164,6 +1280,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
1164
1280
  }
1165
1281
  // --- IAgent: Connection lifecycle ---
1166
1282
  async disconnect() {
1283
+ this.earningsGeneration++;
1284
+ this.earningsReads.clear();
1285
+ this.earningsRefreshes.clear();
1286
+ this.earningsSnapshot = null;
1167
1287
  if (this.connectedAddress && this.connectedChainId !== null) {
1168
1288
  clearSession(this.connectedAddress, this.connectedChainId);
1169
1289
  }
@@ -1712,26 +1832,59 @@ var ZyfaiAgent = class _ZyfaiAgent {
1712
1832
  const raw = await this.sdk.getPortfolio(this.getAddress());
1713
1833
  return mapBalances(raw, validChainId, smartWallet);
1714
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
+ }
1715
1858
  async getEarnings(state, chainId) {
1716
1859
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1717
- const raw = await this.sdk.getOnchainEarnings(smartWallet);
1860
+ const raw = await this.readEarnings(
1861
+ this.earningsKey(state, chainId, smartWallet),
1862
+ smartWallet
1863
+ );
1718
1864
  return mapEarnings(raw, smartWallet);
1719
1865
  }
1720
1866
  async refreshEarnings(state, chainId) {
1721
- if (this.earningsRefreshInFlight) return this.earningsRefreshInFlight;
1722
- this.earningsRefreshInFlight = (async () => {
1723
- const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1724
- 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);
1725
1875
  const lastCheck = current.data.lastCheckTimestamp ? Date.parse(current.data.lastCheckTimestamp) : Number.NaN;
1726
1876
  const isFresh = Number.isFinite(lastCheck) && Date.now() - lastCheck < EARNINGS_REFRESH_COOLDOWN_MS;
1727
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
+ }
1728
1881
  return mapEarnings(earnings, smartWallet);
1729
- })();
1730
- try {
1731
- return await this.earningsRefreshInFlight;
1732
- } finally {
1733
- this.earningsRefreshInFlight = null;
1734
- }
1882
+ })().finally(() => {
1883
+ if (this.earningsRefreshes.get(key2) === pending)
1884
+ this.earningsRefreshes.delete(key2);
1885
+ });
1886
+ this.earningsRefreshes.set(key2, pending);
1887
+ return pending;
1735
1888
  }
1736
1889
  async getAccountApy(state, chainId, days, tokenSymbol) {
1737
1890
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -2653,6 +2806,17 @@ var OwneySDK = class {
2653
2806
  cachedWethSponsoredCallback = null;
2654
2807
  paymasterServiceUrl;
2655
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
+ }
2656
2820
  initializingAgentsPromise = null;
2657
2821
  constructor(config) {
2658
2822
  this.apiKey = config.apiKey;
@@ -2679,6 +2843,8 @@ var OwneySDK = class {
2679
2843
  "No accounts found. Ensure the wallet is unlocked and connected."
2680
2844
  );
2681
2845
  }
2846
+ this.agentReads.clearInFlight();
2847
+ this.activationChecks.clear();
2682
2848
  this.state = { provider, walletAddress, chainId: null };
2683
2849
  this.cachedSponsoredCallback = null;
2684
2850
  this.cachedWethSponsoredCallback = null;
@@ -2694,6 +2860,8 @@ var OwneySDK = class {
2694
2860
  }
2695
2861
  this.activeAgents.clear();
2696
2862
  this.disabledAgents.clear();
2863
+ this.agentReads.clearInFlight();
2864
+ this.activationChecks.clear();
2697
2865
  this.state = null;
2698
2866
  this.cachedSponsoredCallback = null;
2699
2867
  this.cachedWethSponsoredCallback = null;
@@ -2924,6 +3092,37 @@ var OwneySDK = class {
2924
3092
  }
2925
3093
  return null;
2926
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
3122
+ });
3123
+ }
3124
+ return available;
3125
+ }
2927
3126
  // --- Account lifecycle ---
2928
3127
  /**
2929
3128
  * Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
@@ -2971,9 +3170,7 @@ var OwneySDK = class {
2971
3170
  this.activeAgents.add(id);
2972
3171
  }
2973
3172
  state.chainId = chainId;
2974
- this.activateAgentsInTurn(agents, state, chainId).catch((error) => {
2975
- console.error("activateAgent background init failed:", error);
2976
- });
3173
+ await this.activateAgentsInTurn(agents, state, chainId);
2977
3174
  return;
2978
3175
  }
2979
3176
  const compatible = [...this.agents.values()].filter(
@@ -2994,11 +3191,7 @@ var OwneySDK = class {
2994
3191
  const enabledCompatible = compatible.filter(
2995
3192
  (agent) => !this.isAgentDisabled(agent.id)
2996
3193
  );
2997
- this.activateAgentsInTurn(enabledCompatible, state, chainId).catch(
2998
- (error) => {
2999
- console.error("activateAgent background init failed:", error);
3000
- }
3001
- );
3194
+ await this.activateAgentsInTurn(enabledCompatible, state, chainId);
3002
3195
  }
3003
3196
  /**
3004
3197
  * Activate agents ONE AT A TIME, each followed by its org policy.
@@ -3035,7 +3228,7 @@ var OwneySDK = class {
3035
3228
  if (firstError !== null) throw firstError;
3036
3229
  }
3037
3230
  /**
3038
- * 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.
3039
3232
  * Validates that the asset is supported and amount meets minimums for the target agent(s).
3040
3233
  * @param options - Deposit parameters
3041
3234
  * @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
@@ -3043,7 +3236,8 @@ var OwneySDK = class {
3043
3236
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
3044
3237
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
3045
3238
  * split amount and smart wallet address — expect multiple wallet prompts.
3046
- * @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.
3047
3241
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
3048
3242
  */
3049
3243
  async deposit(options) {
@@ -3081,7 +3275,7 @@ var OwneySDK = class {
3081
3275
  });
3082
3276
  const exemptFlags = await Promise.all(
3083
3277
  eligibleAgents.map(
3084
- (a) => this.hasExistingBalance(a, state, chainId, asset)
3278
+ (a) => this.hasExistingBalance(a, state, chainId, asset, true)
3085
3279
  )
3086
3280
  );
3087
3281
  const exempt = new Set(
@@ -3095,23 +3289,67 @@ var OwneySDK = class {
3095
3289
  exempt
3096
3290
  );
3097
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);
3098
3303
  throw new OwneyError(
3099
3304
  "DEPOSIT_AMOUNT_BELOW_MINIMUM",
3100
- `Amount "${amount}" cannot satisfy minimum deposit requirements for any eligible agent.`,
3101
- { 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
+ }
3102
3321
  );
3103
3322
  }
3104
3323
  const agentResults = {};
3105
- for (const { agent, amount: agentAmount } of agentAmounts) {
3106
- agentResults[agent.id] = await this.depositWithFallback(
3107
- agent,
3108
- state,
3109
- chainId,
3110
- agentAmount,
3111
- asset,
3112
- effectiveCallback,
3113
- depositCallback
3114
- );
3324
+ for (const [
3325
+ index,
3326
+ { agent, amount: agentAmount }
3327
+ ] of agentAmounts.entries()) {
3328
+ try {
3329
+ agentResults[agent.id] = await this.depositWithFallback(
3330
+ agent,
3331
+ state,
3332
+ chainId,
3333
+ agentAmount,
3334
+ asset,
3335
+ effectiveCallback,
3336
+ depositCallback
3337
+ );
3338
+ } catch (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
+ );
3352
+ }
3115
3353
  }
3116
3354
  return { agentResults };
3117
3355
  }
@@ -3184,6 +3422,17 @@ var OwneySDK = class {
3184
3422
  }
3185
3423
  splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
3186
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
+ }
3187
3436
  const perAgent = totalAmount / BigInt(agents.length);
3188
3437
  const remainder = totalAmount % BigInt(agents.length);
3189
3438
  const splits = agents.map((agent, i) => ({
@@ -3191,18 +3440,26 @@ var OwneySDK = class {
3191
3440
  amount: i === agents.length - 1 ? perAgent + remainder : perAgent
3192
3441
  }));
3193
3442
  const valid = splits.filter(
3194
- (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))
3195
3444
  );
3196
3445
  if (valid.length === agents.length) {
3197
3446
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
3198
3447
  }
3199
- return this.splitDepositAmount(
3200
- totalAmount,
3201
- valid.map((s) => s.agent),
3202
- chainId,
3203
- asset,
3204
- exempt
3205
- );
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
+ }));
3206
3463
  }
3207
3464
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
3208
3465
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
@@ -3227,15 +3484,29 @@ var OwneySDK = class {
3227
3484
  * per-agent minimum. Fails closed: any error reading balances returns
3228
3485
  * false, so the minimum is enforced as today.
3229
3486
  */
3230
- async hasExistingBalance(agent, state, chainId, asset) {
3487
+ async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
3231
3488
  try {
3232
3489
  const balance = await agent.getBalances(state, chainId);
3233
3490
  const target = asset.toLowerCase();
3234
3491
  const token = balance.tokens.find(
3235
3492
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
3236
3493
  );
3237
- return !!token && Number(token.amount) > 0;
3238
- } 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
+ }
3239
3510
  return false;
3240
3511
  }
3241
3512
  }
@@ -3438,7 +3709,8 @@ var OwneySDK = class {
3438
3709
  const state = this.requireState();
3439
3710
  const chainId = this.requireChainId();
3440
3711
  if (agentId) {
3441
- 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));
3442
3714
  return result;
3443
3715
  }
3444
3716
  let totalBalance = 0;
@@ -3446,12 +3718,13 @@ var OwneySDK = class {
3446
3718
  const entries = [...this.getActiveAgents().entries()];
3447
3719
  const balanceResults = await Promise.allSettled(
3448
3720
  entries.map(async ([id, agent]) => {
3449
- const b = await agent.getBalances(state, chainId);
3721
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3450
3722
  return [id, b];
3451
3723
  })
3452
3724
  );
3453
3725
  let successCount = 0;
3454
3726
  const agentErrors = {};
3727
+ const agentFailures = [];
3455
3728
  for (let i = 0; i < balanceResults.length; i++) {
3456
3729
  const settledResult = balanceResults[i];
3457
3730
  const [agentId2] = entries[i];
@@ -3463,13 +3736,14 @@ var OwneySDK = class {
3463
3736
  continue;
3464
3737
  }
3465
3738
  const reason = settledResult.reason;
3739
+ agentFailures.push(reason);
3466
3740
  agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
3467
3741
  }
3468
3742
  if (successCount === 0) {
3469
3743
  throw new OwneyError(
3470
3744
  "BALANCE_ALL_FAILED",
3471
3745
  "Failed to fetch balances for all active agents.",
3472
- { agentErrors }
3746
+ { agentErrors, failures: agentFailures }
3473
3747
  );
3474
3748
  }
3475
3749
  return {
@@ -3487,14 +3761,15 @@ var OwneySDK = class {
3487
3761
  const state = this.requireState();
3488
3762
  const chainId = this.requireChainId();
3489
3763
  if (agentId) {
3490
- return this.getAgent(agentId).getEarnings(state, chainId);
3764
+ const agent = this.getAgent(agentId);
3765
+ return this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
3491
3766
  }
3492
3767
  let totalEarnings = 0;
3493
3768
  const results = {};
3494
3769
  const entries = [...this.getActiveAgents().entries()];
3495
3770
  const earningsResults = await Promise.all(
3496
3771
  entries.map(async ([id, agent]) => {
3497
- const e = await agent.getEarnings(state, chainId);
3772
+ const e = await this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
3498
3773
  return [id, e];
3499
3774
  })
3500
3775
  );
@@ -3514,7 +3789,11 @@ var OwneySDK = class {
3514
3789
  async refreshEarnings(agentId) {
3515
3790
  const state = this.requireState();
3516
3791
  const chainId = this.requireChainId();
3517
- 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
+ );
3518
3797
  if (agentId) return refreshAgent(this.getAgent(agentId));
3519
3798
  let totalEarnings = 0;
3520
3799
  const agentEarnings = {};
@@ -3545,11 +3824,12 @@ var OwneySDK = class {
3545
3824
  const state = this.requireState();
3546
3825
  const chainId = this.requireChainId();
3547
3826
  if (agentId) {
3548
- return this.getAgent(agentId).getAccountApy(
3549
- state,
3550
- chainId,
3551
- days,
3552
- 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 }
3553
3833
  );
3554
3834
  }
3555
3835
  const activeAgents = this.getActiveAgents();
@@ -3557,18 +3837,18 @@ var OwneySDK = class {
3557
3837
  const [apyResults, balanceResults] = await Promise.all([
3558
3838
  Promise.all(
3559
3839
  entries.map(async ([id, agent]) => {
3560
- const apy = await agent.getAccountApy(
3561
- state,
3562
- chainId,
3563
- days,
3564
- tokenSymbol
3840
+ const apy = await this.readAgent(
3841
+ agent,
3842
+ "accountApy",
3843
+ () => agent.getAccountApy(state, chainId, days, tokenSymbol),
3844
+ { days, tokenSymbol }
3565
3845
  );
3566
3846
  return [id, apy];
3567
3847
  })
3568
3848
  ),
3569
3849
  Promise.all(
3570
3850
  entries.map(async ([id, agent]) => {
3571
- const b = await agent.getBalances(state, chainId);
3851
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3572
3852
  return [id, Number(b.totalBalance)];
3573
3853
  })
3574
3854
  )
@@ -3625,7 +3905,8 @@ var OwneySDK = class {
3625
3905
  const chainId = this.requireChainId();
3626
3906
  const { agentId, filters } = options ?? {};
3627
3907
  if (agentId) {
3628
- 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);
3629
3910
  }
3630
3911
  const activeAgents = [...this.getActiveAgents().values()];
3631
3912
  const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
@@ -3635,10 +3916,13 @@ var OwneySDK = class {
3635
3916
  if (filters?.cursor && agentCursor === void 0) {
3636
3917
  return { agentId: agent.id, page: null };
3637
3918
  }
3638
- const page = await agent.getHistory(state, chainId, {
3639
- ...filters,
3640
- cursor: agentCursor
3641
- });
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
+ );
3642
3926
  return { agentId: agent.id, page };
3643
3927
  })
3644
3928
  );
@@ -3678,13 +3962,14 @@ var OwneySDK = class {
3678
3962
  const state = this.requireState();
3679
3963
  const chainId = this.requireChainId();
3680
3964
  if (agentId) {
3681
- return this.getAgent(agentId).getUserProfile(state, chainId);
3965
+ const agent = this.getAgent(agentId);
3966
+ return this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
3682
3967
  }
3683
3968
  const results = {};
3684
3969
  const entries = [...this.getActiveAgents().entries()];
3685
3970
  const profileResults = await Promise.all(
3686
3971
  entries.map(async ([id, agent]) => {
3687
- const p = await agent.getUserProfile(state, chainId);
3972
+ const p = await this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
3688
3973
  return [id, p];
3689
3974
  })
3690
3975
  );
@@ -3707,7 +3992,20 @@ var OwneySDK = class {
3707
3992
  const chainId = this.requireChainId();
3708
3993
  const agent = this.getAgent(agentId);
3709
3994
  if (typeof agent.ensureAutoSelectProtocols !== "function") return false;
3710
- 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;
3711
4009
  }
3712
4010
  /**
3713
4011
  * One-time, user-paid approval of Permit2 on the sponsored WETH token for
@@ -3778,13 +4076,14 @@ var OwneySDK = class {
3778
4076
  await this.ensureAgentsInitialized();
3779
4077
  const agentOptions = { tokenSymbol, chainId };
3780
4078
  if (agentId) {
3781
- 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 });
3782
4081
  }
3783
4082
  const results = {};
3784
4083
  const agentEntries = [...this.agents.entries()];
3785
4084
  const apyResults = await Promise.all(
3786
4085
  agentEntries.map(async ([id, agent]) => {
3787
- const apy = await agent.getAgentApy(days, agentOptions);
4086
+ const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
3788
4087
  return [id, apy];
3789
4088
  })
3790
4089
  );
@@ -3811,7 +4110,7 @@ var OwneySDK = class {
3811
4110
  const entries = [...activeAgents.entries()];
3812
4111
  const balanceResults = await Promise.allSettled(
3813
4112
  entries.map(async ([id, agent]) => {
3814
- const b = await agent.getBalances(state, chainId);
4113
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3815
4114
  return [id, b.positions ?? []];
3816
4115
  })
3817
4116
  );
@@ -3819,6 +4118,7 @@ var OwneySDK = class {
3819
4118
  const allPositions = [];
3820
4119
  let successCount = 0;
3821
4120
  const agentErrors = {};
4121
+ const agentFailures = [];
3822
4122
  for (let i = 0; i < balanceResults.length; i++) {
3823
4123
  const settled = balanceResults[i];
3824
4124
  const [aid] = entries[i];
@@ -3833,6 +4133,7 @@ var OwneySDK = class {
3833
4133
  successCount += 1;
3834
4134
  } else {
3835
4135
  const reason = settled.reason;
4136
+ agentFailures.push(reason);
3836
4137
  agentErrors[aid] = reason instanceof Error ? reason.message : String(reason);
3837
4138
  console.error(`getAllocationApy agent "${aid}" failed:`, reason);
3838
4139
  }
@@ -3841,7 +4142,7 @@ var OwneySDK = class {
3841
4142
  throw new OwneyError(
3842
4143
  "ALLOCATION_ALL_FAILED",
3843
4144
  "Failed to fetch allocation APY: all agents failed.",
3844
- { agentErrors }
4145
+ { agentErrors, failures: agentFailures }
3845
4146
  );
3846
4147
  }
3847
4148
  const overall = computeAllocationApy(allPositions);