go
Send a transaction over UDP from Go using the standard library net package.
Minimal Example
go
package main
import (
"log"
"net"
)
const (
Endpoint = "fra.blocksprint.io:5000"
APIKey = "YOUR_API_KEY"
)
// SendUDP submits a serialized Solana transaction to BlockSprint over UDP.
// tx must be the raw wire bytes (the same bytes you would base64-encode for sendTransaction).
func SendUDP(tx []byte) error {
pkt := make([]byte, 0, 1+len(APIKey)+len(tx))
pkt = append(pkt, byte(len(APIKey))) // key_len
pkt = append(pkt, []byte(APIKey)...) // key (ASCII)
pkt = append(pkt, tx...) // tx wire bytes
addr, err := net.ResolveUDPAddr("udp", Endpoint)
if err != nil {
return err
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write(pkt)
return err
}
func main() {
var tx []byte // = your serialized signed transaction
if err := SendUDP(tx); err != nil {
log.Fatalf("send: %v", err)
}
}Full Example: Sign + Send with solana-go
go
package main
import (
"context"
"log"
"net"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/programs/system"
"github.com/gagliardetto/solana-go/rpc"
)
const (
Endpoint = "fra.blocksprint.io:5000"
APIKey = "YOUR_API_KEY"
TipAddress = "YOUR_TIP_ADDRESS"
TipLamports = 1_000_000 // 0.001 SOL (minimum)
)
func sendOverUDP(tx []byte) error {
pkt := make([]byte, 0, 1+len(APIKey)+len(tx))
pkt = append(pkt, byte(len(APIKey)))
pkt = append(pkt, []byte(APIKey)...)
pkt = append(pkt, tx...)
addr, err := net.ResolveUDPAddr("udp", Endpoint)
if err != nil {
return err
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write(pkt)
return err
}
func main() {
ctx := context.Background()
rpcClient := rpc.New("YOUR_SOLANA_RPC_URL") // your own Solana RPC, only used for the blockhash
// Build instructions
payer := solana.MustPrivateKeyFromBase58("YOUR_BASE58_SECRET_KEY")
recipient := solana.MustPublicKeyFromBase58("RECIPIENT_ADDRESS")
tipPubkey := solana.MustPublicKeyFromBase58(TipAddress)
transferIx := system.NewTransferInstruction(1_000_000, payer.PublicKey(), recipient).Build()
tipIx := system.NewTransferInstruction(TipLamports, payer.PublicKey(), tipPubkey).Build()
recent, err := rpcClient.GetLatestBlockhash(ctx, rpc.CommitmentConfirmed)
if err != nil {
log.Fatal(err)
}
tx, err := solana.NewTransaction(
[]solana.Instruction{transferIx, tipIx},
recent.Value.Blockhash,
solana.TransactionPayer(payer.PublicKey()),
)
if err != nil {
log.Fatal(err)
}
_, err = tx.Sign(func(key solana.PublicKey) *solana.PrivateKey {
if key.Equals(payer.PublicKey()) {
return &payer
}
return nil
})
if err != nil {
log.Fatal(err)
}
txBytes, err := tx.MarshalBinary()
if err != nil {
log.Fatal(err)
}
if err := sendOverUDP(txBytes); err != nil {
log.Fatal(err)
}
log.Printf("Sent signature: %s", tx.Signatures[0])
}Reusing the Connection (High Throughput)
DialUDP creates a "connected" UDP socket that's cheap to reuse:
go
package udpsender
import (
"errors"
"net"
"sync"
)
type Sender struct {
conn *net.UDPConn
prefix []byte // [key_len][key bytes] — concatenated to every tx
mu sync.Mutex
}
func New(endpoint, apiKey string) (*Sender, error) {
if len(apiKey) < 16 || len(apiKey) > 128 {
return nil, errors.New("api key length must be 16..128 bytes")
}
addr, err := net.ResolveUDPAddr("udp", endpoint)
if err != nil {
return nil, err
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
return nil, err
}
prefix := make([]byte, 1+len(apiKey))
prefix[0] = byte(len(apiKey))
copy(prefix[1:], apiKey)
return &Sender{conn: conn, prefix: prefix}, nil
}
func (s *Sender) Send(tx []byte) error {
s.mu.Lock()
defer s.mu.Unlock()
pkt := append(append(make([]byte, 0, len(s.prefix)+len(tx)), s.prefix...), tx...)
_, err := s.conn.Write(pkt)
return err
}
func (s *Sender) Close() error {
return s.conn.Close()
}Notes
INFO
- No response.
Writereturns once the OS has accepted the packet. There is no ACK from BlockSprint. - Silent drops. Bad keys, missing tips, and rate-limit hits drop the packet without an error.
- MTU. Keep the total packet under ~1300 bytes (1 + key length + ≤ 1232 tx bytes).
- Transaction V1. Build it with solana-go v1.23.0 or later (
solana.TransactionV1Config, with the compute-unit limit and the loaded-accounts data size limit set).MarshalBinarythen writes the V1 wire bytes. Keep V1 datagrams within about 1,400 bytes, and send larger V1 transactions (up to 4,096 bytes) via QUIC or sendTransaction.
