@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.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
  }
@@ -3331,9 +3602,12 @@ var OwneySDK = class {
3331
3602
  { asset, agentErrors: agentErrors2 }
3332
3603
  );
3333
3604
  }
3605
+ const amountsKnown = Object.values(results2).every(
3606
+ (result) => typeof result.amount === "string" && /^\d+$/.test(result.amount)
3607
+ );
3334
3608
  return {
3335
3609
  agentResult: results2,
3336
- totalWithdrawn: sumWithdrawnAmount(results2).toString()
3610
+ totalWithdrawn: amountsKnown ? sumWithdrawnAmount(results2).toString() : null
3337
3611
  };
3338
3612
  }
3339
3613
  const requested = BigInt(amount);
@@ -3438,7 +3712,8 @@ var OwneySDK = class {
3438
3712
  const state = this.requireState();
3439
3713
  const chainId = this.requireChainId();
3440
3714
  if (agentId) {
3441
- const result = await this.getAgent(agentId).getBalances(state, chainId);
3715
+ const agent = this.getAgent(agentId);
3716
+ const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3442
3717
  return result;
3443
3718
  }
3444
3719
  let totalBalance = 0;
@@ -3446,12 +3721,14 @@ var OwneySDK = class {
3446
3721
  const entries = [...this.getActiveAgents().entries()];
3447
3722
  const balanceResults = await Promise.allSettled(
3448
3723
  entries.map(async ([id, agent]) => {
3449
- const b = await agent.getBalances(state, chainId);
3724
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3450
3725
  return [id, b];
3451
3726
  })
3452
3727
  );
3453
3728
  let successCount = 0;
3454
3729
  const agentErrors = {};
3730
+ const agentRetryAt = {};
3731
+ const agentFailures = [];
3455
3732
  for (let i = 0; i < balanceResults.length; i++) {
3456
3733
  const settledResult = balanceResults[i];
3457
3734
  const [agentId2] = entries[i];
@@ -3463,19 +3740,23 @@ var OwneySDK = class {
3463
3740
  continue;
3464
3741
  }
3465
3742
  const reason = settledResult.reason;
3743
+ agentFailures.push(reason);
3744
+ const retryDelay = rateLimitDelay(reason);
3745
+ if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
3466
3746
  agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
3467
3747
  }
3468
3748
  if (successCount === 0) {
3469
3749
  throw new OwneyError(
3470
3750
  "BALANCE_ALL_FAILED",
3471
3751
  "Failed to fetch balances for all active agents.",
3472
- { agentErrors }
3752
+ { agentErrors, failures: agentFailures }
3473
3753
  );
3474
3754
  }
3475
3755
  return {
3476
3756
  totalBalance: String(totalBalance),
3477
3757
  totalBalanceAsset: "usdc",
3478
- agentBalances: results
3758
+ agentBalances: results,
3759
+ ...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
3479
3760
  };
3480
3761
  }
3481
3762
  /**
@@ -3487,14 +3768,15 @@ var OwneySDK = class {
3487
3768
  const state = this.requireState();
3488
3769
  const chainId = this.requireChainId();
3489
3770
  if (agentId) {
3490
- return this.getAgent(agentId).getEarnings(state, chainId);
3771
+ const agent = this.getAgent(agentId);
3772
+ return this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
3491
3773
  }
3492
3774
  let totalEarnings = 0;
3493
3775
  const results = {};
3494
3776
  const entries = [...this.getActiveAgents().entries()];
3495
3777
  const earningsResults = await Promise.all(
3496
3778
  entries.map(async ([id, agent]) => {
3497
- const e = await agent.getEarnings(state, chainId);
3779
+ const e = await this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
3498
3780
  return [id, e];
3499
3781
  })
3500
3782
  );
@@ -3514,7 +3796,11 @@ var OwneySDK = class {
3514
3796
  async refreshEarnings(agentId) {
3515
3797
  const state = this.requireState();
3516
3798
  const chainId = this.requireChainId();
3517
- const refreshAgent = (agent) => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId);
3799
+ const refreshAgent = (agent) => this.readAgent(
3800
+ agent,
3801
+ agent.refreshEarnings ? "refreshEarnings" : "earnings",
3802
+ () => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId)
3803
+ );
3518
3804
  if (agentId) return refreshAgent(this.getAgent(agentId));
3519
3805
  let totalEarnings = 0;
3520
3806
  const agentEarnings = {};
@@ -3545,11 +3831,12 @@ var OwneySDK = class {
3545
3831
  const state = this.requireState();
3546
3832
  const chainId = this.requireChainId();
3547
3833
  if (agentId) {
3548
- return this.getAgent(agentId).getAccountApy(
3549
- state,
3550
- chainId,
3551
- days,
3552
- tokenSymbol
3834
+ const agent = this.getAgent(agentId);
3835
+ return this.readAgent(
3836
+ agent,
3837
+ "accountApy",
3838
+ () => agent.getAccountApy(state, chainId, days, tokenSymbol),
3839
+ { days, tokenSymbol }
3553
3840
  );
3554
3841
  }
3555
3842
  const activeAgents = this.getActiveAgents();
@@ -3557,18 +3844,18 @@ var OwneySDK = class {
3557
3844
  const [apyResults, balanceResults] = await Promise.all([
3558
3845
  Promise.all(
3559
3846
  entries.map(async ([id, agent]) => {
3560
- const apy = await agent.getAccountApy(
3561
- state,
3562
- chainId,
3563
- days,
3564
- tokenSymbol
3847
+ const apy = await this.readAgent(
3848
+ agent,
3849
+ "accountApy",
3850
+ () => agent.getAccountApy(state, chainId, days, tokenSymbol),
3851
+ { days, tokenSymbol }
3565
3852
  );
3566
3853
  return [id, apy];
3567
3854
  })
3568
3855
  ),
3569
3856
  Promise.all(
3570
3857
  entries.map(async ([id, agent]) => {
3571
- const b = await agent.getBalances(state, chainId);
3858
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3572
3859
  return [id, Number(b.totalBalance)];
3573
3860
  })
3574
3861
  )
@@ -3625,7 +3912,8 @@ var OwneySDK = class {
3625
3912
  const chainId = this.requireChainId();
3626
3913
  const { agentId, filters } = options ?? {};
3627
3914
  if (agentId) {
3628
- return this.getAgent(agentId).getHistory(state, chainId, filters);
3915
+ const agent = this.getAgent(agentId);
3916
+ return this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
3629
3917
  }
3630
3918
  const activeAgents = [...this.getActiveAgents().values()];
3631
3919
  const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
@@ -3635,10 +3923,13 @@ var OwneySDK = class {
3635
3923
  if (filters?.cursor && agentCursor === void 0) {
3636
3924
  return { agentId: agent.id, page: null };
3637
3925
  }
3638
- const page = await agent.getHistory(state, chainId, {
3639
- ...filters,
3640
- cursor: agentCursor
3641
- });
3926
+ const agentFilters = { ...filters, cursor: agentCursor };
3927
+ const page = await this.readAgent(
3928
+ agent,
3929
+ "history",
3930
+ () => agent.getHistory(state, chainId, agentFilters),
3931
+ agentFilters
3932
+ );
3642
3933
  return { agentId: agent.id, page };
3643
3934
  })
3644
3935
  );
@@ -3678,13 +3969,14 @@ var OwneySDK = class {
3678
3969
  const state = this.requireState();
3679
3970
  const chainId = this.requireChainId();
3680
3971
  if (agentId) {
3681
- return this.getAgent(agentId).getUserProfile(state, chainId);
3972
+ const agent = this.getAgent(agentId);
3973
+ return this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
3682
3974
  }
3683
3975
  const results = {};
3684
3976
  const entries = [...this.getActiveAgents().entries()];
3685
3977
  const profileResults = await Promise.all(
3686
3978
  entries.map(async ([id, agent]) => {
3687
- const p = await agent.getUserProfile(state, chainId);
3979
+ const p = await this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
3688
3980
  return [id, p];
3689
3981
  })
3690
3982
  );
@@ -3707,7 +3999,20 @@ var OwneySDK = class {
3707
3999
  const chainId = this.requireChainId();
3708
4000
  const agent = this.getAgent(agentId);
3709
4001
  if (typeof agent.ensureAutoSelectProtocols !== "function") return false;
3710
- return agent.ensureAutoSelectProtocols(state, chainId, asset);
4002
+ const key2 = JSON.stringify([
4003
+ state.walletAddress.toLowerCase(),
4004
+ agentId,
4005
+ chainId,
4006
+ asset
4007
+ ]);
4008
+ const existing = this.activationChecks.get(key2);
4009
+ if (existing) return existing;
4010
+ const pending = Promise.resolve().then(() => agent.ensureAutoSelectProtocols(state, chainId, asset)).finally(() => {
4011
+ if (this.activationChecks.get(key2) === pending)
4012
+ this.activationChecks.delete(key2);
4013
+ });
4014
+ this.activationChecks.set(key2, pending);
4015
+ return pending;
3711
4016
  }
3712
4017
  /**
3713
4018
  * One-time, user-paid approval of Permit2 on the sponsored WETH token for
@@ -3778,13 +4083,14 @@ var OwneySDK = class {
3778
4083
  await this.ensureAgentsInitialized();
3779
4084
  const agentOptions = { tokenSymbol, chainId };
3780
4085
  if (agentId) {
3781
- return this.getAgent(agentId).getAgentApy(days, agentOptions);
4086
+ const agent = this.getAgent(agentId);
4087
+ return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
3782
4088
  }
3783
4089
  const results = {};
3784
4090
  const agentEntries = [...this.agents.entries()];
3785
4091
  const apyResults = await Promise.all(
3786
4092
  agentEntries.map(async ([id, agent]) => {
3787
- const apy = await agent.getAgentApy(days, agentOptions);
4093
+ const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
3788
4094
  return [id, apy];
3789
4095
  })
3790
4096
  );
@@ -3811,7 +4117,7 @@ var OwneySDK = class {
3811
4117
  const entries = [...activeAgents.entries()];
3812
4118
  const balanceResults = await Promise.allSettled(
3813
4119
  entries.map(async ([id, agent]) => {
3814
- const b = await agent.getBalances(state, chainId);
4120
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3815
4121
  return [id, b.positions ?? []];
3816
4122
  })
3817
4123
  );
@@ -3819,6 +4125,7 @@ var OwneySDK = class {
3819
4125
  const allPositions = [];
3820
4126
  let successCount = 0;
3821
4127
  const agentErrors = {};
4128
+ const agentFailures = [];
3822
4129
  for (let i = 0; i < balanceResults.length; i++) {
3823
4130
  const settled = balanceResults[i];
3824
4131
  const [aid] = entries[i];
@@ -3833,6 +4140,7 @@ var OwneySDK = class {
3833
4140
  successCount += 1;
3834
4141
  } else {
3835
4142
  const reason = settled.reason;
4143
+ agentFailures.push(reason);
3836
4144
  agentErrors[aid] = reason instanceof Error ? reason.message : String(reason);
3837
4145
  console.error(`getAllocationApy agent "${aid}" failed:`, reason);
3838
4146
  }
@@ -3841,7 +4149,7 @@ var OwneySDK = class {
3841
4149
  throw new OwneyError(
3842
4150
  "ALLOCATION_ALL_FAILED",
3843
4151
  "Failed to fetch allocation APY: all agents failed.",
3844
- { agentErrors }
4152
+ { agentErrors, failures: agentFailures }
3845
4153
  );
3846
4154
  }
3847
4155
  const overall = computeAllocationApy(allPositions);