tokenguard

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Dec 26, 2025 License: MIT Imports: 8 Imported by: 0

README

solana-token-guard

Go Reference Go Report Card

A token safety screening library for Solana. Check tokens for common red flags before trading.

⚠️ Disclaimer: This library helps identify common risk patterns but cannot guarantee token safety. Always do your own research.

Features

  • 🔐 Authority Checks - Mint and freeze authority status
  • 💧 Liquidity Analysis - LP size, locked percentage
  • 👥 Holder Concentration - Top holder distribution
  • 🍯 Honeypot Detection - Basic sellability checks
  • 📊 Safety Score - 0-100 risk score
  • ⚙️ Configurable Thresholds - Strict, normal, relaxed presets

Installation

go get github.com/Laminar-Bot/solana-token-guard

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/Laminar-Bot/solana-token-guard"
    "github.com/Laminar-Bot/helius-go"
    "github.com/Laminar-Bot/birdeye-go"
)

func main() {
    // Initialize with data providers
    guard := tokenguard.New(tokenguard.Config{
        Helius:  helius.NewClient(helius.Config{APIKey: "..."}),
        Birdeye: birdeye.NewClient(birdeye.Config{APIKey: "..."}),
    })

    // Screen a token
    result, err := guard.Screen(context.Background(), "TokenMintAddress...", tokenguard.LevelNormal)
    if err != nil {
        log.Fatal(err)
    }

    if result.Passed {
        fmt.Printf("✅ Token passed screening (score: %d/100)\n", result.Score)
    } else {
        fmt.Printf("❌ Token failed screening\n")
        for _, check := range result.FailedChecks {
            fmt.Printf("   - %s: %s\n", check.Name, check.Message)
        }
    }
}

Screening Checks

Check Description
mint_authority Is mint authority revoked? (can't print more tokens)
freeze_authority Is freeze authority revoked? (can't freeze wallets)
liquidity Is there enough LP? Is your trade size safe vs LP?
lp_locked Is liquidity locked/burned?
holder_concentration Are tokens distributed or concentrated?
honeypot Basic sellability heuristics

Preset Levels

Strict

Best for automated trading, rejects more tokens.

result, _ := guard.Screen(ctx, token, tokenguard.LevelStrict)
  • Mint/freeze authority: must be revoked
  • Min LP: 50 SOL
  • LP locked: required, 80%+
  • Top 10 holders: <30%
  • Max single holder: <10%
Normal (Default)

Balanced for most use cases.

result, _ := guard.Screen(ctx, token, tokenguard.LevelNormal)
  • Mint/freeze authority: must be revoked
  • Min LP: 20 SOL
  • LP locked: not required
  • Top 10 holders: <50%
  • Max single holder: <20%
Relaxed

For experienced traders, accepts more risk.

result, _ := guard.Screen(ctx, token, tokenguard.LevelRelaxed)
  • Mint/freeze authority: not required
  • Min LP: 5 SOL
  • Top 10 holders: <70%
  • Max single holder: <30%

Custom Thresholds

result, _ := guard.ScreenWithThresholds(ctx, token, tokenguard.Thresholds{
    RequireMintRevoked:   true,
    RequireFreezeRevoked: true,
    MinLPValueSOL:        decimal.NewFromFloat(100),
    RequireLPLocked:      true,
    MinLPLockedPct:       decimal.NewFromFloat(90),
    MaxTop10HolderPct:    decimal.NewFromFloat(25),
    MaxSingleHolderPct:   decimal.NewFromFloat(5),
    MaxPositionPctOfLP:   decimal.NewFromFloat(0.5),
})

Position Size Check

Check if your trade size is safe relative to liquidity:

result, _ := guard.ScreenWithPositionSize(ctx, token, tokenguard.LevelNormal, 
    decimal.NewFromFloat(2.0)) // 2 SOL position

// Will fail if 2 SOL > threshold % of LP

Result Structure

type Result struct {
    Passed       bool      // Overall pass/fail
    Score        int       // 0-100 safety score
    Checks       []Check   // All checks performed
    FailedChecks []Check   // Only failed checks
    Warnings     []string  // Non-fatal warnings
}

type Check struct {
    Name    string      // e.g., "mint_authority"
    Passed  bool
    Value   interface{} // Actual value found
    Message string      // Human-readable result
}

Caching

Results are cached to avoid redundant API calls:

guard := tokenguard.New(tokenguard.Config{
    Helius:   heliusClient,
    Birdeye:  birdeyeClient,
    CacheTTL: 30 * time.Minute, // default
})

// Force fresh data
result, _ := guard.Screen(ctx, token, level, tokenguard.WithNoCache())

Contributing

Contributions are welcome! Please read our Contributing Guide first.

License

MIT License - see LICENSE for details.

Documentation

Overview

Package tokenguard provides token screening and safety analysis for Solana tokens.

The screener performs security checks on Solana tokens before trading, including:

  • Mint/freeze authority status (rug pull risk)
  • Liquidity depth (slippage risk)
  • LP lock percentage (rug pull risk)
  • Holder concentration (manipulation risk)

Results are cached to avoid redundant API calls.

Package tokenguard provides token screening and safety analysis for Solana tokens.

This library performs security checks on Solana tokens before trading, including:

  • Authority validation (mint/freeze authority status)
  • Liquidity analysis (minimum LP thresholds)
  • Holder concentration analysis (top holder distribution)
  • LP lock verification (estimated based on creator holdings)

The library supports three screening levels (Strict, Normal, Relaxed) with configurable thresholds for each check.

Example usage:

guard := tokenguard.New(tokenguard.Config{
    BirdeyeAPIKey: "your-api-key",
})

result, err := guard.Screen(ctx, tokenMint, tokenguard.ScreeningLevelNormal)
if err != nil {
    return err
}

if !result.Passed {
    log.Printf("Token failed screening: %v", result.FailureReasons)
}

Index

Constants

View Source
const DefaultCacheTTL = 5 * time.Minute

DefaultCacheTTL is the default time-to-live for cached screening results.

View Source
const DefaultMaxCacheSize = 10000

DefaultMaxCacheSize is the default maximum number of entries in the cache. With ~1KB per entry, 10,000 entries = ~10MB max memory.

Variables

This section is empty.

Functions

func ValidScreeningLevel

func ValidScreeningLevel(level ScreeningLevel) bool

ValidScreeningLevel checks if a screening level is valid.

Types

type Cache

type Cache interface {
	// Get retrieves a cached screening result if available and not expired.
	Get(ctx context.Context, tokenMint string) (*TokenScreeningResult, bool)

	// Set stores a screening result in the cache.
	Set(ctx context.Context, result *TokenScreeningResult) error
}

Cache provides caching for screening results. This interface allows swapping cache implementations.

type Config

type Config struct {
	// SecurityProvider is used to fetch token security data (required).
	SecurityProvider TokenSecurityProvider

	// OverviewProvider is used to fetch token market data (required).
	OverviewProvider TokenOverviewProvider

	// Cache stores screening results (optional; nil disables caching).
	Cache Cache

	// Logger for structured logging (required).
	Logger *zap.Logger
}

Config holds configuration for creating a new Screener.

type InMemoryCache

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

InMemoryCache provides a simple in-memory cache for screening results.

This implementation is suitable for single-instance deployments. For distributed deployments, use a custom cache implementation.

Features:

  • Thread-safe
  • Configurable TTL
  • Configurable max size (prevents unbounded memory growth)
  • Automatic expiration checks on read
  • Periodic cleanup goroutine (optional)

func NewInMemoryCache

func NewInMemoryCache(cfg InMemoryCacheConfig) *InMemoryCache

NewInMemoryCache creates a new in-memory cache.

func (*InMemoryCache) Clear

func (c *InMemoryCache) Clear(_ context.Context)

Clear removes all entries from the cache.

func (*InMemoryCache) Close

func (c *InMemoryCache) Close() error

Close stops the background cleanup goroutine. Must be called if CleanupInterval was set.

func (*InMemoryCache) Delete

func (c *InMemoryCache) Delete(_ context.Context, tokenMint string)

Delete removes an entry from the cache.

func (*InMemoryCache) Get

func (c *InMemoryCache) Get(_ context.Context, tokenMint string) (*TokenScreeningResult, bool)

Get retrieves a cached screening result.

Returns the result and true if found and not expired. Returns nil and false if not found or expired.

func (*InMemoryCache) Set

Set stores a screening result in the cache.

If the cache is at maximum capacity, it will: 1. First evict all expired entries 2. If still at capacity, evict one entry (effectively random due to map iteration)

This prevents unbounded memory growth if cleanup goroutine fails or TTL is extended.

func (*InMemoryCache) Size

func (c *InMemoryCache) Size() int

Size returns the number of entries in the cache (including expired).

type InMemoryCacheConfig

type InMemoryCacheConfig struct {
	// TTL is the time-to-live for cached entries.
	// Defaults to 5 minutes if zero.
	TTL time.Duration

	// MaxSize is the maximum number of entries in the cache.
	// When the cache is full, expired entries are evicted first,
	// then the oldest entries are evicted to make room.
	// Defaults to 10,000 if zero.
	MaxSize int

	// CleanupInterval is how often expired entries are removed.
	// Set to 0 to disable background cleanup.
	// If enabled, you must call Close() to stop the cleanup goroutine.
	CleanupInterval time.Duration
}

InMemoryCacheConfig holds configuration for InMemoryCache.

type NoOpCache

type NoOpCache struct{}

NoOpCache is a cache implementation that doesn't cache anything. Useful for testing or when caching should be disabled.

func NewNoOpCache

func NewNoOpCache() *NoOpCache

NewNoOpCache creates a new no-op cache.

func (*NoOpCache) Get

Get always returns nil and false (cache miss).

func (*NoOpCache) Set

Set does nothing.

type Screener

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

Screener performs token security analysis before trades.

It checks:

  • Mint/freeze authority status (rug pull risk)
  • Liquidity depth (slippage risk)
  • LP lock percentage (rug pull risk)
  • Holder concentration (manipulation risk)

Results are cached to avoid redundant API calls.

func New

func New(cfg Config) (*Screener, error)

New creates a new token screener.

func (*Screener) Screen

func (s *Screener) Screen(ctx context.Context, tokenMint string, level ScreeningLevel) (*TokenScreeningResult, error)

Screen performs security analysis on a token.

It runs multiple checks based on the specified screening level and returns a result indicating whether the token passes screening.

The screening process:

  1. Check cache for recent results
  2. Fetch token security data (authorities, holders)
  3. Fetch token market data (liquidity)
  4. Run checks against thresholds
  5. Cache and return result

Example:

result, err := screener.Screen(ctx, "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", tokenguard.ScreeningLevelNormal)
if err != nil {
    return err
}
if !result.Passed {
    log.Warn("token failed screening", zap.Strings("reasons", result.FailureReasons))
}

type ScreeningDetails

type ScreeningDetails struct {
	// Authority checks
	HasMintAuthority   bool `json:"hasMintAuthority"`   // Token has active mint authority
	HasFreezeAuthority bool `json:"hasFreezeAuthority"` // Token has active freeze authority

	// Liquidity check
	LiquidityUSD decimal.Decimal `json:"liquidityUsd"` // Total liquidity in USD

	// LP lock check (estimated from creator holdings)
	LPLockedPct decimal.Decimal `json:"lpLockedPct"` // Estimated LP locked percentage

	// Holder concentration checks
	Top10HoldersPct decimal.Decimal `json:"top10HoldersPct"` // % held by top 10 holders
	TopHolderPct    decimal.Decimal `json:"topHolderPct"`    // % held by single top holder

	// Token-2022 specific features
	IsToken2022     bool `json:"isToken2022"`     // Uses Token-2022 program
	HasTransferFee  bool `json:"hasTransferFee"`  // Has transfer fee enabled
	NonTransferable bool `json:"nonTransferable"` // Token is non-transferable (soulbound)
	MutableMetadata bool `json:"mutableMetadata"` // Metadata can be changed
}

ScreeningDetails contains detailed information about each check performed.

type ScreeningLevel

type ScreeningLevel string

ScreeningLevel defines how strict token screening should be.

const (
	// ScreeningLevelStrict requires:
	//   - No mint authority
	//   - No freeze authority
	//   - Min liquidity: $50,000
	//   - Min LP locked: 80%
	//   - Max top 10 holders: 40%
	//   - Max single holder: 15%
	ScreeningLevelStrict ScreeningLevel = "strict"

	// ScreeningLevelNormal requires:
	//   - No mint authority
	//   - No freeze authority
	//   - Min liquidity: $20,000
	//   - Min LP locked: 50%
	//   - Max top 10 holders: 60%
	//   - Max single holder: 25%
	ScreeningLevelNormal ScreeningLevel = "normal"

	// ScreeningLevelRelaxed requires:
	//   - Mint authority allowed
	//   - No freeze authority
	//   - Min liquidity: $5,000
	//   - Min LP locked: 25%
	//   - Max top 10 holders: 75%
	//   - Max single holder: 35%
	ScreeningLevelRelaxed ScreeningLevel = "relaxed"
)

Screening level constants.

type ScreeningThresholds

type ScreeningThresholds struct {
	// RequireNoMintAuth requires that mint authority be revoked/disabled.
	RequireNoMintAuth bool

	// RequireNoFreezeAuth requires that freeze authority be revoked/disabled.
	RequireNoFreezeAuth bool

	// MinLiquidityUSD is the minimum required liquidity in USD.
	MinLiquidityUSD decimal.Decimal

	// MinLPLockedPct is the minimum estimated LP locked percentage.
	// This is estimated as 100% - creator percentage.
	MinLPLockedPct decimal.Decimal

	// MaxTop10HoldersPct is the maximum percentage that can be held by top 10 holders.
	MaxTop10HoldersPct decimal.Decimal

	// MaxTopHolderPct is the maximum percentage that can be held by a single holder.
	MaxTopHolderPct decimal.Decimal
}

ScreeningThresholds defines thresholds for each screening level.

These can be customized per-screening call using ScreenWithThresholds, or use the preset levels (Strict/Normal/Relaxed).

type TokenOverviewProvider

type TokenOverviewProvider interface {
	GetTokenOverview(ctx context.Context, address string) (*birdeye.TokenOverview, error)
}

TokenOverviewProvider provides token market data (liquidity, price, volume). This interface allows mocking in tests.

type TokenScreeningResult

type TokenScreeningResult struct {
	// TokenMint is the token's mint address that was screened.
	TokenMint string `json:"tokenMint"`

	// Passed indicates whether the token passed all checks for the given level.
	Passed bool `json:"passed"`

	// Score is a 0-100 safety score, where higher = safer.
	// Starts at 100 and deducts points for each failed check.
	Score int `json:"score"`

	// Level is the screening level that was applied.
	Level ScreeningLevel `json:"level"`

	// Details contains detailed information about each check performed.
	Details ScreeningDetails `json:"details"`

	// FailureReasons lists human-readable reasons why checks failed.
	// Empty if Passed is true.
	FailureReasons []string `json:"failureReasons,omitempty"`

	// ScreenedAt is when the screening was performed.
	ScreenedAt time.Time `json:"screenedAt"`
}

TokenScreeningResult contains the outcome of token analysis.

type TokenSecurityProvider

type TokenSecurityProvider interface {
	GetTokenSecurity(ctx context.Context, address string) (*birdeye.TokenSecurity, error)
}

TokenSecurityProvider provides token security data (authorities, holder concentration). This interface allows mocking in tests.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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