If you delve into the realm of Blockchain development, one noticeable aspect is the limited availability of native apps. Only a select few exist, and this scarcity can be attributed to various factors such as inadequate tooling, broken functionalities, or insufficient documentation. However, it's crucial to dispel the notion that everything is inaccessible or broken. In reality, the resources are available; one simply needs to explore and construct them, as the process is straightforward.
Adopting this perspective, Let's explore the world of decentralized apps (DApps) on Solana—a blockchain for scalable and decentralized applications. Founded in 2017 , Solana is known for its impressive speed and lower transaction fees compared to other Layer 1 platforms.
This guide delves deep into the Solana Mobile Stack, a powerful toolkit tailored for crafting mobile-native DApps. It's the go-to for Web3 developers seeking to build user-friendly and potent decentralized applications
Before continuing , Let's see What Exactly is Solana Mobile Stack(SMS).
The Solana Mobile Stack (SMS) comprises essential technologies designed for constructing mobile applications capable of seamless interaction with the Solana blockchain. Among its key components is the Mobile Wallet Adapter (MWA), a protocol specification facilitating the connection between mobile decentralized applications (dApps) and mobile Wallet Apps.(You may consider it as WalletConnect that provides a way to connect wallet and DApps).
This protocol enables effective communication for Solana transaction and message signing. Notably, dApps implementing MWA can seamlessly connect to any compatible MWA Wallet App, streamlining the process of authorization, signing, and sending for transactions and messages. This eliminates the need for developers to individually incorporate support for each wallet, offering a significant advantage as they can integrate once and employ a unified API, ensuring compatibility with every compliant Solana wallet.
Overview of MWA Visualised :
To further enhance the development experience, Solana Mobile provides an official Mobile Wallet Adapter SDK that implements the MWA protocol.
Originally crafted as an Android Kotlin/Java library, the SDK is versatile and has been ported to other popular frameworks listed below.
Android - (Java/Kotlin)
React Native
Flutter
Unity
Unreal Engine
This cross-framework compatibility empowers developers to leverage the SDK's capabilities across a variety of platforms, fostering flexibility and efficiency in the creation of mobile-native dApps within the web3 mobile landscape.
Let's Go through each of SDK available in a brief .
Kotlin Library (mobile-wallet-adapter-clientlib-ktx):
Implements MWA protocol in Kotlin.
Facilitates session initiation between dApp and wallet.
Recommended for native Android app development.
Java Library (mobile-wallet-adapter-clientlib
):
Equivalent to Kotlin library but implemented in Java.
Kotlin library is recommended for a modern development experience.
React Native SDK:
Uses React Native for cross-platform app development.
Leverages Solana's @solana/web3.js
library.
Includes @solana-mobile/mobile-wallet-adapter-protocol
for MWA support.
@solana/web3.js
Library:
Official Solana JavaScript SDK.
Provides abstraction classes and RPC Connection Client.
Used for interacting with Solana network through JSON RPC API.
Flutter SDK:
Integrates Solana Mobile Stack (SMS) into Flutter.
Includes Mobile Wallet Adapter for Flutter projects.
Solana Dart Library:
Dart implementation of Solana JSON RPC API client.
Convenient interface for interacting with Solana RPC methods.
Unity SDK:
Community-led project for Solana NFT support and RPC in Unity.
Integrates Solana blockchain features into Unity applications.
Unreal Engine SDK (solana-saga-unreal-sdk):
Open-source Unreal Engine plugin.
Integrates with Solana Mobile Stack for wallet signing in Unreal Engine projects.
Community-driven, may be under ongoing development.
But Solana has launched it's own android based smart phone, called Solana Saga.
Without Saga Phone: Developers using Solana Mobile Stack (SMS) without the Saga phone can still harness its powerful capabilities for building web3 native dApps. SMS offers tools like Mobile Wallet Adapter, providing access to Solana's efficient blockchain network. While developers enjoy flexibility across various devices, they may miss out on premium hardware optimizations tailored for web3.
With Saga Phone: The Saga phone enhances the web3 development experience with premium hardware, including a Seed Vault for seamless asset self-custody and heightened security. The curated dApp Store simplifies exploration and utilization of web3 applications. The synergy between Saga phone and SMS creates an optimized environment, offering developers an efficient, secure, and premium platform for web3 mobile development.
So At this point, you should have clear that What is Solana Mobile Stack, what it offers and how one can leverage to build DApps with it.
So Let's create one sample app using SMS, here I am going to build the App with React Native SDK, so it will be easy for beginners to grasp .
Before Starting to code, make sure you have Installed
A Basic knowledge about React and Javascript
NodeJS environment
JAVA_HOME environment variable set
Android Studio Installed along with Android SDK Tools
A Basic knowledge about Solana.
A Physical device or emulator installed with MWA Compatible Solana Wallet.
So Let's start coding.
Here's the Overview Of How our App will look like
Here's the Flow of our App:
Connect Wallet Screen: User will be to connect their wallet
Sign Message: Implemented a sign message feature to enable users to sign messages as a means of identity verification or for interacting with decentralized applications (dApps).
Home: Here, We will display user's connected wallet address, the SOL balance, and different activities like
Request Test Faucet: Integrate a feature that allows users to request test SOL tokens from a faucet. This can be beneficial for testing and development purposes on the Solana devnet.
Direct Transfers: Enable users to perform direct transfers between their own addresses within the app. This feature can simplify the process of managing assets and funds for users.
Disconnect Wallet: Users will be able to disconnect the wallet
Block height details: Shows the current block height and displays the cluster to which user is connected
Open your terminal and run below commands.
1git clone https://github.com/VIVEK-SUTHAR/SolMWARnApp.git 2cd SolMWARnApp
This will clone the example app and change the current directory to cloned repository.
Now open your project with Your favourite code editor.Your project structure should look like this .
Feeling overwhelmed ? 🤔 Let's breakdown the project structure.
android
: Our native code directory for android including build.gradle
and MainActivity.java
assets
: Where we keep our assets like images and font
components
: Where we keep our Reusable React Components
constants
: Here, we keep our constant like name and metadata
hooks
: Reusable logic as Custom React hooks
navigation
: Where we define our navigation structures of the app.
screens
: Where we place our Screen files.
types
: types declaration is kept here
utils
:Here, we keep the utility function, we we need very frequent
Now, You should have the basic idea of our project.
Let's See the overall structure of the App.
This is our Overall structure of the App.
App.tsx
: Our Main App file
StackNavigation.tsx
: Contains our screens like ConnectWallet
,Home
and SignMessage
We also have two Contexts
AuthorizationProvider
: It helps us to manage the Authorization with wallet and keep a global state of connected wallet details
ConnectionProvider
: Provides us a connection instance ,via which we can interact with the Solana Nodes via JSON RPC.
Now Let's Understand AuthorizationProvider
:
Don’t get overwhelmed , Let's under stand it by getting one function at a time .
1export type Account = Readonly<{ 2 address: Base64EncodedAddress; 3 label?: string; 4 publicKey: PublicKey; 5}>; 6function getAccountFromAuthorizedAccount(account: AuthorizedAccount): Account { 7 return { 8 ...account, 9 publicKey: getPublicKeyFromAddress(account.address), 10 }; 11}
It takes :
account
: expected to be of type AuthorizedAccount
. This suggests that the function is designed to work with an object that has the structure defined by the AuthorizedAccount
type.It preserves all original properties and adds a publicKey
obtained by calling getPublicKeyFromAddress
with the address
property.
It returns an object whose type is Account
You might wonder what is getPublicKeyFromAddress
, it's just a helper function, let's understand it.
1 2function getPublicKeyFromAddress(address: Base64EncodedAddress): PublicKey { 3 const publicKeyByteArray = toUint8Array(address); 4 return new PublicKey(publicKeyByteArray); 5}
The getPublicKeyFromAddress
function takes a Base64EncodedAddress
as input and returns a PublicKey
. It begins by converting the base64-encoded address into a byte array using the toUint8Array
function.
toUint8Array
is the utility function, which takes a base-64 encoded string and returns a Uint8Array.We have imported it from js-base64
The final step involves creating a new PublicKey
object from the byte array.
1type Authorization = Readonly<{ 2 accounts: Account[]; 3 authToken: AuthToken; 4 selectedAccount: Account; 5}>; 6 7function getAuthorizationFromAuthorizationResult( 8 authorizationResult: AuthorizationResult, 9 previouslySelectedAccount?: Account, 10): Authorization { 11 let selectedAccount: Account; 12 if ( 13 14 previouslySelectedAccount == null || 15 16 !authorizationResult.accounts.some( 17 ({address}) => address === previouslySelectedAccount.address, 18 ) 19 ) { 20 const firstAccount = authorizationResult.accounts[0]; 21 selectedAccount = getAccountFromAuthorizedAccount(firstAccount); 22 } else { 23 selectedAccount = previouslySelectedAccount; 24 } 25 return { 26 accounts: authorizationResult.accounts.map(getAccountFromAuthorizedAccount), 27 authToken: authorizationResult.auth_token, 28 selectedAccount, 29 };
It takes two parameters:
authorizationResult
: This is an object of type AuthorizationResult
and is a required parameter, and this type is imported from @solana-mobile/mobile-wallet-adapter-protocol
,
previouslySelectedAccount?: Account
: This is an optional parameter of type Account
. It represents an account that may have been previously selected.
Return Type: Authorization
Authorization
.Function Body:
Initially ,we are declaring a variable selectedAccount
of type Account
.
Then we are checking whether a previouslySelectedAccount
is provided and if it exists in the list of authorizationResult
accounts. If not, or if there was no previous selection, we are seeting selectedAccount
to be the first account from authorizationResult
, transformed into an Account
using the getAccountFromAuthorizedAccount
function.
If a previouslySelectedAccount
is found in the list, we set selectedAccount
to be the provided previouslySelectedAccount
.
Then we constructs an object of type Authorization
with properties:
accounts
: An array of Account
objects obtained by mapping the getAccountFromAuthorizedAccount
function over all accounts in authorizationResult
.
authToken
: The auth_token
property from authorizationResult
.
selectedAccount
: The determined selectedAccount
Now, it time to move on next.
Interface Definition (AuthorizationProviderContext
):
We are defining an interface named AuthorizationProviderContext
.
Properties of AuthorizationProviderContext
:
accounts
: An array of Account
objects or null
.
authorizeSession
: A function that takes a wallet
parameter (which should have specific methods) and returns a Promise resolving to an Account
.
deauthorizeSession
: A function that takes a wallet
parameter and doesn't return anything (void
).
onChangeAccount
: A function that takes a nextSelectedAccount
parameter (an Account
) and doesn't return anything.
selectedAccount
: An Account
object or null
.
Constant Definition (DEFAULT_AUTHORIZATION_PROVIDER_ERROR_MESSAGE
):
DEFAULT_AUTHORIZATION_PROVIDER_ERROR_MESSAGE
with a default error message.Context Creation (AuthorizationContext
):
Here, we are creating a React context using React.createContext
for the AuthorizationProviderContext
.
The default values for the context are provided as an object:
accounts
is set to null
.
The authorizeSession
, deauthorizeSession
, and onChangeAccount
functions are defined to throw an error with the default message.
selectedAccount
is set to null
.
Now time for our AuthorizationProvider
:
1function AuthorizationProvider(props: {children: ReactNode}) { 2 const {children} = props; 3 const [authorization, setAuthorization] = useState<Authorization | null>( 4 null, 5 ); 6 const handleAuthorizationResult = useCallback( 7 async ( 8 authorizationResult: AuthorizationResult, 9 ): Promise<Authorization> => { 10 const nextAuthorization = getAuthorizationFromAuthorizationResult( 11 authorizationResult, 12 authorization?.selectedAccount, 13 ); 14 setAuthorization(nextAuthorization); 15 return nextAuthorization; 16 }, 17 [authorization, setAuthorization], 18 ); 19 20 21 const authorizeSession = useCallback( 22 async (wallet: AuthorizeAPI & ReauthorizeAPI) => { 23 const authorizationResult = await (authorization 24 ? wallet.reauthorize({ 25 auth_token: authorization.authToken, 26 identity: APP_METADATA, 27 }) 28 : wallet.authorize({ 29 cluster: RPC_ENDPOINT, 30 identity: APP_METADATA, 31 })); 32 return (await handleAuthorizationResult(authorizationResult)) 33 .selectedAccount; 34 }, 35 [authorization, handleAuthorizationResult], 36 ); 37 const deauthorizeSession = useCallback( 38 async (wallet: DeauthorizeAPI) => { 39 if (authorization?.authToken == null) { 40 return; 41 } 42 await wallet.deauthorize({auth_token: authorization.authToken}); 43 setAuthorization(null); 44 }, 45 [authorization, setAuthorization], 46 ); 47 const onChangeAccount = useCallback( 48 (nextSelectedAccount: Account) => { 49 setAuthorization(currentAuthorization => { 50 if ( 51 !currentAuthorization?.accounts.some( 52 ({address}) => address === nextSelectedAccount.address, 53 ) 54 ) { 55 throw new Error( 56 `${nextSelectedAccount.address} is not one of the available addresses`, 57 ); 58 } 59 return { 60 ...currentAuthorization, 61 selectedAccount: nextSelectedAccount, 62 }; 63 }); 64 }, 65 [setAuthorization], 66 ); 67 const value = useMemo( 68 () => ({ 69 accounts: authorization?.accounts ?? null, 70 authorizeSession, 71 deauthorizeSession, 72 onChangeAccount, 73 selectedAccount: authorization?.selectedAccount ?? null, 74 }), 75 [authorization, authorizeSession, deauthorizeSession, onChangeAccount], 76 ); 77 78 return ( 79 <AuthorizationContext.Provider value={value}> 80 {children} 81 </AuthorizationContext.Provider> 82 ); 83}
Let's break down in pieces and understand it.
1 const {children} = props; 2 const [authorization, setAuthorization] = useState<Authorization | null>( 3 null, 4 );
Initially , we are destructuring the props and geting children
Then we initialised a state called authorization
, whose type is Authorization
or null
1const handleAuthorizationResult = useCallback( 2 async ( 3 authorizationResult: AuthorizationResult, 4 ): Promise<Authorization> => { 5 const nextAuthorization = getAuthorizationFromAuthorizationResult( 6 authorizationResult, 7 authorization?.selectedAccount, 8 ); 9 setAuthorization(nextAuthorization); 10 return nextAuthorization; 11 }, 12 [authorization, setAuthorization], 13 );
The function handleAuthorizationResult
is a memoized function, created using useCallback.
It takes an authorizationResult
of type AuthorizationResult
.
Then we are using getAuthorizationFromAuthorizationResult
to derive the next authorization state based on the result.
After it we are updating the state using setAuthorization
and returns the next authorization.
1 const authorizeSession = useCallback( 2 async (wallet: AuthorizeAPI & ReauthorizeAPI) => { 3 const authorizationResult = await (authorization 4 ? wallet.reauthorize({ 5 auth_token: authorization.authToken, 6 identity: APP_METADATA, 7 }) 8 : wallet.authorize({ 9 cluster: RPC_ENDPOINT, 10 identity: APP_METADATA, 11 })); 12 return (await handleAuthorizationResult(authorizationResult)) 13 .selectedAccount; 14 }, 15 [authorization, handleAuthorizationResult], 16 );
The function authorizeSession
is a memoized function, created using useCallback.
This function handles the authorization process.
Calls either wallet.reauthorize
or wallet.authorize
based on whether there is an existing authorization.
Calls handleAuthorizationResult
to update the state and returns the selected account.
1 const deauthorizeSession = useCallback( 2 async (wallet: DeauthorizeAPI) => { 3 if (authorization?.authToken == null) { 4 return; 5 } 6 await wallet.deauthorize({auth_token: authorization.authToken}); 7 setAuthorization(null); 8 }, 9 [authorization, setAuthorization], 10 );
The function deauthorizeSession
is a memoized function, created using useCallback.
This function handles the deauthorization process.
Checks if there is a valid authorization with an auth token before calling wallet.deauthorize
.
Sets the authorization state to null
after deauthorization.
1const onChangeAccount = useCallback( 2 (nextSelectedAccount: Account) => { 3 setAuthorization(currentAuthorization => { 4 if ( 5 !currentAuthorization?.accounts.some( 6 ({address}) => address === nextSelectedAccount.address, 7 ) 8 ) { 9 throw new Error( 10 `${nextSelectedAccount.address} is not one of the available addresses`, 11 ); 12 } 13 return { 14 ...currentAuthorization, 15 selectedAccount: nextSelectedAccount, 16 }; 17 }); 18 }, 19 [setAuthorization], 20 );
The function onChangeAccount
is a memoized function, created using useCallback.
Updates the selected account in the authorization state.
Throws an error if the provided account is not part of the available addresses.
1 const value = useMemo( 2 () => ({ 3 accounts: authorization?.accounts ?? null, 4 authorizeSession, 5 deauthorizeSession, 6 onChangeAccount, 7 selectedAccount: authorization?.selectedAccount ?? null, 8 }), 9 [authorization, authorizeSession, deauthorizeSession, onChangeAccount], 10 );
Memoizes the context value to avoid unnecessary renders.
Contains information such as accounts, authorization and deauthorization functions, and the selected account.
1return ( 2 <AuthorizationContext.Provider value={value}> 3 {children} 4 </AuthorizationContext.Provider> 5 );
It Provides the context value to its childrens.
Wraps the children
with the context provider.
1const useAuthorization = () => React.useContext(AuthorizationContext); 2 3export {AuthorizationProvider, useAuthorization};
const useAuthorization = () => React.useContext(AuthorizationContext);
We defined a custom hook named useAuthorization
.
It utilizes the useContext
hook from React to retrieve the current value of the AuthorizationContext
.
This hook is designed to be used within functional components to access the authorization-related information provided by the AuthorizationProvider
.
So it was all about
It's time for ConnectionProvider
:
1export interface ConnectionProviderProps { 2 children: ReactNode; 3 endpoint: string; 4 config?: ConnectionConfig; 5} 6 7export const ConnectionProvider: FC<ConnectionProviderProps> = ({ 8 children, 9 endpoint, 10 config = {commitment: 'confirmed'}, 11}) => { 12 const connection = useMemo( 13 () => new Connection(endpoint, config), 14 [endpoint, config], 15 ); 16 17 return ( 18 <ConnectionContext.Provider value={{connection}}> 19 {children} 20 </ConnectionContext.Provider> 21 ); 22}; 23 24export interface ConnectionContextState { 25 connection: Connection; 26} 27 28export const ConnectionContext = createContext<ConnectionContextState>( 29 {} as ConnectionContextState, 30); 31 32export function useConnection(): ConnectionContextState { 33 return useContext(ConnectionContext); 34}
ConnectionProvider Component:
The ConnectionProvider
component takes in children
, endpoint
(Solana RPC endpoint), and an optional config
object as props.
It uses the useMemo
hook to create a new Solana Connection
object. This object is initialized with the provided endpoint
and optional config
.
The Connection
object is then wrapped within the ConnectionContext.Provider
, making it accessible to the components in the component tree underneath.
The children
(nested components) are rendered within this provider, allowing them to access the Solana connection through the context.
ConnectionContext State and Provider:
The ConnectionContextState
interface defines the structure of the context state, which includes a connection
property holding the Solana Connection
object.
The ConnectionContext
is created using createContext
, initialized with an empty object as the default value.
The ConnectionProvider
sets the value
of the context provider to an object containing the connection
property with the Solana Connection
object.
useConnection Hook:
The useConnection
function is a custom hook that utilizes useContext
to retrieve the current context value.
It returns the ConnectionContextState
, providing components with access to the Solana Connection
object.
Now you should have the basic idea how wallet authorization works.If you don’t get it, go through it 2-3 times and try to simulate flow, Even I had a hard time in the first shot, so don’t worry, eventually you will get it.
This 2 was our main function that manages the connection and authorization.
Now Let's see how the ConnectWallet uses this to connect our DApp with the wallet.
1const handleConnectPress = useCallback(async () => { 2 try { 3 if (authorizationInProgress) { 4 return; 5 } 6 setAuthorizationInProgress(true); 7 await transact(async wallet => { 8 await authorizeSession(wallet); 9 }); 10 } catch (err: any) { 11 ToastAndroid.show('Failed to connect wallet', ToastAndroid.SHORT); 12 } finally { 13 setAuthorizationInProgress(false); 14 } 15 }, [authorizationInProgress, authorizeSession, selectedAccount]);
This function is triggered when user presses our ConnectWalle button
It first checks if an authorization process is already in progress and, if so, exits early to avoid concurrent requests.
If not, it sets the authorizationInProgress
state to true
to indicate the initiation of the authorization process.
It then utilizes the transact
function, presumably handling transactions, and within this transactional context, it calls the authorizeSession
function to initiate or reauthorize the wallet session. Note that transact
is coming from @solana-mobile/mobile-wallet-adapter-protocol-web3js
,which takes one argument and it it the wallet.
If any errors occur during the process, a toast notification is displayed indicating the failure to connect the wallet. Finally, regardless of success or failure, it sets authorizationInProgress
back to false
.
You should’ve got this, if not follow the approach I given early 😅.
Now Let's understand the SignMessage function in SignMessageButton
1 const signMessage = useCallback( 2 async (messageBuffer: Uint8Array) => { 3 return await transact(async (wallet: Web3MobileWallet) => { 4 const authorizationResult = await authorizeSession(wallet); 5 const signedMessages = await wallet.signMessages({ 6 addresses: [authorizationResult.address], 7 payloads: [messageBuffer], 8 }); 9 10 return signedMessages[0]; 11 }); 12 }, 13 [authorizeSession], 14 );
First we are converting our message into an Uint8Array
, and then the signMessage
function is called to handle the signing process.
The signMessage
function, First, it authorizes or reauthorizes the wallet session using the authorizeSession
function. Then, it utilizes the signMessages
method on the wallet object to sign the provided message buffer using the address associated with the authorized account.
If the signing process is successful, then we are navigating to the Home
screen, indicating a successful connection or authorization.
Now Let's Understand the TransferSol
function.
1const signTransaction = useCallback(async () => { 2 try { 3 if (signingInProgress) { 4 return; 5 } 6 setSigningInProgress(true); 7 const txn = await transact(async (wallet: Web3MobileWallet) => { 8 const [authorizationResult, latestBlockhash] = await Promise.all([ 9 authorizeSession(wallet), 10 connection.getLatestBlockhash(), 11 ]); 12 Logger.Success('Authorized!'); 13 14 const randomTransferTransaction = new Transaction({ 15 ...latestBlockhash, 16 feePayer: authorizationResult.publicKey, 17 }).add( 18 SystemProgram.transfer({ 19 fromPubkey: authorizationResult.publicKey, 20 toPubkey: new PublicKey(toAddress), 21 lamports: solAmmount, 22 }), 23 ); 24 25 const signedTransactions = await wallet.signTransactions({ 26 transactions: [randomTransferTransaction], 27 }); 28 Logger.Success('Signed!', signedTransactions[0]); 29 return signedTransactions[0]; 30 }); 31 32 Logger.Log('Serialized transaction: ' + txn.serialize()); 33 Logger.Log('Sending transaction...'); 34 const signature = await connection.sendRawTransaction(txn.serialize()); 35 Logger.Success('Sent! Txn Hash', signature); 36 setTxnHash(`https://solscan.io/tx/${signature}?cluster=devnet`); 37 ToastAndroid.show('Transaction Sent !', ToastAndroid.SHORT); 38 } catch (error) { 39 Logger.Error('Error in Sending Txn', error); 40 } finally { 41 setSigningInProgress(false); 42 setSolAmmount(LAMPORTS_PER_SOL / 2); 43 setToAddress(''); 44 } 45 }, [authorizeSession, connection, solAmmount, signingInProgress]);
Here's the breakdown:
This function, signTransaction
, is designed to handle the signing and submission of a transaction in a Solana blockchain-based mobile application. Here's a breakdown of its functionality:
Initial Checks: The function begins by checking if a transaction signing process is already in progress (signingInProgress
). If it is, the function exits early to prevent concurrent signing attempts.
Setting Signing in Progress: If no signing process is in progress, the function sets the signingInProgress
state to true
to indicate the initiation of the signing process.
Transaction Authorization and Blockhash Retrieval: The function then initiates a transaction using the transact
function, which involves authorizing or reauthorizing the wallet session (authorizeSession
). Additionally, it fetches the latest block-hash from using connection.getLatestBlockhash()
method.
Transaction Construction and Signing:
With the authorization result and the latest blockhash, the function constructs a transaction. In this case, it's a transfer transaction transferring a specified amount of SOL (solAmount
) to a specified recipient (toAddress
).
The transaction is signed using the wallet.signTransactions
method, obtaining a signed transaction object.
Transaction Submission:
connection.sendRawTransaction
method, and the resulting transaction signature is logged.Logging and UI Updates:
Various log messages are output using a Logger utility, indicating the success of authorization, signing, and transaction submission.
The transaction hash is formatted for viewing on SolScan, and a toast notification is displayed indicating that the transaction has been sent.
Cleanup:
signingInProgress
back to false
. Additionally, it resets the SOL amount (solAmount
) and the recipient address (toAddress
).Now, we have covered the almost main functions and the logic, rest is just a UI and some styling , which I leave upon you .
Now Let's run the app and see it in action.
In the VS Code or your editor, open terminal and run below command
1yarn install 2# 3npm install
This will install all the dependencies required by our app.
Once it's done.
Make sure your device is connected to your computer and USB Debugging is enabled,Alternatively if your pc and phone is on same WiFi network, you can use the WiFi debugging to run your app physically.
Now run below command.
1yarn android
This command will build a debug build of our app, bundles our JS code, starts the local server and runs our app.
If you followed the pre requirements and set up your environment , the app will build and will be ready to test.
Here's the output how the first screen will look like.
Once you click the Connect Wallet, you will be prompted in your wallet to connect our DApp just like below.
Now your app is up and running, try out sign message, request some test faucet and try to transfer to your another address straight forward from your app, you cal also change the UI and make it more user friendlier .Possibilities are endless, it up to you and your creativity .
Now you a have idea of What Solana Mobile Stack is, how to build an App with it using React Native and handle connections with Wallet and sending transactions.
This is all about Solana Mobile Stack, but there are also other developer tools available. Here's the list
Solana Pay for Android: The Solana Pay protocol was developed independently of the Solana Mobile Stack, but combining payments with a mobile device is a natural fit for Solana Pay.
CandyPay : CandyPay is a low-code checkout solution built on Solana, they also have the Android SDK available to integrate with your app.
Saganize : Saganize enables in-app transactions for Solana Mobile dApps, allowing users to interact with transactions within their app, without the need to build a separate wallet infrastructure.
Also, if you are interested in Learning Solana, Here's the resources that can help you
Solana Official Docs : The official Solana Documentation
Solana CookBook : The Solana Cookbook is a developer resource that provides the essential concepts and references for building applications on Solana.
Solana Core by Buildspace : It'a a 6 week long course offered by Buildspace
Rust Book (Rust is official programming language of Solana)
Anchor (A Framework for building, testing and deploying Solana Programs)
If you enjoyed this blog, please consider sharing it on your social media. Your support motivates me to create more content like this! Sharing helps others discover valuable insights and builds a supportive community of learners and developers.
In the next part, we will see how we can interact with the Custom Solana Program and see from deploying a program to integrating in the App.
Thank you for being part of this journey with me.
Until Next Time,
Keep Coding, Keep Debugging.
See you in next blog 👋
Reference :