Sign In

@three-ws/agent-sniper

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@three-ws/agent-sniper

Lightweight, embeddable pump.fun sniper engine for 3D AI agents — multi-agent, multi-user, with pluggable wallet/store/RPC adapters. Library + CLI + MCP server + x402 paid API.

Source
npmnpm
Version
0.1.2
Version published
Maintainers
1
Created
Source

@three-ws/agent-sniper

A lightweight, embeddable pump.fun sniper engine for 3D AI agents. One trade loop — feed a stream of new launches, score each against a per-agent strategy, run the pre-trade guards, buy, then sweep open positions on an interval and exit on stop-loss / take-profit / trailing-stop / timeout / sentiment-flip. It is multi-agent and multi-user by construction (one wallet per agentId), and every external dependency — wallet custody, persistence, RPC/venue, launch feed — is a pluggable adapter, so the same engine runs locally with in-memory state or hosted across thousands of tenants. The package ships with four faces over that one engine: a library, a CLI, an MCP server, and an x402 paid HTTP API.

Why — the adapter architecture

The engine is pure orchestration. It owns the decision pipeline; it owns nothing about where the money, the data, or the chain live. Five adapter interfaces (src/types.js) are the seams, and a default implementation ships for each:

                            ┌──────────────────────────── engine ────────────────────────────┐
   Feed ──{mint|intel|claim}─▶  score  ──▶  guards  ──▶  executeBuy  ──▶  claimPosition         │
   (PumpPortal)               (scorer)   (concurrency,    (quote +        (atomic slot)          │
                                          budget, SOL      build + sign                          │
                                          headroom,        + broadcast)                          │
                                          price impact)                                          │
                                                                  position sweep (every pollMs)  │
                                                                  ──▶ decideExit ──▶ executeSell │
                            └────────────────────────────────────────────────────────────────────┘
        adapters:    Feed          Solana            Wallet           Executor          Store
                  (launch        (quote/build      (resolve a       (sign +          (strategies,
                   stream)        pump.fun ix)      Keypair per      broadcast)        positions,
                                                    agentId)                           spend ledger)

Swap any one without touching the loop: a custodial KMS wallet instead of local keys, a Postgres store instead of memory, a different launch feed, a different venue. Implement the shape, pass it to createSniper(), done.

AdapterResponsibility
FeedPush { kind: 'mint'|'intel'|'claim', data: Candidate } events into the engine.
SolanaQuote + build pump.fun buy/sell instructions; expose a web3 Connection.
WalletResolve a signing Keypair for an agentId (or null if unprovisioned).
ExecutorThe one place that signs and broadcasts; returns landing telemetry.
StorePersist strategies, positions, and the daily spend ledger; atomic claimPosition.

Optional Hooks (firewall assessSafety, oracleGate, onScreen, onBuy, onSell, recordDecision) are best-effort cross-cutting callbacks — a throw is swallowed and never aborts a trade, except onTip, which is a deliberate veto.

Install

npm i @three-ws/agent-sniper

Optional peers, installed only if you use the face that needs them:

npm i better-sqlite3   # durable Store (src/adapters/store/sqlite.js)
npm i express          # the x402 HTTP API face

Requires Node >= 20.

Quickstart (library)

The presets.local wiring is the zero-to-running path: in-memory store + self-custody wallet + pump.fun client + web3 executor + PumpPortal feed. Run it in simulate mode on devnet first — the engine scores, guards, and logs every decision but never broadcasts.

A strategy must carry a stop_loss_pct — the engine refuses to arm a strategy without one. SOL amounts on a strategy are expressed in lamports (1 SOL = 1,000,000,000 lamports), as a string, number, or bigint.

import { createSniper, createMemoryStore, presets } from '@three-ws/agent-sniper';

// 1 SOL = 1e9 lamports.
const SOL = 1_000_000_000n;

const strategy = {
  id: 'strat_scout_1',
  agent_id: 'scout',                  // one wallet per agentId
  enabled: true,
  trigger: 'new_mint',                // fire on the create event
  network: 'devnet',
  per_trade_lamports: (SOL / 100n).toString(),   // 0.01 SOL per snipe
  daily_budget_lamports: (SOL / 2n).toString(),  // 0.5 SOL/day ceiling
  max_concurrent_positions: 3,
  slippage_bps: 500,                  // 5%
  max_price_impact_pct: 10,           // entry circuit breaker
  stop_loss_pct: 30,                  // REQUIRED — exit when down 30%
  take_profit_pct: 80,                // exit when up 80%
  trailing_stop_pct: 20,              // exit when down 20% from peak (after profit)
  max_hold_seconds: 1800,             // hard time-stop
  require_socials: true,              // skip launches with no twitter/telegram/website
  max_creator_launches: 10,           // skip serial launchers
};

const sniper = await presets.local({
  network: 'devnet',
  mode: 'simulate',                   // no funds move
  strategies: [strategy],
  // secrets: { scout: '<base58 | [byte,array] | 0x-hex secret>' },  // for live mode
});

await sniper.start();

console.log(sniper.stats());          // { events, candidates, buys, sells, errors, strategies, queued, lastEventAt }
console.log(sniper.strategies());     // the live armed-strategy cache

// Fire a manual buy by hand (drives the same path as MCP snipe_now):
sniper.submitCandidate(
  { mint: 'THREEsynthetic1111111111111111111111111111111', entry_trigger: 'manual' },
  { force: true },
);

// later…
await sniper.stop();

createSniper(deps) is the lower-level constructor when you want to bring your own adapters — pass { config, store, wallet, solana, executor, feed, hooks }. presets.local is just one wiring of it.

CLI usage

The package installs an agent-sniper binary. Default mode is simulate — it will not move funds until you pass --mode live (or set SNIPER_MODE=live), which trades real funds.

# Run the engine. Mode defaults to simulate; live trades real funds.
npx agent-sniper run --network devnet --strategy ./strategy.json

# Serve the engine over MCP (stdio) for Claude / Cursor / any MCP client.
agent-sniper mcp

# Serve the x402-gated HTTP API + web console (open http://localhost:8787).
agent-sniper serve

# Help.
agent-sniper help

Safety. run/serve/mcp all start in simulate mode unless you opt into live explicitly. In live mode every armed strategy can spend up to its daily_budget_lamports per UTC day. Read Safety & guardrails below before you flip the switch.

--strategy ./strategy.json loads a strategy (or array of strategies) as JSON. A minimal file mirrors the object in the Quickstart — note stop_loss_pct is required.

MCP server

agent-sniper mcp exposes the engine as an MCP stdio server. Every tool maps 1:1 onto a public engine/store method — no internals are reached around. The server is unauthenticated and meant to run locally against your own wallet/RPC.

ToolKindPurpose
arm_strategywriteRegister/update a strategy. SOL → lamports for you; stop_loss_pct mandatory.
disarm_strategywriteDisable a strategy (enabled=false); open positions still exit on their rules.
list_strategiesreadList the armed strategy set the engine is evaluating.
snipe_nowwriteForce a manual buy of a mint across all armed agents (bypasses the scorer).
list_positionsreadList positions, filter by agentId and/or status.
close_positionwriteSchedule an exit — flips the position's kill switch; the next sweep sells it.
sniper_statusreadEngine health: event/candidate/buy/sell counts, armed count, queue depth, network/mode.

Wire it into an MCP client (Claude Desktop, Cursor, etc.):

{
  "mcpServers": {
    "agent-sniper": {
      "command": "npx",
      "args": ["-y", "@three-ws/agent-sniper", "mcp"],
      "env": {
        "SNIPER_NETWORK": "devnet",
        "SNIPER_MODE": "simulate"
      }
    }
  }
}

createSniperMcpServer(deps) builds the fully-registered server without connecting a transport, so you can construct it in a test and drive it over an in-memory transport.

x402 paid HTTP API

agent-sniper serve (or import { serve } from '@three-ws/agent-sniper/api') mounts the engine over HTTP. Reads are free; the three mutating endpoints are gated behind x402 USDC micropayments via @three-ws/x402-server — the middleware verifies the X-PAYMENT header against the facilitator, runs the work, settles on-chain, and emits the receipt.

MethodEndpointPriceNotes
GET/healthfreeLiveness + network/mode/stats.
GET/statusfreeFull stats + immutable runtime config.
GET/strategiesfreeThe armed strategy set.
GET/positions?agentId=&status=freePositions from the store.
POST/strategies$0.01 USDCArm a strategy (SOL → lamports; stop-loss required).
POST/snipe$0.05 USDCForce a snipe on { mint, symbol?, agentId? }.
POST/strategies/:id/disarm$0.005 USDCDisable a strategy.

Prices are the defaults (USDC atomic units, 6 decimals); override via deps.prices = { arm, snipe, disarm }.

Gating turns on when a merchant wallet is configured — deps.payTo.solana or the X402_PAY_TO_SOLANA env (a Base lane is available via X402_PAY_TO_BASE). When no payTo is set, the mutating routes still mount but answer 503, so the server boots cleanly for local/dev without payment config.

The Solana lane also needs a facilitator sponsor fee-payer — set X402_FEE_PAYER_SOLANA (or deps.feePayer). Without it the Solana lane self-disables (falling back to the Base lane, or the 503 guard if that's the only lane) rather than failing requests, and logs a one-time warning at boot.

import { serve } from '@three-ws/agent-sniper/api';

await serve(
  { payTo: { solana: process.env.X402_PAY_TO_SOLANA } },
  { port: 8787 },
);

Web console

serve also ships a built-in operator dashboard — a single, dependency-free HTML file served from the same origin as the API, at / and /console. Open http://localhost:8787 and you get a live control room:

  • Live stats — buys, sells, candidates, armed strategies, in-flight queue and signal count, animated as they change, with time-since-last-signal.
  • Positions table — every position with entry, current value, and colored P&L (unrealized total in the header), plus a one-click Close that schedules an exit through the normal sell path.
  • Armed strategies — each policy with its stop-loss / take-profit / trailing tags, and a Disarm button.
  • Arm a strategy and Manual snipe forms with inline validation.
  • Live activity feed — the same engine screen events the CLI prints, streamed in with relative timestamps.
  • Designed loading / empty / error states, toasts, keyboard shortcuts (s snipe, n new strategy, r refresh, , settings, ? help), a disconnected banner, and full mobile responsiveness.

Reads are free. The mutating controls (arm, snipe, disarm, close) are x402-paid for external callers — so to drive them from your own console without paying yourself, set an operator token:

export SNIPER_ADMIN_TOKEN="$(openssl rand -hex 24)"
agent-sniper serve --port 8787

Open Settings (⚙ or ,) in the console and paste the same token. It is sent as X-Admin-Token (or Authorization: Bearer …), stored only in your browser, and bypasses the x402 gate — while unauthenticated external agents still pay. Pass it programmatically as deps.adminToken. Without a token set, the console is a full-featured read-only monitor and mutations return 503 with guidance.

Multi-agent / multi-user

The engine is multi-tenant by design. The unit of isolation is the agentId:

  • One wallet per agent. The Wallet adapter resolves a signing Keypair per agentId. Self-custody reads it (in order) from a secrets map, SNIPER_WALLET_<AGENTID> env, a keystore directory of <agentId>.json files, or a single default SOLANA_SECRET_KEY. If an agent has no provisioned wallet, loadKeypair returns null and the engine fails the trade cleanly — it never auto-provisions an unfunded wallet.
  • Custodial for hosted deployments. createCustodialWallet({ resolve }) takes a single async resolve(agentId, ctx) that decrypts a key from your own KMS/secret box on demand, with a short TTL cache so a long-lived process doesn't re-decrypt every trade. The cache is keyed per agent; clearCache() forces a re-resolve.
  • Per-agent caps. Each strategy carries its own daily_budget_lamports and max_concurrent_positions. The guards enforce them per agent, independent of the platform-wide buy throttle.
  • Mandatory stop-loss. getArmedStrategies filters out any strategy missing stop_loss_pct, so an agent can never run uncapped downside.
  • Idempotent claims prevent double-buys. Before a buy, the engine calls Store.claimPosition to atomically reserve the (agent_id, mint, network) slot. A second event for the same slot returns null and is skipped.

3D agents

@three-ws/agent-sniper/agents is the presentation layer that lets a 3D AI agent embody the sniper: avatar definitions, the canonical animation clip library, and a desk-monitor visualization driven by the engine's onScreen hook. Wire the hook to push live activity (Sniper online…, $SYM scored N — BUYING) onto the agent's monitor so the trading loop is something you can watch, not just a log stream. The onScreen payload shape is { text, kind } with kind in activity | trade.

Adapter reference

Implement any of these (src/types.js has the full JSDoc contracts) and pass it to createSniper. Defaults live under src/adapters/.

InterfaceDefault implementationWrite your own when…
StorecreateMemoryStore · createSqliteStore (./adapters/store/sqlite)you need durable or shared multi-process state (e.g. Postgres).
WalletcreateSelfCustodyWallet · createCustodialWalletkeys live in a KMS/HSM or a hosted secret store.
SolanacreatePumpClient (./adapters/solana/pump-client)you route quotes/builds through a different venue.
ExecutorcreateWeb3Executor (./adapters/solana/executor-web3)you broadcast through a custom relay/bundler.
FeedcreatePumpPortalFeed (./adapters/feed/pumpportal)you ingest launches from a different source.

The Store contract is the load-bearing one: getArmedStrategies, countOpenPositions, getDailySpendLamports, claimPosition (atomic), updatePosition, getOpenPositions, and optional recordSpend. See the in-memory reference at src/adapters/store/memory.js.

Configuration

All SNIPER_* env vars (read by loadConfig, src/config.js). Explicit overrides passed to createSniper/loadConfig always win. See .env.example.

VariableDefaultMeaning
SNIPER_NETWORKmainnetmainnet | devnet.
SNIPER_MODEsimulatesimulate | live. Simulate never broadcasts.
SOLANA_RPC_URLRPC endpoint. Live mode requires this (or HELIUS_API_KEY).
SNIPER_GLOBAL_KILLfalseEmergency switch — halts all buys when true.
SNIPER_POLL_MS5000Position re-quote / exit cadence (floor 1000).
SNIPER_STRATEGY_REFRESH_MS15000Strategy-cache refresh interval (floor 5000).
SNIPER_MAX_GLOBAL_BUYS_PER_MIN10Platform-wide buy throttle; 0 disables.
SNIPER_BUY_CONCURRENCY3Max concurrent in-flight snipes (floor 1).
SNIPER_BUY_QUEUE_DEPTH50Queued-snipe cap before drops (floor 1).
SNIPER_CONFIRM_TIMEOUT_MS60000Confirmation timeout per trade (floor 15000).
SNIPER_FEED_WATCHDOG_MS180000Re-subscribe if the feed goes quiet (floor 30000).
SNIPER_EXIT_ON_BEARISHfalseEnable the sentiment-flip exit.
SNIPER_EXIT_BEARISH_MIN_CONFIDENCE0.7Min bearish confidence (0..1) to act.

Safety & guardrails

  • Simulate by default. Every face starts in simulate mode. You opt into live trading explicitly.
  • Mandatory stop-loss. A strategy without stop_loss_pct is never armed.
  • Daily budget + concurrency caps. Per-agent daily_budget_lamports and max_concurrent_positions are enforced before any transaction is built (checkDailyBudgetLamports, checkConcurrency).
  • SOL fee headroom. checkSolHeadroom keeps ~0.012 SOL in the wallet so a snipe can't drain the account below the cost of the very next sell's fee.
  • Price-impact circuit breaker. max_price_impact_pct (default 10) skips a buy whose quote impact is too high (checkPriceImpact; unset ⇒ no gate).
  • Live-RPC refusal. Live mode refuses to start on a public RPC — it requires a real endpoint so trades aren't silently dropped to rate limits.
  • Optional firewall hook. The assessSafety hook runs after the quote and before broadcast; a block verdict cancels the trade.
  • Global kill switch. SNIPER_GLOBAL_KILL=true (or config.globalKill) halts every buy across every agent immediately.

Scaling beyond one process

claimPosition in the memory and sqlite stores is atomic within one process — the memory store relies on JS being single-threaded; the sqlite store on a single-writer transaction. Combined with the engine's bounded in- process buy queue, that prevents double-buying the same (agent_id, mint, network) slot inside a single sniper instance.

Run two sniper processes against the same agent and that guarantee no longer holds: nothing serializes the claim across processes. To scale horizontally, back the Store with a database whose claimPosition is a genuinely atomic insert-if-absent (e.g. a unique constraint on (agent_id, mint, network) with INSERT … ON CONFLICT DO NOTHING RETURNING), so the reservation is decided by the database, not by any one process. The Store contract documents exactly this requirement.

Testing

Pure-logic and in-memory-adapter suites run with no network/RPC:

npm test            # vitest run

License

MIT — see LICENSE. Copyright © three.ws.

Keywords

solana

FAQs

Package last updated on 12 Jul 2026

Related posts