pulseclient

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: Apache-2.0, MIT Imports: 14 Imported by: 0

README

Pulse Go SDK

Go client for the Pulse wire-v2 Solana transaction feed over QUIC. The module path is github.com/thorlabsDev/pulse-go; the Go package name is pulseclient.

go get github.com/thorlabsDev/pulse-go@latest

Quick start

Production connections require a TLS-valid target, a token, and a deliberate account or program filter. The examples do not default to localhost or the unfiltered non-vote feed.

export PULSE_ADDR='<HOST:PORT_FROM_DASHBOARD>'
export PULSE_TOKEN='<TOKEN_FROM_SAME_LOCATION>'
export PULSE_ACCOUNT='<ACCOUNT_OR_PROGRAM_PUBKEY>'

go run ./examples/sigfirst
# or: go run ./examples/fulltx

Copy the target and token together from the same dashboard location.

The same flow in application code, with github.com/mr-tron/base58 imported for Solana signature text:

ctx := context.Background()

client, err := pulseclient.Connect(
    ctx,
    os.Getenv("PULSE_ADDR"),
    pulseclient.WithToken(os.Getenv("PULSE_TOKEN")),
)
if err != nil {
    log.Fatal(err)
}
defer client.Close()

filter := pulseclient.Accounts(os.Getenv("PULSE_ACCOUNT"))
sub, err := client.SubscribeSigFirst(ctx, filter)
if err != nil {
    log.Fatal(err)
}

for {
    item, err := sub.Next(ctx)
    if err != nil {
        if closeInfo, ok := pulseclient.CloseInfoFromError(err); ok {
            log.Fatalf("feed closed: code=%d reason=%q retry=%s",
                closeInfo.Code, closeInfo.Reason, closeInfo.Retry)
        }
        log.Fatal(err)
    }
    fmt.Println(item.Slot, item.Seq, base58.Encode(item.Signature[:]))
}

TLS and authentication

Connect uses TLS 1.3, ALPN pulse, the target hostname for SNI/certificate verification, and the system root store. The bearer token is sent in the first control message only after the TLS handshake succeeds.

Available connection options:

  • WithToken(token) authenticates the first control message.
  • WithRootCAs(pool) uses an explicit CA pool (for a private CA, for example).
  • WithServerName(name) overrides certificate hostname/SNI when dialing an IP.
  • WithSPKIPinSHA256(pin) adds a 32-byte leaf-SPKI SHA-256 pin on top of normal chain and hostname verification.
  • WithAckTimeout and WithPreambleTimeout customize bounded protocol waits.
  • WithSigQueueCapacity sizes the bounded sig-first handoff queue.
  • WithInsecureTLSForLocalDevelopment() is the only way to disable certificate verification. It is accepted only for literal localhost, 127.0.0.0/8, or ::1 targets and rejected before dialing anything else. Never use it with a production token.

ConnectWithToken remains available as a compatibility convenience:

client, err := pulseclient.ConnectWithToken(ctx, target, token)

QUIC is UDP. TCP port forwarding such as ssh -L does not carry Pulse traffic.

Subscription lifecycle

One Client owns one QUIC connection and accepts exactly one initial SubscribeSigFirst or SubscribeFull call. A second initial subscription returns ErrAlreadySubscribed; create another Client for another feed or tier. Change the active filter with the subscription's UpdateFilter method.

Filters support AccountInclude, AccountExclude, AccountRequired, and the Yellowstone-compatible vote predicate. Accounts(keys...) builds an include filter, and AllNonVoteTxs() requests the unfiltered non-vote feed. An omitted Vote or WithVote(false) selects non-votes; WithVote(true) selects votes only. It does not add votes to non-votes. Receiving both requires two Clients/subscriptions merged by the application.

Delivery semantics

Sig-first

SubscribeSigFirst receives unordered QUIC datagrams containing slot, per-connection sequence, and signature. The SDK drains them into a bounded queue and evicts the oldest local item when the consumer falls behind. QueueStats() reports capacity, current depth, and cumulative local drops. Gaps() is a provisional sequence-gap signal and can over-report when datagrams arrive out of order.

Full transaction

SubscribeFull verifies the stream's six-byte wire-v2 preamble before it returns. Next() yields decoded FullTxV2 frames in QUIC stream order. Pass "alt" to SubscribeFull to request loaded-address enrichment.

Stream ordering is not an end-to-end lossless guarantee. A server-side subscriber queue can discard a transaction before it is written to the QUIC stream, and wire v2 does not put a sequence number on every full-tx frame.

Terminal errors and reconnects

QUIC application closes are returned as *pulseclient.TerminalError, not collapsed to io.EOF. CloseInfoFromError works through wrapped errors.

Code Meaning Retry class
0 Normal close RetryNormal
1 Invalid control message RetryNever
2 Missing, invalid, or revoked token RetryAfterCredentialChange
3 Current quota/capacity exhausted RetryTransient
4 Unsupported wire version RetryNever
5 Tier/filter not entitled RetryNever

Only code 3 is retryable unchanged, and it should use bounded backoff. Code 2 requires credential correction. Do not reconnect-loop on codes 1, 4, or 5.

Compatibility

This release speaks Pulse wire v2 only and requires Go 1.25 or newer. See COMPATIBILITY.md for the wire/package compatibility policy and CHANGELOG.md for release-facing changes.

Documentation

Overview

Package pulseclient connects to the Pulse wire-v2 Solana transaction feed over QUIC.

A Client owns one QUIC connection and permits exactly one initial feed subscription. Create another Client for another feed or tier; use the subscription's UpdateFilter method to change the active filter in place. The default vote predicate delivers non-vote transactions; true selects vote transactions only. Receiving both requires two connections.

Connect verifies the endpoint certificate with the target hostname and the system trust store by default. Private certificate authorities can use WithRootCAs, WithServerName, or an additional WithSPKIPinSHA256 constraint. The only way to disable verification is the explicitly named WithInsecureTLSForLocalDevelopment option, which is enforced to literal loopback targets before dialing.

SubscribeSigFirst receives unordered QUIC datagrams. Its bounded local queue evicts the oldest item when a caller falls behind; QueueStats and Gaps expose pressure and provisional wire loss. SubscribeFull receives ordered frames on one QUIC stream. Ordered transport does not imply end-to-end losslessness: an upstream server queue can discard a transaction before it reaches that stream.

QUIC application closes are returned as *TerminalError, preserving the application code and reason. CloseInfo.Retry provides the reconnect policy; callers must not treat every terminal error as retryable.

Index

Constants

View Source
const (
	MsgTx        uint8 = 1
	MsgHeartbeat uint8 = 2
	// MsgShed is assigned to shed notices, which wire v2 does not emit.
	MsgShed uint8 = 3
)

Frame message types.

View Source
const (
	TLVLoadedWritable uint8 = 1
	TLVLoadedReadonly uint8 = 2
	TLVServerTsMs     uint8 = 3
	TLVHighestSeq     uint8 = 4
)

TLV trailer types.

View Source
const (
	DGSigFirst  uint8 = 1
	DGHeartbeat uint8 = 2
)

Datagram types.

View Source
const (
	// DGSigFirstMin is `u8 type | u64 slot | u64 seq | 64B signature`.
	DGSigFirstMin = 1 + 8 + 8 + 64
	// DGHeartbeatMin is `u8 type | u64 server_ts_ms | u64 highest_seq`.
	DGHeartbeatMin = 1 + 8 + 8
)

Minimum datagram lengths, by type. Each type declares a MINIMUM length, not an exact one: a known type that is long enough parses and trailing bytes are ignored, which is what lets a later wire version add a field without breaking this decoder.

View Source
const AckTimeout = 10 * time.Second

AckTimeout bounds the complete control round-trip: opening the stream, writing the message, and waiting for the server's acknowledgement.

Without it, a peer that accepts the control stream and then neither writes nor closes leaves Subscribe* blocked forever: the connection itself is healthy, so no connection-level error is ever raised to release the read. The server acks as soon as admission completes, so ten seconds is headroom for a slow link rather than an expected admission delay.

View Source
const FlagAltIncomplete uint8 = 0x01

FlagAltIncomplete: the ALT address set on this MsgTx frame may be incomplete. Not a TLV-presence bitmap — a per-frame boolean.

View Source
const MaxFullTxBody = 1 << 16

MaxFullTxBody is the 64 KiB cap applied after a v2 frame's message-type and flags bytes. For a v2 transaction this includes the positional transaction payload and its complete TLV trailer; DecodeFullTx applies the same cap to a standalone positional body.

View Source
const NoSeqAssigned uint64 = ^uint64(0)

NoSeqAssigned is the wire sentinel for a heartbeat's HighestSeq meaning "nothing has been assigned to this subscriber yet". 0 is the first assigned sequence number and cannot also represent "none".

View Source
const Preamble = "PLS2\x02\x00"

Preamble is the immutable six-byte value written once at the head of every full-tx unidirectional stream, before any frame: "PLS2", the version, then a reserved flags byte. A v1 stream's first byte is always 0x00 (the high byte of a u32 big-endian length prefix on a frame capped at 64 KiB), so the non-zero magic is unambiguous. A client can identify a non-v1 server from the first byte.

View Source
const PreambleTimeout = 10 * time.Second

PreambleTimeout bounds the wait for the full-tx stream and its six-byte wire-v2 preamble. A peer that acknowledges the subscription but never opens or writes the stream must not leave SubscribeFull blocked forever.

View Source
const SigQueueLen = 4096

SigQueueLen is the depth of the SDK's internal sig-first handoff queue.

It exists because quic-go holds only 128 datagrams and drops new arrivals once that buffer fills. The SDK therefore drains quic-go continuously into this queue instead of leaving datagrams there until the caller happens to call Next.

View Source
const WireVersion = 2

WireVersion is the wire protocol version carried by the stream preamble.

Variables

View Source
var (
	// ErrNilRootCAs is returned when WithRootCAs is given a nil pool.
	ErrNilRootCAs = errors.New("pulseclient: root CA pool must not be nil")
	// ErrInvalidSPKIPin is returned when an SPKI SHA-256 pin isn't 32 bytes.
	ErrInvalidSPKIPin = errors.New("pulseclient: SPKI SHA-256 pin must be exactly 32 bytes")
	// ErrInvalidTimeout is returned when a configured protocol wait is not positive.
	ErrInvalidTimeout = errors.New("pulseclient: timeout must be greater than zero")
	// ErrInvalidQueueCapacity is returned when the sig-first queue capacity is not positive.
	ErrInvalidQueueCapacity = errors.New("pulseclient: sig-first queue capacity must be greater than zero")
	// ErrConflictingTLSOptions is returned when custom trust or certificate
	// pinning is combined with the explicitly insecure local-development mode.
	ErrConflictingTLSOptions = errors.New("pulseclient: custom trust or SPKI pinning cannot be combined with insecure local-development TLS")
	// ErrInsecureTLSNonLoopback is returned when the local-development-only
	// insecure TLS option is used with a target outside the loopback interface.
	ErrInsecureTLSNonLoopback = errors.New("pulseclient: insecure local-development TLS requires a loopback target")
)
View Source
var ErrAlreadySubscribed = errors.New("pulseclient: this connection already has an initial subscription")

ErrAlreadySubscribed is returned when a Client is used for a second initial subscription. Pulse selects one feed and tier from the first control message; later control messages are filter updates, not new subscriptions.

View Source
var ErrBadFrame = errors.New("pulseclient: malformed full-tx frame")

ErrBadFrame is returned when a full-tx body, v2 frame, or v2 datagram does not match the documented layout (truncated, oversized, or malformed).

View Source
var ErrBadPreamble = errors.New("pulseclient: bad stream preamble: this server is not speaking pulse wire v2")

ErrBadPreamble is returned when the full-tx stream's opening bytes do not match Preamble. The peer is not serving wire v2, or the stream was corrupted in transit. It remains distinct from ErrBadFrame so callers can identify a protocol mismatch during setup.

View Source
var ErrMissingNegotiatedVersion = errors.New("pulseclient: initial acknowledgement omitted the negotiated wire version")

ErrMissingNegotiatedVersion is returned when the initial success ack omits v. Sig-first datagrams carry no per-message version marker, so accepting such an ack would start decoding without proof that the server selected wire v2.

Functions

func ComputeBudgetProgramID

func ComputeBudgetProgramID() [32]byte

ComputeBudgetProgramID returns the 32-byte program ID for ComputeBudget111111111111111111111111111111. It returns a value copy, so callers cannot mutate the package's derived-field matching invariant.

func ComputeUnitLimit

func ComputeUnitLimit(tx *FullTx) (limit uint32, ok bool)

ComputeUnitLimit returns the explicit SetComputeUnitLimit (discriminator 2) only. ok is false when the transaction set no limit; no implicit per-instruction default is applied.

func ComputeUnitPrice

func ComputeUnitPrice(tx *FullTx) (price uint64, ok bool)

ComputeUnitPrice returns micro-lamports per compute unit from SetComputeUnitPrice (discriminator 3). ok is false when the transaction set no price — NOT zero.

func EncodeDGHeartbeat

func EncodeDGHeartbeat(buf []byte, serverTsMs, highestSeq uint64)

EncodeDGHeartbeat encodes a heartbeat datagram into buf, which must be exactly DGHeartbeatMin bytes.

func EncodeDGSigFirst

func EncodeDGSigFirst(buf []byte, slot, seq uint64, sig *[64]byte)

EncodeDGSigFirst encodes a sig-first datagram into buf, which must be exactly DGSigFirstMin bytes.

func EncodeFrameTx

func EncodeFrameTx(ft *FullTx, altIncomplete bool, loadedWritable, loadedReadonly [][32]byte) []byte

EncodeFrameTx encodes a v2 transaction frame: msg_type | flags | v1 body | TLV trailer. The v1 body is reused byte-for-byte; only the framing around it is new. Pass nil/empty slices for a non-enriched subscriber — the trailer is then empty and the frame costs two bytes more than v1.

func EncodeFullTx

func EncodeFullTx(ft *FullTx) []byte

EncodeFullTx is the inverse of DecodeFullTx for the Pulse wire-v2 positional transaction layout.

func FeePayer

func FeePayer(tx *FullTx) (feePayer [32]byte, ok bool)

FeePayer is always the first account key. ok is false for a transaction with no account keys.

func ProgramIDs

func ProgramIDs(tx *FullTx) [][32]byte

ProgramIDs returns every program the transaction invokes, in first-use order, deduplicated. Solana forbids an ALT-sourced program id, so this is complete without any lookup-table resolution.

func StaticWritableAccounts

func StaticWritableAccounts(tx *FullTx) [][32]byte

StaticWritableAccounts returns writable accounts drawn from the STATIC key array only. ALT-loaded writables arrive separately in the frame's LoadedWritable TLV.

Types

type Ack

type Ack struct {
	Type   string                `json:"type,omitempty"`
	OK     bool                  `json:"ok"`
	Reason string                `json:"reason,omitempty"`
	Code   *ApplicationCloseCode `json:"code,omitempty"`
	// V is required on the FIRST control message's ack: the wire
	// version the server actually negotiated
	// (min(client's declared v, the server's max)). Updates normally omit it;
	// when present it must match the established wire version.
	V *int `json:"v,omitempty"`
}

Ack is a parsed `{"type":"ack","ok":bool,...}` control-channel envelope — the server's answer to any control message (first or update).

type AddressTableLookup

type AddressTableLookup struct {
	AccountKey      [32]byte
	WritableIndexes []byte
	ReadonlyIndexes []byte
}

AddressTableLookup is a v0 address-table lookup.

type ApplicationCloseCode

type ApplicationCloseCode uint64

ApplicationCloseCode is a Pulse QUIC application close code.

const (
	CloseNormal             ApplicationCloseCode = 0
	CloseInvalidControl     ApplicationCloseCode = 1
	CloseUnauthenticated    ApplicationCloseCode = 2
	CloseQuotaExceeded      ApplicationCloseCode = 3
	CloseUnsupportedVersion ApplicationCloseCode = 4
	CloseTierNotEntitled    ApplicationCloseCode = 5
)

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a connected Pulse client. A QUIC connection can carry exactly one initial feed subscription; call UpdateFilter on that subscription for later changes, or create another Client for another feed.

func Connect

func Connect(ctx context.Context, addr string, options ...Option) (*Client, error)

Connect dials a Pulse server over QUIC with ALPN "pulse". By default it verifies the server certificate against the target hostname and system root store. Use WithRootCAs, WithServerName, or WithSPKIPinSHA256 for private certificate authorities or additional pinning. Verification can only be disabled with the explicitly named WithInsecureTLSForLocalDevelopment option.

func ConnectWithToken

func ConnectWithToken(ctx context.Context, addr string, token string, options ...Option) (*Client, error)

ConnectWithToken is the token-oriented form of Connect. Additional options configure TLS and protocol bounds. New code may prefer Connect(ctx, addr, WithToken(token), ...).

func (*Client) Close

func (c *Client) Close() error

Close tears down the connection.

func (*Client) SubscribeFull

func (c *Client) SubscribeFull(ctx context.Context, f Filter, fields ...string) (*FullSub, error)

SubscribeFull selects the ordered full-tx stream. fields requests enrichment groups (currently just "alt", which adds each frame's ALT-loaded addresses). Ordered QUIC delivery does not guarantee that an upstream server queue never discards a transaction before it is written to this stream.

The stream's 6-byte preamble is read and verified before the subscription is returned. A mismatch returns ErrBadPreamble.

func (*Client) SubscribeSigFirst

func (c *Client) SubscribeSigFirst(ctx context.Context, f Filter) (*SigFirstSub, error)

SubscribeSigFirst selects the sig-first DATAGRAM tier. This tier has no enrichment fields; use SubscribeFull when loaded-address enrichment is required.

type CloseInfo

type CloseInfo struct {
	Code   ApplicationCloseCode
	Reason string
	Remote bool
	Retry  RetryClass
}

CloseInfo is the structured information carried by a QUIC application close. Retry is derived from Code, not from the human-readable Reason.

func CloseInfoFromError

func CloseInfoFromError(err error) (CloseInfo, bool)

CloseInfoFromError returns Pulse close information from err, including when the terminal error has been wrapped with operation context.

func (CloseInfo) NeedsCredentialChange

func (c CloseInfo) NeedsCredentialChange() bool

NeedsCredentialChange reports whether reconnecting requires a corrected or newly-authorized token.

func (CloseInfo) Retryable

func (c CloseInfo) Retryable() bool

Retryable reports whether an unchanged request may be retried. It is true only for transient quota exhaustion (code 3). Code 2 requires a credential correction first; codes 1, 4, and 5 must not be retried unchanged.

type Datagram

type Datagram interface {
	// contains filtered or unexported methods
}

Datagram is a decoded v2 QUIC datagram: exactly one of SigFirst, DatagramHeartbeat, or UnknownDatagram.

func DecodeDatagram

func DecodeDatagram(src []byte) (Datagram, error)

DecodeDatagram decodes a datagram by its type tag.

Each type declares a MINIMUM length, not an exact one: a known type that is long enough parses, and trailing bytes are ignored. A type this decoder does not recognize is returned as UnknownDatagram rather than an error — that is a deliberate skip. A known type that is too short to parse, or an empty datagram, is rejected as ErrBadFrame: that is the one case a caller must treat as "not decodable", not "a future field I don't understand yet".

type DatagramHeartbeat

type DatagramHeartbeat struct {
	ServerTsMs uint64
	HighestSeq uint64
}

DatagramHeartbeat is a decoded v2 heartbeat datagram (sig-first tier).

type Filter

type Filter struct {
	AccountInclude  []string `json:"account_include,omitempty"`
	AccountExclude  []string `json:"account_exclude,omitempty"`
	AccountRequired []string `json:"account_required,omitempty"`
	// Vote is the Yellowstone-compatible vote predicate. nil and *false select
	// non-vote transactions; *true selects vote transactions only. The
	// predicate ANDs with the account ones, so e.g.
	// account_include=[Vote111…] with Vote=*false yields the empty set.
	Vote *bool `json:"vote,omitempty"`
}

Filter is the account/vote predicate model the server applies. With no account predicates, the zero value subscribes to every non-vote transaction.

func Accounts

func Accounts(keys ...string) Filter

Accounts returns a filter matching transactions that touch any of the given base58 pubkeys / program ids. With the default vote predicate, only matching non-vote transactions are delivered.

func AllNonVoteTxs

func AllNonVoteTxs() Filter

AllNonVoteTxs returns a filter matching every non-vote transaction. Pulse's omitted vote predicate defaults to false, so a single subscription never means both vote and non-vote transactions.

func (Filter) WithVote

func (f Filter) WithVote(isVote bool) Filter

WithVote sets the Yellowstone-compatible vote predicate. true restricts the subscription to vote transactions only; false restricts it to non-vote transactions only. An omitted predicate also defaults to non-votes. To receive both categories, use two Clients and merge their subscriptions. It returns a copy, so it composes with Accounts.

type Frame

type Frame interface {
	// contains filtered or unexported methods
}

Frame is a decoded v2 stream frame: exactly one of FullTxV2, FrameHeartbeat, or UnknownFrame.

func DecodeFrame

func DecodeFrame(src []byte) (Frame, error)

DecodeFrame decodes one v2 frame (the caller has already stripped the u32 big-endian length prefix). Bounds-checked; never panics. A msg_type this decoder does not recognize is returned as UnknownFrame rather than an error — that is a deliberate skip, not a failure.

type FrameHeartbeat

type FrameHeartbeat struct {
	ServerTsMs uint64
	HighestSeq uint64
}

FrameHeartbeat is a decoded v2 heartbeat frame (full-tx tier).

type FullSub

type FullSub struct {
	// contains filtered or unexported fields
}

FullSub is a live full-tx subscription.

func (*FullSub) Heartbeat

func (s *FullSub) Heartbeat() (serverTsMs, highestSeq uint64, ok bool)

Heartbeat returns the most recent heartbeat observed on this stream: (serverTsMs, highestSeq). highestSeq == NoSeqAssigned means the server has not assigned this subscriber a transaction yet. ok is false when no heartbeat has arrived at all yet (a busy stream can go a long time without one — the server resets its heartbeat timer on every real send).

Unlike SigFirstSub.Gaps, the full-tx wire carries no per-frame sequence number, so this SDK cannot compute a numeric gap count for this tier — highestSeq is the raw signal a caller can compare against its own received-frame count if it wants that.

func (*FullSub) Next

func (s *FullSub) Next() (*FullTxV2, error)

Next awaits the next transaction frame. UnknownFrame message types are skipped transparently — a client must never error on a message kind it does not recognize — and FrameHeartbeat frames update FullSub.Heartbeat instead of being returned. Only a *FullTxV2 is ever handed back here. It returns io.EOF at a clean end of stream. A QUIC application close is returned as *TerminalError with its code and reason.

func (*FullSub) UpdateFilter

func (s *FullSub) UpdateFilter(ctx context.Context, f Filter, fields ...string) (*Ack, error)

UpdateFilter updates the active filter/enrichment fields live (opens a fresh control stream) and returns the server's parsed ack, or a *RejectedError if the server refused it. The tier cannot change after the first control message — this always sends full=false and no token, which the server accepts unconditionally on any control message but the first.

type FullTx

type FullTx struct {
	Slot                       uint64
	Versioned                  bool
	NumRequiredSignatures      uint32
	NumReadonlySignedAccounts  uint32
	NumReadonlyUnsignedAccount uint32
	RecentBlockhash            [32]byte
	Signatures                 [][64]byte
	AccountKeys                [][32]byte
	Instructions               []Instruction
	AddressTableLookups        []AddressTableLookup
}

FullTx is a fully-decoded transaction (the full-tx tier payload).

func DecodeFullTx

func DecodeFullTx(b []byte) (*FullTx, error)

DecodeFullTx decodes a full-tx body. It is strict: bounds-checked, rejects truncation and trailing garbage, and never panics.

type FullTxV2

type FullTxV2 struct {
	Tx             FullTx
	AltIncomplete  bool
	LoadedWritable [][32]byte
	LoadedReadonly [][32]byte
}

FullTxV2 is a decoded v2 transaction frame: the v1 body plus its v2 additions.

type InsecureTLSTargetError

type InsecureTLSTargetError struct {
	Host string
}

InsecureTLSTargetError reports a non-loopback target rejected before dialing because WithInsecureTLSForLocalDevelopment was configured.

func (*InsecureTLSTargetError) Error

func (e *InsecureTLSTargetError) Error() string

func (*InsecureTLSTargetError) Unwrap

func (e *InsecureTLSTargetError) Unwrap() error

type Instruction

type Instruction struct {
	ProgramIDIndex uint32
	Accounts       []byte
	Data           []byte
}

Instruction is a compiled instruction within a transaction.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option configures Connect.

Options are validated before any network connection is attempted.

func WithAckTimeout

func WithAckTimeout(timeout time.Duration) Option

WithAckTimeout changes the maximum duration of a control-message round trip. The default is AckTimeout. A caller context with an earlier deadline wins.

func WithInsecureTLSForLocalDevelopment

func WithInsecureTLSForLocalDevelopment() Option

WithInsecureTLSForLocalDevelopment disables certificate and hostname verification. It is intentionally explicit and is rejected before dialing unless the target is localhost, 127.0.0.0/8, or ::1. Never use it with a production token or endpoint.

func WithPreambleTimeout

func WithPreambleTimeout(timeout time.Duration) Option

WithPreambleTimeout changes the maximum wait for the full-tx stream and its wire-v2 preamble. The default is PreambleTimeout. A caller context with an earlier deadline wins.

func WithRootCAs

func WithRootCAs(roots *x509.CertPool) Option

WithRootCAs replaces the system trust store with roots for this connection. The pool is cloned before use. To add a private CA while retaining public roots, start with x509.SystemCertPool, append the CA, and pass that pool here.

func WithSPKIPinSHA256

func WithSPKIPinSHA256(pin []byte) Option

WithSPKIPinSHA256 requires the leaf certificate's SubjectPublicKeyInfo to match the supplied SHA-256 digest in addition to normal chain and hostname verification. The pin is copied before use.

func WithServerName

func WithServerName(name string) Option

WithServerName overrides the DNS name used for certificate verification and SNI. By default Connect derives it from the target host. This is useful when dialing an IP address whose certificate is issued for a DNS name.

func WithSigQueueCapacity

func WithSigQueueCapacity(capacity int) Option

WithSigQueueCapacity changes the bounded sig-first handoff queue capacity. The default is SigQueueLen. Use SigFirstSub.QueueStats to observe pressure.

func WithToken

func WithToken(token string) Option

WithToken authenticates the first wire-v2 control message with token. Pulse tokens are bearer credentials and are sent only after TLS verification succeeds.

type QueueStats

type QueueStats struct {
	Capacity int
	Queued   int
	Dropped  uint64
}

QueueStats is a point-in-time snapshot of the bounded sig-first handoff queue. Dropped is cumulative for the subscription.

type RejectedError

type RejectedError struct{ Reason string }

RejectedError is returned when the server answers a control message with `{"ok":false,...}`. It carries the server's stated Reason so a caller learns *why* a subscribe or UpdateFilter call was refused, rather than getting no error and simply receiving nothing forever.

func (*RejectedError) Error

func (e *RejectedError) Error() string

type RetryClass

type RetryClass uint8

RetryClass describes what must change before reconnecting after a terminal Pulse error.

const (
	// RetryUnknown means the server used an application close code this SDK
	// doesn't recognize. Callers should fail closed instead of looping.
	RetryUnknown RetryClass = iota
	// RetryNormal means the peer closed normally. Reconnect only when the
	// application intends to continue consuming the feed.
	RetryNormal
	// RetryNever means an unchanged reconnect cannot succeed. It covers invalid
	// control messages, unsupported protocol versions, and tier entitlement.
	RetryNever
	// RetryAfterCredentialChange means the token must be supplied, replaced, or
	// re-authorized before reconnecting.
	RetryAfterCredentialChange
	// RetryTransient means the same request may be retried with bounded backoff.
	RetryTransient
)

func ClassifyCloseCode

func ClassifyCloseCode(code ApplicationCloseCode) RetryClass

ClassifyCloseCode returns the reconnect policy for a Pulse application close code. Unknown codes fail closed and return RetryUnknown.

func (RetryClass) String

func (r RetryClass) String() string

type SigFirst

type SigFirst struct {
	Slot      uint64
	Seq       uint64
	Signature [64]byte
}

SigFirst is one sig-first datagram delivery.

type SigFirstItem

type SigFirstItem struct {
	Slot      uint64
	Seq       uint64
	Signature [64]byte
}

SigFirstItem is one sig-first delivery: the transaction's slot, this subscriber's per-connection sequence number (see SigFirstSub.Gaps), and its signature.

type SigFirstSub

type SigFirstSub struct {
	// contains filtered or unexported fields
}

SigFirstSub is a live sig-first subscription.

A goroutine drains the connection into a bounded queue as fast as the network delivers. When a slow Next loop fills the queue, the oldest items are evicted and counted by Dropped.

func (*SigFirstSub) Dropped

func (s *SigFirstSub) Dropped() uint64

Dropped reports how many items were evicted because the caller's Next loop fell behind. Export it: it is the only signal that this is happening, and no kernel or NIC counter will show it.

func (*SigFirstSub) Gaps

func (s *SigFirstSub) Gaps() uint64

Gaps is a provisional loss indicator: item-to-item Seq gaps plus trailing loss revealed by a heartbeat's HighestSeq. NoSeqAssigned on the wire never contributes to this counter.

It can OVER-report under reordering. QUIC DATAGRAMs are unordered by definition, so a scalar high-watermark cannot distinguish "this seq is late" from "this seq is lost" at the moment a later one arrives out of order — it charges one provisional gap on that jump, and never reverses the charge if the late item shows up afterward. A perfectly lossless but reordered stream can therefore report Gaps() > 0. Treat this as "loss happened, or reordering did" rather than an exact count of sequence numbers that never arrived on the wire at all.

func (*SigFirstSub) Next

func (s *SigFirstSub) Next(ctx context.Context) (SigFirstItem, error)

Next blocks for the next SigFirstItem. A clean datagram source end returns io.EOF; a QUIC application close returns *TerminalError with its code and reason; ctx.Err() is returned if ctx ends first.

Do the work for each item elsewhere. This call should be a drain loop: every microsecond spent between two Next calls is queue depth.

func (*SigFirstSub) QueueStats

func (s *SigFirstSub) QueueStats() QueueStats

QueueStats returns the queue capacity, current depth, and cumulative number of evicted items. Sustained depth near Capacity is a backpressure warning.

func (*SigFirstSub) Queued

func (s *SigFirstSub) Queued() int

Queued reports the current depth of the handoff queue. Sustained depth near SigQueueLen means the consumer is about to start losing items.

func (*SigFirstSub) UpdateFilter

func (s *SigFirstSub) UpdateFilter(ctx context.Context, f Filter) (*Ack, error)

UpdateFilter updates the active sig-first filter live (opens a fresh control stream) and returns the server's parsed ack, or a *RejectedError if the server refused it. The tier cannot change after the first control message — this always sends full=false and no token, which the server accepts unconditionally on any control message but the first.

type TerminalError

type TerminalError struct {
	CloseInfo
	// contains filtered or unexported fields
}

TerminalError is returned when a Pulse QUIC connection ends with an application close. It is never collapsed to io.EOF. Use errors.As to access CloseInfo and make a reconnect decision.

func (*TerminalError) Error

func (e *TerminalError) Error() string

func (*TerminalError) Unwrap

func (e *TerminalError) Unwrap() error

type UnknownDatagram

type UnknownDatagram struct{ Type uint8 }

UnknownDatagram carries the type tag of a datagram this decoder does not recognize, so a caller can skip it deliberately rather than erroring.

type UnknownFrame

type UnknownFrame struct{ Type uint8 }

UnknownFrame carries the message type of a frame this decoder does not recognize, so a caller can skip it deliberately rather than erroring — that is what keeps a future wire addition from breaking this client.

type VersionMismatchError

type VersionMismatchError struct{ Negotiated int }

VersionMismatchError is returned when the server's first-control-message ack names a negotiated wire version this SDK does not speak. In practice the server closes the connection outright (code 4) rather than acking success with a version it can't actually serve, so this is a defensive backstop, not the primary version-mismatch signal.

func (*VersionMismatchError) Error

func (e *VersionMismatchError) Error() string

Directories

Path Synopsis
examples
fulltx command
Subscribe to the ordered full-tx tier and print decoded transactions.
Subscribe to the ordered full-tx tier and print decoded transactions.
sigfirst command
Subscribe to the sig-first tier and print (slot, signature) as they arrive.
Subscribe to the sig-first tier and print (slot, signature) as they arrive.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL