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)}")
Transaction Explorer Links
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