@phantom/react-native-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
@@ -99,7 +99,6 @@ export default function App() {
99
99
  redirectUrl: "mywalletapp://phantom-auth-callback",
100
100
  },
101
101
  appName: "My Wallet App", // Optional branding
102
- debug: false, // Optional debug logging
103
102
  }}
104
103
  >
105
104
  <YourAppContent />
@@ -193,7 +192,7 @@ interface PhantomSDKConfig {
193
192
  organizationId: string; // Your Phantom organization ID
194
193
  scheme: string; // Custom URL scheme for your app
195
194
  embeddedWalletType: "user-wallet" | "app-wallet";
196
- addressTypes: AddressType[]; // e.g., [AddressType.solana]
195
+ addressTypes: [AddressType, ...AddressType[]]; // e.g., [AddressType.solana]
197
196
  apiBaseUrl: string; // e.g., "https://api.phantom.app/v1/wallets"
198
197
  solanaProvider: "web3js" | "kit"; // Solana provider to use
199
198
  authOptions?: {
@@ -203,7 +202,6 @@ interface PhantomSDKConfig {
203
202
  appName?: string; // Optional app name for branding
204
203
  appLogo?: string; // Optional app logo URL for branding
205
204
  autoConnect?: boolean; // Auto-connect to existing session on SDK instantiation (default: true)
206
- debug?: boolean; // Enable debug logging (optional)
207
205
  }
208
206
  ```
209
207
 
@@ -422,19 +420,40 @@ adb shell am start -W -a android.intent.action.VIEW -d "myapp://phantom-auth-cal
422
420
  - Verify URL scheme configuration
423
421
  - Check intent filters (Android) or URL schemes (iOS)
424
422
 
425
- ### Debug Mode
423
+ ### Debug Configuration
426
424
 
427
- Enable debug logging in development:
425
+ The React Native SDK supports separate debug configuration for better performance and dynamic control:
428
426
 
429
427
  ```typescript
430
- <PhantomProvider
431
- config={{
432
- ...config,
433
- debug: true // Enable debug logging
434
- }}
435
- >
436
- <App />
437
- </PhantomProvider>
428
+ import { PhantomProvider, type PhantomSDKConfig, type PhantomDebugConfig } from "@phantom/react-native-sdk";
429
+
430
+ function App() {
431
+ // SDK configuration - static, won't change when debug settings change
432
+ const config: PhantomSDKConfig = {
433
+ organizationId: "your-org-id",
434
+ scheme: "mywalletapp",
435
+ // ... other config
436
+ };
437
+
438
+ // Debug configuration - separate to avoid SDK reinstantiation
439
+ const debugConfig: PhantomDebugConfig = {
440
+ enabled: true, // Enable debug logging
441
+ };
442
+
443
+ return (
444
+ <PhantomProvider config={config} debugConfig={debugConfig}>
445
+ <App />
446
+ </PhantomProvider>
447
+ );
448
+ }
449
+ ```
450
+
451
+ **PhantomDebugConfig Interface:**
452
+
453
+ ```typescript
454
+ interface PhantomDebugConfig {
455
+ enabled?: boolean; // Enable debug logging (default: false)
456
+ }
438
457
  ```
439
458
 
440
459
  ## Support
package/dist/index.d.ts CHANGED
@@ -6,11 +6,13 @@ export { ConnectResult, SignAndSendTransactionParams, SignMessageParams, SignMes
6
6
  export { AddressType } from '@phantom/client';
7
7
  export { NetworkId } from '@phantom/constants';
8
8
 
9
+ interface PhantomDebugConfig {
10
+ /** Enable debug logging */
11
+ enabled?: boolean;
12
+ }
9
13
  interface PhantomSDKConfig extends EmbeddedProviderConfig {
10
14
  /** Custom URL scheme for your app (e.g., "myapp") */
11
15
  scheme: string;
12
- /** Enable debug logging */
13
- debug?: boolean;
14
16
  /** Enable auto-connect to existing sessions (default: true) */
15
17
  autoConnect?: boolean;
16
18
  }
@@ -24,7 +26,7 @@ interface ConnectOptions {
24
26
  }
25
27
 
26
28
  interface PhantomContextValue {
27
- sdk: EmbeddedProvider;
29
+ sdk: EmbeddedProvider | null;
28
30
  isConnected: boolean;
29
31
  isConnecting: boolean;
30
32
  connectError: Error | null;
@@ -35,8 +37,9 @@ interface PhantomContextValue {
35
37
  interface PhantomProviderProps {
36
38
  children: ReactNode;
37
39
  config: PhantomSDKConfig;
40
+ debugConfig?: PhantomDebugConfig;
38
41
  }
39
- declare function PhantomProvider({ children, config }: PhantomProviderProps): react_jsx_runtime.JSX.Element;
42
+ declare function PhantomProvider({ children, config, debugConfig }: PhantomProviderProps): react_jsx_runtime.JSX.Element;
40
43
  /**
41
44
  * Hook to access the Phantom context
42
45
  * Must be used within a PhantomProvider
@@ -73,4 +76,4 @@ declare function useSignAndSendTransaction(): {
73
76
  error: Error | null;
74
77
  };
75
78
 
76
- export { ConnectOptions, PhantomProvider, PhantomSDKConfig, useAccounts, useConnect, useDisconnect, usePhantom, useSignAndSendTransaction, useSignMessage };
79
+ export { ConnectOptions, PhantomDebugConfig, PhantomProvider, PhantomSDKConfig, useAccounts, useConnect, useDisconnect, usePhantom, useSignAndSendTransaction, useSignMessage };
package/dist/index.js CHANGED
@@ -295,7 +295,8 @@ var import_sdk_types = require("@phantom/sdk-types");
295
295
  var ReactNativeStamper = class {
296
296
  // Optional for PKI, required for OIDC
297
297
  constructor(config = {}) {
298
- this.keyInfo = null;
298
+ this.activeKeyRecord = null;
299
+ this.pendingKeyRecord = null;
299
300
  this.algorithm = import_sdk_types.Algorithm.ed25519;
300
301
  this.type = "PKI";
301
302
  this.keyPrefix = config.keyPrefix || "phantom-rn-stamper";
@@ -305,32 +306,27 @@ var ReactNativeStamper = class {
305
306
  * Initialize the stamper and generate/load cryptographic keys
306
307
  */
307
308
  async init() {
308
- const storedSecretKey = await this.getStoredSecretKey();
309
- if (storedSecretKey) {
310
- const keyInfo2 = await this.getStoredKeyInfo();
311
- if (keyInfo2) {
312
- this.keyInfo = keyInfo2;
313
- return keyInfo2;
314
- }
309
+ this.activeKeyRecord = await this.loadActiveKeyRecord();
310
+ if (!this.activeKeyRecord) {
311
+ this.activeKeyRecord = await this.generateAndStoreNewKeyRecord("active");
315
312
  }
316
- const keyInfo = await this.generateAndStoreKeyPair();
317
- this.keyInfo = keyInfo;
318
- return keyInfo;
313
+ this.pendingKeyRecord = await this.loadPendingKeyRecord();
314
+ return this.activeKeyRecord.keyInfo;
319
315
  }
320
316
  /**
321
317
  * Get the current key information
322
318
  */
323
319
  getKeyInfo() {
324
- return this.keyInfo;
320
+ return this.activeKeyRecord?.keyInfo || null;
325
321
  }
326
322
  /**
327
323
  * Generate and store a new key pair, replacing any existing keys
328
324
  */
329
325
  async resetKeyPair() {
330
326
  await this.clear();
331
- const keyInfo = await this.generateAndStoreKeyPair();
332
- this.keyInfo = keyInfo;
333
- return keyInfo;
327
+ this.activeKeyRecord = await this.generateAndStoreNewKeyRecord("active");
328
+ this.pendingKeyRecord = null;
329
+ return this.activeKeyRecord.keyInfo;
334
330
  }
335
331
  /**
336
332
  * Create X-Phantom-Stamp header value using stored secret key
@@ -338,79 +334,129 @@ var ReactNativeStamper = class {
338
334
  * @returns Complete X-Phantom-Stamp header value
339
335
  */
340
336
  async stamp(params) {
341
- if (!this.keyInfo) {
337
+ if (!this.activeKeyRecord) {
342
338
  throw new Error("Stamper not initialized. Call init() first.");
343
339
  }
344
- const storedSecretKey = await this.getStoredSecretKey();
345
- if (!storedSecretKey) {
346
- throw new Error("Secret key not found in secure storage");
347
- }
348
- const apiKeyStamper = new import_api_key_stamper.ApiKeyStamper({ apiSecretKey: storedSecretKey });
340
+ const apiKeyStamper = new import_api_key_stamper.ApiKeyStamper({ apiSecretKey: this.activeKeyRecord.secretKey });
349
341
  return await apiKeyStamper.stamp(params);
350
342
  }
351
343
  /**
352
344
  * Clear all stored keys from SecureStore
353
345
  */
354
346
  async clear() {
355
- const infoKey = this.getInfoKey();
356
- const secretKey = this.getSecretKey();
347
+ const activeKey = this.getActiveKeyName();
348
+ const pendingKey = this.getPendingKeyName();
349
+ try {
350
+ await SecureStore2.deleteItemAsync(activeKey);
351
+ } catch (error) {
352
+ }
357
353
  try {
358
- await SecureStore2.deleteItemAsync(infoKey);
354
+ await SecureStore2.deleteItemAsync(pendingKey);
359
355
  } catch (error) {
360
356
  }
357
+ this.activeKeyRecord = null;
358
+ this.pendingKeyRecord = null;
359
+ }
360
+ /**
361
+ * Generate a new keypair for rotation without making it active
362
+ */
363
+ async rotateKeyPair() {
364
+ this.pendingKeyRecord = await this.generateAndStoreNewKeyRecord("pending");
365
+ return this.pendingKeyRecord.keyInfo;
366
+ }
367
+ /**
368
+ * Switch to the pending keypair, making it active and cleaning up the old one
369
+ */
370
+ async commitRotation(authenticatorId) {
371
+ if (!this.pendingKeyRecord) {
372
+ throw new Error("No pending keypair to commit");
373
+ }
374
+ if (this.activeKeyRecord) {
375
+ try {
376
+ await SecureStore2.deleteItemAsync(this.getActiveKeyName());
377
+ } catch (error) {
378
+ }
379
+ }
380
+ this.pendingKeyRecord.status = "active";
381
+ this.pendingKeyRecord.authenticatorId = authenticatorId;
382
+ this.pendingKeyRecord.keyInfo.authenticatorId = authenticatorId;
383
+ this.activeKeyRecord = this.pendingKeyRecord;
384
+ this.pendingKeyRecord = null;
385
+ await this.storeKeyRecord(this.activeKeyRecord, "active");
386
+ try {
387
+ await SecureStore2.deleteItemAsync(this.getPendingKeyName());
388
+ } catch (error) {
389
+ }
390
+ }
391
+ /**
392
+ * Discard the pending keypair on rotation failure
393
+ */
394
+ async rollbackRotation() {
395
+ if (!this.pendingKeyRecord) {
396
+ return;
397
+ }
361
398
  try {
362
- await SecureStore2.deleteItemAsync(secretKey);
399
+ await SecureStore2.deleteItemAsync(this.getPendingKeyName());
363
400
  } catch (error) {
364
401
  }
365
- this.keyInfo = null;
402
+ this.pendingKeyRecord = null;
366
403
  }
367
- async generateAndStoreKeyPair() {
404
+ async generateAndStoreNewKeyRecord(type) {
368
405
  const keypair = (0, import_crypto.generateKeyPair)();
369
406
  const keyId = this.createKeyId(keypair.publicKey);
407
+ const now = Date.now();
370
408
  const keyInfo = {
371
409
  keyId,
372
- publicKey: keypair.publicKey
410
+ publicKey: keypair.publicKey,
411
+ createdAt: now
373
412
  };
374
- await this.storeKeyPair(keypair.secretKey, keyInfo);
375
- return keyInfo;
413
+ const record = {
414
+ keyInfo,
415
+ secretKey: keypair.secretKey,
416
+ createdAt: now,
417
+ expiresAt: 0,
418
+ // Not used anymore, kept for backward compatibility
419
+ status: type
420
+ };
421
+ await this.storeKeyRecord(record, type);
422
+ return record;
376
423
  }
377
424
  createKeyId(publicKey) {
378
425
  return (0, import_base64url.base64urlEncode)(new TextEncoder().encode(publicKey)).substring(0, 16);
379
426
  }
380
- async storeKeyPair(secretKey, keyInfo) {
381
- const infoKey = this.getInfoKey();
382
- const secretKeyName = this.getSecretKey();
383
- await SecureStore2.setItemAsync(infoKey, JSON.stringify(keyInfo), {
384
- requireAuthentication: false
385
- });
386
- await SecureStore2.setItemAsync(secretKeyName, secretKey, {
427
+ async storeKeyRecord(record, type) {
428
+ const keyName = type === "active" ? this.getActiveKeyName() : this.getPendingKeyName();
429
+ await SecureStore2.setItemAsync(keyName, JSON.stringify(record), {
387
430
  requireAuthentication: false
388
431
  });
389
432
  }
390
- async getStoredKeyInfo() {
433
+ async loadActiveKeyRecord() {
391
434
  try {
392
- const infoKey = this.getInfoKey();
393
- const storedInfo = await SecureStore2.getItemAsync(infoKey);
394
- if (storedInfo) {
395
- return JSON.parse(storedInfo);
435
+ const activeKey = this.getActiveKeyName();
436
+ const storedRecord = await SecureStore2.getItemAsync(activeKey);
437
+ if (storedRecord) {
438
+ return JSON.parse(storedRecord);
396
439
  }
397
440
  } catch (error) {
398
441
  }
399
442
  return null;
400
443
  }
401
- async getStoredSecretKey() {
444
+ async loadPendingKeyRecord() {
402
445
  try {
403
- const secretKeyName = this.getSecretKey();
404
- return await SecureStore2.getItemAsync(secretKeyName);
446
+ const pendingKey = this.getPendingKeyName();
447
+ const storedRecord = await SecureStore2.getItemAsync(pendingKey);
448
+ if (storedRecord) {
449
+ return JSON.parse(storedRecord);
450
+ }
405
451
  } catch (error) {
406
- return null;
407
452
  }
453
+ return null;
408
454
  }
409
- getInfoKey() {
410
- return `${this.keyPrefix}-${this.organizationId}-info`;
455
+ getActiveKeyName() {
456
+ return `${this.keyPrefix}-${this.organizationId}-active`;
411
457
  }
412
- getSecretKey() {
413
- return `${this.keyPrefix}-${this.organizationId}-secret`;
458
+ getPendingKeyName() {
459
+ return `${this.keyPrefix}-${this.organizationId}-pending`;
414
460
  }
415
461
  };
416
462
 
@@ -418,23 +464,31 @@ var ReactNativeStamper = class {
418
464
  var import_react_native2 = require("react-native");
419
465
  var import_jsx_runtime = require("react/jsx-runtime");
420
466
  var PhantomContext = (0, import_react.createContext)(void 0);
421
- function PhantomProvider({ children, config }) {
422
- const sdk = (0, import_react.useMemo)(() => {
467
+ function PhantomProvider({ children, config, debugConfig }) {
468
+ const [isConnected, setIsConnected] = (0, import_react.useState)(false);
469
+ const [isConnecting, setIsConnecting] = (0, import_react.useState)(false);
470
+ const [connectError, setConnectError] = (0, import_react.useState)(null);
471
+ const [addresses, setAddresses] = (0, import_react.useState)([]);
472
+ const [walletId, setWalletId] = (0, import_react.useState)(null);
473
+ const [sdk, setSdk] = (0, import_react.useState)(null);
474
+ const memoizedConfig = (0, import_react.useMemo)(() => {
423
475
  const redirectUrl = config.authOptions?.redirectUrl || `${config.scheme}://phantom-auth-callback`;
424
- const embeddedConfig = {
476
+ return {
425
477
  ...config,
426
478
  authOptions: {
427
479
  ...config.authOptions || {},
428
480
  redirectUrl
429
481
  }
430
482
  };
483
+ }, [config]);
484
+ (0, import_react.useEffect)(() => {
431
485
  const storage = new ExpoSecureStorage();
432
486
  const authProvider = new ExpoAuthProvider();
433
487
  const urlParamsAccessor = new ExpoURLParamsAccessor();
434
- const logger = new ExpoLogger(config.debug);
488
+ const logger = new ExpoLogger(debugConfig?.enabled || false);
435
489
  const stamper = new ReactNativeStamper({
436
- keyPrefix: `phantom-rn-${config.organizationId}`,
437
- organizationId: config.organizationId
490
+ keyPrefix: `phantom-rn-${memoizedConfig.organizationId}`,
491
+ organizationId: memoizedConfig.organizationId
438
492
  });
439
493
  const platform = {
440
494
  storage,
@@ -443,7 +497,7 @@ function PhantomProvider({ children, config }) {
443
497
  stamper,
444
498
  name: `${import_react_native2.Platform.OS}-${import_react_native2.Platform.Version}`
445
499
  };
446
- const sdkInstance = new import_embedded_provider_core.EmbeddedProvider(embeddedConfig, platform, logger);
500
+ const sdkInstance = new import_embedded_provider_core.EmbeddedProvider(memoizedConfig, platform, logger);
447
501
  const handleConnectStart = () => {
448
502
  setIsConnecting(true);
449
503
  setConnectError(null);
@@ -478,14 +532,17 @@ function PhantomProvider({ children, config }) {
478
532
  sdkInstance.on("connect", handleConnect);
479
533
  sdkInstance.on("connect_error", handleConnectError);
480
534
  sdkInstance.on("disconnect", handleDisconnect);
481
- return sdkInstance;
482
- }, [config]);
483
- const [isConnected, setIsConnected] = (0, import_react.useState)(false);
484
- const [isConnecting, setIsConnecting] = (0, import_react.useState)(false);
485
- const [connectError, setConnectError] = (0, import_react.useState)(null);
486
- const [addresses, setAddresses] = (0, import_react.useState)([]);
487
- const [walletId, setWalletId] = (0, import_react.useState)(null);
535
+ setSdk(sdkInstance);
536
+ return () => {
537
+ sdkInstance.off("connect_start", handleConnectStart);
538
+ sdkInstance.off("connect", handleConnect);
539
+ sdkInstance.off("connect_error", handleConnectError);
540
+ sdkInstance.off("disconnect", handleDisconnect);
541
+ };
542
+ }, [memoizedConfig, debugConfig]);
488
543
  (0, import_react.useEffect)(() => {
544
+ if (!sdk)
545
+ return;
489
546
  if (config.autoConnect !== false) {
490
547
  sdk.autoConnect().catch(() => {
491
548
  });
package/dist/index.mjs CHANGED
@@ -251,7 +251,8 @@ import { Algorithm } from "@phantom/sdk-types";
251
251
  var ReactNativeStamper = class {
252
252
  // Optional for PKI, required for OIDC
253
253
  constructor(config = {}) {
254
- this.keyInfo = null;
254
+ this.activeKeyRecord = null;
255
+ this.pendingKeyRecord = null;
255
256
  this.algorithm = Algorithm.ed25519;
256
257
  this.type = "PKI";
257
258
  this.keyPrefix = config.keyPrefix || "phantom-rn-stamper";
@@ -261,32 +262,27 @@ var ReactNativeStamper = class {
261
262
  * Initialize the stamper and generate/load cryptographic keys
262
263
  */
263
264
  async init() {
264
- const storedSecretKey = await this.getStoredSecretKey();
265
- if (storedSecretKey) {
266
- const keyInfo2 = await this.getStoredKeyInfo();
267
- if (keyInfo2) {
268
- this.keyInfo = keyInfo2;
269
- return keyInfo2;
270
- }
265
+ this.activeKeyRecord = await this.loadActiveKeyRecord();
266
+ if (!this.activeKeyRecord) {
267
+ this.activeKeyRecord = await this.generateAndStoreNewKeyRecord("active");
271
268
  }
272
- const keyInfo = await this.generateAndStoreKeyPair();
273
- this.keyInfo = keyInfo;
274
- return keyInfo;
269
+ this.pendingKeyRecord = await this.loadPendingKeyRecord();
270
+ return this.activeKeyRecord.keyInfo;
275
271
  }
276
272
  /**
277
273
  * Get the current key information
278
274
  */
279
275
  getKeyInfo() {
280
- return this.keyInfo;
276
+ return this.activeKeyRecord?.keyInfo || null;
281
277
  }
282
278
  /**
283
279
  * Generate and store a new key pair, replacing any existing keys
284
280
  */
285
281
  async resetKeyPair() {
286
282
  await this.clear();
287
- const keyInfo = await this.generateAndStoreKeyPair();
288
- this.keyInfo = keyInfo;
289
- return keyInfo;
283
+ this.activeKeyRecord = await this.generateAndStoreNewKeyRecord("active");
284
+ this.pendingKeyRecord = null;
285
+ return this.activeKeyRecord.keyInfo;
290
286
  }
291
287
  /**
292
288
  * Create X-Phantom-Stamp header value using stored secret key
@@ -294,79 +290,129 @@ var ReactNativeStamper = class {
294
290
  * @returns Complete X-Phantom-Stamp header value
295
291
  */
296
292
  async stamp(params) {
297
- if (!this.keyInfo) {
293
+ if (!this.activeKeyRecord) {
298
294
  throw new Error("Stamper not initialized. Call init() first.");
299
295
  }
300
- const storedSecretKey = await this.getStoredSecretKey();
301
- if (!storedSecretKey) {
302
- throw new Error("Secret key not found in secure storage");
303
- }
304
- const apiKeyStamper = new ApiKeyStamper({ apiSecretKey: storedSecretKey });
296
+ const apiKeyStamper = new ApiKeyStamper({ apiSecretKey: this.activeKeyRecord.secretKey });
305
297
  return await apiKeyStamper.stamp(params);
306
298
  }
307
299
  /**
308
300
  * Clear all stored keys from SecureStore
309
301
  */
310
302
  async clear() {
311
- const infoKey = this.getInfoKey();
312
- const secretKey = this.getSecretKey();
303
+ const activeKey = this.getActiveKeyName();
304
+ const pendingKey = this.getPendingKeyName();
305
+ try {
306
+ await SecureStore2.deleteItemAsync(activeKey);
307
+ } catch (error) {
308
+ }
313
309
  try {
314
- await SecureStore2.deleteItemAsync(infoKey);
310
+ await SecureStore2.deleteItemAsync(pendingKey);
315
311
  } catch (error) {
316
312
  }
313
+ this.activeKeyRecord = null;
314
+ this.pendingKeyRecord = null;
315
+ }
316
+ /**
317
+ * Generate a new keypair for rotation without making it active
318
+ */
319
+ async rotateKeyPair() {
320
+ this.pendingKeyRecord = await this.generateAndStoreNewKeyRecord("pending");
321
+ return this.pendingKeyRecord.keyInfo;
322
+ }
323
+ /**
324
+ * Switch to the pending keypair, making it active and cleaning up the old one
325
+ */
326
+ async commitRotation(authenticatorId) {
327
+ if (!this.pendingKeyRecord) {
328
+ throw new Error("No pending keypair to commit");
329
+ }
330
+ if (this.activeKeyRecord) {
331
+ try {
332
+ await SecureStore2.deleteItemAsync(this.getActiveKeyName());
333
+ } catch (error) {
334
+ }
335
+ }
336
+ this.pendingKeyRecord.status = "active";
337
+ this.pendingKeyRecord.authenticatorId = authenticatorId;
338
+ this.pendingKeyRecord.keyInfo.authenticatorId = authenticatorId;
339
+ this.activeKeyRecord = this.pendingKeyRecord;
340
+ this.pendingKeyRecord = null;
341
+ await this.storeKeyRecord(this.activeKeyRecord, "active");
342
+ try {
343
+ await SecureStore2.deleteItemAsync(this.getPendingKeyName());
344
+ } catch (error) {
345
+ }
346
+ }
347
+ /**
348
+ * Discard the pending keypair on rotation failure
349
+ */
350
+ async rollbackRotation() {
351
+ if (!this.pendingKeyRecord) {
352
+ return;
353
+ }
317
354
  try {
318
- await SecureStore2.deleteItemAsync(secretKey);
355
+ await SecureStore2.deleteItemAsync(this.getPendingKeyName());
319
356
  } catch (error) {
320
357
  }
321
- this.keyInfo = null;
358
+ this.pendingKeyRecord = null;
322
359
  }
323
- async generateAndStoreKeyPair() {
360
+ async generateAndStoreNewKeyRecord(type) {
324
361
  const keypair = generateKeyPair();
325
362
  const keyId = this.createKeyId(keypair.publicKey);
363
+ const now = Date.now();
326
364
  const keyInfo = {
327
365
  keyId,
328
- publicKey: keypair.publicKey
366
+ publicKey: keypair.publicKey,
367
+ createdAt: now
329
368
  };
330
- await this.storeKeyPair(keypair.secretKey, keyInfo);
331
- return keyInfo;
369
+ const record = {
370
+ keyInfo,
371
+ secretKey: keypair.secretKey,
372
+ createdAt: now,
373
+ expiresAt: 0,
374
+ // Not used anymore, kept for backward compatibility
375
+ status: type
376
+ };
377
+ await this.storeKeyRecord(record, type);
378
+ return record;
332
379
  }
333
380
  createKeyId(publicKey) {
334
381
  return base64urlEncode(new TextEncoder().encode(publicKey)).substring(0, 16);
335
382
  }
336
- async storeKeyPair(secretKey, keyInfo) {
337
- const infoKey = this.getInfoKey();
338
- const secretKeyName = this.getSecretKey();
339
- await SecureStore2.setItemAsync(infoKey, JSON.stringify(keyInfo), {
340
- requireAuthentication: false
341
- });
342
- await SecureStore2.setItemAsync(secretKeyName, secretKey, {
383
+ async storeKeyRecord(record, type) {
384
+ const keyName = type === "active" ? this.getActiveKeyName() : this.getPendingKeyName();
385
+ await SecureStore2.setItemAsync(keyName, JSON.stringify(record), {
343
386
  requireAuthentication: false
344
387
  });
345
388
  }
346
- async getStoredKeyInfo() {
389
+ async loadActiveKeyRecord() {
347
390
  try {
348
- const infoKey = this.getInfoKey();
349
- const storedInfo = await SecureStore2.getItemAsync(infoKey);
350
- if (storedInfo) {
351
- return JSON.parse(storedInfo);
391
+ const activeKey = this.getActiveKeyName();
392
+ const storedRecord = await SecureStore2.getItemAsync(activeKey);
393
+ if (storedRecord) {
394
+ return JSON.parse(storedRecord);
352
395
  }
353
396
  } catch (error) {
354
397
  }
355
398
  return null;
356
399
  }
357
- async getStoredSecretKey() {
400
+ async loadPendingKeyRecord() {
358
401
  try {
359
- const secretKeyName = this.getSecretKey();
360
- return await SecureStore2.getItemAsync(secretKeyName);
402
+ const pendingKey = this.getPendingKeyName();
403
+ const storedRecord = await SecureStore2.getItemAsync(pendingKey);
404
+ if (storedRecord) {
405
+ return JSON.parse(storedRecord);
406
+ }
361
407
  } catch (error) {
362
- return null;
363
408
  }
409
+ return null;
364
410
  }
365
- getInfoKey() {
366
- return `${this.keyPrefix}-${this.organizationId}-info`;
411
+ getActiveKeyName() {
412
+ return `${this.keyPrefix}-${this.organizationId}-active`;
367
413
  }
368
- getSecretKey() {
369
- return `${this.keyPrefix}-${this.organizationId}-secret`;
414
+ getPendingKeyName() {
415
+ return `${this.keyPrefix}-${this.organizationId}-pending`;
370
416
  }
371
417
  };
372
418
 
@@ -374,23 +420,31 @@ var ReactNativeStamper = class {
374
420
  import { Platform } from "react-native";
375
421
  import { jsx } from "react/jsx-runtime";
376
422
  var PhantomContext = createContext(void 0);
377
- function PhantomProvider({ children, config }) {
378
- const sdk = useMemo(() => {
423
+ function PhantomProvider({ children, config, debugConfig }) {
424
+ const [isConnected, setIsConnected] = useState(false);
425
+ const [isConnecting, setIsConnecting] = useState(false);
426
+ const [connectError, setConnectError] = useState(null);
427
+ const [addresses, setAddresses] = useState([]);
428
+ const [walletId, setWalletId] = useState(null);
429
+ const [sdk, setSdk] = useState(null);
430
+ const memoizedConfig = useMemo(() => {
379
431
  const redirectUrl = config.authOptions?.redirectUrl || `${config.scheme}://phantom-auth-callback`;
380
- const embeddedConfig = {
432
+ return {
381
433
  ...config,
382
434
  authOptions: {
383
435
  ...config.authOptions || {},
384
436
  redirectUrl
385
437
  }
386
438
  };
439
+ }, [config]);
440
+ useEffect(() => {
387
441
  const storage = new ExpoSecureStorage();
388
442
  const authProvider = new ExpoAuthProvider();
389
443
  const urlParamsAccessor = new ExpoURLParamsAccessor();
390
- const logger = new ExpoLogger(config.debug);
444
+ const logger = new ExpoLogger(debugConfig?.enabled || false);
391
445
  const stamper = new ReactNativeStamper({
392
- keyPrefix: `phantom-rn-${config.organizationId}`,
393
- organizationId: config.organizationId
446
+ keyPrefix: `phantom-rn-${memoizedConfig.organizationId}`,
447
+ organizationId: memoizedConfig.organizationId
394
448
  });
395
449
  const platform = {
396
450
  storage,
@@ -399,7 +453,7 @@ function PhantomProvider({ children, config }) {
399
453
  stamper,
400
454
  name: `${Platform.OS}-${Platform.Version}`
401
455
  };
402
- const sdkInstance = new EmbeddedProvider(embeddedConfig, platform, logger);
456
+ const sdkInstance = new EmbeddedProvider(memoizedConfig, platform, logger);
403
457
  const handleConnectStart = () => {
404
458
  setIsConnecting(true);
405
459
  setConnectError(null);
@@ -434,14 +488,17 @@ function PhantomProvider({ children, config }) {
434
488
  sdkInstance.on("connect", handleConnect);
435
489
  sdkInstance.on("connect_error", handleConnectError);
436
490
  sdkInstance.on("disconnect", handleDisconnect);
437
- return sdkInstance;
438
- }, [config]);
439
- const [isConnected, setIsConnected] = useState(false);
440
- const [isConnecting, setIsConnecting] = useState(false);
441
- const [connectError, setConnectError] = useState(null);
442
- const [addresses, setAddresses] = useState([]);
443
- const [walletId, setWalletId] = useState(null);
491
+ setSdk(sdkInstance);
492
+ return () => {
493
+ sdkInstance.off("connect_start", handleConnectStart);
494
+ sdkInstance.off("connect", handleConnect);
495
+ sdkInstance.off("connect_error", handleConnectError);
496
+ sdkInstance.off("disconnect", handleDisconnect);
497
+ };
498
+ }, [memoizedConfig, debugConfig]);
444
499
  useEffect(() => {
500
+ if (!sdk)
501
+ return;
445
502
  if (config.autoConnect !== false) {
446
503
  sdk.autoConnect().catch(() => {
447
504
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phantom/react-native-sdk",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Phantom Wallet SDK for React Native and Expo applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -47,11 +47,11 @@
47
47
  "dependencies": {
48
48
  "@phantom/api-key-stamper": "^0.1.5",
49
49
  "@phantom/base64url": "^0.1.0",
50
- "@phantom/client": "^0.1.8",
50
+ "@phantom/client": "^0.1.10",
51
51
  "@phantom/constants": "^0.0.3",
52
52
  "@phantom/crypto": "^0.1.2",
53
- "@phantom/embedded-provider-core": "^0.1.6",
54
- "@phantom/sdk-types": "^0.1.4",
53
+ "@phantom/embedded-provider-core": "^0.1.9",
54
+ "@phantom/sdk-types": "^0.1.5",
55
55
  "@types/bs58": "^5.0.0",
56
56
  "bs58": "^6.0.0",
57
57
  "buffer": "^6.0.3"