Overview

This guide outlines how to integrate and use external cryptocurrency wallets with the ParaSwift SDK. With the unified wallet architecture, the Swift SDK has removed all blockchain-specific dependencies and now provides a streamlined approach to external wallet connectivity. The SDK supports external wallets like MetaMask through deep-linking protocols and provides built-in authentication and transaction signing capabilities. You can also use external wallet addresses to authenticate with Para.
Unified Wallet Architecture: The Swift SDK v2 has removed all blockchain-specific packages (web3swift, solana-swift, etc.) and now uses only BigInt for handling blockchain values. This provides a cleaner, more maintainable architecture while still supporting all necessary external wallet operations.The SDK now has minimal dependencies:
  • BigInt: For handling large blockchain numbers (Wei, gas values, etc.)
  • PhoneNumberKit: For phone number validation in authentication flows

Deep Linking

The ParaSwift SDK communicates with other apps via deep linking. To enable deep linking for your application, you’ll need to configure your app appropriately.

Configure URL Schemes

First, you need to configure your app to handle custom URL schemes:
  1. Open your app’s Info.plist file
  2. Add a new entry for LSApplicationQueriesSchemes as an array
  3. Add the URL schemes of supported wallets as strings to this array
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>metamask</string>
</array>

Configure URL Types

Next, you need to set up URL Types to handle callbacks from external wallets:
  1. In Xcode, select your app target
  2. Go to “Info” tab
  3. Expand “URL Types”
  4. Click the ”+” button to add a new URL Type
  5. Set the “Identifier” to your app’s bundle identifier
  6. Set the “URL Schemes” to a unique identifier for your app
Important: You must use a unique URL scheme to avoid conflicts with other apps. We recommend using reverse-domain notation like com.yourcompany.yourapp.In our examples we use paraswift, but you must replace this with your own unique scheme:
<!-- In Info.plist -->
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <!-- Replace with your unique scheme -->
      <string>com.yourcompany.yourapp</string>
    </array>
  </dict>
</array>

MetaMask Connector

Setup

Create and initialize the MetaMask connector with your app’s configuration:
import ParaSwift

// Create MetaMask configuration
let appScheme = "com.yourcompany.yourapp" // Replace with your unique scheme
let metaMaskConfig = MetaMaskConfig(
    appName: "Your App Name",
    appId: appScheme,
    apiVersion: "1.0"
)

// Initialize the connector
let metaMaskConnector = MetaMaskConnector(
    para: paraManager,
    appUrl: "https://\(appScheme)",
    config: metaMaskConfig
)
Handle incoming deep links from MetaMask by adding a URL handler in your SwiftUI app:
@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    // Handle MetaMask deep links with URL validation
                    if url.scheme == "yourapp", url.host == "mmsdk" {
                        MetaMaskConnector.handleDeepLink(url)
                    }
                }
        }
    }
}
The deep link handling has been simplified to use a static method. You no longer need to maintain a MetaMaskConnector instance at the app level just for handling deep links.

Connecting

To connect to MetaMask and retrieve the user’s accounts:
do {
    try await metaMaskConnector.connect()
    // MetaMask will automatically attempt Para authentication after connection
    // Access connected accounts via metaMaskConnector.accounts
} catch {
    // Handle connection error
}
Automatic Authentication: The MetaMask connector automatically attempts to authenticate with Para after a successful connection. This streamlines the user experience by reducing the number of manual steps required.

Sign Message

To request a signature for a message from MetaMask:
guard let account = metaMaskConnector.accounts.first else { return }

do {
    let signature = try await metaMaskConnector.signMessage(
        "Message to sign! Hello World",
        account: account
    )
    // Use the signature
} catch {
    // Handle signing error
}

Send Transaction

To send a transaction through MetaMask using the EVMTransaction model:
import BigInt

guard let account = metaMaskConnector.accounts.first else { return }

do {
    // Convert 0.001 ETH to wei (1 ETH = 10^18 wei)
    let valueInWei = BigUInt("1000000000000000")! // 0.001 ETH in wei
    let gasLimit = BigUInt(100000)
    
    let transaction = EVMTransaction(
        to: "0x13158486860B81Dee9e43Dd0391e61c2F82B577F",
        value: valueInWei,
        gasLimit: gasLimit
    )
    
    let txHash = try await metaMaskConnector.sendTransaction(transaction, account: account)
    // Transaction sent successfully, use txHash
} catch {
    // Handle transaction error
}

Alternative: Raw Transaction Format

You can also use a raw transaction dictionary format:
guard let account = metaMaskConnector.accounts.first else { return }

let transaction: [String: String] = [
    "from": account,
    "to": "0x13158486860B81Dee9e43Dd0391e61c2F82B577F",
    "value": "0x38D7EA4C68000", // 0.001 ETH in wei (hex)
    "gasLimit": "0x186A0" // 100000 in hex
]

do {
    let txHash = try await metaMaskConnector.sendTransaction(
        transaction,
        account: account
    )
    // Transaction sent successfully
} catch {
    // Handle transaction error
}

Properties

The MetaMask connector provides several useful properties:
// Check if connected to MetaMask
let isConnected = metaMaskConnector.isConnected

// Get list of connected accounts
let accounts = metaMaskConnector.accounts

// Get current chain ID (e.g., "0x1" for Ethereum mainnet)
let chainId = metaMaskConnector.chainId

Supported Networks

The MetaMask connector works with any EVM-compatible network that MetaMask supports. Common networks include:
NetworkChain IDDescription
Ethereum Mainnet0x1Main Ethereum network
Sepolia Testnet0xaa36a7Ethereum test network
Polygon0x89Polygon mainnet
Arbitrum One0xa4b1Arbitrum Layer 2
Base0x2105Base Layer 2

Working with BigUInt Values

Since the Swift SDK now uses BigInt for handling blockchain values, here are some utility patterns for working with Ether and Wei conversions:
import BigInt

// Convert Ether to Wei (multiply by 10^18)
func etherToWei(_ ether: String) -> BigUInt? {
    guard let etherDecimal = Decimal(string: ether) else { return nil }
    let weiDecimal = etherDecimal * pow(10, 18)
    return BigUInt(weiDecimal.description.components(separatedBy: ".").first ?? "")
}

// Convert Wei to Ether (divide by 10^18)
func weiToEther(_ wei: BigUInt) -> String {
    let weiDecimal = Decimal(string: wei.description) ?? 0
    let etherDecimal = weiDecimal / pow(10, 18)
    return etherDecimal.description
}

// Usage examples
let oneEthInWei = etherToWei("1.0")! // 1000000000000000000
let halfEthInWei = etherToWei("0.5")! // 500000000000000000
let pointZeroOneEthInWei = etherToWei("0.01")! // 10000000000000000

// Convert back to Ether
let ethAmount = weiToEther(BigUInt("1000000000000000000")!) // "1"
These utility functions help you convert between human-readable Ether amounts and the Wei values required by the blockchain. Always validate decimal inputs to prevent runtime crashes.

Advanced Configuration

Customizing MetaMask Connection

You can customize various aspects of the MetaMask connection by modifying the MetaMaskConfig:
let metaMaskConfig = MetaMaskConfig(
    appName: "Your App Name",
    appId: bundleId,
    apiVersion: "1.0"
)

Working with Different Networks

MetaMask supports multiple networks. You can check the current network and adjust your app’s behavior accordingly:
switch metaMaskConnector.chainId {
case "0x1":
    // Ethereum Mainnet
case "0x5":
    // Goerli Testnet
case "0x89":
    // Polygon
default:
    // Other network
}

External Wallet Authentication with Para

Para supports authentication using external wallets like MetaMask. This allows users to log into Para using their existing wallet credentials.

Using External Wallet for Para Login

After connecting to MetaMask, you can use the wallet address to authenticate with Para:
// First, connect to MetaMask
try await metaMaskConnector.connect()

// Get the first connected account
guard let account = metaMaskConnector.accounts.first else {
    throw ParaError.error("No MetaMask accounts found")
}

// Create external wallet info for Para authentication
let externalWallet = ExternalWalletInfo(
    address: account,
    type: .evm,
    provider: "metamask",
    isConnectionOnly: false // Set to false for full authentication
)

// Login to Para using the external wallet
try await paraManager.loginExternalWallet(wallet: externalWallet)

// User is now logged into Para with their MetaMask wallet

Complete External Wallet Login Example

Here’s a complete example showing external wallet authentication:
import SwiftUI
import ParaSwift

struct ExternalWalletLoginView: View {
    @EnvironmentObject var paraManager: ParaManager
    @EnvironmentObject var appRootManager: AppRootManager
    @StateObject private var metaMaskConnector: MetaMaskConnector
    
    @State private var isConnecting = false
    @State private var errorMessage: String?
    
    init(paraManager: ParaManager) {
        let config = MetaMaskConfig(
            appName: "Your App",
            appId: "com.yourcompany.yourapp", // Use your unique app scheme
            apiVersion: "1.0"
        )
        
        _metaMaskConnector = StateObject(wrappedValue: MetaMaskConnector(
            para: paraManager,
            appUrl: "https://com.yourcompany.yourapp", // Use your unique app scheme
            config: config
        ))
    }
    
    var body: some View {
        VStack(spacing: 20) {
            Image("metamask")
                .resizable()
                .frame(width: 80, height: 80)
            
            Text("Connect with MetaMask")
                .font(.title2)
                .fontWeight(.semibold)
            
            Text("Use your existing MetaMask wallet to log into Para")
                .font(.body)
                .foregroundColor(.secondary)
                .multilineTextAlignment(.center)
            
            if let errorMessage = errorMessage {
                Text(errorMessage)
                    .foregroundColor(.red)
                    .font(.caption)
                    .padding()
                    .background(Color.red.opacity(0.1))
                    .cornerRadius(8)
            }
            
            Button {
                connectAndLogin()
            } label: {
                if isConnecting {
                    ProgressView()
                        .tint(.white)
                } else {
                    Text("Connect MetaMask")
                        .fontWeight(.semibold)
                }
            }
            .frame(maxWidth: .infinity)
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            .disabled(isConnecting)
        }
        .padding()
        .navigationTitle("External Wallet")
    }
    
    private func connectAndLogin() {
        isConnecting = true
        errorMessage = nil
        
        Task {
            do {
                // Connect to MetaMask
                try await metaMaskConnector.connect()
                
                // Get the first account
                guard let account = metaMaskConnector.accounts.first else {
                    throw ParaError.error("No MetaMask accounts found")
                }
                
                // Create external wallet info
                let externalWallet = ExternalWalletInfo(
                    address: account,
                    type: .evm,
                    provider: "metamask",
                    isConnectionOnly: false // Set to false for full authentication
                )
                
                // Login to Para using external wallet
                try await paraManager.loginExternalWallet(wallet: externalWallet)
                
                // Navigate to home on success
                appRootManager.currentRoot = .home
                
            } catch {
                errorMessage = error.localizedDescription
            }
            
            isConnecting = false
        }
    }
}