Decentralized Monad token launchpad with bonding curve trading, token creation, real-time event streaming, and historical data querying via pure viem calls.
# SKILL.md - NadFun Integration Guide
**The Monad token launchpad with bonding curves.** Trade tokens, launch your own, monitor events—all with pure viem.
## What is NadFun?
NadFun is a decentralized token launchpad on the Monad blockchain. Key features:
- **Bonding Curve Trading**: Tokens start on a bonding curve. Price increases as more people buy.
- **Graduation**: When curve reaches target reserves, token graduates to DEX (Uniswap V3).
- **Token Creation**: Anyone can launch a token with image, metadata, and optional initial buy.
- **Real-time Events**: Stream token trades, creations, and DEX swaps as they happen.
- **Historical Data**: Query past events for analytics and monitoring.
## Skills Documentation
This skill guide is split into focused modules. Start with the overview, then dive into specific guides:
| Module | Purpose | Audience |
| ------------------------ | ------------------------------------- | --------------------- |
| **SKILL.md** (this file) | Architecture, constants, setup | Everyone |
| **QUOTE.md** | Price quotes, curve state | Traders, analyzers |
| **TRADING.md** | Buy, sell, permit signatures | Traders, bots |
| **TOKEN.md** | Balances, metadata, transfers | App developers |
| **CREATE.md** | Token creation, image upload | Token creators |
| **INDEXER.md** | Historical event querying | Analytics, dashboards |
| **AGENT-API.md** | REST API for trading data, token info | AI agents, bots |
For API download/endpoints/header usage, see **AGENT-API.md**.
> **Note:** To obtain an API key, you must first login to [nad.fun](https://nad.fun) via wallet signature. See the [Authentication](#authentication-login-flow) section below for the login flow, then use the session cookie to create an API key via `POST /api-key`.
## Skill Files
| File | URL |
| ------------ | ---------------------------- |
| ABI.md | https://nad.fun/abi.md |
| AGENT-API.md | https://nad.fun/agent-api.md |
| CREATE.md | https://nad.fun/create.md |
| INDEXER.md | https://nad.fun/indexer.md |
| QUOTE.md | https://nad.fun/quote.md |
| TOKEN.md | https://nad.fun/token.md |
| TRADING.md | https://nad.fun/trading.md |
| WALLET.md | https://nad.fun/wallet.md |
mkdir -p ~/.nadfun/skills
curl -s https://nad.fun/skill.md > ~/.nadfun/skills/SKILL.md
curl -s https://nad.fun/abi.md > ~/.nadfun/skills/ABI.md
curl -s https://nad.fun/agent-api.md > ~/.nadfun/skills/AGENT-API.md
curl -s https://nad.fun/create.md > ~/.nadfun/skills/CREATE.md
curl -s https://nad.fun/indexer.md > ~/.nadfun/skills/INDEXER.md
curl -s https://nad.fun/quote.md > ~/.nadfun/skills/QUOTE.md
curl -s https://nad.fun/token.md > ~/.nadfun/skills/TOKEN.md
curl -s https://nad.fun/trading.md > ~/.nadfun/skills/TRADING.md
curl -s https://nad.fun/wallet.md > ~/.nadfun/skills/WALLET.md
## Quick Facts
- **Network**: Testnet (chain 10143) or Mainnet (chain 143)
- **Language**: TypeScript/JavaScript
- **Pure viem**: All examples use direct contract calls with viem
- **Fees**: Check API for deploy fees via REST endpoint
## Network Constants
All addresses and endpoints are here. Update this when network changes.
```typescript
const NETWORK = "testnet" // 'testnet' | 'mainnet'
const CONFIG = {
testnet: {
chainId: 10143,
rpcUrl: "https://monad-testnet.drpc.org",
apiUrl: "https://dev-api.nad.fun", // For token creation
// Contract addresses
DEX_ROUTER: "0x5D4a4f430cA3B1b2dB86B9cFE48a5316800F5fb2",
BONDING_CURVE_ROUTER: "0x865054F0F6A288adaAc30261731361EA7E908003",
LENS: "0xB056d79CA5257589692699a46623F901a3BB76f1",
CURVE: "0x1228b0dc9481C11D3071E7A924B794CfB038994e",
WMON: "0x5a4E0bFDeF88C9032CB4d24338C5EB3d3870BfDd",
V3_FACTORY: "0xd0a37cf728CE2902eB8d4F6f2afc76854048253b",
CREATOR_TREASURY: "0x24dFf9B68fA36f8400302e2babC3e049eA19459E",
},
mainnet: {
chainId: 143,
rpcUrl: "https://monad-mainnet.drpc.org",
apiUrl: "https://api.nadapp.net",
// Contract addresses
DEX_ROUTER: "0x0B79d71AE99528D1dB24A4148b5f4F865cc2b137",
BONDING_CURVE_ROUTER: "0x6F6B8F1a20703309951a5127c45B49b1CD981A22",
LENS: "0x7e78A8DE94f21804F7a17F4E8BF9EC2c872187ea",
CURVE: "0xA7283d07812a02AFB7C09B60f8896bCEA3F90aCE",
WMON: "0x3bd359C1119dA7Da1D913D1C4D2B7c461115433A",
V3_FACTORY: "0x6B5F564339DbAD6b780249827f2198a841FEB7F3",
CREATOR_TREASURY: "0x42e75B4B96d7000E7Da1e0c729Cec8d2049B9731",
},
}[NETWORK]
```
## Basic Setup
Every skill guide assumes you start with viem. Here's the foundation:
```typescript
import { createPublicClient, createWalletClient, http, privateKeyToAccount } from "viem"
const NETWORK = "testnet"
const CONFIG = {
/* from above */
}[NETWORK]
// Create clients
const publicClient = createPublicClient({
chain: {
id: CONFIG.chainId,
name: "Monad",
nativeCurrency: { name: "MON", symbol: "MON", decimals: 18 },
rpcUrls: { default: { http: [CONFIG.rpcUrl] } },
},
transport: http(CONFIG.rpcUrl),
})
const account = privateKeyToAccount("0x...")
const walletClient = createWalletClient({
account,
chain: publicClient.chain,
transport: http(CONFIG.rpcUrl),
})
```
This is your foundation. All other modules build on top of this.
## Authentication (Login Flow)
To access session-protected endpoints (API key management, account settings, etc.), you need to authenticate via wallet signature.
### Login Flow
```typescript
import { createWalletClient, http, privateKeyToAccount } from "viem"
const account = privateKeyToAccount("0x...")
const walletClient = createWalletClient({
account,
chain: {
id: CONFIG.chainId,
name: "Monad",
nativeCurrency: { name: "MON", symbol: "MON", decimals: 18 },
rpcUrls: { default: { http: [CONFIG.rpcUrl] } },
},
transport: http(CONFIG.rpcUrl),
})
// Step 1: Request nonce
const nonceRes = await fetch(`${CONFIG.apiUrl}/auth/nonce`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ address: account.address }),
})
const { nonce } = await nonceRes.json()
// Step 2: Sign the nonce
const signature = await walletClient.signMessage({ message: nonce })
// Step 3: Create session
const sessionRes = await fetch(`${CONFIG.apiUrl}/auth/session`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
signature,
nonce,
chain_id: CONFIG.chainId,
}),
})
// Extract session cookie from response headers
const sessionCookie = sessionRes.headers.get("set-cookie")
const { account_info } = await sessionRes.json()
console.log("Logged in as:", account_info.account_id)
```
### Using Session Cookie
```typescript
// Use session cookie for authenticated requests
const headers = { Cookie: sessionCookie }
// Example: Create API Key (requires session)
const apiKeyRes = await fetch(`${CONFIG.apiUrl}/api-key`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ name: "My Bot", expires_in_days: 365 }),
})
const { api_key } = await apiKeyRes.json()
console.log("API Key:", api_key) // Store this securely!
```
### Logout
```typescript
await fetch(`${CONFIG.apiUrl}/auth/delete_session`, {
method: "DELETE",
headers: { Cookie: sessionCookie },
})
```
### TypeScript Interfaces
```typescript
interface AuthNonceRequest {
address: string
}
interface AuthNonceResponse {
nonce: string
}
interface AuthSessionRequest {
signature: string
nonce: string
chain_id: number
wallet_address?: string // Optional
}
interface AuthSessionResponse {
account_info: {
account_id: string
nickname: string
bio: string
image_uri: string
}
}
```
## Core Concepts
### Bonding Curve
Tokens on NadFun start on a bonding curve. The curve defines price and availability:
- **Real reserves**: Actual MON and tokens in the pool
- **Virtual reserves**: Initial pseudo-reserves for curve math
- **K constant**: Maintains x\*y=k formula for price
- **Target**: How many tokens must be sold to graduate
Get curve state with `getCurveState(token)` → see **QUOTE.md**
### Graduation
When enough tokens are bought:
1. Curve reaches target reserve
2. Liquidity moves to Uniswap V3 pool
3. Token transitions from bonding curve to DEX
4. `isGraduated(token)` returns true
Check progress with `getProgress(token)` (0-10000 = 0-100%)
### Action IDs
The `actionId` parameter in token creation identifies the type of action:
| actionId | Description |
| -------- | -------------- |
| 1 | Token Creation |
Always use `actionId: 1` when calling the `create` function.
### Permit Signatures
EIP-2612 lets wallets sign approval transactions instead of sending separate approve() calls:
1. Generate signature: `generatePermitSignature(token, spender, amount, deadline)`
2. Use in trade: `sellPermit({ ...params, ...signature })`
3. No separate approve needed—saves gas
See **TRADING.md** for details.
## Common Patterns
### Pattern 1: Simple Trade
```typescript
// Get quote (3 args: token, amountIn, isBuy)
const [router, amountOut] = await publicClient.readContract({
address: LENS,
abi: lensAbi,
functionName: "getAmountOut",
args: [token, amountIn, true], // true = buy
})
// Buy with slippage protection (1 tuple arg)
const minOut = (amountOut * BigInt(995)) / BigInt(1000) // 0.5% slippage
const deadline = BigInt(Math.floor(Date.now() / 1000) + 300)
const tx = await walletClient.writeContract({
address: router, // Use router returned from getAmountOut
abi: bondingCurveRouterAbi,
functionName: "buy",
args: [
{
amountOutMin: minOut,
token: token,
to: account.address,
deadline: deadline,
},
],
value: amountIn,
})
```
See **TRADING.md** for full examples.
### Pattern 2: Launch Token
```typescript
import { bondingCurveRouterAbi } from "./abis/router"
// Requires API_KEY and deployFeeAmount (see CREATE.md for feeConfig())
// 1. Upload image to Agent API (raw binary, NOT formData)
const imageRes = await fetch(`${CONFIG.apiUrl}/agent/token/image`, {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "image/png", // or 'image/jpeg', 'image/webp', 'image/svg+xml'
},
body: imageBuffer, // raw binary data (Buffer, Blob, or ArrayBuffer)
})
const { image_uri: imageUri } = await imageRes.json()
// 2. Upload metadata to Agent API
const metadataRes = await fetch(`${CONFIG.apiUrl}/agent/token/metadata`, {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
body: JSON.stringify({
name: "My Token",
symbol: "MTK",
description: "My awesome token",
image_uri: imageUri,
}),
})
const { metadata_uri: metadataUri } = await metadataRes.json()
// 3. Mine salt via Agent API
const saltRes = await fetch(`${CONFIG.apiUrl}/agent/salt`, {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
body: JSON.stringify({
creator: account.address,
name: "My Token",
symbol: "MTK",
metadata_uri: metadataUri,
}),
})
const { salt } = await saltRes.json()
// 4. Create token on-chain
const tx = await walletClient.writeContract({
address: BONDING_CURVE_ROUTER,
abi: bondingCurveRouterAbi,
functionName: "create",
args: [
{
name: "My Token",
symbol: "MTK",
tokenURI: metadataUri,
amountOut: 0n, // Set > 0n for initial buy amount (in tokens)
salt: salt,
actionId: 1, // Token creation action identifier (always 1 for create)
},
],
value: deployFeeAmount, // plus optional initial buy MON if amountOut > 0
gas, // Use estimated gas (see below)
})
console.log("Transaction:", tx)
// Gas estimation (recommended over hardcoded values)
const gasEstimate = await publicClient.estimateContractGas({
address: CONFIG.BONDING_CURVE_ROUTER,
abi: bondingCurveRouterAbi,
functionName: "create",
args: [
{ name: "My Token", symbol: "MTK", tokenURI: metadataUri, amountOut: 0n, salt, actionId: 1 },
],
value: deployFeeAmount,
account: account.address,
})
const gas = (gasEstimate * 120n) / 100n // 20% buffer for safety
```
See **CREATE.md** for step-by-step.
## Prompts for AI Agents
Use these prompts to guide your work:
### Trading
- "Get a price quote for token X with 0.1 MON"
- "Buy 0.1 MON of token X with 1% slippage"
- "Sell all tokens with permit signature"
- "Check if token has graduated"
### Token Info
- "Get metadata for token address X"
- "Check my MON balance"
- "Get available tokens to buy before graduation"
### Creation
- "Launch a token called MyToken (MTK) with this image"
- "Mine a vanity address for my token"
- "Check token creation fees"
### Analysis
- "Calculate graduation progress for token X"
- "Get the bonding curve state for token X"
- "Track all swaps on the graduated token"
- "Analyze token creation rate"
## Required Files
For integration, you'll need:
```
abis/
├── curve.ts # Bonding curve contract ABI
├── lens.ts # Price quote ABI
├── router.ts # DEX router ABI
├── token.ts # ERC20 token ABI
└── v3*.ts # Uniswap V3 ABIs
constants.ts # Network configs, contract addresses
```
All ABIs are documented in **ABI.md**.
### ABI Import Guide
Each ABI can be copied from **ABI.md**. Reference the correct section:
| ABI | ABI.md Section | Purpose |
| ----------------------- | ------------------------------------------------- | -------------------------------- |
| `bondingCurveRouterAbi` | [BondingCurveRouter ABI](#bondingcurverouter-abi) | Token creation, buy/sell |
| `lensAbi` | [Lens ABI](#lens-abi) | Price quotes, getAmountOut |
| `curveAbi` | [Curve ABI](#curve-abi) | Curve state, graduation check |
| `tokenAbi` | [Token ABI](#token-abi) | ERC20 operations, permit signing |
Example import pattern:
```typescript
// Copy the ABI from ABI.md into your project
import { bondingCurveRouterAbi } from "./abis/router"
import { lensAbi } from "./abis/lens"
```
## Installation
```bash
npm install viem
# or
pnpm add viem
# or
yarn add viem
```
## Dependencies
```json
{
"viem": "^2.0.0"
}
```
Pure viem. No other blockchain libraries needed.
## Troubleshooting
### "No such contract"
You're on the wrong network. Check NETWORK constant matches your setup.
### "Token not graduated yet"
Query `getProgress()` via Lens contract. Returns 0-10000. Need 10000 (100%) to graduate.
### "Transaction reverted"
1. Check amountOutMin—slippage might be too high or curve moved
2. Verify deadline—transaction took too long
3. Check allowance—approve token before sell
## Next Steps
Pick a guide based on what you need:
- **Building a dex bot?** → TRADING.md
- **Creating a dashboard?** → INDEXER.md + QUOTE.md
- **Launching tokens?** → CREATE.md
- **Token info queries?** → TOKEN.md
Each guide has complete examples ready to copy.
## General Development Practices & Troubleshooting
### Type Safety Tips
Always use the `as const` assertion when working with ABIs in viem. All ABIs in this module are pre-declared with `as const` for full type inference:
```typescript
// Types are automatically narrowed
const result = await contract.read.getAmountOut([...])
// result type is precisely bigint (not bigint | undefined)
```
Use viem type helpers for type-safe conversions:
```typescript
import { Address, Hex } from "viem"
import { parseEther, formatEther } from "viem"
// Amounts
const wei = parseEther("1") // string -> bigint
const eth = formatEther(1000000000000000000n) // bigint -> string
// Addresses
const addr: Address = "0x..." // Validated address type
// Signatures
const sig: Hex = "0x..." // Hex string type
```
### Common ABI Errors
When interacting with smart contracts, you might encounter specific errors. Here's a table of common ABI-related errors and how to approach them:
| Error | Meaning |
| ----------------------- | ---------------------------------------------------------- |
| `InsufficientAmount` | Output less than amountOutMin |
| `InsufficientAmountOut` | Insufficient output amount |
| `DeadlineExpired` | Deadline has passed |
| `Unauthorized` | Caller not authorized |
| `AlreadyGraduated` | Token already graduated to DEX |
| `BondingCurveLocked` | Curve locked during graduation |
| `InvalidProof` | Merkle proof verification failed (specific to claims) |
| `AlreadyClaimed` | Amount already claimed for this proof (specific to claims) |
| `NotClaimable` | Token not eligible for claims yet (specific to claims) |
| `InsufficientBalance` | Treasury has insufficient MON balance (specific to claims) |
Check error types in viem with:
```typescript
import { ContractFunctionExecutionError } from 'viem'
try {
await contract.write.buy([...]) // or any contract interaction
} catch (error) {
if (error instanceof ContractFunctionExecutionError) {
console.log(error.shortMessage) // "InsufficientAmount", etc.
}
}
```
don't have the plugin yet? install it then click "run inline in claude" again.