Skip to content

go ​

Send transactions over QUIC for the lowest possible latency.

Dependencies ​

bash
go get github.com/quic-go/quic-go

Full Example ​

go
package blocksprintclient

import (
	"context"
	"crypto/ed25519"
	"crypto/rand"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"math/big"
	"sync"
	"time"

	"github.com/quic-go/quic-go"
)

const (
	alpnTPUProtocolID  = "solana-tpu"
	defaultServerName  = "blocksprint-node"
	defaultKeepAlive   = 25 * time.Second
	defaultIdleTimeout = 30 * time.Second
)

// Client maintains a QUIC connection to a Blocksprint ingress endpoint.
//
// Thread-safe: can be shared across goroutines.
type Client struct {
	endpointAddr string
	tlsConfig    *tls.Config
	quicConfig   *quic.Config

	connMu      sync.RWMutex
	conn        *quic.Conn
	reconnectMu sync.Mutex
}

// Connect creates a client using your Blocksprint API key.
func Connect(ctx context.Context, endpointAddr string, apiKey string) (*Client, error) {
	cert, err := x509CertificateFromAPIKey(apiKey)
	if err != nil {
		return nil, err
	}

	tlsConfig := &tls.Config{
		Certificates:       []tls.Certificate{cert},
		ServerName:         defaultServerName,
		InsecureSkipVerify: true,
		NextProtos:         []string{alpnTPUProtocolID},
		MinVersion:         tls.VersionTLS13,
	}

	quicConfig := &quic.Config{
		KeepAlivePeriod: defaultKeepAlive,
		MaxIdleTimeout:  defaultIdleTimeout,
	}

	client := &Client{
		endpointAddr: endpointAddr,
		tlsConfig:    tlsConfig,
		quicConfig:   quicConfig,
	}

	if err := client.reconnect(ctx); err != nil {
		return nil, err
	}
	return client, nil
}

// Close closes the underlying QUIC connection.
func (c *Client) Close() error {
	c.reconnectMu.Lock()
	defer c.reconnectMu.Unlock()

	c.connMu.Lock()
	conn := c.conn
	c.conn = nil
	c.connMu.Unlock()

	if conn == nil {
		return nil
	}
	return conn.CloseWithError(0, "")
}

// SendTransaction sends a signed Solana transaction payload.
// The payload should be the raw wire bytes (e.g., from tx.MarshalBinary()).
// Automatically reconnects on failure and retries once.
func (c *Client) SendTransaction(ctx context.Context, transactionBytes []byte) error {
	if len(transactionBytes) == 0 {
		return nil
	}

	conn := c.getConn()
	if conn != nil {
		if err := trySendBytes(ctx, conn, transactionBytes); err == nil {
			return nil
		}
	}

	if err := c.reconnect(ctx); err != nil {
		return err
	}
	conn = c.getConn()
	if conn == nil {
		return errors.New("missing QUIC connection")
	}
	return trySendBytes(ctx, conn, transactionBytes)
}

func (c *Client) getConn() *quic.Conn {
	c.connMu.RLock()
	defer c.connMu.RUnlock()
	return c.conn
}

func (c *Client) reconnect(ctx context.Context) error {
	c.reconnectMu.Lock()
	defer c.reconnectMu.Unlock()

	if existing := c.getConn(); existing != nil && existing.Context().Err() == nil {
		return nil
	}

	conn, err := quic.DialAddr(ctx, c.endpointAddr, c.tlsConfig, c.quicConfig)
	if err != nil {
		return err
	}

	c.connMu.Lock()
	old := c.conn
	c.conn = conn
	c.connMu.Unlock()

	if old != nil {
		_ = old.CloseWithError(0, "")
	}
	return nil
}

func trySendBytes(ctx context.Context, conn *quic.Conn, payload []byte) error {
	stream, err := conn.OpenUniStreamSync(ctx)
	if err != nil {
		return err
	}
	if _, err := stream.Write(payload); err != nil {
		_ = stream.Close()
		return err
	}
	return stream.Close()
}

// x509CertificateFromAPIKey creates a self-signed X.509 certificate
// derived from the API key for client authentication.
func x509CertificateFromAPIKey(apiKey string) (tls.Certificate, error) {
	// Derive keypair from API key using SHA256 hash
	hash := sha256.Sum256([]byte(apiKey))
	seed := hash[:]

	priv := ed25519.NewKeyFromSeed(seed)
	pub := priv.Public().(ed25519.PublicKey)

	serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
	if err != nil {
		return tls.Certificate{}, err
	}

	template := &x509.Certificate{
		SerialNumber: serial,
		NotBefore:    time.Now().Add(-5 * time.Minute),
		NotAfter:     time.Now().Add(365 * 24 * time.Hour),
		KeyUsage:     x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}

	der, err := x509.CreateCertificate(rand.Reader, template, template, pub, priv)
	if err != nil {
		return tls.Certificate{}, err
	}

	return tls.Certificate{
		Certificate: [][]byte{der},
		PrivateKey:  priv,
	}, nil
}

Usage ​

go
package main

import (
	"context"
	"log"
	"time"

	"github.com/gagliardetto/solana-go"

	"example.com/yourapp/blocksprintclient" // the client above
)

func main() {
	ctx := context.Background()

	// Connect to Blocksprint
	client, err := blocksprintclient.Connect(
		ctx,
		"fra.blocksprint.io:10000",
		"YOUR_API_KEY",
	)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// Build your transaction
	tx := /* your signed solana.Transaction */
	txBytes, err := tx.MarshalBinary()
	if err != nil {
		log.Fatal(err)
	}

	// Send via QUIC
	if err := client.SendTransaction(ctx, txBytes); err != nil {
		log.Fatal(err)
	}

	log.Println("Transaction sent!")

	// Give the transaction a moment to leave before Close
	time.Sleep(time.Second)
}

INFO

Notes

  • The client automatically reconnects on connection failures
  • Thread-safe: can be shared across goroutines
  • Uses the same API key as HTTP endpoints
  • Transactions must be fully signed before sending
  • Transaction V1 (up to 4,096 bytes) is sent the same way: tx.MarshalBinary() on a V1 transaction (solana-go v1.23.0 or later)