Unleashing the Power of Solana: A Comprehensive Guide to Building Mobile Apps with Solana Mobile Stack

Unleashing the Power of Solana: A Comprehensive Guide to Building Mobile Apps with Solana Mobile Stack

Building Seamless Solana-Powered Apps with Solana Mobile Stack(SMS)

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

Solana Mobile Stack (SMS)

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 .

Mobile Wallet Adapter (MWA) Client Libraries:

  • 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.

  1. 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.

  2. @solana/web3.jsLibrary:

    • Official Solana JavaScript SDK.

    • Provides abstraction classes and RPC Connection Client.

    • Used for interacting with Solana network through JSON RPC API.

  3. Flutter SDK:

    • Integrates Solana Mobile Stack (SMS) into Flutter.

    • Includes Mobile Wallet Adapter for Flutter projects.

  4. Solana Dart Library:

    • Dart implementation of Solana JSON RPC API client.

    • Convenient interface for interacting with Solana RPC methods.

  5. Unity SDK:

    • Community-led project for Solana NFT support and RPC in Unity.

    • Integrates Solana blockchain features into Unity applications.

  6. 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 .

Prerequisites

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

  • ANDROID_HOME Path setup. (See this to set up depending on your system to set up)

  • 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

Setup

Open your terminal and run below commands.

git clone https://github.com/VIVEK-SUTHAR/SolMWARnApp.git
cd 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 .

export type Account = Readonly<{
  address: Base64EncodedAddress;
  label?: string;
  publicKey: PublicKey;
}>;
function getAccountFromAuthorizedAccount(account: AuthorizedAccount): Account {
  return {
    ...account,
    publicKey: getPublicKeyFromAddress(account.address),
  };
}
  1. 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.
  2. It preserves all original properties and adds a publicKey obtained by calling getPublicKeyFromAddress with the address property.

  3. It returns an object whose type is Account

You might wonder what is getPublicKeyFromAddress , it's just a helper function, let's understand it.


function getPublicKeyFromAddress(address: Base64EncodedAddress): PublicKey {
  const publicKeyByteArray = toUint8Array(address);
  return new PublicKey(publicKeyByteArray);
}
  • 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.

type Authorization = Readonly<{
  accounts: Account[];
  authToken: AuthToken;
  selectedAccount: Account;
}>;

function getAuthorizationFromAuthorizationResult(
  authorizationResult: AuthorizationResult,
  previouslySelectedAccount?: Account,
): Authorization {
  let selectedAccount: Account;
  if (

    previouslySelectedAccount == null ||

    !authorizationResult.accounts.some(
      ({address}) => address === previouslySelectedAccount.address,
    )
  ) {
    const firstAccount = authorizationResult.accounts[0];
    selectedAccount = getAccountFromAuthorizedAccount(firstAccount);
  } else {
    selectedAccount = previouslySelectedAccount;
  }
  return {
    accounts: authorizationResult.accounts.map(getAccountFromAuthorizedAccount),
    authToken: authorizationResult.auth_token,
    selectedAccount,
  };
  1. 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.

  2. Return Type: Authorization

    • This indicates that the function is expected to return an object of type Authorization.
  3. 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):

    • We are defining a string 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 :

function AuthorizationProvider(props: {children: ReactNode}) {
  const {children} = props;
  const [authorization, setAuthorization] = useState<Authorization | null>(
    null,
  );
  const handleAuthorizationResult = useCallback(
    async (
      authorizationResult: AuthorizationResult,
    ): Promise<Authorization> => {
      const nextAuthorization = getAuthorizationFromAuthorizationResult(
        authorizationResult,
        authorization?.selectedAccount,
      );
      setAuthorization(nextAuthorization);
      return nextAuthorization;
    },
    [authorization, setAuthorization],
  );


  const authorizeSession = useCallback(
    async (wallet: AuthorizeAPI & ReauthorizeAPI) => {
      const authorizationResult = await (authorization
        ? wallet.reauthorize({
            auth_token: authorization.authToken,
            identity: APP_METADATA,
          })
        : wallet.authorize({
            cluster: RPC_ENDPOINT,
            identity: APP_METADATA,
          }));
      return (await handleAuthorizationResult(authorizationResult))
        .selectedAccount;
    },
    [authorization, handleAuthorizationResult],
  );
  const deauthorizeSession = useCallback(
    async (wallet: DeauthorizeAPI) => {
      if (authorization?.authToken == null) {
        return;
      }
      await wallet.deauthorize({auth_token: authorization.authToken});
      setAuthorization(null);
    },
    [authorization, setAuthorization],
  );
  const onChangeAccount = useCallback(
    (nextSelectedAccount: Account) => {
      setAuthorization(currentAuthorization => {
        if (
          !currentAuthorization?.accounts.some(
            ({address}) => address === nextSelectedAccount.address,
          )
        ) {
          throw new Error(
            `${nextSelectedAccount.address} is not one of the available addresses`,
          );
        }
        return {
          ...currentAuthorization,
          selectedAccount: nextSelectedAccount,
        };
      });
    },
    [setAuthorization],
  );
  const value = useMemo(
    () => ({
      accounts: authorization?.accounts ?? null,
      authorizeSession,
      deauthorizeSession,
      onChangeAccount,
      selectedAccount: authorization?.selectedAccount ?? null,
    }),
    [authorization, authorizeSession, deauthorizeSession, onChangeAccount],
  );

  return (
    <AuthorizationContext.Provider value={value}>
      {children}
    </AuthorizationContext.Provider>
  );
}

Let's break down in pieces and understand it.

 const {children} = props;
  const [authorization, setAuthorization] = useState<Authorization | null>(
    null,
  );

Initially , we are destructuring the props and geting children

Then we initialised a state called authorization, whose type is Authorization or null

const handleAuthorizationResult = useCallback(
    async (
      authorizationResult: AuthorizationResult,
    ): Promise<Authorization> => {
      const nextAuthorization = getAuthorizationFromAuthorizationResult(
        authorizationResult,
        authorization?.selectedAccount,
      );
      setAuthorization(nextAuthorization);
      return nextAuthorization;
    },
    [authorization, setAuthorization],
  );

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.

 const authorizeSession = useCallback(
    async (wallet: AuthorizeAPI & ReauthorizeAPI) => {
      const authorizationResult = await (authorization
        ? wallet.reauthorize({
            auth_token: authorization.authToken,
            identity: APP_METADATA,
          })
        : wallet.authorize({
            cluster: RPC_ENDPOINT,
            identity: APP_METADATA,
          }));
      return (await handleAuthorizationResult(authorizationResult))
        .selectedAccount;
    },
    [authorization, handleAuthorizationResult],
  );

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.

 const deauthorizeSession = useCallback(
    async (wallet: DeauthorizeAPI) => {
      if (authorization?.authToken == null) {
        return;
      }
      await wallet.deauthorize({auth_token: authorization.authToken});
      setAuthorization(null);
    },
    [authorization, setAuthorization],
  );

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.

const onChangeAccount = useCallback(
    (nextSelectedAccount: Account) => {
      setAuthorization(currentAuthorization => {
        if (
          !currentAuthorization?.accounts.some(
            ({address}) => address === nextSelectedAccount.address,
          )
        ) {
          throw new Error(
            `${nextSelectedAccount.address} is not one of the available addresses`,
          );
        }
        return {
          ...currentAuthorization,
          selectedAccount: nextSelectedAccount,
        };
      });
    },
    [setAuthorization],
  );

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.

 const value = useMemo(
    () => ({
      accounts: authorization?.accounts ?? null,
      authorizeSession,
      deauthorizeSession,
      onChangeAccount,
      selectedAccount: authorization?.selectedAccount ?? null,
    }),
    [authorization, authorizeSession, deauthorizeSession, onChangeAccount],
  );
  • Memoizes the context value to avoid unnecessary renders.

  • Contains information such as accounts, authorization and deauthorization functions, and the selected account.

return (
    <AuthorizationContext.Provider value={value}>
      {children}
    </AuthorizationContext.Provider>
  );
  • It Provides the context value to its childrens.

  • Wraps the children with the context provider.

const useAuthorization = () => React.useContext(AuthorizationContext);

export {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:

export interface ConnectionProviderProps {
  children: ReactNode;
  endpoint: string;
  config?: ConnectionConfig;
}

export const ConnectionProvider: FC<ConnectionProviderProps> = ({
  children,
  endpoint,
  config = {commitment: 'confirmed'},
}) => {
  const connection = useMemo(
    () => new Connection(endpoint, config),
    [endpoint, config],
  );

  return (
    <ConnectionContext.Provider value={{connection}}>
      {children}
    </ConnectionContext.Provider>
  );
};

export interface ConnectionContextState {
  connection: Connection;
}

export const ConnectionContext = createContext<ConnectionContextState>(
  {} as ConnectionContextState,
);

export function useConnection(): ConnectionContextState {
  return useContext(ConnectionContext);
}
  1. 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.

  2. 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.

  3. 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.

const handleConnectPress = useCallback(async () => {
    try {
      if (authorizationInProgress) {
        return;
      }
      setAuthorizationInProgress(true);
      await transact(async wallet => {
        await authorizeSession(wallet);
      });
    } catch (err: any) {
      ToastAndroid.show('Failed to connect wallet', ToastAndroid.SHORT);
    } finally {
      setAuthorizationInProgress(false);
    }
  }, [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

 const signMessage = useCallback(
    async (messageBuffer: Uint8Array) => {
      return await transact(async (wallet: Web3MobileWallet) => {
        const authorizationResult = await authorizeSession(wallet);
        const signedMessages = await wallet.signMessages({
          addresses: [authorizationResult.address],
          payloads: [messageBuffer],
        });

        return signedMessages[0];
      });
    },
    [authorizeSession],
  );
  • 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.

const signTransaction = useCallback(async () => {
    try {
      if (signingInProgress) {
        return;
      }
      setSigningInProgress(true);
      const txn = await transact(async (wallet: Web3MobileWallet) => {
        const [authorizationResult, latestBlockhash] = await Promise.all([
          authorizeSession(wallet),
          connection.getLatestBlockhash(),
        ]);
        Logger.Success('Authorized!');

        const randomTransferTransaction = new Transaction({
          ...latestBlockhash,
          feePayer: authorizationResult.publicKey,
        }).add(
          SystemProgram.transfer({
            fromPubkey: authorizationResult.publicKey,
            toPubkey: new PublicKey(toAddress),
            lamports: solAmmount,
          }),
        );

        const signedTransactions = await wallet.signTransactions({
          transactions: [randomTransferTransaction],
        });
        Logger.Success('Signed!', signedTransactions[0]);
        return signedTransactions[0];
      });

      Logger.Log('Serialized transaction: ' + txn.serialize());
      Logger.Log('Sending transaction...');
      const signature = await connection.sendRawTransaction(txn.serialize());
      Logger.Success('Sent! Txn Hash', signature);
      setTxnHash(`https://solscan.io/tx/${signature}?cluster=devnet`);
      ToastAndroid.show('Transaction Sent !', ToastAndroid.SHORT);
    } catch (error) {
      Logger.Error('Error in Sending Txn', error);
    } finally {
      setSigningInProgress(false);
      setSolAmmount(LAMPORTS_PER_SOL / 2);
      setToAddress('');
    }
  }, [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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. Transaction Submission:

    • The serialized transaction is then sent to the Solana blockchain using the connection.sendRawTransaction method, and the resulting transaction signature is logged.
  6. 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.

  7. Cleanup:

    • Finally, whether the transaction was successful or encountered an error, the function sets 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

yarn install 
#
npm 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.

yarn 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 :

Did you find this article valuable?

Support Vivek Suthar by becoming a sponsor. Any amount is appreciated!