@reown/appkit-solana-react-native 2.0.0-alpha.1 → 2.0.0-alpha.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.
Files changed (43) hide show
  1. package/lib/commonjs/adapter.js +120 -1
  2. package/lib/commonjs/adapter.js.map +1 -1
  3. package/lib/commonjs/connectors/PhantomConnector.js +11 -14
  4. package/lib/commonjs/connectors/PhantomConnector.js.map +1 -1
  5. package/lib/commonjs/helpers.js +0 -1
  6. package/lib/commonjs/helpers.js.map +1 -1
  7. package/lib/commonjs/index.js +1 -1
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/package.json +1 -0
  10. package/lib/commonjs/providers/PhantomProvider.js +3 -3
  11. package/lib/commonjs/providers/PhantomProvider.js.map +1 -1
  12. package/lib/commonjs/types.js.map +1 -1
  13. package/lib/commonjs/utils/createSendTransaction.js +44 -0
  14. package/lib/commonjs/utils/createSendTransaction.js.map +1 -0
  15. package/lib/module/adapter.js +121 -1
  16. package/lib/module/adapter.js.map +1 -1
  17. package/lib/module/connectors/PhantomConnector.js +14 -15
  18. package/lib/module/connectors/PhantomConnector.js.map +1 -1
  19. package/lib/module/helpers.js +2 -1
  20. package/lib/module/helpers.js.map +1 -1
  21. package/lib/module/index.js +6 -4
  22. package/lib/module/index.js.map +1 -1
  23. package/lib/module/providers/PhantomProvider.js +5 -3
  24. package/lib/module/providers/PhantomProvider.js.map +1 -1
  25. package/lib/module/types.js +2 -0
  26. package/lib/module/types.js.map +1 -1
  27. package/lib/module/utils/createSendTransaction.js +41 -0
  28. package/lib/module/utils/createSendTransaction.js.map +1 -0
  29. package/lib/typescript/adapter.d.ts +10 -0
  30. package/lib/typescript/adapter.d.ts.map +1 -1
  31. package/lib/typescript/connectors/PhantomConnector.d.ts +2 -1
  32. package/lib/typescript/connectors/PhantomConnector.d.ts.map +1 -1
  33. package/lib/typescript/index.d.ts +2 -2
  34. package/lib/typescript/index.d.ts.map +1 -1
  35. package/lib/typescript/providers/PhantomProvider.d.ts.map +1 -1
  36. package/lib/typescript/utils/createSendTransaction.d.ts +10 -0
  37. package/lib/typescript/utils/createSendTransaction.d.ts.map +1 -0
  38. package/package.json +9 -3
  39. package/src/adapter.ts +151 -1
  40. package/src/connectors/PhantomConnector.ts +16 -15
  41. package/src/index.ts +4 -4
  42. package/src/providers/PhantomProvider.ts +3 -1
  43. package/src/utils/createSendTransaction.ts +57 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reown/appkit-solana-react-native",
3
- "version": "2.0.0-alpha.1",
3
+ "version": "2.0.0-alpha.2",
4
4
  "main": "lib/commonjs/index.js",
5
5
  "types": "lib/typescript/index.d.ts",
6
6
  "module": "lib/module/index.js",
@@ -28,7 +28,7 @@
28
28
  "react-native"
29
29
  ],
30
30
  "repository": "https://github.com/reown-com/appkit-react-native",
31
- "author": "Reown <support@reown.com> (https://reown.com)",
31
+ "author": "Reown (https://discord.gg/reown)",
32
32
  "homepage": "https://reown.com/appkit",
33
33
  "license": "Apache-2.0",
34
34
  "bugs": {
@@ -39,8 +39,14 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@reown/appkit-common-react-native": "2.0.0-alpha.1",
42
+ "@reown/appkit-common-react-native": "2.0.0-alpha.2",
43
+ "@solana/web3.js": "1.98.2",
43
44
  "bs58": "6.0.0",
44
45
  "tweetnacl": "1.0.3"
46
+ },
47
+ "peerDependencies": {
48
+ "@walletconnect/react-native-compat": ">=2.16.1",
49
+ "react": ">=18",
50
+ "react-native": ">=0.72"
45
51
  }
46
52
  }
package/src/adapter.ts CHANGED
@@ -7,6 +7,17 @@ import {
7
7
  type GetBalanceResponse
8
8
  } from '@reown/appkit-common-react-native';
9
9
  import { getSolanaNativeBalance, getSolanaTokenBalance } from './helpers';
10
+ import { Connection, Transaction, VersionedTransaction } from '@solana/web3.js';
11
+ import base58 from 'bs58';
12
+ import { createSendTransaction } from './utils/createSendTransaction';
13
+
14
+ export interface SolanaTransactionData {
15
+ fromAddress: string;
16
+ toAddress: string;
17
+ amount: number;
18
+ network?: AppKitNetwork;
19
+ rpcUrl?: string;
20
+ }
10
21
 
11
22
  export class SolanaAdapter extends SolanaBaseAdapter {
12
23
  private static supportedNamespace: ChainNamespace = 'solana';
@@ -14,7 +25,8 @@ export class SolanaAdapter extends SolanaBaseAdapter {
14
25
  constructor(configParams: { projectId: string }) {
15
26
  super({
16
27
  projectId: configParams.projectId,
17
- supportedNamespace: SolanaAdapter.supportedNamespace
28
+ supportedNamespace: SolanaAdapter.supportedNamespace,
29
+ adapterType: 'solana'
18
30
  });
19
31
  }
20
32
 
@@ -64,6 +76,144 @@ export class SolanaAdapter extends SolanaBaseAdapter {
64
76
  }
65
77
  }
66
78
 
79
+ async signTransaction<T extends Transaction | VersionedTransaction>(
80
+ transaction: T,
81
+ network?: AppKitNetwork
82
+ ): Promise<T> {
83
+ if (!this.connector) {
84
+ throw new Error('SolanaAdapter:signTransaction - no active connector');
85
+ }
86
+
87
+ if (!network) {
88
+ throw new Error('SolanaAdapter:signTransaction - network is undefined');
89
+ }
90
+
91
+ const provider = this.connector.getProvider();
92
+ if (!provider) {
93
+ throw new Error('SolanaAdapter:signTransaction - provider is undefined');
94
+ }
95
+
96
+ try {
97
+ // Serialize transaction to base64 (following WalletConnect standard)
98
+ const serializedTransaction = Buffer.from(
99
+ new Uint8Array(transaction.serialize({ verifySignatures: false }))
100
+ ).toString('base64');
101
+
102
+ const result = (await provider.request(
103
+ {
104
+ method: 'solana_signTransaction',
105
+ params: {
106
+ transaction: serializedTransaction,
107
+ pubkey: this.getAccounts()?.[0]?.split(':')[2] || ''
108
+ }
109
+ },
110
+ network.caipNetworkId
111
+ )) as { signature?: string; transaction?: string };
112
+
113
+ // Handle different response formats
114
+ if ('signature' in result && result.signature) {
115
+ // Old RPC response format - add signature to transaction
116
+ const decoded = base58.decode(result.signature);
117
+ if (transaction instanceof Transaction && transaction.feePayer) {
118
+ transaction.addSignature(
119
+ transaction.feePayer,
120
+ Buffer.from(decoded) as Buffer & Uint8Array
121
+ );
122
+ }
123
+
124
+ return transaction;
125
+ }
126
+
127
+ if ('transaction' in result && result.transaction) {
128
+ // New response format - deserialize the signed transaction
129
+ const decodedTransaction = Buffer.from(result.transaction, 'base64');
130
+
131
+ if (transaction instanceof VersionedTransaction) {
132
+ return VersionedTransaction.deserialize(new Uint8Array(decodedTransaction)) as T;
133
+ }
134
+
135
+ return Transaction.from(decodedTransaction) as T;
136
+ }
137
+
138
+ throw new Error('SolanaAdapter:signTransaction - invalid response format');
139
+ } catch (error) {
140
+ if (error instanceof Error) {
141
+ throw new Error(`SolanaAdapter:signTransaction - ${error.message}`);
142
+ }
143
+ throw new Error('SolanaAdapter:signTransaction - unknown error occurred');
144
+ }
145
+ }
146
+
147
+ async sendTransaction(data: SolanaTransactionData): Promise<string | null> {
148
+ const { fromAddress, toAddress, amount, network, rpcUrl } = data;
149
+
150
+ if (!this.connector) {
151
+ throw new Error('SolanaAdapter:sendTransaction - no active connector');
152
+ }
153
+
154
+ const provider = this.connector.getProvider();
155
+ if (!provider) {
156
+ throw new Error('SolanaAdapter:sendTransaction - provider is undefined');
157
+ }
158
+
159
+ if (!network) {
160
+ throw new Error('SolanaAdapter:sendTransaction - network is undefined');
161
+ }
162
+
163
+ if (!fromAddress) {
164
+ throw new Error('SolanaAdapter:sendTransaction - fromAddress is undefined');
165
+ }
166
+
167
+ if (!toAddress) {
168
+ throw new Error('SolanaAdapter:sendTransaction - toAddress is undefined');
169
+ }
170
+
171
+ if (!amount || amount <= 0) {
172
+ throw new Error('SolanaAdapter:sendTransaction - amount must be greater than 0');
173
+ }
174
+
175
+ try {
176
+ // Determine RPC URL
177
+ let connectionRpcUrl = rpcUrl;
178
+ if (!connectionRpcUrl && network) {
179
+ connectionRpcUrl = network.rpcUrls?.default?.http?.[0];
180
+ }
181
+ if (!connectionRpcUrl) {
182
+ throw new Error('SolanaAdapter:sendTransaction - no RPC URL available');
183
+ }
184
+
185
+ // Create connection
186
+ const connection = new Connection(connectionRpcUrl, 'confirmed');
187
+
188
+ const transaction = await createSendTransaction({
189
+ connection,
190
+ fromAddress,
191
+ toAddress,
192
+ value: amount
193
+ });
194
+
195
+ // Sign the transaction
196
+ const signedTransaction = await this.signTransaction(transaction, network);
197
+
198
+ // Send the signed transaction
199
+ const signature = await connection.sendRawTransaction(signedTransaction.serialize(), {
200
+ skipPreflight: false,
201
+ preflightCommitment: 'confirmed'
202
+ });
203
+
204
+ if (!signature) {
205
+ throw new Error('SolanaAdapter:sendTransaction - no signature returned');
206
+ }
207
+
208
+ return signature;
209
+ } catch (error) {
210
+ if (error instanceof Error) {
211
+ throw new Error(`SolanaAdapter:sendTransaction - ${error.message}`);
212
+ }
213
+ throw new Error('SolanaAdapter:sendTransaction - unknown error occurred');
214
+ }
215
+ }
216
+
67
217
  async switchNetwork(network: AppKitNetwork): Promise<void> {
68
218
  if (!this.connector) throw new Error('No active connector');
69
219
 
@@ -11,7 +11,9 @@ import {
11
11
  type Storage,
12
12
  solana,
13
13
  solanaDevnet,
14
- solanaTestnet
14
+ solanaTestnet,
15
+ type ConnectionProperties,
16
+ ConstantsUtil
15
17
  } from '@reown/appkit-common-react-native';
16
18
  import nacl from 'tweetnacl';
17
19
  import bs58 from 'bs58';
@@ -51,10 +53,10 @@ export class PhantomConnector extends WalletConnector {
51
53
  this.storage = ops.storage;
52
54
  await this.initializeKeyPair();
53
55
 
54
- const appScheme = ops.metadata.redirect?.universal;
56
+ const appScheme = ops.metadata.redirect?.universal ?? ops.metadata.redirect?.native;
55
57
  if (!appScheme) {
56
58
  throw new Error(
57
- 'Phantom Connector: No universal link found in metadata. Please add redirect.universal to the metadata.'
59
+ 'Phantom Connector: No redirect link found in metadata. Please add redirect.universal or redirect.native to the metadata.'
58
60
  );
59
61
  }
60
62
 
@@ -119,10 +121,7 @@ export class PhantomConnector extends WalletConnector {
119
121
  }
120
122
  this.currentCaipNetworkId = `solana:${solanaChainIdPart}` as CaipNetworkId;
121
123
 
122
- this.wallet = {
123
- name: 'Phantom Wallet',
124
- id: 'phantom-wallet'
125
- };
124
+ this.wallet = ConstantsUtil.PHANTOM_CUSTOM_WALLET;
126
125
 
127
126
  const userPublicKey = this.getProvider().getUserPublicKey();
128
127
  if (!userPublicKey) {
@@ -149,11 +148,10 @@ export class PhantomConnector extends WalletConnector {
149
148
  }
150
149
 
151
150
  override async disconnect(): Promise<void> {
152
- if (!this.isConnected()) {
153
- return Promise.resolve();
154
- }
155
151
  try {
156
- await this.getProvider().disconnect();
152
+ if (this.isConnected()) {
153
+ await super.disconnect();
154
+ }
157
155
  } catch (error: any) {
158
156
  // console.warn(`PhantomConnector: Error during provider disconnect: ${error.message}. Proceeding with local clear.`);
159
157
  }
@@ -199,6 +197,10 @@ export class PhantomConnector extends WalletConnector {
199
197
  return undefined;
200
198
  }
201
199
 
200
+ override getProperties(): ConnectionProperties | undefined {
201
+ return this.properties;
202
+ }
203
+
202
204
  override getWalletInfo(): WalletInfo | undefined {
203
205
  if (!this.isConnected()) {
204
206
  return undefined;
@@ -234,9 +236,7 @@ export class PhantomConnector extends WalletConnector {
234
236
  return Promise.resolve();
235
237
  }
236
238
 
237
- // For deeplink wallets, switching network effectively means re-connecting to the new cluster.
238
- // We can try to disconnect the current session and then initiate a new connection.
239
- // console.log(`Attempting to switch network to: ${targetClusterName}`);
239
+ // Phantom doesn't provide a way to switch network, so we need to disconnect and reconnect.
240
240
  await this.disconnect(); // Clear current session
241
241
 
242
242
  // Create a temporary options object to guide the new connection
@@ -247,6 +247,7 @@ export class PhantomConnector extends WalletConnector {
247
247
  // Attempt to connect to the new cluster
248
248
  // The connect method will use the defaultChain from opts to determine the cluster.
249
249
  await this.connect(tempConnectOpts);
250
+ this.getProvider().emit('chainChanged', network.id);
250
251
 
251
252
  // Verify if the connection was successful and to the correct new network
252
253
  if (
@@ -260,7 +261,7 @@ export class PhantomConnector extends WalletConnector {
260
261
  }
261
262
 
262
263
  // Orchestrates session restoration
263
- public async restoreSession(): Promise<boolean> {
264
+ override async restoreSession(): Promise<boolean> {
264
265
  try {
265
266
  const providerSession = await this.getProvider().restoreSession();
266
267
  if (!providerSession) {
package/src/index.ts CHANGED
@@ -1,8 +1,8 @@
1
- // Connectors
2
- export { PhantomConnector } from './connectors/PhantomConnector';
1
+ // Adapter
2
+ export { SolanaAdapter } from './adapter';
3
3
 
4
4
  // Types
5
5
  export type { PhantomConnectorConfig } from './types';
6
6
 
7
- // Adapter
8
- export { SolanaAdapter } from './adapter';
7
+ // Connectors
8
+ export { PhantomConnector } from './connectors/PhantomConnector';
@@ -172,7 +172,7 @@ export class PhantomProvider extends EventEmitter implements Provider {
172
172
  cluster: this.currentCluster
173
173
  };
174
174
  try {
175
- await this.storage.setItem(PHANTOM_PROVIDER_STORAGE_KEY, JSON.stringify(session));
175
+ await this.storage.setItem(PHANTOM_PROVIDER_STORAGE_KEY, session);
176
176
  } catch (error) {
177
177
  // console.error('PhantomProvider: Failed to save session.', error);
178
178
  }
@@ -271,6 +271,7 @@ export class PhantomProvider extends EventEmitter implements Provider {
271
271
  public async disconnect(): Promise<void> {
272
272
  if (!this.sessionToken || !this.phantomEncryptionPublicKeyBs58) {
273
273
  await this.clearSession();
274
+ this.emit('disconnect');
274
275
 
275
276
  return Promise.resolve();
276
277
  }
@@ -284,6 +285,7 @@ export class PhantomProvider extends EventEmitter implements Provider {
284
285
  if (!encryptedDisconnectPayload) {
285
286
  // console.warn('PhantomProvider: Failed to encrypt disconnect payload. Clearing session locally.');
286
287
  await this.clearSession();
288
+ this.emit('disconnect');
287
289
 
288
290
  return Promise.resolve(); // Or reject, depending on desired strictness
289
291
  }
@@ -0,0 +1,57 @@
1
+ import {
2
+ ComputeBudgetProgram,
3
+ type Connection,
4
+ LAMPORTS_PER_SOL,
5
+ PublicKey,
6
+ SystemProgram,
7
+ Transaction
8
+ } from '@solana/web3.js';
9
+
10
+ // import type { Provider } from '@reown/appkit-utils/solana'
11
+
12
+ type SendTransactionArgs = {
13
+ connection: Connection;
14
+ fromAddress: string;
15
+ toAddress: string;
16
+ value: number;
17
+ };
18
+
19
+ /**
20
+ * These constants defines the cost of running the program, allowing to calculate the maximum
21
+ * amount of SOL that can be sent in case of cleaning the account and remove the rent exemption error.
22
+ */
23
+ const COMPUTE_BUDGET_CONSTANTS = {
24
+ UNIT_PRICE_MICRO_LAMPORTS: 20000000,
25
+ UNIT_LIMIT: 500
26
+ };
27
+
28
+ export async function createSendTransaction({
29
+ fromAddress,
30
+ toAddress,
31
+ value,
32
+ connection
33
+ }: SendTransactionArgs): Promise<Transaction> {
34
+ const fromPubkey = new PublicKey(fromAddress);
35
+ const toPubkey = new PublicKey(toAddress);
36
+ const lamports = Math.floor(value * LAMPORTS_PER_SOL);
37
+
38
+ const { blockhash } = await connection.getLatestBlockhash();
39
+
40
+ const instructions = [
41
+ ComputeBudgetProgram.setComputeUnitPrice({
42
+ microLamports: COMPUTE_BUDGET_CONSTANTS.UNIT_PRICE_MICRO_LAMPORTS
43
+ }),
44
+ ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_BUDGET_CONSTANTS.UNIT_LIMIT }),
45
+ SystemProgram.transfer({
46
+ fromPubkey,
47
+ toPubkey,
48
+ lamports
49
+ })
50
+ ];
51
+
52
+ const transaction = new Transaction().add(...instructions);
53
+ transaction.feePayer = fromPubkey;
54
+ transaction.recentBlockhash = blockhash;
55
+
56
+ return transaction;
57
+ }