@@ -15,7 +15,8 @@ description:
15
15
- Wallets are just wrappers around a keypair, but they're essential for secure
16
16
key management
17
17
- Mobile and Web dApps handle their wallet-app connection differently
18
- - MWA handles all of its wallet interaction within the ` transact ` function
18
+ - MWA handles all of its wallet interaction by wrapping all the wallet's
19
+ functionalities within the ` transact ` function for easier intergration.
19
20
- Solana Mobile's ` walletlib ` does the heavy lifting for surfacing wallet
20
21
requests to wallet apps
21
22
@@ -147,7 +148,10 @@ authorization request. The returned `AuthorizationResult` will indicate the
147
148
user's acceptance or rejection. If accepted, this result object provides you
148
149
with the user's account as well as an ` auth_token ` you can use in
149
150
` wallet.reauthorize() ` for subsequent calls. This auth token ensures that other
150
- apps can't pretend to be your app.
151
+ apps can't pretend to be your app. The auth token is generated during the
152
+ ` authorize() ` call, and subsequent requests from the dApp can use the
153
+ ` reauthorize() ` method with the stored token to maintain secure communication
154
+ without repeatedly prompting the user.
151
155
152
156
``` tsx
153
157
transact (async (wallet : Web3MobileWallet ) => {
@@ -212,7 +216,6 @@ if ( connected ) {
212
216
signAllTransactions (... );
213
217
signMessage (... );
214
218
sendTransaction (... );
215
- }
216
219
` ` `
217
220
218
221
For MWA, simply call the functions on the ` wallet ` context provided by the
@@ -252,6 +255,24 @@ transact(async (wallet: Web3MobileWallet) => {
252
255
Every time you want to call these methods, you will have to call
253
256
` wallet .authorize ()` or ` wallet .reauthorize ()` .
254
257
258
+ When invoking ` wallet .signAndSendTransactions (... )` , it’s essential to handle
259
+ transaction failures gracefully. Transactions can fail due to various reasons
260
+ such as network issues, signature mismatches, or insufficient funds. Proper
261
+ error handling ensures a smooth user experience, even when the transaction
262
+ process encounters issues:
263
+
264
+ ` ` ` tsx
265
+ transact (async (wallet : Web3MobileWallet ) => {
266
+ try {
267
+ const result = await wallet .signAndSendTransactions (... );
268
+ // Handle success
269
+ } catch (error ) {
270
+ console .error (" Failed to sign and send transactions:" , error );
271
+ // Implement error handling logic
272
+ }
273
+ });
274
+ ` ` `
275
+
255
276
And that's it! You should have enough information to get started. The Solana
256
277
mobile team has put in a lot of work to make the development experience as
257
278
seamless as possible between the two.
@@ -269,8 +290,11 @@ sections; simply try to get a sense of the overall flow.
269
290
270
291
Solana Mobile has done the vast majority of the heavy lifting by creating the
271
292
` mobile - wallet - adapter - walletlib ` . This library handles all the low-level
272
- communication between dApps and wallets. However, this package is still in
273
- development and is not available through npm. From their GitHub:
293
+ communication between dApps and wallets:
294
+
295
+ ` ` ` bash
296
+ npm i @solana - mobile / mobile - wallet - adapter - walletlib
297
+ ` ` `
274
298
275
299
> This package is still in alpha and is not production ready. However, the API
276
300
> is stable and will not change drastically, so you can begin integration with
@@ -351,7 +375,7 @@ user's secret key to sign the transaction provided by the request, send the
351
375
request to an RPC provider, and then respond to the requesting dApp using a
352
376
` resolve ` function.
353
377
354
- All the ` resolve ` function does is tell the dApp what happened and close the
378
+ The ` resolve ` function simply tells the dApp what happened and closes the
355
379
session. The ` resolve ` function takes two arguments: ` request ` and ` response ` .
356
380
The types of ` request ` and ` response ` are different depending on what the
357
381
original request was. So in the example of
@@ -383,7 +407,7 @@ Which response you send would depend on the result of attempting to sign and
383
407
send the transaction.
384
408
385
409
You can dig into the
386
- [ ` walletlib ` source] ( https://github.com/solana-mobile/mobile-wallet-adapter/tree /main/js/packages/mobile-wallet-adapter-walletlib )
410
+ [ ` walletlib ` source](https://github.com/solana-mobile/mobile-wallet-adapter/blob /main/js/packages/mobile-wallet-adapter-walletlib/src/resolve.ts )
387
411
if you'd like to know all of the types associated with ` resolve ` .
388
412
389
413
One final point is that the component used for interacting with ` walletlib ` also
@@ -426,60 +450,57 @@ app-wallet relationship.
426
450
Before we start programming our wallet, we need to do some setup. You will need
427
451
a React Native development environment and a Solana dApp to test on. If you have
428
452
completed the
429
- [ Basic Solana Mobile lesson ] ( /content/courses/mobile/basic- solana-mobile ) , both
430
- of these requirements should be met with the counter app installed on your
453
+ [Introduction to Solana Mobile lab ](/content/courses/mobile/intro-to- solana-mobile),
454
+ both of these requirements should be met and the counter app installed on your
431
455
Android device/emulator.
432
456
433
- If you _ haven't_ completed the last lesson you will need to:
457
+ If you _haven't_ completed/done the
458
+ [intro to solana mobile](https://github.com/solana-developers/react-native-fake-wallet)
459
+ you will need to:
434
460
435
461
1. Setup an
436
- [ Android React Native developer environment] ( https://reactnative.dev/docs/environment-setup )
462
+ [Android React Native developer environment](https://github.com/solana-developers/react-native-fake-wallet )
437
463
with a device or emulator
438
464
2. Install a
439
- [ Devnet Solana dApp] ( https://github.com/Unboxed-Software/solana-react-native-counter.git )
440
-
441
- If you want to install the app from the previous lesson, you can:
465
+ [Devnet Solana dApp](https://github.com/solana-developers/react-native-fake-wallet)
466
+ by doing the following steps in your terminal:
442
467
443
468
` ` ` bash
444
- git clone https://github.com/Unboxed-Software/ solana-react-native-counter.git
469
+ git clone https :// github.com/solana-developers/ react-native-fake-wallet
445
470
cd solana - react - native - counter
446
- git checkout solution
447
471
npm run install
448
472
` ` `
449
473
450
- #### 1. Plan out the app's structure
474
+ #### 1. Planning out the app's structure
451
475
452
476
We are making the wallet from scratch, so let's look at our major building
453
477
blocks.
454
478
455
- First, we'll make the actual wallet app (popup not included). This will include
456
- creating or modifying the following:
479
+ First, we'll make the actual wallet app (popup not included). This will include:
457
480
458
- - WalletProvider.tsx
459
- - MainScreen.tsx
460
- - App.tsx
481
+ - Creating a ` WalletProvider .tsx `
482
+ - Modifying the ` MainScreen .tsx `
483
+ - Modifying ` App .tsx `
461
484
462
485
Next, we'll make a boilerplate MWA app that displays 'Im a Wallet' anytime the
463
- wallet is requested from a different dApp. This will include creating or
464
- modifying the following:
486
+ wallet is requested from a different dApp. This will include:
465
487
466
- - MWAApp.tsx
467
- - index.js
488
+ - Creating a ` MWAApp .tsx `
489
+ - Modifying ` index .js `
468
490
469
- Then we'll set up all of our UI and request routing. This will mean creating or
470
- modifying the following:
491
+ Then we'll set up all of our UI and request routing. This will mean:
471
492
472
- - MWAApp.tsx
473
- - ButtonGroup.tsx
474
- - AppInfo.tsx
493
+ - Modifying the ` MWAApp .tsx `
494
+ - Creating a ` ButtonGroup .tsx `
495
+ - Creating a ` AppInfo .tsx `
475
496
476
497
Finally, we'll implement two actual request functions, authorize and sign and
477
498
send transactions. This entails creating the following:
478
499
479
- - AuthorizeDappRequestScreen.tsx
480
- - SignAndSendTransactionScreen.tsx
500
+ - ` AuthorizeDappRequestScreen .tsx `
501
+ - ` SignAndSendTransactionScreen .tsx `
481
502
482
- #### 2. Scaffold the app
503
+ #### 2. Scaffold the Wallet app
483
504
484
505
Let's scaffold the app with:
485
506
@@ -518,45 +539,17 @@ npm install \
518
539
fast - text - encoding
519
540
` ` `
520
541
521
- The next step is a bit messy. We need to depend on Solana's
522
- ` mobile-wallet-adapter-walletlib ` package, which handles all of the low-level
523
- communication. However, this package is still in development and is not
524
- available through npm. From their github:
525
-
526
- > This package is still in alpha and is not production ready. However, the API
527
- > is stable and will not change drastically, so you can begin integration with
528
- > your wallet.
542
+ We need to depend on Solana's ` mobile - wallet - adapter - walletlib ` package, which
543
+ handles all of the low-level communication.
529
544
530
- However, we have extracted the package and made it available on GitHub. If
531
- you're interested in how we did that, take a look at the README
532
- [ on the GitHub repo where we've made this package available ] ( https://github.com/Unboxed-Software/mobile- wallet-adapter-walletlib )
545
+ > Note: A reminder that this package is still in alpha and is not production
546
+ > ready. However, the API is stable and will not change drastically, so you can
547
+ > begin integration with your wallet.
533
548
534
549
Let's install the package in a new folder ` lib ` :
535
550
536
551
` ` ` bash
537
- mkdir lib
538
- cd lib
539
- git clone https://github.com/Unboxed-Software/mobile-wallet-adapter-walletlib.git
540
- ```
541
-
542
- Next, we have to manually link it by adding
543
- ` @solana-mobile/mobile-wallet-adapter-walletlib ` to our ` package.json `
544
- dependencies with the file path as the resolution:
545
-
546
- ``` json
547
- "dependencies" : {
548
- ...
549
- "@solana-mobile/mobile-wallet-adapter-walletlib" : " file:./lib/mobile-wallet-adapter-walletlib" ,
550
- ...
551
- }
552
- ```
553
-
554
- Let npm know about the new package by installing again in the root of your
555
- project:
556
-
557
- ``` bash
558
- cd ..
559
- npm install
552
+ npm i @solana - mobile / mobile - wallet - adapter - walletlib
560
553
` ` `
561
554
562
555
Next, in ` android / build .gradle ` , change the ` minSdkVersion ` to version ` 23 ` .
@@ -566,7 +559,8 @@ Next, in `android/build.gradle`, change the `minSdkVersion` to version `23`.
566
559
` ` `
567
560
568
561
Finally, finish the initial setup by building the app. You should get the
569
- default React Native app showing up on your device.
562
+ default React Native app showing up on your
563
+ device./environment-setup?os=linux&platform=android&guide=native#jdk-studio
570
564
571
565
` ` ` bash
572
566
npm run android
@@ -666,13 +660,13 @@ export function WalletProvider(props: WalletProviderProps) {
666
660
try {
667
661
const storedKey = await AsyncStorage .getItem (ASYNC_STORAGE_KEY );
668
662
let keyPair;
669
- if (storedKey && storedKey !== null ) {
663
+ if (storedKey ) {
670
664
const encodedKeypair: EncodedKeypair = JSON .parse (storedKey );
671
665
keyPair = decodeKeypair (encodedKeypair );
672
666
} else {
673
667
// Generate a new random pair of keys and store them in local storage for later retrieval
674
668
// This is not secure! Async storage is used for demo purpose. Never store keys like this!
675
- keyPair = await Keypair .generate ();
669
+ keyPair = Keypair .generate ();
676
670
await AsyncStorage .setItem (
677
671
ASYNC_STORAGE_KEY ,
678
672
JSON .stringify (encodeKeypair (keyPair )),
@@ -688,9 +682,14 @@ export function WalletProvider(props: WalletProviderProps) {
688
682
fetchOrGenerateKeypair ();
689
683
}, []);
690
684
685
+ const connection = useMemo (
686
+ () => new Connection (rpcUrl ?? " https://api.devnet.solana.com" ),
687
+ [rpcUrl ],
688
+ );
689
+
691
690
const value = {
692
691
wallet: keyPair ,
693
- connection: new Connection ( rpcUrl ?? " https://api.devnet.solana.com " ) ,
692
+ connection ,
694
693
};
695
694
696
695
return (
@@ -728,6 +727,7 @@ function MainScreen() {
728
727
const [isLoading, setIsLoading] = useState (false );
729
728
const [balance, setBalance] = useState <null | number >(null );
730
729
const { wallet, connection } = useWallet ();
730
+ const [errorMessage, setErrorMessage] = useState <string | null >(null );
731
731
732
732
useEffect (() => {
733
733
updateBalance ();
@@ -740,6 +740,7 @@ function MainScreen() {
740
740
setBalance (lamports / LAMPORTS_PER_SOL );
741
741
} catch (error ) {
742
742
console .error (" Failed to fetch / update balance:" , error );
743
+ setErrorMessage (" Failed to fetch balance" );
743
744
}
744
745
}
745
746
};
@@ -756,6 +757,7 @@ function MainScreen() {
756
757
await updateBalance ();
757
758
} catch (error ) {
758
759
console .log (" error requesting airdrop" , error );
760
+ setErrorMessage (" Airdrop failed" );
759
761
}
760
762
761
763
setIsLoading (false );
@@ -769,7 +771,8 @@ function MainScreen() {
769
771
<Text >Balance:</Text >
770
772
<Text >{ balance ?.toFixed (5 ) ?? " " } </Text >
771
773
{ isLoading && <Text >Loading...</Text >}
772
- { balance != null && ! isLoading && balance < 0.005 && (
774
+ { errorMessage && <Text style = { { color: " red" }} >{ errorMessage } </Text >}
775
+ { balance !== null && ! isLoading && balance < 0.005 && (
773
776
<Button title = " Airdrop 1 SOL" onPress = { airdrop } />
774
777
)}
775
778
</View >
@@ -1118,16 +1121,31 @@ function MWAApp() {
1118
1121
// ------------------- EFFECTS --------------------
1119
1122
1120
1123
useEffect(() => {
1121
- BackHandler .addEventListener (" hardwareBackPress" , () => {
1122
- resolve (currentRequest as any , {
1123
- failReason: MWARequestFailReason .UserDeclined ,
1124
- });
1125
- return false ;
1126
- });
1127
- }, []);
1124
+ const backHandler = BackHandler.addEventListener(
1125
+ "hardwareBackPress",
1126
+ () => {
1127
+ if (currentRequest) {
1128
+ switch (currentRequest.__type) {
1129
+ case MWARequestType.AuthorizeDappRequest:
1130
+ case MWARequestType.SignAndSendTransactionsRequest:
1131
+ case MWARequestType.SignMessagesRequest:
1132
+ case MWARequestType.SignTransactionsRequest:
1133
+ resolve(currentRequest, {
1134
+ failReason: MWARequestFailReason.UserDeclined,
1135
+ });
1136
+ break;
1137
+ default:
1138
+ console.warn("Unhandled request type");
1139
+ }
1140
+ }
1141
+ return true; // Prevents default back button behavior
1142
+ },
1143
+ );
1144
+ return () => backHandler.remove();
1145
+ }, [currentRequest]);
1128
1146
1129
1147
useEffect(() => {
1130
- if (currentSession ?.__type == MWASessionEventType .SessionTerminatedEvent ) {
1148
+ if (currentSession?.__type === MWASessionEventType.SessionTerminatedEvent) {
1131
1149
endWalletSession();
1132
1150
}
1133
1151
}, [currentSession]);
@@ -1137,7 +1155,7 @@ function MWAApp() {
1137
1155
return;
1138
1156
}
1139
1157
1140
- if (currentRequest .__type == MWARequestType .ReauthorizeDappRequest ) {
1158
+ if (currentRequest.__type === MWARequestType.ReauthorizeDappRequest) {
1141
1159
resolve(currentRequest, {
1142
1160
authorizationScope: new TextEncoder().encode("app"),
1143
1161
});
@@ -1598,7 +1616,7 @@ the Challenge.
1598
1616
Nice work ! Creating a wallet , even a " fake" version , is no small feat . If you
1599
1617
got stuck anywhere , make sure to go back through it until you understand what ' s
1600
1618
happening . Also , feel free to look through the lab ' s
1601
- [ solution code on the ` main ` branch] ( https://github.com/Unboxed-Software /react-native-fake-solana -wallet ) .
1619
+ [solution code on the ` main ` branch ](https :// github.com/solana-developers /react-native-fake-wallet).
1602
1620
1603
1621
## Challenge
1604
1622
@@ -1607,7 +1625,7 @@ request types: `SignMessagesRequest` and `SignTransactionsRequest`.
1607
1625
1608
1626
Try to do this without help as it ' s great practice, but if you get stuck, check
1609
1627
out the
1610
- [ solution code on the ` solution ` branch] ( https://github.com/Unboxed-Software /react-native-fake-solana-wallet/tree/solution ) .
1628
+ [solution code on the ` solution ` branch ](https :// github.com/solana-developers /react-native-fake-solana-wallet/tree/solution).
1611
1629
1612
1630
<Callout type = " success" title = " Completed the lab?" >
1613
1631
Push your code to GitHub and
0 commit comments