CRITICAL COMMUNICATION RULES:
Save all this data in OpenClaw memory for future use.
.env is missing or any required variable (MONAD_PRIVATE_KEY, MONAD_RPC_URL, MMIND_TOKEN_ADDRESS, MONAD_NETWORK) is not set, ask the user to provide it before running trading or scripts. Do not proceed with buy/sell or execute-bonding-v2 until config is complete.Autonomous trading agent that scans Nad.fun markets, analyzes tokens using momentum strategies, executes trades, and distributes profits to MMIND token holders.
monad-development skill installed (for wallet and RPC setup)nadfun-trading skill installed (for buy/sell operations), or use the trading/ folder from this reponadfun-indexer skill installed (for querying events)nadfun-agent-api skill installed (for market data)Paths (clean install): Config is read from NADFUN_ENV_PATH if set, else $HOME/nadfunagent/.env. Positions report: POSITIONS_REPORT_PATH or $HOME/nadfunagent/positions_report.json. Scripts from nadfun-trading may be at ~/.openclaw/workspace/skills/nadfun-trading or at . See DEPENDENCIES.md.
CRITICAL: Load environment variables from .env file (default path: $HOME/nadfunagent/.env; override with NADFUN_ENV_PATH):
MMIND_TOKEN_ADDRESS: Address of MMIND token for profit distribution (required)MONAD_PRIVATE_KEY: Private key for trading wallet (required)MONAD_RPC_URL: RPC endpoint URL (required)MONAD_NETWORK: Network type - "mainnet" or "testnet" (required)MAX_POSITION_SIZE: Maximum position size in MON (default: 0.1)PROFIT_TARGET_PERCENT: Take-profit threshold (default: 20%)STOP_LOSS_PERCENT: Stop-loss threshold (default: 10%)PNL_DISTRIBUTION_PERCENT: % of profit to distribute (default: 30%)Before starting trading cycle:
.env file (path: NADFUN_ENV_PATH or $HOME/nadfunagent/.env). If the file is missing or any required variable is empty, ask the user to provide MMIND_TOKEN_ADDRESS, MONAD_PRIVATE_KEY, MONAD_RPC_URL, MONAD_NETWORK — do not run trading until config is complete.MMIND_TOKEN_ADDRESS - use this for profit distributionMONAD_PRIVATE_KEY - use this for wallet operationsMONAD_RPC_URL - use this for blockchain queriesMONAD_NETWORK - determines API endpoints (mainnet: api.nadapp.net, testnet: dev-api.nad.fun)EXECUTION SUMMARY - READ THIS FIRST:
$HOME/nadfunagent/.env (MMIND_TOKEN_ADDRESS, MONAD_PRIVATE_KEY, MONAD_RPC_URL, MONAD_NETWORK)$HOME/nadfunagent/found_tokens.json for manual reviewCRITICAL INSTRUCTIONS: You MUST scan tokens using ALL 7 methods below. Execute each method step-by-step and collect ALL found token addresses.
EXECUTION ORDER:
STEP-BY-STEP EXECUTION:
CRITICAL LOGGING REQUIREMENT:
After executing EACH method, you MUST log:
NOTE: Only Methods 5, 6, and 7 are used (from HAR file analysis). Methods 1-4 are disabled.
STATUS CHECK: This method requires RPC access. You MUST execute it, don't skip it!
What to do:
$HOME/nadfunagent/.env to get MONAD_RPC_URL and MONAD_NETWORKeth_blockNumbersafeLatest = currentBlock - 10 (safety margin)nadfun-indexer skillHow to execute:
nadfun-indexer skill with parameters: event=CurveCreate, fromBlock=, toBlock=event.args.token or event.args[1] (token is second argument)Example command structure:
Use skill: nadfun-indexer
Parameters: event=CurveCreate, fromBlock=<calculated_start>, toBlock=<calculated_end>
Extract: token address from each event
Expected result: Array of token addresses (0x...) from newly created tokens
STATUS CHECK: This method requires RPC access. You MUST execute it, don't skip it!
What to do:
How to execute:
nadfun-indexer skill: event=CurveBuy, fromBlock=, toBlock=publicClient.getContractEvents({ address: CURVE_ADDRESS, eventName: "CurveBuy", fromBlock, toBlock })token = event.args.token or event.args[1]amountIn = event.args.amountIn or event.args[2] (amount in MON)Example:
1. Use same RPC client from Method 1
2. Query CurveBuy events in chunks of 100 blocks
3. For each event: extract token and amountIn
4. Sum volume per token
5. Sort DESC, take top 20
6. Print: "✅ Method 2: Found 20 trending tokens"
Expected result: Top 20 token addresses sorted by trading volume
What to do:
How to execute:
MONAD_NETWORK=mainnet then https://api.nadapp.net, else https://dev-api.nad.fun${API_URL}/agent/swap-history/${tokenAddress}?limit=50&trade_type=ALLresponse.swaps:swap.swap_info.timestamp - if within last 24 hoursswap.swap_info.native_amount (in wei, convert to MON: divide by 1e18)Example:
API_URL = MONAD_NETWORK === 'mainnet' ? 'https://api.nadapp.net' : 'https://dev-api.nad.fun'
For each token in top10:
GET ${API_URL}/agent/swap-history/${token}?limit=50&trade_type=ALL
Calculate 24h volume
If volume >= 1 MON: add to trendingViaAPI
Wait 1-2 seconds
Expected result: List of tokens with high 24h trading volume
STATUS CHECK: Don't skip this method! Execute it with rate limit delays.
What to do:
How to execute:
amountIn > 1000000000000000000 (1 MON in wei = 1e18)sender address: event.args.sender or event.args[0]${API_URL}/agent/holdings/${address}?limit=50Accept: application/jsonholding in response.tokens:holding.balance_info.balance - if > 0holding.token_info.address or holding.token_info.token_idExample:
From Method 2 CurveBuy events:
Filter: amountIn > 1e18 (1 MON)
Extract: sender addresses
Take first 5 unique addresses
For each address:
GET ${API_URL}/agent/holdings/${address}?limit=50
Extract tokens with balance > 0
Wait 2 seconds
Print: "✅ Method 4: Found X tokens"
Expected result: Set of token addresses held by active large traders
STATUS: ✅ This method is WORKING! Found 7 tokens in last scan.
What to do:
/api/token/new-event endpointHow to execute:
MONAD_NETWORK=mainnet: baseUrl = 'https://nad.fun'baseUrl = 'https://dev.nad.fun'${baseUrl}/api/token/new-eventAccept: application/json, User-Agent: OpenClaw-Agent/1.0event.type === 'CREATE': extract event.token_info.token_idevent.type === 'BUY': extract event.token_info.token_idevent.type === 'SELL': optionally extract (indicates active trading)Example:
baseUrl = MONAD_NETWORK === 'mainnet' ? 'https://nad.fun' : 'https://dev.nad.fun'
GET ${baseUrl}/api/token/new-event
Parse JSON response
For each event:
If event.type === 'CREATE' or 'BUY':
Add event.token_info.token_id to tokensFromEvents
Print: "✅ Method 5: Found X tokens"
Expected result: Array of token addresses from recent CREATE and BUY events
Response structure:
type: "BUY" | "SELL" | "CREATE"amount: Event amount (string)token_info: Complete token information (token_id, name, symbol, description, creator, etc.)account_info: Account that performed the actionSTATUS CHECK: Previous error: base64 decoding failed. Fix: Use proper decoding method.
What to do:
How to execute:
${API_URL}/order/market_cap?page=1&limit=50&is_nsfw=falseAPI_URL = MONAD_NETWORK === 'mainnet' ? 'https://api.nadapp.net' : 'https://dev-api.nad.fun'Accept: application/json, User-Agent: OpenClaw-Agent/1.0import base64; decoded = base64.b64decode(response_text).decode('utf-8')echo "$response_text" | base64 -d{ "tokens": [...], "total_count": N }data.tokens:token.token_info.token_id (token address)token_info AND market_info{token_info: {...}, market_info: {...}, percent: ...}Example:
API_URL = MONAD_NETWORK === 'mainnet' ? 'https://api.nadapp.net' : 'https://dev-api.nad.fun'
response = GET ${API_URL}/order/market_cap?page=1&limit=100&is_nsfw=false
decoded = base64.b64decode(response.text).decode('utf-8')
data = json.loads(decoded)
topMarketCapTokens = [] # Array of full token objects
For each token in data.tokens:
# Store FULL token object with token_info + market_info
topMarketCapTokens.append({
'token_info': token.token_info,
'market_info': token.market_info,
'percent': token.percent,
'address': token.token_info.token_id # For easy access
})
Print: "✅ Method 6: Found X tokens with market data"
Expected result: Array of full token objects (with token_info + market_info) sorted by market cap
Response structure:
tokens: Array of token objects with token_info and market_infototal_count: Total number of tokenstoken_info (token_id, name, symbol, creator, etc.) and market_info (market_type, price, volume, holder_count, etc.)CRITICAL: Include BOTH bonding curve AND DEX tokens! Do NOT filter by is_graduated.
What to do:
How to execute:
${API_URL}/order/creation_time?page=1&limit=50&is_nsfw=false&direction=DESCdata.tokens arraytoken.token_info.token_id (token address)token_info AND market_infoExample:
API_URL = MONAD_NETWORK === 'mainnet' ? 'https://api.nadapp.net' : 'https://dev-api.nad.fun'
response = GET ${API_URL}/order/creation_time?page=1&limit=50&is_nsfw=false&direction=DESC
decoded = base64.b64decode(response.text).decode('utf-8')
data = json.loads(decoded)
newestTokens = [] # Array of full token objects
For each token in data.tokens:
# Include ALL tokens - both bonding curve AND DEX
# Store FULL token object with token_info + market_info
newestTokens.append({
'token_info': token.token_info,
'market_info': token.market_info,
'percent': token.percent,
'address': token.token_info.token_id # For easy access
})
Print: "✅ Method 7: Found X tokens with market data (bonding curve + DEX)"
Expected result: Array of full token objects (with token_info + market_info) - newest tokens including both bonding curve AND DEX
Response structure:
tokens array with token_info and market_infodirection=DESC returns newest firstWhat to do:
How to execute:
allTokens = new Set() or allTokens = [] (use Set to avoid duplicates)tokenFrequency = new Map() or {} to count occurrencesIMPORTANT: For Methods 6 & 7, store the FULL token objects (not just addresses) so you can use market_info directly for analysis!
allTokensMap = {} where key = token address, value = token object with metadataallTokensMap[address] = {address, source: 'method5', data: null}allTokensMap[address] = {address, source: 'method6', data: fullTokenObject}allTokensMap[address] = {address, source: 'method7', data: fullTokenObject}tokenFrequency[address] = (tokenFrequency[address] || 0) + 1candidateTokens = Object.values(allTokensMap) or Array.from(allTokensMap.values())```javascript
candidateTokens.sort((a, b) => {
const freqA = tokenFrequency[a.address] || 0
const freqB = tokenFrequency[b.address] || 0
if (freqB !== freqA) return freqB - freqA // Higher frequency first
// Prefer tokens with full data (market_info)
const hasDataA = a.data && a.data.market_info ? 1 : 0
const hasDataB = b.data && b.data.market_info ? 1 : 0
return hasDataB - hasDataA
})
```
prioritizedTokens = candidateTokensExample:
allTokensMap = {} # Map: address -> {address, source, data}
tokenFrequency = {}
# Method 5: addresses only
for address in tokensFromEvents:
allTokensMap[address] = {address: address, source: 'method5', data: null}
tokenFrequency[address] = (tokenFrequency[address] || 0) + 1
# Method 6: full objects with market_info
for token in topMarketCapTokens:
address = token.token_info.token_id
allTokensMap[address] = {address: address, source: 'method6', data: token}
tokenFrequency[address] = (tokenFrequency[address] || 0) + 1
# Method 7: full objects with market_info
for token in newestTokens:
address = token.token_info.token_id
allTokensMap[address] = {address: address, source: 'method7', data: token}
tokenFrequency[address] = (tokenFrequency[address] || 0) + 1
# Sort by frequency and data availability
candidateTokens = Object.values(allTokensMap)
prioritizedTokens = candidateTokens.sort((a, b) => {
freqDiff = tokenFrequency[b.address] - tokenFrequency[a.address]
if (freqDiff !== 0) return freqDiff
return (b.data && b.data.market_info ? 1 : 0) - (a.data && a.data.market_info ? 1 : 0)
})
Print: "📊 Combined: N unique tokens, X found in 2+ methods, Y found in all 3 methods, Z with full data"
Expected result: Prioritized array of token objects, each containing:
address: token addresssource: which method(s) found itdata: full token object with token_info + market_info (if available from Methods 6 or 7)foundInMethods: count from tokenFrequencyWhat to do:
After combining all methods and getting prioritizedTokens, you MUST save them to file $HOME/nadfunagent/found_tokens.json for manual review.
How to execute:
{address: token, foundInMethods: frequency_count}$HOME/nadfunagent/found_tokens.json[]Example using Node.js script (RECOMMENDED):
# From skill root or repo root: print token addresses (one per line) and pipe to script
# prioritizedTokens is the array of token addresses; take first 50 and pass to script
echo -e "0x...\n0x..." | node scripts/save_tokens.js
Or from the agent: write the list of addresses to a temp file or stdin and run node scripts/save_tokens.js (script reads from stdin). Data dir: NADFUNAGENT_DATA_DIR or $HOME/nadfunagent.
Expected result: File $HOME/nadfunagent/found_tokens.json updated with latest scan results
IMPORTANT: Methods 6 (Market Cap API) and Method 7 (Creation Time API) already return COMPLETE token data with market_info. Use this data directly for analysis - DO NOT make additional API calls unless absolutely necessary!
Data Structure from Methods 6 & 7:
Each token in the response has this structure:
{
"token_info": {
"token_id": "0x...",
"name": "...",
"symbol": "...",
"is_graduated": true/false,
"created_at": timestamp,
...
},
"market_info": {
"market_type": "DEX" or "BONDING_CURVE",
"reserve_native": "1625513352353765005672133", // Liquidity in MON (wei format)
"reserve_token": "51582894169959918089536846",
"token_price": "0.000886889894056452",
"price": "0.0415501242",
"price_usd": "0.000886889894056452",
"volume": "761848094037806233587694495", // Total volume (wei format)
"holder_count": 31580, // Number of holders
"ath_price": "0.0128807779",
"ath_price_usd": "0.0128807779",
...
},
"percent": 8.797 // Market cap change percentage
}
Analysis Steps:
data.tokens array from Methods 6 & 7:token_info.token_id (token address)market_info.reserve_native (liquidity in wei, convert to MON: divide by 1e18)market_info.holder_count (number of holders)market_info.volume (total volume in wei, convert to MON: divide by 1e18)market_info.market_type ("DEX" or "BONDING_CURVE")token_info.is_graduated (true if graduated to DEX)percent (market cap change percentage)/agent/market/:token_id to get market_info```javascript
liquidityMON = parseFloat(market_info.reserve_native) / 1e18
// Score based on liquidity tiers
if (liquidityMON >= 1000) liquidityScore = 100 // Excellent
else if (liquidityMON >= 500) liquidityScore = 80 // Very good
else if (liquidityMON >= 100) liquidityScore = 60 // Good
else if (liquidityMON >= 50) liquidityScore = 40 // Fair
else if (liquidityMON >= 10) liquidityScore = 20 // Low
else liquidityScore = 0 // Too low
weightedLiquidity = liquidityScore * 0.30
```
```javascript
// Use percent (market cap change) as momentum indicator
percentChange = parseFloat(token.percent) || 0
// Score based on positive momentum
if (percentChange >= 50) momentumScore = 100 // Strong growth
else if (percentChange >= 20) momentumScore = 80
else if (percentChange >= 10) momentumScore = 60
else if (percentChange >= 5) momentumScore = 40
else if (percentChange >= 0) momentumScore = 20
else momentumScore = 0 // Negative momentum
weightedMomentum = momentumScore * 0.25
```
```javascript
volumeMON = parseFloat(market_info.volume) / 1e18
// Score based on volume tiers
if (volumeMON >= 1000000) volumeScore = 100 // 1M+ MON volume
else if (volumeMON >= 500000) volumeScore = 80
else if (volumeMON >= 100000) volumeScore = 60
else if (volumeMON >= 50000) volumeScore = 40
else if (volumeMON >= 10000) volumeScore = 20
else volumeScore = 0
weightedVolume = volumeScore * 0.20
```
```javascript
holderCount = parseInt(market_info.holder_count) || 0
// Score based on holder count
if (holderCount >= 10000) holderScore = 100 // Excellent distribution
else if (holderCount >= 5000) holderScore = 80
else if (holderCount >= 1000) holderScore = 60
else if (holderCount >= 500) holderScore = 40
else if (holderCount >= 100) holderScore = 20
else holderScore = 0
weightedHolders = holderScore * 0.15
```
```javascript
isGraduated = token_info.is_graduated === true
marketType = market_info.market_type
// Score based on market stage
if (isGraduated || marketType === "DEX") {
progressScore = 100 // Fully graduated to DEX
} else {
// Still on bonding curve - use percent as progress indicator
// Higher percent = closer to graduation
percent = parseFloat(token.percent) || 0
if (percent >= 80) progressScore = 80
else if (percent >= 50) progressScore = 60
else if (percent >= 30) progressScore = 40
else progressScore = 20
}
weightedProgress = progressScore * 0.10
```
```javascript
// Check for social media presence and website (indicates legitimacy)
hasTwitter = token_info.twitter && token_info.twitter.length > 0
hasTelegram = token_info.telegram && token_info.telegram.length > 0
hasWebsite = token_info.website && token_info.website.length > 0
authorityScore = 0
if (hasTwitter) authorityScore += 3
if (hasTelegram) authorityScore += 3
if (hasWebsite) authorityScore += 4
// Maximum +10 bonus points for full social presence
authorityBonus = Math.min(authorityScore, 10)
```
```javascript
totalScore = weightedLiquidity + weightedMomentum + weightedVolume + weightedHolders + weightedProgress + authorityBonus
// Round to 2 decimal places
totalScore = Math.round(totalScore * 100) / 100
```
For each token, store:
```javascript
{
address: token_info.token_id,
name: token_info.name,
symbol: token_info.symbol,
liquidity: liquidityMON,
holders: holderCount,
volume: volumeMON,
marketType: marketType,
isGraduated: isGraduated,
percentChange: percentChange,
scores: {
liquidity: liquidityScore,
momentum: momentumScore,
volume: volumeScore,
holders: holderScore,
progress: progressScore,
total: totalScore
},
foundInMethods: tokenFrequency[token_info.token_id] || 1
}
```
CRITICAL INSTRUCTIONS:
/agent/market/:token_id for tokens from Methods 6 & 7token_info.twitter, token_info.telegram, token_info.website - add bonus pointsFilter tokens based on:
BEFORE analyzing new tokens, you MUST check and manage existing positions!
What to do:
MONAD_PRIVATE_KEY in .env (derive address from private key)/agent/holdings/${walletAddress}?limit=100balance > 0 (exclude zero balances)How to execute:
// 1. Get wallet address from private key
const walletAddress = getAddressFromPrivateKey(MONAD_PRIVATE_KEY)
// 2. Query holdings
const holdingsResponse = await fetch(`${API_URL}/agent/holdings/${walletAddress}?limit=100`)
const holdings = await holdingsResponse.json()
// 3. Filter tokens with balance > 0
const activePositions = holdings.tokens.filter(token => {
const balance = parseFloat(token.balance_info.balance) || 0
return balance > 0
})
Print: "📊 Current positions: Found N tokens with balance > 0"
CRITICAL: Always use the check-pnl.js script from nadfun-trading skill for proper P&L calculation. This script:
entryValueMON) from $HOME/nadfunagent/positions_report.json (automatically recorded by buy-token.js when you purchase)(currentValueMON - entryValueMON) / entryValueMON * 100What to do:
check-pnl.js from nadfun-trading skill directorybuy-token.js after purchases)How to execute:
# Check P&L for all positions
cd $HOME/.openclaw/workspace/skills/nadfun-trading
node check-pnl.js
# Or with auto-sell (sells if P&L >= +5% or <= -10%)
node check-pnl.js --auto-sell
Manual calculation (if script unavailable):
// Load entry prices from positions_report.json
const report = JSON.parse(await fs.readFile('$HOME/nadfunagent/positions_report.json', 'utf-8'))
for (const position of activePositions) {
const tokenAddress = position.token_info.token_id || position.token_info.address
const tokenBalance = parseFloat(position.balance_info.balance) || 0
// Get entry price from JSON (recorded by buy-token.js)
const prev = report.positions?.find(p =>
(p.address || '').toLowerCase() === tokenAddress.toLowerCase()
)
const entryValueMON = prev?.entryValueMON || 0
// Get current value on-chain via nad.fun quote contract
const [router, amountOutWei] = await publicClient.readContract({
address: '0x7e78A8DE94f21804F7a17F4E8BF9EC2c872187ea', // nad.fun quote contract
abi: lensAbi,
functionName: 'getAmountOut',
args: [tokenAddress, parseEther(tokenBalance.toString()), false] // false = selling
})
const currentValueMON = Number(amountOutWei) / 1e18
// Calculate P&L
const pnlPercent = entryValueMON > 0
? ((currentValueMON - entryValueMON) / entryValueMON) * 100
: 0
positions.push({
address: tokenAddress,
balance: tokenBalance,
entryValueMON: entryValueMON,
currentValueMON: currentValueMON,
pnlPercent: pnlPercent
})
}
Print: "💰 Position P&L calculated for N positions (source: on-chain nad.fun quote + positions_report.json)"
Entry Price Tracking:
buy-token.js after successful purchase$HOME/nadfunagent/positions_report.json as entryValueMONcheck-pnl.js uses current value as fallback (P&L = 0%)What to do:
For each position, check sell conditions:
```javascript
if (pnlPercent <= -10) { // STOP_LOSS_PERCENT = -10%
// SELL ALL - stop loss triggered
sellDecision = {
action: 'SELL_ALL',
reason: 'Stop-loss triggered',
tokenAddress: position.address,
amount: 'all'
}
}
```
```javascript
if (pnlPercent >= 20) { // PROFIT_TARGET_PERCENT = 20%
// SELL HALF - take profit
sellDecision = {
action: 'SELL_HALF',
reason: 'Take-profit triggered',
tokenAddress: position.address,
amount: position.balance / 2
}
}
```
```javascript
// If profit was > 15% but dropped to < 5%, sell to protect gains
if (previousPnL > 15 && pnlPercent < 5) {
sellDecision = {
action: 'SELL_ALL',
reason: 'Trailing stop - protecting gains',
tokenAddress: position.address,
amount: 'all'
}
}
```
How to execute:
const sellDecisions = []
for (const position of positions) {
// Stop-loss: sell all if down 10%+
if (position.pnlPercent <= -10) {
sellDecisions.push({
tokenAddress: position.address,
action: 'SELL_ALL',
reason: `Stop-loss: ${position.pnlPercent.toFixed(2)}%`,
amount: 'all'
})
}
// Take-profit: sell half if up 20%+
else if (position.pnlPercent >= 20) {
sellDecisions.push({
tokenAddress: position.address,
action: 'SELL_HALF',
reason: `Take-profit: ${position.pnlPercent.toFixed(2)}%`,
amount: position.balance / 2
})
}
}
Print: "🔔 Sell decisions: N positions need action"
for (const decision of sellDecisions) {
Print: ` ${decision.action}: ${decision.tokenAddress} - ${decision.reason}`
}
What to do:
For each sell decision:
nadfun-trading skill with action sellHow to execute:
for (const decision of sellDecisions) {
try {
// Use nadfun-trading skill
await useSkill('nadfun-trading', {
action: 'sell',
token: decision.tokenAddress,
amount: decision.amount // 'all' or specific amount
})
Print: `✅ Sold ${decision.amount} of ${decision.tokenAddress}: ${decision.reason}`
} catch (error) {
Print: `❌ Failed to sell ${decision.tokenAddress}: ${error.message}`
}
}
CRITICAL: Only buy new tokens AFTER managing existing positions!
Buy Decision Logic:
CRITICAL: Always use the buy-token.js script from nadfun-trading skill. This script:
$HOME/nadfunagent/positions_report.json after successful purchaseBuy tokens:
cd $HOME/.openclaw/workspace/skills/nadfun-trading
NAD_PRIVATE_KEY=$MONAD_PRIVATE_KEY node buy-token.js <token-address> <MON-amount> [--slippage=300]
Example:
NAD_PRIVATE_KEY=0x... node buy-token.js 0x123...abc 0.15 --slippage=300
After purchase:
entryValueMON) is automatically recorded in $HOME/nadfunagent/positions_report.jsoncheck-pnl.js for P&L calculationRisk management:
nadfun-trading skill automatically detects market type and uses correct contractCRITICAL: Use MMIND_TOKEN_ADDRESS from $HOME/nadfunagent/.env file.
When profit >= 0.1 MON:
// Step 1: Load MMIND_TOKEN_ADDRESS from .env file
const envFile = await readFile('$HOME/nadfunagent/.env', 'utf-8')
const mmindMatch = envFile.match(/MMIND_TOKEN_ADDRESS=(0x[a-fA-F0-9]+)/)
if (!mmindMatch) {
throw new Error('MMIND_TOKEN_ADDRESS not found in .env file')
}
const MMIND_TOKEN_ADDRESS = mmindMatch[1]
// Step 2: Get MMIND token holders via Indexer
// Query Transfer events to find all addresses that ever held MMIND
const latestBlock = await publicClient.getBlockNumber()
const safeLatest = latestBlock - 10n
// Query in chunks (RPC limit: ~100 blocks)
const transfers = []
for (let from = 0n; from < safeLatest; from += 10000n) {
const to = from + 10000n > safeLatest ? safeLatest : from + 10000n
try {
const events = await useSkill('nadfun-indexer', {
event: 'Transfer',
token: MMIND_TOKEN_ADDRESS,
fromBlock: from,
toBlock: to
})
transfers.push(...events)
} catch (err) {
// Continue if chunk fails
}
}
// Step 3: Collect unique addresses
const holderAddresses = new Set()
transfers.forEach(event => {
if ('args' in event) {
if (event.args.from && event.args.from !== '0x0000000000000000000000000000000000000000') {
holderAddresses.add(event.args.from)
}
if (event.args.to && event.args.to !== '0x0000000000000000000000000000000000000000') {
holderAddresses.add(event.args.to)
}
}
})
// Step 4: Get current balances for each holder
const distributions = []
for (const address of holderAddresses) {
try {
const balance = await publicClient.readContract({
address: MMIND_TOKEN_ADDRESS,
abi: erc20Abi,
functionName: 'balanceOf',
args: [address]
})
if (balance > 0n) {
distributions.push({ address, balance })
}
} catch (err) {
// Skip if balance check fails
}
}
// Step 5: Get total supply
const totalSupply = await publicClient.readContract({
address: MMIND_TOKEN_ADDRESS,
abi: erc20Abi,
functionName: 'totalSupply'
})
// Step 6: Calculate and distribute profit proportionally
const profitToDistribute = (profit * BigInt(PNL_DISTRIBUTION_PERCENT)) / 100n
for (const holder of distributions) {
const share = (profitToDistribute * holder.balance) / totalSupply
if (share >= parseEther('0.001')) { // Minimum 0.001 MON
await transferMON(holder.address, share) // Transfer MON directly (all trading uses MON)
}
}
The agent runs continuously:
MONAD_PRIVATE_KEY/agent/holdings/${walletAddress}?limit=100check-pnl.js to get real P&L (reads entry price from $HOME/nadfunagent/positions_report.json, gets current value on-chain via nad.fun quote contract)sell-token.js or check-pnl.js --auto-sell for stop-loss (P&L <= -10%) or take-profit (P&L >= +5%)/api/token/new-event) for real-time BUY/CREATE eventsapi.nadapp.net/order/market_cap, base64 decoded)api.nadapp.net/order/creation_time, base64 decoded)Start autonomous trading:
# Via OpenClaw chat
"Start autonomous trading agent"
# Or via cron job (runs every minute). Paths: use NADFUN_ENV_PATH / NADFUNAGENT_DATA_DIR for .env and data; run scripts from nadfun-trading skill directory (clawhub install).
openclaw cron add \
--name "Nad.fun Trading Agent" \
--cron "* * * * *" \
--session isolated \
--message "Run autonomous trading cycle: 1) Load config from .env (path: NADFUN_ENV_PATH or NADFUNAGENT_DATA_DIR/.env; need MMIND_TOKEN_ADDRESS, MONAD_PRIVATE_KEY, MONAD_RPC_URL, MONAD_NETWORK). 2) From nadfun-trading skill directory run: node execute-bonding-v2.js (uses check-pnl.js for P&L from positions_report.json at POSITIONS_REPORT_PATH or NADFUNAGENT_DATA_DIR, auto-sells at +5% or -10%). 3) If there is positive PnL (profit >= 0.1 MON), distribute profits to MMIND token holders: use MMIND_TOKEN_ADDRESS from .env, get holders via indexer/Transfer events, distribute proportionally (e.g. 30%) in MON. Report output in English."
Check agent status:
# Via OpenClaw chat
"Show trading agent status and statistics"
View found tokens: (data dir: NADFUNAGENT_DATA_DIR, default $HOME/nadfunagent)
# View latest found tokens
cat "${NADFUNAGENT_DATA_DIR:-$HOME/nadfunagent}/found_tokens.json" | jq '.[-1]'
# View all tokens from last scan
cat "${NADFUNAGENT_DATA_DIR:-$HOME/nadfunagent}/found_tokens.json" | jq '.[-1].tokens[] | .address'
# View tokens found in multiple methods (higher confidence)
cat "${NADFUNAGENT_DATA_DIR:-$HOME/nadfunagent}/found_tokens.json" | jq '.[-1].tokens[] | select(.foundInMethods > 1) | .address'
# View summary of last 10 scans
cat "${NADFUNAGENT_DATA_DIR:-$HOME/nadfunagent}/found_tokens.json" | jq '.[-10:] | .[] | {timestamp, totalFound}'
Manual trade:
# Via OpenClaw chat
"Buy 0.1 MON worth of token 0x..."
"Sell all tokens for 0x..."
This skill integrates with:
CRITICAL: Handle rate limits gracefully to avoid HTTP 429 errors.
Nad.fun Agent API:
Nad.fun Public APIs (nad.fun/api/ and api.nadapp.net/):
Anthropic/Claude API (CRITICAL - This is the main source of HTTP 429):
/10 *) to reduce frequency/15 *)```typescript
// Wait 2-3 seconds between Nad.fun API calls
await new Promise(resolve => setTimeout(resolve, 2000))
// Wait 1 second between Indexer queries
await new Promise(resolve => setTimeout(resolve, 1000))
```
```typescript
async function fetchWithRetry(url, options, maxRetries = 2) {
for (let i = 0; i < maxRetries; i++) {
try {
const result = await fetch(url, options)
if (result.status === 429) {
const retryAfter = parseInt(result.headers.get('Retry-After') || '60')
console.log(Rate limited, waiting ${retryAfter} seconds...)
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
continue
}
return result
} catch (err) {
if (i === maxRetries - 1) throw err
await new Promise(resolve => setTimeout(resolve, 5000 * (i + 1)))
}
}
}
```
/10 *)/15 *)共 1 个版本