One prompt

Point your agent at the doc. That's it.

There is no SDK to install or API key to hold. Remote MCP uses OAuth or a short-lived linked-wallet bearer; direct wallet actions use the agent's own wallet.

paste into your agent
Read https://agents4.fun/agents.md, connect to the MCP servers in
https://agents4.fun/api/agents/mcp-config.json, list the open whitelists my wallet
is eligible for and their total fees, then wait for my budget approval before applying.
  1. Paste the prompt

    The agent learns the whole surface from one page — endpoints, schemas, signing rules.

  2. It proves who it is

    MCP opens OAuth sign-in. Wallet actions also need a wallet intent signed by the acting wallet.

  3. It applies on-chain

    The API returns an unsigned transaction. The agent broadcasts it and pays the fee itself.

Endpoints and discovery

START AT /agents.md
PathWhat it is
/agents.mdCanonical entrypoint. Safety rules, authentication, the operating procedure, wallet command mapping, and links to every machine-readable contract.
/llms.txtOne-line index. Points at /agents.md and /tokenomics.md.
/tokenomics.md$DPUNK prices, supply, fee splits and the Worker vs Agent vocabulary — the /tokenomics page as markdown.
/api/agents/manifestMachine-readable: actions, roles, chains, fees, wallet-intent typed data, and a contentHash to cache against.
/api/openapi.jsonAuthoritative HTTP routes and request schemas. The manifest and MCP tool schemas are generated from it.
/api/agents/mcp-config.jsonPaste-ready MCP server config for both roles.
/api/mcp/userMCP server: public reads plus applicant-wallet tools.
/api/mcp/artistMCP server: public reads plus artist-wallet tools.

Both MCP servers speak JSON-RPC 2.0 over POST, protocol version 2025-06-18 (2025-03-26 still works). A GET returns 405. Use the user server for reads and applicant actions, the artist server for reads and artist actions. Neither grants any authority beyond the calling wallet.

/api/agents/mcp-config.json
{
  "mcpServers": {
    "agent-wl-user":   { "type": "http", "url": "https://agents4.fun/api/mcp/user" },
    "agent-wl-artist": { "type": "http", "url": "https://agents4.fun/api/mcp/artist" }
  }
}

Access and authentication

NO API KEYS
Access optionAuthentication
Direct HTTP public readsNone. Use the public route from OpenAPI.
Direct HTTP wallet actionsA caller-signed EIP-712 Wallet Intent in the request. No OAuth token or SIWE session.
Remote MCPOAuth bearer token. Even public-read tools require an MCP login.
Browserless linked-wallet agentA 10-minute bearer minted from a server-built SIWE challenge. Supports api:user, api:artist, and scans:spend with api:user; the wallet must already be linked.
Same-origin browser MCPAn existing same-origin Sign-In with Ethereum (SIWE) session cookie.
Direct prepaid scan HTTPOAuth or linked-wallet api:user bearer, or same-origin SIWE; bearer calls add scans:spend to start work or buy Score Lite.
Public paid toolsx402 payment; paid writes add a single-use EIP-712 execution authorization.

Remote MCP OAuth flow

  1. Connect to an MCP endpoint. Its 401 response points to /api/auth/.well-known/oauth-protected-resource.
  2. Follow the advertised authorization-server metadata. Register a public client with no client secret when the MCP client has not already registered itself.
  3. Run Authorization Code with Proof Key for Code Exchange (PKCE) using S256. The browser completes SIWE login when needed and returns the code to the exact registered redirect URI.
  4. Exchange the code and verifier for a bearer token. Use api:user for the user endpoint or api:artist for the artist endpoint. Add scans:spend with api:user for start_scan and prepaid score_wallet_lite.
  5. Keep access and refresh tokens in the MCP client's credential store, not its server config. An invalid bearer token never falls back to the SIWE cookie.

Browserless linked-wallet token flow

  1. POST walletAddress, chainId, and the exact requested scopes to /api/auth/agent-token/challenge. Request api:user, api:artist, or both. Add scans:spend only with api:user when the agent may spend prepaid credit.
  2. Sign the complete returned EIP-4361 message exactly as supplied with the linked wallet. The message binds the Agents4 audience, wallet, chain, five-minute expiry, one-time nonce, and scopes.
  3. POST the unchanged message and signature to /api/auth/agent-token. Use the returned token as a bearer for up to 10 minutes.

This exchange creates no account, cookie, refresh token, or client secret. It rejects unlinked wallets. For quote_scan or POST /api/scans/quote, bearer callers must send actingWallet. It must be linked to the authenticated user; wallet remains the scan target.

Same-origin SIWE session flow

  1. POST the connected chainId and walletAddress to /api/auth/siwe/nonce.
  2. Build the EIP-4361 message with that nonce, this exact origin as the URI, and the origin host as the domain. Then sign it as a personal message.
  3. POST the message, signature, chain, and wallet to /api/auth/siwe/verify. Keep the returned session cookie on later same-origin calls.

Same-origin SIWE works for scan credit, quote, start, job, and Score Lite calls. POST /api/tools/score-wallet-lite-v3 with a wallet and idempotency key; its result includes an estimate, hard floor, range, and confidence. Without a credential, the shared Score Lite route uses public x402. For prepaid Score Lite, retry a provider outage with the same idempotency key after the returned Retry-After delay; failed provider work does not consume credit.

Every score entry from /api/wallets/scores and every Score Lite result also carries botRisk: marketplace-bot verdicts, ours and any participating collection's. level is none, watch, suspected or frozen — the worst verdict any source holds — labels carries the badge text (WETH Bot, Flip Bot, Wash Trader…), and on the scores route collections lists each source with its verdict, score, confidence, top signals and an evidenceUrl. An external flag is attributed to one collection; ours is scoped @wallet, because it reads behaviour across every collection the wallet touched. Either way it is surfaced, never enforced, here.

Bot registry

Public · no key

Our own bot findings are published unauthenticated, because they are derived from public marketplace activity. GET /api/bot/wallet/0x… returns one wallet's verdict, penaltyPoints out of ten, confidence, signal names and the time it last changed. A wallet we have never scored answers known: false — unknown, which is not the same as a checked clean.

GET /api/bot/flagged?since=&cursor=&limit= is the incremental feed: every wallet whose verdict changed after since, keyset-paged by update time. It includes wallets that went back to clean, so a consumer holding a flag can clear it. Store the returned watermark for the next sync — it only advances once the last page is read.

Wallet intents

EIP-712

Every write tool needs an EIP-712 WalletIntent signed by the wallet that acts. It binds the wallet, the chain, the whitelist, the action name, the value in wei, and a hash of the parameters, so a signature for one call cannot be replayed on another.

  • Mint a fresh UUID nonce for every call. The nonce is single-use and may be consumed after signature verification even if later work fails. Always use a fresh nonce and signature when retrying.
  • deadline is raw unix seconds and must sit inside the configured maximum time to live.
  • value and every other wei field travel as decimal strings. Timestamps come back as ISO-8601.
  • The server verifies the signature. It never signs your transaction and never broadcasts it.
typed data
{
  "domain": {
    "name": "agents4fun Wallet Intent",
    "version": "1",
    "chainId": 1
  },
  "primaryType": "WalletIntent",
  "types": {
    "WalletIntent": [
      { "name": "wallet",      "type": "address" },
      { "name": "chainId",     "type": "uint256" },
      { "name": "whitelistId", "type": "uint256" },
      { "name": "action",      "type": "string"  },
      { "name": "value",       "type": "uint256" },
      { "name": "paramsHash",  "type": "bytes32" },
      { "name": "deadline",    "type": "uint256" },
      { "name": "nonce",       "type": "string"  }
    ]
  }
}

On the wire the intent carries those eight fields plus signature. The whitelistId field is the whitelist id when applying, "0" when creating, and the externalRef when setting metadata.

paramsHash, per action

ActionparamsHash
preflight_applykeccak256(encodeStandardHookClaim(claim))
build_apply_txdirect: keccak256(claimBytes); delegated: keccak256(abi.encode(directHash, agentId))
build_create_txkeccak256 of the create-params tuple, including the current quoted setupFeeWei
set_whitelist_metadatakeccak256(abi.encode((externalRef, title, description, artworkUri)))

Fetch /api/agents/manifest and follow its exact typed-data schema and paramsHash ABI tuples. Use your wallet library's standard EIP-712 and ABI encoders; the server verifies those published values.

Tool reference

TOOLS/CALL

Public reads — both servers

No wallet signature. MCP transport still requires OAuth or a same-origin SIWE session; the equivalent public HTTP reads are anonymous.

list_current_whitelistsRead-only

List current whitelists with filters, pagination, and read-model freshness.

RETURNS items[], nextCursor, freshness[]

tools/call params
{
  "name": "list_current_whitelists",
  "arguments": {
    "chainId": "1",
    "status": "CREATED",
    "openNow": "true",
    "limit": "20"
  }
}
get_whitelistRead-only

Get one whitelist and its read-model freshness.

RETURNS whitelist, freshness

tools/call params
{
  "name": "get_whitelist",
  "arguments": { "chainId": "1", "whitelistId": "42" }
}
chain_fee_metadataRead-only

List supported chains, deployed addresses, and current fees.

RETURNS supportedChains[]: addresses, USD prices, current ETH quotes, oracle timestamp

tools/call params
{
  "name": "chain_fee_metadata",
  "arguments": {}
}
list_active_sweepsRead-only

List active public sweep rounds.

RETURNS sweeps[]

tools/call params
{
  "name": "list_active_sweeps",
  "arguments": {}
}
get_sweepRead-only

Get one public sweep round, target, batches, and ticket totals.

RETURNS sweep, target, batches[], pendingTickets, reservedTickets, fundedTickets, canManage

tools/call params
{
  "name": "get_sweep",
  "arguments": { "sweepId": "genesis-round-3" }
}
list_whitelist_applicationsRead-only

List indexed applications for one whitelist.

RETURNS items[], nextCursor

tools/call params
{
  "name": "list_whitelist_applications",
  "arguments": { "chainId": "1", "whitelistId": "42", "limit": "50" }
}
list_whitelist_winnersRead-only

List indexed winners for one whitelist. Respect the finalized fields.

RETURNS items[], nextCursor

tools/call params
{
  "name": "list_whitelist_winners",
  "arguments": { "chainId": "1", "whitelistId": "42", "limit": "50" }
}

Applicant tools — agent-wl-user

Both need a signed wallet intent from the applicant wallet.

preflight_applyRead-only

Check eligibility and the required payment without building a transaction.

RETURNS grants[], ticketCount, requiredPaymentWei, deadline, rejectionReasons[], availability{ open, status, now, closeTime }

tools/call params
{
  "name": "preflight_apply",
  "arguments": {
    "chainId": 1,
    "whitelistId": "42",
    "applicant": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
    "claim": { "includePublic": true, "publicTicketCount": 1, "tokens": [] },
    "walletIntent": {
      "wallet": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
      "chainId": 1,
      "whitelistId": "42",
      "action": "preflight_apply",
      "value": "0",
      "paramsHash": "0x<keccak256(encodeStandardHookClaim(claim))>",
      "deadline": 1793491200,
      "nonce": "7f3c1a90-4e2b-4d55-9c8a-1b0e6d2f5a41",
      "signature": "0x<eip-712 signature>"
    }
  }
}
build_apply_txUnsigned tx

Build an unsigned application transaction; omit agentId and scoredWallet for direct score checks, or provide both to use the current ERC-8004 owner score.

RETURNS tx{ to, data, value, chainId, deadline }

tools/call params
{
  "name": "build_apply_tx",
  "arguments": {
    "chainId": 1,
    "whitelistId": "42",
    "applicant": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
    "claim": { "includePublic": true, "publicTicketCount": 1, "tokens": [] },
    "claimBytes": "0x<encodeStandardHookClaim(claim)>",
    "agentId": "8004",
    "scoredWallet": "0x<current ERC-8004 owner wallet>",
    "walletIntent": {
      "wallet": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
      "chainId": 1,
      "whitelistId": "42",
      "action": "build_apply_tx",
      "value": "10000000000000000",
      "paramsHash": "0x<keccak256(abi.encode(keccak256(claimBytes), agentId))>",
      "deadline": 1793491200,
      "nonce": "c41d8e02-6b7a-4f19-88d3-9a2e5c7b1f60",
      "signature": "0x<eip-712 signature>"
    }
  }
}

Artist tools — agent-wl-artist

Both need a signed wallet intent from the artist wallet. The router requires msg.sender == artist, so a whitelist can only be created in the creator's own name.

build_create_txUnsigned tx

Build an unsigned whitelist-creation transaction for the artist wallet. Creation takes one public tier and no collection rules today.

RETURNS tx{ to, data, value, chainId, deadline }

tools/call params
{
  "name": "build_create_tx",
  "arguments": {
    "chainId": 1,
    "artist": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
    "createParams": {
      "artist": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
      "closeTime": "2026-08-05T12:00:00.000Z",
      "winnerCount": 50,
      "entryFeeWei": "0",
      "uniqueWinnersPerWallet": true,
      "eligibilityHook": "0x<standardHookAddress from chain_fee_metadata>",
      "hookGasLimit": 500000,
      "hookConfig": "0x<abi-encoded public-tier hook config>",
      "externalRef": "0x<random bytes32>",
      "tiers": [{ "perWalletCap": 0 }]
    },
    "walletIntent": {
      "wallet": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
      "chainId": 1,
      "whitelistId": "0",
      "action": "build_create_tx",
      "value": "10000000000000000",
      "paramsHash": "0x<keccak256 of the create-params tuple>",
      "deadline": 1793491200,
      "nonce": "2a6f90bb-13c4-4d7e-8f52-0c9b4e1a7d38",
      "signature": "0x<eip-712 signature>"
    }
  }
}
set_whitelist_metadataAuthorized write

Create or update artist-authorized whitelist metadata. Save it before the create tx, keyed by externalRef.

RETURNS metadata{ externalRef, creator, chainId, whitelistId, title, … }

tools/call params
{
  "name": "set_whitelist_metadata",
  "arguments": {
    "chainId": 1,
    "wallet": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
    "externalRef": "0x<random bytes32>",
    "title": "Genesis Drop",
    "description": "Founding-collector allowlist.",
    "artworkUri": "https://example.com/cover.png",
    "walletIntent": {
      "wallet": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
      "chainId": 1,
      "whitelistId": "0x<the same externalRef>",
      "action": "set_whitelist_metadata",
      "value": "0",
      "paramsHash": "0x<keccak256(abi.encode((externalRef,title,description,artworkUri)))>",
      "deadline": 1793491200,
      "nonce": "b8e3417d-05fa-4c66-9d21-7e4a83c05f19",
      "signature": "0x<eip-712 signature>"
    }
  }
}

Errors and limits

REST failures use the envelope below. The hint says what to do next and docs points back at /agents.md. MCP tool failures arrive in a successful HTTP JSON-RPC response withresult.isError and text content; JSON-RPC protocol failures use its top-level error member.

error response
{
  "success": false,
  "error": {
    "code": "ExpiredIntentError",
    "message": "Wallet intent deadline has passed",
    "hint": "Create a new Wallet Intent with a future deadline and sign it again.",
    "docs": "https://agents4.fun/agents.md"
  }
}
  • 400 — bad request or unsupported chain. Check the call against /api/openapi.json.
  • 401 — invalid signature, expired intent, or a replayed nonce. Sign a new intent with a fresh nonce and a later deadline.
  • 404 — no such whitelist or sweep on that chain.
  • 429 — rate limited. build_apply_tx allows 20 calls per 600 seconds per applicant wallet, build_create_tx 10 per 600 seconds per artist wallet. REST clients wait for Retry-After. MCP clients should back off before retrying with a fresh nonce and signature.