Skip to content
This repository was archived by the owner on Jan 24, 2025. It is now read-only.

Commit 7fc2435

Browse files
feat: update Mobile Wallet Adapter lesson (#451)
* feat: update Mobile Wallet Adapter lesson * Formatting update * Update again prettier! * Formatting issue: This will work now. * Readding accidentally deleted export statement. * Fix: Requested changes. Also removing yarn file * Fix: Prettier formatting. * Fix: Prettier formatting 1 * Test readding yarn file * Misc * Updated links to point to knew repo
1 parent bedcc4b commit 7fc2435

File tree

2 files changed

+791
-950
lines changed

2 files changed

+791
-950
lines changed

content/courses/mobile/mwa-deep-dive.md

+102-84
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ description:
1515
- Wallets are just wrappers around a keypair, but they're essential for secure
1616
key management
1717
- 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.
1920
- Solana Mobile's `walletlib` does the heavy lifting for surfacing wallet
2021
requests to wallet apps
2122

@@ -147,7 +148,10 @@ authorization request. The returned `AuthorizationResult` will indicate the
147148
user's acceptance or rejection. If accepted, this result object provides you
148149
with the user's account as well as an `auth_token` you can use in
149150
`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.
151155

152156
```tsx
153157
transact(async (wallet: Web3MobileWallet) => {
@@ -212,7 +216,6 @@ if ( connected ) {
212216
signAllTransactions(...);
213217
signMessage(...);
214218
sendTransaction(...);
215-
}
216219
```
217220
218221
For MWA, simply call the functions on the `wallet` context provided by the
@@ -252,6 +255,24 @@ transact(async (wallet: Web3MobileWallet) => {
252255
Every time you want to call these methods, you will have to call
253256
`wallet.authorize()` or `wallet.reauthorize()`.
254257
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+
255276
And that's it! You should have enough information to get started. The Solana
256277
mobile team has put in a lot of work to make the development experience as
257278
seamless as possible between the two.
@@ -269,8 +290,11 @@ sections; simply try to get a sense of the overall flow.
269290
270291
Solana Mobile has done the vast majority of the heavy lifting by creating the
271292
`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+
```
274298
275299
> This package is still in alpha and is not production ready. However, the API
276300
> 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
351375
request to an RPC provider, and then respond to the requesting dApp using a
352376
`resolve` function.
353377
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
355379
session. The `resolve` function takes two arguments: `request` and `response`.
356380
The types of `request` and `response` are different depending on what the
357381
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
383407
send the transaction.
384408
385409
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)
387411
if you'd like to know all of the types associated with `resolve`.
388412
389413
One final point is that the component used for interacting with `walletlib` also
@@ -426,60 +450,57 @@ app-wallet relationship.
426450
Before we start programming our wallet, we need to do some setup. You will need
427451
a React Native development environment and a Solana dApp to test on. If you have
428452
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
431455
Android device/emulator.
432456
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:
434460
435461
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)
437463
with a device or emulator
438464
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:
442467
443468
```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
445470
cd solana-react-native-counter
446-
git checkout solution
447471
npm run install
448472
```
449473
450-
#### 1. Plan out the app's structure
474+
#### 1. Planning out the app's structure
451475
452476
We are making the wallet from scratch, so let's look at our major building
453477
blocks.
454478
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:
457480
458-
- WalletProvider.tsx
459-
- MainScreen.tsx
460-
- App.tsx
481+
- Creating a `WalletProvider.tsx`
482+
- Modifying the `MainScreen.tsx`
483+
- Modifying `App.tsx`
461484
462485
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:
465487
466-
- MWAApp.tsx
467-
- index.js
488+
- Creating a `MWAApp.tsx`
489+
- Modifying `index.js`
468490
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:
471492
472-
- MWAApp.tsx
473-
- ButtonGroup.tsx
474-
- AppInfo.tsx
493+
- Modifying the `MWAApp.tsx`
494+
- Creating a `ButtonGroup.tsx`
495+
- Creating a `AppInfo.tsx`
475496
476497
Finally, we'll implement two actual request functions, authorize and sign and
477498
send transactions. This entails creating the following:
478499
479-
- AuthorizeDappRequestScreen.tsx
480-
- SignAndSendTransactionScreen.tsx
500+
- `AuthorizeDappRequestScreen.tsx`
501+
- `SignAndSendTransactionScreen.tsx`
481502
482-
#### 2. Scaffold the app
503+
#### 2. Scaffold the Wallet app
483504
484505
Let's scaffold the app with:
485506
@@ -518,45 +539,17 @@ npm install \
518539
fast-text-encoding
519540
```
520541
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.
529544
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.
533548
534549
Let's install the package in a new folder `lib`:
535550
536551
```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
560553
```
561554
562555
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`.
566559
```
567560
568561
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
570564
571565
```bash
572566
npm run android
@@ -666,13 +660,13 @@ export function WalletProvider(props: WalletProviderProps) {
666660
try {
667661
const storedKey = await AsyncStorage.getItem(ASYNC_STORAGE_KEY);
668662
let keyPair;
669-
if (storedKey && storedKey !== null) {
663+
if (storedKey) {
670664
const encodedKeypair: EncodedKeypair = JSON.parse(storedKey);
671665
keyPair = decodeKeypair(encodedKeypair);
672666
} else {
673667
// Generate a new random pair of keys and store them in local storage for later retrieval
674668
// 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();
676670
await AsyncStorage.setItem(
677671
ASYNC_STORAGE_KEY,
678672
JSON.stringify(encodeKeypair(keyPair)),
@@ -688,9 +682,14 @@ export function WalletProvider(props: WalletProviderProps) {
688682
fetchOrGenerateKeypair();
689683
}, []);
690684

685+
const connection = useMemo(
686+
() => new Connection(rpcUrl ?? "https://api.devnet.solana.com"),
687+
[rpcUrl],
688+
);
689+
691690
const value = {
692691
wallet: keyPair,
693-
connection: new Connection(rpcUrl ?? "https://api.devnet.solana.com"),
692+
connection,
694693
};
695694

696695
return (
@@ -728,6 +727,7 @@ function MainScreen() {
728727
const [isLoading, setIsLoading] = useState(false);
729728
const [balance, setBalance] = useState<null | number>(null);
730729
const { wallet, connection } = useWallet();
730+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
731731

732732
useEffect(() => {
733733
updateBalance();
@@ -740,6 +740,7 @@ function MainScreen() {
740740
setBalance(lamports / LAMPORTS_PER_SOL);
741741
} catch (error) {
742742
console.error("Failed to fetch / update balance:", error);
743+
setErrorMessage("Failed to fetch balance");
743744
}
744745
}
745746
};
@@ -756,6 +757,7 @@ function MainScreen() {
756757
await updateBalance();
757758
} catch (error) {
758759
console.log("error requesting airdrop", error);
760+
setErrorMessage("Airdrop failed");
759761
}
760762

761763
setIsLoading(false);
@@ -769,7 +771,8 @@ function MainScreen() {
769771
<Text>Balance:</Text>
770772
<Text>{balance?.toFixed(5) ?? ""}</Text>
771773
{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 && (
773776
<Button title="Airdrop 1 SOL" onPress={airdrop} />
774777
)}
775778
</View>
@@ -1118,16 +1121,31 @@ function MWAApp() {
11181121
// ------------------- EFFECTS --------------------
11191122
11201123
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]);
11281146
11291147
useEffect(() => {
1130-
if (currentSession?.__type == MWASessionEventType.SessionTerminatedEvent) {
1148+
if (currentSession?.__type === MWASessionEventType.SessionTerminatedEvent) {
11311149
endWalletSession();
11321150
}
11331151
}, [currentSession]);
@@ -1137,7 +1155,7 @@ function MWAApp() {
11371155
return;
11381156
}
11391157
1140-
if (currentRequest.__type == MWARequestType.ReauthorizeDappRequest) {
1158+
if (currentRequest.__type === MWARequestType.ReauthorizeDappRequest) {
11411159
resolve(currentRequest, {
11421160
authorizationScope: new TextEncoder().encode("app"),
11431161
});
@@ -1598,7 +1616,7 @@ the Challenge.
15981616
Nice work! Creating a wallet, even a "fake" version, is no small feat. If you
15991617
got stuck anywhere, make sure to go back through it until you understand what's
16001618
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).
16021620

16031621
## Challenge
16041622

@@ -1607,7 +1625,7 @@ request types: `SignMessagesRequest` and `SignTransactionsRequest`.
16071625

16081626
Try to do this without help as it's great practice, but if you get stuck, check
16091627
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).
16111629

16121630
<Callout type="success" title="Completed the lab?">
16131631
Push your code to GitHub and

0 commit comments

Comments
 (0)