Use Lunar Lander QUIC when you want the lowest-latency path for sending Solana transactions through Lunar Lander.
QUIC is designed for advanced integrations that:
- Want to avoid HTTP overhead
- Build and sign transactions locally
- Can manage long-lived connections
If you want simpler request and response semantics, our HTTP endpoints may be a better fit.
What it does
Lunar Lander QUIC is a fire-and-forget transaction submission path:
- One transaction per QUIC unidirectional stream
- No JSON-RPC wrapper
- No batch framing
- No per-stream response body
Each valid transaction is handed into Lunar Lander's normal send pipeline after authentication and ingress checks.
Endpoint
Use the same regional Lunar Lander hostnames you use for HTTP, but connect over QUIC on UDP port 16888.
Example:
fra.lunar-lander.hellomoon.io:16888
Use the region closest to your sender. You can see available regions here.
API key
You cannot connect without an API key. If you don't have one, please reach out to the team for access.
Tip requirement
Lunar Lander QUIC is tip-enforced. Please see here for our tip accounts and tip minimums.
MEV Protection
You can enable MEV protection on your QUIC connection by embedding a custom X.509 extension in your client certificate. When the server sees this extension, it applies MEV protection to all transactions sent over that connection — the same blacklist filtering available via ?mev-protect=true on the HTTP endpoints.
MEV protection is per-connection. To change it, reconnect with or without the extension.
Using the official Hello Moon client
use lunar_lander_quic_client::{ClientOptions, LunarLanderQuicClient};
let client = LunarLanderQuicClient::connect_with_options(
"fra.lunar-lander.hellomoon.io:16888",
std::env::var("LUNAR_LANDER_API_KEY")?,
ClientOptions {
mev_protect: true,
..ClientOptions::default()
},
)
.await?;Custom client implementation
If you are building your own QUIC client, add a non-critical X.509 extension to the self-signed client certificate before connecting:
| Field | Value |
|---|---|
| OID | 2.999.1.1 |
| Critical | false |
| Value | DER-encoded BOOLEAN TRUE: 0x01 0x01 0xFF |
The extension is optional. Connections without it behave normally with MEV protection disabled.
Rust example
use rcgen::CustomExtension;
// Inside your cert generation, before calling params.self_signed():
let mev_ext = CustomExtension::from_oid_content(
&[2, 999, 1, 1],
vec![0x01, 0x01, 0xFF], // DER BOOLEAN TRUE
);
params.custom_extensions.push(mev_ext);Go example
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
)
// Inside your certificate template, before calling x509.CreateCertificate():
template.ExtraExtensions = []pkix.Extension{
{
Id: asn1.ObjectIdentifier{2, 999, 1, 1},
Critical: false,
Value: []byte{0x01, 0x01, 0xFF}, // DER BOOLEAN TRUE
},
}How to send a transaction
Submission model:
- Connect over QUIC.
- Authenticate with your client certificate.
- Open one unidirectional stream.
- Write one serialized transaction payload.
- Finish the stream.
Expected behavior:
- Valid transactions are accepted into the send pipeline.
- Invalid or non-qualifying transactions are dropped.
- The connection can remain open across many submissions.
There is no per-stream success response. Lunar Lander optimizes this path for submission latency rather than request/response ergonomics.
Using the official Hello Moon client
The example below shows how to submit to QUIC using the official Hello Moon Rust client. It is available at https://crates.io/crates/lunar-lander-quic-client
use lunar_lander_quic_client::LunarLanderQuicClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = std::env::var("LUNAR_LANDER_API_KEY")?;
let client = LunarLanderQuicClient::connect(
"fra.lunar-lander.hellomoon.io:16888",
api_key,
)
.await?;
let tx_bytes: Vec<u8> = load_signed_transaction_bytes()?;
client.send_transaction(&tx_bytes).await?;
Ok(())
}[dependencies]
lunar-lander-quic-client = "0.1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }Example client application
Below is a Rust and Go example of how you can use QUIC to send transactions in your app if you don't want to use our client. This is only recommended for advanced users!
Both examples generate a self-signed client certificate in code and set CN=<YOUR_API_KEY> automatically.
If you prefer, you can still pre-generate certificate files and load them from disk, but that is not required.
use quinn::{ClientConfig, Endpoint};
use rcgen::{CertificateParams, DistinguishedName, DnType, KeyPair};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use std::{net::SocketAddr, sync::Arc};
const LUNAR_LANDER_TPU_PROTOCOL_ID: &[u8] = b"lunar-lander-tpu";
const SERVER_ADDR: &str = "fra.lunar-lander.hellomoon.io:16888";
const SERVER_NAME: &str = "fra.lunar-lander.hellomoon.io";
fn build_client_config(api_key: &str) -> anyhow::Result<ClientConfig> {
let key_pair = KeyPair::generate()?;
let mut params = CertificateParams::new(Vec::new())?;
let mut distinguished_name = DistinguishedName::new();
distinguished_name.push(DnType::CommonName, api_key);
params.distinguished_name = distinguished_name;
let certificate = params.self_signed(&key_pair)?;
let private_key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key_pair.serialize_der()));
let mut rustls = rustls::ClientConfig::builder()
.with_root_certificates(load_server_roots()?)
.with_client_auth_cert(
vec![CertificateDer::from(certificate.der().to_vec())],
private_key,
)?;
rustls.alpn_protocols = vec![LUNAR_LANDER_TPU_PROTOCOL_ID.to_vec()];
Ok(ClientConfig::new(Arc::new(
quinn::crypto::rustls::QuicClientConfig::try_from(rustls)?
)))
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let api_key = std::env::var("LUNAR_LANDER_API_KEY")?;
let server_addr: SocketAddr = SERVER_ADDR.parse()?;
let client_config = build_client_config(&api_key)?;
let mut endpoint = Endpoint::client("0.0.0.0:0".parse()?)?;
endpoint.set_default_client_config(client_config);
let connection = endpoint.connect(server_addr, SERVER_NAME)?.await?;
let tx_bytes: Vec<u8> = load_signed_transaction_bytes()?;
let mut send = connection.open_uni().await?;
send.write_all(&tx_bytes).await?;
send.finish()?;
Ok(())
}package main
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"os"
"time"
quic "github.com/quic-go/quic-go"
)
const (
ServerAddr = "fra.lunar-lander.hellomoon.io:16888"
ServerName = "fra.lunar-lander.hellomoon.io"
ALPN = "lunar-lander-tpu"
)
func buildClientCertificate(apiKey string) (tls.Certificate, error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, err
}
template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{
CommonName: apiKey,
},
NotBefore: time.Now().Add(-time.Minute),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
if err != nil {
return tls.Certificate{}, err
}
return tls.Certificate{
Certificate: [][]byte{certDER},
PrivateKey: privateKey,
}, nil
}
func main() {
apiKey := os.Getenv("LUNAR_LANDER_API_KEY")
if apiKey == "" {
panic("LUNAR_LANDER_API_KEY is required")
}
clientCert, err := buildClientCertificate(apiKey)
if err != nil {
panic(err)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{clientCert},
ServerName: ServerName,
NextProtos: []string{ALPN},
InsecureSkipVerify: true,
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn, err := quic.DialAddr(ctx, ServerAddr, tlsConfig, &quic.Config{
// quic-go defaults to 1280-byte Initial packets; Lunar Lander expects 1200 here.
InitialPacketSize: 1200,
})
if err != nil {
panic(err)
}
defer conn.CloseWithError(0, "")
txBytes, err := os.ReadFile("signed-tx.bin")
if err != nil {
panic(err)
}
stream, err := conn.OpenUniStreamSync(ctx)
if err != nil {
panic(err)
}
if _, err := stream.Write(txBytes); err != nil {
panic(err)
}
if err := stream.Close(); err != nil {
panic(err)
}
}ALPN
ALPN is the application protocol identifier negotiated during the QUIC/TLS handshake. Your client must send one of the supported values below.
Supported ALPN values:
lunar-lander-tpu
TLS server name
For native integrations, use the regional hostname as the TLS server name.
Example:
- Endpoint:
fra.lunar-lander.hellomoon.io:16888 - Server name:
fra.lunar-lander.hellomoon.io
Error behavior
Connection-level failures
These failures close the connection:
| Code | Reason | Meaning |
|---|---|---|
1 | bad_cert | Missing or malformed certificate CN. |
1 | invalid_auth | Invalid, revoked, or wrong-scope API key. |
2 | connection_limit | Too many concurrent connections for one API key. |
Stream-level failures
These failures do not require the connection to close:
tip_requiredinvalid_payload- Oversized payload
- Transaction-level rate-limit drop
These are treated as dropped submissions on the stream path. The connection may remain open and be reused.
Limits
| Setting | Value |
|---|---|
| Max transaction size | 1232 bytes |
| Max concurrent unidirectional streams per connection | 64 |
| Idle timeout | 30s |
| Max concurrent connections per API key | 10 |
| Max connection attempts per source IP | 10/min |
Best practices
- Reuse connections; do not reconnect for every transaction
- Send one transaction per unidirectional stream
- Use the closest region to your sender
FAQ
Does QUIC return a transaction signature?
No. Lunar Lander QUIC is fire-and-forget at the stream level.
Can I enable MEV protection over QUIC?
Yes. Embed a custom X.509 extension (OID 2.999.1.1) in your client certificate, or set mev_protect: true in ClientOptions if you are using the official Rust client. See MEV Protection above.
Can I send tipless transactions over QUIC?
No. Lunar Lander QUIC is tip-enforced.
Can I batch multiple transactions in one stream?
No. Send one transaction per unidirectional stream.
Are you compatible with any other QUIC clients?
Yes. Lunar Lander is drop-in compatible with several other QUIC clients.
Is there an official Hello Moon QUIC client library?
Yes, we have an official Rust client. You can get it at https://crates.io/crates/lunar-lander-quic-client