Skip to content

Latest commit

Β 

History

History

flutter-solana-example

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

README.md

MetaMask Embedded Wallets Flutter - Solana Example

Web3Auth Solana Flutter

This example demonstrates how to integrate MetaMask Embedded Wallets with Solana blockchain in a Flutter application. It showcases social authentication combined with Solana wallet operations including SOL transfers, SPL token management, and NFT interactions.

πŸ“ Features

  • Social Authentication: Google, Facebook, Twitter, Discord, Apple, and more
  • Solana Wallet: Automatic Ed25519 Solana wallet creation
  • SOL Operations: Check balance, send SOL, request airdrops (devnet/testnet)
  • SPL Token Support: Token balances, transfers, and token account management
  • NFT Support: View and manage Solana NFTs
  • Secure Key Management: Non-custodial Ed25519 key management
  • Cross-Platform: Single codebase for iOS and Android
  • Devnet & Mainnet: Easily switch between Solana networks

What's new in v8

This example uses web3auth_flutter ^8.0.0 with explicit Solana chain config in Web3AuthOptions and getEd25519PrivateKey(). See the root README.

  • iOS 15, native Web3Auth from Swift Package Manager
  • sessionTime comes from the dashboard
  • Email passwordless login uses MFALevel.OPTIONAL (MFALevel.DEFAULT was removed)

πŸš€ Getting Started

Prerequisites

  • Flutter: 3.0.0 or higher
  • Dart: 2.18.0 or higher
  • MetaMask Embedded Wallets: Dashboard account
  • Solana Knowledge: Basic understanding of Solana blockchain
  • iOS (for iOS development):
    • iOS 15+, Xcode 15+, Swift 5.x, CocoaPods (other plugins) + Swift Package Manager (Web3Auth)
  • Android (for Android development):
    • API level 26+, compileSdkVersion 34, JDK 11+

Installation

  1. Clone the repository:

    git clone https://github.com/Web3Auth/web3auth-flutter-examples.git
    cd web3auth-flutter-examples/flutter-solana-example
  2. Install dependencies:

    flutter pub get
  3. iOS Setup (for iOS development):

    cd ios && pod install && cd ..

Dashboard Configuration

  1. Create a project on the Embedded Wallets Dashboard

  2. Choose your network:

    • Sapphire Devnet: For development/testing
    • Sapphire Mainnet: For production
  3. Configure platform settings:

    • iOS: Allowlist {bundleId}://auth (e.g., com.example.solanaapp://auth)
    • Android: Allowlist your package name and configure deep link scheme
  4. Get your Client ID from the dashboard

Code Configuration

Update the configuration in lib/main.dart:

import 'package:web3auth_flutter/web3auth_flutter.dart';
import 'dart:io';

Future<void> initWeb3Auth() async {
  String redirectUrl;
  
  if (Platform.isAndroid) {
    redirectUrl = 'w3a://com.example.solanaapp/auth';
  } else if (Platform.isIOS) {
    redirectUrl = 'com.example.solanaapp://auth';
  }

  await Web3AuthFlutter.init(
    Web3AuthOptions(
      clientId: "YOUR_WEB3AUTH_CLIENT_ID",
      web3AuthNetwork: Web3AuthNetwork.sapphire_mainnet, // or Web3AuthNetwork.sapphire_devnet
      redirectUrl: redirectUrl,
    )
  );
  
  await Web3AuthFlutter.initialize();
}

Configure Solana network in lib/config/solana_config.dart:

class SolanaConfig {
  // Solana Devnet (for testing)
  static const String devnetRpcUrl = 'https://api.devnet.solana.com';
  
  // Solana Mainnet (for production)
  static const String mainnetRpcUrl = 'https://api.mainnet-beta.solana.com';
  
  // Or use a dedicated RPC provider for better performance
  // static const String mainnetRpcUrl = 'https://solana-mainnet.g.alchemy.com/v2/YOUR_API_KEY';
  
  // Current environment
  static const bool useDevnet = true; // Set to false for mainnet
  
  static String get rpcUrl => useDevnet ? devnetRpcUrl : mainnetRpcUrl;
}

Running the App

# Run in debug mode
flutter run

# Build for release
flutter build ios  # For iOS
flutter build apk  # For Android

πŸ’‘ Implementation Details

Project Structure

lib/
β”œβ”€β”€ main.dart              # Entry point & initialization
β”œβ”€β”€ config/
β”‚   └── solana_config.dart # Solana network configuration
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ web3auth_service.dart # Web3Auth operations
β”‚   └── solana_service.dart   # Solana blockchain operations
└── screens/
    β”œβ”€β”€ home.dart          # Home screen with wallet info
    └── login.dart         # Login screen

Core Implementation

1. Initialize Web3Auth

import 'package:web3auth_flutter/web3auth_flutter.dart';

Future<void> initWeb3Auth() async {
  // ... (see Code Configuration section above)
}

2. Implement Authentication

// Login with Google
Future<void> login() async {
  try {
    final Web3AuthResponse response = await Web3AuthFlutter.connectTo(
      LoginParams(
        authConnection: AuthConnection.google,
        mfaLevel: MFALevel.NONE,
      )
    );
    
    print('User logged in: ${response.userInfo?.email}');
  } catch (e) {
    print('Login error: $e');
  }
}

// Logout
Future<void> logout() async {
  await Web3AuthFlutter.logout();
}

3. Get Solana Private Key (Ed25519)

// IMPORTANT: For Solana, use getEd25519PrivateKey(), not getPrivateKey()
Future<String> getSolanaPrivateKey() async {
  final privateKeyHex = await Web3AuthFlutter.getEd25519PrivateKey();
  return privateKeyHex;
}

4. Create Solana Wallet

import 'package:solana/solana.dart';
import 'dart:typed_data';

Future<Ed25519HDKeyPair> getSolanaKeyPair() async {
  // Get Ed25519 private key from Web3Auth
  final privateKeyHex = await Web3AuthFlutter.getEd25519PrivateKey();
  
  // Convert hex to bytes
  final privateKeyBytes = Uint8List.fromList(
    List<int>.generate(
      privateKeyHex.length ~/ 2,
      (i) => int.parse(privateKeyHex.substring(i * 2, i * 2 + 2), radix: 16),
    ),
  );
  
  // Create KeyPair
  final keyPair = await Ed25519HDKeyPair.fromPrivateKeyBytes(
    privateKey: privateKeyBytes,
  );
  
  return keyPair;
}

5. Get Solana Address

Future<String> getSolanaAddress() async {
  final keyPair = await getSolanaKeyPair();
  return keyPair.address;
}

6. Get SOL Balance

import 'package:solana/solana.dart';

Future<double> getBalance() async {
  final keyPair = await getSolanaKeyPair();
  
  // Create Solana client
  final client = SolanaClient(
    rpcUrl: Uri.parse(SolanaConfig.rpcUrl),
    websocketUrl: Uri.parse(
      SolanaConfig.rpcUrl.replaceFirst('https://', 'wss://').replaceFirst('http://', 'ws://'),
    ),
  );
  
  // Get balance
  final balance = await client.rpcClient.getBalance(keyPair.address);
  
  // Convert lamports to SOL (1 SOL = 1,000,000,000 lamports)
  final solBalance = balance.value / 1e9;
  
  return solBalance;
}

7. Request Airdrop (Devnet/Testnet Only)

Future<String> requestAirdrop() async {
  final keyPair = await getSolanaKeyPair();
  
  final client = SolanaClient(
    rpcUrl: Uri.parse(SolanaConfig.devnetRpcUrl),
    websocketUrl: Uri.parse('wss://api.devnet.solana.com'),
  );
  
  // Request 1 SOL airdrop
  final signature = await client.rpcClient.requestAirdrop(
    address: keyPair.address,
    lamports: 1000000000, // 1 SOL
  );
  
  print('Airdrop signature: $signature');
  return signature;
}

8. Send SOL Transaction

Future<String> sendSol(String recipientAddress, double amount) async {
  final keyPair = await getSolanaKeyPair();
  
  final client = SolanaClient(
    rpcUrl: Uri.parse(SolanaConfig.rpcUrl),
    websocketUrl: Uri.parse(
      SolanaConfig.rpcUrl.replaceFirst('https://', 'wss://').replaceFirst('http://', 'ws://'),
    ),
  );
  
  // Convert SOL to lamports
  final lamports = (amount * 1e9).toInt();
  
  // Create and send transaction
  final signature = await client.transferLamports(
    source: keyPair,
    destination: Ed25519HDPublicKey.fromBase58(recipientAddress),
    lamports: lamports,
  );
  
  print('Transaction signature: $signature');
  return signature;
}

9. Get SPL Token Balance

import 'package:solana/solana.dart';

Future<double> getTokenBalance(String tokenMintAddress) async {
  final keyPair = await getSolanaKeyPair();
  
  final client = SolanaClient(
    rpcUrl: Uri.parse(SolanaConfig.rpcUrl),
    websocketUrl: Uri.parse(
      SolanaConfig.rpcUrl.replaceFirst('https://', 'wss://').replaceFirst('http://', 'ws://'),
    ),
  );
  
  // Get token accounts for the wallet
  final tokenAccounts = await client.rpcClient.getTokenAccountsByOwner(
    keyPair.address,
    TokenAccountsFilter.byMint(tokenMintAddress),
    encoding: Encoding.jsonParsed,
  );
  
  if (tokenAccounts.value.isEmpty) {
    return 0.0;
  }
  
  // Parse token balance
  final accountInfo = tokenAccounts.value.first.account.data;
  if (accountInfo is ParsedAccountData) {
    final tokenAmount = accountInfo.parsed['info']['tokenAmount'];
    return double.parse(tokenAmount['uiAmountString']);
  }
  
  return 0.0;
}

10. Get All NFTs (Metaplex)

Future<List<String>> getNFTs() async {
  final keyPair = await getSolanaKeyPair();
  
  final client = SolanaClient(
    rpcUrl: Uri.parse(SolanaConfig.rpcUrl),
    websocketUrl: Uri.parse(
      SolanaConfig.rpcUrl.replaceFirst('https://', 'wss://').replaceFirst('http://', 'ws://'),
    ),
  );
  
  // Get all token accounts
  final tokenAccounts = await client.rpcClient.getTokenAccountsByOwner(
    keyPair.address,
    TokenAccountsFilter.byProgramId(TokenProgram.programId),
    encoding: Encoding.jsonParsed,
  );
  
  // Filter for NFTs (amount = 1, decimals = 0)
  List<String> nftMints = [];
  
  for (var account in tokenAccounts.value) {
    if (account.account.data is ParsedAccountData) {
      final data = account.account.data as ParsedAccountData;
      final tokenAmount = data.parsed['info']['tokenAmount'];
      
      if (tokenAmount['decimals'] == 0 && 
          tokenAmount['amount'] == '1') {
        // This is likely an NFT
        nftMints.add(data.parsed['info']['mint']);
      }
    }
  }
  
  return nftMints;
}

πŸ”’ Security Considerations

  • Ed25519 Keys: Solana uses Ed25519 curve, not secp256k1 - use getEd25519PrivateKey()
  • Non-Custodial: Private keys managed using Shamir Secret Sharing
  • Network Consistency: Never change Client ID or Sapphire network in production
  • Devnet vs Mainnet: Always test on devnet before deploying to mainnet
  • RPC Rate Limits: Use dedicated RPC providers (Alchemy, QuickNode, Helius) for production
  • Transaction Fees: Ensure wallet has sufficient SOL for transaction fees

πŸ› οΈ Troubleshooting

Common Issues

Problem: Different wallet address than expected

Solution:

  • Ensure you're using getEd25519PrivateKey() not getPrivateKey()
  • Verify you're using the same Client ID and network

Problem: Airdrop fails

Solutions:

  • Airdrops only work on devnet/testnet
  • Rate limits: Wait a few minutes between airdrop requests
  • Use alternative devnet/testnet faucets

Problem: Transaction fails with "insufficient funds"

Solutions:

  • Check SOL balance for transaction fees (~0.000005 SOL per signature)
  • Request devnet airdrop or transfer SOL from another wallet

Problem: Token balance shows 0 but tokens exist

Solutions:

  • Verify token mint address is correct
  • Check that token account exists for the wallet
  • Ensure using correct Solana network (devnet vs mainnet)

Problem: RPC errors or slow responses

Solutions:

  • Use dedicated RPC provider (Alchemy, QuickNode, Helius) instead of public endpoints
  • Implement retry logic with exponential backoff
  • Check RPC provider status page

πŸ“š Resources

Documentation

SDK & Packages

Solana Tools

Community & Support

🀝 Support

Need help? Reach out through:

πŸ“„ License

This example is available under the MIT License. See the LICENSE file for more info.