@solana-mobile/mobile-wallet-adapter-walletlib 1.4.0 → 1.4.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 (23) hide show
  1. package/LICENSE +12 -12
  2. package/README.md +202 -196
  3. package/android/build.gradle +151 -151
  4. package/android/gradle/wrapper/gradle-wrapper.properties +5 -5
  5. package/android/gradle.properties +5 -5
  6. package/android/gradlew +185 -185
  7. package/android/src/main/java/com/solanamobile/mobilewalletadapterwalletlib/reactnative/Extensions.kt +98 -98
  8. package/android/src/main/java/com/solanamobile/mobilewalletadapterwalletlib/reactnative/JsonSerializationUtils.kt +156 -156
  9. package/android/src/main/java/com/solanamobile/mobilewalletadapterwalletlib/reactnative/MobileWalletAdapterWalletLibReactNativePackage.kt +18 -18
  10. package/android/src/main/java/com/solanamobile/mobilewalletadapterwalletlib/reactnative/SolanaMobileWalletAdapterWalletLibModule.kt +691 -691
  11. package/android/src/main/java/com/solanamobile/mobilewalletadapterwalletlib/reactnative/model/MobileWalletAdapterConfig.kt +59 -59
  12. package/android/src/main/java/com/solanamobile/mobilewalletadapterwalletlib/reactnative/model/MobileWalletAdapterRequest.kt +97 -97
  13. package/android/src/main/java/com/solanamobile/mobilewalletadapterwalletlib/reactnative/model/MobileWalletAdapterResponse.kt +68 -68
  14. package/lib/esm/index.js +119 -194
  15. package/lib/esm/index.js.map +1 -0
  16. package/lib/esm/index.native.js +119 -194
  17. package/lib/esm/index.native.js.map +1 -0
  18. package/lib/esm/package.json +1 -3
  19. package/lib/types/index.d.ts +124 -112
  20. package/lib/types/index.d.ts.map +1 -1
  21. package/package.json +49 -46
  22. package/lib/types/index.native.d.ts +0 -226
  23. package/lib/types/index.native.d.ts.map +0 -1
@@ -1,691 +1,691 @@
1
- package com.solanamobile.mobilewalletadapterwalletlib.reactnative
2
-
3
- import android.net.Uri
4
- import android.util.Log
5
- import com.facebook.react.bridge.*
6
- import com.facebook.react.modules.core.DeviceEventManagerModule
7
- import com.solana.mobilewalletadapter.common.ProtocolContract
8
- import com.solana.mobilewalletadapter.walletlib.association.AssociationUri
9
- import com.solana.mobilewalletadapter.walletlib.association.LocalAssociationUri
10
- import com.solana.mobilewalletadapter.walletlib.authorization.AuthIssuerConfig
11
- import com.solana.mobilewalletadapter.walletlib.scenario.*
12
- import com.solana.mobilewalletadapter.walletlib.scenario.AuthorizedAccount
13
- import com.solanamobile.mobilewalletadapterwalletlib.reactnative.model.*
14
- import java.util.UUID
15
- import kotlinx.coroutines.*
16
- import kotlinx.serialization.json.Json
17
- import kotlinx.serialization.json.JsonObject
18
- import kotlinx.serialization.json.JsonPrimitive
19
- import kotlinx.serialization.json.jsonObject
20
-
21
- enum class ErrorCode(val code: String) {
22
- ERROR_INTENT_DATA_NOT_FOUND("ERROR_INTENT_DATA_NOT_FOUND"),
23
- ERROR_SESSION_ALREADY_CREATED("ERROR_SESSION_ALREADY_CREATED"),
24
- ERROR_UNSUPPORTED_ASSOCIATION_URI("ERROR_UNSUPPORTED_ASSOCIATION_URI"),
25
- ERROR_UNSUPPORTED_ASSOCIATION_TYPE("ERROR_UNSUPPORTED_ASSOCIATION_TYPE")
26
- }
27
-
28
- class SolanaMobileWalletAdapterWalletLibModule(val reactContext: ReactApplicationContext) :
29
- ReactContextBaseJavaModule(reactContext), CoroutineScope {
30
-
31
- private val json = Json { ignoreUnknownKeys = true }
32
-
33
- // Sets the name of the module in React, accessible at
34
- // ReactNative.NativeModules.SolanaMobileWalletAdapterWalletLib
35
- override fun getName() = "SolanaMobileWalletAdapterWalletLib"
36
-
37
- override val coroutineContext =
38
- Dispatchers.IO + CoroutineName("SolanaMobileWalletAdapterWalletLibModuleScope") + SupervisorJob()
39
-
40
- // Session events that notify about the lifecycle of the Scenario session. We are choosing
41
- // to go with the naming convention Session rather than Scenario for readability.
42
- sealed interface MobileWalletAdapterSessionEvent {
43
- val type: String
44
- object None : MobileWalletAdapterSessionEvent {
45
- override val type: String = ""
46
- }
47
- object SessionTerminated : MobileWalletAdapterSessionEvent {
48
- override val type: String = "SESSION_TERMINATED"
49
- }
50
- object ScenarioReady : MobileWalletAdapterSessionEvent {
51
- override val type: String = "SESSION_READY"
52
- }
53
- object ScenarioServingClients : MobileWalletAdapterSessionEvent {
54
- override val type: String = "SESSION_SERVING_CLIENTS"
55
- }
56
- object ScenarioServingComplete : MobileWalletAdapterSessionEvent {
57
- override val type: String = "SESSION_SERVING_COMPLETE"
58
- }
59
- object ScenarioComplete : MobileWalletAdapterSessionEvent {
60
- override val type: String = "SESSION_COMPLETE"
61
- }
62
- class ScenarioError(val message: String? = null) : MobileWalletAdapterSessionEvent {
63
- override val type: String = "SESSION_ERROR"
64
- }
65
- object ScenarioTeardownComplete : MobileWalletAdapterSessionEvent {
66
- override val type: String = "SESSION_TEARDOWN_COMPLETE"
67
- }
68
- object LowPowerNoConnection : MobileWalletAdapterSessionEvent {
69
- override val type: String = "LOW_POWER_NO_CONNECTION"
70
- }
71
- }
72
-
73
- // Service requests that come from the dApp for Authorization, Signing, Sending, hence
74
- // "RemoteRequest".
75
- sealed class MobileWalletAdapterRemoteRequest(
76
- open val request: ScenarioRequest,
77
- val id: String = UUID.randomUUID().toString()
78
- ) {
79
- data class AuthorizeDapp(override val request: AuthorizeRequest) :
80
- MobileWalletAdapterRemoteRequest(request)
81
-
82
- data class ReauthorizeDapp(override val request: ReauthorizeRequest) :
83
- MobileWalletAdapterRemoteRequest(request)
84
-
85
- data class DeauthorizeDapp(override val request: DeauthorizedEvent) :
86
- MobileWalletAdapterRemoteRequest(request)
87
-
88
- sealed class SignPayloads(override val request: SignPayloadsRequest) :
89
- MobileWalletAdapterRemoteRequest(request)
90
-
91
- data class SignTransactions(override val request: SignTransactionsRequest) :
92
- SignPayloads(request)
93
-
94
- data class SignMessages(override val request: SignMessagesRequest) :
95
- SignPayloads(request)
96
-
97
- data class SignAndSendTransactions(
98
- override val request: SignAndSendTransactionsRequest,
99
- val endpointUri: Uri,
100
- ) : MobileWalletAdapterRemoteRequest(request)
101
- }
102
-
103
- // currently we only allow a single scenario to exist at a time
104
- private var scenarioId: String? = null
105
- private var scenarioUri: Uri? = null
106
- private var scenario: Scenario? = null
107
- set(value) {
108
- value?.let { scenarioId = UUID.randomUUID().toString() }
109
- ?: run {
110
- scenarioUri = null
111
- scenarioId = null
112
- scenario?.close()
113
- }
114
- pendingRequests.clear()
115
- field = value
116
- }
117
-
118
- // very basic/naive implememtion of request cache.
119
- // we could replace this with an abstraction that has expiration, persistance, etc.
120
- private val pendingRequests = mutableMapOf<String, MobileWalletAdapterRemoteRequest>()
121
-
122
- private fun clusterToRpcUri(cluster: String?): Uri {
123
- return when (cluster) {
124
- ProtocolContract.CLUSTER_MAINNET_BETA ->
125
- Uri.parse("https://api.mainnet-beta.solana.com")
126
-
127
- ProtocolContract.CLUSTER_DEVNET ->
128
- Uri.parse("https://api.devnet.solana.com")
129
-
130
- else -> Uri.parse("https://api.testnet.solana.com")
131
- }
132
- }
133
- @ReactMethod
134
- fun createScenario(
135
- walletName: String,
136
- config: String,
137
- promise: Promise,
138
- ) = launch {
139
- // Get the intent that started this activity
140
- val intent = reactContext.getCurrentActivity()?.intent
141
- if (intent == null) {
142
- Log.e(TAG, "Unable to get intent in current context")
143
- promise.reject(ErrorCode.ERROR_INTENT_DATA_NOT_FOUND.code, "Unable to get intent in current context")
144
- return@launch
145
- }
146
-
147
- // Get the data from the intent and parse into URI
148
- val data = intent.data
149
- if (data == null) {
150
- Log.e(TAG, "Unable to get intent data in current context")
151
- promise.reject(ErrorCode.ERROR_INTENT_DATA_NOT_FOUND.code, "Unable to get intent data in current context")
152
- return@launch
153
- }
154
- val uri = Uri.parse(data.toString())
155
-
156
- // TODO: this is dirty, need some stateful object/data to know what state we are in.
157
- // also, should we support multiple simultaneous scenarios?
158
- if (uri == scenarioUri && scenario != null) {
159
- Log.w(TAG, "Session already created for uri: $uri")
160
- promise.reject(ErrorCode.ERROR_SESSION_ALREADY_CREATED.code, "Session already created for uri: $uri")
161
- return@launch
162
- }
163
-
164
- val associationUri = AssociationUri.parse(uri)
165
- if (associationUri == null) {
166
- Log.e(TAG, "Unsupported association URI: $uri")
167
- promise.reject(ErrorCode.ERROR_UNSUPPORTED_ASSOCIATION_URI.code, "Unsupported association URI: $uri")
168
- return@launch
169
- } else if (associationUri !is LocalAssociationUri) {
170
- Log.e(TAG, "Current implementation of fakewallet does not support remote clients")
171
- promise.reject(ErrorCode.ERROR_UNSUPPORTED_ASSOCIATION_TYPE.code, "Current implementation of fakewallet does not support remote clients")
172
- return@launch
173
- }
174
-
175
- scenarioUri = uri
176
-
177
- val kotlinConfig =
178
- json.decodeFromString(MobileWalletAdapterConfigSerializer, config)
179
-
180
- // created a scenario, told it to start (kicks off some threads in the background)
181
- // we've kept a reference to it in the global state of this module (scenario)
182
- // this won't be garbage collected and will just run, sit & wait for an incoming
183
- // connection
184
- scenario =
185
- associationUri.createScenario(
186
- reactContext,
187
- kotlinConfig,
188
- AuthIssuerConfig(walletName),
189
- MobileWalletAdapterScenarioCallbacks()
190
- ).also {
191
- it.start()
192
- }
193
-
194
- promise.resolve(scenarioId)
195
- Log.d(TAG, "scenario created: $walletName")
196
- }
197
-
198
- /* Generic Request functions */
199
- @ReactMethod
200
- fun cancelRequest(sessionId: String, requestId: String) {
201
- Log.d(TAG, "Cancelled request $requestId")
202
- (pendingRequests.remove(requestId) as? ScenarioRequest)?.let { scenarioRequest ->
203
- scenarioRequest.cancel()
204
- }
205
- }
206
-
207
- @ReactMethod
208
- fun resolve(requestJson: String, responseJson: String) = launch {
209
- val completedRequest =
210
- json.decodeFromString(
211
- MobileWalletAdapterRequestSerializer,
212
- requestJson
213
- )
214
- val response =
215
- json.decodeFromString(
216
- MobileWalletAdapterResponseSerializer,
217
- responseJson
218
- )
219
- val pendingRequest = pendingRequests[completedRequest.requestId]
220
-
221
- if (completedRequest.sessionId != scenarioId) {
222
- sendSessionEventToReact(
223
- MobileWalletAdapterSessionEvent.ScenarioError(
224
- "Invalid session (${completedRequest.sessionId}). This session does not exist/is no longer active."
225
- )
226
- )
227
- return@launch
228
- }
229
-
230
- fun completeWithInvalidResponse() {
231
- pendingRequest?.request?.completeWithInternalError(
232
- Exception("Invalid Response For Request: response = $responseJson")
233
- )
234
- }
235
-
236
- when (completedRequest) {
237
- is AuthorizeDapp ->
238
- when (response) {
239
- is MobileWalletAdapterFailureResponse -> {
240
- when (response) {
241
- is UserDeclinedResponse ->
242
- (pendingRequest as?
243
- MobileWalletAdapterRemoteRequest.AuthorizeDapp)
244
- ?.request
245
- ?.completeWithDecline()
246
-
247
- else ->
248
- completeWithInvalidResponse()
249
- }
250
- }
251
-
252
- is AuthorizeDappResponse ->
253
- (pendingRequest as?
254
- MobileWalletAdapterRemoteRequest.AuthorizeDapp)
255
- ?.request
256
- ?.completeWithAuthorize(
257
- response.accounts
258
- .first()
259
- .let { account
260
- ->
261
- AuthorizedAccount(
262
- account.publicKey,
263
- account.accountLabel,
264
- account.icon
265
- ?.let {
266
- Uri.parse(
267
- it
268
- )
269
- },
270
- account.chains
271
- ?.toTypedArray(),
272
- account.features
273
- ?.toTypedArray()
274
- )
275
- },
276
- response.walletUriBase
277
- ?.let {
278
- Uri.parse(
279
- response.walletUriBase
280
- )
281
- },
282
- response.authorizationScope,
283
- response.signInResult
284
- )
285
-
286
- else -> completeWithInvalidResponse()
287
- }
288
-
289
- is ReauthorizeDapp ->
290
- when (response) {
291
- is MobileWalletAdapterFailureResponse -> {
292
- when (response) {
293
- is AuthorizationNotValidResponse ->
294
- (pendingRequest as?
295
- MobileWalletAdapterRemoteRequest.ReauthorizeDapp)
296
- ?.request
297
- ?.completeWithDecline()
298
-
299
- else ->
300
- completeWithInvalidResponse()
301
- }
302
- }
303
-
304
- is ReauthorizeDappResponse ->
305
- (pendingRequest as?
306
- MobileWalletAdapterRemoteRequest.ReauthorizeDapp)
307
- ?.request
308
- ?.completeWithReauthorize()
309
-
310
- else -> completeWithInvalidResponse()
311
- }
312
-
313
- is DeauthorizeDapp ->
314
- when (response) {
315
- is DeauthorizeDappResponse ->
316
- (pendingRequest as?
317
- MobileWalletAdapterRemoteRequest.DeauthorizeDapp)
318
- ?.request
319
- ?.complete()
320
-
321
- else -> completeWithInvalidResponse()
322
- }
323
-
324
- is SignAndSendTransactions ->
325
- when (response) {
326
- is MobileWalletAdapterFailureResponse -> {
327
- when (response) {
328
- is UserDeclinedResponse ->
329
- (pendingRequest as?
330
- MobileWalletAdapterRemoteRequest.SignAndSendTransactions)
331
- ?.request
332
- ?.completeWithDecline()
333
-
334
- is TooManyPayloadsResponse ->
335
- (pendingRequest as?
336
- MobileWalletAdapterRemoteRequest.SignAndSendTransactions)
337
- ?.request
338
- ?.completeWithTooManyPayloads()
339
-
340
- is AuthorizationNotValidResponse ->
341
- (pendingRequest as?
342
- MobileWalletAdapterRemoteRequest.SignAndSendTransactions)
343
- ?.request
344
- ?.completeWithAuthorizationNotValid()
345
-
346
- is InvalidSignaturesResponse ->
347
- (pendingRequest as?
348
- MobileWalletAdapterRemoteRequest.SignAndSendTransactions)
349
- ?.request
350
- ?.completeWithInvalidSignatures(
351
- response.valid
352
- )
353
- }
354
- }
355
-
356
- is SignedAndSentTransactions ->
357
- (pendingRequest as?
358
- MobileWalletAdapterRemoteRequest.SignAndSendTransactions)
359
- ?.request
360
- ?.completeWithSignatures(
361
- response.signedTransactions
362
- .toTypedArray()
363
- )
364
-
365
- else -> completeWithInvalidResponse()
366
- }
367
-
368
- is SignPayloads ->
369
- when (response) {
370
- is MobileWalletAdapterFailureResponse -> {
371
- when (response) {
372
- is UserDeclinedResponse ->
373
- (pendingRequest as?
374
- MobileWalletAdapterRemoteRequest.SignPayloads)
375
- ?.request
376
- ?.completeWithDecline()
377
-
378
- is TooManyPayloadsResponse ->
379
- (pendingRequest as?
380
- MobileWalletAdapterRemoteRequest.SignPayloads)
381
- ?.request
382
- ?.completeWithTooManyPayloads()
383
-
384
- is AuthorizationNotValidResponse ->
385
- (pendingRequest as?
386
- MobileWalletAdapterRemoteRequest.SignPayloads)
387
- ?.request
388
- ?.completeWithAuthorizationNotValid()
389
-
390
- is InvalidSignaturesResponse ->
391
- (pendingRequest as?
392
- MobileWalletAdapterRemoteRequest.SignPayloads)
393
- ?.request
394
- ?.completeWithInvalidPayloads(
395
- response.valid
396
- )
397
- }
398
- }
399
-
400
- is SignedPayloads ->
401
- (pendingRequest as?
402
- MobileWalletAdapterRemoteRequest.SignPayloads)
403
- ?.request
404
- ?.completeWithSignedPayloads(
405
- response.signedPayloads
406
- .toTypedArray()
407
- )
408
-
409
- else -> completeWithInvalidResponse()
410
- }
411
- }
412
- }
413
-
414
- private fun checkSessionId(sessionId: String, doIfValid: (() -> Unit)) =
415
- if (sessionId == scenarioId) doIfValid()
416
- else
417
- sendSessionEventToReact(
418
- MobileWalletAdapterSessionEvent
419
- .ScenarioError(
420
- "Invalid session ($sessionId). This session does not exist/is no longer active."
421
- )
422
- )
423
-
424
- private fun sendSessionEventToReact(sessionEvent: MobileWalletAdapterSessionEvent) {
425
- val eventInfo =
426
- when (sessionEvent) {
427
- is MobileWalletAdapterSessionEvent.None -> null
428
- is MobileWalletAdapterSessionEvent.ScenarioError ->
429
- Arguments.createMap().apply {
430
- putString(
431
- "__type",
432
- sessionEvent.type
433
- )
434
- putString(
435
- "error",
436
- sessionEvent.message
437
- )
438
- }
439
-
440
- else ->
441
- Arguments.createMap().apply {
442
- putString(
443
- "__type",
444
- sessionEvent.type
445
- )
446
- }
447
- }
448
-
449
- eventInfo?.putString("sessionId", scenarioId)
450
-
451
- eventInfo?.let {
452
- sendEvent(
453
- reactContext,
454
- Companion.MOBILE_WALLET_ADAPTER_SERVICE_REQUEST_BRIDGE_NAME,
455
- it
456
- )
457
- }
458
- }
459
-
460
- private fun sendWalletServiceRequestToReact(request: MobileWalletAdapterRemoteRequest) {
461
- val surrogate =
462
- when (request) {
463
- is MobileWalletAdapterRemoteRequest.AuthorizeDapp ->
464
- AuthorizeDapp(
465
- scenarioId!!,
466
- request.request.chain,
467
- request.request
468
- .identityName,
469
- request.request.identityUri
470
- .toString(),
471
- request.request
472
- .iconRelativeUri
473
- .toString(),
474
- request.request.features
475
- ?.asList(),
476
- request.request.addresses
477
- ?.asList(),
478
- request.request
479
- .signInPayload
480
- )
481
-
482
- is MobileWalletAdapterRemoteRequest.ReauthorizeDapp ->
483
- ReauthorizeDapp(
484
- scenarioId!!,
485
- request.request.chain,
486
- request.request
487
- .identityName,
488
- request.request.identityUri
489
- .toString(),
490
- request.request
491
- .iconRelativeUri
492
- .toString(),
493
- request.request
494
- .authorizationScope
495
- )
496
-
497
- is MobileWalletAdapterRemoteRequest.DeauthorizeDapp ->
498
- DeauthorizeDapp(
499
- scenarioId!!,
500
- request.request.chain,
501
- request.request
502
- .identityName,
503
- request.request.identityUri
504
- .toString(),
505
- request.request
506
- .iconRelativeUri
507
- .toString(),
508
- request.request
509
- .authorizationScope
510
- )
511
-
512
- is MobileWalletAdapterRemoteRequest.SignMessages ->
513
- SignMessages(
514
- scenarioId!!,
515
- request.request.chain,
516
- request.request
517
- .identityName,
518
- request.request.identityUri
519
- .toString(),
520
- request.request
521
- .iconRelativeUri
522
- .toString(),
523
- request.request
524
- .authorizationScope,
525
- request.request.payloads
526
- .toList()
527
- )
528
-
529
- is MobileWalletAdapterRemoteRequest.SignTransactions ->
530
- SignTransactions(
531
- scenarioId!!,
532
- request.request.chain,
533
- request.request
534
- .identityName,
535
- request.request.identityUri
536
- .toString(),
537
- request.request
538
- .iconRelativeUri
539
- .toString(),
540
- request.request
541
- .authorizationScope,
542
- request.request.payloads
543
- .toList()
544
- )
545
-
546
- is MobileWalletAdapterRemoteRequest.SignAndSendTransactions ->
547
- SignAndSendTransactions(
548
- scenarioId!!,
549
- request.request.chain,
550
- request.request
551
- .identityName,
552
- request.request.identityUri
553
- .toString(),
554
- request.request
555
- .iconRelativeUri
556
- .toString(),
557
- request.request
558
- .authorizationScope,
559
- request.request.payloads
560
- .toList()
561
- )
562
- }
563
-
564
- // this is dirty, the requestId needs to line up so have to manually overwrite here
565
- // should we change javascript side to accept json?
566
- val eventInfo =
567
- JsonObject(
568
- json.encodeToJsonElement(
569
- MobileWalletAdapterRequestSerializer,
570
- surrogate
571
- )
572
- .jsonObject
573
- .toMutableMap()
574
- .apply {
575
- put(
576
- "requestId",
577
- JsonPrimitive(
578
- request.id
579
- )
580
- )
581
- }
582
- )
583
- .toReadableMap()
584
-
585
- sendEvent(
586
- reactContext,
587
- Companion.MOBILE_WALLET_ADAPTER_SERVICE_REQUEST_BRIDGE_NAME,
588
- eventInfo
589
- )
590
- }
591
-
592
- private fun sendEvent(
593
- reactContext: ReactContext,
594
- eventName: String,
595
- params: ReadableMap? = null
596
- ) {
597
- reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
598
- .emit(eventName, params)
599
- }
600
-
601
- private inner class MobileWalletAdapterScenarioCallbacks : LocalScenario.Callbacks {
602
- /* Session Events */
603
- override fun onScenarioReady() {
604
- sendSessionEventToReact(MobileWalletAdapterSessionEvent.ScenarioReady)
605
- }
606
-
607
- override fun onScenarioServingClients() {
608
- sendSessionEventToReact(
609
- MobileWalletAdapterSessionEvent.ScenarioServingClients
610
- )
611
- }
612
-
613
- override fun onScenarioServingComplete() {
614
- launch(Dispatchers.Main) {
615
- scenario = null
616
- sendSessionEventToReact(MobileWalletAdapterSessionEvent.ScenarioServingComplete)
617
- }
618
- }
619
-
620
- override fun onScenarioComplete() {
621
- sendSessionEventToReact(MobileWalletAdapterSessionEvent.ScenarioComplete)
622
- }
623
-
624
- override fun onScenarioError() {
625
- sendSessionEventToReact(MobileWalletAdapterSessionEvent.ScenarioError())
626
- }
627
-
628
- override fun onScenarioTeardownComplete() {
629
- sendSessionEventToReact(MobileWalletAdapterSessionEvent.ScenarioTeardownComplete)
630
- sendSessionEventToReact(MobileWalletAdapterSessionEvent.SessionTerminated)
631
- }
632
-
633
- override fun onLowPowerAndNoConnection() {
634
- sendSessionEventToReact(
635
- MobileWalletAdapterSessionEvent.LowPowerNoConnection
636
- )
637
- }
638
-
639
- /* Remote Requests */
640
- override fun onAuthorizeRequest(request: AuthorizeRequest) {
641
- val request = MobileWalletAdapterRemoteRequest.AuthorizeDapp(request)
642
- pendingRequests.put(request.id, request)
643
- sendWalletServiceRequestToReact(request)
644
- }
645
-
646
- override fun onReauthorizeRequest(request: ReauthorizeRequest) {
647
- val request = MobileWalletAdapterRemoteRequest.ReauthorizeDapp(request)
648
- pendingRequests.put(request.id, request)
649
- sendWalletServiceRequestToReact(request)
650
- }
651
-
652
- override fun onSignTransactionsRequest(request: SignTransactionsRequest) {
653
- val request = MobileWalletAdapterRemoteRequest.SignTransactions(request)
654
- pendingRequests.put(request.id, request)
655
- sendWalletServiceRequestToReact(request)
656
- }
657
-
658
- override fun onSignMessagesRequest(request: SignMessagesRequest) {
659
- val request = MobileWalletAdapterRemoteRequest.SignMessages(request)
660
- pendingRequests.put(request.id, request)
661
- sendWalletServiceRequestToReact(request)
662
- }
663
-
664
- override fun onSignAndSendTransactionsRequest(
665
- request: SignAndSendTransactionsRequest
666
- ) {
667
- val endpointUri = clusterToRpcUri(request.cluster)
668
- val request =
669
- MobileWalletAdapterRemoteRequest.SignAndSendTransactions(
670
- request,
671
- endpointUri
672
- )
673
- pendingRequests.put(request.id, request)
674
- sendWalletServiceRequestToReact(request)
675
- }
676
-
677
- override fun onDeauthorizedEvent(event: DeauthorizedEvent) {
678
- val request = MobileWalletAdapterRemoteRequest.DeauthorizeDapp(event)
679
- pendingRequests.put(request.id, request)
680
- sendWalletServiceRequestToReact(request)
681
- }
682
- }
683
-
684
- companion object {
685
- private val TAG = SolanaMobileWalletAdapterWalletLibModule::class.simpleName
686
- const val MOBILE_WALLET_ADAPTER_SERVICE_REQUEST_BRIDGE_NAME =
687
- "MobileWalletAdapterServiceRequestBridge"
688
- const val MOBILE_WALLET_ADAPTER_SESSION_EVENT_BRIDGE_NAME =
689
- "MobileWalletAdapterSessionEventBridge"
690
- }
691
- }
1
+ package com.solanamobile.mobilewalletadapterwalletlib.reactnative
2
+
3
+ import android.net.Uri
4
+ import android.util.Log
5
+ import com.facebook.react.bridge.*
6
+ import com.facebook.react.modules.core.DeviceEventManagerModule
7
+ import com.solana.mobilewalletadapter.common.ProtocolContract
8
+ import com.solana.mobilewalletadapter.walletlib.association.AssociationUri
9
+ import com.solana.mobilewalletadapter.walletlib.association.LocalAssociationUri
10
+ import com.solana.mobilewalletadapter.walletlib.authorization.AuthIssuerConfig
11
+ import com.solana.mobilewalletadapter.walletlib.scenario.*
12
+ import com.solana.mobilewalletadapter.walletlib.scenario.AuthorizedAccount
13
+ import com.solanamobile.mobilewalletadapterwalletlib.reactnative.model.*
14
+ import java.util.UUID
15
+ import kotlinx.coroutines.*
16
+ import kotlinx.serialization.json.Json
17
+ import kotlinx.serialization.json.JsonObject
18
+ import kotlinx.serialization.json.JsonPrimitive
19
+ import kotlinx.serialization.json.jsonObject
20
+
21
+ enum class ErrorCode(val code: String) {
22
+ ERROR_INTENT_DATA_NOT_FOUND("ERROR_INTENT_DATA_NOT_FOUND"),
23
+ ERROR_SESSION_ALREADY_CREATED("ERROR_SESSION_ALREADY_CREATED"),
24
+ ERROR_UNSUPPORTED_ASSOCIATION_URI("ERROR_UNSUPPORTED_ASSOCIATION_URI"),
25
+ ERROR_UNSUPPORTED_ASSOCIATION_TYPE("ERROR_UNSUPPORTED_ASSOCIATION_TYPE")
26
+ }
27
+
28
+ class SolanaMobileWalletAdapterWalletLibModule(val reactContext: ReactApplicationContext) :
29
+ ReactContextBaseJavaModule(reactContext), CoroutineScope {
30
+
31
+ private val json = Json { ignoreUnknownKeys = true }
32
+
33
+ // Sets the name of the module in React, accessible at
34
+ // ReactNative.NativeModules.SolanaMobileWalletAdapterWalletLib
35
+ override fun getName() = "SolanaMobileWalletAdapterWalletLib"
36
+
37
+ override val coroutineContext =
38
+ Dispatchers.IO + CoroutineName("SolanaMobileWalletAdapterWalletLibModuleScope") + SupervisorJob()
39
+
40
+ // Session events that notify about the lifecycle of the Scenario session. We are choosing
41
+ // to go with the naming convention Session rather than Scenario for readability.
42
+ sealed interface MobileWalletAdapterSessionEvent {
43
+ val type: String
44
+ object None : MobileWalletAdapterSessionEvent {
45
+ override val type: String = ""
46
+ }
47
+ object SessionTerminated : MobileWalletAdapterSessionEvent {
48
+ override val type: String = "SESSION_TERMINATED"
49
+ }
50
+ object ScenarioReady : MobileWalletAdapterSessionEvent {
51
+ override val type: String = "SESSION_READY"
52
+ }
53
+ object ScenarioServingClients : MobileWalletAdapterSessionEvent {
54
+ override val type: String = "SESSION_SERVING_CLIENTS"
55
+ }
56
+ object ScenarioServingComplete : MobileWalletAdapterSessionEvent {
57
+ override val type: String = "SESSION_SERVING_COMPLETE"
58
+ }
59
+ object ScenarioComplete : MobileWalletAdapterSessionEvent {
60
+ override val type: String = "SESSION_COMPLETE"
61
+ }
62
+ class ScenarioError(val message: String? = null) : MobileWalletAdapterSessionEvent {
63
+ override val type: String = "SESSION_ERROR"
64
+ }
65
+ object ScenarioTeardownComplete : MobileWalletAdapterSessionEvent {
66
+ override val type: String = "SESSION_TEARDOWN_COMPLETE"
67
+ }
68
+ object LowPowerNoConnection : MobileWalletAdapterSessionEvent {
69
+ override val type: String = "LOW_POWER_NO_CONNECTION"
70
+ }
71
+ }
72
+
73
+ // Service requests that come from the dApp for Authorization, Signing, Sending, hence
74
+ // "RemoteRequest".
75
+ sealed class MobileWalletAdapterRemoteRequest(
76
+ open val request: ScenarioRequest,
77
+ val id: String = UUID.randomUUID().toString()
78
+ ) {
79
+ data class AuthorizeDapp(override val request: AuthorizeRequest) :
80
+ MobileWalletAdapterRemoteRequest(request)
81
+
82
+ data class ReauthorizeDapp(override val request: ReauthorizeRequest) :
83
+ MobileWalletAdapterRemoteRequest(request)
84
+
85
+ data class DeauthorizeDapp(override val request: DeauthorizedEvent) :
86
+ MobileWalletAdapterRemoteRequest(request)
87
+
88
+ sealed class SignPayloads(override val request: SignPayloadsRequest) :
89
+ MobileWalletAdapterRemoteRequest(request)
90
+
91
+ data class SignTransactions(override val request: SignTransactionsRequest) :
92
+ SignPayloads(request)
93
+
94
+ data class SignMessages(override val request: SignMessagesRequest) :
95
+ SignPayloads(request)
96
+
97
+ data class SignAndSendTransactions(
98
+ override val request: SignAndSendTransactionsRequest,
99
+ val endpointUri: Uri,
100
+ ) : MobileWalletAdapterRemoteRequest(request)
101
+ }
102
+
103
+ // currently we only allow a single scenario to exist at a time
104
+ private var scenarioId: String? = null
105
+ private var scenarioUri: Uri? = null
106
+ private var scenario: Scenario? = null
107
+ set(value) {
108
+ value?.let { scenarioId = UUID.randomUUID().toString() }
109
+ ?: run {
110
+ scenarioUri = null
111
+ scenarioId = null
112
+ scenario?.close()
113
+ }
114
+ pendingRequests.clear()
115
+ field = value
116
+ }
117
+
118
+ // very basic/naive implememtion of request cache.
119
+ // we could replace this with an abstraction that has expiration, persistance, etc.
120
+ private val pendingRequests = mutableMapOf<String, MobileWalletAdapterRemoteRequest>()
121
+
122
+ private fun clusterToRpcUri(cluster: String?): Uri {
123
+ return when (cluster) {
124
+ ProtocolContract.CLUSTER_MAINNET_BETA ->
125
+ Uri.parse("https://api.mainnet-beta.solana.com")
126
+
127
+ ProtocolContract.CLUSTER_DEVNET ->
128
+ Uri.parse("https://api.devnet.solana.com")
129
+
130
+ else -> Uri.parse("https://api.testnet.solana.com")
131
+ }
132
+ }
133
+ @ReactMethod
134
+ fun createScenario(
135
+ walletName: String,
136
+ config: String,
137
+ promise: Promise,
138
+ ) = launch {
139
+ // Get the intent that started this activity
140
+ val intent = reactContext.getCurrentActivity()?.intent
141
+ if (intent == null) {
142
+ Log.e(TAG, "Unable to get intent in current context")
143
+ promise.reject(ErrorCode.ERROR_INTENT_DATA_NOT_FOUND.code, "Unable to get intent in current context")
144
+ return@launch
145
+ }
146
+
147
+ // Get the data from the intent and parse into URI
148
+ val data = intent.data
149
+ if (data == null) {
150
+ Log.e(TAG, "Unable to get intent data in current context")
151
+ promise.reject(ErrorCode.ERROR_INTENT_DATA_NOT_FOUND.code, "Unable to get intent data in current context")
152
+ return@launch
153
+ }
154
+ val uri = Uri.parse(data.toString())
155
+
156
+ // TODO: this is dirty, need some stateful object/data to know what state we are in.
157
+ // also, should we support multiple simultaneous scenarios?
158
+ if (uri == scenarioUri && scenario != null) {
159
+ Log.w(TAG, "Session already created for uri: $uri")
160
+ promise.reject(ErrorCode.ERROR_SESSION_ALREADY_CREATED.code, "Session already created for uri: $uri")
161
+ return@launch
162
+ }
163
+
164
+ val associationUri = AssociationUri.parse(uri)
165
+ if (associationUri == null) {
166
+ Log.e(TAG, "Unsupported association URI: $uri")
167
+ promise.reject(ErrorCode.ERROR_UNSUPPORTED_ASSOCIATION_URI.code, "Unsupported association URI: $uri")
168
+ return@launch
169
+ } else if (associationUri !is LocalAssociationUri) {
170
+ Log.e(TAG, "Current implementation of fakewallet does not support remote clients")
171
+ promise.reject(ErrorCode.ERROR_UNSUPPORTED_ASSOCIATION_TYPE.code, "Current implementation of fakewallet does not support remote clients")
172
+ return@launch
173
+ }
174
+
175
+ scenarioUri = uri
176
+
177
+ val kotlinConfig =
178
+ json.decodeFromString(MobileWalletAdapterConfigSerializer, config)
179
+
180
+ // created a scenario, told it to start (kicks off some threads in the background)
181
+ // we've kept a reference to it in the global state of this module (scenario)
182
+ // this won't be garbage collected and will just run, sit & wait for an incoming
183
+ // connection
184
+ scenario =
185
+ associationUri.createScenario(
186
+ reactContext,
187
+ kotlinConfig,
188
+ AuthIssuerConfig(walletName),
189
+ MobileWalletAdapterScenarioCallbacks()
190
+ ).also {
191
+ it.start()
192
+ }
193
+
194
+ promise.resolve(scenarioId)
195
+ Log.d(TAG, "scenario created: $walletName")
196
+ }
197
+
198
+ /* Generic Request functions */
199
+ @ReactMethod
200
+ fun cancelRequest(sessionId: String, requestId: String) {
201
+ Log.d(TAG, "Cancelled request $requestId")
202
+ (pendingRequests.remove(requestId) as? ScenarioRequest)?.let { scenarioRequest ->
203
+ scenarioRequest.cancel()
204
+ }
205
+ }
206
+
207
+ @ReactMethod
208
+ fun resolve(requestJson: String, responseJson: String) = launch {
209
+ val completedRequest =
210
+ json.decodeFromString(
211
+ MobileWalletAdapterRequestSerializer,
212
+ requestJson
213
+ )
214
+ val response =
215
+ json.decodeFromString(
216
+ MobileWalletAdapterResponseSerializer,
217
+ responseJson
218
+ )
219
+ val pendingRequest = pendingRequests[completedRequest.requestId]
220
+
221
+ if (completedRequest.sessionId != scenarioId) {
222
+ sendSessionEventToReact(
223
+ MobileWalletAdapterSessionEvent.ScenarioError(
224
+ "Invalid session (${completedRequest.sessionId}). This session does not exist/is no longer active."
225
+ )
226
+ )
227
+ return@launch
228
+ }
229
+
230
+ fun completeWithInvalidResponse() {
231
+ pendingRequest?.request?.completeWithInternalError(
232
+ Exception("Invalid Response For Request: response = $responseJson")
233
+ )
234
+ }
235
+
236
+ when (completedRequest) {
237
+ is AuthorizeDapp ->
238
+ when (response) {
239
+ is MobileWalletAdapterFailureResponse -> {
240
+ when (response) {
241
+ is UserDeclinedResponse ->
242
+ (pendingRequest as?
243
+ MobileWalletAdapterRemoteRequest.AuthorizeDapp)
244
+ ?.request
245
+ ?.completeWithDecline()
246
+
247
+ else ->
248
+ completeWithInvalidResponse()
249
+ }
250
+ }
251
+
252
+ is AuthorizeDappResponse ->
253
+ (pendingRequest as?
254
+ MobileWalletAdapterRemoteRequest.AuthorizeDapp)
255
+ ?.request
256
+ ?.completeWithAuthorize(
257
+ response.accounts
258
+ .first()
259
+ .let { account
260
+ ->
261
+ AuthorizedAccount(
262
+ account.publicKey,
263
+ account.accountLabel,
264
+ account.icon
265
+ ?.let {
266
+ Uri.parse(
267
+ it
268
+ )
269
+ },
270
+ account.chains
271
+ ?.toTypedArray(),
272
+ account.features
273
+ ?.toTypedArray()
274
+ )
275
+ },
276
+ response.walletUriBase
277
+ ?.let {
278
+ Uri.parse(
279
+ response.walletUriBase
280
+ )
281
+ },
282
+ response.authorizationScope,
283
+ response.signInResult
284
+ )
285
+
286
+ else -> completeWithInvalidResponse()
287
+ }
288
+
289
+ is ReauthorizeDapp ->
290
+ when (response) {
291
+ is MobileWalletAdapterFailureResponse -> {
292
+ when (response) {
293
+ is AuthorizationNotValidResponse ->
294
+ (pendingRequest as?
295
+ MobileWalletAdapterRemoteRequest.ReauthorizeDapp)
296
+ ?.request
297
+ ?.completeWithDecline()
298
+
299
+ else ->
300
+ completeWithInvalidResponse()
301
+ }
302
+ }
303
+
304
+ is ReauthorizeDappResponse ->
305
+ (pendingRequest as?
306
+ MobileWalletAdapterRemoteRequest.ReauthorizeDapp)
307
+ ?.request
308
+ ?.completeWithReauthorize()
309
+
310
+ else -> completeWithInvalidResponse()
311
+ }
312
+
313
+ is DeauthorizeDapp ->
314
+ when (response) {
315
+ is DeauthorizeDappResponse ->
316
+ (pendingRequest as?
317
+ MobileWalletAdapterRemoteRequest.DeauthorizeDapp)
318
+ ?.request
319
+ ?.complete()
320
+
321
+ else -> completeWithInvalidResponse()
322
+ }
323
+
324
+ is SignAndSendTransactions ->
325
+ when (response) {
326
+ is MobileWalletAdapterFailureResponse -> {
327
+ when (response) {
328
+ is UserDeclinedResponse ->
329
+ (pendingRequest as?
330
+ MobileWalletAdapterRemoteRequest.SignAndSendTransactions)
331
+ ?.request
332
+ ?.completeWithDecline()
333
+
334
+ is TooManyPayloadsResponse ->
335
+ (pendingRequest as?
336
+ MobileWalletAdapterRemoteRequest.SignAndSendTransactions)
337
+ ?.request
338
+ ?.completeWithTooManyPayloads()
339
+
340
+ is AuthorizationNotValidResponse ->
341
+ (pendingRequest as?
342
+ MobileWalletAdapterRemoteRequest.SignAndSendTransactions)
343
+ ?.request
344
+ ?.completeWithAuthorizationNotValid()
345
+
346
+ is InvalidSignaturesResponse ->
347
+ (pendingRequest as?
348
+ MobileWalletAdapterRemoteRequest.SignAndSendTransactions)
349
+ ?.request
350
+ ?.completeWithInvalidSignatures(
351
+ response.valid
352
+ )
353
+ }
354
+ }
355
+
356
+ is SignedAndSentTransactions ->
357
+ (pendingRequest as?
358
+ MobileWalletAdapterRemoteRequest.SignAndSendTransactions)
359
+ ?.request
360
+ ?.completeWithSignatures(
361
+ response.signedTransactions
362
+ .toTypedArray()
363
+ )
364
+
365
+ else -> completeWithInvalidResponse()
366
+ }
367
+
368
+ is SignPayloads ->
369
+ when (response) {
370
+ is MobileWalletAdapterFailureResponse -> {
371
+ when (response) {
372
+ is UserDeclinedResponse ->
373
+ (pendingRequest as?
374
+ MobileWalletAdapterRemoteRequest.SignPayloads)
375
+ ?.request
376
+ ?.completeWithDecline()
377
+
378
+ is TooManyPayloadsResponse ->
379
+ (pendingRequest as?
380
+ MobileWalletAdapterRemoteRequest.SignPayloads)
381
+ ?.request
382
+ ?.completeWithTooManyPayloads()
383
+
384
+ is AuthorizationNotValidResponse ->
385
+ (pendingRequest as?
386
+ MobileWalletAdapterRemoteRequest.SignPayloads)
387
+ ?.request
388
+ ?.completeWithAuthorizationNotValid()
389
+
390
+ is InvalidSignaturesResponse ->
391
+ (pendingRequest as?
392
+ MobileWalletAdapterRemoteRequest.SignPayloads)
393
+ ?.request
394
+ ?.completeWithInvalidPayloads(
395
+ response.valid
396
+ )
397
+ }
398
+ }
399
+
400
+ is SignedPayloads ->
401
+ (pendingRequest as?
402
+ MobileWalletAdapterRemoteRequest.SignPayloads)
403
+ ?.request
404
+ ?.completeWithSignedPayloads(
405
+ response.signedPayloads
406
+ .toTypedArray()
407
+ )
408
+
409
+ else -> completeWithInvalidResponse()
410
+ }
411
+ }
412
+ }
413
+
414
+ private fun checkSessionId(sessionId: String, doIfValid: (() -> Unit)) =
415
+ if (sessionId == scenarioId) doIfValid()
416
+ else
417
+ sendSessionEventToReact(
418
+ MobileWalletAdapterSessionEvent
419
+ .ScenarioError(
420
+ "Invalid session ($sessionId). This session does not exist/is no longer active."
421
+ )
422
+ )
423
+
424
+ private fun sendSessionEventToReact(sessionEvent: MobileWalletAdapterSessionEvent) {
425
+ val eventInfo =
426
+ when (sessionEvent) {
427
+ is MobileWalletAdapterSessionEvent.None -> null
428
+ is MobileWalletAdapterSessionEvent.ScenarioError ->
429
+ Arguments.createMap().apply {
430
+ putString(
431
+ "__type",
432
+ sessionEvent.type
433
+ )
434
+ putString(
435
+ "error",
436
+ sessionEvent.message
437
+ )
438
+ }
439
+
440
+ else ->
441
+ Arguments.createMap().apply {
442
+ putString(
443
+ "__type",
444
+ sessionEvent.type
445
+ )
446
+ }
447
+ }
448
+
449
+ eventInfo?.putString("sessionId", scenarioId)
450
+
451
+ eventInfo?.let {
452
+ sendEvent(
453
+ reactContext,
454
+ Companion.MOBILE_WALLET_ADAPTER_SERVICE_REQUEST_BRIDGE_NAME,
455
+ it
456
+ )
457
+ }
458
+ }
459
+
460
+ private fun sendWalletServiceRequestToReact(request: MobileWalletAdapterRemoteRequest) {
461
+ val surrogate =
462
+ when (request) {
463
+ is MobileWalletAdapterRemoteRequest.AuthorizeDapp ->
464
+ AuthorizeDapp(
465
+ scenarioId!!,
466
+ request.request.chain,
467
+ request.request
468
+ .identityName,
469
+ request.request.identityUri
470
+ .toString(),
471
+ request.request
472
+ .iconRelativeUri
473
+ .toString(),
474
+ request.request.features
475
+ ?.asList(),
476
+ request.request.addresses
477
+ ?.asList(),
478
+ request.request
479
+ .signInPayload
480
+ )
481
+
482
+ is MobileWalletAdapterRemoteRequest.ReauthorizeDapp ->
483
+ ReauthorizeDapp(
484
+ scenarioId!!,
485
+ request.request.chain,
486
+ request.request
487
+ .identityName,
488
+ request.request.identityUri
489
+ .toString(),
490
+ request.request
491
+ .iconRelativeUri
492
+ .toString(),
493
+ request.request
494
+ .authorizationScope
495
+ )
496
+
497
+ is MobileWalletAdapterRemoteRequest.DeauthorizeDapp ->
498
+ DeauthorizeDapp(
499
+ scenarioId!!,
500
+ request.request.chain,
501
+ request.request
502
+ .identityName,
503
+ request.request.identityUri
504
+ .toString(),
505
+ request.request
506
+ .iconRelativeUri
507
+ .toString(),
508
+ request.request
509
+ .authorizationScope
510
+ )
511
+
512
+ is MobileWalletAdapterRemoteRequest.SignMessages ->
513
+ SignMessages(
514
+ scenarioId!!,
515
+ request.request.chain,
516
+ request.request
517
+ .identityName,
518
+ request.request.identityUri
519
+ .toString(),
520
+ request.request
521
+ .iconRelativeUri
522
+ .toString(),
523
+ request.request
524
+ .authorizationScope,
525
+ request.request.payloads
526
+ .toList()
527
+ )
528
+
529
+ is MobileWalletAdapterRemoteRequest.SignTransactions ->
530
+ SignTransactions(
531
+ scenarioId!!,
532
+ request.request.chain,
533
+ request.request
534
+ .identityName,
535
+ request.request.identityUri
536
+ .toString(),
537
+ request.request
538
+ .iconRelativeUri
539
+ .toString(),
540
+ request.request
541
+ .authorizationScope,
542
+ request.request.payloads
543
+ .toList()
544
+ )
545
+
546
+ is MobileWalletAdapterRemoteRequest.SignAndSendTransactions ->
547
+ SignAndSendTransactions(
548
+ scenarioId!!,
549
+ request.request.chain,
550
+ request.request
551
+ .identityName,
552
+ request.request.identityUri
553
+ .toString(),
554
+ request.request
555
+ .iconRelativeUri
556
+ .toString(),
557
+ request.request
558
+ .authorizationScope,
559
+ request.request.payloads
560
+ .toList()
561
+ )
562
+ }
563
+
564
+ // this is dirty, the requestId needs to line up so have to manually overwrite here
565
+ // should we change javascript side to accept json?
566
+ val eventInfo =
567
+ JsonObject(
568
+ json.encodeToJsonElement(
569
+ MobileWalletAdapterRequestSerializer,
570
+ surrogate
571
+ )
572
+ .jsonObject
573
+ .toMutableMap()
574
+ .apply {
575
+ put(
576
+ "requestId",
577
+ JsonPrimitive(
578
+ request.id
579
+ )
580
+ )
581
+ }
582
+ )
583
+ .toReadableMap()
584
+
585
+ sendEvent(
586
+ reactContext,
587
+ Companion.MOBILE_WALLET_ADAPTER_SERVICE_REQUEST_BRIDGE_NAME,
588
+ eventInfo
589
+ )
590
+ }
591
+
592
+ private fun sendEvent(
593
+ reactContext: ReactContext,
594
+ eventName: String,
595
+ params: ReadableMap? = null
596
+ ) {
597
+ reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
598
+ .emit(eventName, params)
599
+ }
600
+
601
+ private inner class MobileWalletAdapterScenarioCallbacks : LocalScenario.Callbacks {
602
+ /* Session Events */
603
+ override fun onScenarioReady() {
604
+ sendSessionEventToReact(MobileWalletAdapterSessionEvent.ScenarioReady)
605
+ }
606
+
607
+ override fun onScenarioServingClients() {
608
+ sendSessionEventToReact(
609
+ MobileWalletAdapterSessionEvent.ScenarioServingClients
610
+ )
611
+ }
612
+
613
+ override fun onScenarioServingComplete() {
614
+ launch(Dispatchers.Main) {
615
+ scenario = null
616
+ sendSessionEventToReact(MobileWalletAdapterSessionEvent.ScenarioServingComplete)
617
+ }
618
+ }
619
+
620
+ override fun onScenarioComplete() {
621
+ sendSessionEventToReact(MobileWalletAdapterSessionEvent.ScenarioComplete)
622
+ }
623
+
624
+ override fun onScenarioError() {
625
+ sendSessionEventToReact(MobileWalletAdapterSessionEvent.ScenarioError())
626
+ }
627
+
628
+ override fun onScenarioTeardownComplete() {
629
+ sendSessionEventToReact(MobileWalletAdapterSessionEvent.ScenarioTeardownComplete)
630
+ sendSessionEventToReact(MobileWalletAdapterSessionEvent.SessionTerminated)
631
+ }
632
+
633
+ override fun onLowPowerAndNoConnection() {
634
+ sendSessionEventToReact(
635
+ MobileWalletAdapterSessionEvent.LowPowerNoConnection
636
+ )
637
+ }
638
+
639
+ /* Remote Requests */
640
+ override fun onAuthorizeRequest(request: AuthorizeRequest) {
641
+ val request = MobileWalletAdapterRemoteRequest.AuthorizeDapp(request)
642
+ pendingRequests.put(request.id, request)
643
+ sendWalletServiceRequestToReact(request)
644
+ }
645
+
646
+ override fun onReauthorizeRequest(request: ReauthorizeRequest) {
647
+ val request = MobileWalletAdapterRemoteRequest.ReauthorizeDapp(request)
648
+ pendingRequests.put(request.id, request)
649
+ sendWalletServiceRequestToReact(request)
650
+ }
651
+
652
+ override fun onSignTransactionsRequest(request: SignTransactionsRequest) {
653
+ val request = MobileWalletAdapterRemoteRequest.SignTransactions(request)
654
+ pendingRequests.put(request.id, request)
655
+ sendWalletServiceRequestToReact(request)
656
+ }
657
+
658
+ override fun onSignMessagesRequest(request: SignMessagesRequest) {
659
+ val request = MobileWalletAdapterRemoteRequest.SignMessages(request)
660
+ pendingRequests.put(request.id, request)
661
+ sendWalletServiceRequestToReact(request)
662
+ }
663
+
664
+ override fun onSignAndSendTransactionsRequest(
665
+ request: SignAndSendTransactionsRequest
666
+ ) {
667
+ val endpointUri = clusterToRpcUri(request.cluster)
668
+ val request =
669
+ MobileWalletAdapterRemoteRequest.SignAndSendTransactions(
670
+ request,
671
+ endpointUri
672
+ )
673
+ pendingRequests.put(request.id, request)
674
+ sendWalletServiceRequestToReact(request)
675
+ }
676
+
677
+ override fun onDeauthorizedEvent(event: DeauthorizedEvent) {
678
+ val request = MobileWalletAdapterRemoteRequest.DeauthorizeDapp(event)
679
+ pendingRequests.put(request.id, request)
680
+ sendWalletServiceRequestToReact(request)
681
+ }
682
+ }
683
+
684
+ companion object {
685
+ private val TAG = SolanaMobileWalletAdapterWalletLibModule::class.simpleName
686
+ const val MOBILE_WALLET_ADAPTER_SERVICE_REQUEST_BRIDGE_NAME =
687
+ "MobileWalletAdapterServiceRequestBridge"
688
+ const val MOBILE_WALLET_ADAPTER_SESSION_EVENT_BRIDGE_NAME =
689
+ "MobileWalletAdapterSessionEventBridge"
690
+ }
691
+ }