@clawhub-nansen-devops-81d2a0769c
Set up a local webhook server to receive Nansen smart alerts in real-time with HMAC signature verification and public tunneling. Use when a user wants to lis...
---
name: nansen-alerts-webhook-listener
description: Set up a local webhook server to receive Nansen smart alerts in real-time with HMAC signature verification and public tunneling. Use when a user wants to listen for alerts on their local machine.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
- node
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*), Bash(node:*), Bash(npx:*), Bash(ngrok:*), Write
---
# Alert Webhook Listener
Set up a local HTTP server to receive Nansen smart alert webhook payloads in real-time.
## How It Works
Nansen smart alerts support a **webhook** channel type. When an alert fires, Nansen sends an HTTP POST with a JSON payload to your webhook URL. This skill sets up:
1. A local HTTP server (Node.js, zero external dependencies) that receives and displays alert payloads
2. HMAC-SHA256 signature verification so only authentic Nansen payloads are accepted
3. A public tunnel so Nansen's servers can reach your local machine
**This skill does NOT create or modify alerts.** It sets up the listener infrastructure and then provides a summary of what the user needs to do to start receiving alerts.
**OpenClaw users:** If OpenClaw is running locally on the same machine, the webhook server can forward verified alert payloads to OpenClaw's Gateway (`/hooks/agent`), triggering an agent turn for each alert. Set the `OPENCLAW_GATEWAY_URL` env var to enable this. See the **OpenClaw Integration** section below.
## Security Warning
**Before proceeding, inform the user:**
> This skill starts an HTTP server on your machine and exposes it to the internet via a tunnel (ngrok or localtunnel). While the server only binds to localhost (`127.0.0.1`) — meaning no one on your local network can access it directly — the tunnel creates a public URL that **anyone on the internet** can send requests to.
>
> **Mitigations in place:**
> - HMAC-SHA256 signature verification rejects all requests not signed by Nansen
> - 1 MB body size limit prevents memory abuse
> - Only `POST /webhook` and `GET /health` are accepted; everything else returns 404
>
> **You should be aware that:**
> - The tunnel URL is publicly discoverable (ngrok URLs can be enumerated)
> - Unsigned requests still reach your machine — they're rejected, but the connection is made
> - Stop the tunnel when you're done to close the public endpoint
Wait for the user to confirm they want to proceed before continuing.
## Execution Plan
Follow these steps **in order**. Do not skip signature verification — it is mandatory.
### Step 0: Choose a tunnel provider
Before starting, ask the user which tunnel provider they want to use:
| | **ngrok** (recommended) | **localtunnel** |
|---|---|---|
| Stability | Stable — persistent connections with keepalive | Flaky — free relay drops idle connections without warning, tunnels die randomly |
| Install | `brew install ngrok` + free account at ngrok.com | Zero install (`npx localtunnel`) |
| HTTPS | Yes | Yes |
| Auth required | Yes (free authtoken from ngrok.com) | No |
**Recommend ngrok.** localtunnel is convenient but unreliable — in testing, tunnels silently exit after minutes, causing alerts to fail with "503 Tunnel Unavailable". ngrok maintains stable connections.
Check if ngrok is available:
```bash
which ngrok && ngrok version
```
If not installed, tell the user:
1. `brew install ngrok` (or download from ngrok.com)
2. Create a free account at ngrok.com and copy the authtoken
3. `ngrok config add-authtoken <token>`
If the user prefers localtunnel or can't install ngrok, proceed with localtunnel but warn them that the tunnel may drop and they'll need to restart it and update their alert's webhook URL.
### Step 1: Generate a webhook secret
```bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
Store the output — you need it for both the server and the alert configuration. **Never log or echo the secret after this point.**
### Step 2: Write the webhook receiver script
Create `nansen-webhook-server.mjs` in the current working directory. Use **only** Node.js built-in modules (`node:http`, `node:crypto`). No `npm install` required.
**Requirements — do not deviate:**
| Requirement | Detail |
|---|---|
| Bind address | `127.0.0.1` only — **never** `0.0.0.0` |
| Default port | `9477` (override via `PORT` env var) |
| Webhook path | `POST /webhook` — reject all other method/path combos with 404 |
| Health check | `GET /health` → 200 `{"status":"ok"}` |
| Signature verification | Verify `x-nansen-signature` header using HMAC-SHA256 with timing-safe comparison. Reject 401 on mismatch. |
| Secret validation | Exit on startup if `WEBHOOK_SECRET` env var is missing or < 16 chars |
| Payload logging | Pretty-print valid JSON payloads to stdout with ISO timestamp |
| Request size limit | Reject bodies > 1 MB (413) to prevent memory abuse |
| Graceful shutdown | Handle `SIGINT` and `SIGTERM` — close server, then exit |
| OpenClaw forwarding | If `OPENCLAW_GATEWAY_URL` env var is set, forward verified payloads to `<url>/hooks/agent` via POST. Include `OPENCLAW_AUTH_TOKEN` as Bearer token if set. Log forward success/failure. |
| No dependencies | Only `node:http`, `node:https`, and `node:crypto` — nothing from npm |
**Signature verification — use timing-safe comparison:**
```javascript
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifySignature(rawBody, signatureHeader, secret) {
if (!signatureHeader || !secret) return false;
// Nansen sends "sha256=<hex>" — strip the prefix before comparing
const sig = signatureHeader.startsWith('sha256=') ? signatureHeader.slice(7) : signatureHeader;
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
try {
return timingSafeEqual(Buffer.from(sig, 'utf8'), Buffer.from(expected, 'utf8'));
} catch {
return false; // length mismatch
}
}
```
**Full server template:**
```javascript
import { createServer } from 'node:http';
import { createHmac, timingSafeEqual } from 'node:crypto';
const PORT = parseInt(process.env.PORT || '9477', 10);
const SECRET = process.env.WEBHOOK_SECRET;
const MAX_BODY = 1_048_576; // 1 MB
// Optional: forward verified payloads to a local OpenClaw Gateway
const OPENCLAW_URL = process.env.OPENCLAW_GATEWAY_URL; // e.g. http://localhost:3000
const OPENCLAW_TOKEN = process.env.OPENCLAW_AUTH_TOKEN;
if (!SECRET || SECRET.length < 16) {
console.error('WEBHOOK_SECRET env var required (minimum 16 characters).');
console.error('Generate one: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"');
process.exit(1);
}
function verifySignature(rawBody, signatureHeader) {
if (!signatureHeader) return false;
// Nansen sends "sha256=<hex>" — strip the prefix before comparing
const sig = signatureHeader.startsWith('sha256=') ? signatureHeader.slice(7) : signatureHeader;
const expected = createHmac('sha256', SECRET).update(rawBody).digest('hex');
try {
return timingSafeEqual(Buffer.from(sig, 'utf8'), Buffer.from(expected, 'utf8'));
} catch {
return false;
}
}
async function forwardToOpenClaw(payload) {
if (!OPENCLAW_URL) return;
const url = `OPENCLAW_URL.replace(/\/+$/, '')/hooks/agent`;
const headers = { 'Content-Type': 'application/json' };
if (OPENCLAW_TOKEN) headers['Authorization'] = `Bearer OPENCLAW_TOKEN`;
try {
const res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(payload),
});
if (res.ok) {
console.log(`[ts()] Forwarded to OpenClaw (res.status)`);
} else {
console.error(`[ts()] OpenClaw forward failed (res.status)`);
}
} catch (err) {
console.error(`[ts()] OpenClaw forward error: err.message`);
}
}
function ts() { return new Date().toISOString(); }
const server = createServer((req, res) => {
if (req.method === 'GET' && req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
return res.end('{"status":"ok"}');
}
if (req.method !== 'POST' || req.url !== '/webhook') {
res.writeHead(404);
return res.end();
}
let size = 0;
const chunks = [];
req.on('data', (chunk) => {
size += chunk.length;
if (size > MAX_BODY) {
res.writeHead(413);
res.end('{"error":"Payload too large"}');
req.destroy();
return;
}
chunks.push(chunk);
});
req.on('end', () => {
if (res.writableEnded) return;
const rawBody = Buffer.concat(chunks).toString('utf8');
const signature = req.headers['x-nansen-signature'];
if (!verifySignature(rawBody, signature)) {
console.error(`[ts()] REJECTED — invalid signature`);
res.writeHead(401, { 'Content-Type': 'application/json' });
return res.end('{"error":"Invalid signature"}');
}
let payload;
try {
payload = JSON.parse(rawBody);
console.log(`\n[ts()] Alert received:`);
console.log(JSON.stringify(payload, null, 2));
} catch {
console.error(`[ts()] WARNING — valid signature but malformed JSON`);
}
// Forward to OpenClaw if configured (fire-and-forget — don't block response)
if (payload) forwardToOpenClaw(payload);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end('{"received":true}');
});
});
for (const sig of ['SIGINT', 'SIGTERM']) {
process.on(sig, () => {
console.log(`\nsig — shutting down`);
server.close(() => process.exit(0));
});
}
server.listen(PORT, '127.0.0.1', () => {
console.log(`Webhook listener ready — http://127.0.0.1:PORT/webhook`);
if (OPENCLAW_URL) console.log(`OpenClaw forwarding → OPENCLAW_URL/hooks/agent`);
console.log('Waiting for alerts… (Ctrl+C to stop)\n');
});
```
### Step 3: Start the server and tunnel
Start the server:
```bash
WEBHOOK_SECRET='<secret>' node nansen-webhook-server.mjs
```
Then start a public tunnel so Nansen's servers can reach it.
**ngrok (recommended):**
```bash
ngrok http 9477
```
Get the public URL from ngrok's output or its local API:
```bash
curl -s http://127.0.0.1:4040/api/tunnels | node -e "process.stdin.on('data',d=>console.log(JSON.parse(d).tunnels[0]?.public_url))"
```
The webhook URL is `https://<subdomain>.ngrok-free.dev/webhook`.
**localtunnel (fallback — unreliable):**
```bash
npx localtunnel --port 9477
```
Prints a URL like `https://xxx.loca.lt`. The webhook URL is `https://xxx.loca.lt/webhook`.
**Warning:** localtunnel's free relay silently drops connections after minutes. When this happens, all alerts fail with "503 Tunnel Unavailable" until you restart the tunnel and update the alert webhook URL. Use ngrok unless you have a reason not to.
**Note:** Tunnel URLs are ephemeral — they change every restart. For permanent setups, deploy the server to a host with a static URL.
### Step 4: Provide a next-steps summary
**Do NOT create or modify any alerts.** Instead, print a clear summary for the user explaining what was set up and what they need to do next.
The summary MUST include:
1. Confirmation of what was created (the server script path and the generated secret)
2. The commands to start the server and tunnel (with the actual secret filled in)
3. The exact `nansen alerts create` or `nansen alerts update` command they should run, with the `--webhook` and `--webhook-secret` flags filled in with the tunnel URL and secret — but leave the alert-specific flags (`--name`, `--type`, `--chains`, etc.) as placeholders for the user to fill in
4. A reminder that the server and tunnel must be running before the alert is created (Nansen validates the webhook endpoint on creation)
5. A note that tunnel URLs are ephemeral and will change on restart
Example summary format:
```
## Webhook listener ready
**Server script:** ./nansen-webhook-server.mjs
**Port:** 9477
### To start receiving alerts:
1. Start the server (keep this terminal open):
WEBHOOK_SECRET='<actual-secret>' node nansen-webhook-server.mjs
2. In a new terminal, start the tunnel:
ngrok http 9477 # recommended
# or: npx localtunnel --port 9477 (unreliable — tunnel drops silently)
3. Create an alert pointing to your webhook (fill in your alert details):
nansen alerts create \
--name '<your alert name>' \
--type <sm-token-flows|common-token-transfer|smart-contract-call> \
--chains <chains> \
--webhook 'https://<your-tunnel-url>/webhook' \
--webhook-secret '<actual-secret>' \
[type-specific flags...]
Or add the webhook to an existing alert:
nansen alerts update <alert-id> \
--webhook 'https://<your-tunnel-url>/webhook' \
--webhook-secret '<actual-secret>'
Note: The tunnel URL changes each time you restart. Update the alert
webhook URL if you restart the tunnel.
See `nansen alerts create --help` for full flag reference per alert type.
```
## Security Checklist
- **Always use a webhook secret** — the server refuses to start without one
- **Always verify signatures** — never accept unverified payloads
- **Bind to localhost only** — the tunnel handles public exposure; direct `0.0.0.0` binding exposes you to unauthenticated traffic
- **Use HTTPS** — both localtunnel and ngrok tunnel via HTTPS by default
- **Body size limit** — the 1 MB cap prevents memory exhaustion from oversized requests
- **Timing-safe comparison** — prevents timing side-channel attacks on the signature
## Troubleshooting
| Symptom | Fix |
|---|---|
| "Invalid signature" on every request | Ensure the **exact same secret** is in `WEBHOOK_SECRET` and `--webhook-secret` |
| "Failed to send welcome message" on alert create | Start the server and tunnel **before** creating the alert |
| No alerts arriving | Check `nansen alerts list --table` — is the alert enabled? Is the webhook URL correct (includes `/webhook`)? |
| Tunnel URL expired / tunnel died | Restart the tunnel, get the new URL, then `nansen alerts update <id> --webhook '<new-url>/webhook'`. If this keeps happening, switch from localtunnel to ngrok. |
| Port already in use | Set a different port: `PORT=9478 WEBHOOK_SECRET='...' node nansen-webhook-server.mjs` and update the tunnel accordingly |
## OpenClaw Integration
If the user is running OpenClaw locally on the same machine, the webhook server can forward verified alert payloads to OpenClaw's Gateway, triggering an agent turn for each alert.
**Flow:** `Nansen → ngrok → webhook server (signature check) → OpenClaw /hooks/agent`
### Additional env vars
| Var | Required | Purpose |
|-----|----------|---------|
| `OPENCLAW_GATEWAY_URL` | Yes | OpenClaw Gateway base URL (e.g. `http://localhost:3000`) |
| `OPENCLAW_AUTH_TOKEN` | If auth enabled | Bearer token for OpenClaw webhook endpoints |
### Start command (with OpenClaw forwarding)
```bash
WEBHOOK_SECRET='<secret>' \
OPENCLAW_GATEWAY_URL='http://localhost:3000' \
OPENCLAW_AUTH_TOKEN='<token>' \
node nansen-webhook-server.mjs
```
The server logs both the alert payload and the OpenClaw forward status. If OpenClaw is unreachable, the forward fails silently (the alert is still logged to stdout).
### Ask the user
Before enabling OpenClaw forwarding, ask:
1. Is OpenClaw running locally? What port?
2. Does their Gateway require auth? If so, what's the Bearer token?
If they don't know or aren't running OpenClaw, skip — the server works fine standalone.
## Notes
- The server uses zero npm dependencies — only Node.js built-ins
- One server can receive alerts from multiple Nansen alerts (as long as they share the same webhook secret)
- For production use, deploy to a cloud host with a static URL and run behind a reverse proxy with TLS
- The `x-nansen-signature` header format is `sha256=<HMAC-SHA256(secret, rawBody)>` — strip the `sha256=` prefix before comparing
Routing guide -- when to use `nansen agent` (AI research) vs direct CLI data commands. Use when deciding how to answer a user's research question with Nansen...
---
name: nansen-agent-guide
description: Routing guide -- when to use `nansen agent` (AI research) vs direct CLI data commands. Use when deciding how to answer a user's research question with Nansen tools.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Agent vs CLI Routing
| Need a... | Use |
|-----------|-----|
| **take** (analysis, interpretation) | `nansen agent` |
| **table** (raw data, specific metrics) | Direct CLI commands |
| **report** (both) | Agent for narrative + CLI for data |
## Use `nansen agent` when
- Question requires interpretation or synthesis across multiple data sources
- Open-ended research: "analyse this wallet", "what's happening with ETH smart money?"
```bash
nansen agent "What are top smart money tokens on Solana today and why?"
nansen agent "Analyse wallet 0x123... -- is this a smart trader?"
nansen agent "..." --expert # deeper analysis, 750 credits
```
Cost: 200 credits (fast) / 750 credits (expert)
## Use direct CLI commands when
- You need specific structured data -- prices, volumes, holders, flows
- Deterministic question: "top 10 tokens by netflow on ethereum"
- Piping output or building a data table
```bash
nansen research token screener --chain ethereum --smart-money --limit 10
nansen research smart-money netflow --chain solana
nansen research profiler balance --address 0x123... --chain ethereum
```
Cost: 5-50 credits per call
## Anti-patterns
- Don't use `nansen agent` for simple data fetches -- 40x more expensive
- Don't use raw CLI for open-ended analysis -- returns data, not interpretation
- Don't chain 3+ agent calls -- get raw data via CLI, call agent once for synthesis
Search the web for one or more queries in parallel. Use when you need current information, news, prices, or any web content to complement on-chain Nansen data.
---
name: nansen-web-searcher
description: Search the web for one or more queries in parallel. Use when you need current information, news, prices, or any web content to complement on-chain Nansen data.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Web Search
Search the web for one or more queries in parallel via the Serper API.
```bash
nansen web search "bitcoin price"
nansen web search "solana ecosystem news" --num-results 5
nansen web search --query "ethereum ETF" --query "bitcoin ETF" --num-results 3
```
Positional args and `--query` flags can be combined — all become queries.
| Flag | Values | Default | Purpose |
|------|--------|---------|---------|
| `--query` | string | — | Query string (repeatable for multiple queries) |
| `--num-results` | 1–20 | 10 | Results per query |
| `--pretty` | flag | off | Human-readable JSON |
Returns `results[]` — one entry per query, each with `organic[]` (title, link, snippet, date) and optional `knowledge_graph`.
**Note:** Some domains are excluded from results (paywalled/unfetchable sites like bloomberg.com, twitter.com). Use `nansen web fetch` to retrieve content from specific URLs.
Fetch and analyze content from one or more URLs using AI (Gemini 2.5 Flash). Use when you have specific URLs and need to extract or summarize their content....
---
name: nansen-web-fetcher
description: Fetch and analyze content from one or more URLs using AI (Gemini 2.5 Flash). Use when you have specific URLs and need to extract or summarize their content. Pairs well with `nansen web search` results.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Web Fetch
Fetch and analyze content from one or more URLs using Gemini 2.5 Flash with URL context.
```bash
nansen web fetch https://nansen.ai --question "What products does Nansen offer?"
nansen web fetch --url https://example.com --url https://other.com --question "Compare these two sites"
nansen web fetch https://docs.uniswap.org/contracts/v4/overview --question "What changed in v4?"
```
Positional args and `--url` flags can be combined — all become URLs to fetch.
| Flag | Values | Default | Purpose |
|------|--------|---------|---------|
| `--url` | URL | — | URL to fetch (repeatable for multiple URLs, up to 20) |
| `--question` | string | **required** | Question to answer about the URL content |
| `--pretty` | flag | off | Human-readable JSON |
Returns:
- `analysis` — AI-generated answer to your question
- `retrieved_urls` — URLs successfully fetched
- `failed_urls` — URLs that could not be retrieved
**Tip:** Combine with `web search` — search first to find relevant URLs, then fetch to get full content.
```bash
# Find and analyze in two steps
nansen web search "uniswap v4 launch" --num-results 3 --fields link
nansen web fetch https://blog.uniswap.org/... --question "What are the key changes?"
```
**Note:** 30s timeout. Paywalled or bot-blocked pages may appear in `failed_urls`.
Wallet profiler — balance, PnL, labels, transactions, counterparties, related wallets, batch, trace, compare. Use when analysing a specific wallet address or...
---
name: nansen-wallet-profiler
description: Wallet profiler — balance, PnL, labels, transactions, counterparties, related wallets, batch, trace, compare. Use when analysing a specific wallet address or comparing wallets.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Wallet Profiler
All commands: `nansen research profiler <sub> [options]`
`--address` and `--chain` required for most commands.
## Balance & Identity
```bash
nansen research profiler balance --address <addr> --chain ethereum
nansen research profiler labels --address <addr> --chain ethereum
nansen research profiler search --query "Vitalik"
```
## PnL
```bash
nansen research profiler pnl --address <addr> --chain ethereum --days 30
nansen research profiler pnl-summary --address <addr> --chain ethereum
```
## Transactions & History
```bash
nansen research profiler transactions --address <addr> --chain ethereum --limit 20
nansen research profiler historical-balances --address <addr> --chain solana --days 30
```
## Relationships
```bash
nansen research profiler related-wallets --address <addr> --chain ethereum
nansen research profiler counterparties --address <addr> --chain ethereum --days 30
```
## Perps (no --chain)
```bash
nansen research profiler perp-positions --address <addr>
nansen research profiler perp-trades --address <addr> --days 7
```
## Batch, Trace & Compare
```bash
# Batch — profile multiple wallets at once
nansen research profiler batch \
--addresses "0xabc,0xdef" --chain ethereum \
--include labels,balance,pnl
# Trace — BFS multi-hop counterparty trace (makes N*width API calls)
nansen research profiler trace --address <addr> --chain ethereum --depth 2 --width 5
# Compare — shared counterparties and tokens between two wallets
nansen research profiler compare --addresses "0xabc,0xdef" --chain ethereum
```
## Flags
| Flag | Purpose |
|------|---------|
| `--address` | Wallet address (required) |
| `--chain` | Required except for perps and search |
| `--days` | Lookback period (default 30) |
| `--limit` | Number of results |
| `--include` | Batch fields: `labels,balance,pnl` |
| `--depth` | Trace depth 1-5 (default 2) |
| `--width` | Trace width — keep low to save credits |
| `--fields` | Select specific fields |
| `--table` | Human-readable table output |
| `--format csv` | CSV export |
## Notes
- `pnl-summary` has no pagination support (returns aggregate stats, not a list).
- `perp-positions` has no pagination support.
- `labels` has no pagination support — the API ignores `per_page` and always returns all labels for the address. `--limit` is not available for this sub-command.
- `transactions` caps at per_page=100 (API limit).
- `trace` makes many API calls — use `--width` conservatively.
- `batch` accepts `--file <path>` with one address per line as alternative to `--addresses`.
Wallet management — create (local or Privy server-side), list, show, export, send, delete. Use when creating wallets, checking balances, or sending tokens.
---
name: nansen-wallet-manager
description: Wallet management — create (local or Privy server-side), list, show, export, send, delete. Use when creating wallets, checking balances, or sending tokens.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Wallet
## Auth Setup
```bash
# Save API key (non-interactive)
nansen login --api-key <key>
# Or via env var:
NANSEN_API_KEY=<key> nansen login
# Verify
nansen research profiler labels --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --chain ethereum
```
## Wallet Providers
The CLI supports two wallet providers:
| | **Local** (default) | **Privy** (server-side) |
|---|---|---|
| Key storage | Encrypted on disk | Server-side via Privy API |
| Password required | Yes (min 12 chars) | No |
| Export private keys | Yes (`wallet export`) | No — keys are managed by Privy |
| Best for | Human users, manual trading | Agents, automated workflows |
| Flag | `--provider local` (default) | `--provider privy` |
| Required env vars | `NANSEN_WALLET_PASSWORD` | `PRIVY_APP_ID` + `PRIVY_APP_SECRET` |
## Privy Wallet Creation
Privy wallets are server-side wallets managed by the Privy API. No password is needed — keys never touch the local machine.
### Prerequisites
The following environment variables must be set:
| Var | Purpose |
|-----|---------|
| `PRIVY_APP_ID` | Privy application ID |
| `PRIVY_APP_SECRET` | Privy application secret |
### Create a Privy wallet
```bash
nansen wallet create --provider privy
# Or with a custom name:
nansen wallet create --name agent-wallet --provider privy
```
### Critical rules for agents (Privy)
- **No password needed** — Privy manages keys server-side
- **Cannot export keys** — `wallet export` only works for local wallets
- All other operations (`list`, `show`, `send`, `delete`, `default`) work identically for both providers
## Local Wallet Creation (Two-Step Agent Flow)
> This section covers **local** wallet creation. For Privy server-side wallets, see the [Privy Wallet Creation](#privy-wallet-creation) section above — no password is needed.
Wallet creation requires a password from the **human user**. The agent must NOT generate or store the password itself.
> **Step 1 (Agent → Human):** Ask the user to provide a wallet password (minimum 12 characters).
>
> **Step 2 (Agent executes):** Run the create command with the password the user gave you.
```bash
NANSEN_WALLET_PASSWORD="<password_from_user>" nansen wallet create
```
After creation, the CLI automatically saves the password:
- **OS keychain** (macOS Keychain, Linux secret-tool, Windows Credential Manager) — secure, preferred
- **~/.nansen/wallets/.credentials file** — insecure fallback when no keychain is available (e.g. containers, CI)
**All future wallet operations retrieve the password automatically** — no env var or human input needed.
If the `.credentials` file fallback is used, the CLI prints a warning on every operation. To migrate to secure storage later, run `nansen wallet secure`.
### Password resolution order (automatic)
1. `NANSEN_WALLET_PASSWORD` env var (if set)
2. OS keychain (saved automatically on wallet create)
3. `~/.nansen/wallets/.credentials` file (insecure fallback, with warning)
4. Structured JSON error with instructions (if none available)
### Critical rules for agents
- **NEVER generate a password yourself** — always ask the human user
- **NEVER store the password** in files, memory, logs, or conversation history
- **NEVER use `--human` flag** — that enables interactive prompts which agents cannot handle
- After wallet creation, you do NOT need the password for future operations — the keychain handles it
- If you get a `PASSWORD_REQUIRED` error, ask the user to provide their password again
## Create
### Privy (server-side, no password)
```bash
nansen wallet create --provider privy
# Or with a custom name:
nansen wallet create --name trading --provider privy
```
Requires `PRIVY_APP_ID` + `PRIVY_APP_SECRET` env vars. No password needed.
### Local (encrypted on disk, password required)
```bash
# Ask the user for a password first, then:
NANSEN_WALLET_PASSWORD="<password_from_user>" nansen wallet create
# Or with a custom name:
NANSEN_WALLET_PASSWORD="<password_from_user>" nansen wallet create --name trading
```
## List & Show
```bash
nansen wallet list
nansen wallet show <name>
nansen wallet default <name>
```
## Send
```bash
# Send native token (SOL, ETH) — password auto-resolved from keychain
nansen wallet send --to <addr> --amount 1.5 --chain solana
# Send entire balance
nansen wallet send --to <addr> --chain evm --max
# Dry run (preview, no broadcast)
nansen wallet send --to <addr> --amount 1.0 --chain evm --dry-run
```
## Export & Delete
```bash
# Password auto-resolved from keychain
nansen wallet export <name>
nansen wallet delete <name>
```
## Forget Password
```bash
# Remove saved password from all stores (keychain + .credentials file)
nansen wallet forget-password
```
## Migrate to Secure Storage
```bash
nansen wallet secure
```
For detailed migration steps (from `~/.nansen/.env`, `.credentials`, or env-var-only setups), see the **nansen-wallet-migration** skill.
## Flags
| Flag | Purpose |
|------|---------|
| `--to` | Recipient address |
| `--amount` | Amount to send |
| `--chain` | `evm` or `solana` |
| `--max` | Send entire balance |
| `--dry-run` | Preview without broadcasting |
| `--provider` | Wallet provider: `local` (default, encrypted on disk) or `privy` (server-side via Privy API) |
| `--human` | Enable interactive prompts (human terminal use only — agents must NOT use this) |
| `--unsafe-no-password` | Skip encryption (keys stored in plaintext — NOT recommended) |
## Environment Variables
| Var | Purpose |
|-----|---------|
| `NANSEN_WALLET_PASSWORD` | Wallet encryption password — only needed for initial `wallet create`. After that, the OS keychain handles it. |
| `NANSEN_API_KEY` | API key (also set via `nansen login --api-key <key>`) |
| `PRIVY_APP_ID` | Privy application ID (required for `--provider privy`) |
| `PRIVY_APP_SECRET` | Privy application secret (required for `--provider privy`) |
| `NANSEN_WALLET_PROVIDER` | Default provider for wallet create — `local` or `privy` |
| `NANSEN_EVM_RPC` | Custom EVM RPC endpoint |
| `NANSEN_SOLANA_RPC` | Custom Solana RPC endpoint |
Migrate an existing nansen-cli wallet from insecure password storage (env files, .credentials) to the new secure keychain-backed flow.
---
name: nansen-wallet-keychain-migration
description: Migrate an existing nansen-cli wallet from insecure password storage (env files, .credentials) to the new secure keychain-backed flow.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Wallet Migration — Old Flow to Secure Keychain
Use this skill when a user already has a nansen-cli wallet set up with the
**old** password storage method and wants to migrate to the new secure flow.
## When to use
- User mentions they stored their password in `~/.nansen/.env`, a `.env` file, or `memory.md`
- User gets the stderr warning: `⚠ Password loaded from insecure .credentials file`
- User asks to "secure my wallet" or "migrate to keychain"
- User created a wallet before the keychain update was released
## Detect current state
`wallet show` only displays addresses and does NOT load or check the password.
To detect the actual password situation, check for stored password sources:
```bash
# 1. Check if a wallet exists at all
nansen wallet list 2>&1
# 2. Check for insecure password stores
ls -la ~/.nansen/.env 2>/dev/null && echo "FOUND: ~/.nansen/.env (insecure)"
ls -la ~/.nansen/wallets/.credentials 2>/dev/null && echo "FOUND: .credentials file (insecure)"
# 3. Try an operation that requires the password (without setting env var)
nansen wallet export default 2>&1
```
Interpret the `export` output:
- `⚠ Password loaded from ~/.nansen/wallets/.credentials` on stderr → needs migration (Path B)
- Export succeeds silently → password is in keychain, no migration needed
- `PASSWORD_REQUIRED` JSON error → password not persisted anywhere (Path C or D)
## Migration paths
### Path A: Password in `~/.nansen/.env` (old skill pattern)
The previous wallet skill told agents to write the password to `~/.nansen/.env`.
**Step 1 — Ask the human for their password:**
> "Your wallet password is currently stored in ~/.nansen/.env, which is insecure.
> I can migrate it to your OS keychain. Please confirm the password you used when
> creating the wallet, or I can read it from ~/.nansen/.env if you authorize it."
**Step 2 — Migrate:**
The `source` and `nansen wallet secure` MUST run in the same shell so the env
var is available to the node process:
```bash
source ~/.nansen/.env 2>/dev/null && nansen wallet secure
```
**Step 3 — Verify the password actually decrypts the wallet:**
```bash
# Unset env var to prove keychain works, then export to verify decryption
unset NANSEN_WALLET_PASSWORD
nansen wallet export default 2>&1
```
If export succeeds (shows private keys), the migration worked. If it shows
`Incorrect password`, the wrong password was migrated — run `nansen wallet
forget-password` and retry with the correct password.
**Step 4 — Clean up the insecure file:**
```bash
rm -f ~/.nansen/.env
```
### Path B: Password in `.credentials` file (auto-saved fallback)
This happens when `wallet create` couldn't access the OS keychain (containers, CI).
```bash
nansen wallet secure
```
If the keychain is still unavailable (e.g. containerized Linux without D-Bus),
`nansen wallet secure` will explain the situation and suggest alternatives.
After migrating, verify decryption works:
```bash
nansen wallet export default 2>&1
```
### Path C: Password only in `NANSEN_WALLET_PASSWORD` env var
```bash
# Persist the env var password to keychain
nansen wallet secure
```
Then verify without the env var:
```bash
unset NANSEN_WALLET_PASSWORD
nansen wallet export default 2>&1
```
### Path D: Password lost entirely
The password cannot be recovered. The wallet's private keys are encrypted with
AES-256-GCM and the password is not stored anywhere recoverable.
**Tell the human:**
> "Your wallet password cannot be recovered. If you have funds in this wallet,
> they may be inaccessible. You can create a new wallet and transfer any remaining
> accessible funds."
```bash
# Create a fresh wallet (human must provide a new password)
NANSEN_WALLET_PASSWORD="<new_password_from_user>" nansen wallet create --name new-wallet
```
## Post-migration verification
After any migration, confirm the password was migrated correctly by proving
the keychain password can actually decrypt the wallet:
```bash
# Unset env var to prove keychain works
unset NANSEN_WALLET_PASSWORD
# This MUST succeed — it proves the keychain password decrypts the wallet
nansen wallet export default 2>&1
```
If export shows `Incorrect password`, the wrong password was saved to the
keychain. Fix with:
```bash
nansen wallet forget-password
NANSEN_WALLET_PASSWORD="<correct_password>" nansen wallet secure
```
If `stderr` still shows the `.credentials` warning, the keychain migration did
not succeed — check if the OS keychain service is running (`secret-tool` on Linux,
`security` on macOS).
## Forget password (all stores)
If the user wants to remove their persisted password entirely:
```bash
nansen wallet forget-password
```
This clears the password from both OS keychain and `.credentials` file. Future
wallet operations will require `NANSEN_WALLET_PASSWORD` env var or re-running
`nansen wallet secure`.
## Critical rules for agents
- **NEVER generate a password** — always ask the human
- **NEVER store the password** in files, memory, logs, or conversation history
- **NEVER use `--human` flag** — interactive prompts break agents
- If the human authorizes reading `~/.nansen/.env`, read it in the same command
(`source ~/.nansen/.env && nansen wallet secure`) — do not echo or log the value
- **ALWAYS verify after migration** with `nansen wallet export default` — `wallet show` does NOT prove the password works (it never loads the password)
Who is this wallet and what have they been doing? Identity labels, balance, PnL summary, recent transactions, perp positions, and counterparties.
---
name: nansen-wallet-deep-dive
description: "Who is this wallet and what have they been doing? Identity labels, balance, PnL summary, recent transactions, perp positions, and counterparties."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Wallet Analysis
**Answers:** "Who is this wallet and what have they been doing?"
```bash
ADDR=<address> CHAIN=ethereum
nansen research profiler labels --address $ADDR --chain $CHAIN
# → label, category (e.g. "Smart Trader", "Fund", "Public Figure", ENS names)
nansen research profiler balance --address $ADDR --chain $CHAIN
# → token_symbol, token_name, token_amount, price_usd, value_usd per holding
nansen research profiler pnl-summary --address $ADDR --chain $CHAIN --days 30
# → realized_pnl_usd, realized_pnl_percent, win_rate, traded_token_count, traded_times, top5_tokens
nansen research profiler transactions --address $ADDR --chain $CHAIN --limit 20
# → block_timestamp, method, tokens_sent, tokens_received, volume_usd, source_type
nansen research profiler perp-positions --address $ADDR
# → asset_positions, margin_summary_account_value_usd, margin_summary_total_margin_used_usd
nansen research profiler counterparties --address $ADDR --chain $CHAIN --days 30
# → counterparty_address, counterparty_address_label, interaction_count, total_volume_usd, volume_in/out_usd
```
perp-positions returns Hyperliquid data — returns empty if the wallet has no open perps.
Cluster and attribute related wallets — funding chains, shared signers, CEX deposit patterns. Use when tracing wallet ownership, comparing two wallets, findi...
---
name: nansen-wallet-clustering
description: "Cluster and attribute related wallets — funding chains, shared signers, CEX deposit patterns. Use when tracing wallet ownership, comparing two wallets, finding wallet relationships, governance voters, or related address clusters."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Wallet Attribution
**Answers:** "Who controls this wallet? Are these wallets related?"
Chain: `0x` → `--chain ethereum` (also base, arbitrum, optimism, polygon). Base58 → `--chain solana`.
```bash
ADDR=<address> CHAIN=<ethereum|solana|base|...> # detect from address format above
# 1. Identity
nansen research profiler labels --address $ADDR --chain $CHAIN
# 2. Related wallets (paginate with --page N)
nansen research profiler related-wallets --address $ADDR --chain $CHAIN
# 3. Counterparties (paginate with --page N; widen with --days 365 if empty)
nansen research profiler counterparties --address $ADDR --chain $CHAIN --days 90
# 4. Batch profile cluster
nansen research profiler batch --addresses "addr1,addr2" --chain $CHAIN --include labels,balance,pnl
# 5. Compare pairs → shared_counterparties, shared_tokens, balances
nansen research profiler compare --addresses "addr1,addr2" --chain $CHAIN
# 6. Historical balances (fingerprint drained wallets)
nansen research profiler historical-balances --address $ADDR --chain $CHAIN --days 90
# 7. Multi-hop trace (credit-heavy — keep --width ≤3)
nansen research profiler trace --address $ADDR --chain $CHAIN --depth 2 --width 3
```
**Expansion:** Run steps 1-2 on seed. For each new address found, ask the human before querying. Reserve step 3 for seed only.
**Stop when:** known protocol/CEX · Low confidence · already visited · cluster > 10 wallets.
**Confidence:** High = first funder / shared Safe signers / same CEX deposit. Medium = coordinated movements / related-wallets + label match. Exclude = ENS only, single CEX withdrawal, single deployer.
Full attribution rules in REFERENCE.md.
FILE:REFERENCE.md
# Wallet Attribution — Reference
## Expansion Protocol
Run steps 1-2 on the seed address. For every new address found, ask the human:
**"Found `<addr>` via `<signal>` (`<label>`). Want me to query it?"**
On confirm, re-run steps 1-2 on it. Reserve step 3 (counterparties) for the seed address only.
**Stop expanding when:** address is a known protocol/CEX · confidence is Low · already visited · cluster > 10 wallets.
## Attribution Rules
- CEX withdrawal → wallet owner (NOT the CEX)
- Smart account/DCA bot → end-user who funds it (NOT the protocol)
- Safe deployer ≠ owner — identical signer sets across Safes = same controller
## Confidence Scoring
| Confidence | Signals |
|------------|---------|
| **High** | First Funder / shared Safe signers / same CEX deposit address |
| **Medium** | Coordinated balance movements / related-wallets + label match |
| **Exclude** | ENS alone, single CEX withdrawal, single deployer |
## Output Format
`address` · `owner` · `confidence (H/M/L)` · `signals` · `role`
## L2 Coverage
When step 3 returns sparse results on a mainnet EVM address, extend to L2s (4 calls):
```bash
for CHAIN in base arbitrum optimism polygon; do
nansen research profiler counterparties --address $ADDR --chain $CHAIN --days 365
done
```
## Cost Warnings
- `trace` is credit-heavy; keep `--width 3` or lower
- L2 counterparty checks above add 4 API calls per address
- Historical balances reveal past holdings on drained wallets — useful fingerprint
Which of these addresses are smart money? Batch-profile a list in one call.
---
name: nansen-wallet-batch
description: "Which of these addresses are smart money? Batch-profile a list in one call."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
```bash
ADDRESSES="0xaddr1,0xaddr2,0xaddr3,..." CHAIN=ethereum
nansen research profiler batch --addresses "$ADDRESSES" --chain $CHAIN --include labels,balance
# → .data.{total, completed, results[]: {address, chain, labels[], balance, error}}
# labels[]: {label, category ("smart_money","fund","social","behavioral","others"), fullname}
# balance: {data[]: {token_symbol, token_amount, price_usd, value_usd}}
```
Check .error per result — invalid addresses return an error message, not a crash. Skip those.
Keep addresses where any label.category == "smart_money" or "fund". Omit balance for faster checks.
Execute DEX swaps on Solana or Base, including cross-chain bridges. Use when buying or selling a token, getting a swap quote, or executing a trade.
---
name: nansen-trading
description: Execute DEX swaps on Solana or Base, including cross-chain bridges. Use when buying or selling a token, getting a swap quote, or executing a trade.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
- NANSEN_WALLET_PASSWORD
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Trade
Two-step flow: quote then execute. **Trades are irreversible once on-chain.**
**Prerequisite:** You need a wallet first. Run `nansen wallet create` before trading.
## Quote
```bash
nansen trade quote \
--chain solana \
--from SOL \
--to USDC \
--amount 1000000000
```
Symbols resolve automatically: `SOL`, `ETH`, `USDC`, `USDT`, `WETH`. Raw addresses also work. Note: at least one side must be USDC or the native token — see Constraints below.
## Constraints
**Swap constraint:** At least one side of every swap must be **USDC** or the chain's **native token** (SOL on Solana, ETH on Base). Arbitrary token-to-token swaps (e.g. WETH→USDT, BONK→JUP) are rejected.
- USDC (Solana): `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`
- USDC (Base): `0x833589fcd6edb6e08f4c7c32d4f71b54bda02913`
- Native SOL: `So11111111111111111111111111111111111111112`
- Native ETH: `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`
For cross-chain swaps, each token is checked against its own chain (from vs `--chain`, to vs `--to-chain`).
## Execute
```bash
nansen trade execute --quote <quote-id>
```
## Cross-Chain Swap
Bridge tokens between Solana and Base using `--to-chain`:
```bash
nansen trade quote \
--chain base \
--to-chain solana \
--from USDC \
--to USDC \
--amount 1000000
```
For Solana↔Base bridges, the destination wallet address is auto-derived from your wallet (which stores both EVM and Solana keys). Override with `--to-wallet <address>` if needed.
Note: you need gas on the **source** chain to submit the initial transaction (e.g. SOL for Solana→Base, ETH for Base→Solana).
## Bridge Status
After executing a cross-chain swap, the CLI polls bridge status automatically. To check manually:
```bash
nansen trade bridge-status --tx-hash <hash> --from-chain base --to-chain solana
```
## Agent pattern
```bash
# Pipe quote ID directly into execute
quote_id=$(nansen trade quote --chain solana --from SOL --to USDC --amount 1000000000 2>&1 | grep "Quote ID:" | awk '{print $NF}')
nansen trade execute --quote "$quote_id"
```
## Common Token Addresses
| Token | Chain | Address |
|-------|-------|---------|
| SOL | Solana | `So11111111111111111111111111111111111111112` |
| USDC | Solana | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` |
| ETH | Base | `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` |
| USDC | Base | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
## Amounts
By default, `--amount` accepts **integer base units** (lamports, wei, etc). Use `--amount-unit token` for human-readable token amounts, or `--amount-unit usd` to specify a USD value — the CLI resolves price and decimals automatically.
```bash
# Base units (default)
nansen trade quote --chain solana --from SOL --to USDC --amount 1000000000
# Token units (0.5 SOL = 500000000 lamports, resolved automatically)
nansen trade quote --chain solana --from SOL --to USDC --amount 0.5 --amount-unit token
# USD amount ($50 worth of SOL, price resolved via Nansen search API)
nansen trade quote --chain solana --from SOL --to USDC --amount 50 --amount-unit usd
```
| Token | Decimals | 1 token = |
|-------|----------|-----------|
| SOL | 9 | `1000000000` |
| ETH | 18 | `1000000000000000000` |
| USDC | 6 | `1000000` |
If the user says "$20 worth of X", use `--amount-unit usd` directly — no manual conversion needed. The CLI fetches the current price and converts for you.
## Flags
### `trade quote` flags
| Flag | Purpose |
|------|---------|
| `--chain` | Source chain: `solana` or `base` |
| `--to-chain` | Destination chain for cross-chain swap (omit for same-chain) |
| `--from` | Source token (symbol or address) |
| `--to` | Destination token (symbol or address, resolved against destination chain) |
| `--amount` | Amount in base units (integer), or token/USD units with `--amount-unit` |
| `--amount-unit` | `token` for token units (e.g. 0.5 SOL), `usd` for USD (e.g. 50), `base` = default |
| `--wallet` | Wallet name (default: default wallet) |
| `--to-wallet` | Destination wallet address (auto-derived for cross-chain if omitted) |
| `--slippage` | Slippage tolerance as decimal (e.g. 0.03) |
| `--auto-slippage` | Enable auto slippage calculation |
| `--max-auto-slippage` | Max auto slippage when `--auto-slippage` is enabled |
| `--swap-mode` | `exactIn` (default) or `exactOut` |
### `trade execute` flags
| Flag | Purpose |
|------|---------|
| `--quote` | Quote ID from `trade quote` |
| `--wallet` | Wallet name (default: default wallet) |
| `--quote-index` | Pin a specific quote by index (0-based) when multiple quotes were returned |
| `--no-simulate` | Skip pre-broadcast simulation |
### `trade bridge-status` flags
| Flag | Purpose |
|------|---------|
| `--tx-hash` | Source tx hash |
| `--from-chain` | Source chain (for bridge-status) |
| `--to-chain` | Destination chain (for bridge-status) |
## Environment Variables
| Var | Purpose |
|-----|---------|
| `NANSEN_WALLET_PASSWORD` | **Required for `trade execute`.** Wallet encryption password — persisted in `~/.nansen/.env`. Source before executing: `source ~/.nansen/.env && nansen trade execute ...` |
| `NANSEN_API_KEY` | API key (also set via `nansen login`) |
> **Agents:** Never hold a wallet password only in session memory. If `NANSEN_WALLET_PASSWORD` is not in `~/.nansen/.env`, follow the setup flow in the nansen-wallet-manager skill Password Policy to generate and persist one before proceeding.
## Notes
- Quotes expire after ~1 hour. If execute fails, get a fresh quote.
- A wallet is required even for quotes (the API builds sender-specific transactions).
- ERC-20 swaps may require an approval step — execute handles this automatically.
## Source
- npm: https://www.npmjs.com/package/nansen-cli
- GitHub: https://github.com/nansen-ai/nansen-cli
Where is this token moving and why? Large transfers, flow trends over time, and breakdown by wallet label.
---
name: nansen-token-transfer-analysis
description: "Where is this token moving and why? Large transfers, flow trends over time, and breakdown by wallet label."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Token Forensics
**Answers:** "Where is this token moving? Who is sending it and where?"
```bash
TOKEN=<address> CHAIN=ethereum
# Examples: UNI on ethereum (0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984)
# BONK on solana (DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263)
# Note: token flows does NOT support stablecoins (USDC, USDT, etc.) — use non-stablecoin tokens
nansen research token transfers --token $TOKEN --chain $CHAIN --days 7 --limit 20
# → from_address_label, to_address_label, transfer_amount, transfer_value_usd
nansen research token flows --token $TOKEN --chain $CHAIN --days 7 --limit 20
# → date, price_usd, holders_count, total_inflows_count, total_outflows_count
# ⚠ Returns HTTP 422 for stablecoins — skip this command if TOKEN is a stablecoin
nansen research token flow-intelligence --token $TOKEN --chain $CHAIN
# → net_flow_usd per label: smart_trader, whale, exchange, fresh_wallets, public_figure
```
Rising exchange_net_flow + large transfers to exchange addresses = potential sell pressure. Fresh wallet inflows may signal new interest or wash trading.
Discover trending tokens — screener, SM holdings, Nansen indicators, and flow intelligence for promising finds. Use when scanning for new tokens or screening...
---
name: nansen-token-screener
description: "Discover trending tokens — screener, SM holdings, Nansen indicators, and flow intelligence for promising finds. Use when scanning for new tokens or screening what's hot."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Token Discovery
**Answers:** "What tokens are trending and worth a deeper look?"
```bash
CHAIN=solana
# Screen top tokens by volume
nansen research token screener --chain $CHAIN --timeframe 24h --limit 20
# → token_symbol, price_usd, price_change, volume, buy_volume, market_cap_usd, fdv, liquidity, token_age_days
# Smart money only
nansen research token screener --chain $CHAIN --timeframe 24h --smart-money --limit 20
# Search within screener results (client-side filter)
nansen research token screener --chain $CHAIN --search "bonk"
# Smart money holdings — what SM wallets are holding
nansen research smart-money holdings --chain $CHAIN --labels "Smart Trader" --limit 20
# → token_symbol, value_usd, holders_count, balance_24h_percent_change, share_of_holdings_percent
# Nansen indicators for a specific token
TOKEN=<address>
nansen research token indicators --token $TOKEN --chain $CHAIN
# → risk_indicators, reward_indicators (each with score, signal, signal_percentile)
# Flow intelligence — only use for promising tokens from screener/indicators above
nansen research token flow-intelligence --token $TOKEN --chain $CHAIN
# → net_flow_usd per label: smart_trader, whale, exchange, fresh_wallets, public_figure
# Nansen Score Top Tokens — "what should I buy?" (public endpoint, any authenticated API key)
# Use this FIRST for discovery, then drill into individual tokens with `indicators` above
nansen research token top-tokens --limit 25
nansen research token top-tokens --market-cap largecap --limit 10
# → chain, token_address, token_symbol, performance_score, risk_score,
# per-indicator contributions, market_cap_group, latest_date, last_trigger_on
```
Screener timeframes: `5m`, `10m`, `1h`, `6h`, `24h`, `7d`, `30d`
Indicators: score is "bullish"/"bearish"/"neutral". signal_percentile > 70 = historically significant. Some tokens return empty indicators — not an error.
## Top tokens — Nansen Score field reference
Results are pre-filtered to `performance_score >= 15` server-side and returned sorted by:
1. `performance_score` DESC
2. `market_cap_group` priority (largecap → midcap → lowcap)
3. `risk_score` DESC
4. 24h volume DESC
So row 0 is always the strongest candidate for the filter you applied — no client-side ranking needed.
Market cap buckets (used in both the sort priority and the `--market-cap` filter):
- `lowcap`: market cap < $100M
- `midcap`: market cap $100M – $1B
- `largecap`: market cap > $1B
Every contribution is **ternary** — exactly one of `{negative, 0, positive}` per field. No partial values. Zero means "indicator didn't apply to this token" (out of scope), not "indicator was neutral".
**Performance Score (Alpha — "likely to outperform BTC over 7–30d")**
Range: `-60 to +75` (arithmetic bounds; live max is closer to `+45` since no single token hits every positive indicator simultaneously). Buy threshold: `>= 15`. Sum of the five `*_performance` fields below.
| Field | Contribution | Trigger | What the underlying indicator measures |
|---|---|---|---|
| `price_momentum_performance` | +30 / 0 | upstream score `bullish` → +30 | Price momentum, scored against separate thresholds for large-cap vs. low/mid-cap tokens. |
| `chain_fees_performance` | +30 / 0 | `bullish` (30-day fee growth > +1%) → +30 | 30-day spending momentum on network fees (geometric mean of daily returns). **Only tracked for a handful of L1 native tokens (e.g. ETH, TRX, AVAX, RON); always 0 for every other token.** |
| `trading_range_performance` | +15 / 0 | `bullish` (price breaks above resistance in an uptrend) → +15 | 14-day price trend combined with position vs. nearest support/resistance. In practice fires mostly on established tokens that have well-defined levels — can fire at any market cap, but is rare for new / low-liquidity tokens. |
| `chain_tvl_performance` | 0 / -35 | `bearish` (composite TVL growth < 0) → -35 | TVL momentum composite signal. Only non-zero for chains / L2s whose TVL is tracked. No positive path exists — the field only deducts. |
| `protocol_fees_performance` | 0 / -25 | `bearish` (14-day fee growth < -3%) → -25 | 14-day protocol fee momentum. Only non-zero for tokens backed by protocols with measurable fee revenue. No positive path — deduction only. |
**Risk Score (Safety — "filters falling knives / dangerous setups")**
Range: `-60 to +80` (arithmetic bounds). Safety threshold: `> 0` (positive = safer, negative = riskier). Sum of the four `*_risk` fields below. For every risk field: upstream score `low` → positive contribution, `high` → negative contribution, `medium`/missing → 0.
| Field | Contribution | What the underlying indicator measures |
|---|---|---|
| `btc_reflexivity_risk` | +40 / -20 | Rolling 5-event median ratio of token drop to BTC drop on days BTC falls >3%. Ratio ≤ 1 → `low` → **+40** (token holds up as well as or better than BTC on drawdowns). Ratio > 1 → `high` → **-20** (token drops harder than BTC). Skipped for stablecoins and tokens with <$1M 24h volume. |
| `liquidity_risk` | +20 / -20 | Ratio of on-chain liquidity to market cap (`total_liquidity_usd / market_cap_usd`). Higher ratio → `low` → **+20** (deep books relative to cap). Very thin ratio → `high` → **-20**. |
| `concentration_risk` | +10 / -10 | Top-10 holder concentration as a fraction of supply. `< 0.12` → `low` → **+10** (well-distributed). `> 0.55` → `high` → **-10** (whale-concentrated). |
| `inflation_risk` | +10 / -10 | EMA of daily token supply inflation rate. Negative / near-zero → `low` → **+10** (stable or deflationary supply). Strongly positive → `high` → **-10** (high dilution). Only evaluated for tokens >= $100M market cap. |
Other response fields:
- `market_cap_group`: `lowcap` / `midcap` / `largecap` — see thresholds above.
- `latest_date`: ISO datetime of the most recent indicator refresh for this token.
- `last_trigger_on`: ISO datetime of the most recent trigger across contributing indicators (MAX aggregate — individual indicators may be days-to-months stale even when this looks fresh). Use `indicators` on a specific token to audit per-indicator ages.
**Stablecoins rank high but aren't picks.** USDC, USDT, DAI, FDUSD and similar score well on chain_fees + liquidity indicators but aren't what "what should I buy" means. Filter them out of the shortlist using the canonical whitelist at `nansen-dbt-ch-tokens/seeds/stablecoins_for_indicator.csv` before drilling into `indicators`.
Typical workflow: start with `top-tokens` for a shortlist → drop stablecoins → run `indicators` on the top 3–5 to inspect individual signals and their signal_percentile → `flow-intelligence` only on the finalists to confirm SM conviction.
Field meanings and contribution mappings above are sourced from `nansen-dbt-ch-tokens/models/indicators/api_nansen_score_indicators_all_tokens_latest.sql` and per-indicator model yml files. Sign conventions and live value ranges were validated against production ClickHouse data.
Flow intelligence is credit-heavy. Use it to confirm SM conviction on tokens that already look promising from screener + indicators, not as a first pass on every token.
Token deep dive — info, OHLCV, holders, flows, flow intelligence, who bought/sold, DEX trades, PnL, perp trades, perp positions, perp PnL leaderboard. Use wh...
---
name: nansen-token-research
description: Token deep dive — info, OHLCV, holders, flows, flow intelligence, who bought/sold, DEX trades, PnL, perp trades, perp positions, perp PnL leaderboard. Use when researching a specific token in depth.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Token Deep Dive
All commands: `nansen research token <sub> [options]`
`--chain` required for spot endpoints. Use `--token <address>` for token-specific endpoints.
## Info & Price
```bash
nansen research token info --token <addr> --chain solana
nansen research token ohlcv --token <addr> --chain solana --timeframe 1h
```
Timeframes: `1m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `1d`, `1w`, `1M`
## Holders
```bash
nansen research token holders --token <addr> --chain solana
nansen research token holders --token <addr> --chain solana --smart-money
```
## Flows
```bash
nansen research token flows --token <addr> --chain solana --days 7
nansen research token flow-intelligence --token <addr> --chain solana
nansen research token who-bought-sold --token <addr> --chain solana
```
`flow-intelligence` breaks down by label: whales, smart traders, exchanges, fresh wallets, public figures.
## DEX Trades
```bash
nansen research token dex-trades --token <addr> --chain solana --limit 20
```
## PnL
```bash
nansen research token pnl --token <addr> --chain solana --sort total_pnl_usd:desc
```
## Perps (no --chain)
```bash
nansen research token perp-trades --symbol ETH --days 7
nansen research token perp-positions --symbol BTC
nansen research token perp-pnl-leaderboard --symbol SOL
```
## Flags
| Flag | Purpose |
|------|---------|
| `--chain` | Required for spot endpoints (ethereum, solana, base, etc.) |
| `--token` | Token address (alias: `--token-address`) |
| `--symbol` | Token symbol for perp endpoints (e.g. BTC) |
| `--timeframe` | OHLCV interval |
| `--smart-money` | Filter to SM wallets only (holders) |
| `--days` | Lookback period (default 30) |
| `--sort` | Sort field:direction (e.g. `total_pnl_usd:desc`) |
| `--fields` | Select specific fields |
| `--table` | Human-readable table output |
| `--format csv` | CSV export |
## Notes
- Perp endpoints use `--symbol` (e.g. BTC), not `--token`.
- `holders --smart-money` returns UNSUPPORTED_FILTER for tokens without SM tracking.
- `flow-intelligence` may return all-zero flows for illiquid tokens.
Has SM been in this token for weeks, or did they just enter? Are they still buying?
---
name: nansen-smart-money-trend
description: "Has SM been in this token for weeks, or did they just enter? Are they still buying?"
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
```bash
TOKEN=<address> CHAIN=ethereum
nansen research smart-money netflow --chain $CHAIN --limit 200
# → filter by token_address; net_flow_1h_usd, net_flow_24h_usd, net_flow_7d_usd, net_flow_30d_usd
nansen research token holders --token $TOKEN --chain $CHAIN --smart-money --limit 20
# → address_label, value_usd, balance_change_24h, balance_change_7d, balance_change_30d
nansen research token flow-intelligence --token $TOKEN --chain $CHAIN
# → smart_trader_net_flow_usd, whale_net_flow_usd, fund_net_flow_usd, fresh_wallets_net_flow_usd
nansen research token dex-trades --token $TOKEN --chain $CHAIN --limit 50
# → block_timestamp, action, trader_address_label — find oldest SM-labeled BUY
```
1h/24h+ & 7d/30d+ = sustained accumulation. 24h+ & 7d− = fresh entry.
24h− & 7d+ = reducing. All negative = distribution.
Smart money tracking — netflow, trades, holdings, perp trades. Use when finding what smart money wallets are buying/selling or tracking whale activity.
---
name: nansen-smart-money-tracker
description: Smart money tracking — netflow, trades, holdings, perp trades. Use when finding what smart money wallets are buying/selling or tracking whale activity.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Smart Money
All commands: `nansen research smart-money <sub> [options]`
## Subcommands
```bash
# Netflow — what tokens are smart money accumulating?
nansen research smart-money netflow --chain solana --limit 10
# DEX trades — real-time spot trades by smart money
nansen research smart-money dex-trades --chain solana --labels "Smart Trader" --limit 20
# Holdings — aggregated SM portfolio
nansen research smart-money holdings --chain solana --limit 10
# Perp trades — Hyperliquid only (no --chain needed)
nansen research smart-money perp-trades --limit 10
```
## Labels
Filter by smart money category with `--labels`:
| Label | Use case |
|-------|----------|
| `Fund` | Crypto funds |
| `Smart Trader` | All-time top performers |
| `30D Smart Trader` | Hot hands — top 30 days |
| `90D Smart Trader` | Top 90 days |
| `180D Smart Trader` | Top 180 days |
| `Smart HL Perps Trader` | Top Hyperliquid perp traders |
```bash
nansen research smart-money netflow --chain solana --labels "Fund" --limit 10
```
## Flags
| Flag | Purpose |
|------|---------|
| `--chain` | Required for netflow/dex-trades/holdings |
| `--labels` | Filter by SM label (quote multi-word values) |
| `--limit` | Number of results |
| `--sort` | Sort field:direction (e.g. `value_usd:desc`) |
| `--fields` | Select specific fields |
| `--table` | Human-readable table output |
| `--format csv` | CSV export |
## Notes
- `perp-trades` is Hyperliquid-only. No `--chain` flag.
- For a time-series view of SM positions: `nansen research smart-money historical-holdings --chain <chain> --days 30`
What tokens is smart money accumulating before they pump? Token screener with SM filter cross-referenced against netflow.
---
name: nansen-smart-money-alpha
description: "What tokens is smart money accumulating before they pump? Token screener with SM filter cross-referenced against netflow."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Alpha Discovery
**Answers:** "What tokens is smart money accumulating before they pump?"
```bash
CHAIN=solana
nansen research token screener --chain $CHAIN --timeframe 24h --smart-money --limit 20
# → token_symbol, price_usd, price_change, volume, buy_volume, market_cap_usd, fdv, liquidity, token_age_days
nansen research smart-money netflow --chain $CHAIN --labels "Smart Trader" --limit 10
# → token_symbol, net_flow_1h/24h/7d/30d_usd, trader_count
# Confirm SM flow on a specific token from screener results
TOKEN=<address_from_screener>
nansen research token flow-intelligence --token $TOKEN --chain $CHAIN
# → net_flow_usd per label: smart_trader, whale, exchange, fresh_wallets
```
Cross-reference screener results with positive netflow to find early accumulation.
## Source
- npm: https://www.npmjs.com/package/nansen-cli
- GitHub: https://github.com/nansen-ai/nansen-cli
Manage smart alerts — list, create, update, toggle, delete. Use when setting up or managing token flow alerts, smart money alerts, or notification rules.
---
name: nansen-smart-alerts
description: Manage smart alerts — list, create, update, toggle, delete. Use when setting up or managing token flow alerts, smart money alerts, or notification rules.
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Smart Alerts
CRUD management for smart alerts. Alerts are internal-only (requires Nansen internal API key).
## Quick Reference
```bash
nansen alerts list --table
nansen alerts create --name <name> --type <type> --chains <chains> --telegram <chatId>
nansen alerts update <id> [--name <name>] [--chains <chains>]
nansen alerts toggle <id> --enabled|--disabled
nansen alerts delete <id>
```
## Options Reference
| Flag | Create | Update | Toggle | Delete |
|------|--------|--------|--------|--------|
| `<id>` (positional) | | required | required | required |
| `--name` | required | optional | | |
| `--type` | required | required with type-specific flags | | |
| `--chains` | recommended | optional | | |
| `--telegram` | chat ID | optional | | |
| `--slack` | webhook URL | optional | | |
| `--discord` | webhook URL | optional | | |
| `--webhook` | endpoint URL | optional | optional | |
| `--webhook-secret` | optional (webhook only) | optional | | |
| `--description` | optional | optional | | |
| `--enabled` | | flag | flag | |
| `--disabled` | flag | flag | flag | |
| `--data` | optional (JSON escape hatch) | optional | | |
## Alert Types
### 1. `sm-token-flows` — Smart Money Token Flows
Track aggregated SM inflow/outflow. At least one flow threshold should be specified.
**Type-specific flags:**
- `--inflow-1h-min/max`, `--inflow-1d-min/max`, `--inflow-7d-min/max` (USD thresholds)
- `--outflow-1h-min/max`, `--outflow-1d-min/max`, `--outflow-7d-min/max`
- `--netflow-1h-min/max`, `--netflow-1d-min/max`, `--netflow-7d-min/max`
- `--token <address:chain>` (repeatable) — include specific tokens
- `--exclude-token <address:chain>` (repeatable)
- `--token-sector <name>` / `--exclude-token-sector <name>` (repeatable)
- `--token-age-max <days>`
- `--market-cap-min/max <usd>`, `--fdv-min/max <usd>`
**Example:**
```bash
nansen alerts create \
--name 'SM ETH Inflow >5M' \
--type sm-token-flows \
--chains ethereum \
--telegram 5238612255 \
--inflow-1h-min 5000000 \
--token 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2:ethereum
```
### 2. `common-token-transfer` — Token Transfer Events
Track real-time transfer events matching specified criteria.
**Subject types:** `address`, `entity`, `label`, `custom-label`
Format: `--subject type:value` (e.g. `--subject label:"Centralized Exchange"`)
**Type-specific flags:**
- `--events <buy,sell,swap,send,receive>` (comma-separated)
- `--usd-min/max <usd>`, `--token-amount-min/max <n>`
- `--subject <type:value>` (repeatable) — addresses/entities/labels to track
- `--counterparty <type:value>` (repeatable) — requires `--subject`
- `--token <address:chain>` / `--exclude-token <address:chain>` (repeatable)
- `--token-sector <name>` / `--exclude-token-sector <name>` (repeatable)
- `--token-age-min/max <days>`, `--market-cap-min/max <usd>`
- `--exclude-from <type:value>` / `--exclude-to <type:value>` (repeatable)
**Event direction notes:**
- `buy` for counterparties = `sell` for subjects
- `send` for counterparties = `receive` for subjects
- To track "any address sending to CEX": use `--subject` with `receive`, not `--counterparty` with `send`
**Example:**
```bash
nansen alerts create \
--name 'Large USDC Transfers' \
--type common-token-transfer \
--chains ethereum \
--telegram 123456789 \
--events send,receive \
--usd-min 1000000 \
--token 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48:ethereum
```
### 3. `smart-contract-call` — Smart Contract Interactions
Track contract calls matching specified criteria.
**Type-specific flags:**
- `--usd-min/max <usd>`
- `--signature-hash <hash>` (repeatable, e.g. `0x095ea7b3` for `approve`)
- `--caller <type:value>` / `--exclude-caller <type:value>` (repeatable)
- `--contract <type:value>` / `--exclude-contract <type:value>` (repeatable)
**Example:**
```bash
nansen alerts create \
--name 'Uniswap V3 Large Swaps' \
--type smart-contract-call \
--chains ethereum \
--telegram 123456789 \
--usd-min 1000000 \
--contract entity:"Uniswap V3"
```
## Notes
- Chain aliases: Hyperliquid = `hyperevm`, BSC = `bnb`.
- Multiple channels can be combined: `--telegram 123 --slack https://... --webhook https://...`
- `--webhook <url>` sends a POST request with the alert payload to any HTTP/HTTPS endpoint. Useful for server deployments, Zapier, n8n, or custom integrations. The endpoint must be publicly reachable and return a 2xx response.
- `--data '<json>'` merges raw JSON on top of named flags (escape hatch for fields without named flags).
- Alert endpoints are internal-only. Non-internal users receive 404.
- Use single quotes for names with `$` or special characters: `--name 'SM >$1M'`
Is SM buying this token on one chain but selling on another? Detect capital rotation.
---
name: nansen-sm-cross-chain-flows
description: "Is SM buying this token on one chain but selling on another? Detect capital rotation."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
```bash
TOKEN_SYMBOL=<symbol e.g. "AAVE"> CHAINS=(ethereum solana base bnb)
for chain in "CHAINS[@]"; do
nansen research smart-money netflow --chain $chain --limit 200
# Filter by token_symbol; → net_flow_1h_usd, net_flow_24h_usd, net_flow_7d_usd, net_flow_30d_usd
done
```
Absent from results = SM activity below threshold on that chain, not necessarily unsupported. Use --limit 200; --limit 100 silently drops mid-tier tokens.
ETH+ & SOL− = rotating from SOL→ETH. One chain positive only = chain-specific play.
24h/7d divergence across chains is the rotation signal, not 1h.
Polymarket screeners — discover trending events, top markets by volume, and search for specific markets. Use when browsing what's happening on prediction mar...
---
name: nansen-prediction-markets
description: "Polymarket screeners — discover trending events, top markets by volume, and search for specific markets. Use when browsing what's happening on prediction markets."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Prediction Market Screeners
All commands: `nansen research prediction-market <sub> [options]` (alias: `nansen research pm <sub>`)
No `--chain` flag needed — Polymarket runs on Polygon.
```bash
# Top events (groups of related markets)
nansen research pm event-screener --sort-by volume_24hr --limit 20
# → event_title, market_count, total_volume, total_volume_24hr, total_liquidity, total_open_interest, tags
# Top markets by 24h volume
nansen research pm market-screener --sort-by volume_24hr --limit 20
# → market_id, question, best_bid, best_ask, volume_24hr, liquidity, open_interest, unique_traders_24h
# Search for specific markets
nansen research pm market-screener --query "bitcoin" --limit 10
# Find resolved/closed markets
nansen research pm market-screener --status closed --limit 10
# Browse categories
nansen research pm categories --pretty
# → category, active_markets, total_volume_24hr, total_open_interest
```
Sort options: `volume_24hr`, `volume`, `volume_1wk`, `volume_1mo`, `liquidity`, `open_interest`, `unique_traders_24h`, `age_hours`
Screeners return active/open markets by default. Use `--status closed` for resolved markets.
How has a wallet's portfolio changed over time? Historical balances, current snapshot, and per-token PnL.
---
name: nansen-portfolio-tracker
description: "How has a wallet's portfolio changed over time? Historical balances, current snapshot, and per-token PnL."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Portfolio History
**Answers:** "How has this wallet's portfolio evolved over the past month?"
```bash
ADDR=<address> CHAIN=ethereum
nansen research profiler historical-balances --address $ADDR --chain $CHAIN --days 30 --limit 20
# → block_timestamp, token_symbol, token_amount, value_usd, chain
nansen research profiler balance --address $ADDR --chain $CHAIN
# → token_symbol, token_name, token_amount, price_usd, value_usd
nansen research profiler pnl --address $ADDR --chain $CHAIN --days 30 --limit 20
# → token_symbol, pnl_usd_realised, roi_percent_realised, bought_usd, sold_usd, holding_usd, nof_buys, nof_sells
```
Compare historical-balances over time against current balance to see what was added/removed. PnL shows trade performance.
What is a Polymarket trader betting on? Trades by address, PnL breakdown, and market context. Use when analysing a specific Polymarket wallet.
---
name: nansen-polymarket-trader-profile
description: "What is a Polymarket trader betting on? Trades by address, PnL breakdown, and market context. Use when analysing a specific Polymarket wallet."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Polymarket Wallet Activity
**Answers:** "What is this Polymarket trader betting on? Are they profitable?"
**Finding an active trader address:** Source from `trades-by-market` (guarantees trade history) rather than `top-holders` (position holders may have no recorded trades):
```bash
# Step 1: find active traders from a market
nansen research pm trades-by-market --market-id <market_id> --limit 5
# → seller/buyer addresses with confirmed trade history — use one as ADDR below
```
```bash
ADDR=<polymarket_address>
nansen research pm trades-by-address --address $ADDR --limit 20
# → timestamp, market_question, event_title, taker_action, side, size, price, usdc_value
nansen research pm pnl-by-address --address $ADDR --limit 20
# → question, event_title, side_held, net_buy_cost_usd, unrealized_value_usd, total_pnl_usd, market_resolved
```
Note: addresses sourced from `top-holders` may return empty trade history — use `trades-by-market` to find addresses with confirmed activity.
Look at PnL across resolved vs unresolved markets to gauge trader skill. Large positions in trending categories signal conviction.
Scan a resolved Polymarket market for wallets exhibiting suspicious trading patterns: fresh funding, single-market focus, extreme ROI, late entry at high pri...
---
name: nansen-polymarket-insider-scan
description: "Scan a resolved Polymarket market for wallets exhibiting suspicious trading patterns: fresh funding, single-market focus, extreme ROI, late entry at high prices."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# PM Suspicious Wallet Scanner
**Answers:** "Are there wallets with suspicious trading patterns in this Polymarket market?"
```bash
# 1. Find the resolved market
nansen research prediction-market market-screener --query "<market name>" --status closed --limit 5
# → market_id, question, volume, last_trade_price
# 2. Get top winners (positive PnL) — paginate if needed, keep per_page <= 10
MID=<market_id>
nansen research prediction-market pnl-by-market --market-id $MID --limit 10
# → address (proxy), owner_address (wallet), side_held, net_buy_cost_usd, total_pnl_usd
# 3. For each top winner, run these three calls (use proxy address for PM, owner for profiler):
PROXY=<address_from_pnl>
nansen research prediction-market trades-by-address --address $PROXY --limit 100
# → market_id, market_question, side, price, size, usdc_value, taker_action, timestamp
OWNER=<owner_address_from_pnl>
nansen research profiler historical-balances --address $OWNER --chain polygon --days 365 --sort block_timestamp:asc --limit 100
# → block_timestamp, value_usd, token_symbol — first non-zero value_usd = wallet funding date
nansen research profiler labels --address $OWNER --chain polygon
# → label, category
```
For each winner, compute ROI = total_pnl_usd / net_buy_cost_usd * 100 (skip ROI flags if net_buy_cost_usd <= 0), then score (0–13). Within each tier group, apply only the highest matching flag:
| Flag | Pts | Trigger |
|---|---|---|
| NEW_WALLET | 3 | First funded within 7 days of now |
| YOUNG_WALLET | 1 | First funded 8–28 days ago (skip if NEW_WALLET fires) |
| SINGLE_MARKET | 3 | trades-by-address shows only 1 distinct market_id |
| FEW_MARKETS | 1 | 2–3 distinct market_ids (skip if SINGLE_MARKET fires) |
| EXTREME_ROI | 3 | ROI >= 500% |
| HIGH_ROI | 2 | ROI 200–499% (skip if EXTREME_ROI fires) |
| LATE_ENTRY | 2 | Any trade on this market at price >= 0.80 |
| LARGE_POSITION | 2 | net_buy_cost_usd >= $10k |
| KNOWN_ENTITY | -2 | Has Nansen labels |
Flagged at score >= 3. High risk at >= 7. High-confidence suspicious pattern: NEW_WALLET + SINGLE_MARKET + EXTREME_ROI (score 9+).
If owner_address is invalid (e.g. "0x"), use the proxy address for profiler calls too. If historical-balances returns no records with value_usd > 0, the wallet may predate the 365-day window — treat wallet age as unknown and skip NEW_WALLET / YOUNG_WALLET flags. Pause ~1.5s between wallets to avoid rate limits. Skip wallets that error and continue scanning.
Deep dive on a Polymarket market — OHLCV, orderbook, top holders, positions, trades, and PnL leaderboard. Use when analysing a specific prediction market.
---
name: nansen-polymarket-deep-dive
description: "Deep dive on a Polymarket market — OHLCV, orderbook, top holders, positions, trades, and PnL leaderboard. Use when analysing a specific prediction market."
metadata:
openclaw:
requires:
env:
- NANSEN_API_KEY
bins:
- nansen
primaryEnv: NANSEN_API_KEY
install:
- kind: node
package: nansen-cli
bins: [nansen]
allowed-tools: Bash(nansen:*)
---
# Prediction Market Deep Dive
**Answers:** "What's happening in this specific market? Who holds it, who's trading it?"
Use `market_id` from the screener (`nansen-prediction-market` skill).
```bash
MID=<market_id>
nansen research pm ohlcv --market-id $MID --sort period_start:desc --limit 50
# → period_start, open, high, low, close, volume
nansen research pm orderbook --market-id $MID
# → bids[], asks[] with price and size
nansen research pm top-holders --market-id $MID --limit 20
# → address, side, position_size, avg_entry_price, current_price, unrealized_pnl_usd
nansen research pm position-detail --market-id $MID --limit 20
# → address, side, size, avg_entry_price, current_price, pnl
nansen research pm trades-by-market --market-id $MID --limit 20
# → timestamp, buyer, seller, taker_action, side, size, price, usdc_value
nansen research pm pnl-by-market --market-id $MID --limit 20
# → address, side_held, net_buy_cost_usd, unrealized_value_usd, total_pnl_usd
```
Notes:
- `--market-id` is a numeric ID from the screener, not a slug.
- Works with any market ID regardless of status (active or closed/resolved).
- All addresses are Polygon (EVM).