@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.cjs CHANGED
@@ -23,6 +23,7 @@ __export(src_exports, {
23
23
  AgentChainIncompatibleError: () => AgentChainIncompatibleError,
24
24
  AgentNotFoundError: () => AgentNotFoundError,
25
25
  NotConnectedError: () => NotConnectedError,
26
+ OwneyError: () => OwneyError,
26
27
  OwneySDK: () => OwneySDK
27
28
  });
28
29
  module.exports = __toCommonJS(src_exports);
@@ -37,12 +38,76 @@ var P_SUPPORTED_CHAINS = {
37
38
  };
38
39
  var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
39
40
  var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
41
+ var SupportedAssets = [
42
+ {
43
+ address: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`,
44
+ symbol: "USDC",
45
+ chainId: 8453
46
+ },
47
+ {
48
+ address: `0xaf88d065e77c8cC2239327C5EDb3A432268e5831`,
49
+ symbol: "USDC",
50
+ chainId: 42161
51
+ },
52
+ {
53
+ address: `0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9`,
54
+ symbol: "USDT",
55
+ chainId: 42161
56
+ }
57
+ ];
58
+
59
+ // src/errors.ts
60
+ var OwneyError = class extends Error {
61
+ code;
62
+ details;
63
+ constructor(code, message, details) {
64
+ super(message);
65
+ this.name = "OwneyError";
66
+ this.code = code;
67
+ this.details = details;
68
+ }
69
+ };
70
+ var AgentNotFoundError = class extends OwneyError {
71
+ constructor(agentId, available) {
72
+ super(
73
+ "AGENT_NOT_FOUND",
74
+ `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
75
+ { agentId, available }
76
+ );
77
+ this.name = "AgentNotFoundError";
78
+ }
79
+ };
80
+ var NotConnectedError = class extends OwneyError {
81
+ constructor() {
82
+ super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
83
+ this.name = "NotConnectedError";
84
+ }
85
+ };
86
+ var AgentChainIncompatibleError = class extends OwneyError {
87
+ incompatibleAgents;
88
+ connectedChainId;
89
+ constructor(incompatibleAgents, connectedChainId) {
90
+ const details = incompatibleAgents.map(
91
+ ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
92
+ ).join("; ");
93
+ super(
94
+ "AGENT_CHAIN_INCOMPATIBLE",
95
+ `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
96
+ { incompatibleAgents, connectedChainId }
97
+ );
98
+ this.name = "AgentChainIncompatibleError";
99
+ this.incompatibleAgents = incompatibleAgents;
100
+ this.connectedChainId = connectedChainId;
101
+ }
102
+ };
40
103
 
41
104
  // src/lib/utils.ts
42
105
  var isValidChainId = (chainId) => {
43
106
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
44
- throw new Error(
45
- `Unsupported chain ID: ${chainId}. Supported chains: ${SUPPORTED_CHAIN_IDS.join(", ")}`
107
+ throw new OwneyError(
108
+ "CHAIN_UNSUPPORTED",
109
+ `Unsupported chain ID: ${chainId}. Supported chains: ${SUPPORTED_CHAIN_IDS.join(", ")}`,
110
+ { chainId, supportedChainIds: [...SUPPORTED_CHAIN_IDS] }
46
111
  );
47
112
  }
48
113
  return chainId;
@@ -57,6 +122,15 @@ function hexToDecimal(hex, decimals = 6) {
57
122
  const remainder = parsed % divisor;
58
123
  return Number(whole) + Number(remainder) / Number(divisor);
59
124
  }
125
+ function normalizeDecimalPlace(value, decimals = 6) {
126
+ const parsed = BigInt(value);
127
+ if (decimals <= 0)
128
+ return Number(parsed);
129
+ const divisor = BigInt(10 ** decimals);
130
+ const whole = parsed / divisor;
131
+ const remainder = parsed % divisor;
132
+ return Number(whole) + Number(remainder) / Number(divisor);
133
+ }
60
134
  var DayFilterMapping = {
61
135
  "30D": 30,
62
136
  "14D": 14,
@@ -86,7 +160,7 @@ var CHAIN_NAME_TO_ID = {
86
160
  BASE: 8453,
87
161
  ARBITRUM: 42161
88
162
  };
89
- function mapBalances(raw, chainId) {
163
+ function mapBalances(raw, chainId, smartWallet) {
90
164
  const portfolio = raw.portfolio;
91
165
  const tokenMap = /* @__PURE__ */ new Map();
92
166
  for (const p of portfolio.positions || []) {
@@ -97,18 +171,7 @@ function mapBalances(raw, chainId) {
97
171
  tokenMap.set(key, {
98
172
  symbol,
99
173
  chainId: positionChainId,
100
- amount: (current?.amount ?? 0) + hexToDecimal(p.amount || "0")
101
- });
102
- }
103
- for (const s of portfolio.staleBalances || []) {
104
- const symbol = s.tokenSymbol || "USDC";
105
- const staleChainId = s.chainId ?? chainId;
106
- const key = `${symbol}:${staleChainId}`;
107
- const current = tokenMap.get(key);
108
- tokenMap.set(key, {
109
- symbol,
110
- chainId: staleChainId,
111
- amount: (current?.amount ?? 0) + hexToDecimal(s.balance || "0")
174
+ amount: (current?.amount ?? 0) + normalizeDecimalPlace(p.underlyingAmount || "0")
112
175
  });
113
176
  }
114
177
  let totalBalance = 0;
@@ -123,7 +186,7 @@ function mapBalances(raw, chainId) {
123
186
  });
124
187
  }
125
188
  return {
126
- smartWallet: portfolio.smartWallet,
189
+ smartWallet,
127
190
  totalBalance: String(totalBalance),
128
191
  tokens
129
192
  };
@@ -214,13 +277,41 @@ function mapUserProfile(raw, userAddress) {
214
277
  };
215
278
  }
216
279
  function mapApyByStrategy(raw) {
217
- const first = raw.data[0];
280
+ const apyPerAsset = {};
281
+ let apySum = 0;
282
+ let apyCount = 0;
283
+ for (const entry of raw.data) {
284
+ const supported = SupportedAssets.find(
285
+ (asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
286
+ );
287
+ if (!supported)
288
+ continue;
289
+ const chainKey = supported.chainId;
290
+ const symbolKey = supported.symbol;
291
+ const bucket = apyPerAsset[chainKey] ?? {};
292
+ bucket[symbolKey] = entry.average_apy;
293
+ apyPerAsset[chainKey] = bucket;
294
+ apySum += entry.average_apy;
295
+ apyCount += 1;
296
+ }
297
+ const averageApy = apyCount > 0 ? apySum / apyCount : 0;
298
+ const hasDetails = Object.keys(apyPerAsset).length > 0;
218
299
  return {
219
- averageApy: first?.average_apy ?? 0
300
+ averageApy,
301
+ detailedApys: hasDetails ? { apyPerAsset } : void 0
220
302
  };
221
303
  }
222
304
 
223
305
  // src/agents/zyfai/zyfai.agent.ts
306
+ var ZYFAI_TIMING_PREFIX = "[AgentTiming][zyfai]";
307
+ function logTiming(label, startedAt, meta) {
308
+ const elapsedMs = Date.now() - startedAt;
309
+ if (meta) {
310
+ console.log(`${ZYFAI_TIMING_PREFIX} ${label} ${elapsedMs}ms`, meta);
311
+ return;
312
+ }
313
+ console.log(`${ZYFAI_TIMING_PREFIX} ${label} ${elapsedMs}ms`);
314
+ }
224
315
  var ZyfaiAgent = class {
225
316
  id = "zyfai";
226
317
  supportedChainIds = [8453, 42161];
@@ -239,62 +330,122 @@ var ZyfaiAgent = class {
239
330
  sdk;
240
331
  connectedAddress = null;
241
332
  connectedChainId = null;
333
+ smartWalletCache = /* @__PURE__ */ new Map();
334
+ connectAuthPromise = null;
242
335
  constructor(apiKey) {
243
336
  this.sdk = new import_sdk.ZyfaiSDK({ apiKey });
244
337
  }
245
338
  // --- Private helpers ---
246
339
  getAddress() {
247
340
  if (!this.connectedAddress) {
248
- throw new Error("Wallet not connected. Call sdk.connect() first.");
341
+ throw new OwneyError(
342
+ "NOT_CONNECTED",
343
+ "Wallet not connected. Call sdk.connect() first."
344
+ );
249
345
  }
250
346
  return this.connectedAddress;
251
347
  }
252
348
  /** Light connect: SIWE auth only. Re-auths if chain changed. */
253
349
  async connectAuth(state, chainId) {
350
+ const startedAt = Date.now();
254
351
  const validChainId = isValidChainId(chainId);
255
- if (!this.connectedAddress || this.connectedChainId !== validChainId) {
352
+ if (this.connectedAddress && this.connectedChainId === validChainId) {
353
+ logTiming("connectAuth cache-hit", startedAt, { chainId: validChainId });
354
+ return;
355
+ }
356
+ if (this.connectAuthPromise) {
357
+ await this.connectAuthPromise;
358
+ logTiming("connectAuth awaited-inflight", startedAt, {
359
+ chainId: validChainId
360
+ });
361
+ return;
362
+ }
363
+ this.connectAuthPromise = (async () => {
256
364
  const address = await this.sdk.connectAccount(
257
365
  state.provider,
258
366
  validChainId
259
367
  );
260
368
  this.connectedAddress = address;
261
369
  this.connectedChainId = validChainId;
370
+ })();
371
+ try {
372
+ await this.connectAuthPromise;
373
+ logTiming("connectAuth success", startedAt, { chainId: validChainId });
374
+ } finally {
375
+ this.connectAuthPromise = null;
262
376
  }
263
377
  }
264
378
  /** Full connect: SIWE auth + Safe deploy check + session key. */
265
379
  async connectFull(state, chainId) {
380
+ const startedAt = Date.now();
266
381
  await this.connectAuth(state, chainId);
267
382
  const validChainId = isValidChainId(chainId);
268
383
  const address = this.getAddress();
384
+ const walletLookupStart = Date.now();
269
385
  const wallet = await this.sdk.getSmartWalletAddress(address, validChainId);
386
+ logTiming("connectFull getSmartWalletAddress", walletLookupStart, {
387
+ chainId: validChainId
388
+ });
389
+ this.smartWalletCache.set(`${address}_${validChainId}`, wallet.address);
270
390
  if (!wallet.isDeployed) {
391
+ const deployStart = Date.now();
271
392
  await this.sdk.deploySafe(address, validChainId, "aggressive", true);
393
+ logTiming("connectFull deploySafe", deployStart, {
394
+ chainId: validChainId
395
+ });
272
396
  }
397
+ const userDetailsStart = Date.now();
273
398
  const user = await this.sdk.getUserDetails();
399
+ logTiming("connectFull getUserDetails", userDetailsStart, {
400
+ chainId: validChainId
401
+ });
274
402
  if (!user.hasActiveSessionKey) {
403
+ const sessionKeyStart = Date.now();
275
404
  await this.sdk.createSessionKey(address, validChainId);
405
+ logTiming("connectFull createSessionKey", sessionKeyStart, {
406
+ chainId: validChainId
407
+ });
276
408
  }
409
+ logTiming("connectFull success", startedAt, { chainId: validChainId });
277
410
  }
278
411
  /** Connect + resolve smart wallet address for read operations. */
279
412
  async resolveSmartWallet(state, chainId) {
413
+ const startedAt = Date.now();
280
414
  await this.connectAuth(state, chainId);
281
415
  const validChainId = isValidChainId(chainId);
282
- const { address } = await this.sdk.getSmartWalletAddress(
283
- this.getAddress(),
284
- validChainId
285
- );
286
- return { smartWallet: address, chainId: validChainId };
416
+ const ownerAddress = this.getAddress();
417
+ const cacheKey = `${ownerAddress}_${validChainId}`;
418
+ let smartWallet = this.smartWalletCache.get(cacheKey);
419
+ if (!smartWallet) {
420
+ const lookupStart = Date.now();
421
+ const { address } = await this.sdk.getSmartWalletAddress(
422
+ ownerAddress,
423
+ validChainId
424
+ );
425
+ logTiming("resolveSmartWallet remote-lookup", lookupStart, {
426
+ chainId: validChainId
427
+ });
428
+ smartWallet = address;
429
+ this.smartWalletCache.set(cacheKey, smartWallet);
430
+ } else {
431
+ logTiming("resolveSmartWallet cache-hit", startedAt, {
432
+ chainId: validChainId
433
+ });
434
+ }
435
+ logTiming("resolveSmartWallet ready", startedAt, { chainId: validChainId });
436
+ return { smartWallet, chainId: validChainId };
287
437
  }
288
438
  // --- IAgent: Connection lifecycle ---
289
439
  async disconnect() {
290
440
  await this.sdk.disconnectAccount();
291
441
  this.connectedAddress = null;
292
442
  this.connectedChainId = null;
443
+ this.smartWalletCache.clear();
293
444
  }
294
445
  // --- IAgent: Account lifecycle ---
295
446
  async activateAgent(state, chainId) {
296
447
  try {
297
- await this.connectFull(state, chainId);
448
+ await this.connectAuth(state, chainId);
298
449
  } catch (error) {
299
450
  await this.disconnect();
300
451
  throw error;
@@ -341,10 +492,15 @@ var ZyfaiAgent = class {
341
492
  }
342
493
  // --- IAgent: Portfolio reads ---
343
494
  async getBalances(state, chainId) {
344
- await this.connectAuth(state, chainId);
345
- const validChainId = isValidChainId(chainId);
346
- const raw = await this.sdk.getPositions(this.getAddress(), validChainId);
347
- return mapBalances(raw, validChainId);
495
+ const startedAt = Date.now();
496
+ const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
497
+ const portfolioStart = Date.now();
498
+ const raw = await this.sdk.getPortfolio(this.getAddress());
499
+ logTiming("getBalances getPortfolio", portfolioStart, {
500
+ chainId: validChainId
501
+ });
502
+ logTiming("getBalances success", startedAt, { chainId: validChainId });
503
+ return mapBalances(raw, validChainId, smartWallet);
348
504
  }
349
505
  async getEarnings(state, chainId) {
350
506
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -378,11 +534,13 @@ var ZyfaiAgent = class {
378
534
  return mapUserProfile(raw, this.connectedAddress);
379
535
  }
380
536
  // --- IAgent: Discovery (no wallet required) ---
381
- async getAgentApy(days) {
537
+ async getAgentApy(days, options) {
382
538
  const raw = await this.sdk.getAPYPerStrategy(
383
539
  false,
384
540
  DayFilterMapping[days],
385
- "aggressive"
541
+ "aggressive",
542
+ options?.chainId,
543
+ options?.tokenSymbol
386
544
  );
387
545
  return mapApyByStrategy(raw);
388
546
  }
@@ -428,9 +586,11 @@ function mapEarnings2(raw, smartWallet) {
428
586
  };
429
587
  }
430
588
  function mapApyHistory2(raw, walletAddress) {
589
+ const apyPerAsset = remapApyPerAssetToSymbols(raw.result.apy_per_asset);
431
590
  return {
432
591
  walletAddress,
433
- weightedApyAfterFee: raw.result
592
+ weightedApyAfterFee: raw.result.apy,
593
+ weightedApyAfterFeeDetails: apyPerAsset ? { apyPerAsset } : void 0
434
594
  };
435
595
  }
436
596
  function mapSailTransaction(tx) {
@@ -564,15 +724,45 @@ function mapUserProfile2(raw) {
564
724
  };
565
725
  }
566
726
  function mapApyByStrategy2(raw) {
727
+ const apyPerAsset = remapApyPerAssetToSymbols(raw.result.apy_per_asset);
567
728
  return {
568
- averageApy: raw.result
729
+ averageApy: raw.result.apy,
730
+ detailedApys: apyPerAsset ? { apyPerAsset } : void 0
569
731
  };
570
732
  }
733
+ function remapApyPerAssetToSymbols(raw) {
734
+ if (!raw)
735
+ return void 0;
736
+ const remapped = {};
737
+ for (const asset of SupportedAssets) {
738
+ const chainKey = asset.chainId;
739
+ const apyByAddress = raw[String(chainKey)];
740
+ if (!apyByAddress)
741
+ continue;
742
+ const apy = apyByAddress[asset.address] ?? apyByAddress[asset.address.toLowerCase()];
743
+ if (apy === void 0)
744
+ continue;
745
+ const bucket = remapped[chainKey] ?? {};
746
+ bucket[asset.symbol] = apy;
747
+ remapped[chainKey] = bucket;
748
+ }
749
+ return Object.keys(remapped).length > 0 ? remapped : void 0;
750
+ }
571
751
 
572
752
  // src/agents/sail/sail.agent.ts
573
753
  var SAIL_API_BASE = "https://app.sail.money";
574
754
  var PROJECT_ID = "sail";
575
755
  var PAGE_ID = "home";
756
+ var SAIL_TIMING_PREFIX = "[AgentTiming][sail]";
757
+ var SAIL_PORTFOLIO_TIMEOUT_MS = 8e3;
758
+ function logTiming2(label, startedAt, meta) {
759
+ const elapsedMs = Date.now() - startedAt;
760
+ if (meta) {
761
+ console.log(`${SAIL_TIMING_PREFIX} ${label} ${elapsedMs}ms`, meta);
762
+ return;
763
+ }
764
+ console.log(`${SAIL_TIMING_PREFIX} ${label} ${elapsedMs}ms`);
765
+ }
576
766
  var SailAgent = class {
577
767
  id = "sail";
578
768
  supportedChainIds = [8453, 42161];
@@ -594,11 +784,12 @@ var SailAgent = class {
594
784
  connectedAddress = null;
595
785
  smartWalletAddress = null;
596
786
  connectedChainId = null;
787
+ connectPromise = null;
597
788
  constructor(apiKey) {
598
789
  this.apiKey = apiKey;
599
790
  }
600
791
  // --- Private helpers ---
601
- async request(method, path, body) {
792
+ async request(method, path, body, timeoutMs) {
602
793
  const headers = {
603
794
  "Content-Type": "application/json",
604
795
  "X-Business-API-Key": this.apiKey
@@ -609,32 +800,57 @@ var SailAgent = class {
609
800
  if (this.accountToken) {
610
801
  headers["X-Account-Token"] = this.accountToken;
611
802
  }
612
- const res = await fetch(`${SAIL_API_BASE}${path}`, {
613
- method,
614
- headers,
615
- body: body ? JSON.stringify(body) : void 0
616
- });
803
+ const controller = new AbortController();
804
+ const timeoutId = typeof timeoutMs === "number" && timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
805
+ let res;
806
+ try {
807
+ res = await fetch(`${SAIL_API_BASE}${path}`, {
808
+ method,
809
+ headers,
810
+ body: body ? JSON.stringify(body) : void 0,
811
+ signal: controller.signal
812
+ });
813
+ } catch (error) {
814
+ if (error instanceof Error && error.name === "AbortError" && timeoutMs) {
815
+ throw new OwneyError(
816
+ "API_SAIL_TIMEOUT",
817
+ `Sail API timeout ${timeoutMs}ms: ${method} ${path}`,
818
+ { timeoutMs, method, path }
819
+ );
820
+ }
821
+ throw error;
822
+ } finally {
823
+ if (timeoutId)
824
+ clearTimeout(timeoutId);
825
+ }
617
826
  if (!res.ok) {
618
827
  const text = await res.text().catch(() => "");
619
- throw new Error(`Sail API error ${res.status}: ${text}`);
828
+ throw new OwneyError(
829
+ "API_SAIL_ERROR",
830
+ `Sail API error ${res.status}: ${text}`,
831
+ { statusCode: res.status, method, path, responseBody: text }
832
+ );
620
833
  }
621
834
  return res.json();
622
835
  }
623
836
  /** Initiate SIWE and sign with the user's wallet. */
624
837
  async signSiwe(state, chainId) {
838
+ const startedAt = Date.now();
625
839
  const initiate = await this.request(
626
840
  "POST",
627
841
  `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/auth/initiate`,
628
842
  {
629
843
  method: "siwe",
630
844
  address: state.walletAddress,
631
- chainId
845
+ chainId,
846
+ identityMode: "byWallet"
632
847
  }
633
848
  );
634
849
  const signature = await state.provider.request({
635
850
  method: "personal_sign",
636
851
  params: [initiate.message, state.walletAddress]
637
852
  });
853
+ logTiming2("signSiwe success", startedAt, { chainId });
638
854
  return {
639
855
  sessionId: initiate.sessionId,
640
856
  message: initiate.message,
@@ -643,38 +859,62 @@ var SailAgent = class {
643
859
  }
644
860
  /** Full connect: auth handles smart account creation via SIWE flow. */
645
861
  async connectFull(state, chainId) {
862
+ const startedAt = Date.now();
646
863
  if (this.connectedAddress && this.connectedChainId === chainId) {
864
+ logTiming2("connectFull cache-hit", startedAt, { chainId });
865
+ return;
866
+ }
867
+ if (this.connectPromise) {
868
+ await this.connectPromise;
869
+ logTiming2("connectFull awaited-inflight", startedAt, { chainId });
647
870
  return;
648
871
  }
649
872
  if (!state.walletAddress) {
650
- throw new Error("Wallet address is required");
873
+ throw new OwneyError(
874
+ "WALLET_ADDRESS_REQUIRED",
875
+ "Wallet address is required. Call sdk.connect(provider) first."
876
+ );
877
+ }
878
+ this.connectPromise = (async () => {
879
+ const { sessionId, signature, message } = await this.signSiwe(
880
+ state,
881
+ chainId
882
+ );
883
+ const complete = await this.request(
884
+ "POST",
885
+ `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/auth/complete`,
886
+ {
887
+ method: "custom",
888
+ sessionId,
889
+ signature,
890
+ walletAddress: state.walletAddress,
891
+ identityMode: "byWallet"
892
+ }
893
+ );
894
+ this.authToken = complete.token;
895
+ this.accountToken = complete.accountToken;
896
+ this.connectedAddress = state.walletAddress;
897
+ this.smartWalletAddress = complete.walletAddress;
898
+ this.connectedChainId = chainId;
899
+ })();
900
+ try {
901
+ await this.connectPromise;
902
+ logTiming2("connectFull success", startedAt, { chainId });
903
+ } finally {
904
+ this.connectPromise = null;
651
905
  }
652
- const { sessionId, signature, message } = await this.signSiwe(
653
- state,
654
- chainId
655
- );
656
- const complete = await this.request(
657
- "POST",
658
- `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/auth/complete`,
659
- {
660
- method: "custom",
661
- sessionId,
662
- signature,
663
- walletAddress: state.walletAddress
664
- }
665
- );
666
- this.authToken = complete.token;
667
- this.accountToken = complete.accountToken;
668
- this.connectedAddress = state.walletAddress;
669
- this.smartWalletAddress = complete.walletAddress;
670
- this.connectedChainId = chainId;
671
906
  }
672
907
  async resolveSmartWallet(state, chainId) {
908
+ const startedAt = Date.now();
673
909
  await this.connectFull(state, chainId);
674
910
  const validChainId = isValidChainId(chainId);
675
911
  if (!this.smartWalletAddress) {
676
- throw new Error("Wallet not connected. Call sdk.connect() first.");
912
+ throw new OwneyError(
913
+ "NOT_CONNECTED",
914
+ "Wallet not connected. Call sdk.connect() first."
915
+ );
677
916
  }
917
+ logTiming2("resolveSmartWallet ready", startedAt, { chainId: validChainId });
678
918
  return {
679
919
  smartWallet: this.smartWalletAddress,
680
920
  chainId: validChainId
@@ -699,7 +939,8 @@ var SailAgent = class {
699
939
  }
700
940
  async deposit(state, chainId, amount, depositCallback) {
701
941
  if (!depositCallback) {
702
- throw new Error(
942
+ throw new OwneyError(
943
+ "DEPOSIT_CALLBACK_REQUIRED",
703
944
  "depositCallback is required for Sail. Provide a callback that transfers tokens and returns the transaction hash."
704
945
  );
705
946
  }
@@ -708,12 +949,16 @@ var SailAgent = class {
708
949
  const depositInfo = await this.getDepositInfo();
709
950
  const resolvedTokenAddress = depositInfo.permittedTokens[0]?.address;
710
951
  if (!resolvedTokenAddress) {
711
- throw new Error("No permitted tokens available for deposit.");
952
+ throw new OwneyError(
953
+ "DEPOSIT_NO_PERMITTED_TOKENS",
954
+ "No permitted tokens available for deposit."
955
+ );
712
956
  }
713
957
  await this.executePreHooks(resolvedTokenAddress, amount, chainId);
714
958
  const txHash = await depositCallback(smartWallet, chainId, amount);
715
959
  if (!txHash) {
716
- throw new Error(
960
+ throw new OwneyError(
961
+ "DEPOSIT_CALLBACK_INVALID",
717
962
  "depositCallback must return a transaction hash (txHash) for Sail deposits."
718
963
  );
719
964
  }
@@ -767,7 +1012,10 @@ var SailAgent = class {
767
1012
  const withdrawInfo = await this.getWithdrawInfo();
768
1013
  const tokenAddress = withdrawInfo.permittedTokens[0]?.address;
769
1014
  if (!tokenAddress) {
770
- throw new Error("No permitted tokens available for withdrawal.");
1015
+ throw new OwneyError(
1016
+ "WITHDRAW_NO_PERMITTED_TOKENS",
1017
+ "No permitted tokens available for withdrawal."
1018
+ );
771
1019
  }
772
1020
  const resolvedAmount = amount ?? withdrawInfo.currentBalance;
773
1021
  const { signature: adminWalletSignature, message: adminWalletPayload } = await this.signSiwe(state, chainId);
@@ -791,9 +1039,30 @@ var SailAgent = class {
791
1039
  }
792
1040
  // --- IAgent: Portfolio reads ---
793
1041
  async getBalances(state, chainId) {
1042
+ const startedAt = Date.now();
794
1043
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
795
- const raw = await this.getMetricsPortfolio(state, chainId);
796
- return mapPortfolioBalances(raw, smartWallet);
1044
+ const portfolioStart = Date.now();
1045
+ try {
1046
+ const raw = await this.getMetricsPortfolio(state, chainId);
1047
+ logTiming2("getBalances getMetricsPortfolio", portfolioStart, { chainId });
1048
+ logTiming2("getBalances success", startedAt, { chainId });
1049
+ return mapPortfolioBalances(raw, smartWallet);
1050
+ } catch (error) {
1051
+ if (error instanceof OwneyError && error.code === "API_SAIL_TIMEOUT") {
1052
+ console.warn(
1053
+ "Sail portfolio request timed out; using empty snapshot.",
1054
+ {
1055
+ chainId
1056
+ }
1057
+ );
1058
+ logTiming2("getBalances timeout-fallback", startedAt, { chainId });
1059
+ return mapPortfolioBalances(
1060
+ { message: "timeout", result: [] },
1061
+ smartWallet
1062
+ );
1063
+ }
1064
+ throw error;
1065
+ }
797
1066
  }
798
1067
  async getEarnings(state, chainId) {
799
1068
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -814,7 +1083,9 @@ var SailAgent = class {
814
1083
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
815
1084
  return this.request(
816
1085
  "GET",
817
- `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/portfolio?${smartWallet ? `smart_account=${smartWallet}` : ""}`
1086
+ `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/portfolio?${smartWallet ? `smart_account=${smartWallet}` : ""}`,
1087
+ void 0,
1088
+ SAIL_PORTFOLIO_TIMEOUT_MS
818
1089
  );
819
1090
  }
820
1091
  async getMetricsEarnings(state, chainId) {
@@ -861,8 +1132,10 @@ var SailAgent = class {
861
1132
  }
862
1133
  const dayCount = DayFilterMapping[days];
863
1134
  if (dayCount === void 0) {
864
- throw new Error(
865
- `Invalid days value: "${days}". Expected one of: "7D", "14D", "30D".`
1135
+ throw new OwneyError(
1136
+ "VALIDATION_INVALID_DAYS",
1137
+ `Invalid days value: "${days}". Expected one of: "7D", "14D", "30D".`,
1138
+ { days }
866
1139
  );
867
1140
  }
868
1141
  const now = /* @__PURE__ */ new Date();
@@ -933,43 +1206,12 @@ var SailAgent = class {
933
1206
  return mapUserProfile2(raw);
934
1207
  }
935
1208
  // --- IAgent: Discovery (no wallet required) ---
936
- async getAgentApy(days) {
1209
+ async getAgentApy(days, _options) {
937
1210
  const raw = await this.getMetricsYield(days);
938
1211
  return mapApyByStrategy2(raw);
939
1212
  }
940
1213
  };
941
1214
 
942
- // src/errors.ts
943
- var AgentNotFoundError = class extends Error {
944
- constructor(agentId, available) {
945
- super(
946
- `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`
947
- );
948
- this.name = "AgentNotFoundError";
949
- }
950
- };
951
- var NotConnectedError = class extends Error {
952
- constructor() {
953
- super("Not connected. Call sdk.connect(provider) first.");
954
- this.name = "NotConnectedError";
955
- }
956
- };
957
- var AgentChainIncompatibleError = class extends Error {
958
- incompatibleAgents;
959
- connectedChainId;
960
- constructor(incompatibleAgents, connectedChainId) {
961
- const details = incompatibleAgents.map(
962
- ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
963
- ).join("; ");
964
- super(
965
- `Chain ${connectedChainId} is not supported by the following agents: ${details}`
966
- );
967
- this.name = "AgentChainIncompatibleError";
968
- this.incompatibleAgents = incompatibleAgents;
969
- this.connectedChainId = connectedChainId;
970
- }
971
- };
972
-
973
1215
  // src/lib/routing-api.ts
974
1216
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
975
1217
  async function fetchAgentKeys(apiKey) {
@@ -983,11 +1225,19 @@ async function fetchAgentKeys(apiKey) {
983
1225
  });
984
1226
  if (!res.ok) {
985
1227
  const text = await res.text().catch(() => "");
986
- throw new Error(`Routing API error ${res.status}: ${text}`);
1228
+ throw new OwneyError(
1229
+ "API_ROUTING_ERROR",
1230
+ `Routing API error ${res.status}: ${text}`,
1231
+ { statusCode: res.status, responseBody: text }
1232
+ );
987
1233
  }
988
1234
  const json = await res.json();
989
1235
  if (!json.success) {
990
- throw new Error(`Routing API request failed: ${json.message}`);
1236
+ throw new OwneyError(
1237
+ "API_ROUTING_FAILED",
1238
+ `Routing API request failed: ${json.message}`,
1239
+ { message: json.message }
1240
+ );
991
1241
  }
992
1242
  return json.data;
993
1243
  }
@@ -997,11 +1247,21 @@ var AGENT_CONSTRUCTORS = {
997
1247
  zyfai: ZyfaiAgent,
998
1248
  sail: SailAgent
999
1249
  };
1250
+ var SDK_TIMING_PREFIX = "[OwneySDKTiming]";
1251
+ function logTiming3(label, startedAt, meta) {
1252
+ const elapsedMs = Date.now() - startedAt;
1253
+ if (meta) {
1254
+ console.log(`${SDK_TIMING_PREFIX} ${label} ${elapsedMs}ms`, meta);
1255
+ return;
1256
+ }
1257
+ console.log(`${SDK_TIMING_PREFIX} ${label} ${elapsedMs}ms`);
1258
+ }
1000
1259
  var OwneySDK = class {
1001
1260
  agents = /* @__PURE__ */ new Map();
1002
1261
  activeAgents = /* @__PURE__ */ new Set();
1003
1262
  state = null;
1004
1263
  apiKey;
1264
+ initializingAgentsPromise = null;
1005
1265
  constructor(config) {
1006
1266
  this.apiKey = config.apiKey;
1007
1267
  }
@@ -1017,7 +1277,8 @@ var OwneySDK = class {
1017
1277
  });
1018
1278
  const walletAddress = accounts[0];
1019
1279
  if (!walletAddress) {
1020
- throw new Error(
1280
+ throw new OwneyError(
1281
+ "WALLET_NO_ACCOUNTS",
1021
1282
  "No accounts found. Ensure the wallet is unlocked and connected."
1022
1283
  );
1023
1284
  }
@@ -1056,7 +1317,8 @@ var OwneySDK = class {
1056
1317
  requireChainId() {
1057
1318
  const chainId = this.state?.chainId ?? null;
1058
1319
  if (chainId === null) {
1059
- throw new Error(
1320
+ throw new OwneyError(
1321
+ "NO_ACTIVE_CHAIN",
1060
1322
  "No active chain. Call sdk.activateAgent(chainId) first."
1061
1323
  );
1062
1324
  }
@@ -1078,6 +1340,33 @@ var OwneySDK = class {
1078
1340
  }
1079
1341
  return active;
1080
1342
  }
1343
+ async ensureAgentsInitialized() {
1344
+ if (this.agents.size > 0)
1345
+ return;
1346
+ if (!this.initializingAgentsPromise) {
1347
+ this.initializingAgentsPromise = (async () => {
1348
+ const agentKeys = await fetchAgentKeys(this.apiKey);
1349
+ for (const { key, agent_type } of agentKeys) {
1350
+ const AgentClass = AGENT_CONSTRUCTORS[agent_type];
1351
+ if (!AgentClass)
1352
+ continue;
1353
+ const agent = new AgentClass(key);
1354
+ this.agents.set(agent_type, agent);
1355
+ }
1356
+ if (this.agents.size === 0) {
1357
+ throw new OwneyError(
1358
+ "API_NO_AGENTS",
1359
+ "No supported agents returned by the routing API. Check your API key and routing configuration."
1360
+ );
1361
+ }
1362
+ })();
1363
+ }
1364
+ try {
1365
+ await this.initializingAgentsPromise;
1366
+ } finally {
1367
+ this.initializingAgentsPromise = null;
1368
+ }
1369
+ }
1081
1370
  // --- Account lifecycle ---
1082
1371
  /**
1083
1372
  * Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
@@ -1090,25 +1379,13 @@ var OwneySDK = class {
1090
1379
  * throws before activating any agent.
1091
1380
  */
1092
1381
  async activateAgent(chainId, agentId) {
1382
+ const startedAt = Date.now();
1093
1383
  const state = this.requireState();
1094
- if (this.agents.size === 0) {
1095
- const agentKeys = await fetchAgentKeys(this.apiKey);
1096
- for (const { key, agent_type } of agentKeys) {
1097
- const AgentClass = AGENT_CONSTRUCTORS[agent_type];
1098
- if (!AgentClass)
1099
- continue;
1100
- const agent = new AgentClass(key);
1101
- this.agents.set(agent_type, agent);
1102
- }
1103
- if (this.agents.size === 0) {
1104
- throw new Error(
1105
- "No supported agents returned by the routing API. Check your API key and routing configuration."
1106
- );
1107
- }
1108
- }
1384
+ await this.ensureAgentsInitialized();
1109
1385
  if (agentId !== void 0) {
1110
1386
  if (agentId.length === 0) {
1111
- throw new Error(
1387
+ throw new OwneyError(
1388
+ "AGENT_EMPTY_LIST",
1112
1389
  "agentId array must not be empty. Omit the argument to activate all compatible agents."
1113
1390
  );
1114
1391
  }
@@ -1123,30 +1400,60 @@ var OwneySDK = class {
1123
1400
  if (incompatibleAgents.length > 0) {
1124
1401
  throw new AgentChainIncompatibleError(incompatibleAgents, chainId);
1125
1402
  }
1126
- for (const agent of agents) {
1127
- await agent.activateAgent(state, chainId);
1128
- }
1129
1403
  this.activeAgents.clear();
1130
1404
  for (const id of uniqueIds) {
1131
1405
  this.activeAgents.add(id);
1132
1406
  }
1133
1407
  state.chainId = chainId;
1408
+ Promise.all(
1409
+ agents.map(async (agent) => {
1410
+ const agentStart = Date.now();
1411
+ await agent.activateAgent(state, chainId);
1412
+ logTiming3("activateAgent background-agent-ready", agentStart, {
1413
+ chainId,
1414
+ agentId: agent.id
1415
+ });
1416
+ })
1417
+ ).catch((error) => {
1418
+ console.error("activateAgent background init failed:", error);
1419
+ });
1420
+ logTiming3("activateAgent scheduled", startedAt, {
1421
+ chainId,
1422
+ agentIds: uniqueIds
1423
+ });
1134
1424
  return;
1135
1425
  }
1136
1426
  const compatible = [...this.agents.values()].filter(
1137
1427
  (agent) => agent.supportedChainIds.includes(chainId)
1138
1428
  );
1139
1429
  if (compatible.length === 0) {
1140
- throw new Error(`No agents support chain ${chainId}.`);
1141
- }
1142
- for (const agent of compatible) {
1143
- await agent.activateAgent(state, chainId);
1430
+ throw new OwneyError(
1431
+ "CHAIN_NO_COMPATIBLE_AGENTS",
1432
+ `No agents support chain ${chainId}.`,
1433
+ { chainId }
1434
+ );
1144
1435
  }
1145
1436
  this.activeAgents.clear();
1146
1437
  for (const agent of compatible) {
1147
1438
  this.activeAgents.add(agent.id);
1148
1439
  }
1149
1440
  state.chainId = chainId;
1441
+ Promise.all(
1442
+ compatible.map(async (agent) => {
1443
+ const agentStart = Date.now();
1444
+ await agent.activateAgent(state, chainId);
1445
+ logTiming3("activateAgent background-agent-ready", agentStart, {
1446
+ chainId,
1447
+ agentId: agent.id
1448
+ });
1449
+ })
1450
+ ).catch((error) => {
1451
+ console.error("activateAgent background init failed:", error);
1452
+ });
1453
+ logTiming3("activateAgent scheduled", startedAt, {
1454
+ chainId,
1455
+ agentIds: compatible.map((agent) => agent.id)
1456
+ });
1150
1457
  }
1151
1458
  /**
1152
1459
  * Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
@@ -1172,8 +1479,10 @@ var OwneySDK = class {
1172
1479
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
1173
1480
  const totalAmount = BigInt(amount);
1174
1481
  if (totalAmount < BigInt(eligibleAgents.length)) {
1175
- throw new Error(
1176
- `Amount "${amount}" is too small to split across ${eligibleAgents.length} agents.`
1482
+ throw new OwneyError(
1483
+ "DEPOSIT_AMOUNT_TOO_SMALL",
1484
+ `Amount "${amount}" is too small to split across ${eligibleAgents.length} agents.`,
1485
+ { amount, agentCount: eligibleAgents.length }
1177
1486
  );
1178
1487
  }
1179
1488
  const perAgent = totalAmount / BigInt(eligibleAgents.length);
@@ -1196,8 +1505,10 @@ var OwneySDK = class {
1196
1505
  (sa) => sa.chainId === chainId
1197
1506
  );
1198
1507
  if (!chainAssets || !chainAssets.assets.includes(asset)) {
1199
- throw new Error(
1200
- `Agent "${agent.id}" does not support asset "${asset}" on chain ${chainId}.`
1508
+ throw new OwneyError(
1509
+ "ASSET_UNSUPPORTED",
1510
+ `Agent "${agent.id}" does not support asset "${asset}" on chain ${chainId}.`,
1511
+ { agentId: agent.id, asset, chainId }
1201
1512
  );
1202
1513
  }
1203
1514
  }
@@ -1209,8 +1520,10 @@ var OwneySDK = class {
1209
1520
  return chainAssets && chainAssets.assets.includes(asset);
1210
1521
  });
1211
1522
  if (eligible.length === 0) {
1212
- throw new Error(
1213
- `No agents support asset "${asset}" on chain ${chainId}.`
1523
+ throw new OwneyError(
1524
+ "ASSET_NO_COMPATIBLE_AGENTS",
1525
+ `No agents support asset "${asset}" on chain ${chainId}.`,
1526
+ { asset, chainId }
1214
1527
  );
1215
1528
  }
1216
1529
  return eligible;
@@ -1235,10 +1548,25 @@ var OwneySDK = class {
1235
1548
  return agent.withdraw(state, chainId, amount);
1236
1549
  }
1237
1550
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
1551
+ if (!amount) {
1552
+ const results2 = {};
1553
+ for (const agent of eligibleAgents) {
1554
+ results2[agent.id] = await agent.withdraw(state, chainId);
1555
+ }
1556
+ return { agentResult: results2 };
1557
+ }
1238
1558
  const results = {};
1559
+ let lastError;
1239
1560
  for (const agent of eligibleAgents) {
1240
- results[agent.id] = await agent.withdraw(state, chainId, amount);
1561
+ try {
1562
+ results[agent.id] = await agent.withdraw(state, chainId, amount);
1563
+ return { agentResult: results };
1564
+ } catch (err) {
1565
+ lastError = err;
1566
+ }
1241
1567
  }
1568
+ if (lastError)
1569
+ throw lastError;
1242
1570
  return {
1243
1571
  agentResult: results
1244
1572
  };
@@ -1250,17 +1578,50 @@ var OwneySDK = class {
1250
1578
  * @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
1251
1579
  */
1252
1580
  async getBalances(agentId) {
1581
+ const startedAt = Date.now();
1253
1582
  const state = this.requireState();
1254
1583
  const chainId = this.requireChainId();
1255
1584
  if (agentId) {
1256
- return this.getAgent(agentId).getBalances(state, chainId);
1585
+ const result = await this.getAgent(agentId).getBalances(state, chainId);
1586
+ logTiming3("getBalances single-agent", startedAt, { chainId, agentId });
1587
+ return result;
1257
1588
  }
1258
1589
  let totalBalance = 0;
1259
1590
  const results = {};
1260
- for (const [id, agent] of this.getActiveAgents()) {
1261
- results[id] = await agent.getBalances(state, chainId);
1262
- totalBalance = totalBalance + Number(results[id].totalBalance);
1591
+ const entries = [...this.getActiveAgents().entries()];
1592
+ const balanceResults = await Promise.allSettled(
1593
+ entries.map(async ([id, agent]) => {
1594
+ const agentStartedAt = Date.now();
1595
+ const b = await agent.getBalances(state, chainId);
1596
+ logTiming3("getBalances per-agent", agentStartedAt, {
1597
+ chainId,
1598
+ agentId: id
1599
+ });
1600
+ return [id, b];
1601
+ })
1602
+ );
1603
+ let successCount = 0;
1604
+ for (const settledResult of balanceResults) {
1605
+ if (settledResult.status === "fulfilled") {
1606
+ const [id, b] = settledResult.value;
1607
+ results[id] = b;
1608
+ totalBalance += Number(b.totalBalance);
1609
+ successCount += 1;
1610
+ continue;
1611
+ }
1612
+ console.error("getBalances agent failed:", settledResult.reason);
1613
+ }
1614
+ if (successCount === 0) {
1615
+ throw new OwneyError(
1616
+ "BALANCE_ALL_FAILED",
1617
+ "Failed to fetch balances for all active agents."
1618
+ );
1263
1619
  }
1620
+ logTiming3("getBalances aggregate", startedAt, {
1621
+ chainId,
1622
+ totalAgents: entries.length,
1623
+ successCount
1624
+ });
1264
1625
  return {
1265
1626
  totalBalance: String(totalBalance),
1266
1627
  agentBalances: results
@@ -1279,9 +1640,16 @@ var OwneySDK = class {
1279
1640
  }
1280
1641
  let totalEarnings = 0;
1281
1642
  const results = {};
1282
- for (const [id, agent] of this.getActiveAgents()) {
1283
- results[id] = await agent.getEarnings(state, chainId);
1284
- totalEarnings = totalEarnings + Number(results[id].lifetimeEarnings);
1643
+ const entries = [...this.getActiveAgents().entries()];
1644
+ const earningsResults = await Promise.all(
1645
+ entries.map(async ([id, agent]) => {
1646
+ const e = await agent.getEarnings(state, chainId);
1647
+ return [id, e];
1648
+ })
1649
+ );
1650
+ for (const [id, e] of earningsResults) {
1651
+ results[id] = e;
1652
+ totalEarnings += Number(e.lifetimeEarnings);
1285
1653
  }
1286
1654
  return {
1287
1655
  totalEarnings: String(totalEarnings),
@@ -1306,16 +1674,30 @@ var OwneySDK = class {
1306
1674
  return this.getAgent(agentId).getAccountApy(state, chainId, days);
1307
1675
  }
1308
1676
  const activeAgents = this.getActiveAgents();
1677
+ const entries = [...activeAgents.entries()];
1678
+ const [apyResults, balanceResults] = await Promise.all([
1679
+ Promise.all(
1680
+ entries.map(async ([id, agent]) => {
1681
+ const apy = await agent.getAccountApy(state, chainId, days);
1682
+ return [id, apy];
1683
+ })
1684
+ ),
1685
+ Promise.all(
1686
+ entries.map(async ([id, agent]) => {
1687
+ const b = await agent.getBalances(state, chainId);
1688
+ return [id, Number(b.totalBalance)];
1689
+ })
1690
+ )
1691
+ ]);
1309
1692
  const results = {};
1310
- for (const [id, agent] of activeAgents) {
1311
- results[id] = await agent.getAccountApy(state, chainId, days);
1693
+ for (const [id, apy] of apyResults) {
1694
+ results[id] = apy;
1312
1695
  }
1313
1696
  const balances = {};
1314
1697
  let totalBalance = 0;
1315
- for (const [id, agent] of activeAgents) {
1316
- const b = await agent.getBalances(state, chainId);
1317
- balances[id] = Number(b.totalBalance);
1318
- totalBalance += balances[id];
1698
+ for (const [id, bal] of balanceResults) {
1699
+ balances[id] = bal;
1700
+ totalBalance += bal;
1319
1701
  }
1320
1702
  let totalApy = 0;
1321
1703
  if (totalBalance > 0) {
@@ -1343,9 +1725,13 @@ var OwneySDK = class {
1343
1725
  if (agentId) {
1344
1726
  return this.getAgent(agentId).getHistory(state, chainId, filters);
1345
1727
  }
1728
+ const historyResults = await Promise.all(
1729
+ [...this.getActiveAgents().values()].map(
1730
+ (agent) => agent.getHistory(state, chainId, filters)
1731
+ )
1732
+ );
1346
1733
  let results = [];
1347
- for (const [, agent] of this.getActiveAgents()) {
1348
- const { data } = await agent.getHistory(state, chainId, filters);
1734
+ for (const { data } of historyResults) {
1349
1735
  results = [...results, ...data];
1350
1736
  }
1351
1737
  results.sort(
@@ -1389,8 +1775,15 @@ var OwneySDK = class {
1389
1775
  return this.getAgent(agentId).getUserProfile(state, chainId);
1390
1776
  }
1391
1777
  const results = {};
1392
- for (const [id, agent] of this.getActiveAgents()) {
1393
- results[id] = await agent.getUserProfile(state, chainId);
1778
+ const entries = [...this.getActiveAgents().entries()];
1779
+ const profileResults = await Promise.all(
1780
+ entries.map(async ([id, agent]) => {
1781
+ const p = await agent.getUserProfile(state, chainId);
1782
+ return [id, p];
1783
+ })
1784
+ );
1785
+ for (const [id, p] of profileResults) {
1786
+ results[id] = p;
1394
1787
  }
1395
1788
  return {
1396
1789
  agentUserProfile: results
@@ -1402,18 +1795,34 @@ var OwneySDK = class {
1402
1795
  * @param options - Contains agentId (optional) and days ("7D", "14D", or "30D")
1403
1796
  * @param options.agentId - Optional. Agent to query. Omit for all agents.
1404
1797
  * @param options.days - Lookback period: "7D", "14D", or "30D"
1798
+ * @param options.tokenSymbol - Optional asset symbol ("USDC" / "WETH") to
1799
+ * scope the APY to a specific asset+chain. Agents whose backends do not
1800
+ * support per-asset APY (currently Sail) ignore this.
1801
+ * @param options.chainId - Optional chain id for per-asset APY lookups.
1802
+ * Typically paired with `tokenSymbol`.
1405
1803
  * @returns {AgentApy} for a single agent, or {OwneyAgentApy} with per-agent APY
1406
1804
  */
1407
1805
  async getAgentApy({
1408
1806
  agentId,
1409
- days
1807
+ days,
1808
+ tokenSymbol,
1809
+ chainId
1410
1810
  }) {
1811
+ await this.ensureAgentsInitialized();
1812
+ const agentOptions = { tokenSymbol, chainId };
1411
1813
  if (agentId) {
1412
- return this.getAgent(agentId).getAgentApy(days);
1814
+ return this.getAgent(agentId).getAgentApy(days, agentOptions);
1413
1815
  }
1414
1816
  const results = {};
1415
- for (const [id, agent] of this.agents) {
1416
- results[id] = await agent.getAgentApy(days);
1817
+ const agentEntries = [...this.agents.entries()];
1818
+ const apyResults = await Promise.all(
1819
+ agentEntries.map(async ([id, agent]) => {
1820
+ const apy = await agent.getAgentApy(days, agentOptions);
1821
+ return [id, apy];
1822
+ })
1823
+ );
1824
+ for (const [id, apy] of apyResults) {
1825
+ results[id] = apy;
1417
1826
  }
1418
1827
  return {
1419
1828
  agentApy: results
@@ -1429,10 +1838,29 @@ var OwneySDK = class {
1429
1838
  const activeAgents = this.getActiveAgents();
1430
1839
  const balances = {};
1431
1840
  let totalBalance = 0;
1432
- for (const [id, agent] of activeAgents) {
1433
- const b = await agent.getBalances(state, chainId);
1434
- balances[id] = Number(b.totalBalance);
1435
- totalBalance += balances[id];
1841
+ const entries = [...activeAgents.entries()];
1842
+ const balanceResults = await Promise.allSettled(
1843
+ entries.map(async ([id, agent]) => {
1844
+ const b = await agent.getBalances(state, chainId);
1845
+ return [id, Number(b.totalBalance)];
1846
+ })
1847
+ );
1848
+ let successCount = 0;
1849
+ for (const settledResult of balanceResults) {
1850
+ if (settledResult.status === "fulfilled") {
1851
+ const [id, bal] = settledResult.value;
1852
+ balances[id] = bal;
1853
+ totalBalance += bal;
1854
+ successCount += 1;
1855
+ } else {
1856
+ console.error("getAgentAllocation agent failed:", settledResult.reason);
1857
+ }
1858
+ }
1859
+ if (successCount === 0) {
1860
+ throw new OwneyError(
1861
+ "ALLOCATION_ALL_FAILED",
1862
+ "Failed to fetch allocation: all agents failed."
1863
+ );
1436
1864
  }
1437
1865
  const allocation = {};
1438
1866
  if (totalBalance === 0) {
@@ -1461,5 +1889,6 @@ var OwneySDK = class {
1461
1889
  AgentChainIncompatibleError,
1462
1890
  AgentNotFoundError,
1463
1891
  NotConnectedError,
1892
+ OwneyError,
1464
1893
  OwneySDK
1465
1894
  });