@owney/sdk 0.2.6 → 0.2.8

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);
@@ -55,11 +56,58 @@ var SupportedAssets = [
55
56
  }
56
57
  ];
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
+ };
103
+
58
104
  // src/lib/utils.ts
59
105
  var isValidChainId = (chainId) => {
60
106
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
61
- throw new Error(
62
- `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] }
63
111
  );
64
112
  }
65
113
  return chainId;
@@ -74,6 +122,15 @@ function hexToDecimal(hex, decimals = 6) {
74
122
  const remainder = parsed % divisor;
75
123
  return Number(whole) + Number(remainder) / Number(divisor);
76
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
+ }
77
134
  var DayFilterMapping = {
78
135
  "30D": 30,
79
136
  "14D": 14,
@@ -103,7 +160,7 @@ var CHAIN_NAME_TO_ID = {
103
160
  BASE: 8453,
104
161
  ARBITRUM: 42161
105
162
  };
106
- function mapBalances(raw, chainId) {
163
+ function mapBalances(raw, chainId, smartWallet) {
107
164
  const portfolio = raw.portfolio;
108
165
  const tokenMap = /* @__PURE__ */ new Map();
109
166
  for (const p of portfolio.positions || []) {
@@ -114,18 +171,7 @@ function mapBalances(raw, chainId) {
114
171
  tokenMap.set(key, {
115
172
  symbol,
116
173
  chainId: positionChainId,
117
- amount: (current?.amount ?? 0) + hexToDecimal(p.amount || "0")
118
- });
119
- }
120
- for (const s of portfolio.staleBalances || []) {
121
- const symbol = s.tokenSymbol || "USDC";
122
- const staleChainId = s.chainId ?? chainId;
123
- const key = `${symbol}:${staleChainId}`;
124
- const current = tokenMap.get(key);
125
- tokenMap.set(key, {
126
- symbol,
127
- chainId: staleChainId,
128
- amount: (current?.amount ?? 0) + hexToDecimal(s.balance || "0")
174
+ amount: (current?.amount ?? 0) + normalizeDecimalPlace(p.underlyingAmount || "0")
129
175
  });
130
176
  }
131
177
  let totalBalance = 0;
@@ -140,7 +186,7 @@ function mapBalances(raw, chainId) {
140
186
  });
141
187
  }
142
188
  return {
143
- smartWallet: portfolio.smartWallet,
189
+ smartWallet,
144
190
  totalBalance: String(totalBalance),
145
191
  tokens
146
192
  };
@@ -257,15 +303,6 @@ function mapApyByStrategy(raw) {
257
303
  }
258
304
 
259
305
  // src/agents/zyfai/zyfai.agent.ts
260
- var ZYFAI_TIMING_PREFIX = "[AgentTiming][zyfai]";
261
- function logTiming(label, startedAt, meta) {
262
- const elapsedMs = Date.now() - startedAt;
263
- if (meta) {
264
- console.log(`${ZYFAI_TIMING_PREFIX} ${label} ${elapsedMs}ms`, meta);
265
- return;
266
- }
267
- console.log(`${ZYFAI_TIMING_PREFIX} ${label} ${elapsedMs}ms`);
268
- }
269
306
  var ZyfaiAgent = class {
270
307
  id = "zyfai";
271
308
  supportedChainIds = [8453, 42161];
@@ -286,29 +323,27 @@ var ZyfaiAgent = class {
286
323
  connectedChainId = null;
287
324
  smartWalletCache = /* @__PURE__ */ new Map();
288
325
  connectAuthPromise = null;
289
- constructor(apiKey) {
290
- this.sdk = new import_sdk.ZyfaiSDK({ apiKey });
326
+ constructor(apiKey, rpcUrls) {
327
+ this.sdk = new import_sdk.ZyfaiSDK({ apiKey, rpcUrls });
291
328
  }
292
329
  // --- Private helpers ---
293
330
  getAddress() {
294
331
  if (!this.connectedAddress) {
295
- throw new Error("Wallet not connected. Call sdk.connect() first.");
332
+ throw new OwneyError(
333
+ "NOT_CONNECTED",
334
+ "Wallet not connected. Call sdk.connect() first."
335
+ );
296
336
  }
297
337
  return this.connectedAddress;
298
338
  }
299
339
  /** Light connect: SIWE auth only. Re-auths if chain changed. */
300
340
  async connectAuth(state, chainId) {
301
- const startedAt = Date.now();
302
341
  const validChainId = isValidChainId(chainId);
303
342
  if (this.connectedAddress && this.connectedChainId === validChainId) {
304
- logTiming("connectAuth cache-hit", startedAt, { chainId: validChainId });
305
343
  return;
306
344
  }
307
345
  if (this.connectAuthPromise) {
308
346
  await this.connectAuthPromise;
309
- logTiming("connectAuth awaited-inflight", startedAt, {
310
- chainId: validChainId
311
- });
312
347
  return;
313
348
  }
314
349
  this.connectAuthPromise = (async () => {
@@ -321,69 +356,40 @@ var ZyfaiAgent = class {
321
356
  })();
322
357
  try {
323
358
  await this.connectAuthPromise;
324
- logTiming("connectAuth success", startedAt, { chainId: validChainId });
325
359
  } finally {
326
360
  this.connectAuthPromise = null;
327
361
  }
328
362
  }
329
363
  /** Full connect: SIWE auth + Safe deploy check + session key. */
330
364
  async connectFull(state, chainId) {
331
- const startedAt = Date.now();
332
365
  await this.connectAuth(state, chainId);
333
366
  const validChainId = isValidChainId(chainId);
334
367
  const address = this.getAddress();
335
- const walletLookupStart = Date.now();
336
368
  const wallet = await this.sdk.getSmartWalletAddress(address, validChainId);
337
- logTiming("connectFull getSmartWalletAddress", walletLookupStart, {
338
- chainId: validChainId
339
- });
340
369
  this.smartWalletCache.set(`${address}_${validChainId}`, wallet.address);
341
370
  if (!wallet.isDeployed) {
342
- const deployStart = Date.now();
343
371
  await this.sdk.deploySafe(address, validChainId, "aggressive", true);
344
- logTiming("connectFull deploySafe", deployStart, {
345
- chainId: validChainId
346
- });
347
372
  }
348
- const userDetailsStart = Date.now();
349
373
  const user = await this.sdk.getUserDetails();
350
- logTiming("connectFull getUserDetails", userDetailsStart, {
351
- chainId: validChainId
352
- });
353
374
  if (!user.hasActiveSessionKey) {
354
- const sessionKeyStart = Date.now();
355
375
  await this.sdk.createSessionKey(address, validChainId);
356
- logTiming("connectFull createSessionKey", sessionKeyStart, {
357
- chainId: validChainId
358
- });
359
376
  }
360
- logTiming("connectFull success", startedAt, { chainId: validChainId });
361
377
  }
362
378
  /** Connect + resolve smart wallet address for read operations. */
363
379
  async resolveSmartWallet(state, chainId) {
364
- const startedAt = Date.now();
365
380
  await this.connectAuth(state, chainId);
366
381
  const validChainId = isValidChainId(chainId);
367
382
  const ownerAddress = this.getAddress();
368
383
  const cacheKey = `${ownerAddress}_${validChainId}`;
369
384
  let smartWallet = this.smartWalletCache.get(cacheKey);
370
385
  if (!smartWallet) {
371
- const lookupStart = Date.now();
372
386
  const { address } = await this.sdk.getSmartWalletAddress(
373
387
  ownerAddress,
374
388
  validChainId
375
389
  );
376
- logTiming("resolveSmartWallet remote-lookup", lookupStart, {
377
- chainId: validChainId
378
- });
379
390
  smartWallet = address;
380
391
  this.smartWalletCache.set(cacheKey, smartWallet);
381
- } else {
382
- logTiming("resolveSmartWallet cache-hit", startedAt, {
383
- chainId: validChainId
384
- });
385
392
  }
386
- logTiming("resolveSmartWallet ready", startedAt, { chainId: validChainId });
387
393
  return { smartWallet, chainId: validChainId };
388
394
  }
389
395
  // --- IAgent: Connection lifecycle ---
@@ -443,16 +449,9 @@ var ZyfaiAgent = class {
443
449
  }
444
450
  // --- IAgent: Portfolio reads ---
445
451
  async getBalances(state, chainId) {
446
- const startedAt = Date.now();
447
- await this.connectAuth(state, chainId);
448
- const validChainId = isValidChainId(chainId);
449
- const positionsStart = Date.now();
450
- const raw = await this.sdk.getPositions(this.getAddress(), validChainId);
451
- logTiming("getBalances getPositions", positionsStart, {
452
- chainId: validChainId
453
- });
454
- logTiming("getBalances success", startedAt, { chainId: validChainId });
455
- return mapBalances(raw, validChainId);
452
+ const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
453
+ const raw = await this.sdk.getPortfolio(this.getAddress());
454
+ return mapBalances(raw, validChainId, smartWallet);
456
455
  }
457
456
  async getEarnings(state, chainId) {
458
457
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -516,13 +515,13 @@ function mapWithdraw2(raw) {
516
515
  function mapPortfolioBalances(raw, smartWallet) {
517
516
  let total = 0;
518
517
  const tokens = [];
519
- for (const item of raw.result) {
520
- total += item.usd_value;
518
+ for (const item of raw.tokens) {
519
+ total += item.balanceFormatted;
521
520
  tokens.push({
522
- chain: item.chain.toUpperCase(),
523
- chainId: item.chain_id,
524
- asset: item.token === "USD\u20AE0" ? "USDT" : item.token,
525
- amount: String(item.usd_value)
521
+ chain: SUPPORTED_CHAINS[item.chain],
522
+ chainId: item.chain,
523
+ asset: item.symbol === "USD\u20AE0" ? "USDT" : item.symbol,
524
+ amount: item.balance
526
525
  });
527
526
  }
528
527
  return {
@@ -705,16 +704,7 @@ function remapApyPerAssetToSymbols(raw) {
705
704
  var SAIL_API_BASE = "https://app.sail.money";
706
705
  var PROJECT_ID = "sail";
707
706
  var PAGE_ID = "home";
708
- var SAIL_TIMING_PREFIX = "[AgentTiming][sail]";
709
707
  var SAIL_PORTFOLIO_TIMEOUT_MS = 8e3;
710
- function logTiming2(label, startedAt, meta) {
711
- const elapsedMs = Date.now() - startedAt;
712
- if (meta) {
713
- console.log(`${SAIL_TIMING_PREFIX} ${label} ${elapsedMs}ms`, meta);
714
- return;
715
- }
716
- console.log(`${SAIL_TIMING_PREFIX} ${label} ${elapsedMs}ms`);
717
- }
718
708
  var SailAgent = class {
719
709
  id = "sail";
720
710
  supportedChainIds = [8453, 42161];
@@ -764,7 +754,11 @@ var SailAgent = class {
764
754
  });
765
755
  } catch (error) {
766
756
  if (error instanceof Error && error.name === "AbortError" && timeoutMs) {
767
- throw new Error(`Sail API timeout ${timeoutMs}ms: ${method} ${path}`);
757
+ throw new OwneyError(
758
+ "API_SAIL_TIMEOUT",
759
+ `Sail API timeout ${timeoutMs}ms: ${method} ${path}`,
760
+ { timeoutMs, method, path }
761
+ );
768
762
  }
769
763
  throw error;
770
764
  } finally {
@@ -773,13 +767,16 @@ var SailAgent = class {
773
767
  }
774
768
  if (!res.ok) {
775
769
  const text = await res.text().catch(() => "");
776
- throw new Error(`Sail API error ${res.status}: ${text}`);
770
+ throw new OwneyError(
771
+ "API_SAIL_ERROR",
772
+ `Sail API error ${res.status}: ${text}`,
773
+ { statusCode: res.status, method, path, responseBody: text }
774
+ );
777
775
  }
778
776
  return res.json();
779
777
  }
780
778
  /** Initiate SIWE and sign with the user's wallet. */
781
779
  async signSiwe(state, chainId) {
782
- const startedAt = Date.now();
783
780
  const initiate = await this.request(
784
781
  "POST",
785
782
  `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/auth/initiate`,
@@ -794,7 +791,6 @@ var SailAgent = class {
794
791
  method: "personal_sign",
795
792
  params: [initiate.message, state.walletAddress]
796
793
  });
797
- logTiming2("signSiwe success", startedAt, { chainId });
798
794
  return {
799
795
  sessionId: initiate.sessionId,
800
796
  message: initiate.message,
@@ -803,18 +799,18 @@ var SailAgent = class {
803
799
  }
804
800
  /** Full connect: auth handles smart account creation via SIWE flow. */
805
801
  async connectFull(state, chainId) {
806
- const startedAt = Date.now();
807
802
  if (this.connectedAddress && this.connectedChainId === chainId) {
808
- logTiming2("connectFull cache-hit", startedAt, { chainId });
809
803
  return;
810
804
  }
811
805
  if (this.connectPromise) {
812
806
  await this.connectPromise;
813
- logTiming2("connectFull awaited-inflight", startedAt, { chainId });
814
807
  return;
815
808
  }
816
809
  if (!state.walletAddress) {
817
- throw new Error("Wallet address is required");
810
+ throw new OwneyError(
811
+ "WALLET_ADDRESS_REQUIRED",
812
+ "Wallet address is required. Call sdk.connect(provider) first."
813
+ );
818
814
  }
819
815
  this.connectPromise = (async () => {
820
816
  const { sessionId, signature, message } = await this.signSiwe(
@@ -840,19 +836,19 @@ var SailAgent = class {
840
836
  })();
841
837
  try {
842
838
  await this.connectPromise;
843
- logTiming2("connectFull success", startedAt, { chainId });
844
839
  } finally {
845
840
  this.connectPromise = null;
846
841
  }
847
842
  }
848
843
  async resolveSmartWallet(state, chainId) {
849
- const startedAt = Date.now();
850
844
  await this.connectFull(state, chainId);
851
845
  const validChainId = isValidChainId(chainId);
852
846
  if (!this.smartWalletAddress) {
853
- throw new Error("Wallet not connected. Call sdk.connect() first.");
847
+ throw new OwneyError(
848
+ "NOT_CONNECTED",
849
+ "Wallet not connected. Call sdk.connect() first."
850
+ );
854
851
  }
855
- logTiming2("resolveSmartWallet ready", startedAt, { chainId: validChainId });
856
852
  return {
857
853
  smartWallet: this.smartWalletAddress,
858
854
  chainId: validChainId
@@ -877,7 +873,8 @@ var SailAgent = class {
877
873
  }
878
874
  async deposit(state, chainId, amount, depositCallback) {
879
875
  if (!depositCallback) {
880
- throw new Error(
876
+ throw new OwneyError(
877
+ "DEPOSIT_CALLBACK_REQUIRED",
881
878
  "depositCallback is required for Sail. Provide a callback that transfers tokens and returns the transaction hash."
882
879
  );
883
880
  }
@@ -886,12 +883,16 @@ var SailAgent = class {
886
883
  const depositInfo = await this.getDepositInfo();
887
884
  const resolvedTokenAddress = depositInfo.permittedTokens[0]?.address;
888
885
  if (!resolvedTokenAddress) {
889
- throw new Error("No permitted tokens available for deposit.");
886
+ throw new OwneyError(
887
+ "DEPOSIT_NO_PERMITTED_TOKENS",
888
+ "No permitted tokens available for deposit."
889
+ );
890
890
  }
891
891
  await this.executePreHooks(resolvedTokenAddress, amount, chainId);
892
892
  const txHash = await depositCallback(smartWallet, chainId, amount);
893
893
  if (!txHash) {
894
- throw new Error(
894
+ throw new OwneyError(
895
+ "DEPOSIT_CALLBACK_INVALID",
895
896
  "depositCallback must return a transaction hash (txHash) for Sail deposits."
896
897
  );
897
898
  }
@@ -945,7 +946,10 @@ var SailAgent = class {
945
946
  const withdrawInfo = await this.getWithdrawInfo();
946
947
  const tokenAddress = withdrawInfo.permittedTokens[0]?.address;
947
948
  if (!tokenAddress) {
948
- throw new Error("No permitted tokens available for withdrawal.");
949
+ throw new OwneyError(
950
+ "WITHDRAW_NO_PERMITTED_TOKENS",
951
+ "No permitted tokens available for withdrawal."
952
+ );
949
953
  }
950
954
  const resolvedAmount = amount ?? withdrawInfo.currentBalance;
951
955
  const { signature: adminWalletSignature, message: adminWalletPayload } = await this.signSiwe(state, chainId);
@@ -969,25 +973,20 @@ var SailAgent = class {
969
973
  }
970
974
  // --- IAgent: Portfolio reads ---
971
975
  async getBalances(state, chainId) {
972
- const startedAt = Date.now();
973
976
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
974
- const portfolioStart = Date.now();
975
977
  try {
976
- const raw = await this.getMetricsPortfolio(state, chainId);
977
- logTiming2("getBalances getMetricsPortfolio", portfolioStart, { chainId });
978
- logTiming2("getBalances success", startedAt, { chainId });
978
+ const raw = await this.getPortfolio();
979
979
  return mapPortfolioBalances(raw, smartWallet);
980
980
  } catch (error) {
981
- if (error instanceof Error && error.message.startsWith("Sail API timeout")) {
981
+ if (error instanceof OwneyError && error.code === "API_SAIL_TIMEOUT") {
982
982
  console.warn(
983
983
  "Sail portfolio request timed out; using empty snapshot.",
984
984
  {
985
985
  chainId
986
986
  }
987
987
  );
988
- logTiming2("getBalances timeout-fallback", startedAt, { chainId });
989
988
  return mapPortfolioBalances(
990
- { message: "timeout", result: [] },
989
+ { message: "timeout", tokens: [] },
991
990
  smartWallet
992
991
  );
993
992
  }
@@ -1018,6 +1017,14 @@ var SailAgent = class {
1018
1017
  SAIL_PORTFOLIO_TIMEOUT_MS
1019
1018
  );
1020
1019
  }
1020
+ async getPortfolio() {
1021
+ return this.request(
1022
+ "GET",
1023
+ `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/portfolio/tokens`,
1024
+ void 0,
1025
+ SAIL_PORTFOLIO_TIMEOUT_MS
1026
+ );
1027
+ }
1021
1028
  async getMetricsEarnings(state, chainId) {
1022
1029
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1023
1030
  return this.request(
@@ -1025,7 +1032,7 @@ var SailAgent = class {
1025
1032
  `/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/earnings?smart_account=${smartWallet}`
1026
1033
  );
1027
1034
  }
1028
- async getMetricsBalance(state, chainId, currency) {
1035
+ async getMetricsBalance(state, chainId, currency = "usd") {
1029
1036
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1030
1037
  return this.request(
1031
1038
  "GET",
@@ -1062,8 +1069,10 @@ var SailAgent = class {
1062
1069
  }
1063
1070
  const dayCount = DayFilterMapping[days];
1064
1071
  if (dayCount === void 0) {
1065
- throw new Error(
1066
- `Invalid days value: "${days}". Expected one of: "7D", "14D", "30D".`
1072
+ throw new OwneyError(
1073
+ "VALIDATION_INVALID_DAYS",
1074
+ `Invalid days value: "${days}". Expected one of: "7D", "14D", "30D".`,
1075
+ { days }
1067
1076
  );
1068
1077
  }
1069
1078
  const now = /* @__PURE__ */ new Date();
@@ -1140,37 +1149,6 @@ var SailAgent = class {
1140
1149
  }
1141
1150
  };
1142
1151
 
1143
- // src/errors.ts
1144
- var AgentNotFoundError = class extends Error {
1145
- constructor(agentId, available) {
1146
- super(
1147
- `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`
1148
- );
1149
- this.name = "AgentNotFoundError";
1150
- }
1151
- };
1152
- var NotConnectedError = class extends Error {
1153
- constructor() {
1154
- super("Not connected. Call sdk.connect(provider) first.");
1155
- this.name = "NotConnectedError";
1156
- }
1157
- };
1158
- var AgentChainIncompatibleError = class extends Error {
1159
- incompatibleAgents;
1160
- connectedChainId;
1161
- constructor(incompatibleAgents, connectedChainId) {
1162
- const details = incompatibleAgents.map(
1163
- ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
1164
- ).join("; ");
1165
- super(
1166
- `Chain ${connectedChainId} is not supported by the following agents: ${details}`
1167
- );
1168
- this.name = "AgentChainIncompatibleError";
1169
- this.incompatibleAgents = incompatibleAgents;
1170
- this.connectedChainId = connectedChainId;
1171
- }
1172
- };
1173
-
1174
1152
  // src/lib/routing-api.ts
1175
1153
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1176
1154
  async function fetchAgentKeys(apiKey) {
@@ -1184,37 +1162,34 @@ async function fetchAgentKeys(apiKey) {
1184
1162
  });
1185
1163
  if (!res.ok) {
1186
1164
  const text = await res.text().catch(() => "");
1187
- throw new Error(`Routing API error ${res.status}: ${text}`);
1165
+ throw new OwneyError(
1166
+ "API_ROUTING_ERROR",
1167
+ `Routing API error ${res.status}: ${text}`,
1168
+ { statusCode: res.status, responseBody: text }
1169
+ );
1188
1170
  }
1189
1171
  const json = await res.json();
1190
1172
  if (!json.success) {
1191
- throw new Error(`Routing API request failed: ${json.message}`);
1173
+ throw new OwneyError(
1174
+ "API_ROUTING_FAILED",
1175
+ `Routing API request failed: ${json.message}`,
1176
+ { message: json.message }
1177
+ );
1192
1178
  }
1193
1179
  return json.data;
1194
1180
  }
1195
1181
 
1196
1182
  // src/client.ts
1197
- var AGENT_CONSTRUCTORS = {
1198
- zyfai: ZyfaiAgent,
1199
- sail: SailAgent
1200
- };
1201
- var SDK_TIMING_PREFIX = "[OwneySDKTiming]";
1202
- function logTiming3(label, startedAt, meta) {
1203
- const elapsedMs = Date.now() - startedAt;
1204
- if (meta) {
1205
- console.log(`${SDK_TIMING_PREFIX} ${label} ${elapsedMs}ms`, meta);
1206
- return;
1207
- }
1208
- console.log(`${SDK_TIMING_PREFIX} ${label} ${elapsedMs}ms`);
1209
- }
1210
1183
  var OwneySDK = class {
1211
1184
  agents = /* @__PURE__ */ new Map();
1212
1185
  activeAgents = /* @__PURE__ */ new Set();
1213
1186
  state = null;
1214
1187
  apiKey;
1188
+ zyfaiRpcUrls;
1215
1189
  initializingAgentsPromise = null;
1216
1190
  constructor(config) {
1217
1191
  this.apiKey = config.apiKey;
1192
+ this.zyfaiRpcUrls = config.zyfaiRpcUrls;
1218
1193
  }
1219
1194
  /**
1220
1195
  * Establish connection state for all subsequent SDK calls.
@@ -1228,7 +1203,8 @@ var OwneySDK = class {
1228
1203
  });
1229
1204
  const walletAddress = accounts[0];
1230
1205
  if (!walletAddress) {
1231
- throw new Error(
1206
+ throw new OwneyError(
1207
+ "WALLET_NO_ACCOUNTS",
1232
1208
  "No accounts found. Ensure the wallet is unlocked and connected."
1233
1209
  );
1234
1210
  }
@@ -1267,7 +1243,8 @@ var OwneySDK = class {
1267
1243
  requireChainId() {
1268
1244
  const chainId = this.state?.chainId ?? null;
1269
1245
  if (chainId === null) {
1270
- throw new Error(
1246
+ throw new OwneyError(
1247
+ "NO_ACTIVE_CHAIN",
1271
1248
  "No active chain. Call sdk.activateAgent(chainId) first."
1272
1249
  );
1273
1250
  }
@@ -1296,14 +1273,14 @@ var OwneySDK = class {
1296
1273
  this.initializingAgentsPromise = (async () => {
1297
1274
  const agentKeys = await fetchAgentKeys(this.apiKey);
1298
1275
  for (const { key, agent_type } of agentKeys) {
1299
- const AgentClass = AGENT_CONSTRUCTORS[agent_type];
1300
- if (!AgentClass)
1276
+ const agent = this.createAgent(agent_type, key);
1277
+ if (!agent)
1301
1278
  continue;
1302
- const agent = new AgentClass(key);
1303
1279
  this.agents.set(agent_type, agent);
1304
1280
  }
1305
1281
  if (this.agents.size === 0) {
1306
- throw new Error(
1282
+ throw new OwneyError(
1283
+ "API_NO_AGENTS",
1307
1284
  "No supported agents returned by the routing API. Check your API key and routing configuration."
1308
1285
  );
1309
1286
  }
@@ -1315,6 +1292,15 @@ var OwneySDK = class {
1315
1292
  this.initializingAgentsPromise = null;
1316
1293
  }
1317
1294
  }
1295
+ createAgent(agentId, key) {
1296
+ if (agentId === "zyfai") {
1297
+ return new ZyfaiAgent(key, this.zyfaiRpcUrls);
1298
+ }
1299
+ if (agentId === "sail") {
1300
+ return new SailAgent(key);
1301
+ }
1302
+ return null;
1303
+ }
1318
1304
  // --- Account lifecycle ---
1319
1305
  /**
1320
1306
  * Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
@@ -1327,12 +1313,12 @@ var OwneySDK = class {
1327
1313
  * throws before activating any agent.
1328
1314
  */
1329
1315
  async activateAgent(chainId, agentId) {
1330
- const startedAt = Date.now();
1331
1316
  const state = this.requireState();
1332
1317
  await this.ensureAgentsInitialized();
1333
1318
  if (agentId !== void 0) {
1334
1319
  if (agentId.length === 0) {
1335
- throw new Error(
1320
+ throw new OwneyError(
1321
+ "AGENT_EMPTY_LIST",
1336
1322
  "agentId array must not be empty. Omit the argument to activate all compatible agents."
1337
1323
  );
1338
1324
  }
@@ -1354,27 +1340,22 @@ var OwneySDK = class {
1354
1340
  state.chainId = chainId;
1355
1341
  Promise.all(
1356
1342
  agents.map(async (agent) => {
1357
- const agentStart = Date.now();
1358
1343
  await agent.activateAgent(state, chainId);
1359
- logTiming3("activateAgent background-agent-ready", agentStart, {
1360
- chainId,
1361
- agentId: agent.id
1362
- });
1363
1344
  })
1364
1345
  ).catch((error) => {
1365
1346
  console.error("activateAgent background init failed:", error);
1366
1347
  });
1367
- logTiming3("activateAgent scheduled", startedAt, {
1368
- chainId,
1369
- agentIds: uniqueIds
1370
- });
1371
1348
  return;
1372
1349
  }
1373
1350
  const compatible = [...this.agents.values()].filter(
1374
1351
  (agent) => agent.supportedChainIds.includes(chainId)
1375
1352
  );
1376
1353
  if (compatible.length === 0) {
1377
- throw new Error(`No agents support chain ${chainId}.`);
1354
+ throw new OwneyError(
1355
+ "CHAIN_NO_COMPATIBLE_AGENTS",
1356
+ `No agents support chain ${chainId}.`,
1357
+ { chainId }
1358
+ );
1378
1359
  }
1379
1360
  this.activeAgents.clear();
1380
1361
  for (const agent of compatible) {
@@ -1383,20 +1364,11 @@ var OwneySDK = class {
1383
1364
  state.chainId = chainId;
1384
1365
  Promise.all(
1385
1366
  compatible.map(async (agent) => {
1386
- const agentStart = Date.now();
1387
1367
  await agent.activateAgent(state, chainId);
1388
- logTiming3("activateAgent background-agent-ready", agentStart, {
1389
- chainId,
1390
- agentId: agent.id
1391
- });
1392
1368
  })
1393
1369
  ).catch((error) => {
1394
1370
  console.error("activateAgent background init failed:", error);
1395
1371
  });
1396
- logTiming3("activateAgent scheduled", startedAt, {
1397
- chainId,
1398
- agentIds: compatible.map((agent) => agent.id)
1399
- });
1400
1372
  }
1401
1373
  /**
1402
1374
  * Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
@@ -1422,8 +1394,10 @@ var OwneySDK = class {
1422
1394
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
1423
1395
  const totalAmount = BigInt(amount);
1424
1396
  if (totalAmount < BigInt(eligibleAgents.length)) {
1425
- throw new Error(
1426
- `Amount "${amount}" is too small to split across ${eligibleAgents.length} agents.`
1397
+ throw new OwneyError(
1398
+ "DEPOSIT_AMOUNT_TOO_SMALL",
1399
+ `Amount "${amount}" is too small to split across ${eligibleAgents.length} agents.`,
1400
+ { amount, agentCount: eligibleAgents.length }
1427
1401
  );
1428
1402
  }
1429
1403
  const perAgent = totalAmount / BigInt(eligibleAgents.length);
@@ -1446,8 +1420,10 @@ var OwneySDK = class {
1446
1420
  (sa) => sa.chainId === chainId
1447
1421
  );
1448
1422
  if (!chainAssets || !chainAssets.assets.includes(asset)) {
1449
- throw new Error(
1450
- `Agent "${agent.id}" does not support asset "${asset}" on chain ${chainId}.`
1423
+ throw new OwneyError(
1424
+ "ASSET_UNSUPPORTED",
1425
+ `Agent "${agent.id}" does not support asset "${asset}" on chain ${chainId}.`,
1426
+ { agentId: agent.id, asset, chainId }
1451
1427
  );
1452
1428
  }
1453
1429
  }
@@ -1459,8 +1435,10 @@ var OwneySDK = class {
1459
1435
  return chainAssets && chainAssets.assets.includes(asset);
1460
1436
  });
1461
1437
  if (eligible.length === 0) {
1462
- throw new Error(
1463
- `No agents support asset "${asset}" on chain ${chainId}.`
1438
+ throw new OwneyError(
1439
+ "ASSET_NO_COMPATIBLE_AGENTS",
1440
+ `No agents support asset "${asset}" on chain ${chainId}.`,
1441
+ { asset, chainId }
1464
1442
  );
1465
1443
  }
1466
1444
  return eligible;
@@ -1485,10 +1463,25 @@ var OwneySDK = class {
1485
1463
  return agent.withdraw(state, chainId, amount);
1486
1464
  }
1487
1465
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
1466
+ if (!amount) {
1467
+ const results2 = {};
1468
+ for (const agent of eligibleAgents) {
1469
+ results2[agent.id] = await agent.withdraw(state, chainId);
1470
+ }
1471
+ return { agentResult: results2 };
1472
+ }
1488
1473
  const results = {};
1474
+ let lastError;
1489
1475
  for (const agent of eligibleAgents) {
1490
- results[agent.id] = await agent.withdraw(state, chainId, amount);
1476
+ try {
1477
+ results[agent.id] = await agent.withdraw(state, chainId, amount);
1478
+ return { agentResult: results };
1479
+ } catch (err) {
1480
+ lastError = err;
1481
+ }
1491
1482
  }
1483
+ if (lastError)
1484
+ throw lastError;
1492
1485
  return {
1493
1486
  agentResult: results
1494
1487
  };
@@ -1500,12 +1493,10 @@ var OwneySDK = class {
1500
1493
  * @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
1501
1494
  */
1502
1495
  async getBalances(agentId) {
1503
- const startedAt = Date.now();
1504
1496
  const state = this.requireState();
1505
1497
  const chainId = this.requireChainId();
1506
1498
  if (agentId) {
1507
1499
  const result = await this.getAgent(agentId).getBalances(state, chainId);
1508
- logTiming3("getBalances single-agent", startedAt, { chainId, agentId });
1509
1500
  return result;
1510
1501
  }
1511
1502
  let totalBalance = 0;
@@ -1513,12 +1504,7 @@ var OwneySDK = class {
1513
1504
  const entries = [...this.getActiveAgents().entries()];
1514
1505
  const balanceResults = await Promise.allSettled(
1515
1506
  entries.map(async ([id, agent]) => {
1516
- const agentStartedAt = Date.now();
1517
1507
  const b = await agent.getBalances(state, chainId);
1518
- logTiming3("getBalances per-agent", agentStartedAt, {
1519
- chainId,
1520
- agentId: id
1521
- });
1522
1508
  return [id, b];
1523
1509
  })
1524
1510
  );
@@ -1534,13 +1520,11 @@ var OwneySDK = class {
1534
1520
  console.error("getBalances agent failed:", settledResult.reason);
1535
1521
  }
1536
1522
  if (successCount === 0) {
1537
- throw new Error("Failed to fetch balances for all active agents.");
1523
+ throw new OwneyError(
1524
+ "BALANCE_ALL_FAILED",
1525
+ "Failed to fetch balances for all active agents."
1526
+ );
1538
1527
  }
1539
- logTiming3("getBalances aggregate", startedAt, {
1540
- chainId,
1541
- totalAgents: entries.length,
1542
- successCount
1543
- });
1544
1528
  return {
1545
1529
  totalBalance: String(totalBalance),
1546
1530
  agentBalances: results
@@ -1776,7 +1760,10 @@ var OwneySDK = class {
1776
1760
  }
1777
1761
  }
1778
1762
  if (successCount === 0) {
1779
- throw new Error("Failed to fetch allocation: all agents failed.");
1763
+ throw new OwneyError(
1764
+ "ALLOCATION_ALL_FAILED",
1765
+ "Failed to fetch allocation: all agents failed."
1766
+ );
1780
1767
  }
1781
1768
  const allocation = {};
1782
1769
  if (totalBalance === 0) {
@@ -1805,5 +1792,6 @@ var OwneySDK = class {
1805
1792
  AgentChainIncompatibleError,
1806
1793
  AgentNotFoundError,
1807
1794
  NotConnectedError,
1795
+ OwneyError,
1808
1796
  OwneySDK
1809
1797
  });