Install
openclaw skills install @neonnodesrh/neonnodesskillNeon Nodes — An Agentic Proof of Work NFT on Robinhood Chain. AI solves a single-tier arithmetic puzzle to mint. Every mint becomes a node in a public graph connected through shared traits and geographical location.
openclaw skills install @neonnodesrh/neonnodesskillNeon Nodes — An Agentic Proof of Work NFT on Robinhood Chain. AI solves a single-tier arithmetic puzzle to mint. Every mint becomes a node in a public graph connected through shared traits and geographical location.
| File | URL |
|---|---|
| SKILL.md (this file) | https://neonnodes.xyz/skill.md |
Install locally:
mkdir -p ~/.openclaw/skills/NeonNodes
curl -s https://neonnodes.xyz/skill.md > ~/.openclaw/skills/NeonNodes/SKILL.md
Or just read the URL directly!
Base URL: https://neonnodes.xyz/api
The mint flow has four steps: puzzle → solve → sign locally → submit.
Default is 1 mint per puzzle. To batch mint up to 5 NFTs in one transaction, pass an optional quantity (1..5):
# Single mint (default)
curl -X POST https://neonnodes.xyz/api/puzzle \
-H "Content-Type: application/json" \
-d '{"wallet": "YOUR_EVM_ADDRESS"}'
# Batch mint 5 in one tx
curl -X POST https://neonnodes.xyz/api/puzzle \
-H "Content-Type: application/json" \
-d '{"wallet": "YOUR_EVM_ADDRESS", "quantity": 5}'
Response:
{
"puzzleId": "pzl_abc123...",
"question": "What is 74 + 39?",
"quantity": 1,
"expiresAt": 1699999999999,
"agentHint": "Solve this puzzle and POST the answer to /api/solve..."
}
curl -X POST https://neonnodes.xyz/api/solve \
-H "Content-Type: application/json" \
-d '{
"wallet": "YOUR_EVM_ADDRESS",
"puzzleId": "pzl_abc123...",
"answer": "113"
}'
Response:
{
"unsignedTx": {
"to": "0x...",
"data": "0x...",
"value": "0x2aa1efb94e000",
"chainId": 4663
},
"mintPrice": "0.00075",
"quantity": 1,
"totalCost": "0.00075",
"nonce": "0x...",
"agentHint": "Sign this transaction locally. NEVER send the private key to any server..."
}
For a batch of 5, value and totalCost will be 5× the mint price; the encoded calldata targets mintBatch(quantity, nonce, signature) instead of mint.
Sign with the user's EVM private key. This must happen locally — the private key never leaves the machine.
import { ethers } from "ethers";
const PK = "YOUR_PRIVATE_KEY";
if (!/^0x[0-9a-fA-F]{64}$/.test(PK)) throw new Error("Invalid private key — must be 0x + 64 hex chars");
const provider = new ethers.JsonRpcProvider("https://rpc.mainnet.chain.robinhood.com");
const wallet = new ethers.Wallet(PK, provider);
// Robinhood Chain is an Arbitrum-style L2 with a single sequencer.
// Real-world observations from live mints on this chain:
// - Base fee typically ~0.02 gwei, occasionally spikes to ~0.1 gwei
// - Priority fee is 0 (sequencer, no MEV bidding)
// - EIP-1559 refunds the difference between maxFeePerGas and
// effectiveGasPrice, so a generous ceiling is safe (not costly).
// - A single SeaDrop-style mint uses ~150-200k gas
// The values below give a comfortable ceiling; real cost per mint
// is ~0.00001 ETH regardless of the cap you set.
const signedTx = await wallet.signTransaction({
...unsignedTx,
gasLimit: 220000,
maxFeePerGas: ethers.parseUnits("0.15", "gwei"), // ~7.5x base fee — chain refunds unused portion
maxPriorityFeePerGas: ethers.parseUnits("0.01", "gwei"), // nominal — sequencer takes ~0
nonce: await provider.getTransactionCount(wallet.address),
type: 2,
});
// Optional (safer): read live suggested fee from the chain instead
// of hardcoding, so you always target the current base fee.
// const feeData = await provider.getFeeData();
// const signedTx = await wallet.signTransaction({
// ...unsignedTx,
// gasLimit: 220000,
// maxFeePerGas: (feeData.maxFeePerGas ?? feeData.gasPrice) * 2n,
// maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? 10000000n, // 0.01 gwei
// nonce: await provider.getTransactionCount(wallet.address),
// type: 2,
// });
curl -X POST https://neonnodes.xyz/api/submit \
-H "Content-Type: application/json" \
-d '{
"signedTransaction": "0x02f8..."
}'
Response:
{
"success": true,
"tokenId": "1",
"tokenIds": ["1"],
"quantity": 1,
"hash": "0xabc...",
"wallet": "0x...",
"minted": 1,
"remaining": 24,
"message": "Successfully minted Neon Node #1! You have minted 1 Neon Node. You can mint up to 24 more.",
"agentHint": "Show the user the message field. They can mint 24 more. To mint another, POST to /api/puzzle with their wallet."
}
For a batch of 5, tokenIds will contain all 5 minted ids, quantity is 5, and the message lists every id.
quantity: 1 (or omit quantity) on the first /api/puzzle call. Do NOT ask about batch upfront.new ethers.Wallet(PK).address, then curl POST to /api/puzzle with { wallet } for single (or { wallet, quantity } on subsequent batch mints).curl POST to /api/solve. Server returns unsignedTx with value = quantity × mintPrice.curl POST the signed transaction to /api/submit. Response includes tokenIds (array of ALL minted ids) and quantity.node -e "require('ethers')". If it fails, install to a temp location: npm install --prefix /tmp ethers, then run the script with NODE_PATH=/tmp/node_modules.message field — it lists every token id minted (#1, #2, #3, ...), how many they've minted total, and how many more they can mint.remaining from the previous mint response and let batchMax = min(remaining, 5). Then:
batchMax === 0 → say "This wallet has reached the 25-mint cap. Give me a new wallet's private key to continue."batchMax === 1 → say "You have 1 slot left. Want to mint the last one?"batchMax === 2..4 → say "You can mint up to N more in a single transaction. Want to batch (1..N) or one at a time?" (N = batchMax)batchMax === 5 → say "You can mint up to 5 more in a single transaction. Want to batch (1-5) or one at a time?"quantity on the next /api/puzzle call, capped at batchMax.remaining from the previous mint response (or calling GET /api/check/{wallet}). Then:
remaining < requested_quantity, cap the batch at remaining and tell the user: "You have N slots left, minting N in one batch."remaining === 0, the wallet is capped — say so and ask for a different wallet's key.quantity larger than remaining. The server will return 413 mint_limit_reached and burn the request. Always cap client-side first.quantity: 5. Check remaining → see 1 → tell user "You only have 1 slot left, minting 1" → send quantity: 1./puzzle or /solve returns 413, the wallet has hit its 25-mint cap OR the requested batch exceeds remaining. Refetch /api/check/{wallet} to see how many slots are actually left, then either downsize the batch or ask for a new wallet key.quantity × 0.00075 ETH + gas. Do not proceed until they confirm the wallet is funded.Base URL: https://neonnodes.xyz/api
agentHint— Every API response includes anagentHintfield with step-by-step instructions for what to do next. Always read and follow theagentHint.
| Method | Endpoint | Description |
|---|---|---|
| GET | /info | Collection stats and mint price |
| GET | /check/{wallet} | Wallet mint status and remaining |
| POST | /puzzle | Get a puzzle to solve |
| POST | /solve | Submit answer and get mint transaction |
| POST | /submit | Submit signed transaction to Robinhood Chain |
| GET | /metadata/{id} | NFT metadata from IPFS |
/puzzleRequest body:
{
"wallet": "string (required) — your EVM wallet address",
"quantity": "number (optional, default 1, range 1..5) — batch mint size"
}
Success (200):
{
"puzzleId": "string — signed puzzle token (pass back to /solve)",
"question": "string — the puzzle prompt to solve",
"quantity": "number — how many NFTs this puzzle will mint (1..5)",
"expiresAt": "number — Unix timestamp when puzzle expires",
"agentHint": "string — what to do next"
}
/solveRequest body:
{
"wallet": "string (required) — your EVM wallet address",
"puzzleId": "string (required) — puzzle ID from /puzzle",
"answer": "string (required) — your answer to the puzzle"
}
Success (200):
{
"unsignedTx": "object — unsigned Ethereum transaction to sign",
"mintPrice": "string — per-NFT mint price in ETH",
"quantity": "number — how many NFTs will mint (1..5)",
"totalCost": "string — quantity × mintPrice in ETH (matches unsignedTx.value)",
"nonce": "string — mint nonce",
"agentHint": "string — signing instructions and next step"
}
/submitRequest body:
{
"signedTransaction": "string (required) — hex-encoded fully-signed transaction"
}
Success (200):
{
"success": "boolean — true on success",
"tokenId": "string — FIRST minted token ID (convenience for single-mint agents)",
"tokenIds": "string[] — ALL minted token IDs (length = quantity)",
"quantity": "number — how many NFTs were minted in this tx",
"hash": "string — transaction hash",
"wallet": "string — minter address",
"minted": "number — total NFTs minted by this wallet",
"remaining": "number — how many more this wallet can mint",
"message": "string — human-readable summary",
"agentHint": "string — what to do next (mint more or done)"
}
/puzzle| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_wallet | Invalid wallet address or missing fields |
| 403 | mint_not_active | Minting is paused |
| 413 | mint_limit_reached | Wallet has reached max mints (25) |
| 410 | sold_out | All NFTs have been minted |
| 500 | server_error | Server error |
/solve| HTTP | code | Meaning |
|---|---|---|
| 400 | wrong_answer | Wrong answer (includes attemptsLeft) |
| 400 | puzzle_expired | Puzzle has expired (5 min) |
| 404 | puzzle_not_found | Puzzle ID not found or already consumed |
| 413 | mint_limit_reached | Wallet has reached max mints (25) |
| 410 | sold_out | All NFTs minted |
| 500 | server_error | Server error |
/submit| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_transaction | Missing or invalid transaction hex |
| 400 | invalid_target | Transaction doesn't target the Neon Nodes contract |
| 400 | nonce_too_low | Wallet has pending tx — retry |
| 400 | insufficient_eth | Not enough ETH for gas |
| 400 | mint_reverted | Mint transaction reverted on-chain |
| 409 | already_known | Transaction was already submitted |
| 500 | broadcast_failed | Failed to broadcast transaction |
/solve produces one NFTquantity (1..5) at /api/puzzle to mint up to 5 NFTs in a single transaction. One puzzle unlocks the whole batch. Value = quantity × mintPrice. On confirmation, tokenIds in the submit response contains every minted id.US, JP, DE) is captured server-side to power the public country graph — XX if unknown. Raw IPs, ASNs, and coordinates are never stored, logged, or transmitted.