@phantom/react-native-sdk 0.1.0

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 ADDED
@@ -0,0 +1,440 @@
1
+ # Phantom React Native SDK
2
+
3
+ A comprehensive React Native SDK for integrating Phantom Wallet functionality into your mobile applications. Built with Expo compatibility and optimized for both iOS and Android platforms.
4
+
5
+ ## Features
6
+
7
+ - 🔐 **Hardware-backed security** with Expo SecureStore (iOS Keychain + Android Keystore)
8
+ - 🌐 **OAuth authentication** flows using system browsers for security and UX
9
+ - 🔗 **Automatic deep linking** with custom URL schemes
10
+ - ⚛️ **React hooks API** for easy integration
11
+ - 📱 **Cross-platform** support (iOS, Android)
12
+ - 🛡️ **Biometric authentication** for accessing stored wallet data
13
+ - 🎯 **TypeScript support** with full type safety
14
+
15
+ ## Installation
16
+
17
+ ### Using npm
18
+
19
+ ```bash
20
+ npm install @phantom/react-native-sdk
21
+ ```
22
+
23
+ ### Using yarn
24
+
25
+ ```bash
26
+ yarn add @phantom/react-native-sdk
27
+ ```
28
+
29
+ ### Install peer dependencies
30
+
31
+ ```bash
32
+ # For Expo projects
33
+ npx expo install expo-secure-store expo-web-browser expo-auth-session expo-router
34
+
35
+ # For bare React Native projects (additional setup required)
36
+ npm install expo-secure-store expo-web-browser expo-auth-session
37
+
38
+ # Required polyfill for cryptographic operations
39
+ npm install react-native-get-random-values
40
+ ```
41
+
42
+ ### Required Polyfill
43
+
44
+ You must polyfill random byte generation to ensure cryptographic operations work properly. Add this import **at the very top** of your app's entry point (before any other imports):
45
+
46
+ ```tsx
47
+ // index.js, App.tsx, or _layout.tsx - MUST be the first import
48
+ import "react-native-get-random-values";
49
+
50
+ import { PhantomProvider } from "@phantom/react-native-sdk";
51
+ // ... other imports
52
+ ```
53
+
54
+ > **⚠️ Important**: The polyfill import must be the first import in your application entry point to ensure proper initialization of cryptographic functions.
55
+
56
+ ## Quick Start
57
+
58
+ ### 1. Configure your app scheme
59
+
60
+ #### For Expo projects
61
+
62
+ Add your custom scheme to `app.json`:
63
+
64
+ ```json
65
+ {
66
+ "expo": {
67
+ "name": "My Wallet App",
68
+ "slug": "my-wallet-app",
69
+ "scheme": "mywalletapp",
70
+ "plugins": [["expo-router"], ["expo-secure-store"], ["expo-web-browser"], ["expo-auth-session"]]
71
+ }
72
+ }
73
+ ```
74
+
75
+ #### For bare React Native projects
76
+
77
+ - **iOS**: Configure URL schemes in `Info.plist`
78
+ - **Android**: Add intent filters to `AndroidManifest.xml`
79
+
80
+ ### 2. Set up the provider
81
+
82
+ Wrap your app with `PhantomProvider`:
83
+
84
+ ```tsx
85
+ // App.tsx or _layout.tsx (for Expo Router)
86
+ import { PhantomProvider, AddressType } from "@phantom/react-native-sdk";
87
+
88
+ export default function App() {
89
+ return (
90
+ <PhantomProvider
91
+ config={{
92
+ organizationId: "your-organization-id",
93
+ scheme: "mywalletapp", // Must match app.json scheme
94
+ embeddedWalletType: "user-wallet",
95
+ addressTypes: [AddressType.solana],
96
+ apiBaseUrl: "https://api.phantom.app/v1/wallets",
97
+ authOptions: {
98
+ redirectUrl: "mywalletapp://phantom-auth-callback",
99
+ },
100
+ }}
101
+ >
102
+ <YourAppContent />
103
+ </PhantomProvider>
104
+ );
105
+ }
106
+ ```
107
+
108
+ ### 3. Use hooks in your components
109
+
110
+ ```tsx
111
+ // WalletScreen.tsx
112
+ import React from "react";
113
+ import { View, Button, Text, Alert } from "react-native";
114
+ import { useConnect, useAccounts, useSignMessage, useDisconnect } from "@phantom/react-native-sdk";
115
+
116
+ export function WalletScreen() {
117
+ const { connect, isConnecting, error: connectError } = useConnect();
118
+ const { addresses, isConnected, walletId } = useAccounts();
119
+ const { signMessage, isSigning } = useSignMessage();
120
+ const { disconnect } = useDisconnect();
121
+
122
+ const handleConnect = async () => {
123
+ try {
124
+ await connect({ provider: "google" });
125
+ Alert.alert("Success", "Wallet connected!");
126
+ } catch (error) {
127
+ Alert.alert("Error", `Failed to connect: ${error.message}`);
128
+ }
129
+ };
130
+
131
+ const handleSignMessage = async () => {
132
+ try {
133
+ const signature = await signMessage({
134
+ message: "Hello from my React Native app!",
135
+ networkId: "solana:mainnet",
136
+ });
137
+ Alert.alert("Signed!", `Signature: ${signature.slice(0, 10)}...`);
138
+ } catch (error) {
139
+ Alert.alert("Error", `Failed to sign: ${error.message}`);
140
+ }
141
+ };
142
+
143
+ if (!isConnected) {
144
+ return (
145
+ <View style={{ padding: 20 }}>
146
+ <Button
147
+ title={isConnecting ? "Connecting..." : "Connect Wallet"}
148
+ onPress={handleConnect}
149
+ disabled={isConnecting}
150
+ />
151
+ {connectError && <Text style={{ color: "red", marginTop: 10 }}>Error: {connectError.message}</Text>}
152
+ </View>
153
+ );
154
+ }
155
+
156
+ return (
157
+ <View style={{ padding: 20 }}>
158
+ <Text style={{ fontSize: 18, marginBottom: 10 }}>Wallet Connected</Text>
159
+ <Text>Wallet ID: {walletId}</Text>
160
+ <Text>Address: {addresses[0]?.address}</Text>
161
+
162
+ <Button
163
+ title={isSigning ? "Signing..." : "Sign Message"}
164
+ onPress={handleSignMessage}
165
+ disabled={isSigning}
166
+ style={{ marginTop: 10 }}
167
+ />
168
+
169
+ <Button title="Disconnect" onPress={disconnect} style={{ marginTop: 10 }} />
170
+ </View>
171
+ );
172
+ }
173
+ ```
174
+
175
+ ## API Reference
176
+
177
+ ### PhantomProvider
178
+
179
+ The main provider component that initializes the SDK and provides context to all hooks.
180
+
181
+ ```tsx
182
+ <PhantomProvider config={config}>
183
+ <App />
184
+ </PhantomProvider>
185
+ ```
186
+
187
+ #### Configuration Options
188
+
189
+ ```typescript
190
+ interface PhantomProviderConfig {
191
+ organizationId: string; // Your Phantom organization ID
192
+ scheme: string; // Custom URL scheme for your app
193
+ embeddedWalletType: "user-wallet" | "app-wallet";
194
+ addressTypes: AddressType[];
195
+ apiBaseUrl: "https://api.phantom.app/v1/wallets";
196
+ authOptions?: {
197
+ authUrl?: string; // Custom auth URL (optional)
198
+ redirectUrl?: string; // Custom redirect URL (optional)
199
+ };
200
+ }
201
+ ```
202
+
203
+ ### Hooks
204
+
205
+ #### useConnect
206
+
207
+ Manages wallet connection functionality.
208
+
209
+ ```typescript
210
+ const { connect, isConnecting, error } = useConnect();
211
+
212
+ // Connect with specific provider
213
+ await connect({ provider: "google" });
214
+ await connect({ provider: "apple" });
215
+ await connect({ provider: "jwt", jwtToken: "your-jwt-token" });
216
+ ```
217
+
218
+ #### useAccounts
219
+
220
+ Provides access to connected wallet information.
221
+
222
+ ```typescript
223
+ const {
224
+ addresses, // Array of wallet addresses
225
+ isConnected, // Connection status
226
+ walletId, // Phantom wallet ID
227
+ } = useAccounts();
228
+ ```
229
+
230
+ #### useSignMessage
231
+
232
+ Handles message signing operations.
233
+
234
+ ```typescript
235
+ const { signMessage, isSigning, error } = useSignMessage();
236
+
237
+ const signature = await signMessage({
238
+ message: "Message to sign",
239
+ networkId: "solana:mainnet", // or 'ethereum:1'
240
+ });
241
+ ```
242
+
243
+ #### useSignAndSendTransaction
244
+
245
+ Handles transaction signing and sending.
246
+
247
+ ```typescript
248
+ const { signAndSendTransaction, isSigning, error } = useSignAndSendTransaction();
249
+
250
+ const result = await signAndSendTransaction({
251
+ transaction: "base64-encoded-transaction",
252
+ networkId: NetworkId.SOLANA_MAINNET,
253
+ });
254
+ ```
255
+
256
+ #### useDisconnect
257
+
258
+ Manages wallet disconnection.
259
+
260
+ ```typescript
261
+ const { disconnect, isDisconnecting } = useDisconnect();
262
+
263
+ await disconnect();
264
+ ```
265
+
266
+ ## Authentication Flows
267
+
268
+ ### OAuth Providers
269
+
270
+ The SDK supports multiple OAuth providers:
271
+
272
+ - **Google** (`provider: 'google'`)
273
+ - **Apple** (`provider: 'apple'`)
274
+ - **JWT** (`provider: 'jwt'`) - For custom authentication
275
+
276
+ ### Authentication Process
277
+
278
+ 1. User taps "Connect Wallet" in your app
279
+ 2. System browser opens (Safari on iOS, Chrome Custom Tab on Android)
280
+ 3. User authenticates with their chosen provider
281
+ 4. Browser redirects back to your app using the custom scheme
282
+ 5. SDK automatically processes the authentication result
283
+ 6. Wallet is connected and ready to use
284
+
285
+ ### Deep Link Handling
286
+
287
+ The SDK automatically handles deep link redirects. Ensure your app's URL scheme is properly configured:
288
+
289
+ **Redirect URL format:** `{scheme}://phantom-auth-callback?wallet_id=...&session_id=...`
290
+
291
+ ## Security Features
292
+
293
+ ### Secure Storage
294
+
295
+ - **iOS**: Uses Keychain Services with hardware security
296
+ - **Android**: Uses Android Keystore with hardware-backed keys
297
+ - **Biometric Protection**: Optional biometric authentication for accessing stored data
298
+
299
+ ### Authentication Security
300
+
301
+ - **System Browser**: Uses secure system browsers, not in-app webviews
302
+ - **Origin Verification**: Automatic verification of redirect origins
303
+ - **PKCE**: Support for Proof Key for Code Exchange in OAuth flows
304
+ - **Hardware Security**: Private keys stored in secure hardware when available
305
+
306
+ ## Configuration Examples
307
+
308
+ ### Basic Configuration
309
+
310
+ ```tsx
311
+ import { PhantomProvider, AddressType } from "@phantom/react-native-sdk";
312
+
313
+ <PhantomProvider
314
+ config={{
315
+ organizationId: "org_123456789",
316
+ scheme: "myapp",
317
+ embeddedWalletType: "user-wallet",
318
+ addressTypes: [AddressType.solana],
319
+ apiBaseUrl: "https://api.phantom.app/v1/wallets",
320
+ }}
321
+ >
322
+ <App />
323
+ </PhantomProvider>;
324
+ ```
325
+
326
+ ### Advanced Configuration
327
+
328
+ ```tsx
329
+ import { PhantomProvider, AddressType } from "@phantom/react-native-sdk";
330
+
331
+ <PhantomProvider
332
+ config={{
333
+ organizationId: "org_123456789",
334
+ scheme: "mycompany-wallet",
335
+ embeddedWalletType: "user-wallet",
336
+ addressTypes: [AddressType.solana, AddressType.ethereum],
337
+ apiBaseUrl: "https://api.phantom.app/v1/wallets",
338
+ authOptions: {
339
+ authUrl: "https://auth.yourcompany.com",
340
+ redirectUrl: "mycompany-wallet://auth/success",
341
+ },
342
+ }}
343
+ >
344
+ <App />
345
+ </PhantomProvider>;
346
+ ```
347
+
348
+ ## Platform Setup
349
+
350
+ ### iOS Setup
351
+
352
+ 1. **URL Scheme**: Automatically configured by Expo
353
+ 2. **Keychain Access**: Ensure your app has keychain access entitlements
354
+ 3. **Associated Domains**: Configure if using universal links
355
+
356
+ ### Android Setup
357
+
358
+ 1. **Intent Filters**: Automatically configured by Expo
359
+ 2. **Keystore Access**: Ensure your app targets API level 23+
360
+ 3. **Network Security**: Configure network security config if needed
361
+
362
+ ## Testing
363
+
364
+ ### Development Testing
365
+
366
+ ```typescript
367
+ // Mock provider for testing
368
+ import { PhantomProvider, AddressType } from '@phantom/react-native-sdk';
369
+
370
+ const testConfig = {
371
+ organizationId: "test-org",
372
+ scheme: "testapp",
373
+ embeddedWalletType: "app-wallet" as const,
374
+ addressTypes: [AddressType.solana],
375
+ apiBaseUrl: "https://api.phantom.app/v1/wallets",
376
+
377
+ };
378
+
379
+ // Use app-wallet for testing (no OAuth required)
380
+ <PhantomProvider config={testConfig}>
381
+ <TestApp />
382
+ </PhantomProvider>
383
+ ```
384
+
385
+ ### Deep Link Testing
386
+
387
+ Test deep links in development builds (not Expo Go):
388
+
389
+ ```bash
390
+ # iOS Simulator
391
+ xcrun simctl openurl booted "myapp://phantom-auth-callback?wallet_id=test"
392
+
393
+ # Android Emulator
394
+ adb shell am start -W -a android.intent.action.VIEW -d "myapp://phantom-auth-callback?wallet_id=test" com.yourcompany.myapp
395
+ ```
396
+
397
+ ### Common Issues
398
+
399
+ 1. **"Scheme not configured"**
400
+ - Ensure your app's URL scheme matches the `scheme` in config
401
+ - Check `app.json` (Expo) or platform-specific configuration
402
+
403
+ 2. **"Authentication failed"**
404
+ - Verify your organization ID is correct
405
+ - Check network connectivity
406
+ - Ensure redirect URL matches your scheme
407
+
408
+ 3. **"Storage access denied"**
409
+ - Check device security settings
410
+ - Ensure app has proper permissions
411
+ - Verify biometric authentication is available
412
+
413
+ 4. **Deep links not working**
414
+ - Test with development builds, not Expo Go
415
+ - Verify URL scheme configuration
416
+ - Check intent filters (Android) or URL schemes (iOS)
417
+
418
+ ### Debug Mode
419
+
420
+ Enable debug logging in development:
421
+
422
+ ```typescript
423
+ <PhantomProvider
424
+ config={{
425
+ ...config,
426
+ debug: true // Enable debug logging
427
+ }}
428
+ >
429
+ <App />
430
+ </PhantomProvider>
431
+ ```
432
+
433
+ ## Support
434
+
435
+ - **Documentation**: [phantom.app/docs](https://phantom.app/docs)
436
+ - **GitHub Issues**: [github.com/phantom/wallet-sdk/issues](https://github.com/phantom/wallet-sdk/issues)
437
+
438
+ ## License
439
+
440
+ MIT License - see [LICENSE](LICENSE) file for details.
@@ -0,0 +1,80 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
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';
7
+
8
+ interface PhantomProviderConfig extends Omit<EmbeddedProviderConfig, "authOptions"> {
9
+ /** Custom URL scheme for your app (e.g., "myapp") */
10
+ scheme: string;
11
+ /** Authentication options */
12
+ authOptions?: ReactNativeAuthOptions;
13
+ /** Enable debug logging */
14
+ debug?: boolean;
15
+ }
16
+ interface ReactNativeAuthOptions extends AuthOptions {
17
+ /** Custom redirect URL - defaults to {scheme}://phantom-auth-callback */
18
+ redirectUrl?: string;
19
+ }
20
+ interface ConnectOptions {
21
+ /** OAuth provider to use */
22
+ provider?: "google" | "apple" | "jwt";
23
+ /** JWT token for JWT authentication */
24
+ jwtToken?: string;
25
+ /** Custom authentication data */
26
+ customAuthData?: Record<string, any>;
27
+ }
28
+
29
+ interface PhantomContextValue {
30
+ sdk: EmbeddedProvider;
31
+ isConnected: boolean;
32
+ addresses: WalletAddress[];
33
+ walletId: string | null;
34
+ error: Error | null;
35
+ updateConnectionState: () => void;
36
+ setWalletId: (walletId: string | null) => void;
37
+ }
38
+ interface PhantomProviderProps {
39
+ children: ReactNode;
40
+ config: PhantomProviderConfig;
41
+ }
42
+ declare function PhantomProvider({ children, config }: PhantomProviderProps): react_jsx_runtime.JSX.Element;
43
+ /**
44
+ * Hook to access the Phantom context
45
+ * Must be used within a PhantomProvider
46
+ */
47
+ declare function usePhantom(): PhantomContextValue;
48
+
49
+ declare function useConnect(): {
50
+ connect: (options?: ConnectOptions) => Promise<ConnectResult>;
51
+ isConnecting: boolean;
52
+ error: Error | null;
53
+ };
54
+
55
+ declare function useDisconnect(): {
56
+ disconnect: () => Promise<void>;
57
+ isDisconnecting: boolean;
58
+ error: Error | null;
59
+ };
60
+
61
+ declare function useAccounts(): {
62
+ addresses: _phantom_embedded_provider_core.WalletAddress[];
63
+ isConnected: boolean;
64
+ walletId: string | null;
65
+ error: Error | null;
66
+ };
67
+
68
+ declare function useSignMessage(): {
69
+ signMessage: (params: SignMessageParams) => Promise<string>;
70
+ isSigning: boolean;
71
+ error: Error | null;
72
+ };
73
+
74
+ declare function useSignAndSendTransaction(): {
75
+ signAndSendTransaction: (params: SignAndSendTransactionParams) => Promise<SignedTransaction>;
76
+ isSigning: boolean;
77
+ error: Error | null;
78
+ };
79
+
80
+ export { ConnectOptions, PhantomProvider, PhantomProviderConfig, ReactNativeAuthOptions, useAccounts, useConnect, useDisconnect, usePhantom, useSignAndSendTransaction, useSignMessage };