castar

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 13 Imported by: 0

README

castar-sdk-go

Go client for Castar wallet verification and attestation.

The SDK supports two request paths:

  • API key mode for server-side integrations that already have a Castar API key.
  • x402 mode for accountless, pay-per-request verification.

In x402 mode the client handles the quote request, EIP-3009 payment authorization, wallet-ownership challenge, and completion request. Private keys stay in your application: the SDK asks your signer interfaces to sign and does not import a chain or wallet dependency.

Install

go get github.com/Castar-Labs/castar-sdk-go

Requires Go 1.22 or newer. The package itself uses only the Go standard library.

Quick Start

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	castar "github.com/Castar-Labs/castar-sdk-go"
)

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

	c, err := castar.New(castar.Options{
		BaseURL: "https://api.castar.xyz",
		Mode:    castar.ModeAPIKey,
		APIKey:  os.Getenv("CASTAR_API_KEY"), // usually "client_id:secret"
	})
	if err != nil {
		log.Fatal(err)
	}

	res, err := c.Verify(ctx, castar.VerifyParams{
		Chain:    57073,
		Asset:    "0xToken",
		Wallet:   "0xWallet",
		MinUnits: "1000000",
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(res.Eligible, res.BalanceAt, res.Attestation)
}

Verification Parameters

Verify takes the same parameters in both modes:

Field Type Description
Chain int Chain ID to check.
Asset string Asset identifier expected by Castar, commonly a token address.
Wallet string Wallet address to verify.
MinUnits string Required balance in atomic units, encoded as a decimal string.

Verify returns:

Field Description
Eligible Whether the wallet satisfies the requirement.
BalanceAt Balance observed by the backend.
Attestation Castar attestation, or nil when the wallet is not eligible.
PaymentID x402 payment ID, set only in x402 mode.
SettlementTx Settlement transaction, set only when an x402 request is charged.

API Key Mode

Use API key mode when your server pays Castar directly and users do not need to sign an x402 payment.

c, err := castar.New(castar.Options{
	BaseURL: "https://api.castar.xyz",
	Mode:    castar.ModeAPIKey,
	APIKey:  os.Getenv("CASTAR_API_KEY"),
})
if err != nil {
	return err
}

res, err := c.Verify(ctx, castar.VerifyParams{
	Chain:    57073,
	Asset:    "0xToken",
	Wallet:   "0xWallet",
	MinUnits: "1000000",
})

The SDK sends the API key as a bearer token. x402 endpoints are not called in this mode.

x402 Mode

Use x402 mode when the caller should pay per verification request.

c, err := castar.New(castar.Options{
	BaseURL: "https://api.castar.xyz",
	Mode:    castar.ModeX402,

	Payer:         payerSigner,
	SubjectSigner: walletSigner, // required when the verified wallet differs from Payer

	MaxSpend:        "100000",
	AllowedNetworks: []string{"eip155:57073"},
	OnPaymentRequired: func(req castar.PaymentRequirements) {
		log.Printf("quote: amount=%s asset=%s network=%s", req.Amount, req.Asset, req.Network)
	},
})
if err != nil {
	return err
}

res, err := c.Verify(ctx, castar.VerifyParams{
	Chain:    57073,
	Asset:    "0xToken",
	Wallet:   walletSigner.Address(),
	MinUnits: "1000000",
})

Before anything is signed, the SDK checks:

  • AllowedNetworks, if set.
  • MaxSpend, if set.

If either guard fails, Verify returns an error and no signature is requested. If the wallet is not eligible, Castar returns Eligible: false and no settlement transaction.

Payer vs Subject Signer

x402 verification can involve two wallets:

  • Payer signs the EIP-3009 TransferWithAuthorization payment.
  • SubjectSigner signs the wallet-ownership challenge for the wallet being verified.

If SubjectSigner is omitted, Payer must also implement ChallengeSigner and Payer.Address() must match VerifyParams.Wallet. If the payer and verified wallet are different, pass SubjectSigner explicitly.

Signer Interfaces

The SDK does not ship a concrete signer. Implement these interfaces with your wallet stack:

type PaymentSigner interface {
	Address() string
	SignTransferWithAuthorization(
		ctx context.Context,
		auth castar.TransferAuthorization,
		domain castar.EIP712Domain,
	) (signatureHex string, err error)
}

type ChallengeSigner interface {
	Address() string
	SignMessage(ctx context.Context, message string) (signature string, err error)
}

For an EVM payer, SignTransferWithAuthorization signs the EIP-712 EIP-3009 authorization. For an EVM verified wallet, SignMessage should return a hex personal_sign / EIP-191 signature for the challenge message.

For a Solana verified wallet, implement only ChallengeSigner: Address() should return the base58 public key and SignMessage should return a base58 ed25519 signature. The x402 payment signer is still the payer.

Quote Without Paying

Quote fetches the x402 payment requirements without signing or paying:

req, err := c.Quote(ctx)
if err != nil {
	return err
}

fmt.Println(req.Amount, req.Asset, req.Network, req.PayTo)

This is useful when you want to show the price before calling Verify.

Options

Field Required Description
BaseURL Yes Castar API base URL, for example https://api.castar.xyz.
Mode Yes castar.ModeAPIKey or castar.ModeX402.
APIKey API key mode Castar API key sent as Authorization: Bearer ....
Payer x402 mode Payment signer for EIP-3009 authorization.
SubjectSigner Sometimes Challenge signer for the verified wallet. Required when Payer cannot sign for VerifyParams.Wallet.
MaxSpend No Decimal atomic-unit cap. Empty string means no cap.
AllowedNetworks No Exact allowlist such as []string{"eip155:57073"}. nil means any quoted network is allowed.
HTTPClient No Custom HTTP client. Defaults to a 30 second timeout.
OnPaymentRequired No Callback invoked after quote guards pass and before signing.

Errors

Expected Castar failures are returned as *castar.Error. Match stable codes with errors.Is:

res, err := c.Verify(ctx, params)
switch {
case err == nil:
	fmt.Println(res.Eligible)
case errors.Is(err, castar.ErrMaxSpendExceeded):
	log.Println("price above MaxSpend; nothing signed")
case errors.Is(err, castar.ErrUnsupportedNetwork):
	log.Println("quoted network is not allowed")
case errors.Is(err, castar.ErrUserRejectedSigning):
	log.Println("signer rejected the request")
case errors.Is(err, castar.ErrPaymentInvalid):
	log.Println("payment was rejected by the backend or facilitator")
default:
	var ce *castar.Error
	if errors.As(err, &ce) {
		log.Printf("castar error: code=%s status=%d message=%s", ce.Code, ce.Status, ce.Message)
	} else {
		log.Printf("unexpected error: %v", err)
	}
}

Common sentinels include:

  • ErrConfig
  • ErrMaxSpendExceeded
  • ErrUnsupportedNetwork
  • ErrUserRejectedSigning
  • ErrPaymentInvalid
  • ErrSettlementFailed
  • ErrChallengeInvalid
  • ErrWalletMismatch
  • ErrPaymentExpired
  • ErrBackend

Example

The repository includes a small standard-library-only example:

CASTAR_API_KEY="client_id:secret" go run ./examples

The x402 branch in examples/main.go uses placeholder signatures so the example can compile without wallet dependencies. Replace demoSigner with a real signer before using x402 against the API.

Development

go test ./...
go test -race ./...
go vet ./...
go build ./...

Examples

A runnable example lives in examples/:

  • examples/main.go — both modes. It uses a placeholder signer so it compiles with the standard library; swap in the go-ethereum signer shown above for real use.
CASTAR_API_KEY=client_id:secret go run ./examples   # business mode
go run ./examples                                    # x402 mode (with a real signer)

License

MIT

Documentation

Overview

Package castar is a client for Castar wallet verification + attestation. Use ModeAPIKey for the business path (an API key, no crypto payment) or ModeX402 to pay per request with the x402 protocol. The x402 handshake, EIP-3009 payment signature, and wallet challenge are handled for you.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrConfig              = &Error{Code: "config_error"}
	ErrMaxSpendExceeded    = &Error{Code: "max_spend_exceeded"}
	ErrUnsupportedNetwork  = &Error{Code: "unsupported_network"}
	ErrUserRejectedSigning = &Error{Code: "user_rejected_signing"}
	ErrBackend             = &Error{Code: "backend_error"}
	ErrPaymentInvalid      = &Error{Code: "payment_invalid"}
	ErrSettlementFailed    = &Error{Code: "settlement_failed"}
	ErrChallengeInvalid    = &Error{Code: "challenge_invalid"}
	ErrWalletMismatch      = &Error{Code: "wallet_mismatch"}
	ErrPaymentExpired      = &Error{Code: "payment_expired"}
)

Sentinels for errors.Is matching.

Functions

This section is empty.

Types

type Attestation

type Attestation struct {
	UID     string `json:"uid"`
	TxHash  string `json:"txHash"`
	ChainID int    `json:"chainId"`
}

type ChallengeSigner

type ChallengeSigner interface {
	Address() string
	SignMessage(ctx context.Context, message string) (signature string, err error)
}

ChallengeSigner signs the wallet-ownership challenge for the wallet being verified. For EVM, the signature is hex; for Solana, base58 ed25519.

type Client

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

func New

func New(opts Options) (*Client, error)

New validates options and returns a Client.

func (*Client) Quote

func (c *Client) Quote(ctx context.Context) (*PaymentRequirements, error)

Quote returns the payment requirements without paying (x402 mode).

func (*Client) Verify

Verify is the one-liner: check the wallet and return the attestation.

type EIP712Domain

type EIP712Domain struct {
	Name              string
	Version           string
	ChainID           int64
	VerifyingContract string
}

EIP712Domain for the EIP-3009 TransferWithAuthorization signature.

type Error

type Error struct {
	Code    string
	Message string
	Status  int // HTTP status for backend errors, 0 otherwise
}

Error is the single error type returned by the SDK. Match on Code with errors.Is(err, castar.ErrMaxSpendExceeded) or inspect the fields directly.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Is

func (e *Error) Is(target error) bool

Is matches by Code, so errors.Is(err, ErrX) works against the sentinels below.

type Mode

type Mode string

Mode selects how a request is paid for.

const (
	ModeAPIKey Mode = "apiKey"
	ModeX402   Mode = "x402"
)

type Options

type Options struct {
	BaseURL string
	Mode    Mode

	// apiKey mode
	APIKey string

	// x402 mode
	Payer           PaymentSigner
	SubjectSigner   ChallengeSigner // optional; defaults to Payer if it can sign messages and is the wallet
	MaxSpend        string          // atomic units; "" = no cap
	AllowedNetworks []string        // e.g. []string{"eip155:57073"}; nil = any

	HTTPClient        *http.Client
	OnPaymentRequired func(PaymentRequirements)
}

Options configure a Client. Build it with New.

type PaymentRequirements

type PaymentRequirements struct {
	Scheme            string `json:"scheme"`
	Network           string `json:"network"`
	Amount            string `json:"amount"`
	Asset             string `json:"asset"`
	PayTo             string `json:"payTo"`
	Resource          string `json:"resource"`
	MaxTimeoutSeconds int    `json:"maxTimeoutSeconds"`
	Description       string `json:"description"`
	Extra             struct {
		Name                string `json:"name"`
		Version             string `json:"version"`
		AssetTransferMethod string `json:"assetTransferMethod"`
	} `json:"extra"`
}

PaymentRequirements is the exact-scheme requirement returned in the 402.

type PaymentSigner

type PaymentSigner interface {
	Address() string
	SignTransferWithAuthorization(ctx context.Context, auth TransferAuthorization, domain EIP712Domain) (signatureHex string, err error)
}

PaymentSigner signs the x402 payment. Implement it with go-ethereum (see the README); the SDK does not pull in a crypto dependency itself.

type TransferAuthorization

type TransferAuthorization struct {
	From        string
	To          string
	Value       string
	ValidAfter  string
	ValidBefore string
	Nonce       string
}

TransferAuthorization is the EIP-3009 message the payer signs.

type VerificationResult

type VerificationResult struct {
	Eligible     bool
	BalanceAt    string
	Attestation  *Attestation
	PaymentID    string // x402 only
	SettlementTx string // x402 only
}

VerificationResult is what Verify returns.

type VerifyParams

type VerifyParams struct {
	Chain    int
	Asset    string
	Wallet   string
	MinUnits string
}

VerifyParams describe what to verify.

Directories

Path Synopsis
Example usage of github.com/Castar-Labs/castar-sdk-go.
Example usage of github.com/Castar-Labs/castar-sdk-go.

Jump to

Keyboard shortcuts

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