@phantom/react-native-sdk 0.1.0 → 0.1.2

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
@@ -94,9 +94,12 @@ export default function App() {
94
94
  embeddedWalletType: "user-wallet",
95
95
  addressTypes: [AddressType.solana],
96
96
  apiBaseUrl: "https://api.phantom.app/v1/wallets",
97
+ solanaProvider: "web3js",
97
98
  authOptions: {
98
99
  redirectUrl: "mywalletapp://phantom-auth-callback",
99
100
  },
101
+ appName: "My Wallet App", // Optional branding
102
+ debug: false, // Optional debug logging
100
103
  }}
101
104
  >
102
105
  <YourAppContent />
@@ -115,7 +118,7 @@ import { useConnect, useAccounts, useSignMessage, useDisconnect } from "@phantom
115
118
 
116
119
  export function WalletScreen() {
117
120
  const { connect, isConnecting, error: connectError } = useConnect();
118
- const { addresses, isConnected, walletId } = useAccounts();
121
+ const { addresses, isConnected } = useAccounts();
119
122
  const { signMessage, isSigning } = useSignMessage();
120
123
  const { disconnect } = useDisconnect();
121
124
 
@@ -156,7 +159,6 @@ export function WalletScreen() {
156
159
  return (
157
160
  <View style={{ padding: 20 }}>
158
161
  <Text style={{ fontSize: 18, marginBottom: 10 }}>Wallet Connected</Text>
159
- <Text>Wallet ID: {walletId}</Text>
160
162
  <Text>Address: {addresses[0]?.address}</Text>
161
163
 
162
164
  <Button
@@ -187,16 +189,20 @@ The main provider component that initializes the SDK and provides context to all
187
189
  #### Configuration Options
188
190
 
189
191
  ```typescript
190
- interface PhantomProviderConfig {
192
+ interface PhantomSDKConfig {
191
193
  organizationId: string; // Your Phantom organization ID
192
194
  scheme: string; // Custom URL scheme for your app
193
195
  embeddedWalletType: "user-wallet" | "app-wallet";
194
- addressTypes: AddressType[];
195
- apiBaseUrl: "https://api.phantom.app/v1/wallets";
196
+ addressTypes: AddressType[]; // e.g., [AddressType.solana]
197
+ apiBaseUrl: string; // e.g., "https://api.phantom.app/v1/wallets"
198
+ solanaProvider: "web3js" | "kit"; // Solana provider to use
196
199
  authOptions?: {
197
200
  authUrl?: string; // Custom auth URL (optional)
198
201
  redirectUrl?: string; // Custom redirect URL (optional)
199
202
  };
203
+ appName?: string; // Optional app name for branding
204
+ appLogo?: string; // Optional app logo URL for branding
205
+ debug?: boolean; // Enable debug logging (optional)
200
206
  }
201
207
  ```
202
208
 
package/dist/index.d.ts CHANGED
@@ -1,22 +1,17 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
3
  import * as _phantom_embedded_provider_core from '@phantom/embedded-provider-core';
4
- import { EmbeddedProviderConfig, AuthOptions, EmbeddedProvider, WalletAddress, ConnectResult, SignMessageParams, SignAndSendTransactionParams, SignedTransaction } from '@phantom/embedded-provider-core';
5
- export { ConnectResult, SignAndSendTransactionParams, SignMessageParams, SignedTransaction, WalletAddress } from '@phantom/embedded-provider-core';
6
- export { AddressType, NetworkId } from '@phantom/client';
4
+ import { EmbeddedProviderConfig, EmbeddedProvider, WalletAddress, ConnectResult, SignMessageParams, SignMessageResult, SignAndSendTransactionParams, SignedTransaction } from '@phantom/embedded-provider-core';
5
+ export { ConnectResult, SignAndSendTransactionParams, SignMessageParams, SignMessageResult, SignedTransaction, WalletAddress } from '@phantom/embedded-provider-core';
6
+ export { AddressType } from '@phantom/client';
7
+ export { NetworkId } from '@phantom/constants';
7
8
 
8
- interface PhantomProviderConfig extends Omit<EmbeddedProviderConfig, "authOptions"> {
9
+ interface PhantomSDKConfig extends EmbeddedProviderConfig {
9
10
  /** Custom URL scheme for your app (e.g., "myapp") */
10
11
  scheme: string;
11
- /** Authentication options */
12
- authOptions?: ReactNativeAuthOptions;
13
12
  /** Enable debug logging */
14
13
  debug?: boolean;
15
14
  }
16
- interface ReactNativeAuthOptions extends AuthOptions {
17
- /** Custom redirect URL - defaults to {scheme}://phantom-auth-callback */
18
- redirectUrl?: string;
19
- }
20
15
  interface ConnectOptions {
21
16
  /** OAuth provider to use */
22
17
  provider?: "google" | "apple" | "jwt";
@@ -37,7 +32,7 @@ interface PhantomContextValue {
37
32
  }
38
33
  interface PhantomProviderProps {
39
34
  children: ReactNode;
40
- config: PhantomProviderConfig;
35
+ config: PhantomSDKConfig;
41
36
  }
42
37
  declare function PhantomProvider({ children, config }: PhantomProviderProps): react_jsx_runtime.JSX.Element;
43
38
  /**
@@ -66,7 +61,7 @@ declare function useAccounts(): {
66
61
  };
67
62
 
68
63
  declare function useSignMessage(): {
69
- signMessage: (params: SignMessageParams) => Promise<string>;
64
+ signMessage: (params: SignMessageParams) => Promise<SignMessageResult>;
70
65
  isSigning: boolean;
71
66
  error: Error | null;
72
67
  };
@@ -77,4 +72,4 @@ declare function useSignAndSendTransaction(): {
77
72
  error: Error | null;
78
73
  };
79
74
 
80
- export { ConnectOptions, PhantomProvider, PhantomProviderConfig, ReactNativeAuthOptions, useAccounts, useConnect, useDisconnect, usePhantom, useSignAndSendTransaction, useSignMessage };
75
+ export { ConnectOptions, PhantomProvider, PhantomSDKConfig, useAccounts, useConnect, useDisconnect, usePhantom, useSignAndSendTransaction, useSignMessage };
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  AddressType: () => import_client.AddressType,
34
- NetworkId: () => import_client.NetworkId,
34
+ NetworkId: () => import_constants.NetworkId,
35
35
  PhantomProvider: () => PhantomProvider,
36
36
  useAccounts: () => useAccounts,
37
37
  useConnect: () => useConnect,
@@ -96,22 +96,66 @@ var ExpoSecureStorage = class {
96
96
 
97
97
  // src/providers/embedded/auth.ts
98
98
  var WebBrowser = __toESM(require("expo-web-browser"));
99
+ var DEFAULT_AUTH_URL = "https://auth.phantom.app";
99
100
  var ExpoAuthProvider = class {
100
101
  async authenticate(options) {
101
102
  if ("jwtToken" in options) {
102
103
  return;
103
104
  }
104
- const { authUrl, redirectUrl } = options;
105
- if (!authUrl || !redirectUrl) {
106
- throw new Error("authUrl and redirectUrl are required for web browser authentication");
105
+ const phantomOptions = options;
106
+ const {
107
+ authUrl,
108
+ redirectUrl,
109
+ organizationId,
110
+ parentOrganizationId,
111
+ sessionId,
112
+ provider,
113
+ customAuthData,
114
+ appName,
115
+ appLogo
116
+ } = phantomOptions;
117
+ if (!redirectUrl) {
118
+ throw new Error("redirectUrl is required for web browser authentication");
119
+ }
120
+ if (!organizationId || !sessionId) {
121
+ throw new Error("organizationId and sessionId are required for authentication");
107
122
  }
108
123
  try {
124
+ const baseUrl = authUrl || DEFAULT_AUTH_URL;
125
+ const params = new URLSearchParams({
126
+ organization_id: organizationId,
127
+ parent_organization_id: parentOrganizationId,
128
+ redirect_uri: redirectUrl,
129
+ session_id: sessionId,
130
+ clear_previous_session: "true",
131
+ app_name: appName || "",
132
+ // Optional app name
133
+ app_logo: appLogo || ""
134
+ // Optional app logo URL
135
+ });
136
+ if (provider) {
137
+ console.log("[ExpoAuthProvider] Provider specified, will skip selection", { provider });
138
+ params.append("provider", provider);
139
+ } else {
140
+ console.log("[ExpoAuthProvider] No provider specified, defaulting to Google");
141
+ params.append("provider", "google");
142
+ }
143
+ if (customAuthData) {
144
+ console.log("[ExpoAuthProvider] Adding custom auth data");
145
+ params.append("authData", JSON.stringify(customAuthData));
146
+ }
147
+ const fullAuthUrl = `${baseUrl}?${params.toString()}`;
109
148
  console.log("[ExpoAuthProvider] Starting authentication", {
110
- authUrl: authUrl.substring(0, 50) + "...",
111
- redirectUrl
149
+ baseUrl,
150
+ redirectUrl,
151
+ organizationId,
152
+ parentOrganizationId,
153
+ sessionId,
154
+ provider,
155
+ hasCustomData: !!customAuthData
112
156
  });
113
157
  await WebBrowser.warmUpAsync();
114
- const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUrl, {
158
+ const result = await WebBrowser.openAuthSessionAsync(fullAuthUrl, redirectUrl, {
115
159
  // Use system browser on iOS for ASWebAuthenticationSession
116
160
  preferEphemeralSession: false
117
161
  });
@@ -121,19 +165,14 @@ var ExpoAuthProvider = class {
121
165
  });
122
166
  if (result.type === "success" && result.url) {
123
167
  const url = new URL(result.url);
124
- const walletId = url.searchParams.get("walletId");
125
- const provider = url.searchParams.get("provider");
168
+ const walletId = url.searchParams.get("wallet_id");
169
+ const provider2 = url.searchParams.get("provider");
126
170
  if (!walletId) {
127
171
  throw new Error("Authentication failed: no walletId in redirect URL");
128
172
  }
129
- const userInfo = {};
130
- url.searchParams.forEach((value, key) => {
131
- userInfo[key] = value;
132
- });
133
173
  return {
134
174
  walletId,
135
- provider: provider || void 0,
136
- userInfo
175
+ provider: provider2 || void 0
137
176
  };
138
177
  } else if (result.type === "cancel") {
139
178
  throw new Error("User cancelled authentication");
@@ -247,7 +286,134 @@ var ExpoLogger = class {
247
286
  }
248
287
  };
249
288
 
289
+ // src/providers/embedded/stamper.ts
290
+ var SecureStore2 = __toESM(require("expo-secure-store"));
291
+ var import_api_key_stamper = require("@phantom/api-key-stamper");
292
+ var import_crypto = require("@phantom/crypto");
293
+ var import_base64url = require("@phantom/base64url");
294
+ var import_sdk_types = require("@phantom/sdk-types");
295
+ var ReactNativeStamper = class {
296
+ constructor(config = {}) {
297
+ this.keyInfo = null;
298
+ this.algorithm = import_sdk_types.Algorithm.ed25519;
299
+ this.keyPrefix = config.keyPrefix || "phantom-rn-stamper";
300
+ this.organizationId = config.organizationId || "default";
301
+ }
302
+ /**
303
+ * Initialize the stamper and generate/load cryptographic keys
304
+ */
305
+ async init() {
306
+ const storedSecretKey = await this.getStoredSecretKey();
307
+ if (storedSecretKey) {
308
+ const keyInfo2 = await this.getStoredKeyInfo();
309
+ if (keyInfo2) {
310
+ this.keyInfo = keyInfo2;
311
+ return keyInfo2;
312
+ }
313
+ }
314
+ const keyInfo = await this.generateAndStoreKeyPair();
315
+ this.keyInfo = keyInfo;
316
+ return keyInfo;
317
+ }
318
+ /**
319
+ * Get the current key information
320
+ */
321
+ getKeyInfo() {
322
+ return this.keyInfo;
323
+ }
324
+ /**
325
+ * Generate and store a new key pair, replacing any existing keys
326
+ */
327
+ async resetKeyPair() {
328
+ await this.clear();
329
+ const keyInfo = await this.generateAndStoreKeyPair();
330
+ this.keyInfo = keyInfo;
331
+ return keyInfo;
332
+ }
333
+ /**
334
+ * Create X-Phantom-Stamp header value using stored secret key
335
+ * @param data - Data to sign (Buffer)
336
+ * @returns Complete X-Phantom-Stamp header value
337
+ */
338
+ async stamp({ data }) {
339
+ if (!this.keyInfo) {
340
+ throw new Error("Stamper not initialized. Call init() first.");
341
+ }
342
+ const storedSecretKey = await this.getStoredSecretKey();
343
+ if (!storedSecretKey) {
344
+ throw new Error("Secret key not found in secure storage");
345
+ }
346
+ const apiKeyStamper = new import_api_key_stamper.ApiKeyStamper({ apiSecretKey: storedSecretKey });
347
+ return await apiKeyStamper.stamp({ data });
348
+ }
349
+ /**
350
+ * Clear all stored keys from SecureStore
351
+ */
352
+ async clear() {
353
+ const infoKey = this.getInfoKey();
354
+ const secretKey = this.getSecretKey();
355
+ try {
356
+ await SecureStore2.deleteItemAsync(infoKey);
357
+ } catch (error) {
358
+ }
359
+ try {
360
+ await SecureStore2.deleteItemAsync(secretKey);
361
+ } catch (error) {
362
+ }
363
+ this.keyInfo = null;
364
+ }
365
+ async generateAndStoreKeyPair() {
366
+ const keypair = (0, import_crypto.generateKeyPair)();
367
+ const keyId = this.createKeyId(keypair.publicKey);
368
+ const keyInfo = {
369
+ keyId,
370
+ publicKey: keypair.publicKey
371
+ };
372
+ await this.storeKeyPair(keypair.secretKey, keyInfo);
373
+ return keyInfo;
374
+ }
375
+ createKeyId(publicKey) {
376
+ return (0, import_base64url.base64urlEncode)(new TextEncoder().encode(publicKey)).substring(0, 16);
377
+ }
378
+ async storeKeyPair(secretKey, keyInfo) {
379
+ const infoKey = this.getInfoKey();
380
+ const secretKeyName = this.getSecretKey();
381
+ await SecureStore2.setItemAsync(infoKey, JSON.stringify(keyInfo), {
382
+ requireAuthentication: false
383
+ });
384
+ await SecureStore2.setItemAsync(secretKeyName, secretKey, {
385
+ requireAuthentication: false
386
+ });
387
+ }
388
+ async getStoredKeyInfo() {
389
+ try {
390
+ const infoKey = this.getInfoKey();
391
+ const storedInfo = await SecureStore2.getItemAsync(infoKey);
392
+ if (storedInfo) {
393
+ return JSON.parse(storedInfo);
394
+ }
395
+ } catch (error) {
396
+ }
397
+ return null;
398
+ }
399
+ async getStoredSecretKey() {
400
+ try {
401
+ const secretKeyName = this.getSecretKey();
402
+ return await SecureStore2.getItemAsync(secretKeyName);
403
+ } catch (error) {
404
+ return null;
405
+ }
406
+ }
407
+ getInfoKey() {
408
+ return `${this.keyPrefix}-${this.organizationId}-info`;
409
+ }
410
+ getSecretKey() {
411
+ return `${this.keyPrefix}-${this.organizationId}-secret`;
412
+ }
413
+ };
414
+
250
415
  // src/PhantomProvider.tsx
416
+ var import_react_native2 = require("react-native");
251
417
  var import_jsx_runtime = require("react/jsx-runtime");
252
418
  var PhantomContext = (0, import_react.createContext)(void 0);
253
419
  function PhantomProvider({ children, config }) {
@@ -262,16 +428,25 @@ function PhantomProvider({ children, config }) {
262
428
  },
263
429
  embeddedWalletType: config.embeddedWalletType,
264
430
  addressTypes: config.addressTypes,
265
- solanaProvider: config.solanaProvider || "web3js"
431
+ solanaProvider: config.solanaProvider || "web3js",
432
+ appName: config.appName,
433
+ appLogo: config.appLogo
434
+ // Optional app logo URL
266
435
  };
267
436
  const storage = new ExpoSecureStorage();
268
437
  const authProvider = new ExpoAuthProvider();
269
438
  const urlParamsAccessor = new ExpoURLParamsAccessor();
270
439
  const logger = new ExpoLogger(config.debug);
440
+ const stamper = new ReactNativeStamper({
441
+ keyPrefix: `phantom-rn-${config.organizationId}`,
442
+ organizationId: config.organizationId
443
+ });
271
444
  const platform = {
272
445
  storage,
273
446
  authProvider,
274
- urlParamsAccessor
447
+ urlParamsAccessor,
448
+ stamper,
449
+ name: `${import_react_native2.Platform.OS}-${import_react_native2.Platform.Version}`
275
450
  };
276
451
  return new import_embedded_provider_core.EmbeddedProvider(embeddedConfig, platform, logger);
277
452
  }, [config]);
@@ -293,6 +468,14 @@ function PhantomProvider({ children, config }) {
293
468
  } catch (err) {
294
469
  console.error("[PhantomProvider] Error updating connection state", err);
295
470
  setError(err);
471
+ try {
472
+ sdk.disconnect();
473
+ setIsConnected(false);
474
+ setAddresses([]);
475
+ setWalletId(null);
476
+ } catch (disconnectErr) {
477
+ console.error("[PhantomProvider] Error disconnecting after error", disconnectErr);
478
+ }
296
479
  }
297
480
  }, [sdk]);
298
481
  (0, import_react.useEffect)(() => {
@@ -465,6 +648,7 @@ function useSignAndSendTransaction() {
465
648
 
466
649
  // src/index.ts
467
650
  var import_client = require("@phantom/client");
651
+ var import_constants = require("@phantom/constants");
468
652
  // Annotate the CommonJS export names for ESM import in node:
469
653
  0 && (module.exports = {
470
654
  AddressType,
package/dist/index.mjs CHANGED
@@ -52,22 +52,66 @@ var ExpoSecureStorage = class {
52
52
 
53
53
  // src/providers/embedded/auth.ts
54
54
  import * as WebBrowser from "expo-web-browser";
55
+ var DEFAULT_AUTH_URL = "https://auth.phantom.app";
55
56
  var ExpoAuthProvider = class {
56
57
  async authenticate(options) {
57
58
  if ("jwtToken" in options) {
58
59
  return;
59
60
  }
60
- const { authUrl, redirectUrl } = options;
61
- if (!authUrl || !redirectUrl) {
62
- throw new Error("authUrl and redirectUrl are required for web browser authentication");
61
+ const phantomOptions = options;
62
+ const {
63
+ authUrl,
64
+ redirectUrl,
65
+ organizationId,
66
+ parentOrganizationId,
67
+ sessionId,
68
+ provider,
69
+ customAuthData,
70
+ appName,
71
+ appLogo
72
+ } = phantomOptions;
73
+ if (!redirectUrl) {
74
+ throw new Error("redirectUrl is required for web browser authentication");
75
+ }
76
+ if (!organizationId || !sessionId) {
77
+ throw new Error("organizationId and sessionId are required for authentication");
63
78
  }
64
79
  try {
80
+ const baseUrl = authUrl || DEFAULT_AUTH_URL;
81
+ const params = new URLSearchParams({
82
+ organization_id: organizationId,
83
+ parent_organization_id: parentOrganizationId,
84
+ redirect_uri: redirectUrl,
85
+ session_id: sessionId,
86
+ clear_previous_session: "true",
87
+ app_name: appName || "",
88
+ // Optional app name
89
+ app_logo: appLogo || ""
90
+ // Optional app logo URL
91
+ });
92
+ if (provider) {
93
+ console.log("[ExpoAuthProvider] Provider specified, will skip selection", { provider });
94
+ params.append("provider", provider);
95
+ } else {
96
+ console.log("[ExpoAuthProvider] No provider specified, defaulting to Google");
97
+ params.append("provider", "google");
98
+ }
99
+ if (customAuthData) {
100
+ console.log("[ExpoAuthProvider] Adding custom auth data");
101
+ params.append("authData", JSON.stringify(customAuthData));
102
+ }
103
+ const fullAuthUrl = `${baseUrl}?${params.toString()}`;
65
104
  console.log("[ExpoAuthProvider] Starting authentication", {
66
- authUrl: authUrl.substring(0, 50) + "...",
67
- redirectUrl
105
+ baseUrl,
106
+ redirectUrl,
107
+ organizationId,
108
+ parentOrganizationId,
109
+ sessionId,
110
+ provider,
111
+ hasCustomData: !!customAuthData
68
112
  });
69
113
  await WebBrowser.warmUpAsync();
70
- const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUrl, {
114
+ const result = await WebBrowser.openAuthSessionAsync(fullAuthUrl, redirectUrl, {
71
115
  // Use system browser on iOS for ASWebAuthenticationSession
72
116
  preferEphemeralSession: false
73
117
  });
@@ -77,19 +121,14 @@ var ExpoAuthProvider = class {
77
121
  });
78
122
  if (result.type === "success" && result.url) {
79
123
  const url = new URL(result.url);
80
- const walletId = url.searchParams.get("walletId");
81
- const provider = url.searchParams.get("provider");
124
+ const walletId = url.searchParams.get("wallet_id");
125
+ const provider2 = url.searchParams.get("provider");
82
126
  if (!walletId) {
83
127
  throw new Error("Authentication failed: no walletId in redirect URL");
84
128
  }
85
- const userInfo = {};
86
- url.searchParams.forEach((value, key) => {
87
- userInfo[key] = value;
88
- });
89
129
  return {
90
130
  walletId,
91
- provider: provider || void 0,
92
- userInfo
131
+ provider: provider2 || void 0
93
132
  };
94
133
  } else if (result.type === "cancel") {
95
134
  throw new Error("User cancelled authentication");
@@ -203,7 +242,134 @@ var ExpoLogger = class {
203
242
  }
204
243
  };
205
244
 
245
+ // src/providers/embedded/stamper.ts
246
+ import * as SecureStore2 from "expo-secure-store";
247
+ import { ApiKeyStamper } from "@phantom/api-key-stamper";
248
+ import { generateKeyPair } from "@phantom/crypto";
249
+ import { base64urlEncode } from "@phantom/base64url";
250
+ import { Algorithm } from "@phantom/sdk-types";
251
+ var ReactNativeStamper = class {
252
+ constructor(config = {}) {
253
+ this.keyInfo = null;
254
+ this.algorithm = Algorithm.ed25519;
255
+ this.keyPrefix = config.keyPrefix || "phantom-rn-stamper";
256
+ this.organizationId = config.organizationId || "default";
257
+ }
258
+ /**
259
+ * Initialize the stamper and generate/load cryptographic keys
260
+ */
261
+ async init() {
262
+ const storedSecretKey = await this.getStoredSecretKey();
263
+ if (storedSecretKey) {
264
+ const keyInfo2 = await this.getStoredKeyInfo();
265
+ if (keyInfo2) {
266
+ this.keyInfo = keyInfo2;
267
+ return keyInfo2;
268
+ }
269
+ }
270
+ const keyInfo = await this.generateAndStoreKeyPair();
271
+ this.keyInfo = keyInfo;
272
+ return keyInfo;
273
+ }
274
+ /**
275
+ * Get the current key information
276
+ */
277
+ getKeyInfo() {
278
+ return this.keyInfo;
279
+ }
280
+ /**
281
+ * Generate and store a new key pair, replacing any existing keys
282
+ */
283
+ async resetKeyPair() {
284
+ await this.clear();
285
+ const keyInfo = await this.generateAndStoreKeyPair();
286
+ this.keyInfo = keyInfo;
287
+ return keyInfo;
288
+ }
289
+ /**
290
+ * Create X-Phantom-Stamp header value using stored secret key
291
+ * @param data - Data to sign (Buffer)
292
+ * @returns Complete X-Phantom-Stamp header value
293
+ */
294
+ async stamp({ data }) {
295
+ if (!this.keyInfo) {
296
+ throw new Error("Stamper not initialized. Call init() first.");
297
+ }
298
+ const storedSecretKey = await this.getStoredSecretKey();
299
+ if (!storedSecretKey) {
300
+ throw new Error("Secret key not found in secure storage");
301
+ }
302
+ const apiKeyStamper = new ApiKeyStamper({ apiSecretKey: storedSecretKey });
303
+ return await apiKeyStamper.stamp({ data });
304
+ }
305
+ /**
306
+ * Clear all stored keys from SecureStore
307
+ */
308
+ async clear() {
309
+ const infoKey = this.getInfoKey();
310
+ const secretKey = this.getSecretKey();
311
+ try {
312
+ await SecureStore2.deleteItemAsync(infoKey);
313
+ } catch (error) {
314
+ }
315
+ try {
316
+ await SecureStore2.deleteItemAsync(secretKey);
317
+ } catch (error) {
318
+ }
319
+ this.keyInfo = null;
320
+ }
321
+ async generateAndStoreKeyPair() {
322
+ const keypair = generateKeyPair();
323
+ const keyId = this.createKeyId(keypair.publicKey);
324
+ const keyInfo = {
325
+ keyId,
326
+ publicKey: keypair.publicKey
327
+ };
328
+ await this.storeKeyPair(keypair.secretKey, keyInfo);
329
+ return keyInfo;
330
+ }
331
+ createKeyId(publicKey) {
332
+ return base64urlEncode(new TextEncoder().encode(publicKey)).substring(0, 16);
333
+ }
334
+ async storeKeyPair(secretKey, keyInfo) {
335
+ const infoKey = this.getInfoKey();
336
+ const secretKeyName = this.getSecretKey();
337
+ await SecureStore2.setItemAsync(infoKey, JSON.stringify(keyInfo), {
338
+ requireAuthentication: false
339
+ });
340
+ await SecureStore2.setItemAsync(secretKeyName, secretKey, {
341
+ requireAuthentication: false
342
+ });
343
+ }
344
+ async getStoredKeyInfo() {
345
+ try {
346
+ const infoKey = this.getInfoKey();
347
+ const storedInfo = await SecureStore2.getItemAsync(infoKey);
348
+ if (storedInfo) {
349
+ return JSON.parse(storedInfo);
350
+ }
351
+ } catch (error) {
352
+ }
353
+ return null;
354
+ }
355
+ async getStoredSecretKey() {
356
+ try {
357
+ const secretKeyName = this.getSecretKey();
358
+ return await SecureStore2.getItemAsync(secretKeyName);
359
+ } catch (error) {
360
+ return null;
361
+ }
362
+ }
363
+ getInfoKey() {
364
+ return `${this.keyPrefix}-${this.organizationId}-info`;
365
+ }
366
+ getSecretKey() {
367
+ return `${this.keyPrefix}-${this.organizationId}-secret`;
368
+ }
369
+ };
370
+
206
371
  // src/PhantomProvider.tsx
372
+ import { Platform } from "react-native";
207
373
  import { jsx } from "react/jsx-runtime";
208
374
  var PhantomContext = createContext(void 0);
209
375
  function PhantomProvider({ children, config }) {
@@ -218,16 +384,25 @@ function PhantomProvider({ children, config }) {
218
384
  },
219
385
  embeddedWalletType: config.embeddedWalletType,
220
386
  addressTypes: config.addressTypes,
221
- solanaProvider: config.solanaProvider || "web3js"
387
+ solanaProvider: config.solanaProvider || "web3js",
388
+ appName: config.appName,
389
+ appLogo: config.appLogo
390
+ // Optional app logo URL
222
391
  };
223
392
  const storage = new ExpoSecureStorage();
224
393
  const authProvider = new ExpoAuthProvider();
225
394
  const urlParamsAccessor = new ExpoURLParamsAccessor();
226
395
  const logger = new ExpoLogger(config.debug);
396
+ const stamper = new ReactNativeStamper({
397
+ keyPrefix: `phantom-rn-${config.organizationId}`,
398
+ organizationId: config.organizationId
399
+ });
227
400
  const platform = {
228
401
  storage,
229
402
  authProvider,
230
- urlParamsAccessor
403
+ urlParamsAccessor,
404
+ stamper,
405
+ name: `${Platform.OS}-${Platform.Version}`
231
406
  };
232
407
  return new EmbeddedProvider(embeddedConfig, platform, logger);
233
408
  }, [config]);
@@ -249,6 +424,14 @@ function PhantomProvider({ children, config }) {
249
424
  } catch (err) {
250
425
  console.error("[PhantomProvider] Error updating connection state", err);
251
426
  setError(err);
427
+ try {
428
+ sdk.disconnect();
429
+ setIsConnected(false);
430
+ setAddresses([]);
431
+ setWalletId(null);
432
+ } catch (disconnectErr) {
433
+ console.error("[PhantomProvider] Error disconnecting after error", disconnectErr);
434
+ }
252
435
  }
253
436
  }, [sdk]);
254
437
  useEffect(() => {
@@ -420,7 +603,8 @@ function useSignAndSendTransaction() {
420
603
  }
421
604
 
422
605
  // src/index.ts
423
- import { AddressType, NetworkId } from "@phantom/client";
606
+ import { AddressType } from "@phantom/client";
607
+ import { NetworkId } from "@phantom/constants";
424
608
  export {
425
609
  AddressType,
426
610
  NetworkId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phantom/react-native-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Phantom Wallet SDK for React Native and Expo applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -45,8 +45,16 @@
45
45
  "directory": "packages/react-native-sdk"
46
46
  },
47
47
  "dependencies": {
48
- "@phantom/client": "^0.1.3",
49
- "@phantom/embedded-provider-core": "^0.1.1"
48
+ "@phantom/api-key-stamper": "^0.1.3",
49
+ "@phantom/base64url": "^0.1.0",
50
+ "@phantom/client": "^0.1.6",
51
+ "@phantom/constants": "^0.0.2",
52
+ "@phantom/crypto": "^0.1.2",
53
+ "@phantom/embedded-provider-core": "^0.1.3",
54
+ "@phantom/sdk-types": "^0.1.2",
55
+ "@types/bs58": "^5.0.0",
56
+ "bs58": "^6.0.0",
57
+ "buffer": "^6.0.3"
50
58
  },
51
59
  "peerDependencies": {
52
60
  "expo": ">=53.0.0",