Introduction

This guide demonstrates how to integrate Para with Solana in your Flutter applications. Para acts as a secure signer for your Solana transactions, while the Solana Dart package handles the blockchain interactions.

Para’s approach to Solana transaction signing is designed for flexibility and security:

  • Para signs the raw transaction bytes without modifying the transaction data
  • Your application maintains full control over transaction construction
  • Your private keys remain secure in Para’s wallet infrastructure

Prerequisites

Before you begin, ensure you have:

  1. Para SDK set up in your Flutter project (see the Setup Guide)
  2. A user authenticated with Para
  3. Solana Dart package installed in your project
flutter pub add solana

Setting Up the Para Solana Signer

The Para SDK provides a Solana signer that works with the Solana Dart package. First, let’s create the necessary components:

import 'package:para/para.dart';
import 'package:solana/solana.dart' as web3;

// Initialize Para and get wallet (assuming user is already authenticated)
final para = Para(
  environment: Environment.beta,
  apiKey: 'YOUR_API_KEY',
);

// Set up Solana client with the desired network
final devnetRpcUrl = Uri.parse('https://api.devnet.solana.com');
final devnetWsUrl = Uri.parse('wss://api.devnet.solana.com');
final solanaClient = web3.SolanaClient(
  rpcUrl: devnetRpcUrl,
  websocketUrl: devnetWsUrl,
);

// Initialize Para Solana signer
final solanaSigner = ParaSolanaWeb3Signer(
  para: para,
  solanaClient: solanaClient,
);

Sending Transactions

After signing a transaction, you can send it to the Solana network:

Future<String> sendSolanaTransaction(
  Wallet wallet,
  String recipientAddress,
  double amountInSol,
) async {
  // Convert wallet address to Solana public key
  final publicKey = web3.Ed25519HDPublicKey.fromBase58(wallet.address!);
  
  // Convert recipient address to Solana public key
  final recipient = web3.Ed25519HDPublicKey.fromBase58(recipientAddress);
  
  // Get the latest blockhash (required for Solana transactions)
  final blockhash = (await solanaClient.rpcClient.getLatestBlockhash()).value;
  
  // Convert SOL amount to lamports (Solana's smallest unit)
  final lamports = (web3.lamportsPerSol * amountInSol).toInt();
  
  // Create a transfer instruction
  final instruction = web3.SystemInstruction.transfer(
    fundingAccount: publicKey,
    recipientAccount: recipient,
    lamports: lamports,
  );
  
  // Create and compile the message
  final message = web3.Message(instructions: [instruction]);
  final compiledMessage = message.compile(
    recentBlockhash: blockhash.blockhash,
    feePayer: publicKey,
  );
  
  // Sign the transaction using Para
  final signedTransaction = await solanaSigner.signTransaction(compiledMessage);
  
  // Send the signed transaction to the network
  final signature = await solanaSigner.sendTransaction(signedTransaction);
  
  return signature;
}

Checking Transaction Status

After sending a transaction, you may want to check its status:

Future<bool> confirmTransaction(String signature) async {
  try {
    final confirmation = await solanaClient.rpcClient
        .confirmTransaction(signature, commitment: web3.Commitment.confirmed);
    
    return confirmation.value;
  } catch (e) {
    return false;
  }
}

Working with SPL Tokens

Para can also sign transactions involving SPL tokens (Solana’s token standard). Here’s an example of transferring an SPL token:

import 'package:solana/solana.dart' as web3;
import 'package:solana/token_program.dart' as token_program;

Future<String> transferSplToken(
  Wallet wallet,
  String recipientAddress,
  String tokenMintAddress,
  double amount,
  int decimals,
) async {
  // Convert wallet address to Solana public key
  final publicKey = web3.Ed25519HDPublicKey.fromBase58(wallet.address!);
  
  // Convert recipient address to Solana public key
  final recipient = web3.Ed25519HDPublicKey.fromBase58(recipientAddress);
  
  // Convert token mint address to Solana public key
  final tokenMint = web3.Ed25519HDPublicKey.fromBase58(tokenMintAddress);
  
  // Get the latest blockhash
  final blockhash = (await solanaClient.rpcClient.getLatestBlockhash()).value;
  
  // Find the token account addresses for sender and recipient
  final senderTokenAccount = await token_program.findAssociatedTokenAddress(
    owner: publicKey,
    mint: tokenMint,
  );
  
  final recipientTokenAccount = await token_program.findAssociatedTokenAddress(
    owner: recipient,
    mint: tokenMint,
  );
  
  // Check if recipient token account exists, if not create it
  final instructions = <web3.Instruction>[];
  final accounts = await solanaClient.rpcClient
      .getTokenAccountsByOwner(
        recipient.toBase58(),
        const web3.TokenAccountsFilter.byMint(tokenMint),
      );
      
  final recipientTokenAccountExists = accounts.value.any(
    (account) => account.pubkey == recipientTokenAccount,
  );
  
  if (!recipientTokenAccountExists) {
    instructions.add(
      token_program.createAssociatedTokenAccountInstruction(
        associatedToken: recipientTokenAccount,
        payer: publicKey,
        owner: recipient,
        mint: tokenMint,
      ),
    );
  }
  
  // Convert token amount accounting for decimals
  final tokenAmount = (amount * (10 * decimals)).toInt();
  
  // Add token transfer instruction
  instructions.add(
    token_program.transferInstruction(
      source: senderTokenAccount,
      destination: recipientTokenAccount,
      owner: publicKey,
      amount: tokenAmount,
    ),
  );
  
  // Create and compile the message
  final message = web3.Message(instructions: instructions);
  final compiledMessage = message.compile(
    recentBlockhash: blockhash.blockhash,
    feePayer: publicKey,
  );
  
  // Sign the transaction using Para
  final signedTransaction = await solanaSigner.signTransaction(compiledMessage);
  
  // Send the signed transaction
  final signature = await solanaSigner.sendTransaction(signedTransaction);
  
  return signature;
}

Best Practices

When using Para with Solana:

  1. Always verify transaction data before signing: Para signs whatever you provide, so ensure transaction data is correct
  2. Use appropriate commitment levels: For important transactions, wait for “confirmed” or “finalized” commitment
  3. Handle network congestion: Solana transactions can fail during network congestion, implement appropriate retry mechanisms
  4. Check token accounts: Always verify token accounts exist before sending SPL tokens
  5. Test on devnet first: Always test your integration on Solana devnet before moving to mainnet

Example

For an example of signing with Solana transactions using para and the Solana Dart package, check out the following code example:

Resources

For more information about using the Solana Dart package, refer to the official documentation: