SENTINELDocs
API reference

Sentinel docs

Sentinel sits between an AI agent and its Solana wallet. It scores the token, builds and simulates the transaction, applies the owner's policy and returns a decision with an unsigned transaction. It never holds keys.

Overview

ItemValue
Base URLhttps://sentinel-clawpump.vercel.app
FormatJSON over HTTPS. Transactions are base64-encoded serialized VersionedTransactions.
AuthNone. The hosted API is free and open, CORS allows any origin.
Rate limit30 requests per minute per IP on scan, execute, check, guard and MCP. Over the limit: 429 with retry-after.
Rules versionsScan scan-v1.1, Bastion bastion-v1.0. Every result carries the version that produced it.
NetworkMainnet. Guard endpoints also accept "cluster": "devnet" for testing.

Quickstart

Replace direct signing with one request. Sign only when decision is allow.

curl -X POST https://sentinel-clawpump.vercel.app/api/execute \
  -H 'content-type: application/json' \
  -d '{
  "intent": { "type": "buy", "wallet": "<agent wallet>", "mint": "<token CA>", "sol": 0.5 },
  "policy": { "limits": { "per_tx_sol": 2 } }
}'

With the SDK, which also checks the transaction locally before signing:

import { Sentinel } from 'clawpump-sentinel';
const sentinel = new Sentinel();
const res = await sentinel.executeAndSend({ type: 'buy', wallet, mint, sol: 0.5 }, agentKeypair, connection);
// res.signature is set only when the decision was "allow" and the local check passed

Decisions

Every check ends in one of four decisions. The strictest reason wins.

DecisionMeaningTransaction returned
allowPassed every check.Yes, unsigned
confirmNeeds the owner's approval, for example a new recipient.Yes, unsigned. Show it to the owner first.
blockFailed a check: a risky token, a limit, a drainer pattern.No
freezeKill-switch: looks like a compromised agent. On a guarded wallet it persists until the owner unfreezes.No

With "mode": "warn" the same analysis runs, enforced is false and the transaction is returned whatever Bastion decides, so you can watch verdicts before enforcing them. A token that fails Scan still returns no transaction.

Execute an intent

POST

/api/execute

rate limited

Scans the token being bought, builds the trade through Jupiter (or a plain SOL transfer), simulates it and applies the policy. Never signs.

Request

FieldTypeNotes
intentobjectOne of the intents below. Required.
policyobjectOptional. Merged over the default policy.
intent.typeFields
buywallet, mint, sol (SOL to spend), optional slippageBps
sellwallet, mint, amount (token units, not raw), optional slippageBps
swapwallet, inputMint, outputMint, amount (input token units), optional slippageBps
transferwallet, to, sol

slippageBps is 1 to 5000, default 100. wallet is the agent's address; the returned transaction has it as fee payer and signer.

Response

{
  "decision": "allow",                 // allow | confirm | block | freeze
  "enforced": true,                    // false in warn mode
  "transaction": "AQAAAA…",            // unsigned base64, null on block / freeze
  "requiresOwnerConfirmation": false,
  "reasons": [],                        // see Reason codes
  "intent": { … },
  "scan": { "mint", "symbol", "score", "verdict", "criticalFlags" },   // null for sells and transfers
  "bastion": { … },                     // full Bastion result, see Check a transaction
  "journalId": "f91daca5-…",
  "durationMs": 1840
}

Check a transaction

POST

/api/bastion/check

rate limited

For agents that build their own transactions. Send one before signing and get Bastion's verdict.

FieldTypeNotes
transactionstringBase64 serialized transaction. Required.
walletstringWhose funds to judge. Defaults to the fee payer.
policyobjectOptional, merged over the default.
{
  "decision": "confirm", "enforced": true, "wallet": "…",
  "reasons": [{ "id": "new_recipient", "action": "confirm", "message": "Transfer of 0.5 SOL to a new address …" }],
  "simulation": { "success": true, "error": null, "logsTail": [], "unitsConsumed": 450 },
  "changes": { "sol": -0.500005, "tokens": [{ "mint", "symbol", "amount", "valueSol" }] },
  "spend": { "txSol", "perTxLimitSol", "day24hSol", "perDayLimitSol", "txLastHour" },
  "programs": [{ "id", "name", "allowed" }],
  "recipients": [{ "address", "amount", "asset", "isNew" }],
  "tokenScans": [{ "mint", "score", "verdict", "critical" }],
  "slippageBps": null,
  "rulesVersion": "bastion-v1.0", "durationMs": 912, "journalId": "…"
}

Bastion runs, in order: simulation, drainer patterns, program allowlist, per-trade and daily limits, recipients, a Scan of every token the wallet receives, effective slippage against a fresh Jupiter quote, and the kill-switch rules.

Score a token

GET

/api/scan/:mint

rate limited

0 to 100 risk score for any SPL or Token-2022 mint on mainnet, with flags and a per-block breakdown. Results are cached for 60 seconds (cached: true).

{
  "mint": "…", "name": "…", "symbol": "…",
  "score": 71, "verdict": "warn", "risk": "medium",
  "criticalFlags": [], "flags": [{ "id", "severity", "message" }],
  "blocks": [{ "id": "holders", "label": "Holders", "weight": 20, "score": 12.1, "status": "ok", "details": { … }, "flags": [] }],
  "rulesVersion": "scan-v1.1", "scannedAt": "…", "durationMs": 1063, "cached": false,
  "disclaimer": "The score is a risk indicator, not financial advice."
}

See Scoring rules for how the score and verdict are computed.

Decision log

GET

/api/journal?wallet=&limit=

Every execute, check, freeze and unfreeze with its reasons and rules version, newest first. limit defaults to 50, max 200.

[{ "id", "at", "kind": "execute", "wallet", "summary": "buy USDC for 0.02 SOL", "decision", "enforced", "reasons", "rulesVersion" }]

Guarded wallets

A guarded wallet is a Squads v4 multisig with threshold 1. The owner has every permission, the agent can propose and execute, Sentinel can only vote. Nothing executes without a vote and the agent has none, so funds move only when Sentinel (or the owner) approves. The check is enforced by the Squads program on-chain.

All guard endpoints are POST, rate limited, and accept "cluster": "mainnet" | "devnet" (default mainnet).

EndpointBodyReturns
/api/guard/setupowner, agent, optional fundSolmultisig, vault, sentinel, telegramLink, transaction for the owner to sign
/api/guard/executemultisig, intent, optional policyDecision, reasons, transactionIndex, approvedBySentinel, approvalUrl on confirm, and the proposal transaction for the agent to sign
/api/guard/finalizemultisig, agent, transactionIndexThe execute transaction once the proposal is approved
/api/guard/statusmultisigMembers and permissions, vaultSol, threshold, frozen
/api/guard/revokemultisig, owner, agentA transaction for the owner that removes the agent, no Sentinel vote needed

Guard intents use agent instead of wallet: buy (mint, sol), sell (mint, amount) and transfer (to, sol). Funds move from the vault.

Owner-side endpoints used by the approval page: /api/guard/proposal, /api/guard/approve, /api/guard/reject, /api/guard/send, /api/guard/unfreeze-message and /api/guard/unfreeze. Unfreezing requires the owner's signature over the message. GET /api/guard/sentinel returns Sentinel's voting key.

Health and defaults

GET

/api/health

Service status and the active rules versions.

GET

/api/policy/default

The default policy as JSON.

Policy

Send any subset; missing fields fall back to the defaults.

FieldDefaultMeaning
limits.per_tx_sol2Max value of one trade, in SOL (tokens sent are valued at market price)
limits.per_day_sol10Max value out of the wallet in 24 hours, this trade included
programs["pump.fun", "jupiter"]Venues the wallet may call directly: pump.fun, jupiter, raydium, orca, meteora or a raw program id. System, token, associated token, compute budget and memo programs are always allowed.
min_token_score60Tokens scoring below are blocked
max_slippage_bps300Max effective slippage against a fresh quote
new_recipient"confirm"allow, confirm or block for transfers to addresses the wallet never paid before
recipients[]Pre-approved recipient addresses
max_tx_per_hour20Reaching it trips the kill-switch
mode"enforce"warn reports decisions without blocking

Reason codes

idActionWhen
simulation_failedblockThe transaction would fail on-chain
token_approveblockGrants someone the right to spend the wallet's tokens. Always blocked.
token_set_authorityblockHands a token account to another owner. Always blocked.
system_assignblockReassigns the wallet to another program. Always blocked.
program_not_allowedblockCalls a program outside the allowlist
per_tx_limitblockTrade value above per_tx_sol
per_day_limitblock24-hour outflow above per_day_sol
new_recipientper policyTransfer to an address the wallet never paid and not in recipients
scan_criticalblockThe token has a critical flag
scan_low_scoreblockThe token scores below min_token_score
scan_unavailableconfirmA received token could not be scanned
slippageblockEffective slippage above max_slippage_bps
kill_amount_spikefreezeTrade value above 5× per_tx_sol
kill_drainfreezeMoves 90% or more of the balance and is above per_tx_sol
kill_frequencyfreezemax_tx_per_hour reached
wallet_frozenfreezeGuarded wallet is frozen until the owner unfreezes it
history_unavailable, history_partialnoteThe daily limit could not count the full 24 hours

Scoring rules

Seven blocks add up to 100. A block whose data source fails gets half its weight and status: "error", so one outage cannot pass or sink a token on its own.

BlockWeightWhat it looks at
Creator20Past launches, dead-token rate, dev dump, wallet age
Holders20Top-10 share, largest wallet, clusters funded from one SOL source
Bundles & snipers15Buys in the first blocks after launch
Authorities15Mint and freeze authority, mutable metadata, Token-2022 extensions
Liquidity15Pool depth, LP lock
Trading anomalies10Sell test (honeypot) and its price impact, wash trading, spikes, crashes
Metadata5Impersonation, duplicate tickers, broken links

Verdict

Critical flags

Each one blocks the token regardless of score:

idMeaning
mint_authority_activeSomeone can still mint new supply. A warning instead of critical for established tokens.
freeze_authority_activeSomeone can freeze holders' tokens. A warning instead of critical for established tokens.
permanent_delegateA Token-2022 delegate can take tokens from any wallet
non_transferableThe token cannot be sold
pausableTransfers can be paused
default_frozenNew token accounts start frozen
no_sell_routeNo route to sell (honeypot)
no_real_liquidityAlmost no liquidity to trade against

Warning flags: bundle_launch, creator_funded_holders, creator_rugs, dev_dump, fresh_creator, funding_cluster, high_price_impact, high_transfer_fee, impersonation, low_liquidity, metadata_mutable, metadata_unreachable, mint_close_authority, no_liquidity, no_sells, price_crash, price_spike, serial_launcher, snipers_heavy, top10_concentrated, transfer_hook, wash_trading_suspected, whale.

The full rules are open source in src/scan.

Benchmarks

Measured on October 1, 2026 with rules scan-v1.1. The samples are small and the numbers will move as the rules change. Both scripts are in the repository, so anyone can rerun them.

Speed

npm run bench scans tokens once each through the hosted API, skipping cached answers, and reads durationMs from the response (server time, without your network).

SetTokensMedian90% underSlowest
Established tokens141.1 s2.1 s2.8 s
Fresh pump.fun launches150.9 s2.0 s2.6 s
All291.0 s2.1 s2.8 s

Accuracy

npm run calibrate scans two labeled sets. Labels come from market outcomes, not from Sentinel.

SetHow it is pickedResult
Established tokensThe most-traded pair of a known ticker with at least 500 trades a day, $200k liquidity and 60 days of history0 of 18 blocked: 15 warn, 3 allow
Dead tokensRecent pump.fun tokens that lost 80% or more and have under $1.5k liquidity, plus four found earlier by hand6 of 6 blocked

What these numbers do and do not show:

Errors and limits

Errors come back as { "error": "message" }.

StatusWhen
400Invalid intent, policy, address or transaction
403Guard: the agent or Sentinel is not a member with the needed permission
404Mint or proposal not found
409Guard: the proposal is not approved yet
422The address is not a token mint, or no route fits a guarded transaction
429Rate limit. Wait for retry-after seconds.
502An upstream source (RPC, Jupiter) failed. Safe to retry.
503The service is missing configuration

Fail closed. Treat any error or timeout as "do not sign". The SDK does this for you: it throws and signs nothing. A guarded wallet cannot move funds without Sentinel's vote at all, while the owner keeps every permission.

TypeScript SDK

npm i clawpump-sentinel @solana/web3.js
MethodWhat it does
scan(mint)Token score
execute(intent, { policy })Decision and unsigned transaction
executeAndSend(intent, signer, connection, { policy, verify })Executes, checks locally, signs and sends only on allow
check(tx, { wallet, policy })Bastion verdict for your own transaction
journal({ wallet, limit })Decision log
guard.setup / execute / finalize / statusGuarded wallet calls
guard.run(params, agent, connection)Propose, check locally, execute in one call

Local check before signing

Before executeAndSend and guard.run sign, the SDK simulates the transaction on your own connection and compares the result with the intent:

A mismatch throws VerificationError and nothing is signed. You can run it on any transaction with verifyTransaction(connection, tx, expectationFor(intent, wallet)). Requests time out after 30 seconds (new Sentinel({ timeoutMs })).

MCP server

Streamable HTTP, stateless. Add it to your agent as a custom connector:

https://sentinel-clawpump.vercel.app/api/mcp
ToolSame as
scan_tokenGET /api/scan/:mint
execute_intentPOST /api/execute
check_transactionPOST /api/bastion/check
guard_setup, guard_execute, guard_finalize, guard_statusThe guard endpoints
decision_logGET /api/journal

MCP and REST clients get the server-side checks. The local check before signing is part of the SDK.