@zyfai/sdk 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -100,16 +100,23 @@ const walletInfo = await sdk.getSmartWalletAddress(userAddress, 42161);
100
100
  console.log("Safe Address:", walletInfo.address);
101
101
  console.log("Is Deployed:", walletInfo.isDeployed);
102
102
 
103
- // Deploy the Safe
103
+ // Deploy the Safe (automatically checks if already deployed)
104
104
  const result = await sdk.deploySafe(userAddress, 42161);
105
105
 
106
106
  if (result.success) {
107
107
  console.log("Safe Address:", result.safeAddress);
108
108
  console.log("Status:", result.status); // 'deployed' | 'failed'
109
- console.log("Transaction Hash:", result.txHash);
109
+
110
+ if (result.alreadyDeployed) {
111
+ console.log("Safe was already deployed - no action needed");
112
+ } else {
113
+ console.log("Transaction Hash:", result.txHash);
114
+ }
110
115
  }
111
116
  ```
112
117
 
118
+ **Note:** The SDK proactively checks if the Safe is already deployed before attempting deployment. If it exists, it returns `alreadyDeployed: true` without making any transactions.
119
+
113
120
  ### 2. Multi-Chain Support
114
121
 
115
122
  The SDK supports the following chains:
@@ -229,25 +236,31 @@ The SDK automatically fetches optimal session configuration from ZyFAI API:
229
236
  ```typescript
230
237
  // SDK automatically:
231
238
  // 1. Authenticates via SIWE (creates user record if needed)
232
- // 2. Calculates the deterministic Safe address
233
- // 3. Resolves the userId via /users/by-smart-wallet
239
+ // 2. Checks if user already has an active session key (returns early if so)
240
+ // 3. Calculates the deterministic Safe address
234
241
  // 4. Retrieves personalized config via /session-keys/config
235
242
  // 5. Signs the session key
236
243
  // 6. Calls /session-keys/add so the session becomes active immediately
237
244
 
238
245
  const result = await sdk.createSessionKey(userAddress, 42161);
239
246
 
240
- console.log("Session created:", result.signature);
241
- console.log("Safe address:", result.sessionKeyAddress);
247
+ // Check if session key already existed
248
+ if (result.alreadyActive) {
249
+ console.log("Session key already active:", result.message);
250
+ } else {
251
+ console.log("Session created:", result.signature);
252
+ console.log("Safe address:", result.sessionKeyAddress);
253
+ console.log("Activation ID:", result.sessionActivation?.id);
254
+ }
242
255
  console.log("User ID:", result.userId);
243
- console.log("Activation ID:", result.sessionActivation?.id);
244
256
  ```
245
257
 
246
258
  **Important**:
247
259
 
248
260
  - `createSessionKey` requires SIWE authentication (prompts wallet signature on first call)
261
+ - The SDK proactively checks if the user already has an active session key (from login response) and returns early without requiring any signature if one exists
249
262
  - The user record must have `smartWallet` and `chainId` set (automatically handled after calling `deploySafe` or `updateUserProfile`)
250
- - The SDK now auto-calls `/users/by-smart-wallet`, `/session-keys/config`, and `/session-keys/add`, so the returned payload already includes the `userId` and the activation record (`sessionActivation`)—no additional API calls are required on your side.
263
+ - When `alreadyActive` is `true`, `sessionKeyAddress` and `signature` are not available in the response
251
264
 
252
265
  ### 4. Deposit Funds
253
266
 
@@ -713,6 +726,24 @@ Make sure to call `connectAccount()` before calling other methods that require s
713
726
 
714
727
  Check that the chain ID is in the supported chains list: Arbitrum (42161), Base (8453), or Plasma (9745).
715
728
 
729
+ ### SIWE Authentication Issues in Browser
730
+
731
+ The SDK automatically detects browser vs Node.js environments for SIWE authentication:
732
+ - **Browser**: Uses `window.location.origin` for the SIWE message domain/uri to match the browser's automatic `Origin` header
733
+ - **Node.js**: Uses the API endpoint URL
734
+
735
+ If you encounter SIWE authentication failures in a browser, ensure:
736
+ 1. Your frontend origin is allowed by the API's CORS configuration
737
+ 2. You're using the correct `environment` setting (`staging` or `production`)
738
+
739
+ ### Session Key Already Exists
740
+
741
+ If `createSessionKey` returns `{ alreadyActive: true }`, the user already has an active session key. This is not an error - the SDK proactively checks before attempting to create a new one.
742
+
743
+ ### Data API CORS Errors
744
+
745
+ Some Data API endpoints may require server-side CORS configuration. If you see CORS errors for endpoints like `onchain-earnings`, `calculate-onchain-earnings`, or `opportunities`, contact ZyFAI support to ensure your origin is whitelisted.
746
+
716
747
  ## Contributing
717
748
 
718
749
  Contributions are welcome! Please open an issue or submit a pull request.
package/dist/index.d.mts CHANGED
@@ -21,6 +21,8 @@ interface DeploySafeResponse {
21
21
  safeAddress: Address;
22
22
  txHash: string;
23
23
  status: "deployed" | "failed";
24
+ /** True if the Safe was already deployed (no new deployment needed) */
25
+ alreadyDeployed?: boolean;
24
26
  }
25
27
  /** @internal */
26
28
  interface UpdateUserProfileRequest {
@@ -52,11 +54,17 @@ interface AddSessionKeyResponse {
52
54
  }
53
55
  interface SessionKeyResponse {
54
56
  success: boolean;
55
- sessionKeyAddress: Address;
56
- signature: Hex;
57
+ /** Session key address (not available when alreadyActive is true) */
58
+ sessionKeyAddress?: Address;
59
+ /** Signature (not available when alreadyActive is true) */
60
+ signature?: Hex;
57
61
  sessionNonces?: bigint[];
58
62
  userId?: string;
59
63
  sessionActivation?: AddSessionKeyResponse;
64
+ /** Message when session key already exists */
65
+ message?: string;
66
+ /** True if a session key was already active for this user */
67
+ alreadyActive?: boolean;
60
68
  }
61
69
  interface SmartWalletResponse {
62
70
  address: Address;
@@ -117,7 +125,6 @@ interface PositionSlot {
117
125
  interface PositionsResponse {
118
126
  success: boolean;
119
127
  userAddress: string;
120
- totalValueUsd: number;
121
128
  positions: Position[];
122
129
  }
123
130
  interface UserDetails {
@@ -142,10 +149,18 @@ interface UserDetailsResponse {
142
149
  success: boolean;
143
150
  user: UserDetails;
144
151
  }
152
+ interface TVLBreakdown {
153
+ chain_id: number;
154
+ protocol_id: string | null;
155
+ protocol_name: string | null;
156
+ pool: string | null;
157
+ total_balance: number;
158
+ }
145
159
  interface TVLResponse {
146
160
  success: boolean;
147
161
  totalTvl: number;
148
- byChain?: Record<string, number>;
162
+ byChain?: Record<number, number>;
163
+ breakdown?: TVLBreakdown[];
149
164
  }
150
165
  interface VolumeResponse {
151
166
  success: boolean;
@@ -397,6 +412,7 @@ declare class ZyfaiSDK {
397
412
  private bundlerApiKey?;
398
413
  private isAuthenticated;
399
414
  private authenticatedUserId;
415
+ private hasActiveSessionKey;
400
416
  private environment;
401
417
  constructor(config: SDKConfig | string);
402
418
  /**
package/dist/index.d.ts CHANGED
@@ -21,6 +21,8 @@ interface DeploySafeResponse {
21
21
  safeAddress: Address;
22
22
  txHash: string;
23
23
  status: "deployed" | "failed";
24
+ /** True if the Safe was already deployed (no new deployment needed) */
25
+ alreadyDeployed?: boolean;
24
26
  }
25
27
  /** @internal */
26
28
  interface UpdateUserProfileRequest {
@@ -52,11 +54,17 @@ interface AddSessionKeyResponse {
52
54
  }
53
55
  interface SessionKeyResponse {
54
56
  success: boolean;
55
- sessionKeyAddress: Address;
56
- signature: Hex;
57
+ /** Session key address (not available when alreadyActive is true) */
58
+ sessionKeyAddress?: Address;
59
+ /** Signature (not available when alreadyActive is true) */
60
+ signature?: Hex;
57
61
  sessionNonces?: bigint[];
58
62
  userId?: string;
59
63
  sessionActivation?: AddSessionKeyResponse;
64
+ /** Message when session key already exists */
65
+ message?: string;
66
+ /** True if a session key was already active for this user */
67
+ alreadyActive?: boolean;
60
68
  }
61
69
  interface SmartWalletResponse {
62
70
  address: Address;
@@ -117,7 +125,6 @@ interface PositionSlot {
117
125
  interface PositionsResponse {
118
126
  success: boolean;
119
127
  userAddress: string;
120
- totalValueUsd: number;
121
128
  positions: Position[];
122
129
  }
123
130
  interface UserDetails {
@@ -142,10 +149,18 @@ interface UserDetailsResponse {
142
149
  success: boolean;
143
150
  user: UserDetails;
144
151
  }
152
+ interface TVLBreakdown {
153
+ chain_id: number;
154
+ protocol_id: string | null;
155
+ protocol_name: string | null;
156
+ pool: string | null;
157
+ total_balance: number;
158
+ }
145
159
  interface TVLResponse {
146
160
  success: boolean;
147
161
  totalTvl: number;
148
- byChain?: Record<string, number>;
162
+ byChain?: Record<number, number>;
163
+ breakdown?: TVLBreakdown[];
149
164
  }
150
165
  interface VolumeResponse {
151
166
  success: boolean;
@@ -397,6 +412,7 @@ declare class ZyfaiSDK {
397
412
  private bundlerApiKey?;
398
413
  private isAuthenticated;
399
414
  private authenticatedUserId;
415
+ private hasActiveSessionKey;
400
416
  private environment;
401
417
  constructor(config: SDKConfig | string);
402
418
  /**
package/dist/index.js CHANGED
@@ -87,7 +87,7 @@ var DATA_ENDPOINTS = {
87
87
  // Portfolio
88
88
  DEBANK_PORTFOLIO_MULTICHAIN: (address) => `/debank/portfolio/multichain/${address}`,
89
89
  // Opportunities
90
- OPPORTUNITIES_SAFE: (chainId) => chainId ? `/opportunities/safes?chainId=${chainId}` : "/opportunities/safes",
90
+ OPPORTUNITIES_SAFE: (chainId) => chainId ? `/opportunities/safe?chainId=${chainId}` : "/opportunities/safe",
91
91
  OPPORTUNITIES_DEGEN: (chainId) => chainId ? `/opportunities/degen-strategies?chainId=${chainId}` : "/opportunities/degen-strategies",
92
92
  // APY History
93
93
  DAILY_APY_HISTORY_WEIGHTED: (walletAddress, days) => `/daily-apy-history/weighted/${walletAddress}${days ? `?days=${days}` : ""}`,
@@ -116,8 +116,7 @@ var HttpClient = class {
116
116
  baseURL: `${endpoint}${API_VERSION}`,
117
117
  headers: {
118
118
  "Content-Type": "application/json",
119
- "X-API-Key": this.apiKey,
120
- Origin: this.origin
119
+ "X-API-Key": this.apiKey
121
120
  },
122
121
  timeout: 3e4
123
122
  });
@@ -149,7 +148,6 @@ var HttpClient = class {
149
148
  this.client.interceptors.request.use(
150
149
  (config) => {
151
150
  config.headers["X-API-Key"] = this.apiKey;
152
- config.headers["Origin"] = this.origin;
153
151
  if (this.authToken) {
154
152
  config.headers["Authorization"] = `Bearer ${this.authToken}`;
155
153
  }
@@ -218,6 +216,9 @@ var HttpClient = class {
218
216
  this.dataClient.interceptors.request.use(
219
217
  (config) => {
220
218
  config.headers["X-API-Key"] = this.dataApiKey;
219
+ if (this.authToken) {
220
+ config.headers["Authorization"] = `Bearer ${this.authToken}`;
221
+ }
221
222
  return config;
222
223
  },
223
224
  (error) => Promise.reject(error)
@@ -404,7 +405,6 @@ var getBundlerUrl = (chainId, bundlerApiKey, bundlerProvider = "pimlico") => {
404
405
  // src/utils/safe-account.ts
405
406
  var import_module_sdk = require("@rhinestone/module-sdk");
406
407
  var import_permissionless = require("permissionless");
407
- var import_actions = require("permissionless/actions");
408
408
  var import_erc7579 = require("permissionless/actions/erc7579");
409
409
  var import_pimlico = require("permissionless/clients/pimlico");
410
410
  var import_accounts = require("permissionless/accounts");
@@ -412,27 +412,36 @@ var import_viem3 = require("viem");
412
412
  var import_account_abstraction = require("viem/account-abstraction");
413
413
  var SAFE_7579_ADDRESS = "0x7579EE8307284F293B1927136486880611F20002";
414
414
  var ERC7579_LAUNCHPAD_ADDRESS = "0x7579011aB74c46090561ea277Ba79D510c6C00ff";
415
- var DEFAULT_ACCOUNT_SALT = "zyfai-staging";
415
+ var ACCOUNT_SALTS = {
416
+ staging: "zyfai-staging",
417
+ production: "zyfai-production"
418
+ };
416
419
  var getSafeAccount = async (config) => {
417
420
  const {
418
421
  owner,
419
422
  safeOwnerAddress,
420
423
  publicClient,
421
- accountSalt = DEFAULT_ACCOUNT_SALT
424
+ environment = "production"
422
425
  } = config;
426
+ const effectiveSalt = ACCOUNT_SALTS[environment];
423
427
  if (!owner || !owner.account) {
424
428
  throw new Error("Wallet not connected. Please connect your wallet first.");
425
429
  }
426
- const actualOwnerAddress = safeOwnerAddress || owner.account.address;
430
+ const signerAddress = owner.account.address;
431
+ if (safeOwnerAddress && safeOwnerAddress.toLowerCase() !== signerAddress.toLowerCase()) {
432
+ throw new Error(
433
+ `Connected wallet address (${signerAddress}) must match the Safe owner address (${safeOwnerAddress}). Please connect with the correct wallet.`
434
+ );
435
+ }
427
436
  const ownableValidator = (0, import_module_sdk.getOwnableValidator)({
428
- owners: [actualOwnerAddress],
437
+ owners: [signerAddress],
429
438
  threshold: 1
430
439
  });
431
- const saltHex = (0, import_viem3.fromHex)((0, import_viem3.toHex)(accountSalt), "bigint");
440
+ const saltHex = (0, import_viem3.fromHex)((0, import_viem3.toHex)(effectiveSalt), "bigint");
432
441
  const safeAccount = await (0, import_accounts.toSafeSmartAccount)({
433
442
  client: publicClient,
434
443
  owners: [owner.account],
435
- // Use connected wallet for signing
444
+ // Pass the account object with address and signMessage capability
436
445
  version: "1.4.1",
437
446
  entryPoint: {
438
447
  address: import_account_abstraction.entryPoint07Address,
@@ -565,8 +574,8 @@ var deploySafeAccount = async (config) => {
565
574
  );
566
575
  }
567
576
  };
568
- var signSessionKey = async (config, sessions) => {
569
- const { owner, publicClient, chain } = config;
577
+ var signSessionKey = async (config, sessions, allPublicClients) => {
578
+ const { owner, publicClient } = config;
570
579
  if (!owner || !owner.account) {
571
580
  throw new Error("Wallet not connected. Please connect your wallet first.");
572
581
  }
@@ -575,21 +584,24 @@ var signSessionKey = async (config, sessions) => {
575
584
  address: safeAccount.address,
576
585
  type: "safe"
577
586
  });
587
+ const clients = allPublicClients || [publicClient];
578
588
  const sessionNonces = await Promise.all(
579
- sessions.map(
580
- (session) => (0, import_module_sdk.getSessionNonce)({
581
- client: publicClient,
589
+ sessions.map((session) => {
590
+ const sessionChainId = Number(session.chainId);
591
+ const client = clients.find((c) => c.chain?.id === sessionChainId) || publicClient;
592
+ return (0, import_module_sdk.getSessionNonce)({
593
+ client,
582
594
  account,
583
595
  permissionId: (0, import_module_sdk.getPermissionId)({
584
596
  session
585
597
  })
586
- })
587
- )
598
+ });
599
+ })
588
600
  );
589
601
  const sessionDetails = await (0, import_module_sdk.getEnableSessionDetails)({
590
602
  sessions,
591
603
  account,
592
- clients: [publicClient],
604
+ clients,
593
605
  permitGenericPolicy: true,
594
606
  sessionNonces
595
607
  });
@@ -614,6 +626,8 @@ var ZyfaiSDK = class {
614
626
  this.walletClient = null;
615
627
  this.isAuthenticated = false;
616
628
  this.authenticatedUserId = null;
629
+ // Stored from login response
630
+ this.hasActiveSessionKey = false;
617
631
  const sdkConfig = typeof config === "string" ? { apiKey: config } : config;
618
632
  const { apiKey, dataApiKey, environment, bundlerApiKey } = sdkConfig;
619
633
  if (!apiKey) {
@@ -636,11 +650,19 @@ var ZyfaiSDK = class {
636
650
  return;
637
651
  }
638
652
  const walletClient = this.getWalletClient();
639
- const userAddress = walletClient.account.address;
653
+ const userAddress = (0, import_viem4.getAddress)(walletClient.account.address);
640
654
  const chainId = walletClient.chain?.id || 8453;
641
655
  const challengeResponse = await this.httpClient.post(ENDPOINTS.AUTH_CHALLENGE, {});
642
- const domain = API_ENDPOINTS[this.environment].split("//")[1];
643
- const uri = API_ENDPOINTS[this.environment];
656
+ let uri;
657
+ let domain;
658
+ const globalWindow = typeof globalThis !== "undefined" ? globalThis.window : void 0;
659
+ if (globalWindow?.location?.origin) {
660
+ uri = globalWindow.location.origin;
661
+ domain = globalWindow.location.host;
662
+ } else {
663
+ uri = API_ENDPOINTS[this.environment];
664
+ domain = API_ENDPOINTS[this.environment].split("//")[1];
665
+ }
644
666
  const messageObj = new import_siwe.SiweMessage({
645
667
  address: userAddress,
646
668
  chainId,
@@ -661,11 +683,6 @@ var ZyfaiSDK = class {
661
683
  {
662
684
  message: messageObj,
663
685
  signature
664
- },
665
- {
666
- headers: {
667
- Origin: uri
668
- }
669
686
  }
670
687
  );
671
688
  const authToken = loginResponse.accessToken || loginResponse.token;
@@ -674,6 +691,7 @@ var ZyfaiSDK = class {
674
691
  }
675
692
  this.httpClient.setAuthToken(authToken);
676
693
  this.authenticatedUserId = loginResponse.userId || null;
694
+ this.hasActiveSessionKey = loginResponse.hasActiveSessionKey || false;
677
695
  this.isAuthenticated = true;
678
696
  } catch (error) {
679
697
  throw new Error(
@@ -823,7 +841,8 @@ var ZyfaiSDK = class {
823
841
  owner: walletClient,
824
842
  safeOwnerAddress: userAddress,
825
843
  chain: chainConfig.chain,
826
- publicClient: chainConfig.publicClient
844
+ publicClient: chainConfig.publicClient,
845
+ environment: this.environment
827
846
  });
828
847
  const isDeployed = await isSafeDeployed(
829
848
  safeAddress,
@@ -856,6 +875,26 @@ var ZyfaiSDK = class {
856
875
  }
857
876
  const walletClient = this.getWalletClient(chainId);
858
877
  const chainConfig = getChainConfig(chainId);
878
+ const safeAddress = await getDeterministicSafeAddress({
879
+ owner: walletClient,
880
+ safeOwnerAddress: userAddress,
881
+ chain: chainConfig.chain,
882
+ publicClient: chainConfig.publicClient,
883
+ environment: this.environment
884
+ });
885
+ const alreadyDeployed = await isSafeDeployed(
886
+ safeAddress,
887
+ chainConfig.publicClient
888
+ );
889
+ if (alreadyDeployed) {
890
+ return {
891
+ success: true,
892
+ safeAddress,
893
+ txHash: "0x0",
894
+ status: "deployed",
895
+ alreadyDeployed: true
896
+ };
897
+ }
859
898
  const accountType = await getAccountType(
860
899
  userAddress,
861
900
  chainConfig.publicClient
@@ -871,7 +910,8 @@ var ZyfaiSDK = class {
871
910
  safeOwnerAddress: userAddress,
872
911
  chain: chainConfig.chain,
873
912
  publicClient: chainConfig.publicClient,
874
- bundlerUrl
913
+ bundlerUrl,
914
+ environment: this.environment
875
915
  });
876
916
  try {
877
917
  await this.updateUserProfile({
@@ -918,14 +958,14 @@ var ZyfaiSDK = class {
918
958
  "User ID not available. Please ensure authentication completed successfully."
919
959
  );
920
960
  }
921
- const walletClient = this.getWalletClient();
922
- const chainConfig = getChainConfig(chainId);
923
- const safeAddress = await getDeterministicSafeAddress({
924
- owner: walletClient,
925
- safeOwnerAddress: userAddress,
926
- chain: chainConfig.chain,
927
- publicClient: chainConfig.publicClient
928
- });
961
+ if (this.hasActiveSessionKey) {
962
+ return {
963
+ success: true,
964
+ userId: this.authenticatedUserId,
965
+ message: "Session key already exists and is active",
966
+ alreadyActive: true
967
+ };
968
+ }
929
969
  const sessionConfig = await this.httpClient.get(
930
970
  ENDPOINTS.SESSION_KEYS_CONFIG
931
971
  );
@@ -941,10 +981,14 @@ var ZyfaiSDK = class {
941
981
  chainId,
942
982
  sessions
943
983
  );
984
+ if (!signatureResult.signature) {
985
+ throw new Error("Failed to obtain session key signature");
986
+ }
944
987
  const activation = await this.activateSessionKey(
945
988
  signatureResult.signature,
946
989
  signatureResult.sessionNonces
947
990
  );
991
+ this.hasActiveSessionKey = true;
948
992
  return {
949
993
  ...signatureResult,
950
994
  userId: this.authenticatedUserId,
@@ -982,20 +1026,27 @@ var ZyfaiSDK = class {
982
1026
  `Invalid account type for ${userAddress}. Must be an EOA.`
983
1027
  );
984
1028
  }
1029
+ const sessionChainIds = [
1030
+ ...new Set(sessions.map((s) => Number(s.chainId)))
1031
+ ];
1032
+ const allPublicClients = sessionChainIds.filter(isSupportedChain).map((id) => getChainConfig(id).publicClient);
985
1033
  const { signature, sessionNonces } = await signSessionKey(
986
1034
  {
987
1035
  owner: walletClient,
988
1036
  safeOwnerAddress: userAddress,
989
1037
  chain: chainConfig.chain,
990
- publicClient: chainConfig.publicClient
1038
+ publicClient: chainConfig.publicClient,
1039
+ environment: this.environment
991
1040
  },
992
- sessions
1041
+ sessions,
1042
+ allPublicClients
993
1043
  );
994
1044
  const safeAddress = await getDeterministicSafeAddress({
995
1045
  owner: walletClient,
996
1046
  safeOwnerAddress: userAddress,
997
1047
  chain: chainConfig.chain,
998
- publicClient: chainConfig.publicClient
1048
+ publicClient: chainConfig.publicClient,
1049
+ environment: this.environment
999
1050
  });
1000
1051
  return {
1001
1052
  success: true,
@@ -1081,7 +1132,8 @@ var ZyfaiSDK = class {
1081
1132
  owner: walletClient,
1082
1133
  safeOwnerAddress: userAddress,
1083
1134
  chain: chainConfig.chain,
1084
- publicClient: chainConfig.publicClient
1135
+ publicClient: chainConfig.publicClient,
1136
+ environment: this.environment
1085
1137
  });
1086
1138
  const isDeployed = await isSafeDeployed(
1087
1139
  safeAddress,
@@ -1156,7 +1208,8 @@ var ZyfaiSDK = class {
1156
1208
  owner: walletClient,
1157
1209
  safeOwnerAddress: userAddress,
1158
1210
  chain: chainConfig.chain,
1159
- publicClient: chainConfig.publicClient
1211
+ publicClient: chainConfig.publicClient,
1212
+ environment: this.environment
1160
1213
  });
1161
1214
  const isDeployed = await isSafeDeployed(
1162
1215
  safeAddress,
@@ -1256,7 +1309,8 @@ var ZyfaiSDK = class {
1256
1309
  owner: walletClient,
1257
1310
  safeOwnerAddress: userAddress,
1258
1311
  chain: chainConfig.chain,
1259
- publicClient: chainConfig.publicClient
1312
+ publicClient: chainConfig.publicClient,
1313
+ environment: this.environment
1260
1314
  });
1261
1315
  const response = await this.httpClient.get(
1262
1316
  ENDPOINTS.DATA_POSITION(safeAddress)
@@ -1264,8 +1318,6 @@ var ZyfaiSDK = class {
1264
1318
  return {
1265
1319
  success: true,
1266
1320
  userAddress,
1267
- totalValueUsd: 0,
1268
- // API doesn't return this yet
1269
1321
  positions: response ? [response] : []
1270
1322
  };
1271
1323
  } catch (error) {
@@ -1337,10 +1389,18 @@ var ZyfaiSDK = class {
1337
1389
  async getTVL() {
1338
1390
  try {
1339
1391
  const response = await this.httpClient.get(ENDPOINTS.DATA_TVL);
1392
+ const byChain = {};
1393
+ for (const key of Object.keys(response)) {
1394
+ const numKey = parseInt(key, 10);
1395
+ if (!isNaN(numKey) && typeof response[key] === "number") {
1396
+ byChain[numKey] = response[key];
1397
+ }
1398
+ }
1340
1399
  return {
1341
1400
  success: true,
1342
- totalTvl: response.totalTvl || response.tvl || 0,
1343
- byChain: response.byChain
1401
+ totalTvl: response.total || response.totalTvl || response.tvl || 0,
1402
+ byChain,
1403
+ breakdown: response.breakdown
1344
1404
  };
1345
1405
  } catch (error) {
1346
1406
  throw new Error(`Failed to get TVL: ${error.message}`);
package/dist/index.mjs CHANGED
@@ -48,7 +48,7 @@ var DATA_ENDPOINTS = {
48
48
  // Portfolio
49
49
  DEBANK_PORTFOLIO_MULTICHAIN: (address) => `/debank/portfolio/multichain/${address}`,
50
50
  // Opportunities
51
- OPPORTUNITIES_SAFE: (chainId) => chainId ? `/opportunities/safes?chainId=${chainId}` : "/opportunities/safes",
51
+ OPPORTUNITIES_SAFE: (chainId) => chainId ? `/opportunities/safe?chainId=${chainId}` : "/opportunities/safe",
52
52
  OPPORTUNITIES_DEGEN: (chainId) => chainId ? `/opportunities/degen-strategies?chainId=${chainId}` : "/opportunities/degen-strategies",
53
53
  // APY History
54
54
  DAILY_APY_HISTORY_WEIGHTED: (walletAddress, days) => `/daily-apy-history/weighted/${walletAddress}${days ? `?days=${days}` : ""}`,
@@ -77,8 +77,7 @@ var HttpClient = class {
77
77
  baseURL: `${endpoint}${API_VERSION}`,
78
78
  headers: {
79
79
  "Content-Type": "application/json",
80
- "X-API-Key": this.apiKey,
81
- Origin: this.origin
80
+ "X-API-Key": this.apiKey
82
81
  },
83
82
  timeout: 3e4
84
83
  });
@@ -110,7 +109,6 @@ var HttpClient = class {
110
109
  this.client.interceptors.request.use(
111
110
  (config) => {
112
111
  config.headers["X-API-Key"] = this.apiKey;
113
- config.headers["Origin"] = this.origin;
114
112
  if (this.authToken) {
115
113
  config.headers["Authorization"] = `Bearer ${this.authToken}`;
116
114
  }
@@ -179,6 +177,9 @@ var HttpClient = class {
179
177
  this.dataClient.interceptors.request.use(
180
178
  (config) => {
181
179
  config.headers["X-API-Key"] = this.dataApiKey;
180
+ if (this.authToken) {
181
+ config.headers["Authorization"] = `Bearer ${this.authToken}`;
182
+ }
182
183
  return config;
183
184
  },
184
185
  (error) => Promise.reject(error)
@@ -293,7 +294,8 @@ import { privateKeyToAccount } from "viem/accounts";
293
294
  import {
294
295
  createWalletClient,
295
296
  custom,
296
- http as http3
297
+ http as http3,
298
+ getAddress
297
299
  } from "viem";
298
300
 
299
301
  // src/config/chains.ts
@@ -376,43 +378,49 @@ import {
376
378
  getSessionNonce
377
379
  } from "@rhinestone/module-sdk";
378
380
  import { createSmartAccountClient } from "permissionless";
379
- import { getAccountNonce } from "permissionless/actions";
380
381
  import { erc7579Actions } from "permissionless/actions/erc7579";
381
382
  import { createPimlicoClient } from "permissionless/clients/pimlico";
382
383
  import { toSafeSmartAccount } from "permissionless/accounts";
383
384
  import {
384
385
  http as http2,
385
386
  fromHex,
386
- pad,
387
387
  toHex
388
388
  } from "viem";
389
389
  import {
390
- entryPoint07Address,
391
- getUserOperationHash
390
+ entryPoint07Address
392
391
  } from "viem/account-abstraction";
393
392
  var SAFE_7579_ADDRESS = "0x7579EE8307284F293B1927136486880611F20002";
394
393
  var ERC7579_LAUNCHPAD_ADDRESS = "0x7579011aB74c46090561ea277Ba79D510c6C00ff";
395
- var DEFAULT_ACCOUNT_SALT = "zyfai-staging";
394
+ var ACCOUNT_SALTS = {
395
+ staging: "zyfai-staging",
396
+ production: "zyfai-production"
397
+ };
396
398
  var getSafeAccount = async (config) => {
397
399
  const {
398
400
  owner,
399
401
  safeOwnerAddress,
400
402
  publicClient,
401
- accountSalt = DEFAULT_ACCOUNT_SALT
403
+ environment = "production"
402
404
  } = config;
405
+ const effectiveSalt = ACCOUNT_SALTS[environment];
403
406
  if (!owner || !owner.account) {
404
407
  throw new Error("Wallet not connected. Please connect your wallet first.");
405
408
  }
406
- const actualOwnerAddress = safeOwnerAddress || owner.account.address;
409
+ const signerAddress = owner.account.address;
410
+ if (safeOwnerAddress && safeOwnerAddress.toLowerCase() !== signerAddress.toLowerCase()) {
411
+ throw new Error(
412
+ `Connected wallet address (${signerAddress}) must match the Safe owner address (${safeOwnerAddress}). Please connect with the correct wallet.`
413
+ );
414
+ }
407
415
  const ownableValidator = getOwnableValidator({
408
- owners: [actualOwnerAddress],
416
+ owners: [signerAddress],
409
417
  threshold: 1
410
418
  });
411
- const saltHex = fromHex(toHex(accountSalt), "bigint");
419
+ const saltHex = fromHex(toHex(effectiveSalt), "bigint");
412
420
  const safeAccount = await toSafeSmartAccount({
413
421
  client: publicClient,
414
422
  owners: [owner.account],
415
- // Use connected wallet for signing
423
+ // Pass the account object with address and signMessage capability
416
424
  version: "1.4.1",
417
425
  entryPoint: {
418
426
  address: entryPoint07Address,
@@ -545,8 +553,8 @@ var deploySafeAccount = async (config) => {
545
553
  );
546
554
  }
547
555
  };
548
- var signSessionKey = async (config, sessions) => {
549
- const { owner, publicClient, chain } = config;
556
+ var signSessionKey = async (config, sessions, allPublicClients) => {
557
+ const { owner, publicClient } = config;
550
558
  if (!owner || !owner.account) {
551
559
  throw new Error("Wallet not connected. Please connect your wallet first.");
552
560
  }
@@ -555,21 +563,24 @@ var signSessionKey = async (config, sessions) => {
555
563
  address: safeAccount.address,
556
564
  type: "safe"
557
565
  });
566
+ const clients = allPublicClients || [publicClient];
558
567
  const sessionNonces = await Promise.all(
559
- sessions.map(
560
- (session) => getSessionNonce({
561
- client: publicClient,
568
+ sessions.map((session) => {
569
+ const sessionChainId = Number(session.chainId);
570
+ const client = clients.find((c) => c.chain?.id === sessionChainId) || publicClient;
571
+ return getSessionNonce({
572
+ client,
562
573
  account,
563
574
  permissionId: getPermissionId({
564
575
  session
565
576
  })
566
- })
567
- )
577
+ });
578
+ })
568
579
  );
569
580
  const sessionDetails = await getEnableSessionDetails({
570
581
  sessions,
571
582
  account,
572
- clients: [publicClient],
583
+ clients,
573
584
  permitGenericPolicy: true,
574
585
  sessionNonces
575
586
  });
@@ -594,6 +605,8 @@ var ZyfaiSDK = class {
594
605
  this.walletClient = null;
595
606
  this.isAuthenticated = false;
596
607
  this.authenticatedUserId = null;
608
+ // Stored from login response
609
+ this.hasActiveSessionKey = false;
597
610
  const sdkConfig = typeof config === "string" ? { apiKey: config } : config;
598
611
  const { apiKey, dataApiKey, environment, bundlerApiKey } = sdkConfig;
599
612
  if (!apiKey) {
@@ -616,11 +629,19 @@ var ZyfaiSDK = class {
616
629
  return;
617
630
  }
618
631
  const walletClient = this.getWalletClient();
619
- const userAddress = walletClient.account.address;
632
+ const userAddress = getAddress(walletClient.account.address);
620
633
  const chainId = walletClient.chain?.id || 8453;
621
634
  const challengeResponse = await this.httpClient.post(ENDPOINTS.AUTH_CHALLENGE, {});
622
- const domain = API_ENDPOINTS[this.environment].split("//")[1];
623
- const uri = API_ENDPOINTS[this.environment];
635
+ let uri;
636
+ let domain;
637
+ const globalWindow = typeof globalThis !== "undefined" ? globalThis.window : void 0;
638
+ if (globalWindow?.location?.origin) {
639
+ uri = globalWindow.location.origin;
640
+ domain = globalWindow.location.host;
641
+ } else {
642
+ uri = API_ENDPOINTS[this.environment];
643
+ domain = API_ENDPOINTS[this.environment].split("//")[1];
644
+ }
624
645
  const messageObj = new SiweMessage({
625
646
  address: userAddress,
626
647
  chainId,
@@ -641,11 +662,6 @@ var ZyfaiSDK = class {
641
662
  {
642
663
  message: messageObj,
643
664
  signature
644
- },
645
- {
646
- headers: {
647
- Origin: uri
648
- }
649
665
  }
650
666
  );
651
667
  const authToken = loginResponse.accessToken || loginResponse.token;
@@ -654,6 +670,7 @@ var ZyfaiSDK = class {
654
670
  }
655
671
  this.httpClient.setAuthToken(authToken);
656
672
  this.authenticatedUserId = loginResponse.userId || null;
673
+ this.hasActiveSessionKey = loginResponse.hasActiveSessionKey || false;
657
674
  this.isAuthenticated = true;
658
675
  } catch (error) {
659
676
  throw new Error(
@@ -803,7 +820,8 @@ var ZyfaiSDK = class {
803
820
  owner: walletClient,
804
821
  safeOwnerAddress: userAddress,
805
822
  chain: chainConfig.chain,
806
- publicClient: chainConfig.publicClient
823
+ publicClient: chainConfig.publicClient,
824
+ environment: this.environment
807
825
  });
808
826
  const isDeployed = await isSafeDeployed(
809
827
  safeAddress,
@@ -836,6 +854,26 @@ var ZyfaiSDK = class {
836
854
  }
837
855
  const walletClient = this.getWalletClient(chainId);
838
856
  const chainConfig = getChainConfig(chainId);
857
+ const safeAddress = await getDeterministicSafeAddress({
858
+ owner: walletClient,
859
+ safeOwnerAddress: userAddress,
860
+ chain: chainConfig.chain,
861
+ publicClient: chainConfig.publicClient,
862
+ environment: this.environment
863
+ });
864
+ const alreadyDeployed = await isSafeDeployed(
865
+ safeAddress,
866
+ chainConfig.publicClient
867
+ );
868
+ if (alreadyDeployed) {
869
+ return {
870
+ success: true,
871
+ safeAddress,
872
+ txHash: "0x0",
873
+ status: "deployed",
874
+ alreadyDeployed: true
875
+ };
876
+ }
839
877
  const accountType = await getAccountType(
840
878
  userAddress,
841
879
  chainConfig.publicClient
@@ -851,7 +889,8 @@ var ZyfaiSDK = class {
851
889
  safeOwnerAddress: userAddress,
852
890
  chain: chainConfig.chain,
853
891
  publicClient: chainConfig.publicClient,
854
- bundlerUrl
892
+ bundlerUrl,
893
+ environment: this.environment
855
894
  });
856
895
  try {
857
896
  await this.updateUserProfile({
@@ -898,14 +937,14 @@ var ZyfaiSDK = class {
898
937
  "User ID not available. Please ensure authentication completed successfully."
899
938
  );
900
939
  }
901
- const walletClient = this.getWalletClient();
902
- const chainConfig = getChainConfig(chainId);
903
- const safeAddress = await getDeterministicSafeAddress({
904
- owner: walletClient,
905
- safeOwnerAddress: userAddress,
906
- chain: chainConfig.chain,
907
- publicClient: chainConfig.publicClient
908
- });
940
+ if (this.hasActiveSessionKey) {
941
+ return {
942
+ success: true,
943
+ userId: this.authenticatedUserId,
944
+ message: "Session key already exists and is active",
945
+ alreadyActive: true
946
+ };
947
+ }
909
948
  const sessionConfig = await this.httpClient.get(
910
949
  ENDPOINTS.SESSION_KEYS_CONFIG
911
950
  );
@@ -921,10 +960,14 @@ var ZyfaiSDK = class {
921
960
  chainId,
922
961
  sessions
923
962
  );
963
+ if (!signatureResult.signature) {
964
+ throw new Error("Failed to obtain session key signature");
965
+ }
924
966
  const activation = await this.activateSessionKey(
925
967
  signatureResult.signature,
926
968
  signatureResult.sessionNonces
927
969
  );
970
+ this.hasActiveSessionKey = true;
928
971
  return {
929
972
  ...signatureResult,
930
973
  userId: this.authenticatedUserId,
@@ -962,20 +1005,27 @@ var ZyfaiSDK = class {
962
1005
  `Invalid account type for ${userAddress}. Must be an EOA.`
963
1006
  );
964
1007
  }
1008
+ const sessionChainIds = [
1009
+ ...new Set(sessions.map((s) => Number(s.chainId)))
1010
+ ];
1011
+ const allPublicClients = sessionChainIds.filter(isSupportedChain).map((id) => getChainConfig(id).publicClient);
965
1012
  const { signature, sessionNonces } = await signSessionKey(
966
1013
  {
967
1014
  owner: walletClient,
968
1015
  safeOwnerAddress: userAddress,
969
1016
  chain: chainConfig.chain,
970
- publicClient: chainConfig.publicClient
1017
+ publicClient: chainConfig.publicClient,
1018
+ environment: this.environment
971
1019
  },
972
- sessions
1020
+ sessions,
1021
+ allPublicClients
973
1022
  );
974
1023
  const safeAddress = await getDeterministicSafeAddress({
975
1024
  owner: walletClient,
976
1025
  safeOwnerAddress: userAddress,
977
1026
  chain: chainConfig.chain,
978
- publicClient: chainConfig.publicClient
1027
+ publicClient: chainConfig.publicClient,
1028
+ environment: this.environment
979
1029
  });
980
1030
  return {
981
1031
  success: true,
@@ -1061,7 +1111,8 @@ var ZyfaiSDK = class {
1061
1111
  owner: walletClient,
1062
1112
  safeOwnerAddress: userAddress,
1063
1113
  chain: chainConfig.chain,
1064
- publicClient: chainConfig.publicClient
1114
+ publicClient: chainConfig.publicClient,
1115
+ environment: this.environment
1065
1116
  });
1066
1117
  const isDeployed = await isSafeDeployed(
1067
1118
  safeAddress,
@@ -1136,7 +1187,8 @@ var ZyfaiSDK = class {
1136
1187
  owner: walletClient,
1137
1188
  safeOwnerAddress: userAddress,
1138
1189
  chain: chainConfig.chain,
1139
- publicClient: chainConfig.publicClient
1190
+ publicClient: chainConfig.publicClient,
1191
+ environment: this.environment
1140
1192
  });
1141
1193
  const isDeployed = await isSafeDeployed(
1142
1194
  safeAddress,
@@ -1236,7 +1288,8 @@ var ZyfaiSDK = class {
1236
1288
  owner: walletClient,
1237
1289
  safeOwnerAddress: userAddress,
1238
1290
  chain: chainConfig.chain,
1239
- publicClient: chainConfig.publicClient
1291
+ publicClient: chainConfig.publicClient,
1292
+ environment: this.environment
1240
1293
  });
1241
1294
  const response = await this.httpClient.get(
1242
1295
  ENDPOINTS.DATA_POSITION(safeAddress)
@@ -1244,8 +1297,6 @@ var ZyfaiSDK = class {
1244
1297
  return {
1245
1298
  success: true,
1246
1299
  userAddress,
1247
- totalValueUsd: 0,
1248
- // API doesn't return this yet
1249
1300
  positions: response ? [response] : []
1250
1301
  };
1251
1302
  } catch (error) {
@@ -1317,10 +1368,18 @@ var ZyfaiSDK = class {
1317
1368
  async getTVL() {
1318
1369
  try {
1319
1370
  const response = await this.httpClient.get(ENDPOINTS.DATA_TVL);
1371
+ const byChain = {};
1372
+ for (const key of Object.keys(response)) {
1373
+ const numKey = parseInt(key, 10);
1374
+ if (!isNaN(numKey) && typeof response[key] === "number") {
1375
+ byChain[numKey] = response[key];
1376
+ }
1377
+ }
1320
1378
  return {
1321
1379
  success: true,
1322
- totalTvl: response.totalTvl || response.tvl || 0,
1323
- byChain: response.byChain
1380
+ totalTvl: response.total || response.totalTvl || response.tvl || 0,
1381
+ byChain,
1382
+ breakdown: response.breakdown
1324
1383
  };
1325
1384
  } catch (error) {
1326
1385
  throw new Error(`Failed to get TVL: ${error.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zyfai/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "TypeScript SDK for ZyFAI Yield Optimization Engine - Deploy Safe smart wallets, manage session keys, and interact with DeFi protocols",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",