@owney/sdk 0.2.5 → 0.2.7

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
@@ -8,12 +8,76 @@ var P_SUPPORTED_CHAINS = {
8
8
  };
9
9
  var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
10
10
  var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
11
+ var SupportedAssets = [
12
+ {
13
+ address: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`,
14
+ symbol: "USDC",
15
+ chainId: 8453
16
+ },
17
+ {
18
+ address: `0xaf88d065e77c8cC2239327C5EDb3A432268e5831`,
19
+ symbol: "USDC",
20
+ chainId: 42161
21
+ },
22
+ {
23
+ address: `0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9`,
24
+ symbol: "USDT",
25
+ chainId: 42161
26
+ }
27
+ ];
28
+
29
+ // src/errors.ts
30
+ var OwneyError = class extends Error {
31
+ code;
32
+ details;
33
+ constructor(code, message, details) {
34
+ super(message);
35
+ this.name = "OwneyError";
36
+ this.code = code;
37
+ this.details = details;
38
+ }
39
+ };
40
+ var AgentNotFoundError = class extends OwneyError {
41
+ constructor(agentId, available) {
42
+ super(
43
+ "AGENT_NOT_FOUND",
44
+ `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
45
+ { agentId, available }
46
+ );
47
+ this.name = "AgentNotFoundError";
48
+ }
49
+ };
50
+ var NotConnectedError = class extends OwneyError {
51
+ constructor() {
52
+ super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
53
+ this.name = "NotConnectedError";
54
+ }
55
+ };
56
+ var AgentChainIncompatibleError = class extends OwneyError {
57
+ incompatibleAgents;
58
+ connectedChainId;
59
+ constructor(incompatibleAgents, connectedChainId) {
60
+ const details = incompatibleAgents.map(
61
+ ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
62
+ ).join("; ");
63
+ super(
64
+ "AGENT_CHAIN_INCOMPATIBLE",
65
+ `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
66
+ { incompatibleAgents, connectedChainId }
67
+ );
68
+ this.name = "AgentChainIncompatibleError";
69
+ this.incompatibleAgents = incompatibleAgents;
70
+ this.connectedChainId = connectedChainId;
71
+ }
72
+ };
11
73
 
12
74
  // src/lib/utils.ts
13
75
  var isValidChainId = (chainId) => {
14
76
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
15
- throw new Error(
16
- `Unsupported chain ID: ${chainId}. Supported chains: ${SUPPORTED_CHAIN_IDS.join(", ")}`
77
+ throw new OwneyError(
78
+ "CHAIN_UNSUPPORTED",
79
+ `Unsupported chain ID: ${chainId}. Supported chains: ${SUPPORTED_CHAIN_IDS.join(", ")}`,
80
+ { chainId, supportedChainIds: [...SUPPORTED_CHAIN_IDS] }
17
81
  );
18
82
  }
19
83
  return chainId;
@@ -28,6 +92,15 @@ function hexToDecimal(hex, decimals = 6) {
28
92
  const remainder = parsed % divisor;
29
93
  return Number(whole) + Number(remainder) / Number(divisor);
30
94
  }
95
+ function normalizeDecimalPlace(value, decimals = 6) {
96
+ const parsed = BigInt(value);
97
+ if (decimals <= 0)
98
+ return Number(parsed);
99
+ const divisor = BigInt(10 ** decimals);
100
+ const whole = parsed / divisor;
101
+ const remainder = parsed % divisor;
102
+ return Number(whole) + Number(remainder) / Number(divisor);
103
+ }
31
104
  var DayFilterMapping = {
32
105
  "30D": 30,
33
106
  "14D": 14,
@@ -57,7 +130,7 @@ var CHAIN_NAME_TO_ID = {
57
130
  BASE: 8453,
58
131
  ARBITRUM: 42161
59
132
  };
60
- function mapBalances(raw, chainId) {
133
+ function mapBalances(raw, chainId, smartWallet) {
61
134
  const portfolio = raw.portfolio;
62
135
  const tokenMap = /* @__PURE__ */ new Map();
63
136
  for (const p of portfolio.positions || []) {
@@ -68,18 +141,7 @@ function mapBalances(raw, chainId) {
68
141
  tokenMap.set(key, {
69
142
  symbol,
70
143
  chainId: positionChainId,
71
- amount: (current?.amount ?? 0) + hexToDecimal(p.amount || "0")
72
- });
73
- }
74
- for (const s of portfolio.staleBalances || []) {
75
- const symbol = s.tokenSymbol || "USDC";
76
- const staleChainId = s.chainId ?? chainId;
77
- const key = `${symbol}:${staleChainId}`;
78
- const current = tokenMap.get(key);
79
- tokenMap.set(key, {
80
- symbol,
81
- chainId: staleChainId,
82
- amount: (current?.amount ?? 0) + hexToDecimal(s.balance || "0")
144
+ amount: (current?.amount ?? 0) + normalizeDecimalPlace(p.underlyingAmount || "0")
83
145
  });
84
146
  }
85
147
  let totalBalance = 0;
@@ -94,7 +156,7 @@ function mapBalances(raw, chainId) {
94
156
  });
95
157
  }
96
158
  return {
97
- smartWallet: portfolio.smartWallet,
159
+ smartWallet,
98
160
  totalBalance: String(totalBalance),
99
161
  tokens
100
162
  };
@@ -185,13 +247,41 @@ function mapUserProfile(raw, userAddress) {
185
247
  };
186
248
  }
187
249
  function mapApyByStrategy(raw) {
188
- const first = raw.data[0];
250
+ const apyPerAsset = {};
251
+ let apySum = 0;
252
+ let apyCount = 0;
253
+ for (const entry of raw.data) {
254
+ const supported = SupportedAssets.find(
255
+ (asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
256
+ );
257
+ if (!supported)
258
+ continue;
259
+ const chainKey = supported.chainId;
260
+ const symbolKey = supported.symbol;
261
+ const bucket = apyPerAsset[chainKey] ?? {};
262
+ bucket[symbolKey] = entry.average_apy;
263
+ apyPerAsset[chainKey] = bucket;
264
+ apySum += entry.average_apy;
265
+ apyCount += 1;
266
+ }
267
+ const averageApy = apyCount > 0 ? apySum / apyCount : 0;
268
+ const hasDetails = Object.keys(apyPerAsset).length > 0;
189
269
  return {
190
- averageApy: first?.average_apy ?? 0
270
+ averageApy,
271
+ detailedApys: hasDetails ? { apyPerAsset } : void 0
191
272
  };
192
273
  }
193
274
 
194
275
  // src/agents/zyfai/zyfai.agent.ts
276
+ var ZYFAI_TIMING_PREFIX = "[AgentTiming][zyfai]";
277
+ function logTiming(label, startedAt, meta) {
278
+ const elapsedMs = Date.now() - startedAt;
279
+ if (meta) {
280
+ console.log(`${ZYFAI_TIMING_PREFIX} ${label} ${elapsedMs}ms`, meta);
281
+ return;
282
+ }
283
+ console.log(`${ZYFAI_TIMING_PREFIX} ${label} ${elapsedMs}ms`);
284
+ }
195
285
  var ZyfaiAgent = class {
196
286
  id = "zyfai";
197
287
  supportedChainIds = [8453, 42161];
@@ -210,62 +300,122 @@ var ZyfaiAgent = class {
210
300
  sdk;
211
301
  connectedAddress = null;
212
302
  connectedChainId = null;
303
+ smartWalletCache = /* @__PURE__ */ new Map();
304
+ connectAuthPromise = null;
213
305
  constructor(apiKey) {
214
306
  this.sdk = new ZyfaiSDK({ apiKey });
215
307
  }
216
308
  // --- Private helpers ---
217
309
  getAddress() {
218
310
  if (!this.connectedAddress) {
219
- throw new Error("Wallet not connected. Call sdk.connect() first.");
311
+ throw new OwneyError(
312
+ "NOT_CONNECTED",
313
+ "Wallet not connected. Call sdk.connect() first."
314
+ );
220
315
  }
221
316
  return this.connectedAddress;
222
317
  }
223
318
  /** Light connect: SIWE auth only. Re-auths if chain changed. */
224
319
  async connectAuth(state, chainId) {
320
+ const startedAt = Date.now();
225
321
  const validChainId = isValidChainId(chainId);
226
- if (!this.connectedAddress || this.connectedChainId !== validChainId) {
322
+ if (this.connectedAddress && this.connectedChainId === validChainId) {
323
+ logTiming("connectAuth cache-hit", startedAt, { chainId: validChainId });
324
+ return;
325
+ }
326
+ if (this.connectAuthPromise) {
327
+ await this.connectAuthPromise;
328
+ logTiming("connectAuth awaited-inflight", startedAt, {
329
+ chainId: validChainId
330
+ });
331
+ return;
332
+ }
333
+ this.connectAuthPromise = (async () => {
227
334
  const address = await this.sdk.connectAccount(
228
335
  state.provider,
229
336
  validChainId
230
337
  );
231
338
  this.connectedAddress = address;
232
339
  this.connectedChainId = validChainId;
340
+ })();
341
+ try {
342
+ await this.connectAuthPromise;
343
+ logTiming("connectAuth success", startedAt, { chainId: validChainId });
344
+ } finally {
345
+ this.connectAuthPromise = null;
233
346
  }
234
347
  }
235
348
  /** Full connect: SIWE auth + Safe deploy check + session key. */
236
349
  async connectFull(state, chainId) {
350
+ const startedAt = Date.now();
237
351
  await this.connectAuth(state, chainId);
238
352
  const validChainId = isValidChainId(chainId);
239
353
  const address = this.getAddress();
354
+ const walletLookupStart = Date.now();
240
355
  const wallet = await this.sdk.getSmartWalletAddress(address, validChainId);
356
+ logTiming("connectFull getSmartWalletAddress", walletLookupStart, {
357
+ chainId: validChainId
358
+ });
359
+ this.smartWalletCache.set(`${address}_${validChainId}`, wallet.address);
241
360
  if (!wallet.isDeployed) {
361
+ const deployStart = Date.now();
242
362
  await this.sdk.deploySafe(address, validChainId, "aggressive", true);
363
+ logTiming("connectFull deploySafe", deployStart, {
364
+ chainId: validChainId
365
+ });
243
366
  }
367
+ const userDetailsStart = Date.now();
244
368
  const user = await this.sdk.getUserDetails();
369
+ logTiming("connectFull getUserDetails", userDetailsStart, {
370
+ chainId: validChainId
371
+ });
245
372
  if (!user.hasActiveSessionKey) {
373
+ const sessionKeyStart = Date.now();
246
374
  await this.sdk.createSessionKey(address, validChainId);
375
+ logTiming("connectFull createSessionKey", sessionKeyStart, {
376
+ chainId: validChainId
377
+ });
247
378
  }
379
+ logTiming("connectFull success", startedAt, { chainId: validChainId });
248
380
  }
249
381
  /** Connect + resolve smart wallet address for read operations. */
250
382
  async resolveSmartWallet(state, chainId) {
383
+ const startedAt = Date.now();
251
384
  await this.connectAuth(state, chainId);
252
385
  const validChainId = isValidChainId(chainId);
253
- const { address } = await this.sdk.getSmartWalletAddress(
254
- this.getAddress(),
255
- validChainId
256
- );
257
- return { smartWallet: address, chainId: validChainId };
386
+ const ownerAddress = this.getAddress();
387
+ const cacheKey = `${ownerAddress}_${validChainId}`;
388
+ let smartWallet = this.smartWalletCache.get(cacheKey);
389
+ if (!smartWallet) {
390
+ const lookupStart = Date.now();
391
+ const { address } = await this.sdk.getSmartWalletAddress(
392
+ ownerAddress,
393
+ validChainId
394
+ );
395
+ logTiming("resolveSmartWallet remote-lookup", lookupStart, {
396
+ chainId: validChainId
397
+ });
398
+ smartWallet = address;
399
+ this.smartWalletCache.set(cacheKey, smartWallet);
400
+ } else {
401
+ logTiming("resolveSmartWallet cache-hit", startedAt, {
402
+ chainId: validChainId
403
+ });
404
+ }
405
+ logTiming("resolveSmartWallet ready", startedAt, { chainId: validChainId });
406
+ return { smartWallet, chainId: validChainId };
258
407
  }
259
408
  // --- IAgent: Connection lifecycle ---
260
409
  async disconnect() {
261
410
  await this.sdk.disconnectAccount();
262
411
  this.connectedAddress = null;
263
412
  this.connectedChainId = null;
413
+ this.smartWalletCache.clear();
264
414
  }
265
415
  // --- IAgent: Account lifecycle ---
266
416
  async activateAgent(state, chainId) {
267
417
  try {
268
- await this.connectFull(state, chainId);
418
+ await this.connectAuth(state, chainId);
269
419
  } catch (error) {
270
420
  await this.disconnect();
271
421
  throw error;
@@ -312,10 +462,15 @@ var ZyfaiAgent = class {
312
462
  }
313
463
  // --- IAgent: Portfolio reads ---
314
464
  async getBalances(state, chainId) {
315
- await this.connectAuth(state, chainId);
316
- const validChainId = isValidChainId(chainId);
317
- const raw = await this.sdk.getPositions(this.getAddress(), validChainId);
318
- return mapBalances(raw, validChainId);
465
+ const startedAt = Date.now();
466
+ const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
467
+ const portfolioStart = Date.now();
468
+ const raw = await this.sdk.getPortfolio(this.getAddress());
469
+ logTiming("getBalances getPortfolio", portfolioStart, {
470
+ chainId: validChainId
471
+ });
472
+ logTiming("getBalances success", startedAt, { chainId: validChainId });
473
+ return mapBalances(raw, validChainId, smartWallet);
319
474
  }
320
475
  async getEarnings(state, chainId) {
321
476
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -349,11 +504,13 @@ var ZyfaiAgent = class {
349
504
  return mapUserProfile(raw, this.connectedAddress);
350
505
  }
351
506
  // --- IAgent: Discovery (no wallet required) ---
352
- async getAgentApy(days) {
507
+ async getAgentApy(days, options) {
353
508
  const raw = await this.sdk.getAPYPerStrategy(
354
509
  false,
355
510
  DayFilterMapping[days],
356
- "aggressive"
511
+ "aggressive",
512
+ options?.chainId,
513
+ options?.tokenSymbol
357
514
  );
358
515
  return mapApyByStrategy(raw);
359
516
  }
@@ -399,9 +556,11 @@ function mapEarnings2(raw, smartWallet) {
399
556
  };
400
557
  }
401
558
  function mapApyHistory2(raw, walletAddress) {
559
+ const apyPerAsset = remapApyPerAssetToSymbols(raw.result.apy_per_asset);
402
560
  return {
403
561
  walletAddress,
404
- weightedApyAfterFee: raw.result
562
+ weightedApyAfterFee: raw.result.apy,
563
+ weightedApyAfterFeeDetails: apyPerAsset ? { apyPerAsset } : void 0
405
564
  };
406
565
  }
407
566
  function mapSailTransaction(tx) {
@@ -535,15 +694,45 @@ function mapUserProfile2(raw) {
535
694
  };
536
695
  }
537
696
  function mapApyByStrategy2(raw) {
697
+ const apyPerAsset = remapApyPerAssetToSymbols(raw.result.apy_per_asset);
538
698
  return {
539
- averageApy: raw.result
699
+ averageApy: raw.result.apy,
700
+ detailedApys: apyPerAsset ? { apyPerAsset } : void 0
540
701
  };
541
702
  }
703
+ function remapApyPerAssetToSymbols(raw) {
704
+ if (!raw)
705
+ return void 0;
706
+ const remapped = {};
707
+ for (const asset of SupportedAssets) {
708
+ const chainKey = asset.chainId;
709
+ const apyByAddress = raw[String(chainKey)];
710
+ if (!apyByAddress)
711
+ continue;
712
+ const apy = apyByAddress[asset.address] ?? apyByAddress[asset.address.toLowerCase()];
713
+ if (apy === void 0)
714
+ continue;
715
+ const bucket = remapped[chainKey] ?? {};
716
+ bucket[asset.symbol] = apy;
717
+ remapped[chainKey] = bucket;
718
+ }
719
+ return Object.keys(remapped).length > 0 ? remapped : void 0;
720
+ }
542
721
 
543
722
  // src/agents/sail/sail.agent.ts
544
723
  var SAIL_API_BASE = "https://app.sail.money";
545
724
  var PROJECT_ID = "sail";
546
725
  var PAGE_ID = "home";
726
+ var SAIL_TIMING_PREFIX = "[AgentTiming][sail]";
727
+ var SAIL_PORTFOLIO_TIMEOUT_MS = 8e3;
728
+ function logTiming2(label, startedAt, meta) {
729
+ const elapsedMs = Date.now() - startedAt;
730
+ if (meta) {
731
+ console.log(`${SAIL_TIMING_PREFIX} ${label} ${elapsedMs}ms`, meta);
732
+ return;
733
+ }
734
+ console.log(`${SAIL_TIMING_PREFIX} ${label} ${elapsedMs}ms`);
735
+ }
547
736
  var SailAgent = class {
548
737
  id = "sail";
549
738
  supportedChainIds = [8453, 42161];
@@ -565,11 +754,12 @@ var SailAgent = class {
565
754
  connectedAddress = null;
566
755
  smartWalletAddress = null;
567
756
  connectedChainId = null;
757
+ connectPromise = null;
568
758
  constructor(apiKey) {
569
759
  this.apiKey = apiKey;
570
760
  }
571
761
  // --- Private helpers ---
572
- async request(method, path, body) {
762
+ async request(method, path, body, timeoutMs) {
573
763
  const headers = {
574
764
  "Content-Type": "application/json",
575
765
  "X-Business-API-Key": this.apiKey
@@ -580,32 +770,57 @@ var SailAgent = class {
580
770
  if (this.accountToken) {
581
771
  headers["X-Account-Token"] = this.accountToken;
582
772
  }
583
- const res = await fetch(`${SAIL_API_BASE}${path}`, {
584
- method,
585
- headers,
586
- body: body ? JSON.stringify(body) : void 0
587
- });
773
+ const controller = new AbortController();
774
+ const timeoutId = typeof timeoutMs === "number" && timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
775
+ let res;
776
+ try {
777
+ res = await fetch(`${SAIL_API_BASE}${path}`, {
778
+ method,
779
+ headers,
780
+ body: body ? JSON.stringify(body) : void 0,
781
+ signal: controller.signal
782
+ });
783
+ } catch (error) {
784
+ if (error instanceof Error && error.name === "AbortError" && timeoutMs) {
785
+ throw new OwneyError(
786
+ "API_SAIL_TIMEOUT",
787
+ `Sail API timeout ${timeoutMs}ms: ${method} ${path}`,
788
+ { timeoutMs, method, path }
789
+ );
790
+ }
791
+ throw error;
792
+ } finally {
793
+ if (timeoutId)
794
+ clearTimeout(timeoutId);
795
+ }
588
796
  if (!res.ok) {
589
797
  const text = await res.text().catch(() => "");
590
- throw new Error(`Sail API error ${res.status}: ${text}`);
798
+ throw new OwneyError(
799
+ "API_SAIL_ERROR",
800
+ `Sail API error ${res.status}: ${text}`,
801
+ { statusCode: res.status, method, path, responseBody: text }
802
+ );
591
803
  }
592
804
  return res.json();
593
805
  }
594
806
  /** Initiate SIWE and sign with the user's wallet. */
595
807
  async signSiwe(state, chainId) {
808
+ const startedAt = Date.now();
596
809
  const initiate = await this.request(
597
810
  "POST",
598
811
  `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/auth/initiate`,
599
812
  {
600
813
  method: "siwe",
601
814
  address: state.walletAddress,
602
- chainId
815
+ chainId,
816
+ identityMode: "byWallet"
603
817
  }
604
818
  );
605
819
  const signature = await state.provider.request({
606
820
  method: "personal_sign",
607
821
  params: [initiate.message, state.walletAddress]
608
822
  });
823
+ logTiming2("signSiwe success", startedAt, { chainId });
609
824
  return {
610
825
  sessionId: initiate.sessionId,
611
826
  message: initiate.message,
@@ -614,38 +829,62 @@ var SailAgent = class {
614
829
  }
615
830
  /** Full connect: auth handles smart account creation via SIWE flow. */
616
831
  async connectFull(state, chainId) {
832
+ const startedAt = Date.now();
617
833
  if (this.connectedAddress && this.connectedChainId === chainId) {
834
+ logTiming2("connectFull cache-hit", startedAt, { chainId });
835
+ return;
836
+ }
837
+ if (this.connectPromise) {
838
+ await this.connectPromise;
839
+ logTiming2("connectFull awaited-inflight", startedAt, { chainId });
618
840
  return;
619
841
  }
620
842
  if (!state.walletAddress) {
621
- throw new Error("Wallet address is required");
843
+ throw new OwneyError(
844
+ "WALLET_ADDRESS_REQUIRED",
845
+ "Wallet address is required. Call sdk.connect(provider) first."
846
+ );
847
+ }
848
+ this.connectPromise = (async () => {
849
+ const { sessionId, signature, message } = await this.signSiwe(
850
+ state,
851
+ chainId
852
+ );
853
+ const complete = await this.request(
854
+ "POST",
855
+ `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/auth/complete`,
856
+ {
857
+ method: "custom",
858
+ sessionId,
859
+ signature,
860
+ walletAddress: state.walletAddress,
861
+ identityMode: "byWallet"
862
+ }
863
+ );
864
+ this.authToken = complete.token;
865
+ this.accountToken = complete.accountToken;
866
+ this.connectedAddress = state.walletAddress;
867
+ this.smartWalletAddress = complete.walletAddress;
868
+ this.connectedChainId = chainId;
869
+ })();
870
+ try {
871
+ await this.connectPromise;
872
+ logTiming2("connectFull success", startedAt, { chainId });
873
+ } finally {
874
+ this.connectPromise = null;
622
875
  }
623
- const { sessionId, signature, message } = await this.signSiwe(
624
- state,
625
- chainId
626
- );
627
- const complete = await this.request(
628
- "POST",
629
- `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/auth/complete`,
630
- {
631
- method: "custom",
632
- sessionId,
633
- signature,
634
- walletAddress: state.walletAddress
635
- }
636
- );
637
- this.authToken = complete.token;
638
- this.accountToken = complete.accountToken;
639
- this.connectedAddress = state.walletAddress;
640
- this.smartWalletAddress = complete.walletAddress;
641
- this.connectedChainId = chainId;
642
876
  }
643
877
  async resolveSmartWallet(state, chainId) {
878
+ const startedAt = Date.now();
644
879
  await this.connectFull(state, chainId);
645
880
  const validChainId = isValidChainId(chainId);
646
881
  if (!this.smartWalletAddress) {
647
- throw new Error("Wallet not connected. Call sdk.connect() first.");
882
+ throw new OwneyError(
883
+ "NOT_CONNECTED",
884
+ "Wallet not connected. Call sdk.connect() first."
885
+ );
648
886
  }
887
+ logTiming2("resolveSmartWallet ready", startedAt, { chainId: validChainId });
649
888
  return {
650
889
  smartWallet: this.smartWalletAddress,
651
890
  chainId: validChainId
@@ -670,7 +909,8 @@ var SailAgent = class {
670
909
  }
671
910
  async deposit(state, chainId, amount, depositCallback) {
672
911
  if (!depositCallback) {
673
- throw new Error(
912
+ throw new OwneyError(
913
+ "DEPOSIT_CALLBACK_REQUIRED",
674
914
  "depositCallback is required for Sail. Provide a callback that transfers tokens and returns the transaction hash."
675
915
  );
676
916
  }
@@ -679,12 +919,16 @@ var SailAgent = class {
679
919
  const depositInfo = await this.getDepositInfo();
680
920
  const resolvedTokenAddress = depositInfo.permittedTokens[0]?.address;
681
921
  if (!resolvedTokenAddress) {
682
- throw new Error("No permitted tokens available for deposit.");
922
+ throw new OwneyError(
923
+ "DEPOSIT_NO_PERMITTED_TOKENS",
924
+ "No permitted tokens available for deposit."
925
+ );
683
926
  }
684
927
  await this.executePreHooks(resolvedTokenAddress, amount, chainId);
685
928
  const txHash = await depositCallback(smartWallet, chainId, amount);
686
929
  if (!txHash) {
687
- throw new Error(
930
+ throw new OwneyError(
931
+ "DEPOSIT_CALLBACK_INVALID",
688
932
  "depositCallback must return a transaction hash (txHash) for Sail deposits."
689
933
  );
690
934
  }
@@ -738,7 +982,10 @@ var SailAgent = class {
738
982
  const withdrawInfo = await this.getWithdrawInfo();
739
983
  const tokenAddress = withdrawInfo.permittedTokens[0]?.address;
740
984
  if (!tokenAddress) {
741
- throw new Error("No permitted tokens available for withdrawal.");
985
+ throw new OwneyError(
986
+ "WITHDRAW_NO_PERMITTED_TOKENS",
987
+ "No permitted tokens available for withdrawal."
988
+ );
742
989
  }
743
990
  const resolvedAmount = amount ?? withdrawInfo.currentBalance;
744
991
  const { signature: adminWalletSignature, message: adminWalletPayload } = await this.signSiwe(state, chainId);
@@ -762,9 +1009,30 @@ var SailAgent = class {
762
1009
  }
763
1010
  // --- IAgent: Portfolio reads ---
764
1011
  async getBalances(state, chainId) {
1012
+ const startedAt = Date.now();
765
1013
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
766
- const raw = await this.getMetricsPortfolio(state, chainId);
767
- return mapPortfolioBalances(raw, smartWallet);
1014
+ const portfolioStart = Date.now();
1015
+ try {
1016
+ const raw = await this.getMetricsPortfolio(state, chainId);
1017
+ logTiming2("getBalances getMetricsPortfolio", portfolioStart, { chainId });
1018
+ logTiming2("getBalances success", startedAt, { chainId });
1019
+ return mapPortfolioBalances(raw, smartWallet);
1020
+ } catch (error) {
1021
+ if (error instanceof OwneyError && error.code === "API_SAIL_TIMEOUT") {
1022
+ console.warn(
1023
+ "Sail portfolio request timed out; using empty snapshot.",
1024
+ {
1025
+ chainId
1026
+ }
1027
+ );
1028
+ logTiming2("getBalances timeout-fallback", startedAt, { chainId });
1029
+ return mapPortfolioBalances(
1030
+ { message: "timeout", result: [] },
1031
+ smartWallet
1032
+ );
1033
+ }
1034
+ throw error;
1035
+ }
768
1036
  }
769
1037
  async getEarnings(state, chainId) {
770
1038
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -785,7 +1053,9 @@ var SailAgent = class {
785
1053
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
786
1054
  return this.request(
787
1055
  "GET",
788
- `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/portfolio?${smartWallet ? `smart_account=${smartWallet}` : ""}`
1056
+ `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/portfolio?${smartWallet ? `smart_account=${smartWallet}` : ""}`,
1057
+ void 0,
1058
+ SAIL_PORTFOLIO_TIMEOUT_MS
789
1059
  );
790
1060
  }
791
1061
  async getMetricsEarnings(state, chainId) {
@@ -832,8 +1102,10 @@ var SailAgent = class {
832
1102
  }
833
1103
  const dayCount = DayFilterMapping[days];
834
1104
  if (dayCount === void 0) {
835
- throw new Error(
836
- `Invalid days value: "${days}". Expected one of: "7D", "14D", "30D".`
1105
+ throw new OwneyError(
1106
+ "VALIDATION_INVALID_DAYS",
1107
+ `Invalid days value: "${days}". Expected one of: "7D", "14D", "30D".`,
1108
+ { days }
837
1109
  );
838
1110
  }
839
1111
  const now = /* @__PURE__ */ new Date();
@@ -904,43 +1176,12 @@ var SailAgent = class {
904
1176
  return mapUserProfile2(raw);
905
1177
  }
906
1178
  // --- IAgent: Discovery (no wallet required) ---
907
- async getAgentApy(days) {
1179
+ async getAgentApy(days, _options) {
908
1180
  const raw = await this.getMetricsYield(days);
909
1181
  return mapApyByStrategy2(raw);
910
1182
  }
911
1183
  };
912
1184
 
913
- // src/errors.ts
914
- var AgentNotFoundError = class extends Error {
915
- constructor(agentId, available) {
916
- super(
917
- `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`
918
- );
919
- this.name = "AgentNotFoundError";
920
- }
921
- };
922
- var NotConnectedError = class extends Error {
923
- constructor() {
924
- super("Not connected. Call sdk.connect(provider) first.");
925
- this.name = "NotConnectedError";
926
- }
927
- };
928
- var AgentChainIncompatibleError = class extends Error {
929
- incompatibleAgents;
930
- connectedChainId;
931
- constructor(incompatibleAgents, connectedChainId) {
932
- const details = incompatibleAgents.map(
933
- ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
934
- ).join("; ");
935
- super(
936
- `Chain ${connectedChainId} is not supported by the following agents: ${details}`
937
- );
938
- this.name = "AgentChainIncompatibleError";
939
- this.incompatibleAgents = incompatibleAgents;
940
- this.connectedChainId = connectedChainId;
941
- }
942
- };
943
-
944
1185
  // src/lib/routing-api.ts
945
1186
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
946
1187
  async function fetchAgentKeys(apiKey) {
@@ -954,11 +1195,19 @@ async function fetchAgentKeys(apiKey) {
954
1195
  });
955
1196
  if (!res.ok) {
956
1197
  const text = await res.text().catch(() => "");
957
- throw new Error(`Routing API error ${res.status}: ${text}`);
1198
+ throw new OwneyError(
1199
+ "API_ROUTING_ERROR",
1200
+ `Routing API error ${res.status}: ${text}`,
1201
+ { statusCode: res.status, responseBody: text }
1202
+ );
958
1203
  }
959
1204
  const json = await res.json();
960
1205
  if (!json.success) {
961
- throw new Error(`Routing API request failed: ${json.message}`);
1206
+ throw new OwneyError(
1207
+ "API_ROUTING_FAILED",
1208
+ `Routing API request failed: ${json.message}`,
1209
+ { message: json.message }
1210
+ );
962
1211
  }
963
1212
  return json.data;
964
1213
  }
@@ -968,11 +1217,21 @@ var AGENT_CONSTRUCTORS = {
968
1217
  zyfai: ZyfaiAgent,
969
1218
  sail: SailAgent
970
1219
  };
1220
+ var SDK_TIMING_PREFIX = "[OwneySDKTiming]";
1221
+ function logTiming3(label, startedAt, meta) {
1222
+ const elapsedMs = Date.now() - startedAt;
1223
+ if (meta) {
1224
+ console.log(`${SDK_TIMING_PREFIX} ${label} ${elapsedMs}ms`, meta);
1225
+ return;
1226
+ }
1227
+ console.log(`${SDK_TIMING_PREFIX} ${label} ${elapsedMs}ms`);
1228
+ }
971
1229
  var OwneySDK = class {
972
1230
  agents = /* @__PURE__ */ new Map();
973
1231
  activeAgents = /* @__PURE__ */ new Set();
974
1232
  state = null;
975
1233
  apiKey;
1234
+ initializingAgentsPromise = null;
976
1235
  constructor(config) {
977
1236
  this.apiKey = config.apiKey;
978
1237
  }
@@ -988,7 +1247,8 @@ var OwneySDK = class {
988
1247
  });
989
1248
  const walletAddress = accounts[0];
990
1249
  if (!walletAddress) {
991
- throw new Error(
1250
+ throw new OwneyError(
1251
+ "WALLET_NO_ACCOUNTS",
992
1252
  "No accounts found. Ensure the wallet is unlocked and connected."
993
1253
  );
994
1254
  }
@@ -1027,7 +1287,8 @@ var OwneySDK = class {
1027
1287
  requireChainId() {
1028
1288
  const chainId = this.state?.chainId ?? null;
1029
1289
  if (chainId === null) {
1030
- throw new Error(
1290
+ throw new OwneyError(
1291
+ "NO_ACTIVE_CHAIN",
1031
1292
  "No active chain. Call sdk.activateAgent(chainId) first."
1032
1293
  );
1033
1294
  }
@@ -1049,6 +1310,33 @@ var OwneySDK = class {
1049
1310
  }
1050
1311
  return active;
1051
1312
  }
1313
+ async ensureAgentsInitialized() {
1314
+ if (this.agents.size > 0)
1315
+ return;
1316
+ if (!this.initializingAgentsPromise) {
1317
+ this.initializingAgentsPromise = (async () => {
1318
+ const agentKeys = await fetchAgentKeys(this.apiKey);
1319
+ for (const { key, agent_type } of agentKeys) {
1320
+ const AgentClass = AGENT_CONSTRUCTORS[agent_type];
1321
+ if (!AgentClass)
1322
+ continue;
1323
+ const agent = new AgentClass(key);
1324
+ this.agents.set(agent_type, agent);
1325
+ }
1326
+ if (this.agents.size === 0) {
1327
+ throw new OwneyError(
1328
+ "API_NO_AGENTS",
1329
+ "No supported agents returned by the routing API. Check your API key and routing configuration."
1330
+ );
1331
+ }
1332
+ })();
1333
+ }
1334
+ try {
1335
+ await this.initializingAgentsPromise;
1336
+ } finally {
1337
+ this.initializingAgentsPromise = null;
1338
+ }
1339
+ }
1052
1340
  // --- Account lifecycle ---
1053
1341
  /**
1054
1342
  * Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
@@ -1061,25 +1349,13 @@ var OwneySDK = class {
1061
1349
  * throws before activating any agent.
1062
1350
  */
1063
1351
  async activateAgent(chainId, agentId) {
1352
+ const startedAt = Date.now();
1064
1353
  const state = this.requireState();
1065
- if (this.agents.size === 0) {
1066
- const agentKeys = await fetchAgentKeys(this.apiKey);
1067
- for (const { key, agent_type } of agentKeys) {
1068
- const AgentClass = AGENT_CONSTRUCTORS[agent_type];
1069
- if (!AgentClass)
1070
- continue;
1071
- const agent = new AgentClass(key);
1072
- this.agents.set(agent_type, agent);
1073
- }
1074
- if (this.agents.size === 0) {
1075
- throw new Error(
1076
- "No supported agents returned by the routing API. Check your API key and routing configuration."
1077
- );
1078
- }
1079
- }
1354
+ await this.ensureAgentsInitialized();
1080
1355
  if (agentId !== void 0) {
1081
1356
  if (agentId.length === 0) {
1082
- throw new Error(
1357
+ throw new OwneyError(
1358
+ "AGENT_EMPTY_LIST",
1083
1359
  "agentId array must not be empty. Omit the argument to activate all compatible agents."
1084
1360
  );
1085
1361
  }
@@ -1094,30 +1370,60 @@ var OwneySDK = class {
1094
1370
  if (incompatibleAgents.length > 0) {
1095
1371
  throw new AgentChainIncompatibleError(incompatibleAgents, chainId);
1096
1372
  }
1097
- for (const agent of agents) {
1098
- await agent.activateAgent(state, chainId);
1099
- }
1100
1373
  this.activeAgents.clear();
1101
1374
  for (const id of uniqueIds) {
1102
1375
  this.activeAgents.add(id);
1103
1376
  }
1104
1377
  state.chainId = chainId;
1378
+ Promise.all(
1379
+ agents.map(async (agent) => {
1380
+ const agentStart = Date.now();
1381
+ await agent.activateAgent(state, chainId);
1382
+ logTiming3("activateAgent background-agent-ready", agentStart, {
1383
+ chainId,
1384
+ agentId: agent.id
1385
+ });
1386
+ })
1387
+ ).catch((error) => {
1388
+ console.error("activateAgent background init failed:", error);
1389
+ });
1390
+ logTiming3("activateAgent scheduled", startedAt, {
1391
+ chainId,
1392
+ agentIds: uniqueIds
1393
+ });
1105
1394
  return;
1106
1395
  }
1107
1396
  const compatible = [...this.agents.values()].filter(
1108
1397
  (agent) => agent.supportedChainIds.includes(chainId)
1109
1398
  );
1110
1399
  if (compatible.length === 0) {
1111
- throw new Error(`No agents support chain ${chainId}.`);
1112
- }
1113
- for (const agent of compatible) {
1114
- await agent.activateAgent(state, chainId);
1400
+ throw new OwneyError(
1401
+ "CHAIN_NO_COMPATIBLE_AGENTS",
1402
+ `No agents support chain ${chainId}.`,
1403
+ { chainId }
1404
+ );
1115
1405
  }
1116
1406
  this.activeAgents.clear();
1117
1407
  for (const agent of compatible) {
1118
1408
  this.activeAgents.add(agent.id);
1119
1409
  }
1120
1410
  state.chainId = chainId;
1411
+ Promise.all(
1412
+ compatible.map(async (agent) => {
1413
+ const agentStart = Date.now();
1414
+ await agent.activateAgent(state, chainId);
1415
+ logTiming3("activateAgent background-agent-ready", agentStart, {
1416
+ chainId,
1417
+ agentId: agent.id
1418
+ });
1419
+ })
1420
+ ).catch((error) => {
1421
+ console.error("activateAgent background init failed:", error);
1422
+ });
1423
+ logTiming3("activateAgent scheduled", startedAt, {
1424
+ chainId,
1425
+ agentIds: compatible.map((agent) => agent.id)
1426
+ });
1121
1427
  }
1122
1428
  /**
1123
1429
  * Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
@@ -1143,8 +1449,10 @@ var OwneySDK = class {
1143
1449
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
1144
1450
  const totalAmount = BigInt(amount);
1145
1451
  if (totalAmount < BigInt(eligibleAgents.length)) {
1146
- throw new Error(
1147
- `Amount "${amount}" is too small to split across ${eligibleAgents.length} agents.`
1452
+ throw new OwneyError(
1453
+ "DEPOSIT_AMOUNT_TOO_SMALL",
1454
+ `Amount "${amount}" is too small to split across ${eligibleAgents.length} agents.`,
1455
+ { amount, agentCount: eligibleAgents.length }
1148
1456
  );
1149
1457
  }
1150
1458
  const perAgent = totalAmount / BigInt(eligibleAgents.length);
@@ -1167,8 +1475,10 @@ var OwneySDK = class {
1167
1475
  (sa) => sa.chainId === chainId
1168
1476
  );
1169
1477
  if (!chainAssets || !chainAssets.assets.includes(asset)) {
1170
- throw new Error(
1171
- `Agent "${agent.id}" does not support asset "${asset}" on chain ${chainId}.`
1478
+ throw new OwneyError(
1479
+ "ASSET_UNSUPPORTED",
1480
+ `Agent "${agent.id}" does not support asset "${asset}" on chain ${chainId}.`,
1481
+ { agentId: agent.id, asset, chainId }
1172
1482
  );
1173
1483
  }
1174
1484
  }
@@ -1180,8 +1490,10 @@ var OwneySDK = class {
1180
1490
  return chainAssets && chainAssets.assets.includes(asset);
1181
1491
  });
1182
1492
  if (eligible.length === 0) {
1183
- throw new Error(
1184
- `No agents support asset "${asset}" on chain ${chainId}.`
1493
+ throw new OwneyError(
1494
+ "ASSET_NO_COMPATIBLE_AGENTS",
1495
+ `No agents support asset "${asset}" on chain ${chainId}.`,
1496
+ { asset, chainId }
1185
1497
  );
1186
1498
  }
1187
1499
  return eligible;
@@ -1206,10 +1518,25 @@ var OwneySDK = class {
1206
1518
  return agent.withdraw(state, chainId, amount);
1207
1519
  }
1208
1520
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
1521
+ if (!amount) {
1522
+ const results2 = {};
1523
+ for (const agent of eligibleAgents) {
1524
+ results2[agent.id] = await agent.withdraw(state, chainId);
1525
+ }
1526
+ return { agentResult: results2 };
1527
+ }
1209
1528
  const results = {};
1529
+ let lastError;
1210
1530
  for (const agent of eligibleAgents) {
1211
- results[agent.id] = await agent.withdraw(state, chainId, amount);
1531
+ try {
1532
+ results[agent.id] = await agent.withdraw(state, chainId, amount);
1533
+ return { agentResult: results };
1534
+ } catch (err) {
1535
+ lastError = err;
1536
+ }
1212
1537
  }
1538
+ if (lastError)
1539
+ throw lastError;
1213
1540
  return {
1214
1541
  agentResult: results
1215
1542
  };
@@ -1221,17 +1548,50 @@ var OwneySDK = class {
1221
1548
  * @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
1222
1549
  */
1223
1550
  async getBalances(agentId) {
1551
+ const startedAt = Date.now();
1224
1552
  const state = this.requireState();
1225
1553
  const chainId = this.requireChainId();
1226
1554
  if (agentId) {
1227
- return this.getAgent(agentId).getBalances(state, chainId);
1555
+ const result = await this.getAgent(agentId).getBalances(state, chainId);
1556
+ logTiming3("getBalances single-agent", startedAt, { chainId, agentId });
1557
+ return result;
1228
1558
  }
1229
1559
  let totalBalance = 0;
1230
1560
  const results = {};
1231
- for (const [id, agent] of this.getActiveAgents()) {
1232
- results[id] = await agent.getBalances(state, chainId);
1233
- totalBalance = totalBalance + Number(results[id].totalBalance);
1561
+ const entries = [...this.getActiveAgents().entries()];
1562
+ const balanceResults = await Promise.allSettled(
1563
+ entries.map(async ([id, agent]) => {
1564
+ const agentStartedAt = Date.now();
1565
+ const b = await agent.getBalances(state, chainId);
1566
+ logTiming3("getBalances per-agent", agentStartedAt, {
1567
+ chainId,
1568
+ agentId: id
1569
+ });
1570
+ return [id, b];
1571
+ })
1572
+ );
1573
+ let successCount = 0;
1574
+ for (const settledResult of balanceResults) {
1575
+ if (settledResult.status === "fulfilled") {
1576
+ const [id, b] = settledResult.value;
1577
+ results[id] = b;
1578
+ totalBalance += Number(b.totalBalance);
1579
+ successCount += 1;
1580
+ continue;
1581
+ }
1582
+ console.error("getBalances agent failed:", settledResult.reason);
1583
+ }
1584
+ if (successCount === 0) {
1585
+ throw new OwneyError(
1586
+ "BALANCE_ALL_FAILED",
1587
+ "Failed to fetch balances for all active agents."
1588
+ );
1234
1589
  }
1590
+ logTiming3("getBalances aggregate", startedAt, {
1591
+ chainId,
1592
+ totalAgents: entries.length,
1593
+ successCount
1594
+ });
1235
1595
  return {
1236
1596
  totalBalance: String(totalBalance),
1237
1597
  agentBalances: results
@@ -1250,9 +1610,16 @@ var OwneySDK = class {
1250
1610
  }
1251
1611
  let totalEarnings = 0;
1252
1612
  const results = {};
1253
- for (const [id, agent] of this.getActiveAgents()) {
1254
- results[id] = await agent.getEarnings(state, chainId);
1255
- totalEarnings = totalEarnings + Number(results[id].lifetimeEarnings);
1613
+ const entries = [...this.getActiveAgents().entries()];
1614
+ const earningsResults = await Promise.all(
1615
+ entries.map(async ([id, agent]) => {
1616
+ const e = await agent.getEarnings(state, chainId);
1617
+ return [id, e];
1618
+ })
1619
+ );
1620
+ for (const [id, e] of earningsResults) {
1621
+ results[id] = e;
1622
+ totalEarnings += Number(e.lifetimeEarnings);
1256
1623
  }
1257
1624
  return {
1258
1625
  totalEarnings: String(totalEarnings),
@@ -1277,16 +1644,30 @@ var OwneySDK = class {
1277
1644
  return this.getAgent(agentId).getAccountApy(state, chainId, days);
1278
1645
  }
1279
1646
  const activeAgents = this.getActiveAgents();
1647
+ const entries = [...activeAgents.entries()];
1648
+ const [apyResults, balanceResults] = await Promise.all([
1649
+ Promise.all(
1650
+ entries.map(async ([id, agent]) => {
1651
+ const apy = await agent.getAccountApy(state, chainId, days);
1652
+ return [id, apy];
1653
+ })
1654
+ ),
1655
+ Promise.all(
1656
+ entries.map(async ([id, agent]) => {
1657
+ const b = await agent.getBalances(state, chainId);
1658
+ return [id, Number(b.totalBalance)];
1659
+ })
1660
+ )
1661
+ ]);
1280
1662
  const results = {};
1281
- for (const [id, agent] of activeAgents) {
1282
- results[id] = await agent.getAccountApy(state, chainId, days);
1663
+ for (const [id, apy] of apyResults) {
1664
+ results[id] = apy;
1283
1665
  }
1284
1666
  const balances = {};
1285
1667
  let totalBalance = 0;
1286
- for (const [id, agent] of activeAgents) {
1287
- const b = await agent.getBalances(state, chainId);
1288
- balances[id] = Number(b.totalBalance);
1289
- totalBalance += balances[id];
1668
+ for (const [id, bal] of balanceResults) {
1669
+ balances[id] = bal;
1670
+ totalBalance += bal;
1290
1671
  }
1291
1672
  let totalApy = 0;
1292
1673
  if (totalBalance > 0) {
@@ -1314,9 +1695,13 @@ var OwneySDK = class {
1314
1695
  if (agentId) {
1315
1696
  return this.getAgent(agentId).getHistory(state, chainId, filters);
1316
1697
  }
1698
+ const historyResults = await Promise.all(
1699
+ [...this.getActiveAgents().values()].map(
1700
+ (agent) => agent.getHistory(state, chainId, filters)
1701
+ )
1702
+ );
1317
1703
  let results = [];
1318
- for (const [, agent] of this.getActiveAgents()) {
1319
- const { data } = await agent.getHistory(state, chainId, filters);
1704
+ for (const { data } of historyResults) {
1320
1705
  results = [...results, ...data];
1321
1706
  }
1322
1707
  results.sort(
@@ -1360,8 +1745,15 @@ var OwneySDK = class {
1360
1745
  return this.getAgent(agentId).getUserProfile(state, chainId);
1361
1746
  }
1362
1747
  const results = {};
1363
- for (const [id, agent] of this.getActiveAgents()) {
1364
- results[id] = await agent.getUserProfile(state, chainId);
1748
+ const entries = [...this.getActiveAgents().entries()];
1749
+ const profileResults = await Promise.all(
1750
+ entries.map(async ([id, agent]) => {
1751
+ const p = await agent.getUserProfile(state, chainId);
1752
+ return [id, p];
1753
+ })
1754
+ );
1755
+ for (const [id, p] of profileResults) {
1756
+ results[id] = p;
1365
1757
  }
1366
1758
  return {
1367
1759
  agentUserProfile: results
@@ -1373,18 +1765,34 @@ var OwneySDK = class {
1373
1765
  * @param options - Contains agentId (optional) and days ("7D", "14D", or "30D")
1374
1766
  * @param options.agentId - Optional. Agent to query. Omit for all agents.
1375
1767
  * @param options.days - Lookback period: "7D", "14D", or "30D"
1768
+ * @param options.tokenSymbol - Optional asset symbol ("USDC" / "WETH") to
1769
+ * scope the APY to a specific asset+chain. Agents whose backends do not
1770
+ * support per-asset APY (currently Sail) ignore this.
1771
+ * @param options.chainId - Optional chain id for per-asset APY lookups.
1772
+ * Typically paired with `tokenSymbol`.
1376
1773
  * @returns {AgentApy} for a single agent, or {OwneyAgentApy} with per-agent APY
1377
1774
  */
1378
1775
  async getAgentApy({
1379
1776
  agentId,
1380
- days
1777
+ days,
1778
+ tokenSymbol,
1779
+ chainId
1381
1780
  }) {
1781
+ await this.ensureAgentsInitialized();
1782
+ const agentOptions = { tokenSymbol, chainId };
1382
1783
  if (agentId) {
1383
- return this.getAgent(agentId).getAgentApy(days);
1784
+ return this.getAgent(agentId).getAgentApy(days, agentOptions);
1384
1785
  }
1385
1786
  const results = {};
1386
- for (const [id, agent] of this.agents) {
1387
- results[id] = await agent.getAgentApy(days);
1787
+ const agentEntries = [...this.agents.entries()];
1788
+ const apyResults = await Promise.all(
1789
+ agentEntries.map(async ([id, agent]) => {
1790
+ const apy = await agent.getAgentApy(days, agentOptions);
1791
+ return [id, apy];
1792
+ })
1793
+ );
1794
+ for (const [id, apy] of apyResults) {
1795
+ results[id] = apy;
1388
1796
  }
1389
1797
  return {
1390
1798
  agentApy: results
@@ -1400,10 +1808,29 @@ var OwneySDK = class {
1400
1808
  const activeAgents = this.getActiveAgents();
1401
1809
  const balances = {};
1402
1810
  let totalBalance = 0;
1403
- for (const [id, agent] of activeAgents) {
1404
- const b = await agent.getBalances(state, chainId);
1405
- balances[id] = Number(b.totalBalance);
1406
- totalBalance += balances[id];
1811
+ const entries = [...activeAgents.entries()];
1812
+ const balanceResults = await Promise.allSettled(
1813
+ entries.map(async ([id, agent]) => {
1814
+ const b = await agent.getBalances(state, chainId);
1815
+ return [id, Number(b.totalBalance)];
1816
+ })
1817
+ );
1818
+ let successCount = 0;
1819
+ for (const settledResult of balanceResults) {
1820
+ if (settledResult.status === "fulfilled") {
1821
+ const [id, bal] = settledResult.value;
1822
+ balances[id] = bal;
1823
+ totalBalance += bal;
1824
+ successCount += 1;
1825
+ } else {
1826
+ console.error("getAgentAllocation agent failed:", settledResult.reason);
1827
+ }
1828
+ }
1829
+ if (successCount === 0) {
1830
+ throw new OwneyError(
1831
+ "ALLOCATION_ALL_FAILED",
1832
+ "Failed to fetch allocation: all agents failed."
1833
+ );
1407
1834
  }
1408
1835
  const allocation = {};
1409
1836
  if (totalBalance === 0) {
@@ -1431,5 +1858,6 @@ export {
1431
1858
  AgentChainIncompatibleError,
1432
1859
  AgentNotFoundError,
1433
1860
  NotConnectedError,
1861
+ OwneyError,
1434
1862
  OwneySDK
1435
1863
  };