Install
openclaw skills install base-budsMint a Base Bud NFT from the agent-only collection on Base mainnet. Requires solving a challenge, paying 1 USDC (x402), and an EVM wallet.
openclaw skills install base-budsMint a Base Bud NFT from the agent-only collection on Base mainnet.
| File | URL |
|---|---|
| SKILL.md (this file) | https://budsbase.xyz/skill.md |
Install locally:
mkdir -p ~/.openclaw/skills/base-buds
curl -s https://budsbase.xyz/skill.md > ~/.openclaw/skills/base-buds/SKILL.md
Or just read the URL directly!
Base URL: https://budsbase.xyz/api
The mint flow has four steps: challenge → prepare → complete (pay & get tx) → broadcast.
curl -X POST https://budsbase.xyz/api/challenge \
-H "Content-Type: application/json" \
-d '{"wallet": "YOUR_EVM_ADDRESS"}'
Response:
{
"challengeId": "0xabc123...",
"puzzle": "What is 347 * 23 + 156?",
"expiresAt": 1699999999999
}
A single node script that submits the challenge answer to /prepare, then signs the USDC payment locally. Your private key never leaves your machine.
Note: /prepare returns only payment data — no mint transaction. The mint transaction is only available after payment settles in Step 3.
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 wallet = new ethers.Wallet(PK);
// 2a. Submit challenge answer, get payment data
const res = await fetch("https://budsbase.xyz/api/prepare", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ wallet: wallet.address, challengeId: "CHALLENGE_ID", answer: "ANSWER" }),
});
const { prepareId, payment } = await res.json();
// 2b. Sign USDC payment (EIP-712)
const paymentSignature = await wallet.signTypedData(payment.domain, payment.types, payment.values);
console.log(JSON.stringify({ prepareId, paymentSignature }));
Submit the payment signature. The backend settles 1 USDC on-chain first, then returns the unsigned mint transaction.
curl -X POST https://budsbase.xyz/api/complete \
-H "Content-Type: application/json" \
-d '{
"prepareId": "0x<from_step_2>",
"paymentSignature": "0x<from_step_2>"
}'
Response:
{
"success": true,
"settleTxHash": "0x...",
"transaction": { "to": "0x...", "data": "0x...", "chainId": 8453, "nonce": 5, "type": 2, "..." : "..." },
"message": "Payment settled. Sign the transaction and POST to /api/broadcast."
}
Sign the mint transaction locally, then submit it.
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 wallet = new ethers.Wallet(PK);
const transaction = /* transaction object from Step 3 */;
const signedTransaction = await wallet.signTransaction(transaction);
console.log(JSON.stringify({ signedTransaction }));
curl -X POST https://budsbase.xyz/api/broadcast \
-H "Content-Type: application/json" \
-d '{"signedTransaction": "0x<from_above>"}'
Response:
{
"success": true,
"tokenId": 9,
"minter": "0x...",
"txHash": "0x...",
"minted": 2,
"remaining": 18,
"message": "Successfully minted Base Bud #9! You have minted 2 Base Buds. You can mint up to 18 more."
}
curl./prepare and signs the USDC payment. Do NOT split this into separate curl + node steps.
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.curl — this settles payment and returns the unsigned mint tx.curl to POST the signed tx to /broadcast./challenge returns 409, ask the user for a new wallet address and private key, then restart the flow with the new wallet./challenge| Code | Meaning |
|---|---|
| 400 | Invalid wallet address or missing fields |
| 409 | Wallet has reached the mint limit (20) |
| 410 | Collection is fully minted |
| 500 | Server error |
/prepare| Code | Meaning |
|---|---|
| 400 | Invalid wallet address, missing fields |
| 403 | Challenge answer is incorrect or expired |
| 500 | Server error |
/completeAll errors include a code field you can switch on:
code | HTTP | Meaning |
|---|---|---|
missing_prepare_id | 400 | No prepareId provided |
missing_payment_signature | 400 | No paymentSignature provided |
prepare_session_expired | 400 | Session not found or expired — call /prepare again |
authorization_expired | 400 | USDC authorization validBefore has passed |
authorization_not_yet_valid | 400 | USDC authorization validAfter is in the future |
insufficient_usdc_balance | 400 | Wallet doesn't have enough USDC |
payment_verification_failed | 402 | x402 facilitator rejected the payment signature |
payment_settlement_failed | 402 | x402 facilitator couldn't settle the USDC transfer |
/broadcastcode | HTTP | Meaning |
|---|---|---|
missing_signed_transaction | 400 | No signedTransaction provided |
nonce_too_low | 400 | Wallet has pending txs — call /complete again |
insufficient_eth | 400 | Not enough ETH for gas |
already_known | 409 | Transaction was already submitted |
mint_reverted | 400 | Mint transaction reverted on-chain |
broadcast_failed | 500 | Failed to broadcast transaction |