Skip to content

go ​

Send a bundle using Go.

Types ​

go
package main

type BundleParams struct {
	Encoding      string `json:"encoding"`
	SkipPreflight bool   `json:"skipPreflight"`
}

type JsonRpcRequest struct {
	Jsonrpc string        `json:"jsonrpc"`
	ID      int           `json:"id"`
	Method  string        `json:"method"`
	Params  []interface{} `json:"params"`
}

type BundleResult struct {
	BundleID   string   `json:"bundle_id"`
	Signatures []string `json:"signatures"`
}

type JsonRpcResponse struct {
	Jsonrpc string        `json:"jsonrpc"`
	ID      int           `json:"id"`
	Result  *BundleResult `json:"result,omitempty"`
	Error   *RpcError     `json:"error,omitempty"`
}

type RpcError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

Send Bundle Function ​

go
package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

const ENDPOINT = "https://fra.blocksprint.io/bundle/v1/YOUR_API_KEY"

func sendBundle(transactions [][]byte) (*BundleResult, error) {
	// Encode transactions to base64
	encodedTxs := make([]string, len(transactions))
	for i, tx := range transactions {
		encodedTxs[i] = base64.StdEncoding.EncodeToString(tx)
	}

	request := JsonRpcRequest{
		Jsonrpc: "2.0",
		ID:      1,
		Method:  "sendBundle",
		Params: []interface{}{
			encodedTxs,
			BundleParams{
				Encoding:      "base64",
				SkipPreflight: true,
			},
		},
	}

	body, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}

	resp, err := http.Post(
		ENDPOINT,
		"application/json",
		bytes.NewBuffer(body),
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var response JsonRpcResponse
	if err := json.Unmarshal(respBody, &response); err != nil {
		return nil, err
	}

	if response.Error != nil {
		return nil, fmt.Errorf("RPC error %d: %s", response.Error.Code, response.Error.Message)
	}

	return response.Result, nil
}

Using solana-go ​

go
package main

import (
	"context"
	"fmt"

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

// Your Solana RPC (for the blockhash)
const RPC_URL = "https://api.mainnet-beta.solana.com"

// BlockSprint tip address, tip at least 0.001 SOL
var TIP_ADDRESS = solana.MustPublicKeyFromBase58("Sp4JHSh9cksfzXbgK7Pq2ovtn8LirLQydaJKTsiNT77")

func main() {
	client := rpc.New(RPC_URL)
	ctx := context.Background()

	wallet := solana.NewWallet()
	recipient := solana.MustPublicKeyFromBase58("RECIPIENT")

	// Get blockhash
	recent, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized)
	if err != nil {
		panic(err)
	}

	// Transaction 1
	tx1, err := solana.NewTransaction(
		[]solana.Instruction{
			system.NewTransferInstruction(
				1_000_000,
				wallet.PublicKey(),
				recipient,
			).Build(),
		},
		recent.Value.Blockhash,
		solana.TransactionPayer(wallet.PublicKey()),
	)
	if err != nil {
		panic(err)
	}
	tx1.Sign(func(key solana.PublicKey) *solana.PrivateKey {
		if wallet.PublicKey().Equals(key) {
			return &wallet.PrivateKey
		}
		return nil
	})

	// Transaction 2 (with the 0.001 SOL tip)
	tx2, err := solana.NewTransaction(
		[]solana.Instruction{
			system.NewTransferInstruction(
				2_000_000,
				wallet.PublicKey(),
				recipient,
			).Build(),
			system.NewTransferInstruction(
				1_000_000,
				wallet.PublicKey(),
				TIP_ADDRESS,
			).Build(),
		},
		recent.Value.Blockhash,
		solana.TransactionPayer(wallet.PublicKey()),
	)
	if err != nil {
		panic(err)
	}
	tx2.Sign(func(key solana.PublicKey) *solana.PrivateKey {
		if wallet.PublicKey().Equals(key) {
			return &wallet.PrivateKey
		}
		return nil
	})

	// Serialize
	tx1Bytes, _ := tx1.MarshalBinary()
	tx2Bytes, _ := tx2.MarshalBinary()

	// Send bundle
	result, err := sendBundle([][]byte{tx1Bytes, tx2Bytes})
	if err != nil {
		panic(err)
	}

	fmt.Printf("Bundle ID: %s\n", result.BundleID)
	for i, sig := range result.Signatures {
		fmt.Printf("Tx %d: %s\n", i+1, sig)
	}
}

With Retry Logic ​

go
package main

import (
	"strings"
	"time"
)

func sendBundleWithRetry(transactions [][]byte, maxRetries int) (*BundleResult, error) {
	var lastErr error

	for i := 0; i < maxRetries; i++ {
		result, err := sendBundle(transactions)
		if err == nil {
			return result, nil
		}

		lastErr = err

		// Check if rate limited
		if strings.Contains(err.Error(), "Rate limit") {
			time.Sleep(time.Duration(1<<i) * 100 * time.Millisecond)
			continue
		}

		break
	}

	return nil, lastErr
}