TypeScriptv0.1.0-betaSolana devnet

SDK Docs

Add real-time threat intelligence to any Solana wallet or dApp in under five minutes. Fully typed, cache-first, zero dependencies beyond the Solana web3 library.

bash
npm install @walour/sdk

Install

Works with any Node 18+ runtime. TypeScript types are bundled.

bash
npm install @walour/sdk
pnpm add @walour/sdk
yarn add @walour/sdk
bun add @walour/sdk

Set these environment variables before calling any SDK function:

bash
ANTHROPIC_API_KEY=...        # for decodeTransaction()
HELIUS_API_KEY=...           # primary RPC + token checks
SUPABASE_URL=...             # threat corpus database
SUPABASE_SERVICE_KEY=...     # service role key
UPSTASH_REDIS_REST_URL=...   # cache layer
UPSTASH_REDIS_REST_TOKEN=...

Quick Start

All SDK functions are stateless exports. Import only what you need.

typescript
import { checkDomain, checkTokenRisk, decodeTransaction } from '@walour/sdk'

// Check a domain before the user signs a transaction from it
const domain = await checkDomain('suspicious-site.xyz')
if (domain.level === 'RED') {
  showWarning(domain.reason)
}

// Check a token mint for rug risk
const token = await checkTokenRisk(mintAddress)
if (token.level === 'RED') {
  console.warn(token.reasons.join(', '))
}

// Stream a human-readable explanation of the transaction
for await (const chunk of decodeTransaction(versionedTx)) {
  appendToUI(chunk)
}
Cache-first on every call. Warm responses return in under 100ms. Cold calls hit GoPlus, Helius, and the on-chain registry in parallel.

checkTokenRisk(mint)

Score a token mint against 8 parallel risk checks. Returns a risk level, a 0-100 score, and a list of reasons for any failures.

typescript
import { checkTokenRisk } from '@walour/sdk'

const result = await checkTokenRisk(mintAddress: string)

interface TokenRiskResult {
  level:   'GREEN' | 'AMBER' | 'RED'
  score:   number        // 0-100, higher = more risk
  reasons: string[]      // human-readable flag descriptions
  checks:  Record<string, {
    passed: boolean
    weight: number
    detail: string
  }>
}

Checks performed

CheckWeightFlags when
Mint authority active15Creator can still mint unlimited supply
Freeze authority active15Creator can freeze holder accounts
Holder concentration8-15Top wallet holds more than 30% of supply
LP lock (Raydium)10Liquidity pool is unlocked or missing
Supply anomaly100 decimals with over 1B supply
Token age8-15Created less than 24h ago
GoPlus honeypot20GoPlus flags as honeypot or blacklisted
Walour corpus hit30Address in threat registry
Cache TTL: 60 seconds. Falls back to AMBER with reason "Risk check unavailable" if the circuit breaker opens.

checkDomain(hostname)

Check a domain against the Walour threat corpus. Falls back to GoPlus if the address is not in the local corpus.

typescript
import { checkDomain } from '@walour/sdk'

const result = await checkDomain('malicious-dapp.xyz')

interface DomainRiskResult {
  level:       'GREEN' | 'AMBER' | 'RED'
  reason:      string
  confidence:  number   // 0-1
  source?:     string   // 'corpus' | 'goplus'
}
Cache TTL: 1 hour. Pass just the hostname, not the full URL.

lookupAddress(pubkey)

Look up any Solana address or domain in the threat corpus. Checks Redis, then Supabase, then the on-chain registry PDA in order.

typescript
import { lookupAddress } from '@walour/sdk'

const threat = await lookupAddress(address: string)
// Returns null if address is clean

interface ThreatReport {
  address:       string
  type:          'drainer' | 'rug' | 'phishing_domain' | 'malicious_token'
  source:        'chainabuse' | 'scam_sniffer' | 'community' | 'twitter'
  confidence:    number        // 0-1
  evidence_url?: string
  first_seen:    string        // ISO 8601
  last_updated:  string        // ISO 8601
}
Cache TTL: 5 minutes. Returns null for clean addresses.

decodeTransaction(tx)

Stream a plain-English explanation of what a transaction does before the user signs it. Powered by Claude Sonnet 4.6. Detects red flags synchronously and streams the AI explanation in parallel.

typescript
import { decodeTransaction } from '@walour/sdk'
import { VersionedTransaction } from '@solana/web3.js'

// Accepts a VersionedTransaction object
for await (const chunk of decodeTransaction(tx: VersionedTransaction)) {
  process.stdout.write(chunk)
}

// In React:
const [explanation, setExplanation] = useState('')

for await (const chunk of decodeTransaction(tx)) {
  setExplanation(prev => prev + chunk)
}
Resolves Address Lookup Tables before analysis. First token arrives in under 400ms. Cache TTL: 24 hours. Falls back gracefully if Claude is unavailable.

submitPrivateReportCloak()

Submit an anonymous threat report using a Cloak UTXO shielded pool with Groth16 proofs. The caller's identity is never linked to the report on-chain.

typescript
import { submitPrivateReportCloak } from '@walour/sdk'

const result = await submitPrivateReportCloak(
  address:    string,          // Solana address or domain
  label:      'drainer' | 'rug' | 'phishing_domain' | 'malicious_token',
  confidence: number,          // 0-1
  options: {
    connection: Connection,
    payer:      Keypair,
    depositLamports?: number   // optional deposit for ZK proof
  }
)

interface PrivateReportCloakResult {
  txSignature: string
  viewingKey:  string          // base64 Groth16 proof
}
Reports are corroborated against existing signals before affecting confidence scores. A single community report never overrides oracle data.

Caching

Every SDK function is cache-first. On a miss the SDK fetches all sources in parallel, writes to Upstash Redis, then returns. Subsequent calls within the TTL window skip all network round-trips.

FunctionTTLWarm latency
checkTokenRisk()60sunder 100ms
lookupAddress()300sunder 100ms
checkDomain()3600sunder 100ms
decodeTransaction()86400sunder 100ms

Bypass cache

Pass skipCache: true in the options argument to any function to force a fresh fetch.

Cache invalidation

When a report is submitted via submitPrivateReportCloak(), the cache entry for that address is evicted immediately so the next lookup reflects the new signal.

Circuit Breakers

Every external provider is wrapped in a circuit breaker. If a provider fails 3 times within 60 seconds, its circuit opens and calls are routed to the next provider in the fallback chain automatically.

RPC fallback chain

PriorityProviderNotes
1HeliusPrimary, enhanced APIs, ALT resolution
2TritonHigh-availability fallback
3Solana public RPCLast resort, rate-limited

Inspect circuit state

typescript
import { getRpcEndpoints } from '@walour/sdk'

// Check which providers are currently healthy
const endpoints = getRpcEndpoints()
// Returns RpcEndpoint[] ordered by priority with circuit state

// Circuit states:
// CLOSED    = healthy, accepting requests
// OPEN      = tripped, routing to next provider
// HALF_OPEN = probing recovery with a single test request