@zyfai/sdk 0.1.5 → 0.1.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/README.md CHANGED
@@ -65,7 +65,7 @@ const sdk = new ZyfaiSDK({
65
65
 
66
66
  ### Connect Account
67
67
 
68
- The SDK accepts either a private key or a modern wallet provider:
68
+ The SDK accepts either a private key or a modern wallet provider. **The SDK automatically authenticates the user via SIWE (Sign-In with Ethereum) when connecting.**
69
69
 
70
70
  ```typescript
71
71
  // Option 1: With private key (chainId required)
@@ -84,7 +84,25 @@ const userAddress = "0xUser...";
84
84
  await sdk.deploySafe(userAddress, 42161);
85
85
  ```
86
86
 
87
- **Note:** When using a wallet provider, the SDK automatically detects the chain from the provider. You can optionally specify `chainId` to override.
87
+ **Note:**
88
+ - When using a wallet provider, the SDK automatically detects the chain from the provider. You can optionally specify `chainId` to override.
89
+ - The SDK automatically performs SIWE authentication when connecting, so you don't need to call any additional authentication methods.
90
+
91
+ ### Disconnect Account
92
+
93
+ Disconnect the current account and clear all authentication state:
94
+
95
+ ```typescript
96
+ // Disconnect account and clear JWT token
97
+ await sdk.disconnectAccount();
98
+ console.log("Account disconnected and authentication cleared");
99
+ ```
100
+
101
+ This method:
102
+ - Clears the wallet connection
103
+ - Resets authentication state
104
+ - Clears the JWT token
105
+ - Resets session key tracking
88
106
 
89
107
  ## Core Features
90
108
 
@@ -164,7 +182,7 @@ new ZyfaiSDK(config: SDKConfig | string)
164
182
 
165
183
  ##### `connectAccount(account: string | any, chainId?: SupportedChainId): Promise<Address>`
166
184
 
167
- Connect account for signing transactions. Accepts either a private key string or a modern wallet provider.
185
+ Connect account for signing transactions and authenticate via SIWE. Accepts either a private key string or a modern wallet provider.
168
186
 
169
187
  **Parameters:**
170
188
 
@@ -176,6 +194,11 @@ Connect account for signing transactions. Accepts either a private key string or
176
194
 
177
195
  **Returns:** Connected wallet address
178
196
 
197
+ **Automatic Actions:**
198
+ - Connects the wallet
199
+ - Authenticates via SIWE (Sign-In with Ethereum)
200
+ - Stores JWT token for authenticated endpoints
201
+
179
202
  **Examples:**
180
203
 
181
204
  ```typescript
@@ -187,6 +210,25 @@ const provider = await connector.getProvider();
187
210
  await sdk.connectAccount(provider); // Uses provider's current chain
188
211
  ```
189
212
 
213
+ ##### `disconnectAccount(): Promise<void>`
214
+
215
+ Disconnect account and clear all authentication state.
216
+
217
+ **Returns:** Promise that resolves when disconnection is complete
218
+
219
+ **Actions:**
220
+ - Clears wallet connection
221
+ - Resets authentication state
222
+ - Clears JWT token
223
+ - Resets session key tracking
224
+
225
+ **Example:**
226
+
227
+ ```typescript
228
+ await sdk.disconnectAccount();
229
+ console.log("Disconnected and cleared");
230
+ ```
231
+
190
232
  ##### `getSmartWalletAddress(userAddress: string, chainId: SupportedChainId): Promise<SmartWalletResponse>`
191
233
 
192
234
  Get the Smart Wallet (Safe) address for a user.
@@ -235,7 +277,7 @@ The SDK automatically fetches optimal session configuration from ZyFAI API:
235
277
 
236
278
  ```typescript
237
279
  // SDK automatically:
238
- // 1. Authenticates via SIWE (creates user record if needed)
280
+ // 1. Uses existing SIWE authentication (from connectAccount)
239
281
  // 2. Checks if user already has an active session key (returns early if so)
240
282
  // 3. Calculates the deterministic Safe address
241
283
  // 4. Retrieves personalized config via /session-keys/config
@@ -257,9 +299,9 @@ console.log("User ID:", result.userId);
257
299
 
258
300
  **Important**:
259
301
 
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
262
- - The user record must have `smartWallet` and `chainId` set (automatically handled after calling `deploySafe` or `updateUserProfile`)
302
+ - User must be authenticated (automatically done via `connectAccount()`)
303
+ - The SDK proactively checks if the user already has an active session key and returns early without requiring any signature if one exists
304
+ - The user record must have `smartWallet` and `chainId` set (automatically handled after calling `deploySafe`)
263
305
  - When `alreadyActive` is `true`, `sessionKeyAddress` and `signature` are not available in the response
264
306
 
265
307
  ### 4. Deposit Funds
@@ -286,7 +328,7 @@ The SDK automatically authenticates via SIWE before logging the deposit with ZyF
286
328
 
287
329
  ### 5. Withdraw Funds
288
330
 
289
- Withdraw funds from your Safe:
331
+ Initiate a withdrawal from your Safe. **Note: Withdrawals are processed asynchronously by the backend.**
290
332
 
291
333
  ```typescript
292
334
  // Full withdrawal
@@ -301,13 +343,22 @@ const result = await sdk.withdrawFunds(
301
343
  );
302
344
 
303
345
  if (result.success) {
304
- console.log("Withdrawal successful!");
305
- console.log("Transaction Hash:", result.txHash);
346
+ console.log("Withdrawal initiated!");
347
+ console.log("Message:", result.message); // e.g., "Withdrawal request sent"
348
+ if (result.txHash) {
349
+ console.log("Transaction Hash:", result.txHash);
350
+ } else {
351
+ console.log("Transaction will be processed asynchronously");
352
+ }
306
353
  }
307
354
  ```
308
355
 
309
- **Note:** Amount must be in least decimal units. For USDC (6 decimals): 1 USDC = 1000000
310
- The SDK authenticates via SIWE before calling the withdrawal endpoints (`/users/withdraw` or `/users/partial-withdraw`) so you don't need to manage tokens manually.
356
+ **Important Notes:**
357
+ - Amount must be in least decimal units. For USDC (6 decimals): 1 USDC = 1000000
358
+ - The SDK authenticates via SIWE before calling the withdrawal endpoints
359
+ - Withdrawals are processed asynchronously - the `txHash` may not be immediately available
360
+ - Check the `message` field for the withdrawal status
361
+ - Use `getHistory()` to track the withdrawal transaction once it's processed
311
362
 
312
363
  ### 6. Get Available Protocols
313
364
 
@@ -549,7 +600,7 @@ async function main() {
549
600
  bundlerApiKey: process.env.BUNDLER_API_KEY!,
550
601
  });
551
602
 
552
- // Connect account (for signing)
603
+ // Connect account (automatically authenticates via SIWE)
553
604
  await sdk.connectAccount(process.env.PRIVATE_KEY!, 42161);
554
605
 
555
606
  const userAddress = "0xUser..."; // User's EOA address
@@ -598,9 +649,10 @@ function SafeDeployment() {
598
649
  try {
599
650
  // Client passes the wallet provider from their frontend
600
651
  // e.g., from wagmi: const provider = await connector.getProvider();
652
+ // connectAccount automatically authenticates via SIWE
601
653
  const address = await sdk.connectAccount(walletProvider); // chainId auto-detected
602
654
  setUserAddress(address);
603
- console.log("Connected:", address);
655
+ console.log("Connected and authenticated:", address);
604
656
 
605
657
  // Get Safe address for this user
606
658
  const walletInfo = await sdk.getSmartWalletAddress(address, 42161);
@@ -720,7 +772,7 @@ CHAIN_ID=8453
720
772
 
721
773
  ### "No account connected" Error
722
774
 
723
- Make sure to call `connectAccount()` before calling other methods that require signing.
775
+ Make sure to call `connectAccount()` before calling other methods that require signing. Note that `connectAccount()` automatically authenticates the user via SIWE.
724
776
 
725
777
  ### "Unsupported chain" Error
726
778
 
@@ -728,18 +780,26 @@ Check that the chain ID is in the supported chains list: Arbitrum (42161), Base
728
780
 
729
781
  ### SIWE Authentication Issues in Browser
730
782
 
731
- The SDK automatically detects browser vs Node.js environments for SIWE authentication:
783
+ The SDK automatically performs SIWE authentication when you call `connectAccount()`. The SDK automatically detects browser vs Node.js environments:
732
784
  - **Browser**: Uses `window.location.origin` for the SIWE message domain/uri to match the browser's automatic `Origin` header
733
785
  - **Node.js**: Uses the API endpoint URL
734
786
 
735
787
  If you encounter SIWE authentication failures in a browser, ensure:
736
788
  1. Your frontend origin is allowed by the API's CORS configuration
737
789
  2. You're using the correct `environment` setting (`staging` or `production`)
790
+ 3. The user approves the SIWE signature request in their wallet
738
791
 
739
792
  ### Session Key Already Exists
740
793
 
741
794
  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
795
 
796
+ ### Withdrawal Transaction Hash Not Available
797
+
798
+ If `withdrawFunds` returns without a `txHash`, the withdrawal is being processed asynchronously by the backend. You can:
799
+ 1. Check the `message` field for status information
800
+ 2. Use `getHistory()` to track when the withdrawal transaction is processed
801
+ 3. The transaction will appear in the history once it's been executed
802
+
743
803
  ### Data API CORS Errors
744
804
 
745
805
  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.
package/dist/index.d.mts CHANGED
@@ -54,8 +54,6 @@ interface AddSessionKeyResponse {
54
54
  }
55
55
  interface SessionKeyResponse {
56
56
  success: boolean;
57
- /** Session key address (not available when alreadyActive is true) */
58
- sessionKeyAddress?: Address;
59
57
  /** Signature (not available when alreadyActive is true) */
60
58
  signature?: Hex;
61
59
  sessionNonces?: bigint[];
@@ -347,7 +345,8 @@ interface DepositResponse {
347
345
  }
348
346
  interface WithdrawResponse {
349
347
  success: boolean;
350
- txHash: string;
348
+ message: string;
349
+ txHash?: string;
351
350
  type: "full" | "partial";
352
351
  amount: string;
353
352
  receiver: string;
@@ -409,10 +408,10 @@ declare class ZyfaiSDK {
409
408
  private signer;
410
409
  private walletClient;
411
410
  private bundlerApiKey?;
412
- private isAuthenticated;
413
411
  private authenticatedUserId;
414
412
  private hasActiveSessionKey;
415
413
  private environment;
414
+ private currentProvider;
416
415
  constructor(config: SDKConfig | string);
417
416
  /**
418
417
  * Authenticate user with SIWE (Sign-In with Ethereum) & JWT token
@@ -438,6 +437,12 @@ declare class ZyfaiSDK {
438
437
  * ```
439
438
  */
440
439
  updateUserProfile(request: UpdateUserProfileRequest): Promise<UpdateUserProfileResponse>;
440
+ /**
441
+ * Handle account changes from wallet provider
442
+ * Resets authentication state when wallet is switched
443
+ * @private
444
+ */
445
+ private handleAccountsChanged;
441
446
  /**
442
447
  * Connect account for signing transactions
443
448
  * Accepts either a private key string or a modern wallet provider
@@ -456,6 +461,17 @@ declare class ZyfaiSDK {
456
461
  * await sdk.connectAccount(provider);
457
462
  */
458
463
  connectAccount(account: string | any, chainId?: SupportedChainId): Promise<Address>;
464
+ /**
465
+ * Disconnect account and clear authentication state
466
+ * Resets wallet connection, JWT token, and all authentication-related state
467
+ *
468
+ * @example
469
+ * ```typescript
470
+ * await sdk.disconnectAccount();
471
+ * console.log("Account disconnected");
472
+ * ```
473
+ */
474
+ disconnectAccount(): Promise<void>;
459
475
  /**
460
476
  * Get wallet client (throws if not connected)
461
477
  * @private
@@ -531,18 +547,20 @@ declare class ZyfaiSDK {
531
547
  depositFunds(userAddress: string, chainId: SupportedChainId, tokenAddress: string, amount: string): Promise<DepositResponse>;
532
548
  /**
533
549
  * Withdraw funds from Safe smart wallet
534
- * Triggers a withdrawal request to the ZyFAI API
550
+ * Initiates a withdrawal request to the ZyFAI API
551
+ * Note: The withdrawal is processed asynchronously, so txHash may not be immediately available
535
552
  *
536
553
  * @param userAddress - User's address (owner of the Safe)
537
554
  * @param chainId - Target chain ID
538
555
  * @param amount - Optional: Amount in least decimal units to withdraw (partial withdrawal). If not specified, withdraws all funds
539
556
  * @param receiver - Optional: Receiver address. If not specified, sends to Safe owner
540
- * @returns Withdraw response with transaction hash
557
+ * @returns Withdraw response with message and optional transaction hash (available once processed)
541
558
  *
542
559
  * @example
543
560
  * ```typescript
544
561
  * // Full withdrawal
545
562
  * const result = await sdk.withdrawFunds("0xUser...", 42161);
563
+ * console.log(result.message); // "Withdrawal request sent"
546
564
  *
547
565
  * // Partial withdrawal of 50 USDC (6 decimals)
548
566
  * const result = await sdk.withdrawFunds(
package/dist/index.d.ts CHANGED
@@ -54,8 +54,6 @@ interface AddSessionKeyResponse {
54
54
  }
55
55
  interface SessionKeyResponse {
56
56
  success: boolean;
57
- /** Session key address (not available when alreadyActive is true) */
58
- sessionKeyAddress?: Address;
59
57
  /** Signature (not available when alreadyActive is true) */
60
58
  signature?: Hex;
61
59
  sessionNonces?: bigint[];
@@ -347,7 +345,8 @@ interface DepositResponse {
347
345
  }
348
346
  interface WithdrawResponse {
349
347
  success: boolean;
350
- txHash: string;
348
+ message: string;
349
+ txHash?: string;
351
350
  type: "full" | "partial";
352
351
  amount: string;
353
352
  receiver: string;
@@ -409,10 +408,10 @@ declare class ZyfaiSDK {
409
408
  private signer;
410
409
  private walletClient;
411
410
  private bundlerApiKey?;
412
- private isAuthenticated;
413
411
  private authenticatedUserId;
414
412
  private hasActiveSessionKey;
415
413
  private environment;
414
+ private currentProvider;
416
415
  constructor(config: SDKConfig | string);
417
416
  /**
418
417
  * Authenticate user with SIWE (Sign-In with Ethereum) & JWT token
@@ -438,6 +437,12 @@ declare class ZyfaiSDK {
438
437
  * ```
439
438
  */
440
439
  updateUserProfile(request: UpdateUserProfileRequest): Promise<UpdateUserProfileResponse>;
440
+ /**
441
+ * Handle account changes from wallet provider
442
+ * Resets authentication state when wallet is switched
443
+ * @private
444
+ */
445
+ private handleAccountsChanged;
441
446
  /**
442
447
  * Connect account for signing transactions
443
448
  * Accepts either a private key string or a modern wallet provider
@@ -456,6 +461,17 @@ declare class ZyfaiSDK {
456
461
  * await sdk.connectAccount(provider);
457
462
  */
458
463
  connectAccount(account: string | any, chainId?: SupportedChainId): Promise<Address>;
464
+ /**
465
+ * Disconnect account and clear authentication state
466
+ * Resets wallet connection, JWT token, and all authentication-related state
467
+ *
468
+ * @example
469
+ * ```typescript
470
+ * await sdk.disconnectAccount();
471
+ * console.log("Account disconnected");
472
+ * ```
473
+ */
474
+ disconnectAccount(): Promise<void>;
459
475
  /**
460
476
  * Get wallet client (throws if not connected)
461
477
  * @private
@@ -531,18 +547,20 @@ declare class ZyfaiSDK {
531
547
  depositFunds(userAddress: string, chainId: SupportedChainId, tokenAddress: string, amount: string): Promise<DepositResponse>;
532
548
  /**
533
549
  * Withdraw funds from Safe smart wallet
534
- * Triggers a withdrawal request to the ZyFAI API
550
+ * Initiates a withdrawal request to the ZyFAI API
551
+ * Note: The withdrawal is processed asynchronously, so txHash may not be immediately available
535
552
  *
536
553
  * @param userAddress - User's address (owner of the Safe)
537
554
  * @param chainId - Target chain ID
538
555
  * @param amount - Optional: Amount in least decimal units to withdraw (partial withdrawal). If not specified, withdraws all funds
539
556
  * @param receiver - Optional: Receiver address. If not specified, sends to Safe owner
540
- * @returns Withdraw response with transaction hash
557
+ * @returns Withdraw response with message and optional transaction hash (available once processed)
541
558
  *
542
559
  * @example
543
560
  * ```typescript
544
561
  * // Full withdrawal
545
562
  * const result = await sdk.withdrawFunds("0xUser...", 42161);
563
+ * console.log(result.message); // "Withdrawal request sent"
546
564
  *
547
565
  * // Partial withdrawal of 50 USDC (6 decimals)
548
566
  * const result = await sdk.withdrawFunds(
package/dist/index.js CHANGED
@@ -620,14 +620,15 @@ var signSessionKey = async (config, sessions, allPublicClients) => {
620
620
  // src/core/ZyfaiSDK.ts
621
621
  var import_siwe = require("siwe");
622
622
  var ZyfaiSDK = class {
623
- // TODO: The environment should be removed. Having the same key for staging and production is not ideal, but for now it's fine.
623
+ // Store reference to current provider for event handling
624
624
  constructor(config) {
625
625
  this.signer = null;
626
626
  this.walletClient = null;
627
- this.isAuthenticated = false;
628
627
  this.authenticatedUserId = null;
629
- // Stored from login response
628
+ // If non-null, user is authenticated
630
629
  this.hasActiveSessionKey = false;
630
+ // TODO: The environment should be removed. Having the same key for staging and production is not ideal, but for now it's fine.
631
+ this.currentProvider = null;
631
632
  const sdkConfig = typeof config === "string" ? { apiKey: config } : config;
632
633
  const { apiKey, dataApiKey, environment, bundlerApiKey } = sdkConfig;
633
634
  if (!apiKey) {
@@ -646,7 +647,7 @@ var ZyfaiSDK = class {
646
647
  */
647
648
  async authenticateUser() {
648
649
  try {
649
- if (this.isAuthenticated) {
650
+ if (this.authenticatedUserId !== null) {
650
651
  return;
651
652
  }
652
653
  const walletClient = this.getWalletClient();
@@ -685,14 +686,13 @@ var ZyfaiSDK = class {
685
686
  signature
686
687
  }
687
688
  );
688
- const authToken = loginResponse.accessToken || loginResponse.token;
689
+ const authToken = loginResponse.accessToken;
689
690
  if (!authToken) {
690
691
  throw new Error("Authentication response missing access token");
691
692
  }
692
693
  this.httpClient.setAuthToken(authToken);
693
694
  this.authenticatedUserId = loginResponse.userId || null;
694
695
  this.hasActiveSessionKey = loginResponse.hasActiveSessionKey || false;
695
- this.isAuthenticated = true;
696
696
  } catch (error) {
697
697
  throw new Error(
698
698
  `Failed to authenticate user: ${error.message}`
@@ -733,6 +733,43 @@ var ZyfaiSDK = class {
733
733
  );
734
734
  }
735
735
  }
736
+ /**
737
+ * Handle account changes from wallet provider
738
+ * Resets authentication state when wallet is switched
739
+ * @private
740
+ */
741
+ async handleAccountsChanged(accounts) {
742
+ if (!accounts || accounts.length === 0) {
743
+ await this.disconnectAccount();
744
+ return;
745
+ }
746
+ const newAddress = accounts[0];
747
+ const currentAddress = this.walletClient?.account?.address;
748
+ if (currentAddress && newAddress.toLowerCase() === currentAddress.toLowerCase()) {
749
+ return;
750
+ }
751
+ this.authenticatedUserId = null;
752
+ this.hasActiveSessionKey = false;
753
+ this.httpClient.clearAuthToken();
754
+ if (this.walletClient && this.currentProvider) {
755
+ const chainConfig = getChainConfig(
756
+ this.walletClient.chain?.id || 42161
757
+ );
758
+ this.walletClient = (0, import_viem4.createWalletClient)({
759
+ account: newAddress,
760
+ chain: chainConfig.chain,
761
+ transport: (0, import_viem4.custom)(this.currentProvider)
762
+ });
763
+ try {
764
+ await this.authenticateUser();
765
+ } catch (error) {
766
+ console.warn(
767
+ "Failed to authenticate after wallet switch:",
768
+ error.message
769
+ );
770
+ }
771
+ }
772
+ }
736
773
  /**
737
774
  * Connect account for signing transactions
738
775
  * Accepts either a private key string or a modern wallet provider
@@ -754,9 +791,16 @@ var ZyfaiSDK = class {
754
791
  if (!isSupportedChain(chainId)) {
755
792
  throw new Error(`Unsupported chain ID: ${chainId}`);
756
793
  }
757
- this.isAuthenticated = false;
794
+ this.authenticatedUserId = null;
758
795
  this.httpClient.clearAuthToken();
796
+ if (this.currentProvider?.removeAllListeners) {
797
+ try {
798
+ this.currentProvider.removeAllListeners("accountsChanged");
799
+ } catch (error) {
800
+ }
801
+ }
759
802
  const chainConfig = getChainConfig(chainId);
803
+ let connectedAddress;
760
804
  if (typeof account === "string") {
761
805
  let privateKey = account;
762
806
  if (!privateKey.startsWith("0x")) {
@@ -768,39 +812,72 @@ var ZyfaiSDK = class {
768
812
  chain: chainConfig.chain,
769
813
  transport: (0, import_viem4.http)(chainConfig.rpcUrl)
770
814
  });
771
- return this.signer.address;
772
- }
773
- const provider = account;
774
- if (!provider) {
775
- throw new Error(
776
- "Invalid account parameter. Expected private key string or wallet provider."
777
- );
778
- }
779
- if (provider.request) {
780
- const accounts = await provider.request({
781
- method: "eth_requestAccounts"
782
- });
783
- if (!accounts || accounts.length === 0) {
784
- throw new Error("No accounts found in wallet provider");
815
+ connectedAddress = this.signer.address;
816
+ this.currentProvider = null;
817
+ } else {
818
+ const provider = account;
819
+ if (!provider) {
820
+ throw new Error(
821
+ "Invalid account parameter. Expected private key string or wallet provider."
822
+ );
823
+ }
824
+ if (provider.request) {
825
+ const accounts = await provider.request({
826
+ method: "eth_requestAccounts"
827
+ });
828
+ if (!accounts || accounts.length === 0) {
829
+ throw new Error("No accounts found in wallet provider");
830
+ }
831
+ this.walletClient = (0, import_viem4.createWalletClient)({
832
+ account: accounts[0],
833
+ chain: chainConfig.chain,
834
+ transport: (0, import_viem4.custom)(provider)
835
+ });
836
+ connectedAddress = accounts[0];
837
+ this.currentProvider = provider;
838
+ if (provider.on) {
839
+ provider.on("accountsChanged", this.handleAccountsChanged.bind(this));
840
+ }
841
+ } else if (provider.account && provider.transport) {
842
+ this.walletClient = (0, import_viem4.createWalletClient)({
843
+ account: provider.account,
844
+ chain: chainConfig.chain,
845
+ transport: provider.transport
846
+ });
847
+ connectedAddress = provider.account.address;
848
+ this.currentProvider = null;
849
+ } else {
850
+ throw new Error(
851
+ "Invalid wallet provider. Expected EIP-1193 provider or viem WalletClient."
852
+ );
785
853
  }
786
- this.walletClient = (0, import_viem4.createWalletClient)({
787
- account: accounts[0],
788
- chain: chainConfig.chain,
789
- transport: (0, import_viem4.custom)(provider)
790
- });
791
- return accounts[0];
792
854
  }
793
- if (provider.account && provider.transport) {
794
- this.walletClient = (0, import_viem4.createWalletClient)({
795
- account: provider.account,
796
- chain: chainConfig.chain,
797
- transport: provider.transport
798
- });
799
- return provider.account.address;
855
+ await this.authenticateUser();
856
+ return connectedAddress;
857
+ }
858
+ /**
859
+ * Disconnect account and clear authentication state
860
+ * Resets wallet connection, JWT token, and all authentication-related state
861
+ *
862
+ * @example
863
+ * ```typescript
864
+ * await sdk.disconnectAccount();
865
+ * console.log("Account disconnected");
866
+ * ```
867
+ */
868
+ async disconnectAccount() {
869
+ if (this.currentProvider?.removeAllListeners) {
870
+ try {
871
+ this.currentProvider.removeAllListeners("accountsChanged");
872
+ } catch (error) {
873
+ }
800
874
  }
801
- throw new Error(
802
- "Invalid wallet provider. Expected EIP-1193 provider or viem WalletClient."
803
- );
875
+ this.signer = null;
876
+ this.walletClient = null;
877
+ this.currentProvider = null;
878
+ this.authenticatedUserId = null;
879
+ this.hasActiveSessionKey = false;
880
+ this.httpClient.clearAuthToken();
804
881
  }
805
882
  /**
806
883
  * Get wallet client (throws if not connected)
@@ -1050,7 +1127,6 @@ var ZyfaiSDK = class {
1050
1127
  });
1051
1128
  return {
1052
1129
  success: true,
1053
- sessionKeyAddress: safeAddress,
1054
1130
  signature,
1055
1131
  sessionNonces
1056
1132
  };
@@ -1172,18 +1248,20 @@ var ZyfaiSDK = class {
1172
1248
  }
1173
1249
  /**
1174
1250
  * Withdraw funds from Safe smart wallet
1175
- * Triggers a withdrawal request to the ZyFAI API
1251
+ * Initiates a withdrawal request to the ZyFAI API
1252
+ * Note: The withdrawal is processed asynchronously, so txHash may not be immediately available
1176
1253
  *
1177
1254
  * @param userAddress - User's address (owner of the Safe)
1178
1255
  * @param chainId - Target chain ID
1179
1256
  * @param amount - Optional: Amount in least decimal units to withdraw (partial withdrawal). If not specified, withdraws all funds
1180
1257
  * @param receiver - Optional: Receiver address. If not specified, sends to Safe owner
1181
- * @returns Withdraw response with transaction hash
1258
+ * @returns Withdraw response with message and optional transaction hash (available once processed)
1182
1259
  *
1183
1260
  * @example
1184
1261
  * ```typescript
1185
1262
  * // Full withdrawal
1186
1263
  * const result = await sdk.withdrawFunds("0xUser...", 42161);
1264
+ * console.log(result.message); // "Withdrawal request sent"
1187
1265
  *
1188
1266
  * // Partial withdrawal of 50 USDC (6 decimals)
1189
1267
  * const result = await sdk.withdrawFunds(
@@ -1234,9 +1312,12 @@ var ZyfaiSDK = class {
1234
1312
  });
1235
1313
  }
1236
1314
  const success = response?.success ?? true;
1315
+ const message = response?.message || "Withdrawal request sent";
1316
+ const txHash = response?.txHash || response?.transactionHash;
1237
1317
  return {
1238
1318
  success,
1239
- txHash: response?.txHash || response?.transactionHash || "pending",
1319
+ message,
1320
+ txHash,
1240
1321
  type: amount ? "partial" : "full",
1241
1322
  amount: amount || "all",
1242
1323
  receiver: receiver || userAddress,
package/dist/index.mjs CHANGED
@@ -599,14 +599,15 @@ var signSessionKey = async (config, sessions, allPublicClients) => {
599
599
  // src/core/ZyfaiSDK.ts
600
600
  import { SiweMessage } from "siwe";
601
601
  var ZyfaiSDK = class {
602
- // TODO: The environment should be removed. Having the same key for staging and production is not ideal, but for now it's fine.
602
+ // Store reference to current provider for event handling
603
603
  constructor(config) {
604
604
  this.signer = null;
605
605
  this.walletClient = null;
606
- this.isAuthenticated = false;
607
606
  this.authenticatedUserId = null;
608
- // Stored from login response
607
+ // If non-null, user is authenticated
609
608
  this.hasActiveSessionKey = false;
609
+ // TODO: The environment should be removed. Having the same key for staging and production is not ideal, but for now it's fine.
610
+ this.currentProvider = null;
610
611
  const sdkConfig = typeof config === "string" ? { apiKey: config } : config;
611
612
  const { apiKey, dataApiKey, environment, bundlerApiKey } = sdkConfig;
612
613
  if (!apiKey) {
@@ -625,7 +626,7 @@ var ZyfaiSDK = class {
625
626
  */
626
627
  async authenticateUser() {
627
628
  try {
628
- if (this.isAuthenticated) {
629
+ if (this.authenticatedUserId !== null) {
629
630
  return;
630
631
  }
631
632
  const walletClient = this.getWalletClient();
@@ -664,14 +665,13 @@ var ZyfaiSDK = class {
664
665
  signature
665
666
  }
666
667
  );
667
- const authToken = loginResponse.accessToken || loginResponse.token;
668
+ const authToken = loginResponse.accessToken;
668
669
  if (!authToken) {
669
670
  throw new Error("Authentication response missing access token");
670
671
  }
671
672
  this.httpClient.setAuthToken(authToken);
672
673
  this.authenticatedUserId = loginResponse.userId || null;
673
674
  this.hasActiveSessionKey = loginResponse.hasActiveSessionKey || false;
674
- this.isAuthenticated = true;
675
675
  } catch (error) {
676
676
  throw new Error(
677
677
  `Failed to authenticate user: ${error.message}`
@@ -712,6 +712,43 @@ var ZyfaiSDK = class {
712
712
  );
713
713
  }
714
714
  }
715
+ /**
716
+ * Handle account changes from wallet provider
717
+ * Resets authentication state when wallet is switched
718
+ * @private
719
+ */
720
+ async handleAccountsChanged(accounts) {
721
+ if (!accounts || accounts.length === 0) {
722
+ await this.disconnectAccount();
723
+ return;
724
+ }
725
+ const newAddress = accounts[0];
726
+ const currentAddress = this.walletClient?.account?.address;
727
+ if (currentAddress && newAddress.toLowerCase() === currentAddress.toLowerCase()) {
728
+ return;
729
+ }
730
+ this.authenticatedUserId = null;
731
+ this.hasActiveSessionKey = false;
732
+ this.httpClient.clearAuthToken();
733
+ if (this.walletClient && this.currentProvider) {
734
+ const chainConfig = getChainConfig(
735
+ this.walletClient.chain?.id || 42161
736
+ );
737
+ this.walletClient = createWalletClient({
738
+ account: newAddress,
739
+ chain: chainConfig.chain,
740
+ transport: custom(this.currentProvider)
741
+ });
742
+ try {
743
+ await this.authenticateUser();
744
+ } catch (error) {
745
+ console.warn(
746
+ "Failed to authenticate after wallet switch:",
747
+ error.message
748
+ );
749
+ }
750
+ }
751
+ }
715
752
  /**
716
753
  * Connect account for signing transactions
717
754
  * Accepts either a private key string or a modern wallet provider
@@ -733,9 +770,16 @@ var ZyfaiSDK = class {
733
770
  if (!isSupportedChain(chainId)) {
734
771
  throw new Error(`Unsupported chain ID: ${chainId}`);
735
772
  }
736
- this.isAuthenticated = false;
773
+ this.authenticatedUserId = null;
737
774
  this.httpClient.clearAuthToken();
775
+ if (this.currentProvider?.removeAllListeners) {
776
+ try {
777
+ this.currentProvider.removeAllListeners("accountsChanged");
778
+ } catch (error) {
779
+ }
780
+ }
738
781
  const chainConfig = getChainConfig(chainId);
782
+ let connectedAddress;
739
783
  if (typeof account === "string") {
740
784
  let privateKey = account;
741
785
  if (!privateKey.startsWith("0x")) {
@@ -747,39 +791,72 @@ var ZyfaiSDK = class {
747
791
  chain: chainConfig.chain,
748
792
  transport: http3(chainConfig.rpcUrl)
749
793
  });
750
- return this.signer.address;
751
- }
752
- const provider = account;
753
- if (!provider) {
754
- throw new Error(
755
- "Invalid account parameter. Expected private key string or wallet provider."
756
- );
757
- }
758
- if (provider.request) {
759
- const accounts = await provider.request({
760
- method: "eth_requestAccounts"
761
- });
762
- if (!accounts || accounts.length === 0) {
763
- throw new Error("No accounts found in wallet provider");
794
+ connectedAddress = this.signer.address;
795
+ this.currentProvider = null;
796
+ } else {
797
+ const provider = account;
798
+ if (!provider) {
799
+ throw new Error(
800
+ "Invalid account parameter. Expected private key string or wallet provider."
801
+ );
802
+ }
803
+ if (provider.request) {
804
+ const accounts = await provider.request({
805
+ method: "eth_requestAccounts"
806
+ });
807
+ if (!accounts || accounts.length === 0) {
808
+ throw new Error("No accounts found in wallet provider");
809
+ }
810
+ this.walletClient = createWalletClient({
811
+ account: accounts[0],
812
+ chain: chainConfig.chain,
813
+ transport: custom(provider)
814
+ });
815
+ connectedAddress = accounts[0];
816
+ this.currentProvider = provider;
817
+ if (provider.on) {
818
+ provider.on("accountsChanged", this.handleAccountsChanged.bind(this));
819
+ }
820
+ } else if (provider.account && provider.transport) {
821
+ this.walletClient = createWalletClient({
822
+ account: provider.account,
823
+ chain: chainConfig.chain,
824
+ transport: provider.transport
825
+ });
826
+ connectedAddress = provider.account.address;
827
+ this.currentProvider = null;
828
+ } else {
829
+ throw new Error(
830
+ "Invalid wallet provider. Expected EIP-1193 provider or viem WalletClient."
831
+ );
764
832
  }
765
- this.walletClient = createWalletClient({
766
- account: accounts[0],
767
- chain: chainConfig.chain,
768
- transport: custom(provider)
769
- });
770
- return accounts[0];
771
833
  }
772
- if (provider.account && provider.transport) {
773
- this.walletClient = createWalletClient({
774
- account: provider.account,
775
- chain: chainConfig.chain,
776
- transport: provider.transport
777
- });
778
- return provider.account.address;
834
+ await this.authenticateUser();
835
+ return connectedAddress;
836
+ }
837
+ /**
838
+ * Disconnect account and clear authentication state
839
+ * Resets wallet connection, JWT token, and all authentication-related state
840
+ *
841
+ * @example
842
+ * ```typescript
843
+ * await sdk.disconnectAccount();
844
+ * console.log("Account disconnected");
845
+ * ```
846
+ */
847
+ async disconnectAccount() {
848
+ if (this.currentProvider?.removeAllListeners) {
849
+ try {
850
+ this.currentProvider.removeAllListeners("accountsChanged");
851
+ } catch (error) {
852
+ }
779
853
  }
780
- throw new Error(
781
- "Invalid wallet provider. Expected EIP-1193 provider or viem WalletClient."
782
- );
854
+ this.signer = null;
855
+ this.walletClient = null;
856
+ this.currentProvider = null;
857
+ this.authenticatedUserId = null;
858
+ this.hasActiveSessionKey = false;
859
+ this.httpClient.clearAuthToken();
783
860
  }
784
861
  /**
785
862
  * Get wallet client (throws if not connected)
@@ -1029,7 +1106,6 @@ var ZyfaiSDK = class {
1029
1106
  });
1030
1107
  return {
1031
1108
  success: true,
1032
- sessionKeyAddress: safeAddress,
1033
1109
  signature,
1034
1110
  sessionNonces
1035
1111
  };
@@ -1151,18 +1227,20 @@ var ZyfaiSDK = class {
1151
1227
  }
1152
1228
  /**
1153
1229
  * Withdraw funds from Safe smart wallet
1154
- * Triggers a withdrawal request to the ZyFAI API
1230
+ * Initiates a withdrawal request to the ZyFAI API
1231
+ * Note: The withdrawal is processed asynchronously, so txHash may not be immediately available
1155
1232
  *
1156
1233
  * @param userAddress - User's address (owner of the Safe)
1157
1234
  * @param chainId - Target chain ID
1158
1235
  * @param amount - Optional: Amount in least decimal units to withdraw (partial withdrawal). If not specified, withdraws all funds
1159
1236
  * @param receiver - Optional: Receiver address. If not specified, sends to Safe owner
1160
- * @returns Withdraw response with transaction hash
1237
+ * @returns Withdraw response with message and optional transaction hash (available once processed)
1161
1238
  *
1162
1239
  * @example
1163
1240
  * ```typescript
1164
1241
  * // Full withdrawal
1165
1242
  * const result = await sdk.withdrawFunds("0xUser...", 42161);
1243
+ * console.log(result.message); // "Withdrawal request sent"
1166
1244
  *
1167
1245
  * // Partial withdrawal of 50 USDC (6 decimals)
1168
1246
  * const result = await sdk.withdrawFunds(
@@ -1213,9 +1291,12 @@ var ZyfaiSDK = class {
1213
1291
  });
1214
1292
  }
1215
1293
  const success = response?.success ?? true;
1294
+ const message = response?.message || "Withdrawal request sent";
1295
+ const txHash = response?.txHash || response?.transactionHash;
1216
1296
  return {
1217
1297
  success,
1218
- txHash: response?.txHash || response?.transactionHash || "pending",
1298
+ message,
1299
+ txHash,
1219
1300
  type: amount ? "partial" : "full",
1220
1301
  amount: amount || "all",
1221
1302
  receiver: receiver || userAddress,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zyfai/sdk",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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",
@@ -28,7 +28,13 @@
28
28
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
29
29
  "lint": "eslint src --ext .ts",
30
30
  "test": "jest",
31
- "prepublishOnly": "npm run build"
31
+ "prepublishOnly": "npm run build",
32
+ "docs": "typedoc",
33
+ "docs:watch": "typedoc --watch",
34
+ "docs:json": "typedoc --json docs/api.json",
35
+ "docs:dev": "cd website && pnpm start",
36
+ "docs:build": "pnpm docs && cd website && pnpm build",
37
+ "docs:serve": "cd website && pnpm serve"
32
38
  },
33
39
  "keywords": [
34
40
  "zyfai",
@@ -70,6 +76,8 @@
70
76
  "@types/node": "^20.0.0",
71
77
  "dotenv": "^17.2.3",
72
78
  "tsup": "^8.0.0",
79
+ "typedoc": "^0.28.15",
80
+ "typedoc-plugin-markdown": "^4.9.0",
73
81
  "typescript": "^5.3.0"
74
82
  },
75
83
  "peerDependencies": {