@reown/appkit-solana-react-native 0.0.0-chore-solflare-20250730210452 → 0.0.0-chore-spring-effect-20250909214820
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 +4 -5
- package/lib/commonjs/adapter.js.map +1 -1
- package/lib/commonjs/connectors/{SolanaDeeplinkConnector.js → DeeplinkConnector.js} +80 -51
- package/lib/commonjs/connectors/DeeplinkConnector.js.map +1 -0
- package/lib/commonjs/connectors/PhantomConnector.js +5 -4
- package/lib/commonjs/connectors/PhantomConnector.js.map +1 -1
- package/lib/commonjs/connectors/SolflareConnector.js +5 -4
- package/lib/commonjs/connectors/SolflareConnector.js.map +1 -1
- package/lib/commonjs/index.js +0 -20
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/providers/{SolanaDeeplinkProvider.js → DeeplinkProvider.js} +177 -138
- package/lib/commonjs/providers/DeeplinkProvider.js.map +1 -0
- package/lib/module/adapter.js +4 -5
- package/lib/module/adapter.js.map +1 -1
- package/lib/module/connectors/{SolanaDeeplinkConnector.js → DeeplinkConnector.js} +77 -48
- package/lib/module/connectors/DeeplinkConnector.js.map +1 -0
- package/lib/module/connectors/PhantomConnector.js +5 -4
- package/lib/module/connectors/PhantomConnector.js.map +1 -1
- package/lib/module/connectors/SolflareConnector.js +5 -4
- package/lib/module/connectors/SolflareConnector.js.map +1 -1
- package/lib/module/index.js +2 -6
- package/lib/module/index.js.map +1 -1
- package/lib/module/providers/{SolanaDeeplinkProvider.js → DeeplinkProvider.js} +175 -136
- package/lib/module/providers/DeeplinkProvider.js.map +1 -0
- package/lib/typescript/adapter.d.ts +1 -3
- package/lib/typescript/adapter.d.ts.map +1 -1
- package/lib/typescript/connectors/{SolanaDeeplinkConnector.d.ts → DeeplinkConnector.d.ts} +11 -13
- package/lib/typescript/connectors/DeeplinkConnector.d.ts.map +1 -0
- package/lib/typescript/connectors/PhantomConnector.d.ts +4 -3
- package/lib/typescript/connectors/PhantomConnector.d.ts.map +1 -1
- package/lib/typescript/connectors/SolflareConnector.d.ts +4 -3
- package/lib/typescript/connectors/SolflareConnector.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/providers/{SolanaDeeplinkProvider.d.ts → DeeplinkProvider.d.ts} +29 -8
- package/lib/typescript/providers/DeeplinkProvider.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +33 -33
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +4 -5
- package/src/connectors/{SolanaDeeplinkConnector.ts → DeeplinkConnector.ts} +107 -90
- package/src/connectors/PhantomConnector.ts +5 -4
- package/src/connectors/SolflareConnector.ts +5 -4
- package/src/index.ts +3 -22
- package/src/providers/{SolanaDeeplinkProvider.ts → DeeplinkProvider.ts} +271 -225
- package/src/types.ts +41 -48
- package/lib/commonjs/connectors/SolanaDeeplinkConnector.js.map +0 -1
- package/lib/commonjs/providers/SolanaDeeplinkProvider.js.map +0 -1
- package/lib/module/connectors/SolanaDeeplinkConnector.js.map +0 -1
- package/lib/module/providers/SolanaDeeplinkProvider.js.map +0 -1
- package/lib/typescript/connectors/SolanaDeeplinkConnector.d.ts.map +0 -1
- package/lib/typescript/providers/SolanaDeeplinkProvider.d.ts.map +0 -1
|
@@ -9,21 +9,21 @@ import type {
|
|
|
9
9
|
Storage
|
|
10
10
|
} from '@reown/appkit-common-react-native';
|
|
11
11
|
import type {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
DeeplinkProviderConfig,
|
|
13
|
+
DeeplinkConnectResult,
|
|
14
|
+
DeeplinkSession,
|
|
15
15
|
DecryptedConnectData,
|
|
16
|
-
|
|
16
|
+
DeeplinkResponse,
|
|
17
17
|
SignAllTransactionsRequestParams,
|
|
18
18
|
SignMessageRequestParams,
|
|
19
19
|
SignTransactionRequestParams,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
DeeplinkRpcMethod,
|
|
21
|
+
DeeplinkConnectParams,
|
|
22
|
+
DeeplinkDisconnectParams,
|
|
23
|
+
DeeplinkSignTransactionParams,
|
|
24
|
+
DeeplinkSignMessageParams,
|
|
25
|
+
DeeplinkSignAllTransactionsParams,
|
|
26
|
+
Cluster
|
|
27
27
|
} from '../types';
|
|
28
28
|
import EventEmitter from 'events';
|
|
29
29
|
|
|
@@ -40,10 +40,11 @@ function isValidSolanaSigningMethod(method: string): method is SolanaSigningMeth
|
|
|
40
40
|
return Object.values(SOLANA_SIGNING_METHODS).includes(method as SolanaSigningMethod);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export class
|
|
44
|
-
private readonly config:
|
|
43
|
+
export class DeeplinkProvider extends EventEmitter implements Provider {
|
|
44
|
+
private readonly config: DeeplinkProviderConfig;
|
|
45
45
|
private dappEncryptionKeyPair: nacl.BoxKeyPair;
|
|
46
|
-
private currentCluster:
|
|
46
|
+
private currentCluster: Cluster;
|
|
47
|
+
private sharedKey: Uint8Array | null = null;
|
|
47
48
|
|
|
48
49
|
private storage: Storage;
|
|
49
50
|
|
|
@@ -51,15 +52,51 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
51
52
|
private userPublicKey: string | null = null;
|
|
52
53
|
private walletEncryptionPublicKeyBs58: string | null = null;
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
// Single subscription management - deep links are sequential by nature
|
|
56
|
+
private activeSubscription: { remove: () => void } | null = null;
|
|
57
|
+
private isOperationPending = false;
|
|
58
|
+
|
|
59
|
+
constructor(config: DeeplinkProviderConfig) {
|
|
55
60
|
super();
|
|
56
61
|
this.config = config;
|
|
62
|
+
this.currentCluster = config.cluster ?? 'mainnet-beta';
|
|
57
63
|
this.dappEncryptionKeyPair = config.dappEncryptionKeyPair;
|
|
58
64
|
this.storage = config.storage;
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
private
|
|
62
|
-
return `@appkit/${this.config.
|
|
67
|
+
private getSessionStorageKey(): string {
|
|
68
|
+
return `@appkit/${this.config.type}-provider-session`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Cleanup method to be called when the provider is destroyed
|
|
73
|
+
*/
|
|
74
|
+
public destroy(): void {
|
|
75
|
+
this.cleanupActiveSubscription();
|
|
76
|
+
this.removeAllListeners();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Safely cleanup the active subscription
|
|
81
|
+
*/
|
|
82
|
+
private cleanupActiveSubscription(): void {
|
|
83
|
+
if (this.activeSubscription) {
|
|
84
|
+
this.activeSubscription.remove();
|
|
85
|
+
this.activeSubscription = null;
|
|
86
|
+
}
|
|
87
|
+
this.isOperationPending = false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Safely set a new subscription, ensuring no operation is pending
|
|
92
|
+
*/
|
|
93
|
+
private setActiveSubscription(subscription: { remove: () => void }): void {
|
|
94
|
+
// If there's already a pending operation, reject it
|
|
95
|
+
if (this.isOperationPending) {
|
|
96
|
+
this.cleanupActiveSubscription();
|
|
97
|
+
}
|
|
98
|
+
this.activeSubscription = subscription;
|
|
99
|
+
this.isOperationPending = true;
|
|
63
100
|
}
|
|
64
101
|
|
|
65
102
|
getUserPublicKey(): string | null {
|
|
@@ -70,13 +107,70 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
70
107
|
return !!this.sessionToken && !!this.userPublicKey && !!this.dappEncryptionKeyPair;
|
|
71
108
|
}
|
|
72
109
|
|
|
73
|
-
|
|
110
|
+
public getCurrentCluster(): Cluster {
|
|
111
|
+
return this.currentCluster;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private buildUrl(rpcMethod: DeeplinkRpcMethod, params: Record<string, string>): string {
|
|
74
115
|
const query = new URLSearchParams(params).toString();
|
|
75
116
|
|
|
76
117
|
return `${this.config.baseUrl}/${rpcMethod}?${query}`;
|
|
77
118
|
}
|
|
78
119
|
|
|
79
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Open a deeplink URL and wait for a redirect back to the app. Handles subscription
|
|
122
|
+
* lifecycle and common error extraction from `errorCode`/`errorMessage` query params.
|
|
123
|
+
*/
|
|
124
|
+
private async openDeeplinkAndWait<T>(
|
|
125
|
+
deeplinkUrl: string,
|
|
126
|
+
processParams: (params: URLSearchParams) => Promise<T> | T,
|
|
127
|
+
contextLabel: string
|
|
128
|
+
): Promise<T> {
|
|
129
|
+
return new Promise<T>((resolve, reject) => {
|
|
130
|
+
const handleDeepLink = async (event: { url: string }) => {
|
|
131
|
+
try {
|
|
132
|
+
this.cleanupActiveSubscription();
|
|
133
|
+
const fullUrl = event.url;
|
|
134
|
+
if (!fullUrl.startsWith(this.config.appScheme)) {
|
|
135
|
+
return reject(
|
|
136
|
+
new Error(`${this.config.type} provider: ${contextLabel}: Unexpected redirect URI.`)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const params = new URLSearchParams(fullUrl.substring(fullUrl.indexOf('?') + 1));
|
|
140
|
+
const errorCode = params.get('errorCode');
|
|
141
|
+
const errorMessage = params.get('errorMessage');
|
|
142
|
+
if (errorCode) {
|
|
143
|
+
return reject(
|
|
144
|
+
new Error(
|
|
145
|
+
`${this.config.type} provider: ${contextLabel} Failed: ${
|
|
146
|
+
errorMessage || 'Unknown error'
|
|
147
|
+
} (Code: ${errorCode})`
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const result = await Promise.resolve(processParams(params));
|
|
152
|
+
resolve(result);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
this.cleanupActiveSubscription();
|
|
155
|
+
reject(error);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const subscription = Linking.addEventListener('url', handleDeepLink);
|
|
160
|
+
this.setActiveSubscription(subscription);
|
|
161
|
+
|
|
162
|
+
Linking.openURL(deeplinkUrl).catch(err => {
|
|
163
|
+
this.cleanupActiveSubscription();
|
|
164
|
+
reject(
|
|
165
|
+
new Error(
|
|
166
|
+
`${this.config.type} provider: Failed to open wallet for ${contextLabel}: ${err.message}.`
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private getRpcMethodName(method: SolanaSigningMethod): DeeplinkRpcMethod {
|
|
80
174
|
switch (method) {
|
|
81
175
|
case SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION:
|
|
82
176
|
return 'signTransaction';
|
|
@@ -94,27 +188,34 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
94
188
|
|
|
95
189
|
private encryptPayload(
|
|
96
190
|
payload: Record<string, unknown>,
|
|
97
|
-
|
|
191
|
+
walletPublicKeyBs58: string
|
|
98
192
|
): { nonce: string; encryptedPayload: string } | null {
|
|
99
|
-
if (!
|
|
193
|
+
if (!walletPublicKeyBs58) {
|
|
100
194
|
return null;
|
|
101
195
|
}
|
|
102
196
|
try {
|
|
103
|
-
const walletPublicKeyBytes = bs58.decode(walletPublicKeyBs58ToEncryptFor);
|
|
104
197
|
const nonce = nacl.randomBytes(nacl.box.nonceLength);
|
|
105
198
|
const payloadBytes = Buffer.from(JSON.stringify(payload), 'utf8');
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
nonce,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
199
|
+
let encryptedPayload: Uint8Array | null;
|
|
200
|
+
if (this.sharedKey) {
|
|
201
|
+
encryptedPayload = nacl.box.after(payloadBytes, nonce, this.sharedKey);
|
|
202
|
+
} else {
|
|
203
|
+
const walletPublicKeyBytes = bs58.decode(walletPublicKeyBs58);
|
|
204
|
+
encryptedPayload = nacl.box(
|
|
205
|
+
payloadBytes,
|
|
206
|
+
nonce,
|
|
207
|
+
walletPublicKeyBytes,
|
|
208
|
+
this.dappEncryptionKeyPair.secretKey
|
|
209
|
+
);
|
|
210
|
+
}
|
|
112
211
|
|
|
113
212
|
return {
|
|
114
213
|
nonce: bs58.encode(nonce),
|
|
115
214
|
encryptedPayload: bs58.encode(encryptedPayload)
|
|
116
215
|
};
|
|
117
216
|
} catch (error) {
|
|
217
|
+
console.warn(`${this.config.type} provider: Failed to encrypt payload.`, error);
|
|
218
|
+
|
|
118
219
|
return null;
|
|
119
220
|
}
|
|
120
221
|
}
|
|
@@ -125,37 +226,56 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
125
226
|
walletSenderPublicKeyBs58: string
|
|
126
227
|
): T | null {
|
|
127
228
|
try {
|
|
128
|
-
const
|
|
229
|
+
const formattedEncryptedDataBs58 = encryptedDataBs58.replace('#', '');
|
|
230
|
+
const encryptedDataBytes = bs58.decode(formattedEncryptedDataBs58);
|
|
129
231
|
const nonceBytes = bs58.decode(nonceBs58);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
encryptedDataBytes,
|
|
133
|
-
|
|
134
|
-
walletSenderPublicKeyBytes
|
|
135
|
-
|
|
136
|
-
|
|
232
|
+
let decryptedPayloadBytes: Uint8Array | null;
|
|
233
|
+
if (this.sharedKey) {
|
|
234
|
+
decryptedPayloadBytes = nacl.box.open.after(encryptedDataBytes, nonceBytes, this.sharedKey);
|
|
235
|
+
} else {
|
|
236
|
+
const walletSenderPublicKeyBytes = bs58.decode(walletSenderPublicKeyBs58);
|
|
237
|
+
decryptedPayloadBytes = nacl.box.open(
|
|
238
|
+
encryptedDataBytes,
|
|
239
|
+
nonceBytes,
|
|
240
|
+
walletSenderPublicKeyBytes,
|
|
241
|
+
this.dappEncryptionKeyPair.secretKey
|
|
242
|
+
);
|
|
243
|
+
}
|
|
137
244
|
if (!decryptedPayloadBytes) {
|
|
138
245
|
return null;
|
|
139
246
|
}
|
|
140
247
|
|
|
141
248
|
return JSON.parse(Buffer.from(decryptedPayloadBytes).toString('utf8')) as T;
|
|
142
249
|
} catch (error) {
|
|
250
|
+
console.warn(`${this.config.type} provider: Failed to decrypt payload.`, error);
|
|
251
|
+
|
|
143
252
|
return null;
|
|
144
253
|
}
|
|
145
254
|
}
|
|
146
255
|
|
|
147
256
|
public async restoreSession(): Promise<boolean> {
|
|
148
257
|
try {
|
|
149
|
-
const session = await this.storage.getItem<
|
|
258
|
+
const session = await this.storage.getItem<DeeplinkSession>(this.getSessionStorageKey());
|
|
150
259
|
if (session) {
|
|
151
260
|
this.setSession(session);
|
|
152
261
|
|
|
262
|
+
// Recompute shared key on session restore
|
|
263
|
+
try {
|
|
264
|
+
const walletPublicKeyBytes = bs58.decode(session.walletEncryptionPublicKeyBs58);
|
|
265
|
+
this.sharedKey = nacl.box.before(
|
|
266
|
+
walletPublicKeyBytes,
|
|
267
|
+
this.dappEncryptionKeyPair.secretKey
|
|
268
|
+
);
|
|
269
|
+
} catch (e) {
|
|
270
|
+
this.sharedKey = null;
|
|
271
|
+
}
|
|
272
|
+
|
|
153
273
|
return true;
|
|
154
274
|
}
|
|
155
275
|
|
|
156
276
|
return false;
|
|
157
277
|
} catch (error) {
|
|
158
|
-
// console.error(`${this.config.
|
|
278
|
+
// console.error(`${this.config.type} provider: Failed to restore session.`, error);
|
|
159
279
|
await this.clearSessionStorage(); // Clear potentially corrupt data
|
|
160
280
|
|
|
161
281
|
return false;
|
|
@@ -166,31 +286,31 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
166
286
|
if (!this.sessionToken || !this.userPublicKey || !this.walletEncryptionPublicKeyBs58) {
|
|
167
287
|
return; // Cannot save incomplete session
|
|
168
288
|
}
|
|
169
|
-
const session:
|
|
289
|
+
const session: DeeplinkSession = {
|
|
170
290
|
sessionToken: this.sessionToken,
|
|
171
291
|
userPublicKey: this.userPublicKey,
|
|
172
292
|
walletEncryptionPublicKeyBs58: this.walletEncryptionPublicKeyBs58,
|
|
173
293
|
cluster: this.currentCluster
|
|
174
294
|
};
|
|
175
295
|
try {
|
|
176
|
-
await this.storage.setItem(this.
|
|
296
|
+
await this.storage.setItem(this.getSessionStorageKey(), session);
|
|
177
297
|
} catch (error) {
|
|
178
|
-
// console.error(`${this.config.
|
|
298
|
+
// console.error(`${this.config.type} provider: Failed to save session.`, error);
|
|
179
299
|
}
|
|
180
300
|
}
|
|
181
301
|
|
|
182
302
|
private async clearSessionStorage(): Promise<void> {
|
|
183
303
|
try {
|
|
184
|
-
await this.storage.removeItem(this.
|
|
304
|
+
await this.storage.removeItem(this.getSessionStorageKey());
|
|
185
305
|
} catch (error) {
|
|
186
|
-
// console.error(`${this.config.
|
|
306
|
+
// console.error(`${this.config.type} provider: Failed to clear session storage.`, error);
|
|
187
307
|
}
|
|
188
308
|
}
|
|
189
309
|
|
|
190
|
-
public async connect<T =
|
|
310
|
+
public async connect<T = DeeplinkConnectResult>(params?: { cluster?: Cluster }): Promise<T> {
|
|
191
311
|
const cluster = params?.cluster ?? 'mainnet-beta';
|
|
192
312
|
this.currentCluster = cluster;
|
|
193
|
-
const connectDeeplinkParams:
|
|
313
|
+
const connectDeeplinkParams: DeeplinkConnectParams = {
|
|
194
314
|
app_url: this.config.dappUrl,
|
|
195
315
|
dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey),
|
|
196
316
|
redirect_link: this.config.appScheme,
|
|
@@ -198,87 +318,59 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
198
318
|
};
|
|
199
319
|
const url = this.buildUrl('connect', connectDeeplinkParams as any);
|
|
200
320
|
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
321
|
+
return this.openDeeplinkAndWait<DeeplinkConnectResult>(
|
|
322
|
+
url,
|
|
323
|
+
(responseUrlParams: URLSearchParams) => {
|
|
324
|
+
const responsePayload: DeeplinkResponse = {
|
|
325
|
+
wallet_encryption_public_key: responseUrlParams.get(this.config.encryptionKeyFieldName)!,
|
|
326
|
+
nonce: responseUrlParams.get('nonce')!,
|
|
327
|
+
data: responseUrlParams.get('data')!
|
|
328
|
+
};
|
|
329
|
+
if (
|
|
330
|
+
!responsePayload.wallet_encryption_public_key ||
|
|
331
|
+
!responsePayload.nonce ||
|
|
332
|
+
!responsePayload.data
|
|
333
|
+
) {
|
|
334
|
+
throw new Error(`${this.config.type} provider: Invalid response - missing parameters.`);
|
|
206
335
|
}
|
|
207
|
-
const fullUrl = event.url;
|
|
208
|
-
if (fullUrl.startsWith(this.config.appScheme)) {
|
|
209
|
-
const responseUrlParams = new URLSearchParams(
|
|
210
|
-
fullUrl.substring(fullUrl.indexOf('?') + 1)
|
|
211
|
-
);
|
|
212
|
-
const errorCode = responseUrlParams.get('errorCode');
|
|
213
|
-
const errorMessage = responseUrlParams.get('errorMessage');
|
|
214
|
-
if (errorCode) {
|
|
215
|
-
return reject(
|
|
216
|
-
new Error(
|
|
217
|
-
`${this.config.walletType} Connection Failed: ${
|
|
218
|
-
errorMessage || 'Unknown error'
|
|
219
|
-
} (Code: ${errorCode})`
|
|
220
|
-
)
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
const walletEncryptionPublicKey = responseUrlParams.get(
|
|
224
|
-
this.config.encryptionKeyFieldName
|
|
225
|
-
);
|
|
226
336
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
!responsePayload.data
|
|
236
|
-
) {
|
|
237
|
-
return reject(
|
|
238
|
-
new Error(`${this.config.walletType} Connect: Invalid response - missing parameters.`)
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
const decryptedData = this.decryptPayload<DecryptedConnectData>(
|
|
242
|
-
responsePayload.data,
|
|
243
|
-
responsePayload.nonce,
|
|
244
|
-
responsePayload.wallet_encryption_public_key
|
|
337
|
+
const decryptedData = this.decryptPayload<DecryptedConnectData>(
|
|
338
|
+
responsePayload.data,
|
|
339
|
+
responsePayload.nonce,
|
|
340
|
+
responsePayload.wallet_encryption_public_key
|
|
341
|
+
);
|
|
342
|
+
if (!decryptedData || !decryptedData.public_key || !decryptedData.session) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
`${this.config.type} provider: Failed to decrypt or invalid decrypted data.`
|
|
245
345
|
);
|
|
246
|
-
if (!decryptedData || !decryptedData.public_key || !decryptedData.session) {
|
|
247
|
-
return reject(
|
|
248
|
-
new Error(
|
|
249
|
-
`${this.config.walletType} Connect: Failed to decrypt or invalid decrypted data.`
|
|
250
|
-
)
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
this.userPublicKey = decryptedData.public_key;
|
|
254
|
-
this.sessionToken = decryptedData.session;
|
|
255
|
-
this.walletEncryptionPublicKeyBs58 = responsePayload.wallet_encryption_public_key;
|
|
256
|
-
|
|
257
|
-
// Save session on successful connect
|
|
258
|
-
this.saveSession();
|
|
259
|
-
|
|
260
|
-
resolve({
|
|
261
|
-
userPublicKey: this.userPublicKey,
|
|
262
|
-
sessionToken: this.sessionToken,
|
|
263
|
-
walletEncryptionPublicKeyBs58: this.walletEncryptionPublicKeyBs58,
|
|
264
|
-
cluster
|
|
265
|
-
});
|
|
266
|
-
} else {
|
|
267
|
-
reject(new Error(`${this.config.walletType} Connect: Unexpected redirect URI.`));
|
|
268
346
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
347
|
+
this.userPublicKey = decryptedData.public_key;
|
|
348
|
+
this.sessionToken = decryptedData.session;
|
|
349
|
+
this.walletEncryptionPublicKeyBs58 = responsePayload.wallet_encryption_public_key;
|
|
350
|
+
|
|
351
|
+
// Precompute shared key for subsequent communications
|
|
352
|
+
try {
|
|
353
|
+
const walletPublicKeyBytes = bs58.decode(this.walletEncryptionPublicKeyBs58);
|
|
354
|
+
this.sharedKey = nacl.box.before(
|
|
355
|
+
walletPublicKeyBytes,
|
|
356
|
+
this.dappEncryptionKeyPair.secretKey
|
|
357
|
+
);
|
|
358
|
+
} catch (e) {
|
|
359
|
+
this.sharedKey = null;
|
|
274
360
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
361
|
+
|
|
362
|
+
// Save session on successful connect
|
|
363
|
+
this.saveSession();
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
userPublicKey: this.userPublicKey!,
|
|
367
|
+
sessionToken: this.sessionToken!,
|
|
368
|
+
walletEncryptionPublicKeyBs58: this.walletEncryptionPublicKeyBs58!,
|
|
369
|
+
cluster
|
|
370
|
+
} as DeeplinkConnectResult;
|
|
371
|
+
},
|
|
372
|
+
'Connection'
|
|
373
|
+
) as Promise<T>;
|
|
282
374
|
}
|
|
283
375
|
|
|
284
376
|
public async disconnect(): Promise<void> {
|
|
@@ -296,14 +388,14 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
296
388
|
);
|
|
297
389
|
|
|
298
390
|
if (!encryptedDisconnectPayload) {
|
|
299
|
-
// console.warn(`${this.config.
|
|
391
|
+
// console.warn(`${this.config.type} provider: Failed to encrypt disconnect payload. Clearing session locally.`);
|
|
300
392
|
await this.clearSession();
|
|
301
393
|
this.emit('disconnect');
|
|
302
394
|
|
|
303
395
|
return Promise.resolve(); // Or reject, depending on desired strictness
|
|
304
396
|
}
|
|
305
397
|
|
|
306
|
-
const disconnectDeeplinkParams:
|
|
398
|
+
const disconnectDeeplinkParams: DeeplinkDisconnectParams = {
|
|
307
399
|
dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey),
|
|
308
400
|
redirect_link: this.config.appScheme,
|
|
309
401
|
payload: encryptedDisconnectPayload.encryptedPayload,
|
|
@@ -311,41 +403,28 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
311
403
|
};
|
|
312
404
|
const url = this.buildUrl('disconnect', disconnectDeeplinkParams as any);
|
|
313
405
|
|
|
314
|
-
return
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (subscription) {
|
|
318
|
-
subscription.remove();
|
|
319
|
-
}
|
|
320
|
-
if (event.url.startsWith(this.config.appScheme)) {
|
|
321
|
-
this.clearSession();
|
|
322
|
-
resolve();
|
|
323
|
-
} else {
|
|
324
|
-
this.clearSession();
|
|
325
|
-
reject(new Error(`${this.config.walletType} Disconnect: Unexpected redirect URI.`));
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
subscription = Linking.addEventListener('url', handleDeepLink);
|
|
329
|
-
Linking.openURL(url).catch(err => {
|
|
330
|
-
if (subscription) {
|
|
331
|
-
subscription.remove();
|
|
332
|
-
}
|
|
406
|
+
return this.openDeeplinkAndWait<void>(
|
|
407
|
+
url,
|
|
408
|
+
() => {
|
|
333
409
|
this.clearSession();
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
});
|
|
338
|
-
});
|
|
410
|
+
},
|
|
411
|
+
'Disconnection'
|
|
412
|
+
);
|
|
339
413
|
}
|
|
340
414
|
|
|
341
415
|
public async clearSession(): Promise<void> {
|
|
342
416
|
this.sessionToken = null;
|
|
343
417
|
this.userPublicKey = null;
|
|
344
418
|
this.walletEncryptionPublicKeyBs58 = null;
|
|
419
|
+
if (this.sharedKey) {
|
|
420
|
+
this.sharedKey.fill(0);
|
|
421
|
+
}
|
|
422
|
+
this.sharedKey = null;
|
|
423
|
+
this.cleanupActiveSubscription();
|
|
345
424
|
await this.clearSessionStorage();
|
|
346
425
|
}
|
|
347
426
|
|
|
348
|
-
public setSession(session:
|
|
427
|
+
public setSession(session: DeeplinkSession): void {
|
|
349
428
|
this.sessionToken = session.sessionToken;
|
|
350
429
|
this.userPublicKey = session.userPublicKey;
|
|
351
430
|
this.walletEncryptionPublicKeyBs58 = session.walletEncryptionPublicKeyBs58;
|
|
@@ -355,7 +434,7 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
355
434
|
public async request<T>(args: RequestArguments, _chainId?: CaipNetworkId): Promise<T> {
|
|
356
435
|
if (!isValidSolanaSigningMethod(args.method)) {
|
|
357
436
|
throw new Error(
|
|
358
|
-
`${this.config.
|
|
437
|
+
`${this.config.type} provider: Unsupported method: ${args.method}. Only Solana signing methods are supported.`
|
|
359
438
|
);
|
|
360
439
|
}
|
|
361
440
|
const signingMethod = args.method as SolanaSigningMethod;
|
|
@@ -363,7 +442,7 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
363
442
|
|
|
364
443
|
if (!this.isConnected() || !this.sessionToken || !this.walletEncryptionPublicKeyBs58) {
|
|
365
444
|
throw new Error(
|
|
366
|
-
`${this.config.
|
|
445
|
+
`${this.config.type} provider: Not connected or session details missing. Cannot process request.`
|
|
367
446
|
);
|
|
368
447
|
}
|
|
369
448
|
|
|
@@ -381,20 +460,20 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
381
460
|
}
|
|
382
461
|
|
|
383
462
|
const dataToEncrypt = {
|
|
384
|
-
session: this.sessionToken
|
|
463
|
+
session: this.sessionToken,
|
|
385
464
|
transaction: typedParams.transaction
|
|
386
465
|
};
|
|
387
466
|
const encryptedData = this.encryptPayload(
|
|
388
467
|
dataToEncrypt,
|
|
389
|
-
this.walletEncryptionPublicKeyBs58
|
|
468
|
+
this.walletEncryptionPublicKeyBs58
|
|
390
469
|
);
|
|
391
470
|
if (!encryptedData) {
|
|
392
471
|
throw new Error(
|
|
393
|
-
`${this.config.
|
|
472
|
+
`${this.config.type} provider: Failed to encrypt payload for ${signingMethod}.`
|
|
394
473
|
);
|
|
395
474
|
}
|
|
396
475
|
|
|
397
|
-
const signTxDeeplinkParams:
|
|
476
|
+
const signTxDeeplinkParams: DeeplinkSignTransactionParams = {
|
|
398
477
|
dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey),
|
|
399
478
|
redirect_link: this.config.appScheme,
|
|
400
479
|
cluster: this.currentCluster,
|
|
@@ -407,7 +486,9 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
407
486
|
case SOLANA_SIGNING_METHODS.SOLANA_SIGN_MESSAGE: {
|
|
408
487
|
const typedParams = requestParams as SignMessageRequestParams;
|
|
409
488
|
if (!typedParams || typeof typedParams.message === 'undefined') {
|
|
410
|
-
throw new Error(
|
|
489
|
+
throw new Error(
|
|
490
|
+
`${this.config.type} provider: Missing 'message' in params for ${signingMethod}`
|
|
491
|
+
);
|
|
411
492
|
}
|
|
412
493
|
|
|
413
494
|
let messageBs58: string;
|
|
@@ -421,27 +502,29 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
421
502
|
messageBs58 = bs58.encode(Buffer.from(typedParams.message));
|
|
422
503
|
}
|
|
423
504
|
} else {
|
|
424
|
-
throw new Error(
|
|
505
|
+
throw new Error(
|
|
506
|
+
`${this.config.type} provider: Invalid message format for signMessage. Expected Uint8Array or string.`
|
|
507
|
+
);
|
|
425
508
|
}
|
|
426
509
|
|
|
427
510
|
const dataToEncrypt = {
|
|
428
511
|
message: messageBs58,
|
|
429
|
-
session: this.sessionToken
|
|
512
|
+
session: this.sessionToken,
|
|
430
513
|
display: typedParams.display || 'utf8'
|
|
431
514
|
};
|
|
432
515
|
|
|
433
516
|
const encryptedPayloadData = this.encryptPayload(
|
|
434
517
|
dataToEncrypt,
|
|
435
|
-
this.walletEncryptionPublicKeyBs58
|
|
518
|
+
this.walletEncryptionPublicKeyBs58
|
|
436
519
|
);
|
|
437
520
|
|
|
438
521
|
if (!encryptedPayloadData) {
|
|
439
522
|
throw new Error(
|
|
440
|
-
`${this.config.
|
|
523
|
+
`${this.config.type} provider: Failed to encrypt payload for signMessage.`
|
|
441
524
|
);
|
|
442
525
|
}
|
|
443
526
|
|
|
444
|
-
const signMsgDeeplinkQueryPayload:
|
|
527
|
+
const signMsgDeeplinkQueryPayload: DeeplinkSignMessageParams = {
|
|
445
528
|
dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey),
|
|
446
529
|
redirect_link: this.config.appScheme,
|
|
447
530
|
payload: encryptedPayloadData.encryptedPayload,
|
|
@@ -458,25 +541,25 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
458
541
|
!typedParams.transactions.every((t: any) => typeof t === 'string')
|
|
459
542
|
) {
|
|
460
543
|
throw new Error(
|
|
461
|
-
|
|
544
|
+
`${this.config.type} provider: Missing or invalid 'transactions' (array of base58 strings) in params for ${signingMethod}`
|
|
462
545
|
);
|
|
463
546
|
}
|
|
464
547
|
|
|
465
548
|
const dataToEncrypt = {
|
|
466
|
-
session: this.sessionToken
|
|
549
|
+
session: this.sessionToken,
|
|
467
550
|
transactions: typedParams.transactions
|
|
468
551
|
};
|
|
469
552
|
const encryptedData = this.encryptPayload(
|
|
470
553
|
dataToEncrypt,
|
|
471
|
-
this.walletEncryptionPublicKeyBs58
|
|
554
|
+
this.walletEncryptionPublicKeyBs58
|
|
472
555
|
);
|
|
473
556
|
if (!encryptedData) {
|
|
474
557
|
throw new Error(
|
|
475
|
-
`${this.config.
|
|
558
|
+
`${this.config.type} provider: Failed to encrypt payload for ${signingMethod}.`
|
|
476
559
|
);
|
|
477
560
|
}
|
|
478
561
|
|
|
479
|
-
const signAllTxDeeplinkParams:
|
|
562
|
+
const signAllTxDeeplinkParams: DeeplinkSignAllTransactionsParams = {
|
|
480
563
|
dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey),
|
|
481
564
|
redirect_link: this.config.appScheme,
|
|
482
565
|
cluster: this.currentCluster,
|
|
@@ -487,72 +570,35 @@ export class SolanaDeeplinkProvider extends EventEmitter implements Provider {
|
|
|
487
570
|
break;
|
|
488
571
|
}
|
|
489
572
|
default: {
|
|
490
|
-
throw new Error(
|
|
491
|
-
`${this.config.walletType}Provider: Unhandled signing method: ${signingMethod}`
|
|
492
|
-
);
|
|
573
|
+
throw new Error(`${this.config.type} provider: Unhandled signing method: ${signingMethod}`);
|
|
493
574
|
}
|
|
494
575
|
}
|
|
495
576
|
|
|
496
|
-
return
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const responseUrlParams = new URLSearchParams(
|
|
505
|
-
fullUrl.substring(fullUrl.indexOf('?') + 1)
|
|
506
|
-
);
|
|
507
|
-
const errorCode = responseUrlParams.get('errorCode');
|
|
508
|
-
const errorMessage = responseUrlParams.get('errorMessage');
|
|
509
|
-
if (errorCode) {
|
|
510
|
-
return reject(
|
|
511
|
-
new Error(
|
|
512
|
-
`${this.config.walletType} ${signingMethod} Failed: ${
|
|
513
|
-
errorMessage || 'Unknown error'
|
|
514
|
-
} (Code: ${errorCode})`
|
|
515
|
-
)
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
const responseNonce = responseUrlParams.get('nonce');
|
|
519
|
-
const responseData = responseUrlParams.get('data');
|
|
520
|
-
if (!responseNonce || !responseData) {
|
|
521
|
-
return reject(
|
|
522
|
-
new Error(
|
|
523
|
-
`${this.config.walletType} ${signingMethod}: Invalid response - missing nonce or data.`
|
|
524
|
-
)
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
const decryptedResult = this.decryptPayload<any>(
|
|
528
|
-
responseData,
|
|
529
|
-
responseNonce,
|
|
530
|
-
this.walletEncryptionPublicKeyBs58!
|
|
577
|
+
return this.openDeeplinkAndWait<T>(
|
|
578
|
+
deeplinkUrl,
|
|
579
|
+
(responseUrlParams: URLSearchParams) => {
|
|
580
|
+
const responseNonce = responseUrlParams.get('nonce');
|
|
581
|
+
const responseData = responseUrlParams.get('data');
|
|
582
|
+
if (!responseNonce || !responseData) {
|
|
583
|
+
throw new Error(
|
|
584
|
+
`${this.config.type} provider: ${signingMethod}: Invalid response - missing nonce or data.`
|
|
531
585
|
);
|
|
532
|
-
if (!decryptedResult) {
|
|
533
|
-
return reject(
|
|
534
|
-
new Error(
|
|
535
|
-
`${this.config.walletType} ${signingMethod}: Failed to decrypt response or invalid decrypted data.`
|
|
536
|
-
)
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
resolve(decryptedResult as T);
|
|
540
|
-
} else {
|
|
541
|
-
reject(new Error(`${this.config.walletType} ${signingMethod}: Unexpected redirect URI.`));
|
|
542
586
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
subscription.remove();
|
|
548
|
-
}
|
|
549
|
-
reject(
|
|
550
|
-
new Error(
|
|
551
|
-
`Failed to open ${this.config.walletType} for ${signingMethod}: ${err.message}. Is it installed?`
|
|
552
|
-
)
|
|
587
|
+
const decryptedResult = this.decryptPayload<any>(
|
|
588
|
+
responseData,
|
|
589
|
+
responseNonce,
|
|
590
|
+
this.walletEncryptionPublicKeyBs58!
|
|
553
591
|
);
|
|
554
|
-
|
|
555
|
-
|
|
592
|
+
if (!decryptedResult) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
`${this.config.type} provider: ${signingMethod}: Failed to decrypt response or invalid decrypted data.`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return decryptedResult as T;
|
|
599
|
+
},
|
|
600
|
+
signingMethod
|
|
601
|
+
);
|
|
556
602
|
}
|
|
557
603
|
}
|
|
558
604
|
|