Building Seamless dApp-to-Wallet Experiences with Flutter and Solana Mobile Stack(SMS)

Building Seamless dApp-to-Wallet Experiences with Flutter and Solana Mobile Stack(SMS)

A detailed guide into how to use Solana Mobile Stack(SMS) with flutter dApps.

Solana Mobile Stack

Solana Mobile Stack (SMS) empowers developers to create mobile decentralized applications (dApps) on the lightning-fast Solana blockchain. It's your ticket to harnessing Solana's power for mobile users. Among various other tools developed by the team like seed vault, the solana team has developed a very useful tool called the Solana Mobile Wallet Adapter.

Solana Mobile Wallet Adapter (MWA)

The Mobile Wallet Adapter is an essential bridge between Solana-based mobile dApps and the user's preferred mobile wallet. It simplifies the user experience by allowing seamless integration with popular mobile wallets, enabling users to interact with dApps using their existing wallet accounts. This adapter streamlines the authentication and transaction signing processes, making it convenient for users to engage with Solana-powered mobile applications while maintaining control over their assets and private keys.

For this tutorial, we will be covering the integration of Solana Mobile Wallet Adapter with flutter dApps. This tutorial will give you insight on how to use the flutter sdk with the Solana Mobile Stack (SMS). Have your favourite brew by your side, and let's get started! 😁

Prerequisites

To understand this tutorial, you must have a basic understanding of :

  1. Flutter

  2. Solana

Getting Started

Let's start by setting up your Flutter project:

  1. Install Flutter: If you haven't already, install Flutter by following the instructions in the official Flutter installation guide.

  2. Create a New Flutter Project: Open a terminal window and run the following command to create a new Flutter project:

     flutter create my_solana_dapp
    
  3. Navigate to Your Project Directory: Move into your project directory:

     cd my_solana_dapp
    

Integrating Solana Mobile Wallet Adapter (MWA)

Now that you have your Flutter project set up, let's dive into integrating the Solana Mobile Wallet Adapter into your dApp. Solana Mobile Wallet Adapter comes with two use cases one for wallet app and another on the client side. We will be considering the client side implementation of Solana Wallet Adapter for this tutorial.

Add Dependency

Open the pubspec.yaml file in your Flutter project and add the Solana Mobile Client as a dependency:

dependencies:
  solana_mobile_client: ^0.1.1

Run flutter pub get to fetch the package.

Import the Library

In the Dart file where you plan to use the MWA, import the library:

import 'package:solana_mobile_client/solana_mobile_client.dart';

Creating a SolanaClient Instance

To start using the Solana Mobile Client, you need to create an instance of the SolanaClient class. Here's how you can do it:

SolanaClient solanaClient = SolanaClient(
  rpcUrl: Uri.parse('https://api.testnet.solana.com'),
  websocketUrl: Uri.parse('wss://api.testnet.solana.com'),
);

In the code snippet above, we create a SolanaClient instance with the necessary RPC and WebSocket URLs for connecting to the Solana network. You can customize these URLs based on your network requirements.

Signing, Sending, and Confirming Transactions

One of the core functionalities of the Solana Mobile Client is to sign, send, and confirm transactions. This is essential for interacting with the Solana blockchain. Let's break down the process step by step.

Step 1: Sign the Transaction

To sign a transaction, you'll need a Message object and a list of Ed25519HDKeyPair signers. Here's how you can sign a transaction:

Message message = ...; // Create your message
List<Ed25519HDKeyPair> signers = [...]; // List of signers

TransactionId signedTransaction = await solanaClient.sendAndConfirmTransaction(
  message: message,
  signers: signers,
  commitment: Commitment.processed, // Specify the commitment level
);

In this code snippet, we call the sendAndConfirmTransaction function, passing the Message object, the list of signers, and the desired commitment level. This function signs the transaction and returns a TransactionId once it's confirmed.

Step 2: Wait for Confirmation

Now that the transaction is signed, we need to wait for it to be confirmed on the Solana network. We can use the waitForSignatureStatus function for this purpose:

await solanaClient.waitForSignatureStatus(
  signedTransaction.signature,
  status: ConfirmationStatus.finalized, // Specify the desired status
);

The waitForSignatureStatus function takes the transaction signature and the desired status as parameters. It waits for the transaction to reach the specified status and throws an exception if it fails.

Creating a Subscription Client

The Solana Mobile Client also allows you to create a subscription client for real-time updates. This is useful for receiving notifications about changes on the Solana network.

SubscriptionClient subscriptionClient = solanaClient.createSubscriptionClient(
  pingInterval: Duration(seconds: 10), // Set ping interval (optional)
  connectTimeout: Duration(seconds: 30), // Set connect timeout (optional)
);

In this example, we create a subscriptionClient with optional parameters like ping interval and connect timeout. This client can be used to listen for events and updates from the Solana network.

Creating Associated Token Account

To work with token accounts on the Solana blockchain, you often need to create associated token accounts. These accounts are associated with a specific token mint and owned by a particular user. Here's how you can create an associated token account using the initialized solanaClient instance:

// Define the mint and funder accounts
String mint = 'Your_Mint_Public_Key';
String funder = 'Your_Funder_Public_Key';

// Create the associated token account
String associatedTokenAccount = await solanaClient.createAssociatedTokenAccount(
  mint: mint,
  funder: funder,
);

In this code snippet:

  • mint should be replaced with the actual public key of the token mint you want to associate with.

  • funder is the account that will fund the creation of the associated token account.

  • associatedTokenAccount will store the public key of the newly created associated token account.

Creating Token Account

If you want to create a non-associated token account, you can use the following code:

// Define the mint, account, and creator accounts
String mint = 'Your_Mint_Public_Key';
String account = 'Your_Account_Public_Key';
String creator = 'Your_Creator_Public_Key';

// Create the token account
String tokenAccount = await solanaClient.createTokenAccount(
  mint: mint,
  account: account,
  creator: creator,
);

In this code snippet:

  • mint is the public key of the token mint for the new token account.

  • account is the public key of the account that will own the new token account.

  • creator is the public key of the account that will create the new token account.

  • tokenAccount will store the public key of the newly created token account.

Fetching Associated Token Account

To retrieve information about an associated token account, you can use the following code:

String owner = 'Your_Owner_Public_Key';
String mint = 'Your_Mint_Public_Key';

String associatedTokenAccount = await solanaClient.getAssociatedTokenAccount(
  owner: owner,
  mint: mint,
);

In this code snippet:

  • owner is the public key of the account that owns the associated token account.

  • mint is the public key of the token mint associated with the token account.

  • associatedTokenAccount will store the public key of the associated token account if it exists.

Getting Minimum Balance for Mint Rent Exemption

To calculate the minimum balance required to create a token account for a specific token mint without paying rent, you can use the following code:

String mintAddress = 'Your_Mint_Public_Key';

int minimumBalance = await solanaClient.getMinimumBalanceForMintRentExemption(
  mint: mintAddress,
);

In this code snippet:

  • mintAddress is the public key of the token mint for which you want to calculate the minimum balance.

  • minimumBalance will store the calculated minimum balance.

Fetching Token Mint Information

To retrieve information about a token mint, you can use the following code:

String mintAddress = 'Your_Mint_Public_Key';

TokenMintInfo mintInfo = await solanaClient.getMint(address: mintAddress);

In this code snippet:

  • mintAddress is the public key of the token mint for which you want to fetch information.

  • mintInfo will store the details about the token mint, including supply and decimals.

Fetching Token Balance

To get the balance of a specific token for a particular owner account, you can use the following code:

String owner = 'Your_Owner_Public_Key';
String mint = 'Your_Mint_Public_Key';

TokenBalance balance = await solanaClient.getTokenBalance(
  owner: owner,
  mint: mint,
);

In this code snippet:

  • owner is the public key of the account for which you want to check the token balance.

  • mint is the public key of the token mint.

  • balance will store the token balance information, including the balance amount.

Checking for Associated Token Account

To check if an associated token account exists for a specific owner and mint, you can use the following code:

String owner = 'Your_Owner_Public_Key';
String mint = 'Your_Mint_Public_Key';

bool hasAssociatedAccount = await solanaClient.hasAssociatedTokenAccount(
  owner: owner,
  mint: mint,
);

In this code snippet:

  • owner is the public key of the account.

  • mint is the public key of the token mint.

  • hasAssociatedAccount will be true if an associated token account exists; otherwise, it will be false.

Fetch Account Information

Now, let's retrieve the account information using the initialized solanaClient. The key function here is getAccountInfo(), which is provided by the rpcClient within solanaClient. It takes a single parameter: the public key of the Solana account for which you want to fetch information. Here's how to use it:

final pubKey = 'Your_Solana_Public_Key_Here';
final accountInfo = await solanaClient.rpcClient.getAccountInfo(pubKey);

In this code snippet:

  • pubKey should be replaced with the actual Solana account's public key that you want to query.

  • await is used to indicate that the operation is asynchronous, and the program will wait for the account information to be fetched before proceeding.

  • accountInfo is a variable where the fetched account information will be stored.

Get Token Balance

Future<void> checkBalance() async {
  final balance = await solanaClient.getTokenBalance(owner: owner, mint: mint);
  print('Account Balance: $balance SOL');
}

The checkBalance() function is an asynchronous function that returns a Future<void>, which means that it will return a Future object that will eventually resolve to void. This is because the function needs to wait for the solanaClient.getTokenBalance() call to return before it can print the account balance.

The solanaClient.getTokenBalance() call is also asynchronous, which means that it will return a Future<u64>, which is a Future object that will eventually resolve to a u64 integer representing the account balance.

The await keyword is used to wait for the solanaClient.getTokenBalance() call to return before the checkBalance() function continues executing. This ensures that the balance variable will always contain the account balance before it is printed.

The print() statement is used to print the account balance to the console.

Sending SOL (Solana's Native Token)

To send SOL from one account to another using the MWA, you can follow these steps:

Future<void> sendSOL(double amount, String recipientAddress) async {
  try {
    // Create a transaction
    final transaction = solanaClient.createTransaction();

    // Add a transfer instruction to the transaction
    transaction.addTransferInstruction(
      amount: amount,  // Amount of SOL to send
      recipientAddress: recipientAddress,  // Recipient's SOL address
    );

    // Sign and send the transaction
    final signedTransaction = await solanaClient.signAndSendTransaction(transaction);

    // Transaction sent successfully
    print('Transaction sent: ${signedTransaction.signature}');
  } catch (e) {
    // Handle any errors
    print('Transaction failed: $e');
  }
}

This code creates a transaction, adds a transfer instruction (specifying the amount of SOL to send and the recipient's address), signs the transaction with the connected wallet, and sends it to the Solana network.

Congratulations! You've successfully learned to how to integrate the Solana Mobile Client into your Flutter dApp, allowing users to connect their preferred mobile wallets and interact seamlessly with the Solana blockchain.

You can find an example app using solana mobile client here: https://github.com/Dhruv-Varshney-developer/my_solana_dapp

If you like this article, consider sharing it with people who might need it.

You can tag my social profiles that will give you more reach!

Meet you in some tutorial,

With ❤️,

Signing off,

Dhruv Varshney