go
Send a transaction using Go with raw HTTP requests.
Full Example
go
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
const ENDPOINT = "https://fra.blocksprint.io/api/v1/YOUR_API_KEY"
// Reuse one client so the connection to BlockSprint stays open between sends.
var httpClient = &http.Client{Timeout: 10 * time.Second}
type JsonRpcRequest struct {
Jsonrpc string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params []interface{} `json:"params"`
}
type JsonRpcResponse struct {
Jsonrpc string `json:"jsonrpc"`
ID int `json:"id"`
Result string `json:"result,omitempty"`
Error *RpcError `json:"error,omitempty"`
}
type RpcError struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (e *RpcError) Error() string {
return fmt.Sprintf("RPC error %d: %s", e.Code, e.Message)
}
type SendOptions struct {
Encoding string `json:"encoding"`
SkipPreflight bool `json:"skipPreflight"`
MaxRetries int `json:"maxRetries"`
}
func sendTransaction(transactionBytes []byte) (string, error) {
base64Tx := base64.StdEncoding.EncodeToString(transactionBytes)
request := JsonRpcRequest{
Jsonrpc: "2.0",
ID: 1,
Method: "sendTransaction",
Params: []interface{}{
base64Tx,
SendOptions{
Encoding: "base64",
SkipPreflight: true,
MaxRetries: 0,
},
},
}
body, err := json.Marshal(request)
if err != nil {
return "", err
}
resp, err := httpClient.Post(ENDPOINT, "application/json", bytes.NewReader(body))
if err != nil {
return "", err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
// Errors come back as JSON-RPC errors with an HTTP 4xx/5xx status.
var response JsonRpcResponse
if err := json.Unmarshal(respBody, &response); err != nil {
return "", fmt.Errorf("HTTP %d: %s", resp.StatusCode, respBody)
}
if response.Error != nil {
return "", response.Error
}
// A signature means "accepted", not "landed": confirm it on your Solana RPC.
return response.Result, nil
}
func main() {
// Your signed, serialized transaction bytes (wire format)
txBytes, err := os.ReadFile("signed_tx.bin")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
signature, err := sendTransaction(txBytes)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Signature: %s\n", signature)
}Using gagliardetto/solana-go
go
package main
import (
"context"
"fmt"
"log"
"math/rand"
"os"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/programs/system"
"github.com/gagliardetto/solana-go/rpc"
)
const (
// Your Solana RPC, for the blockhash and confirmations.
RPC_URL = "https://api.mainnet-beta.solana.com"
// BlockSprint, for sending.
ENDPOINT = "https://fra.blocksprint.io/api/v1/YOUR_API_KEY"
)
// BlockSprint tip addresses
var TIP_ADDRESSES = []solana.PublicKey{
solana.MustPublicKeyFromBase58("Sp1x2AqpQckPLaWnWCJUNg8k6qQexfaEWcSRKf5JcDV"),
solana.MustPublicKeyFromBase58("Sp4JHSh9cksfzXbgK7Pq2ovtn8LirLQydaJKTsiNT77"),
solana.MustPublicKeyFromBase58("Sp1xMS2cbw83SZDNr4AGqkBYYLjb3LvVnmDSrTMaHkr"),
solana.MustPublicKeyFromBase58("SpagSJmnh8E9cGT5Y431xPPaS2c1xLREGGCWN9yDeUf"),
solana.MustPublicKeyFromBase58("SpWrza9E63MQuHeGnnfzmtLVCs3pBdjyKPXUABPo9nq"),
solana.MustPublicKeyFromBase58("SpXyoqMHJEhsNQ9fHh3DTGGzrGroZ5zfDtJwFvN4kXi"),
solana.MustPublicKeyFromBase58("Spv2g3L837bVeHwkyb18LhA5P1bvJbkvaZbxcZXbwC2"),
solana.MustPublicKeyFromBase58("SpDUe5MyFGG97SmdBQMWNPbx2Et9bZt2TCbZm3TujgX"),
solana.MustPublicKeyFromBase58("SpKZ28tFzQ13kVjHhsyDEiQfkPGjA7Wmk185KcEDyuh"),
solana.MustPublicKeyFromBase58("SpxPqegfoR3QkBN7guRUiU6QfhtTW1CnKSTSSapumqm"),
}
func getRandomTipAddress() solana.PublicKey {
return TIP_ADDRESSES[rand.Intn(len(TIP_ADDRESSES))]
}
func main() {
ctx := context.Background()
rpcClient := rpc.New(RPC_URL)
blocksprint := rpc.New(ENDPOINT)
// Your funded wallet (base58 secret key) and the recipient
wallet := solana.MustPrivateKeyFromBase58(os.Getenv("PAYER_SECRET_BASE58"))
recipient := solana.MustPublicKeyFromBase58(os.Getenv("RECIPIENT"))
// Get blockhash from your RPC
recent, err := rpcClient.GetLatestBlockhash(ctx, rpc.CommitmentConfirmed)
if err != nil {
log.Fatal(err)
}
// Build transaction
tx, err := solana.NewTransaction(
[]solana.Instruction{
// Your transfer
system.NewTransferInstruction(
1_000_000,
wallet.PublicKey(),
recipient,
).Build(),
// Tip instruction (0.001 SOL minimum)
system.NewTransferInstruction(
1_000_000,
wallet.PublicKey(),
getRandomTipAddress(),
).Build(),
},
recent.Value.Blockhash,
solana.TransactionPayer(wallet.PublicKey()),
)
if err != nil {
log.Fatal(err)
}
// Sign
_, err = tx.Sign(func(key solana.PublicKey) *solana.PrivateKey {
if wallet.PublicKey().Equals(key) {
return &wallet
}
return nil
})
if err != nil {
log.Fatal(err)
}
// Send through BlockSprint (base64 is solana-go's default encoding)
maxRetries := uint(0)
sig, err := blocksprint.SendTransactionWithOpts(ctx, tx, rpc.TransactionOpts{
SkipPreflight: true,
MaxRetries: &maxRetries,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Signature: %s\n", sig)
}With Retry Logic
Re-send the same signed transaction every ~2 seconds until it confirms or its blockhash expires. Uses sendTransaction from the Full Example; sig is tx.Signatures[0] and lastValidBlockHeight is recent.Value.LastValidBlockHeight.
go
// Additional imports: "context", "errors", "time",
// "github.com/gagliardetto/solana-go", "github.com/gagliardetto/solana-go/rpc"
func sendUntilConfirmed(
ctx context.Context,
rpcClient *rpc.Client,
txBytes []byte,
sig solana.Signature,
lastValidBlockHeight uint64,
) error {
for {
if _, err := sendTransaction(txBytes); err != nil {
var rpcErr *RpcError
// JSON-RPC errors (key, tip, format) will not succeed on retry.
// Network errors are worth retrying.
if errors.As(err, &rpcErr) {
return err
}
}
time.Sleep(2 * time.Second)
statuses, err := rpcClient.GetSignatureStatuses(ctx, false, sig)
if err == nil && len(statuses.Value) > 0 && statuses.Value[0] != nil {
status := statuses.Value[0]
if status.Err != nil {
return fmt.Errorf("transaction failed: %v", status.Err)
}
if status.ConfirmationStatus == rpc.ConfirmationStatusConfirmed ||
status.ConfirmationStatus == rpc.ConfirmationStatusFinalized {
return nil
}
}
height, err := rpcClient.GetBlockHeight(ctx, rpc.CommitmentConfirmed)
if err == nil && height > lastValidBlockHeight {
return errors.New("blockhash expired: rebuild and re-sign the transaction")
}
}
}INFO
Transaction V1 needs solana-go v1.23.0 or later: build it with solana.TransactionV1Config, and set the compute-unit limit and the loaded-accounts data size limit explicitly (in V1 an unset limit is 0). Sign and send it like the examples above.
