Interacting with Solana in Go: A Beginner's Guide

Benjamin Bourgeois - Mar 27 - - Dev Community

Solana is a high-performance blockchain platform gaining traction for its scalability and developer experience. This blog post dives into interacting with Solana using Go. We'll explore the basic functionalities like creating a wallet, importing existing one, fetching account balance, and transferring SOL tokens.

History

Solana, founded in 2020 by Anatoly Yakovenko, is a high-performance blockchain that has rapidly gained popularity within the blockchain community. Its architecture, designed to deliver exceptional speed provides scalability and low transaction costs. The core innovation lies in the unique combination of Proof of History and Proof of Stake, allowing the network to achieve consensus quickly and efficiently. The ability to handle thousands of transactions per second, coupled with its developer-friendly environment, makes it an attractive choice for those looking to build decentralized applications at scale.

On the other hand, the selection of a programming language for DApp development necessitates careful consideration of factors such as platform compatibility, developer community engagement, security features, performance, and interoperability. Various languages, including JavaScript, Rust, and Go, can be considered in this context.

Golang stands out as an optimal programming language for building decentralized applications (DApps) on the Solana blockchain. Go's strong support for concurrency aligns perfectly with Solana's design principles, allowing developers to take full advantage of the Solana’s fast transaction processing and scalability.

Prerequisites:

  • Basic understanding of blockchain
  • Basic understanding of Golang and a Go development environment
  • Familiarity with Solana concepts

Choosing a Golang library for Solana:

As of now there is no official library to support interaction with Solana in Go, but there is popular community librairies such as :

In this article, we’ll use the most popular package : gagliardetto/solana-go


Creating a wallet

Let's start by creating a new Solana wallet. We'll use the solana.NewRandomPrivateKey function to generate a new keypair:

package main

import (
    "fmt"

    "github.com/gagliardetto/solana-go"
)

func main() {
    // Generate a new random keypair
    privateKey, err := solana.NewRandomPrivateKey()
    if err != nil {
        panic(err)
    }

    // Get the public key from the private key
    publicKey := privateKey.PublicKey()

    // Print the public and private keys (**keep private key secure!**)
    fmt.Println("Public Key:", publicKey)
    fmt.Println("Private Key:", privateKey)
}

Enter fullscreen mode Exit fullscreen mode

Running this code gives the following:

Output when running the previous code


Importing an existing wallet

You might have to create a new wallet, but most of the time you will most likely import an existing one. To use an existing wallet, you'll need its private key in base58 format.
Here's an example using solana.PrivateKeyFromBase58:

package main

import "github.com/gagliardetto/solana-go"

func main() {
    // Replace with your actual private key in base58 format
    privateKeyString := ".........."

    // Parse the private key from base58
    privateKey, err := solana.PrivateKeyFromBase58(privateKeyString)
    if err != nil {
        panic(err)
    }

    // Get the public key from the private key
    publicKey := privateKey.PublicKey()

    // Print the public and private keys
    println("Public Key:", publicKey.String())
}
Enter fullscreen mode Exit fullscreen mode

Running this code gives the following output:

Output when running the previous code


Request airdrop and fetching wallet balance

For development and testing purposes, you might need test accounts with some SOL tokens to interact with the network. You could use an airdrop to get Solana token on the Devnet and Testnet. This can help you test your application's functionality.

It can be subject to rate limits when there is a high number of airdrops.

We can use the GetBalance method to fetch the balance of an account (wallet):

package main

import (
    "context"
    "fmt"
    "math/big"
    "time"

    "github.com/gagliardetto/solana-go"
    "github.com/gagliardetto/solana-go/rpc"
)

func main() {

    // Create a new account
    account := solana.NewWallet()
    fmt.Println("account private key:", account.PrivateKey)
    fmt.Println("account public key:", account.PublicKey())

    // Create a new RPC client on the DevNet
    rpcClient := rpc.New(rpc.DevNet_RPC)

    // Airdrop 1 SOL to the new account:
    out, err := rpcClient.RequestAirdrop(
        context.TODO(),
        account.PublicKey(),
        // 1 sol = 1000000000 lamports
        solana.LAMPORTS_PER_SOL*1,
        rpc.CommitmentFinalized,
    )

    if err != nil {
        panic(err)
    }

    fmt.Println("airdrop transaction signature:", out)
    time.Sleep(time.Second * 1)

    // Get the balance of the account
    balance, err := rpcClient.GetBalance(
        context.TODO(),
        account.PublicKey(),
        rpc.CommitmentFinalized,
    )

    if err != nil {
        panic(err)
    }

    var lamportsOnAccount = new(big.Float).SetUint64(uint64(balance.Value))
    var solBalance = new(big.Float).Quo(lamportsOnAccount, new(big.Float).SetUint64(solana.LAMPORTS_PER_SOL))

    fmt.Println("Wallet Balance:", solBalance, "SOL")
}
Enter fullscreen mode Exit fullscreen mode

Running this code gives the following output:

Output when running the previous code


Transfer Solana to another wallet

Transfers involve creating and sending a transaction. Here's a simple example with the wallet we requested airdrop on just before:

package main

import (
    "context"
    "fmt"
    "os"

    "github.com/gagliardetto/solana-go"
    "github.com/gagliardetto/solana-go/programs/system"
    "github.com/gagliardetto/solana-go/rpc"
    confirm "github.com/gagliardetto/solana-go/rpc/sendAndConfirmTransaction"
    "github.com/gagliardetto/solana-go/rpc/ws"
    "github.com/gagliardetto/solana-go/text"
)

func main() {
    // Create a new RPC client on the DevNet
    rpcClient := rpc.New(rpc.DevNet_RPC)

    // Create a new WS client (used for confirming transactions)
    wsClient, err := ws.Connect(context.Background(), rpc.DevNet_WS)
    if err != nil {
        panic(err)
    }

    // Load the account that you will send funds FROM
    accountFrom, err := solana.PrivateKeyFromBase58("..........")
    if err != nil {
        panic(err)
    }
    fmt.Println("accountFrom public key:", accountFrom.PublicKey())

    // The public key of the account that you will send sol TO:
    accountTo := solana.NewWallet()
    fmt.Println("accountTo public key:", accountTo)

    // Get the recent blockhash:
    recent, err := rpcClient.GetRecentBlockhash(context.TODO(), rpc.CommitmentFinalized)
    if err != nil {
        panic(err)
    }

    tx, err := solana.NewTransaction(
        []solana.Instruction{
            system.NewTransferInstruction(
                // 1 sol = 1000000000 lamports
                1e6, // 0.001 SOL,
                accountFrom.PublicKey(),
                accountTo.PublicKey(),
            ).Build(),
        },
        recent.Value.Blockhash,
        solana.TransactionPayer(accountFrom.PublicKey()),
    )
    if err != nil {
        panic(err)
    }

    _, err = tx.Sign(
        func(key solana.PublicKey) *solana.PrivateKey {
            if accountFrom.PublicKey().Equals(key) {
                return &accountFrom
            }
            return nil
        },
    )
    if err != nil {
        panic(fmt.Errorf("unable to sign transaction: %w", err))
    }

    // Pretty print the transaction:
    tx.EncodeTree(text.NewTreeEncoder(os.Stdout, "Transfer SOL"))

    // Send transaction, and wait for confirmation:
    sig, err := confirm.SendAndConfirmTransaction(
        context.TODO(),
        rpcClient,
        wsClient,
        tx,
    )
    if err != nil {
        panic(err)
    }
    fmt.Println("Transaction confirmed:", sig)
}
Enter fullscreen mode Exit fullscreen mode

Running this code gives the following output:

Output when running the previous code


Conclusion

In this blog post, we've explored the basics of interacting with Solana using Golang.

This is just the first step on Solana development with Golang. The chosen library offers a wider range of functionalities beyond what we covered here. Remember, the Solana ecosystem is constantly evolving. Stay updated with the best practices to ensure your dApps remain robust and efficient.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .