Overview

Para v2 supports authentication with external wallets, allowing users to connect their existing MetaMask, Phantom, or other wallets to authenticate with your Flutter application. This guide covers how to implement external wallet authentication and interaction.

Prerequisites

Before implementing external wallet support, ensure you have:

  1. Para SDK set up in your Flutter project (see the Setup Guide)
  2. Deep linking configured for your app
  3. Target external wallet apps installed on the device

External wallet authentication requires deep linking to redirect users back to your app after wallet interaction. Configure this in both iOS and Android.

Android Configuration

Add an intent filter to your android/app/src/main/AndroidManifest.xml:

<activity
    android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
    android:exported="true">
    <intent-filter android:label="flutter_web_auth_2">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="yourapp" />
    </intent-filter>
</activity>

iOS Configuration

Add your URL scheme to ios/Runner/Info.plist:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleTypeRole</key>
    <string>Editor</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>yourapp</string>
    </array>
  </dict>
</array>

External Wallet Authentication

Para v2 provides a unified method for external wallet authentication:

import 'package:para/para.dart';

Future<void> authenticateWithExternalWallet(
  String externalAddress,
  String walletType,
) async {
  try {
    // Login with external wallet address
    await para.loginExternalWallet(
      externalAddress: externalAddress,
      type: walletType, // "EVM" or "SOLANA"
    );
    
    // Check if authentication was successful
    final isActive = await para.isSessionActive();
    if (isActive) {
      final wallets = await para.fetchWallets();
      print('Authenticated with ${wallets.length} wallets');
    }
  } catch (e) {
    print('External wallet authentication failed: $e');
  }
}

Working with Specific Wallets

Para provides dedicated connectors for popular external wallets:

MetaMask Integration

Para includes a MetaMask connector for EVM interactions:

import 'package:para/src/connectors/para_metamask_connector.dart';

class MetaMaskService {
  late ParaMetaMaskConnector _connector;
  
  void initialize() {
    _connector = ParaMetaMaskConnector(
      para: para,
      appUrl: 'https://yourapp.com',
      deepLink: 'yourapp://callback',
    );
  }
  
  Future<void> connectMetaMask() async {
    try {
      await _connector.connect();
      print('MetaMask connected');
    } catch (e) {
      print('Failed to connect MetaMask: $e');
    }
  }
  
  Future<String> signMessage(String message) async {
    if (_connector.accounts.isEmpty) {
      throw Exception('No accounts connected');
    }
    
    final signature = await _connector.signMessage(
      message,
      _connector.accounts.first,
    );
    
    return signature;
  }
  
  Future<String> sendTransaction({
    required String toAddress,
    required BigInt value,
  }) async {
    if (_connector.accounts.isEmpty) {
      throw Exception('No accounts connected');
    }
    
    final transaction = Transaction(
      from: EthereumAddress.fromHex(_connector.accounts.first),
      to: EthereumAddress.fromHex(toAddress),
      value: EtherAmount.inWei(value),
      maxGas: 100000,
      gasPrice: EtherAmount.inWei(BigInt.from(20000000000)), // 20 Gwei
    );
    
    final txHash = await _connector.sendTransaction(
      transaction,
      _connector.accounts.first,
    );
    
    return txHash;
  }
}

Phantom Integration

Para includes a Phantom connector for Solana interactions:

import 'package:para/src/connectors/para_phantom_connector.dart';
import 'package:solana_web3/solana_web3.dart';

class PhantomService {
  late ParaPhantomConnector _connector;
  
  void initialize() {
    _connector = ParaPhantomConnector(
      para: para,
      appUrl: 'https://yourapp.com', 
      deepLink: 'yourapp://callback',
    );
  }
  
  Future<void> connectPhantom() async {
    try {
      await _connector.connect();
      print('Phantom connected');
    } catch (e) {
      print('Failed to connect Phantom: $e');
    }
  }
  
  Future<String> signMessage(String message) async {
    final signature = await _connector.signMessage(message);
    return signature;
  }
  
  Future<Transaction> signTransaction(Transaction transaction) async {
    final signedTx = await _connector.signTransaction(transaction);
    return signedTx;
  }
  
  Future<String> signAndSendTransaction(Transaction transaction) async {
    final signature = await _connector.signAndSendTransaction(transaction);
    return signature;
  }
}

Example: Complete External Wallet Flow

Here’s a complete example showing external wallet authentication and usage:

import 'package:flutter/material.dart';
import 'package:para/para.dart';
import 'package:para/src/connectors/para_metamask_connector.dart';
import 'package:para/src/connectors/para_phantom_connector.dart';

class ExternalWalletScreen extends StatefulWidget {
  @override
  _ExternalWalletScreenState createState() => _ExternalWalletScreenState();
}

class _ExternalWalletScreenState extends State<ExternalWalletScreen> {
  ParaMetaMaskConnector? _metamaskConnector;
  ParaPhantomConnector? _phantomConnector;
  bool _isConnected = false;
  
  @override
  void initState() {
    super.initState();
    _initializeConnectors();
  }
  
  void _initializeConnectors() {
    _metamaskConnector = ParaMetaMaskConnector(
      para: para,
      appUrl: 'https://yourapp.com',
      deepLink: 'yourapp://callback',
    );
    
    _phantomConnector = ParaPhantomConnector(
      para: para,
      appUrl: 'https://yourapp.com',
      deepLink: 'yourapp://callback', 
    );
  }
  
  Future<void> _connectMetaMask() async {
    try {
      await _metamaskConnector!.connect();
      setState(() => _isConnected = true);
      
      // Check if Para session is active after connection
      final isActive = await para.isSessionActive().future;
      if (isActive) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('MetaMask connected and authenticated with Para!')),
        );
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to connect MetaMask: $e')),
      );
    }
  }
  
  Future<void> _connectPhantom() async {
    try {
      await _phantomConnector!.connect();
      setState(() => _isConnected = true);
      
      // Check if Para session is active after connection
      final isActive = await para.isSessionActive().future;
      if (isActive) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Phantom connected and authenticated with Para!')),
        );
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to connect Phantom: $e')),
      );
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('External Wallets')),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Text(
              'Connect an external wallet to get started',
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            SizedBox(height: 32),
            
            // MetaMask Connection
            ElevatedButton.icon(
              onPressed: _connectMetaMask,
              icon: Icon(Icons.account_balance_wallet),
              label: Text('Connect MetaMask'),
              style: ElevatedButton.styleFrom(
                minimumSize: Size(double.infinity, 50),
              ),
            ),
            SizedBox(height: 16),
            
            // Phantom Connection  
            ElevatedButton.icon(
              onPressed: _connectPhantom,
              icon: Icon(Icons.account_balance_wallet),
              label: Text('Connect Phantom'),
              style: ElevatedButton.styleFrom(
                minimumSize: Size(double.infinity, 50),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Security Considerations

When working with external wallets:

  1. Validate Connections: Always verify the wallet connection before performing operations
  2. Handle Errors Gracefully: External wallet apps may not be installed or might reject connections
  3. User Experience: Provide clear instructions for users on wallet installation and connection
  4. Permissions: Ensure your app requests appropriate permissions for wallet interactions
  5. Deep Link Security: Validate deep link callbacks to prevent malicious redirects

Troubleshooting

Common issues and solutions:

Connection Failures

Future<void> connectWithRetry(VoidCallback connectFunction) async {
  int retries = 3;
  
  while (retries > 0) {
    try {
      await connectFunction();
      return;
    } catch (e) {
      retries--;
      if (retries == 0) {
        throw Exception('Failed to connect after 3 attempts: $e');
      }
      await Future.delayed(Duration(seconds: 2));
    }
  }
}

Wallet App Not Installed

Future<bool> isWalletInstalled(String walletScheme) async {
  try {
    return await canLaunchUrl(Uri.parse('$walletScheme://'));
  } catch (e) {
    return false;
  }
}

Future<void> openWalletInstallPage(String storeUrl) async {
  if (await canLaunchUrl(Uri.parse(storeUrl))) {
    await launchUrl(Uri.parse(storeUrl));
  }
}

Resources

For more information about external wallet integration: