Skip to main content

Overview

This guide covers sending SOL, building transactions, and signing transactions on Solana.

Prerequisites

Send SOL

import DynamicSDKSwift
import SolanaWeb3

let sdk = DynamicSDK.instance()

func sendSOL(
    wallet: BaseWallet,
    recipient: String,
    amountInSOL: Double
) async throws -> String {
    // Create connection and signer
    let connection = try sdk.solana.createConnection()
    let signer = sdk.solana.createSigner(wallet: wallet)

    // Get latest blockhash
    let blockhash = try await connection.getLatestBlockhash()

    // Convert SOL to lamports (1 SOL = 10^9 lamports)
    let lamports = UInt64(amountInSOL * 1_000_000_000)

    // Create versioned transfer transaction using the builder
    let transaction = try SolanaWeb3.SolanaTransactionBuilder.createVersionedTransferTransaction(
        from: wallet.address,
        to: recipient,
        lamports: lamports,
        recentBlockhash: blockhash.blockhash
    )

    // Sign and send
    return try await signer.signAndSendEncodedTransaction(
        base64Transaction: transaction.serializeToBase64()
    )
}
SolanaWeb3 Dependency: Add the SolanaWeb3 package from solana-mobile/SolanaSwift to build Solana transactions.

Sign Transaction

Sign a base64-encoded transaction without sending:
let sdk = DynamicSDK.instance()

func signTransaction(wallet: BaseWallet, base64Transaction: String) async throws -> String {
    let signer = sdk.solana.createSigner(wallet: wallet)
    return try await signer.signEncodedTransaction(base64Transaction: base64Transaction)
}

Sign and Send Pre-Built Transaction

If you already have a base64-encoded transaction:
let sdk = DynamicSDK.instance()

func signAndSendTransaction(wallet: BaseWallet, base64Transaction: String) async throws -> String {
    let signer = sdk.solana.createSigner(wallet: wallet)
    return try await signer.signAndSendEncodedTransaction(base64Transaction: base64Transaction)
}

Complete Send SOL View

import SwiftUI
import DynamicSDKSwift
import SolanaWeb3

struct SolanaSendView: View {
    let wallet: BaseWallet

    @State private var recipient = ""
    @State private var amount = ""
    @State private var signature: String?
    @State private var isLoading = false
    @State private var error: String?

    private let sdk = DynamicSDK.instance()

    var body: some View {
        VStack(spacing: 16) {
            Text("From: \(formatAddress(wallet.address))")
                .font(.caption)
                .foregroundColor(.secondary)

            TextField("Recipient Address", text: $recipient)
                .textFieldStyle(.roundedBorder)
                .autocapitalization(.none)

            TextField("Amount (SOL)", text: $amount)
                .textFieldStyle(.roundedBorder)
                .keyboardType(.decimalPad)

            Button("Send SOL") { sendTransaction() }
                .buttonStyle(.borderedProminent)
                .disabled(recipient.isEmpty || amount.isEmpty || isLoading)

            if isLoading {
                ProgressView("Sending...")
            }

            if let signature = signature {
                VStack(spacing: 8) {
                    Text("Success!")
                        .foregroundColor(.green)
                        .fontWeight(.semibold)

                    Text(signature)
                        .font(.caption)
                        .lineLimit(1)

                    Link("View on Explorer",
                         destination: URL(string: "https://explorer.solana.com/tx/\(signature)?cluster=devnet")!)
                        .font(.caption)
                }
                .padding()
                .background(Color.green.opacity(0.1))
                .cornerRadius(8)
            }

            if let error = error {
                Text(error)
                    .foregroundColor(.red)
                    .font(.caption)
            }
        }
        .padding()
        .navigationTitle("Send SOL")
    }

    private func formatAddress(_ address: String) -> String {
        "\(address.prefix(6))...\(address.suffix(4))"
    }

    private func sendTransaction() {
        guard let amountDouble = Double(amount) else {
            error = "Invalid amount"
            return
        }

        isLoading = true
        error = nil
        signature = nil

        Task { @MainActor in
            do {
                // Create connection and signer
                let connection = try sdk.solana.createConnection()
                let signer = sdk.solana.createSigner(wallet: wallet)

                // Get latest blockhash
                let blockhash = try await connection.getLatestBlockhash()

                // Convert SOL to lamports
                let lamports = UInt64(amountDouble * 1_000_000_000)

                // Create versioned transfer transaction using the builder
                let transaction = try SolanaWeb3.SolanaTransactionBuilder.createVersionedTransferTransaction(
                    from: wallet.address,
                    to: recipient,
                    lamports: lamports,
                    recentBlockhash: blockhash.blockhash
                )

                // Sign and send
                signature = try await signer.signAndSendEncodedTransaction(
                    base64Transaction: transaction.serializeToBase64()
                )
            } catch {
                self.error = error.localizedDescription
            }
            isLoading = false
        }
    }
}

Lamports Conversion

Solana uses lamports as its smallest unit (1 SOL = 10^9 lamports):
// Convert SOL to lamports
func solToLamports(_ sol: Double) -> UInt64 {
    return UInt64(sol * 1_000_000_000)
}

// Convert lamports to SOL
func lamportsToSol(_ lamports: UInt64) -> Double {
    return Double(lamports) / 1_000_000_000
}

// Usage
let amount = 1.5 // SOL
let lamports = solToLamports(amount)
print("\(amount) SOL = \(lamports) lamports")
func explorerURL(signature: String, cluster: String = "devnet") -> URL? {
    URL(string: "https://explorer.solana.com/tx/\(signature)?cluster=\(cluster)")
}

// Usage
if let url = explorerURL(signature: txSignature, cluster: "mainnet-beta") {
    // Open in browser or display link
}

Signer Methods

MethodDescription
createSigner(wallet:)Create a signer for a Solana wallet
signMessage(message:)Sign an arbitrary message
signEncodedTransaction(base64Transaction:)Sign a transaction without sending
signAndSendEncodedTransaction(base64Transaction:)Sign and broadcast a transaction

Error Handling

do {
    let signature = try await sendSOL(
        wallet: wallet,
        recipient: recipient,
        amountInSOL: 1.0
    )
} catch {
    let errorDesc = error.localizedDescription.lowercased()

    if errorDesc.contains("insufficient") {
        print("Insufficient balance")
    } else if errorDesc.contains("blockhash") {
        print("Blockhash expired, try again")
    } else {
        print("Transaction failed: \(error)")
    }
}

Devnet Faucet

For testing, get free devnet SOL from the Solana Faucet.

Next Steps