@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.
- package/lib/commonjs/adapter.js +120 -1
- package/lib/commonjs/adapter.js.map +1 -1
- package/lib/commonjs/connectors/PhantomConnector.js +11 -14
- package/lib/commonjs/connectors/PhantomConnector.js.map +1 -1
- package/lib/commonjs/helpers.js +0 -1
- package/lib/commonjs/helpers.js.map +1 -1
- package/lib/commonjs/index.js +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/providers/PhantomProvider.js +3 -3
- package/lib/commonjs/providers/PhantomProvider.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/utils/createSendTransaction.js +44 -0
- package/lib/commonjs/utils/createSendTransaction.js.map +1 -0
- package/lib/module/adapter.js +121 -1
- package/lib/module/adapter.js.map +1 -1
- package/lib/module/connectors/PhantomConnector.js +14 -15
- package/lib/module/connectors/PhantomConnector.js.map +1 -1
- package/lib/module/helpers.js +2 -1
- package/lib/module/helpers.js.map +1 -1
- package/lib/module/index.js +6 -4
- package/lib/module/index.js.map +1 -1
- package/lib/module/providers/PhantomProvider.js +5 -3
- package/lib/module/providers/PhantomProvider.js.map +1 -1
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/createSendTransaction.js +41 -0
- package/lib/module/utils/createSendTransaction.js.map +1 -0
- package/lib/typescript/adapter.d.ts +10 -0
- package/lib/typescript/adapter.d.ts.map +1 -1
- package/lib/typescript/connectors/PhantomConnector.d.ts +2 -1
- package/lib/typescript/connectors/PhantomConnector.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/providers/PhantomProvider.d.ts.map +1 -1
- package/lib/typescript/utils/createSendTransaction.d.ts +10 -0
- package/lib/typescript/utils/createSendTransaction.d.ts.map +1 -0
- package/package.json +9 -3
- package/src/adapter.ts +151 -1
- package/src/connectors/PhantomConnector.ts +16 -15
- package/src/index.ts +4 -4
- package/src/providers/PhantomProvider.ts +3 -1
- 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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
2
|
-
export {
|
|
1
|
+
// Adapter
|
|
2
|
+
export { SolanaAdapter } from './adapter';
|
|
3
3
|
|
|
4
4
|
// Types
|
|
5
5
|
export type { PhantomConnectorConfig } from './types';
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
export {
|
|
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,
|
|
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
|
+
}
|