← Ulvane

API reference

Four onchain intelligence endpoints on Base, priced per call via the x402 protocol. No API keys, no accounts — request the endpoint, get an HTTP 402 with payment requirements, pay in USDC on Base mainnet, get your answer. Every route also has a free, rate-limited, no-payment /api/demo/* mirror with a smaller data window — that's what the homepage demo calls. Prefer a machine-readable schema? See the OpenAPI spec.

Try it

The same free, rate-limited demo from the homepage — no wallet needed.

Real USDC on Base: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913

Payment

Making a paid request

A plain fetch/requests call gets you the 402 — an x402 client library handles signing the payment and retrying automatically. Client library APIs move fast; check coinbase/x402 for the current package names if these have drifted.

TypeScript

import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";import { ExactEvmScheme } from "@x402/evm";import { privateKeyToAccount } from "viem/accounts";const account = privateKeyToAccount("0xYOUR_PRIVATE_KEY");const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {  schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],});const res = await fetchWithPayment(  "https://ulvane.xyz/api/token/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913/risk");const data = await res.json();

Python

from eth_account import Accountfrom x402.clients.requests import x402_requestsaccount = Account.from_key("0xYOUR_PRIVATE_KEY")session = x402_requests(account)res = session.get(    "https://ulvane.xyz/api/token/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913/risk")data = res.json()

The wallet behind the private key needs a small amount of real USDC on Base mainnet — prices are cents-level ($0.01–$0.05 per call).

Endpoints

GET/api/token/{address}/risk$0.01

Is this token a scam clone of a known name? Checks a hand-verified allowlist and flags symbol impersonation, and adds neutral facts read from the contract (ownership, upgradeable proxy, mint/pause/blacklist functions) and from published token lists.

Request

curl https://ulvane.xyz/api/token/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913/risk

Response

{  "address": "0x8335...02913",  "symbol": "USDC",  "name": "USD Coin",  "status": "verified",  "note": "This exact address has been manually vetted by Ulvane.",  "impersonatedSymbol": null,  "signals": [    { "id": "has_owner", "level": "caution", "text": "A privileged owner address exists (0x3ABd...). What it can do depends on the contract's code." },    { "id": "upgradeable_proxy", "level": "caution", "text": "Upgradeable proxy: the logic can be replaced by whoever controls the proxy admin." },    { "id": "listed", "level": "info", "text": "Listed on: Uniswap Labs Default, Superchain Token List, CoinGecko." }  ],  "onchain": { "isContract": true, "ownership": "owned", "upgradeable": true, "decimals": 6, "capabilities": { "mint": true, "pause": true, "blacklist": true } },  "listings": { "listedBy": ["Uniswap Labs Default", "Superchain Token List", "CoinGecko"], "lookalikes": [] },  "asOf": "2026-09-18T17:55:00.476Z",  "signature": "0x22629f79...ee922761c",  "signerAddress": "0xdCac2E5680797cd6d7483764A76278cE5f91ECe8"}
GET/api/tokens/risk$0.05

Token risk for up to 10 tokens in one call, at a flat price. A token that can't be read (e.g. an address with no contract code) fails on its own row without failing the call; if every token fails, nothing is charged.

Request

curl "https://ulvane.xyz/api/tokens/risk?addresses=0xTokenA,0xTokenB,0xTokenC"

Response

{  "count": 3,  "summary": { "verified": 1, "impersonator": 0, "unverified": 1, "withCautionSignals": 2, "errors": 1 },  "results": [    { "ok": true, "address": "0x...", "symbol": "USDC", "status": "verified", "signals": [], "onchain": {}, "listings": {} },    { "ok": true, "address": "0x...", "symbol": "PEPE", "status": "unverified", "signals": [{ "id": "lookalike_symbol", "level": "caution", "text": "..." }] },    { "ok": false, "address": "0x...", "error": "This address has no contract code on Base, so it is not a token." }  ],  "asOf": "2026-10-29T00:00:00Z",  "signature": "0x22629f79...ee922761c",  "signerAddress": "0xdCac2E5680797cd6d7483764A76278cE5f91ECe8"}
GET/api/pnl/{wallet}$0.02

A wallet's realized/unrealized PnL, computed with a running-average cost basis — not just current balance × price.

Request

curl https://ulvane.xyz/api/pnl/0xYourWalletAddress

Response

{  "wallet": "0x...",  "realizedUsd": 1234.56,  "unrealizedUsd": 342.10,  "wins": 14,  "losses": 6,  "totalTrades": 20,  "points": [{ "time": 1737000000, "realizedUsd": 0 }],  "positions": [    { "tokenAddress": "0x...", "symbol": "TOKEN", "avgCostUsd": 0.012, "amountHeld": 50000, "unrealizedUsd": 60.0 }  ],  "windowNote": "Based on the most recent swaps...",  "asOf": "2026-09-18T00:00:00Z",  "signature": "0x22629f79...ee922761c",  "signerAddress": "0xdCac2E5680797cd6d7483764A76278cE5f91ECe8"}
GET/api/token/{address}/top-traders$0.03

Who's actually trading this token, ranked by volume moved — not just top balances.

Request

curl https://ulvane.xyz/api/token/0xTokenAddress/top-traders

Response

{  "tokenAddress": "0x...",  "currentPriceUsd": 0.0123,  "traders": [    { "trader": "0x...", "boughtUsd": 50000, "soldUsd": 42000, "totalPnlUsd": 3900, "tradeCount": 14 }  ],  "windowNote": "Based on the most recent swaps...",  "asOf": "2026-09-18T00:00:00Z",  "signature": "0x22629f79...ee922761c",  "signerAddress": "0xdCac2E5680797cd6d7483764A76278cE5f91ECe8"}
GET/api/wallet/{wallet}/risk$0.02

Should an agent trust this counterparty? Flags a wallet if it's recently traded any token on Ulvane's impersonator list — the wallet-level counterpart to the token risk check.

Request

curl https://ulvane.xyz/api/wallet/0xYourWalletAddress/risk

Response

{  "wallet": "0x...",  "tradesAnalyzed": 42,  "distinctTokensTraded": 11,  "flaggedTokens": [],  "status": "clean",  "note": "No flagged-token activity found in this wallet's recent trade window...",  "windowNote": "Based on the most recent swaps...",  "asOf": "2026-09-18T00:00:00Z",  "signature": "0x22629f79...ee922761c",  "signerAddress": "0xdCac2E5680797cd6d7483764A76278cE5f91ECe8"}
GET/api/token/{address}/pre-trade-gate$0.05

Token risk + top traders (+ an optional wallet risk check) combined into one clear/caution/avoid verdict. Cheaper than calling the three separately.

Request

curl "https://ulvane.xyz/api/token/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913/pre-trade-gate?wallet=0xYourAgentWallet"

Response

{  "tokenAddress": "0x...",  "tokenSymbol": "USDC",  "tokenStatus": "verified",  "tokenNote": "This exact address has been manually vetted by Ulvane.",  "topTraders": [    { "trader": "0x...", "boughtUsd": 50000, "soldUsd": 42000, "totalPnlUsd": 3900, "tradeCount": 14 }  ],  "walletChecked": { "wallet": "0x...", "status": "clean", "note": "No flagged-token activity..." },  "verdict": "clear",  "verdictNote": "The token is on Ulvane's verified allowlist...",  "windowNote": "Top-trader and wallet-risk signals reflect a bounded recent-swap window...",  "asOf": "2026-09-28T00:00:00Z",  "signature": "0x22629f79...ee922761c",  "signerAddress": "0xdCac2E5680797cd6d7483764A76278cE5f91ECe8"}

Verifiable responses

Every paid response includes a signature and signerAddress. It's a standard EIP-191 signature over the exact JSON body (minus those two fields) — proof this data came from Ulvane at this exact time, checkable by anyone, without asking our server again.

import { verifyMessage } from "viem";const { signature, signerAddress, ...data } = response;const valid = await verifyMessage({  address: signerAddress,  message: JSON.stringify(data),  signature,});

Try it

This is the sample response above (signed with a throwaway demo key, not Ulvane's real one) so you can see it work immediately. Paste a real response from any paid endpoint to verify it for real — everything runs in your browser, nothing is sent to Ulvane.

Use from an AI agent (MCP & AgentKit)

Ulvane ships an MCP server, so Claude Desktop, Cursor or any MCP client can call the endpoints as tools. The agent's own wallet pays each call over x402 — the key stays on your machine and is only used to sign payment authorizations. Use a dedicated wallet with a few dollars of USDC on Base. A per-call spend cap (ULVANE_MAX_PRICE_USD, default $0.05) is enforced against the amount the server actually asks for, and every paid result includes a _verification field reporting whether its signature checks out and came from Ulvane. The leaderboard and pricing tools are free and need no key.

// claude_desktop_config.json — no install step, runs via npx{  "mcpServers": {    "ulvane": {      "command": "npx",      "args": ["-y", "ulvane-mcp"],      "env": { "EVM_PRIVATE_KEY": "0x..." }    }  }}

Tools: token_risk, wallet_pnl, wallet_risk, token_top_traders, pre_trade_gate, pricing, base_leaderboard. The package is ulvane-mcp on npm; source is on GitHub.

Building on Coinbase AgentKit? ulvane-agentkit adds the same actions as an AgentKit action provider, with the same spend cap (checked before anything is signed) and signature verification:

npm install ulvane-agentkit @coinbase/agentkitimport { ulvaneActionProvider } from "ulvane-agentkit";const agentKit = await AgentKit.from({  walletProvider, // EVM wallet on base-mainnet with a little USDC  actionProviders: [ulvaneActionProvider()],});

Note: a fresh install of @coinbase/agentkit 0.10.x can fail to load because of an upstream Solana dependency conflict (it reproduces without Ulvane). Installing graphql and adding "overrides": { "@solana/kit": "^8.0.0" } to your package.json fixes it.

Prices are also available as JSON at GET /api/pricing (free), so an agent can budget before it calls anything.

Notes

Build on