@owney/sdk 0.7.16 → 0.7.17-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
@@ -31,6 +31,168 @@ __export(index_exports, {
31
31
  });
32
32
  module.exports = __toCommonJS(index_exports);
33
33
 
34
+ // src/errors.ts
35
+ var OwneyError = class extends Error {
36
+ code;
37
+ details;
38
+ agentId;
39
+ constructor(code, message, details, agentId) {
40
+ const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
41
+ super(`${prefix} ${message}`);
42
+ this.name = "OwneyError";
43
+ this.code = code;
44
+ this.details = details;
45
+ this.agentId = agentId;
46
+ }
47
+ };
48
+ var AgentNotFoundError = class extends OwneyError {
49
+ constructor(agentId, available) {
50
+ super(
51
+ "AGENT_NOT_FOUND",
52
+ `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
53
+ { agentId, available },
54
+ agentId
55
+ );
56
+ this.name = "AgentNotFoundError";
57
+ }
58
+ };
59
+ var NotConnectedError = class extends OwneyError {
60
+ constructor() {
61
+ super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
62
+ this.name = "NotConnectedError";
63
+ }
64
+ };
65
+ var AgentChainIncompatibleError = class extends OwneyError {
66
+ incompatibleAgents;
67
+ connectedChainId;
68
+ constructor(incompatibleAgents, connectedChainId) {
69
+ const details = incompatibleAgents.map(
70
+ ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
71
+ ).join("; ");
72
+ super(
73
+ "AGENT_CHAIN_INCOMPATIBLE",
74
+ `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
75
+ { incompatibleAgents, connectedChainId }
76
+ );
77
+ this.name = "AgentChainIncompatibleError";
78
+ this.incompatibleAgents = incompatibleAgents;
79
+ this.connectedChainId = connectedChainId;
80
+ }
81
+ };
82
+
83
+ // src/lib/rate-limit.ts
84
+ function rateLimitDelay(error, now = Date.now()) {
85
+ const seen = /* @__PURE__ */ new Set();
86
+ let limited = false;
87
+ let delay = 0;
88
+ function visit(value, depth = 0) {
89
+ if (depth > 6 || value == null) return;
90
+ if (typeof value === "string") {
91
+ if (/rate[ _-]?limit|too many requests|HTTP_429|\b429\b/i.test(value))
92
+ limited = true;
93
+ return;
94
+ }
95
+ if (typeof value !== "object" || seen.has(value)) return;
96
+ seen.add(value);
97
+ const record = value;
98
+ if ([record.status, record.statusCode, record.code].some(
99
+ (code) => String(code) === "429"
100
+ )) {
101
+ limited = true;
102
+ }
103
+ for (const key2 of ["retryAfterSeconds", "retryAfter"]) {
104
+ const seconds = Number(record[key2]);
105
+ if (Number.isFinite(seconds) && seconds > 0)
106
+ delay = Math.max(delay, seconds * 1e3);
107
+ }
108
+ const retryAt = Number(record.retryAt);
109
+ if (Number.isFinite(retryAt) && retryAt > now)
110
+ delay = Math.max(delay, retryAt - now);
111
+ const headers = record.headers;
112
+ const header = typeof headers?.get === "function" ? headers.get("Retry-After") : headers?.["retry-after"] ?? headers?.["Retry-After"];
113
+ if (typeof header === "string" || typeof header === "number") {
114
+ const seconds = Number(header);
115
+ const ms = Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(String(header)) - now;
116
+ if (Number.isFinite(ms) && ms > 0) delay = Math.max(delay, ms);
117
+ }
118
+ for (const key2 of [
119
+ "message",
120
+ "code",
121
+ "cause",
122
+ "details",
123
+ "response",
124
+ "data",
125
+ "fields"
126
+ ]) {
127
+ visit(record[key2], depth + 1);
128
+ }
129
+ for (const key2 of ["agentErrors", "failures"]) {
130
+ const entries = record[key2];
131
+ if (entries && typeof entries === "object") {
132
+ for (const entry of Object.values(entries)) visit(entry, depth + 1);
133
+ }
134
+ }
135
+ }
136
+ visit(error);
137
+ return limited ? delay : void 0;
138
+ }
139
+
140
+ // src/lib/agent-reads.ts
141
+ var AgentReads = class {
142
+ inFlight = /* @__PURE__ */ new Map();
143
+ cooldowns = /* @__PURE__ */ new Map();
144
+ // A reconnect must not reuse work started by the previous connection.
145
+ clearInFlight() {
146
+ this.inFlight.clear();
147
+ }
148
+ limited(agentId, until) {
149
+ return new OwneyError(
150
+ "AGENT_RATE_LIMITED",
151
+ "Too many requests. Please wait before trying again.",
152
+ {
153
+ statusCode: 429,
154
+ retryAt: until,
155
+ retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3))
156
+ },
157
+ agentId
158
+ );
159
+ }
160
+ run(agentId, key2, fetch2) {
161
+ const cooldown = this.cooldowns.get(agentId);
162
+ if (cooldown && cooldown.until > Date.now()) {
163
+ return Promise.reject(this.limited(agentId, cooldown.until));
164
+ }
165
+ const requestKey = JSON.stringify([agentId, key2]);
166
+ const existing = this.inFlight.get(requestKey);
167
+ if (existing) return existing;
168
+ const promise = Promise.resolve().then(fetch2).then(
169
+ (value) => {
170
+ if (this.cooldowns.get(agentId) === cooldown)
171
+ this.cooldowns.delete(agentId);
172
+ return value;
173
+ },
174
+ (error) => {
175
+ const requestedDelay = rateLimitDelay(error);
176
+ if (requestedDelay === void 0) throw error;
177
+ const previous = this.cooldowns.get(agentId);
178
+ const failures = previous && previous.until > Date.now() ? previous.failures : Math.min((previous?.failures ?? 0) + 1, 5);
179
+ const delay = Math.max(
180
+ requestedDelay,
181
+ Math.min(3e4 * 2 ** (failures - 1), 3e5)
182
+ );
183
+ const until = Math.max(previous?.until ?? 0, Date.now() + delay);
184
+ this.cooldowns.set(agentId, { until, failures });
185
+ throw this.limited(agentId, until);
186
+ }
187
+ ).finally(() => {
188
+ if (this.inFlight.get(requestKey) === promise)
189
+ this.inFlight.delete(requestKey);
190
+ });
191
+ this.inFlight.set(requestKey, promise);
192
+ return promise;
193
+ }
194
+ };
195
+
34
196
  // src/agents/zyfai/zyfai.agent.ts
35
197
  var import_sdk = require("@zyfai/sdk");
36
198
  var import_viem = require("viem");
@@ -100,55 +262,6 @@ function debugLog(scope, message, data) {
100
262
  }
101
263
  }
102
264
 
103
- // src/errors.ts
104
- var OwneyError = class extends Error {
105
- code;
106
- details;
107
- agentId;
108
- constructor(code, message, details, agentId) {
109
- const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
110
- super(`${prefix} ${message}`);
111
- this.name = "OwneyError";
112
- this.code = code;
113
- this.details = details;
114
- this.agentId = agentId;
115
- }
116
- };
117
- var AgentNotFoundError = class extends OwneyError {
118
- constructor(agentId, available) {
119
- super(
120
- "AGENT_NOT_FOUND",
121
- `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
122
- { agentId, available },
123
- agentId
124
- );
125
- this.name = "AgentNotFoundError";
126
- }
127
- };
128
- var NotConnectedError = class extends OwneyError {
129
- constructor() {
130
- super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
131
- this.name = "NotConnectedError";
132
- }
133
- };
134
- var AgentChainIncompatibleError = class extends OwneyError {
135
- incompatibleAgents;
136
- connectedChainId;
137
- constructor(incompatibleAgents, connectedChainId) {
138
- const details = incompatibleAgents.map(
139
- ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
140
- ).join("; ");
141
- super(
142
- "AGENT_CHAIN_INCOMPATIBLE",
143
- `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
144
- { incompatibleAgents, connectedChainId }
145
- );
146
- this.name = "AgentChainIncompatibleError";
147
- this.incompatibleAgents = incompatibleAgents;
148
- this.connectedChainId = connectedChainId;
149
- }
150
- };
151
-
152
265
  // src/lib/utils.ts
153
266
  var isValidChainId = (chainId) => {
154
267
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
@@ -952,7 +1065,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
952
1065
  // successful decode is cached forever; failures are NOT cached so a transient
953
1066
  // RPC error retries on the next fetch.
954
1067
  withdrawAmountCache = /* @__PURE__ */ new Map();
955
- earningsRefreshInFlight = null;
1068
+ earningsReads = /* @__PURE__ */ new Map();
1069
+ earningsRefreshes = /* @__PURE__ */ new Map();
1070
+ earningsSnapshot = null;
1071
+ earningsGeneration = 0;
956
1072
  constructor(apiKey, rpcUrls, referralSource) {
957
1073
  this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
958
1074
  this.sdk = new import_sdk.ZyfaiSDK({
@@ -1197,6 +1313,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
1197
1313
  }
1198
1314
  // --- IAgent: Connection lifecycle ---
1199
1315
  async disconnect() {
1316
+ this.earningsGeneration++;
1317
+ this.earningsReads.clear();
1318
+ this.earningsRefreshes.clear();
1319
+ this.earningsSnapshot = null;
1200
1320
  if (this.connectedAddress && this.connectedChainId !== null) {
1201
1321
  clearSession(this.connectedAddress, this.connectedChainId);
1202
1322
  }
@@ -1745,26 +1865,59 @@ var ZyfaiAgent = class _ZyfaiAgent {
1745
1865
  const raw = await this.sdk.getPortfolio(this.getAddress());
1746
1866
  return mapBalances(raw, validChainId, smartWallet);
1747
1867
  }
1868
+ earningsKey(state, chainId, smartWallet) {
1869
+ return JSON.stringify([
1870
+ state.walletAddress?.toLowerCase(),
1871
+ chainId,
1872
+ smartWallet.toLowerCase()
1873
+ ]);
1874
+ }
1875
+ readEarnings(key2, smartWallet) {
1876
+ const existing = this.earningsReads.get(key2);
1877
+ if (existing) return existing;
1878
+ const generation = this.earningsGeneration;
1879
+ const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw) => {
1880
+ if (generation === this.earningsGeneration) {
1881
+ this.earningsSnapshot = { key: key2, raw, at: Date.now() };
1882
+ }
1883
+ return raw;
1884
+ }).finally(() => {
1885
+ if (this.earningsReads.get(key2) === pending)
1886
+ this.earningsReads.delete(key2);
1887
+ });
1888
+ this.earningsReads.set(key2, pending);
1889
+ return pending;
1890
+ }
1748
1891
  async getEarnings(state, chainId) {
1749
1892
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1750
- const raw = await this.sdk.getOnchainEarnings(smartWallet);
1893
+ const raw = await this.readEarnings(
1894
+ this.earningsKey(state, chainId, smartWallet),
1895
+ smartWallet
1896
+ );
1751
1897
  return mapEarnings(raw, smartWallet);
1752
1898
  }
1753
1899
  async refreshEarnings(state, chainId) {
1754
- if (this.earningsRefreshInFlight) return this.earningsRefreshInFlight;
1755
- this.earningsRefreshInFlight = (async () => {
1756
- const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1757
- const current = await this.sdk.getOnchainEarnings(smartWallet);
1900
+ const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1901
+ const key2 = this.earningsKey(state, chainId, smartWallet);
1902
+ const existing = this.earningsRefreshes.get(key2);
1903
+ if (existing) return existing;
1904
+ const generation = this.earningsGeneration;
1905
+ const pending = (async () => {
1906
+ const recent = this.earningsSnapshot;
1907
+ const current = recent?.key === key2 && Date.now() - recent.at < 5e3 ? recent.raw : await this.readEarnings(key2, smartWallet);
1758
1908
  const lastCheck = current.data.lastCheckTimestamp ? Date.parse(current.data.lastCheckTimestamp) : Number.NaN;
1759
1909
  const isFresh = Number.isFinite(lastCheck) && Date.now() - lastCheck < EARNINGS_REFRESH_COOLDOWN_MS;
1760
1910
  const earnings = isFresh ? current : await this.sdk.calculateOnchainEarnings(smartWallet);
1911
+ if (!isFresh && generation === this.earningsGeneration) {
1912
+ this.earningsSnapshot = { key: key2, raw: earnings, at: Date.now() };
1913
+ }
1761
1914
  return mapEarnings(earnings, smartWallet);
1762
- })();
1763
- try {
1764
- return await this.earningsRefreshInFlight;
1765
- } finally {
1766
- this.earningsRefreshInFlight = null;
1767
- }
1915
+ })().finally(() => {
1916
+ if (this.earningsRefreshes.get(key2) === pending)
1917
+ this.earningsRefreshes.delete(key2);
1918
+ });
1919
+ this.earningsRefreshes.set(key2, pending);
1920
+ return pending;
1768
1921
  }
1769
1922
  async getAccountApy(state, chainId, days, tokenSymbol) {
1770
1923
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -2682,6 +2835,17 @@ var OwneySDK = class {
2682
2835
  cachedWethSponsoredCallback = null;
2683
2836
  paymasterServiceUrl;
2684
2837
  cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
2838
+ agentReads = new AgentReads();
2839
+ activationChecks = /* @__PURE__ */ new Map();
2840
+ readAgent(agent, method, fetch2, params = null) {
2841
+ const key2 = JSON.stringify([
2842
+ this.state?.walletAddress.toLowerCase() ?? null,
2843
+ this.state?.chainId ?? null,
2844
+ method,
2845
+ params
2846
+ ]);
2847
+ return this.agentReads.run(agent.id, key2, fetch2);
2848
+ }
2685
2849
  initializingAgentsPromise = null;
2686
2850
  constructor(config) {
2687
2851
  this.apiKey = config.apiKey;
@@ -2708,6 +2872,8 @@ var OwneySDK = class {
2708
2872
  "No accounts found. Ensure the wallet is unlocked and connected."
2709
2873
  );
2710
2874
  }
2875
+ this.agentReads.clearInFlight();
2876
+ this.activationChecks.clear();
2711
2877
  this.state = { provider, walletAddress, chainId: null };
2712
2878
  this.cachedSponsoredCallback = null;
2713
2879
  this.cachedWethSponsoredCallback = null;
@@ -2723,6 +2889,8 @@ var OwneySDK = class {
2723
2889
  }
2724
2890
  this.activeAgents.clear();
2725
2891
  this.disabledAgents.clear();
2892
+ this.agentReads.clearInFlight();
2893
+ this.activationChecks.clear();
2726
2894
  this.state = null;
2727
2895
  this.cachedSponsoredCallback = null;
2728
2896
  this.cachedWethSponsoredCallback = null;
@@ -2953,6 +3121,37 @@ var OwneySDK = class {
2953
3121
  }
2954
3122
  return null;
2955
3123
  }
3124
+ /**
3125
+ * Discover the agents actually returned by routing for this organization.
3126
+ * Consumers should use this instead of hardcoding a global agent roster.
3127
+ */
3128
+ async getAvailableAgents(options = {}) {
3129
+ await this.ensureAgentsInitialized();
3130
+ const { chainId, asset, includeDisabled = false } = options;
3131
+ const available = [];
3132
+ for (const [id, agent] of this.agents) {
3133
+ const isEnabled = !this.isAgentDisabled(id);
3134
+ if (!includeDisabled && !isEnabled) continue;
3135
+ if (chainId !== void 0 && !agent.supportedChainIds.includes(chainId)) {
3136
+ continue;
3137
+ }
3138
+ if (asset !== void 0) {
3139
+ const supportsAsset = agent.supportedAssets.some(
3140
+ (entry) => (chainId === void 0 || entry.chainId === chainId) && entry.assets.some((candidate) => candidate.symbol === asset)
3141
+ );
3142
+ if (!supportsAsset) {
3143
+ continue;
3144
+ }
3145
+ }
3146
+ available.push({
3147
+ id,
3148
+ isEnabled,
3149
+ supportedChainIds: agent.supportedChainIds,
3150
+ supportedAssets: agent.supportedAssets
3151
+ });
3152
+ }
3153
+ return available;
3154
+ }
2956
3155
  // --- Account lifecycle ---
2957
3156
  /**
2958
3157
  * Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
@@ -3000,9 +3199,7 @@ var OwneySDK = class {
3000
3199
  this.activeAgents.add(id);
3001
3200
  }
3002
3201
  state.chainId = chainId;
3003
- this.activateAgentsInTurn(agents, state, chainId).catch((error) => {
3004
- console.error("activateAgent background init failed:", error);
3005
- });
3202
+ await this.activateAgentsInTurn(agents, state, chainId);
3006
3203
  return;
3007
3204
  }
3008
3205
  const compatible = [...this.agents.values()].filter(
@@ -3023,11 +3220,7 @@ var OwneySDK = class {
3023
3220
  const enabledCompatible = compatible.filter(
3024
3221
  (agent) => !this.isAgentDisabled(agent.id)
3025
3222
  );
3026
- this.activateAgentsInTurn(enabledCompatible, state, chainId).catch(
3027
- (error) => {
3028
- console.error("activateAgent background init failed:", error);
3029
- }
3030
- );
3223
+ await this.activateAgentsInTurn(enabledCompatible, state, chainId);
3031
3224
  }
3032
3225
  /**
3033
3226
  * Activate agents ONE AT A TIME, each followed by its org policy.
@@ -3064,7 +3257,7 @@ var OwneySDK = class {
3064
3257
  if (firstError !== null) throw firstError;
3065
3258
  }
3066
3259
  /**
3067
- * Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
3260
+ * Deposit into a specific agent, or distribute across all eligible agents.
3068
3261
  * Validates that the asset is supported and amount meets minimums for the target agent(s).
3069
3262
  * @param options - Deposit parameters
3070
3263
  * @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
@@ -3072,7 +3265,8 @@ var OwneySDK = class {
3072
3265
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
3073
3266
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
3074
3267
  * split amount and smart wallet address — expect multiple wallet prompts.
3075
- * @param options.agentId - Optional. Target agent. Omit to split equally across all agents.
3268
+ * @param options.agentId - Optional explicit target. Otherwise split equally,
3269
+ * or fund remaining agents when a recovery deposit cannot meet every minimum.
3076
3270
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
3077
3271
  */
3078
3272
  async deposit(options) {
@@ -3110,7 +3304,7 @@ var OwneySDK = class {
3110
3304
  });
3111
3305
  const exemptFlags = await Promise.all(
3112
3306
  eligibleAgents.map(
3113
- (a) => this.hasExistingBalance(a, state, chainId, asset)
3307
+ (a) => this.hasExistingBalance(a, state, chainId, asset, true)
3114
3308
  )
3115
3309
  );
3116
3310
  const exempt = new Set(
@@ -3124,23 +3318,67 @@ var OwneySDK = class {
3124
3318
  exempt
3125
3319
  );
3126
3320
  if (agentAmounts.length === 0) {
3321
+ const agentsRequiringActivation = eligibleAgents.filter(
3322
+ (agent) => !exempt.has(agent.id)
3323
+ );
3324
+ const perAgentMinimum = agentsRequiringActivation.reduce(
3325
+ (highest, agent) => {
3326
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
3327
+ return minimum > highest ? minimum : highest;
3328
+ },
3329
+ 0n
3330
+ );
3331
+ const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
3127
3332
  throw new OwneyError(
3128
3333
  "DEPOSIT_AMOUNT_BELOW_MINIMUM",
3129
- `Amount "${amount}" cannot satisfy minimum deposit requirements for any eligible agent.`,
3130
- { amount }
3334
+ `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
3335
+ {
3336
+ amount,
3337
+ asset,
3338
+ chainId,
3339
+ minDepositAmount: minimumRequired.toString(),
3340
+ perAgentDepositAmount: perAgentMinimum.toString(),
3341
+ agentMinimums: agentsRequiringActivation.map((agent) => ({
3342
+ agentId: agent.id,
3343
+ minDepositAmount: this.getMinDepositAmount(
3344
+ agent,
3345
+ chainId,
3346
+ asset
3347
+ ).toString()
3348
+ }))
3349
+ }
3131
3350
  );
3132
3351
  }
3133
3352
  const agentResults = {};
3134
- for (const { agent, amount: agentAmount } of agentAmounts) {
3135
- agentResults[agent.id] = await this.depositWithFallback(
3136
- agent,
3137
- state,
3138
- chainId,
3139
- agentAmount,
3140
- asset,
3141
- effectiveCallback,
3142
- depositCallback
3143
- );
3353
+ for (const [
3354
+ index,
3355
+ { agent, amount: agentAmount }
3356
+ ] of agentAmounts.entries()) {
3357
+ try {
3358
+ agentResults[agent.id] = await this.depositWithFallback(
3359
+ agent,
3360
+ state,
3361
+ chainId,
3362
+ agentAmount,
3363
+ asset,
3364
+ effectiveCallback,
3365
+ depositCallback
3366
+ );
3367
+ } catch (error) {
3368
+ if (Object.keys(agentResults).length === 0) throw error;
3369
+ throw new OwneyError(
3370
+ "DEPOSIT_PARTIAL_FAILURE",
3371
+ "Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
3372
+ {
3373
+ agentResults,
3374
+ failedAgentId: agent.id,
3375
+ failedAmount: agentAmount,
3376
+ unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
3377
+ cause: error
3378
+ },
3379
+ agent.id
3380
+ );
3381
+ }
3144
3382
  }
3145
3383
  return { agentResults };
3146
3384
  }
@@ -3213,6 +3451,17 @@ var OwneySDK = class {
3213
3451
  }
3214
3452
  splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
3215
3453
  if (agents.length === 0) return [];
3454
+ const agentsRequiringActivation = agents.filter(
3455
+ (agent) => !exempt.has(agent.id)
3456
+ );
3457
+ if (agentsRequiringActivation.length === agents.length) {
3458
+ const highestMinimum2 = agents.reduce((highest, agent) => {
3459
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
3460
+ return minimum > highest ? minimum : highest;
3461
+ }, 0n);
3462
+ const minimumRequired2 = (highestMinimum2 > 0n ? highestMinimum2 : 1n) * BigInt(agents.length);
3463
+ if (totalAmount < minimumRequired2) return [];
3464
+ }
3216
3465
  const perAgent = totalAmount / BigInt(agents.length);
3217
3466
  const remainder = totalAmount % BigInt(agents.length);
3218
3467
  const splits = agents.map((agent, i) => ({
@@ -3220,18 +3469,26 @@ var OwneySDK = class {
3220
3469
  amount: i === agents.length - 1 ? perAgent + remainder : perAgent
3221
3470
  }));
3222
3471
  const valid = splits.filter(
3223
- (s) => exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset)
3472
+ (s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
3224
3473
  );
3225
3474
  if (valid.length === agents.length) {
3226
3475
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
3227
3476
  }
3228
- return this.splitDepositAmount(
3229
- totalAmount,
3230
- valid.map((s) => s.agent),
3231
- chainId,
3232
- asset,
3233
- exempt
3234
- );
3477
+ const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
3478
+ const highestMinimum = targets.reduce((highest, agent) => {
3479
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
3480
+ return minimum > highest ? minimum : highest;
3481
+ }, 0n);
3482
+ const minimumRequired = (highestMinimum > 0n ? highestMinimum : 1n) * BigInt(targets.length);
3483
+ if (totalAmount < minimumRequired) return [];
3484
+ const targetShare = totalAmount / BigInt(targets.length);
3485
+ const targetRemainder = totalAmount % BigInt(targets.length);
3486
+ return targets.map((agent, index) => ({
3487
+ agent,
3488
+ amount: String(
3489
+ targetShare + (index === targets.length - 1 ? targetRemainder : 0n)
3490
+ )
3491
+ }));
3235
3492
  }
3236
3493
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
3237
3494
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
@@ -3256,15 +3513,29 @@ var OwneySDK = class {
3256
3513
  * per-agent minimum. Fails closed: any error reading balances returns
3257
3514
  * false, so the minimum is enforced as today.
3258
3515
  */
3259
- async hasExistingBalance(agent, state, chainId, asset) {
3516
+ async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
3260
3517
  try {
3261
3518
  const balance = await agent.getBalances(state, chainId);
3262
3519
  const target = asset.toLowerCase();
3263
3520
  const token = balance.tokens.find(
3264
3521
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
3265
3522
  );
3266
- return !!token && Number(token.amount) > 0;
3267
- } catch {
3523
+ const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
3524
+ const position = (balance.positions ?? []).find((p) => {
3525
+ const positionChain = p.chain.trim().toUpperCase();
3526
+ const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
3527
+ return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
3528
+ });
3529
+ return !!token && Number(token.amount) > 0 || !!position;
3530
+ } catch (error) {
3531
+ if (requireReliableRead) {
3532
+ throw new OwneyError(
3533
+ "DEPOSIT_BALANCE_UNAVAILABLE",
3534
+ "Could not verify every agent balance. No deposit was submitted. Try again once balances are available.",
3535
+ { agentId: agent.id, chainId, asset, cause: error },
3536
+ agent.id
3537
+ );
3538
+ }
3268
3539
  return false;
3269
3540
  }
3270
3541
  }
@@ -3360,9 +3631,12 @@ var OwneySDK = class {
3360
3631
  { asset, agentErrors: agentErrors2 }
3361
3632
  );
3362
3633
  }
3634
+ const amountsKnown = Object.values(results2).every(
3635
+ (result) => typeof result.amount === "string" && /^\d+$/.test(result.amount)
3636
+ );
3363
3637
  return {
3364
3638
  agentResult: results2,
3365
- totalWithdrawn: sumWithdrawnAmount(results2).toString()
3639
+ totalWithdrawn: amountsKnown ? sumWithdrawnAmount(results2).toString() : null
3366
3640
  };
3367
3641
  }
3368
3642
  const requested = BigInt(amount);
@@ -3467,7 +3741,8 @@ var OwneySDK = class {
3467
3741
  const state = this.requireState();
3468
3742
  const chainId = this.requireChainId();
3469
3743
  if (agentId) {
3470
- const result = await this.getAgent(agentId).getBalances(state, chainId);
3744
+ const agent = this.getAgent(agentId);
3745
+ const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3471
3746
  return result;
3472
3747
  }
3473
3748
  let totalBalance = 0;
@@ -3475,12 +3750,14 @@ var OwneySDK = class {
3475
3750
  const entries = [...this.getActiveAgents().entries()];
3476
3751
  const balanceResults = await Promise.allSettled(
3477
3752
  entries.map(async ([id, agent]) => {
3478
- const b = await agent.getBalances(state, chainId);
3753
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3479
3754
  return [id, b];
3480
3755
  })
3481
3756
  );
3482
3757
  let successCount = 0;
3483
3758
  const agentErrors = {};
3759
+ const agentRetryAt = {};
3760
+ const agentFailures = [];
3484
3761
  for (let i = 0; i < balanceResults.length; i++) {
3485
3762
  const settledResult = balanceResults[i];
3486
3763
  const [agentId2] = entries[i];
@@ -3492,19 +3769,23 @@ var OwneySDK = class {
3492
3769
  continue;
3493
3770
  }
3494
3771
  const reason = settledResult.reason;
3772
+ agentFailures.push(reason);
3773
+ const retryDelay = rateLimitDelay(reason);
3774
+ if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
3495
3775
  agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
3496
3776
  }
3497
3777
  if (successCount === 0) {
3498
3778
  throw new OwneyError(
3499
3779
  "BALANCE_ALL_FAILED",
3500
3780
  "Failed to fetch balances for all active agents.",
3501
- { agentErrors }
3781
+ { agentErrors, failures: agentFailures }
3502
3782
  );
3503
3783
  }
3504
3784
  return {
3505
3785
  totalBalance: String(totalBalance),
3506
3786
  totalBalanceAsset: "usdc",
3507
- agentBalances: results
3787
+ agentBalances: results,
3788
+ ...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
3508
3789
  };
3509
3790
  }
3510
3791
  /**
@@ -3516,14 +3797,15 @@ var OwneySDK = class {
3516
3797
  const state = this.requireState();
3517
3798
  const chainId = this.requireChainId();
3518
3799
  if (agentId) {
3519
- return this.getAgent(agentId).getEarnings(state, chainId);
3800
+ const agent = this.getAgent(agentId);
3801
+ return this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
3520
3802
  }
3521
3803
  let totalEarnings = 0;
3522
3804
  const results = {};
3523
3805
  const entries = [...this.getActiveAgents().entries()];
3524
3806
  const earningsResults = await Promise.all(
3525
3807
  entries.map(async ([id, agent]) => {
3526
- const e = await agent.getEarnings(state, chainId);
3808
+ const e = await this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
3527
3809
  return [id, e];
3528
3810
  })
3529
3811
  );
@@ -3543,7 +3825,11 @@ var OwneySDK = class {
3543
3825
  async refreshEarnings(agentId) {
3544
3826
  const state = this.requireState();
3545
3827
  const chainId = this.requireChainId();
3546
- const refreshAgent = (agent) => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId);
3828
+ const refreshAgent = (agent) => this.readAgent(
3829
+ agent,
3830
+ agent.refreshEarnings ? "refreshEarnings" : "earnings",
3831
+ () => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId)
3832
+ );
3547
3833
  if (agentId) return refreshAgent(this.getAgent(agentId));
3548
3834
  let totalEarnings = 0;
3549
3835
  const agentEarnings = {};
@@ -3574,11 +3860,12 @@ var OwneySDK = class {
3574
3860
  const state = this.requireState();
3575
3861
  const chainId = this.requireChainId();
3576
3862
  if (agentId) {
3577
- return this.getAgent(agentId).getAccountApy(
3578
- state,
3579
- chainId,
3580
- days,
3581
- tokenSymbol
3863
+ const agent = this.getAgent(agentId);
3864
+ return this.readAgent(
3865
+ agent,
3866
+ "accountApy",
3867
+ () => agent.getAccountApy(state, chainId, days, tokenSymbol),
3868
+ { days, tokenSymbol }
3582
3869
  );
3583
3870
  }
3584
3871
  const activeAgents = this.getActiveAgents();
@@ -3586,18 +3873,18 @@ var OwneySDK = class {
3586
3873
  const [apyResults, balanceResults] = await Promise.all([
3587
3874
  Promise.all(
3588
3875
  entries.map(async ([id, agent]) => {
3589
- const apy = await agent.getAccountApy(
3590
- state,
3591
- chainId,
3592
- days,
3593
- tokenSymbol
3876
+ const apy = await this.readAgent(
3877
+ agent,
3878
+ "accountApy",
3879
+ () => agent.getAccountApy(state, chainId, days, tokenSymbol),
3880
+ { days, tokenSymbol }
3594
3881
  );
3595
3882
  return [id, apy];
3596
3883
  })
3597
3884
  ),
3598
3885
  Promise.all(
3599
3886
  entries.map(async ([id, agent]) => {
3600
- const b = await agent.getBalances(state, chainId);
3887
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3601
3888
  return [id, Number(b.totalBalance)];
3602
3889
  })
3603
3890
  )
@@ -3654,7 +3941,8 @@ var OwneySDK = class {
3654
3941
  const chainId = this.requireChainId();
3655
3942
  const { agentId, filters } = options ?? {};
3656
3943
  if (agentId) {
3657
- return this.getAgent(agentId).getHistory(state, chainId, filters);
3944
+ const agent = this.getAgent(agentId);
3945
+ return this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
3658
3946
  }
3659
3947
  const activeAgents = [...this.getActiveAgents().values()];
3660
3948
  const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
@@ -3664,10 +3952,13 @@ var OwneySDK = class {
3664
3952
  if (filters?.cursor && agentCursor === void 0) {
3665
3953
  return { agentId: agent.id, page: null };
3666
3954
  }
3667
- const page = await agent.getHistory(state, chainId, {
3668
- ...filters,
3669
- cursor: agentCursor
3670
- });
3955
+ const agentFilters = { ...filters, cursor: agentCursor };
3956
+ const page = await this.readAgent(
3957
+ agent,
3958
+ "history",
3959
+ () => agent.getHistory(state, chainId, agentFilters),
3960
+ agentFilters
3961
+ );
3671
3962
  return { agentId: agent.id, page };
3672
3963
  })
3673
3964
  );
@@ -3707,13 +3998,14 @@ var OwneySDK = class {
3707
3998
  const state = this.requireState();
3708
3999
  const chainId = this.requireChainId();
3709
4000
  if (agentId) {
3710
- return this.getAgent(agentId).getUserProfile(state, chainId);
4001
+ const agent = this.getAgent(agentId);
4002
+ return this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
3711
4003
  }
3712
4004
  const results = {};
3713
4005
  const entries = [...this.getActiveAgents().entries()];
3714
4006
  const profileResults = await Promise.all(
3715
4007
  entries.map(async ([id, agent]) => {
3716
- const p = await agent.getUserProfile(state, chainId);
4008
+ const p = await this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
3717
4009
  return [id, p];
3718
4010
  })
3719
4011
  );
@@ -3736,7 +4028,20 @@ var OwneySDK = class {
3736
4028
  const chainId = this.requireChainId();
3737
4029
  const agent = this.getAgent(agentId);
3738
4030
  if (typeof agent.ensureAutoSelectProtocols !== "function") return false;
3739
- return agent.ensureAutoSelectProtocols(state, chainId, asset);
4031
+ const key2 = JSON.stringify([
4032
+ state.walletAddress.toLowerCase(),
4033
+ agentId,
4034
+ chainId,
4035
+ asset
4036
+ ]);
4037
+ const existing = this.activationChecks.get(key2);
4038
+ if (existing) return existing;
4039
+ const pending = Promise.resolve().then(() => agent.ensureAutoSelectProtocols(state, chainId, asset)).finally(() => {
4040
+ if (this.activationChecks.get(key2) === pending)
4041
+ this.activationChecks.delete(key2);
4042
+ });
4043
+ this.activationChecks.set(key2, pending);
4044
+ return pending;
3740
4045
  }
3741
4046
  /**
3742
4047
  * One-time, user-paid approval of Permit2 on the sponsored WETH token for
@@ -3807,13 +4112,14 @@ var OwneySDK = class {
3807
4112
  await this.ensureAgentsInitialized();
3808
4113
  const agentOptions = { tokenSymbol, chainId };
3809
4114
  if (agentId) {
3810
- return this.getAgent(agentId).getAgentApy(days, agentOptions);
4115
+ const agent = this.getAgent(agentId);
4116
+ return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
3811
4117
  }
3812
4118
  const results = {};
3813
4119
  const agentEntries = [...this.agents.entries()];
3814
4120
  const apyResults = await Promise.all(
3815
4121
  agentEntries.map(async ([id, agent]) => {
3816
- const apy = await agent.getAgentApy(days, agentOptions);
4122
+ const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
3817
4123
  return [id, apy];
3818
4124
  })
3819
4125
  );
@@ -3840,7 +4146,7 @@ var OwneySDK = class {
3840
4146
  const entries = [...activeAgents.entries()];
3841
4147
  const balanceResults = await Promise.allSettled(
3842
4148
  entries.map(async ([id, agent]) => {
3843
- const b = await agent.getBalances(state, chainId);
4149
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3844
4150
  return [id, b.positions ?? []];
3845
4151
  })
3846
4152
  );
@@ -3848,6 +4154,7 @@ var OwneySDK = class {
3848
4154
  const allPositions = [];
3849
4155
  let successCount = 0;
3850
4156
  const agentErrors = {};
4157
+ const agentFailures = [];
3851
4158
  for (let i = 0; i < balanceResults.length; i++) {
3852
4159
  const settled = balanceResults[i];
3853
4160
  const [aid] = entries[i];
@@ -3862,6 +4169,7 @@ var OwneySDK = class {
3862
4169
  successCount += 1;
3863
4170
  } else {
3864
4171
  const reason = settled.reason;
4172
+ agentFailures.push(reason);
3865
4173
  agentErrors[aid] = reason instanceof Error ? reason.message : String(reason);
3866
4174
  console.error(`getAllocationApy agent "${aid}" failed:`, reason);
3867
4175
  }
@@ -3870,7 +4178,7 @@ var OwneySDK = class {
3870
4178
  throw new OwneyError(
3871
4179
  "ALLOCATION_ALL_FAILED",
3872
4180
  "Failed to fetch allocation APY: all agents failed.",
3873
- { agentErrors }
4181
+ { agentErrors, failures: agentFailures }
3874
4182
  );
3875
4183
  }
3876
4184
  const overall = computeAllocationApy(allPositions);