Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Build, sign, and send SOL transactions with blockhash management.

Prerequisites

Send SOL Transaction

import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.BaseWallet
import com.solanaweb3.*
import org.sol4k.PublicKey

val sdk = DynamicSDK.getInstance()

suspend fun sendSOL(
    wallet: BaseWallet,
    recipient: String,
    amountInSOL: Double
): String {
    try {
        val connection = Connection(Cluster.DEVNET)
        val fromPubkey = PublicKey(wallet.address)
        val toPubkey = PublicKey(recipient)
        val lamports = (amountInSOL * 1_000_000_000).toLong()
        val blockhash = connection.getLatestBlockhash()

        val instruction = SystemProgram.transfer(fromPubkey, toPubkey, lamports)
        val transaction = Transaction.v0(fromPubkey, listOf(instruction), blockhash.blockhash)
        val base64Transaction = transaction.serializeUnsignedToBase64()
        val signature = sdk.solana.signAndSendTransaction(base64Transaction, wallet)

        println("Transaction sent!")
        println("Signature: $signature")
        return signature
    } catch (e: Exception) {
        println("Transaction failed: ${e.message}")
        throw e
    }
}

Sign Transaction (Without Sending)

suspend fun signTransactionOnly(wallet: BaseWallet, base64Transaction: String): String {
    try {
        val signedTransaction = sdk.solana.signTransaction(base64Transaction, wallet)
        println("Signed transaction: $signedTransaction")
        return signedTransaction
    } catch (e: Exception) {
        println("Failed to sign: ${e.message}")
        throw e
    }
}

Lamports Conversion Helpers

object SolanaUtils {
    const val LAMPORTS_PER_SOL = 1_000_000_000L

    fun solToLamports(sol: Double): Long {
        return (sol * LAMPORTS_PER_SOL).toLong()
    }

    fun lamportsToSol(lamports: Long): Double {
        return lamports.toDouble() / LAMPORTS_PER_SOL
    }

    fun formatSol(sol: Double): String {
        return String.format("%.9f SOL", sol)
    }

    fun formatLamports(lamports: Long): String {
        return String.format("%,d lamports", lamports)
    }
}

// Usage
val amount = 1.5
val lamports = SolanaUtils.solToLamports(amount)
println("${amount} SOL = ${SolanaUtils.formatLamports(lamports)}")
fun getExplorerUrl(signature: String, cluster: Cluster = Cluster.DEVNET): String {
    val clusterParam = when (cluster) {
        Cluster.DEVNET -> "devnet"
        Cluster.TESTNET -> "testnet"
        Cluster.MAINNET_BETA -> "mainnet-beta"
        else -> "devnet"
    }
    return "https://explorer.solana.com/tx/$signature?cluster=$clusterParam"
}

// Usage in Compose
@Composable
fun ExplorerLink(signature: String, cluster: Cluster) {
    val url = getExplorerUrl(signature, cluster)
    Button(onClick = { /* Open URL in browser */ }) {
        Text("View on Solana Explorer")
    }
}

Best Practices

  • Get fresh blockhash immediately before sending (valid ~60 seconds)
  • Validate addresses before creating transactions
  • Check balance includes transaction fees (~5000 lamports)
  • Show transaction confirmations to users
  • Display transaction status with explorer links

Error Handling

Common Transaction Errors

sealed class SolanaTransactionError {
    data class InsufficientFunds(val message: String) : SolanaTransactionError()
    data class InvalidBlockhash(val message: String) : SolanaTransactionError()
    data class NetworkError(val message: String) : SolanaTransactionError()
    data class InvalidAddress(val message: String) : SolanaTransactionError()
    data class UserRejected(val message: String) : SolanaTransactionError()
    data class Unknown(val message: String) : SolanaTransactionError()
}

fun parseTransactionError(e: Exception): SolanaTransactionError {
    val errorMessage = e.message?.lowercase() ?: ""

    return when {
        errorMessage.contains("insufficient") || errorMessage.contains("balance") ->
            SolanaTransactionError.InsufficientFunds("Insufficient balance for transaction")
        errorMessage.contains("blockhash") ->
            SolanaTransactionError.InvalidBlockhash("Blockhash expired, please try again")
        errorMessage.contains("network") || errorMessage.contains("connection") ->
            SolanaTransactionError.NetworkError("Network error, check your connection")
        errorMessage.contains("invalid") && errorMessage.contains("address") ->
            SolanaTransactionError.InvalidAddress("Invalid recipient address")
        errorMessage.contains("rejected") || errorMessage.contains("denied") ->
            SolanaTransactionError.UserRejected("Transaction was rejected")
        else ->
            SolanaTransactionError.Unknown("Transaction failed: ${e.message}")
    }
}

// Usage in ViewModel
try {
    val signature = sdk.solana.signAndSendTransaction(base64Transaction, wallet)
    _transactionSignature.value = signature
} catch (e: Exception) {
    val error = parseTransactionError(e)
    _errorMessage.value = when (error) {
        is SolanaTransactionError.InsufficientFunds -> error.message
        is SolanaTransactionError.InvalidBlockhash -> error.message
        is SolanaTransactionError.NetworkError -> error.message
        is SolanaTransactionError.InvalidAddress -> error.message
        is SolanaTransactionError.UserRejected -> error.message
        is SolanaTransactionError.Unknown -> error.message
    }
}

Troubleshooting

Transaction not confirming: Check transaction status on explorer Blockhash expired: Get fresh blockhash immediately before sending, implement retry logic Network mismatch: Use devnet for testing, mainnet for production Insufficient funds: Get devnet SOL from faucet.solana.com for testing

What’s Next