@reown/appkit-solana-react-native 0.0.1 → 2.0.0-alpha.1

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.
Files changed (44) hide show
  1. package/lib/commonjs/adapter.js +28 -22
  2. package/lib/commonjs/adapter.js.map +1 -1
  3. package/lib/commonjs/connectors/PhantomConnector.js +247 -0
  4. package/lib/commonjs/connectors/PhantomConnector.js.map +1 -0
  5. package/lib/commonjs/helpers.js +103 -0
  6. package/lib/commonjs/helpers.js.map +1 -0
  7. package/lib/commonjs/index.js +7 -0
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/providers/PhantomProvider.js +391 -0
  10. package/lib/commonjs/providers/PhantomProvider.js.map +1 -0
  11. package/lib/commonjs/types.js +6 -0
  12. package/lib/commonjs/types.js.map +1 -0
  13. package/lib/module/adapter.js +28 -22
  14. package/lib/module/adapter.js.map +1 -1
  15. package/lib/module/connectors/PhantomConnector.js +239 -0
  16. package/lib/module/connectors/PhantomConnector.js.map +1 -0
  17. package/lib/module/helpers.js +94 -0
  18. package/lib/module/helpers.js.map +1 -0
  19. package/lib/module/index.js +7 -2
  20. package/lib/module/index.js.map +1 -1
  21. package/lib/module/providers/PhantomProvider.js +383 -0
  22. package/lib/module/providers/PhantomProvider.js.map +1 -0
  23. package/lib/module/types.js +2 -0
  24. package/lib/module/types.js.map +1 -0
  25. package/lib/typescript/adapter.d.ts +0 -1
  26. package/lib/typescript/adapter.d.ts.map +1 -1
  27. package/lib/typescript/connectors/PhantomConnector.d.ts +26 -0
  28. package/lib/typescript/connectors/PhantomConnector.d.ts.map +1 -0
  29. package/lib/typescript/helpers.d.ts +31 -0
  30. package/lib/typescript/helpers.d.ts.map +1 -0
  31. package/lib/typescript/index.d.ts +3 -2
  32. package/lib/typescript/index.d.ts.map +1 -1
  33. package/lib/typescript/providers/PhantomProvider.d.ts +37 -0
  34. package/lib/typescript/providers/PhantomProvider.d.ts.map +1 -0
  35. package/lib/typescript/types.d.ts +96 -0
  36. package/lib/typescript/types.d.ts.map +1 -0
  37. package/package.json +8 -14
  38. package/src/adapter.ts +29 -27
  39. package/src/connectors/PhantomConnector.ts +328 -0
  40. package/src/helpers.ts +102 -0
  41. package/src/index.ts +8 -0
  42. package/src/providers/PhantomProvider.ts +530 -0
  43. package/src/types.ts +131 -0
  44. package/src/index.tsx +0 -2
@@ -0,0 +1,530 @@
1
+ import { Linking } from 'react-native';
2
+ import nacl from 'tweetnacl';
3
+ import bs58 from 'bs58';
4
+ import { Buffer } from 'buffer';
5
+ import type {
6
+ Provider,
7
+ RequestArguments,
8
+ CaipNetworkId,
9
+ Storage
10
+ } from '@reown/appkit-common-react-native';
11
+ import type {
12
+ PhantomProviderConfig,
13
+ PhantomConnectResult,
14
+ PhantomSession,
15
+ DecryptedConnectData,
16
+ PhantomDeeplinkResponse,
17
+ SignAllTransactionsRequestParams,
18
+ SignMessageRequestParams,
19
+ SignTransactionRequestParams,
20
+ PhantomRpcMethod,
21
+ PhantomConnectParams,
22
+ PhantomDisconnectParams,
23
+ PhantomSignTransactionParams,
24
+ PhantomSignMessageParams,
25
+ PhantomSignAllTransactionsParams,
26
+ PhantomCluster
27
+ } from '../types';
28
+ import EventEmitter from 'events';
29
+
30
+ const PHANTOM_BASE_URL = 'https://phantom.app/ul/v1';
31
+ const PHANTOM_PROVIDER_STORAGE_KEY = '@appkit/phantom-provider-session';
32
+
33
+ export const SOLANA_SIGNING_METHODS = {
34
+ SOLANA_SIGN_TRANSACTION: 'solana_signTransaction',
35
+ SOLANA_SIGN_MESSAGE: 'solana_signMessage',
36
+ SOLANA_SIGN_AND_SEND_TRANSACTION: 'solana_signAndSendTransaction',
37
+ SOLANA_SIGN_ALL_TRANSACTIONS: 'solana_signAllTransactions'
38
+ } as const;
39
+
40
+ type SolanaSigningMethod = Values<typeof SOLANA_SIGNING_METHODS>;
41
+
42
+ function isValidSolanaSigningMethod(method: string): method is SolanaSigningMethod {
43
+ return Object.values(SOLANA_SIGNING_METHODS).includes(method as SolanaSigningMethod);
44
+ }
45
+
46
+ export class PhantomProvider extends EventEmitter implements Provider {
47
+ private readonly config: PhantomProviderConfig;
48
+ private dappEncryptionKeyPair: nacl.BoxKeyPair;
49
+ private currentCluster: PhantomCluster = 'mainnet-beta';
50
+
51
+ private storage: Storage;
52
+
53
+ private sessionToken: string | null = null;
54
+ private userPublicKey: string | null = null;
55
+ private phantomEncryptionPublicKeyBs58: string | null = null;
56
+
57
+ constructor(config: PhantomProviderConfig) {
58
+ super();
59
+ this.config = config;
60
+ this.dappEncryptionKeyPair = config.dappEncryptionKeyPair;
61
+ this.storage = config.storage;
62
+ }
63
+
64
+ getUserPublicKey(): string | null {
65
+ return this.userPublicKey;
66
+ }
67
+
68
+ isConnected(): boolean {
69
+ return !!this.sessionToken && !!this.userPublicKey && !!this.dappEncryptionKeyPair;
70
+ }
71
+
72
+ private buildUrl(rpcMethod: PhantomRpcMethod, params: Record<string, string>): string {
73
+ const query = new URLSearchParams(params).toString();
74
+
75
+ return `${PHANTOM_BASE_URL}/${rpcMethod}?${query}`;
76
+ }
77
+
78
+ private getRpcMethodName(method: SolanaSigningMethod): PhantomRpcMethod {
79
+ switch (method) {
80
+ case SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION:
81
+ return 'signTransaction';
82
+ case SOLANA_SIGNING_METHODS.SOLANA_SIGN_AND_SEND_TRANSACTION:
83
+ return 'signAndSendTransaction';
84
+ case SOLANA_SIGNING_METHODS.SOLANA_SIGN_ALL_TRANSACTIONS:
85
+ return 'signAllTransactions';
86
+ case SOLANA_SIGNING_METHODS.SOLANA_SIGN_MESSAGE:
87
+ return 'signMessage';
88
+ default:
89
+ // Should not happen due to type constraints on `method`
90
+ throw new Error(`Unsupported Solana signing method: ${method}`);
91
+ }
92
+ }
93
+
94
+ private encryptPayload(
95
+ payload: Record<string, unknown>,
96
+ phantomPublicKeyBs58ToEncryptFor: string
97
+ ): { nonce: string; encryptedPayload: string } | null {
98
+ if (!phantomPublicKeyBs58ToEncryptFor) {
99
+ return null;
100
+ }
101
+ try {
102
+ const phantomPublicKeyBytes = bs58.decode(phantomPublicKeyBs58ToEncryptFor);
103
+ const nonce = nacl.randomBytes(nacl.box.nonceLength);
104
+ const payloadBytes = Buffer.from(JSON.stringify(payload), 'utf8');
105
+ const encryptedPayload = nacl.box(
106
+ payloadBytes,
107
+ nonce,
108
+ phantomPublicKeyBytes,
109
+ this.dappEncryptionKeyPair.secretKey
110
+ );
111
+
112
+ return {
113
+ nonce: bs58.encode(nonce),
114
+ encryptedPayload: bs58.encode(encryptedPayload)
115
+ };
116
+ } catch (error) {
117
+ return null;
118
+ }
119
+ }
120
+
121
+ private decryptPayload<T>(
122
+ encryptedDataBs58: string,
123
+ nonceBs58: string,
124
+ phantomSenderPublicKeyBs58: string
125
+ ): T | null {
126
+ try {
127
+ const encryptedDataBytes = bs58.decode(encryptedDataBs58);
128
+ const nonceBytes = bs58.decode(nonceBs58);
129
+ const phantomSenderPublicKeyBytes = bs58.decode(phantomSenderPublicKeyBs58);
130
+ const decryptedPayloadBytes = nacl.box.open(
131
+ encryptedDataBytes,
132
+ nonceBytes,
133
+ phantomSenderPublicKeyBytes,
134
+ this.dappEncryptionKeyPair.secretKey
135
+ );
136
+ if (!decryptedPayloadBytes) {
137
+ return null;
138
+ }
139
+
140
+ return JSON.parse(Buffer.from(decryptedPayloadBytes).toString('utf8')) as T;
141
+ } catch (error) {
142
+ return null;
143
+ }
144
+ }
145
+
146
+ public async restoreSession(): Promise<boolean> {
147
+ try {
148
+ const session = await this.storage.getItem<PhantomSession>(PHANTOM_PROVIDER_STORAGE_KEY);
149
+ if (session) {
150
+ this.setSession(session);
151
+
152
+ return true;
153
+ }
154
+
155
+ return false;
156
+ } catch (error) {
157
+ // console.error('PhantomProvider: Failed to restore session.', error);
158
+ await this.clearSessionStorage(); // Clear potentially corrupt data
159
+
160
+ return false;
161
+ }
162
+ }
163
+
164
+ private async saveSession(): Promise<void> {
165
+ if (!this.sessionToken || !this.userPublicKey || !this.phantomEncryptionPublicKeyBs58) {
166
+ return; // Cannot save incomplete session
167
+ }
168
+ const session: PhantomSession = {
169
+ sessionToken: this.sessionToken,
170
+ userPublicKey: this.userPublicKey,
171
+ phantomEncryptionPublicKeyBs58: this.phantomEncryptionPublicKeyBs58,
172
+ cluster: this.currentCluster
173
+ };
174
+ try {
175
+ await this.storage.setItem(PHANTOM_PROVIDER_STORAGE_KEY, JSON.stringify(session));
176
+ } catch (error) {
177
+ // console.error('PhantomProvider: Failed to save session.', error);
178
+ }
179
+ }
180
+
181
+ private async clearSessionStorage(): Promise<void> {
182
+ try {
183
+ await this.storage.removeItem(PHANTOM_PROVIDER_STORAGE_KEY);
184
+ } catch (error) {
185
+ // console.error('PhantomProvider: Failed to clear session storage.', error);
186
+ }
187
+ }
188
+
189
+ public async connect<T = PhantomConnectResult>(params?: {
190
+ cluster?: PhantomCluster;
191
+ }): Promise<T> {
192
+ const cluster = params?.cluster ?? 'mainnet-beta';
193
+ this.currentCluster = cluster;
194
+ const connectDeeplinkParams: PhantomConnectParams = {
195
+ app_url: this.config.dappUrl,
196
+ dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey),
197
+ redirect_link: this.config.appScheme,
198
+ cluster
199
+ };
200
+ const url = this.buildUrl('connect', connectDeeplinkParams as any);
201
+
202
+ return new Promise<PhantomConnectResult>((resolve, reject) => {
203
+ let subscription: { remove: () => void } | null = null;
204
+ const handleDeepLink = async (event: { url: string }) => {
205
+ if (subscription) {
206
+ subscription.remove();
207
+ }
208
+ const fullUrl = event.url;
209
+ if (fullUrl.startsWith(this.config.appScheme)) {
210
+ const responseUrlParams = new URLSearchParams(
211
+ fullUrl.substring(fullUrl.indexOf('?') + 1)
212
+ );
213
+ const errorCode = responseUrlParams.get('errorCode');
214
+ const errorMessage = responseUrlParams.get('errorMessage');
215
+ if (errorCode) {
216
+ return reject(
217
+ new Error(
218
+ `Phantom Connection Failed: ${errorMessage || 'Unknown error'} (Code: ${errorCode})`
219
+ )
220
+ );
221
+ }
222
+ const responsePayload: PhantomDeeplinkResponse = {
223
+ phantom_encryption_public_key: responseUrlParams.get('phantom_encryption_public_key')!,
224
+ nonce: responseUrlParams.get('nonce')!,
225
+ data: responseUrlParams.get('data')!
226
+ };
227
+ if (
228
+ !responsePayload.phantom_encryption_public_key ||
229
+ !responsePayload.nonce ||
230
+ !responsePayload.data
231
+ ) {
232
+ return reject(new Error('Phantom Connect: Invalid response - missing parameters.'));
233
+ }
234
+ const decryptedData = this.decryptPayload<DecryptedConnectData>(
235
+ responsePayload.data,
236
+ responsePayload.nonce,
237
+ responsePayload.phantom_encryption_public_key
238
+ );
239
+ if (!decryptedData || !decryptedData.public_key || !decryptedData.session) {
240
+ return reject(
241
+ new Error('Phantom Connect: Failed to decrypt or invalid decrypted data.')
242
+ );
243
+ }
244
+ this.userPublicKey = decryptedData.public_key;
245
+ this.sessionToken = decryptedData.session;
246
+ this.phantomEncryptionPublicKeyBs58 = responsePayload.phantom_encryption_public_key;
247
+
248
+ // Save session on successful connect
249
+ this.saveSession();
250
+
251
+ resolve({
252
+ userPublicKey: this.userPublicKey,
253
+ sessionToken: this.sessionToken,
254
+ phantomEncryptionPublicKeyBs58: this.phantomEncryptionPublicKeyBs58,
255
+ cluster
256
+ });
257
+ } else {
258
+ reject(new Error('Phantom Connect: Unexpected redirect URI.'));
259
+ }
260
+ };
261
+ subscription = Linking.addEventListener('url', handleDeepLink);
262
+ Linking.openURL(url).catch(err => {
263
+ if (subscription) {
264
+ subscription.remove();
265
+ }
266
+ reject(new Error(`Failed to open Phantom wallet: ${err.message}. Is it installed?`));
267
+ });
268
+ }) as Promise<T>;
269
+ }
270
+
271
+ public async disconnect(): Promise<void> {
272
+ if (!this.sessionToken || !this.phantomEncryptionPublicKeyBs58) {
273
+ await this.clearSession();
274
+
275
+ return Promise.resolve();
276
+ }
277
+
278
+ const payloadToEncrypt = { session: this.sessionToken };
279
+ const encryptedDisconnectPayload = this.encryptPayload(
280
+ payloadToEncrypt,
281
+ this.phantomEncryptionPublicKeyBs58
282
+ );
283
+
284
+ if (!encryptedDisconnectPayload) {
285
+ // console.warn('PhantomProvider: Failed to encrypt disconnect payload. Clearing session locally.');
286
+ await this.clearSession();
287
+
288
+ return Promise.resolve(); // Or reject, depending on desired strictness
289
+ }
290
+
291
+ const disconnectDeeplinkParams: PhantomDisconnectParams = {
292
+ dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey),
293
+ redirect_link: this.config.appScheme,
294
+ payload: encryptedDisconnectPayload.encryptedPayload,
295
+ nonce: encryptedDisconnectPayload.nonce
296
+ };
297
+ const url = this.buildUrl('disconnect', disconnectDeeplinkParams as any);
298
+
299
+ return new Promise<void>((resolve, reject) => {
300
+ let subscription: { remove: () => void } | null = null;
301
+ const handleDeepLink = (event: { url: string }) => {
302
+ if (subscription) {
303
+ subscription.remove();
304
+ }
305
+ if (event.url.startsWith(this.config.appScheme)) {
306
+ this.clearSession();
307
+ resolve();
308
+ } else {
309
+ this.clearSession();
310
+ reject(new Error('Phantom Disconnect: Unexpected redirect URI.'));
311
+ }
312
+ };
313
+ subscription = Linking.addEventListener('url', handleDeepLink);
314
+ Linking.openURL(url).catch(err => {
315
+ if (subscription) {
316
+ subscription.remove();
317
+ }
318
+ this.clearSession();
319
+ reject(new Error(`Failed to open Phantom for disconnection: ${err.message}.`));
320
+ });
321
+ });
322
+ }
323
+
324
+ public async clearSession(): Promise<void> {
325
+ this.sessionToken = null;
326
+ this.userPublicKey = null;
327
+ this.phantomEncryptionPublicKeyBs58 = null;
328
+ await this.clearSessionStorage();
329
+ }
330
+
331
+ public setSession(session: PhantomSession): void {
332
+ this.sessionToken = session.sessionToken;
333
+ this.userPublicKey = session.userPublicKey;
334
+ this.phantomEncryptionPublicKeyBs58 = session.phantomEncryptionPublicKeyBs58;
335
+ this.currentCluster = session.cluster;
336
+ }
337
+
338
+ public async request<T>(args: RequestArguments, _chainId?: CaipNetworkId): Promise<T> {
339
+ if (!isValidSolanaSigningMethod(args.method)) {
340
+ throw new Error(
341
+ `PhantomProvider: Unsupported method: ${args.method}. Only Solana signing methods are supported.`
342
+ );
343
+ }
344
+ const signingMethod = args.method as SolanaSigningMethod;
345
+ const requestParams = args.params as any;
346
+
347
+ if (!this.isConnected() || !this.sessionToken || !this.phantomEncryptionPublicKeyBs58) {
348
+ throw new Error(
349
+ 'PhantomProvider: Not connected or session details missing. Cannot process request.'
350
+ );
351
+ }
352
+
353
+ const rpcMethodName = this.getRpcMethodName(signingMethod);
354
+ let deeplinkUrl = '';
355
+
356
+ switch (signingMethod) {
357
+ case SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION:
358
+ case SOLANA_SIGNING_METHODS.SOLANA_SIGN_AND_SEND_TRANSACTION: {
359
+ const typedParams = requestParams as SignTransactionRequestParams;
360
+ if (!typedParams || typeof typedParams.transaction !== 'string') {
361
+ throw new Error(
362
+ `Missing or invalid 'transaction' (base58 string) in params for ${signingMethod}`
363
+ );
364
+ }
365
+
366
+ const dataToEncrypt = {
367
+ session: this.sessionToken!,
368
+ transaction: typedParams.transaction
369
+ };
370
+ const encryptedData = this.encryptPayload(
371
+ dataToEncrypt,
372
+ this.phantomEncryptionPublicKeyBs58!
373
+ );
374
+ if (!encryptedData) {
375
+ throw new Error(`PhantomProvider: Failed to encrypt payload for ${signingMethod}.`);
376
+ }
377
+
378
+ const signTxDeeplinkParams: PhantomSignTransactionParams = {
379
+ dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey),
380
+ redirect_link: this.config.appScheme,
381
+ cluster: this.currentCluster,
382
+ payload: encryptedData.encryptedPayload,
383
+ nonce: encryptedData.nonce
384
+ };
385
+ deeplinkUrl = this.buildUrl(rpcMethodName, signTxDeeplinkParams as any);
386
+ break;
387
+ }
388
+ case SOLANA_SIGNING_METHODS.SOLANA_SIGN_MESSAGE: {
389
+ const typedParams = requestParams as SignMessageRequestParams;
390
+ if (!typedParams || typeof typedParams.message === 'undefined') {
391
+ throw new Error(`Missing 'message' in params for ${signingMethod}`);
392
+ }
393
+
394
+ let messageBs58: string;
395
+ if (typedParams.message instanceof Uint8Array) {
396
+ messageBs58 = bs58.encode(typedParams.message);
397
+ } else if (typeof typedParams.message === 'string') {
398
+ try {
399
+ bs58.decode(typedParams.message);
400
+ messageBs58 = typedParams.message;
401
+ } catch (e) {
402
+ messageBs58 = bs58.encode(Buffer.from(typedParams.message));
403
+ }
404
+ } else {
405
+ throw new Error('Invalid message format for signMessage. Expected Uint8Array or string.');
406
+ }
407
+
408
+ const dataToEncrypt = {
409
+ message: messageBs58,
410
+ session: this.sessionToken!,
411
+ display: typedParams.display || 'utf8'
412
+ };
413
+
414
+ const encryptedPayloadData = this.encryptPayload(
415
+ dataToEncrypt,
416
+ this.phantomEncryptionPublicKeyBs58!
417
+ );
418
+
419
+ if (!encryptedPayloadData) {
420
+ throw new Error('PhantomProvider: Failed to encrypt payload for signMessage.');
421
+ }
422
+
423
+ const signMsgDeeplinkQueryPayload: PhantomSignMessageParams = {
424
+ dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey),
425
+ redirect_link: this.config.appScheme,
426
+ payload: encryptedPayloadData.encryptedPayload,
427
+ nonce: encryptedPayloadData.nonce
428
+ };
429
+ deeplinkUrl = this.buildUrl(rpcMethodName, signMsgDeeplinkQueryPayload as any);
430
+ break;
431
+ }
432
+ case SOLANA_SIGNING_METHODS.SOLANA_SIGN_ALL_TRANSACTIONS: {
433
+ const typedParams = requestParams as SignAllTransactionsRequestParams;
434
+ if (
435
+ !typedParams ||
436
+ !Array.isArray(typedParams.transactions) ||
437
+ !typedParams.transactions.every((t: any) => typeof t === 'string')
438
+ ) {
439
+ throw new Error(
440
+ `Missing or invalid 'transactions' (array of base58 strings) in params for ${signingMethod}`
441
+ );
442
+ }
443
+
444
+ const dataToEncrypt = {
445
+ session: this.sessionToken!,
446
+ transactions: typedParams.transactions
447
+ };
448
+ const encryptedData = this.encryptPayload(
449
+ dataToEncrypt,
450
+ this.phantomEncryptionPublicKeyBs58!
451
+ );
452
+ if (!encryptedData) {
453
+ throw new Error(`PhantomProvider: Failed to encrypt payload for ${signingMethod}.`);
454
+ }
455
+
456
+ const signAllTxDeeplinkParams: PhantomSignAllTransactionsParams = {
457
+ dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey),
458
+ redirect_link: this.config.appScheme,
459
+ cluster: this.currentCluster,
460
+ payload: encryptedData.encryptedPayload,
461
+ nonce: encryptedData.nonce
462
+ };
463
+ deeplinkUrl = this.buildUrl(rpcMethodName, signAllTxDeeplinkParams as any);
464
+ break;
465
+ }
466
+ default: {
467
+ throw new Error(`PhantomProvider: Unhandled signing method: ${signingMethod}`);
468
+ }
469
+ }
470
+
471
+ return new Promise<T>((resolve, reject) => {
472
+ let subscription: { remove: () => void } | null = null;
473
+ const handleDeepLink = async (event: { url: string }) => {
474
+ if (subscription) {
475
+ subscription.remove();
476
+ }
477
+ const fullUrl = event.url;
478
+ if (fullUrl.startsWith(this.config.appScheme)) {
479
+ const responseUrlParams = new URLSearchParams(
480
+ fullUrl.substring(fullUrl.indexOf('?') + 1)
481
+ );
482
+ const errorCode = responseUrlParams.get('errorCode');
483
+ const errorMessage = responseUrlParams.get('errorMessage');
484
+ if (errorCode) {
485
+ return reject(
486
+ new Error(
487
+ `Phantom ${signingMethod} Failed: ${
488
+ errorMessage || 'Unknown error'
489
+ } (Code: ${errorCode})`
490
+ )
491
+ );
492
+ }
493
+ const responseNonce = responseUrlParams.get('nonce');
494
+ const responseData = responseUrlParams.get('data');
495
+ if (!responseNonce || !responseData) {
496
+ return reject(
497
+ new Error(`Phantom ${signingMethod}: Invalid response - missing nonce or data.`)
498
+ );
499
+ }
500
+ const decryptedResult = this.decryptPayload<any>(
501
+ responseData,
502
+ responseNonce,
503
+ this.phantomEncryptionPublicKeyBs58!
504
+ );
505
+ if (!decryptedResult) {
506
+ return reject(
507
+ new Error(
508
+ `Phantom ${signingMethod}: Failed to decrypt response or invalid decrypted data.`
509
+ )
510
+ );
511
+ }
512
+ resolve(decryptedResult as T);
513
+ } else {
514
+ reject(new Error(`Phantom ${signingMethod}: Unexpected redirect URI.`));
515
+ }
516
+ };
517
+ subscription = Linking.addEventListener('url', handleDeepLink);
518
+ Linking.openURL(deeplinkUrl).catch(err => {
519
+ if (subscription) {
520
+ subscription.remove();
521
+ }
522
+ reject(
523
+ new Error(`Failed to open Phantom for ${signingMethod}: ${err.message}. Is it installed?`)
524
+ );
525
+ });
526
+ });
527
+ }
528
+ }
529
+
530
+ type Values<T> = T[keyof T];
package/src/types.ts ADDED
@@ -0,0 +1,131 @@
1
+ import type {
2
+ CaipNetworkId,
3
+ Namespaces,
4
+ Storage,
5
+ WalletInfo
6
+ } from '@reown/appkit-common-react-native';
7
+ import type nacl from 'tweetnacl';
8
+
9
+ // --- From helpers ---
10
+
11
+ export interface TokenInfo {
12
+ address: string;
13
+ symbol: string;
14
+ name: string;
15
+ decimals: number;
16
+ logoURI?: string;
17
+ }
18
+
19
+ // --- From PhantomProvider ---
20
+
21
+ export type PhantomCluster = 'mainnet-beta' | 'testnet' | 'devnet';
22
+
23
+ export interface PhantomProviderConfig {
24
+ appScheme: string;
25
+ dappUrl: string;
26
+ storage: Storage;
27
+ dappEncryptionKeyPair: nacl.BoxKeyPair;
28
+ }
29
+
30
+ export type PhantomConnectResult = PhantomSession;
31
+
32
+ export interface PhantomSession {
33
+ sessionToken: string;
34
+ userPublicKey: string;
35
+ phantomEncryptionPublicKeyBs58: string;
36
+ cluster: PhantomCluster;
37
+ }
38
+
39
+ export interface SignTransactionRequestParams {
40
+ transaction: string;
41
+ }
42
+ export interface SignMessageRequestParams {
43
+ message: Uint8Array | string;
44
+ display?: 'utf8' | 'hex';
45
+ }
46
+ export interface SignAllTransactionsRequestParams {
47
+ transactions: string[];
48
+ }
49
+
50
+ export interface PhantomDeeplinkResponse {
51
+ phantom_encryption_public_key?: string;
52
+ nonce: string;
53
+ data: string;
54
+ }
55
+
56
+ export interface DecryptedConnectData {
57
+ public_key: string;
58
+ session: string;
59
+ }
60
+
61
+ export interface PhantomProviderConfig {
62
+ appScheme: string;
63
+ dappUrl: string;
64
+ storage: Storage;
65
+ dappEncryptionKeyPair: nacl.BoxKeyPair;
66
+ }
67
+
68
+ export interface PhantomSession {
69
+ sessionToken: string;
70
+ userPublicKey: string;
71
+ phantomEncryptionPublicKeyBs58: string;
72
+ cluster: PhantomCluster;
73
+ }
74
+
75
+ // Actual method names used in Phantom deeplink URLs
76
+ export type PhantomRpcMethod =
77
+ | 'connect'
78
+ | 'disconnect'
79
+ | 'signTransaction'
80
+ | 'signAndSendTransaction'
81
+ | 'signAllTransactions'
82
+ | 'signMessage';
83
+
84
+ export interface PhantomSignTransactionParams {
85
+ dapp_encryption_public_key: string;
86
+ redirect_link: string;
87
+ payload: string; // Encrypted JSON: { session: string, transaction: string }
88
+ nonce: string;
89
+ cluster?: PhantomCluster;
90
+ }
91
+
92
+ export interface PhantomSignAllTransactionsParams {
93
+ dapp_encryption_public_key: string;
94
+ redirect_link: string;
95
+ payload: string; // Encrypted JSON: { session: string, transactions: string[] }
96
+ nonce: string;
97
+ cluster?: PhantomCluster;
98
+ }
99
+
100
+ export interface PhantomSignMessageParams {
101
+ dapp_encryption_public_key: string;
102
+ redirect_link: string;
103
+ payload: string; // Encrypted JSON string: { message: string, session: string, display: 'utf8'|'hex' }
104
+ nonce: string;
105
+ }
106
+
107
+ export interface PhantomConnectParams {
108
+ app_url: string;
109
+ dapp_encryption_public_key: string;
110
+ redirect_link: string;
111
+ cluster?: PhantomCluster;
112
+ }
113
+
114
+ export interface PhantomDisconnectParams {
115
+ dapp_encryption_public_key: string;
116
+ redirect_link: string;
117
+ payload: string; // Encrypted { session: string }
118
+ nonce: string;
119
+ }
120
+
121
+ // --- From PhantomConnector ---
122
+
123
+ export interface PhantomConnectorConfig {
124
+ cluster?: PhantomCluster;
125
+ }
126
+
127
+ export interface PhantomConnectorSessionData {
128
+ namespaces: Namespaces;
129
+ wallet: WalletInfo;
130
+ currentCaipNetworkId: CaipNetworkId;
131
+ }
package/src/index.tsx DELETED
@@ -1,2 +0,0 @@
1
- import { SolanaAdapter } from './adapter';
2
- export { SolanaAdapter };