The Para Swift SDK provides support for Solana blockchain operations through the ParaSolanaSigner class. This guide demonstrates how to perform common operations such as signing messages, signing transactions, and sending transactions on Solana.

Some Solana operations may require additional setup or have limited support in the current bridge implementation. Check the specific method documentation for availability.

Getting Started

To work with Solana, you’ll need:

  1. An initialized ParaManager instance
  2. A wallet with Solana capabilities
  3. An RPC URL for Solana network (mainnet, devnet, or testnet)

Initializing the Solana Signer

First, create an instance of ParaSolanaSigner by providing your Para manager, an RPC URL, and optionally a wallet ID:

import ParaSwift
import SolanaSwift

// Create the Solana signer
let paraSolanaSigner = try ParaSolanaSigner(
    paraManager: paraManager, 
    rpcUrl: "https://api.devnet.solana.com", // or mainnet: https://api.mainnet-beta.solana.com
    walletId: wallet.id // Optional, can be selected later
)

Selecting a Wallet

If you didn’t specify a wallet ID when creating the signer, or if you want to switch to a different wallet, use the selectWallet method:

// Select a wallet for signing
try await paraSolanaSigner.selectWallet(walletId: wallet.id)

Getting Wallet Information

Getting Public Key

// Get the public key for the current wallet
let publicKey = try await paraSolanaSigner.getPublicKey()
print("Wallet address: \(publicKey.base58EncodedString)")

Getting Balance

do {
    // Get balance in lamports (1 SOL = 1,000,000,000 lamports)
    let balance = try await paraSolanaSigner.getBalance()
    let solBalance = Double(balance) / 1_000_000_000
    print("Balance: \(solBalance) SOL")
} catch ParaSolanaSignerError.bridgeError(let message) {
    print("Balance fetching not yet supported: \(message)")
}

Signing Messages

Para makes it easy to sign arbitrary data with your Solana wallet:

// Sign arbitrary bytes
let message = "Hello, Solana!"
let messageData = message.data(using: .utf8)!
let signature = try await paraSolanaSigner.signBytes(messageData)

// Alternatively, you can use the ParaManager directly
let signature2 = try await paraManager.signMessage(
    walletId: wallet.id, 
    message: message // SDK handles Base64 encoding internally
)

Working with Transactions

Creating a Solana Transaction

Para uses the SolanaTransaction model to represent transactions:

// Create a simple transfer transaction
let recipientAddress = "11111111111111111111111111111112" // System program for demo
let amountInLamports: UInt64 = 1_000_000 // 0.001 SOL

let transaction = SolanaTransaction(
    to: recipientAddress,
    lamports: amountInLamports,
    feePayer: nil, // Will use current wallet as fee payer
    recentBlockhash: nil // Will be fetched automatically
)

Signing a Transaction

To sign a transaction without broadcasting it to the network:

do {
    let signedTransactionB64 = try await paraSolanaSigner.signTransaction(transaction)
    print("Signed transaction (Base64): \(signedTransactionB64)")
} catch ParaSolanaSignerError.bridgeError(let message) {
    print("Transaction signing not yet supported: \(message)")
}

Sending a Transaction

To sign and broadcast a transaction in a single step:

do {
    let transactionSignature = try await paraSolanaSigner.sendTransaction(transaction)
    print("Transaction signature: \(transactionSignature)")
} catch ParaSolanaSignerError.bridgeError(let message) {
    print("Transaction sending not yet supported: \(message)")
}

Error Handling

The ParaSolanaSigner provides specific error types for better error handling:

do {
    try await paraSolanaSigner.signTransaction(transaction)
} catch ParaSolanaSignerError.missingWalletId {
    print("No wallet selected. Call selectWallet() first.")
} catch ParaSolanaSignerError.invalidWalletType {
    print("Selected wallet is not a Solana wallet.")
} catch ParaSolanaSignerError.invalidWalletAddress {
    print("Wallet address is invalid.")
} catch ParaSolanaSignerError.signingFailed(let underlyingError) {
    print("Signing failed: \(underlyingError?.localizedDescription ?? "Unknown error")")
} catch ParaSolanaSignerError.networkError(let underlyingError) {
    print("Network error: \(underlyingError?.localizedDescription ?? "Unknown error")")
} catch ParaSolanaSignerError.bridgeError(let message) {
    print("Bridge operation failed: \(message)")
} catch {
    print("Unexpected error: \(error)")
}

Working with Different Networks

Para supports Solana mainnet, devnet, and testnet. Simply update the RPC URL to target different networks:

// Solana Mainnet
let mainnetSigner = try ParaSolanaSigner(
    paraManager: paraManager,
    rpcUrl: "https://api.mainnet-beta.solana.com",
    walletId: wallet.id
)

// Solana Devnet
let devnetSigner = try ParaSolanaSigner(
    paraManager: paraManager,
    rpcUrl: "https://api.devnet.solana.com",
    walletId: wallet.id
)

// Solana Testnet
let testnetSigner = try ParaSolanaSigner(
    paraManager: paraManager,
    rpcUrl: "https://api.testnet.solana.com",
    walletId: wallet.id
)

Complete Example

Here’s a complete example showing how to use the Solana signer:

import SwiftUI
import ParaSwift
import SolanaSwift

struct SolanaWalletView: View {
    @EnvironmentObject var paraManager: ParaManager
    @State private var balance: UInt64 = 0
    @State private var publicKey: String = ""
    @State private var isLoading = false
    @State private var errorMessage: String?
    
    var wallet: Wallet // Assume this is passed in
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Solana Wallet")
                .font(.title)
            
            if !publicKey.isEmpty {
                Text("Address: \(publicKey)")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
            
            Text("Balance: \(Double(balance) / 1_000_000_000, specifier: "%.4f") SOL")
                .font(.headline)
            
            Button("Refresh Balance") {
                refreshBalance()
            }
            .disabled(isLoading)
            
            Button("Sign Message") {
                signTestMessage()
            }
            .disabled(isLoading)
            
            if let errorMessage = errorMessage {
                Text(errorMessage)
                    .foregroundColor(.red)
                    .font(.caption)
            }
        }
        .padding()
        .onAppear {
            refreshBalance()
        }
    }
    
    private func refreshBalance() {
        isLoading = true
        errorMessage = nil
        
        Task {
            do {
                let signer = try ParaSolanaSigner(
                    paraManager: paraManager,
                    rpcUrl: "https://api.devnet.solana.com",
                    walletId: wallet.id
                )
                
                let pubKey = try await signer.getPublicKey()
                publicKey = pubKey.base58EncodedString
                
                do {
                    balance = try await signer.getBalance()
                } catch ParaSolanaSignerError.bridgeError {
                    // Balance fetching not yet supported
                    print("Balance fetching not yet supported in bridge")
                }
                
            } catch {
                errorMessage = error.localizedDescription
            }
            
            isLoading = false
        }
    }
    
    private func signTestMessage() {
        isLoading = true
        errorMessage = nil
        
        Task {
            do {
                let signer = try ParaSolanaSigner(
                    paraManager: paraManager,
                    rpcUrl: "https://api.devnet.solana.com",
                    walletId: wallet.id
                )
                
                let message = "Hello from Para + Solana!"
                let messageData = message.data(using: .utf8)!
                let signature = try await signer.signBytes(messageData)
                
                print("Message signed successfully. Signature length: \(signature.count) bytes")
                
            } catch {
                errorMessage = error.localizedDescription
            }
            
            isLoading = false
        }
    }
}

Example Integration

Notes and Limitations

  • Some bridge operations (like getBalance, signTransaction, sendTransaction) may not be fully implemented yet
  • The SDK will throw ParaSolanaSignerError.bridgeError for unsupported operations
  • Message signing with signBytes is fully supported
  • Always handle errors appropriately and provide fallbacks for unsupported operations