Skill: xiaoyu022025/4claw-mint (version 1.0.0)

--- name: 4claw-mint description: Mint 4Claw tokens on BSC through OpenClaw agents. Each agent can mint 100 tokens every 15 minutes. Use when the user wants to mint 4Claw tokens, check mint status, or learn about the 4Claw token. --- # 4Claw Mint Mint **4Claw (symbol: 4)** tokens on BSC. Only OpenClaw agents can mint — the contract requires a signature from the authorized signer service. ## Token Info - Name: **4Claw**, Symbol: **4** - Chain: BSC (Chain ID: 56) - Contract: [REDACTED_SECRET] - Total Supply: 1,000,000 - Public Mint: 600,000 (100 per mint, 6000 total mints) - LP Reserve: 400,000 (pre-minted to deployer) - Cooldown: 15 minutes per wallet ## How It Works 1. Agent calls the signer service with its wallet address 2. Service checks cooldown, generates nonce + deadline, signs the mint authorization 3. Agent submits the signature to the on-chain contract 4. Contract verifies signature, enforces cooldown, mints 100 tokens ## Mint Run the mint script with the agent's wallet private key: ```bash node scripts/mint.js <PRIVATE_KEY> <SERVER_URL> ``` - `PRIVATE_KEY`: Agent's BSC wallet private key (needs small BNB for gas) - `SERVER_URL`: Signer service URL (default: http://43.160.201.224:3456) The script handles everything: request signature → send tx → confirm → report balance. ## Signer Service The signer service must be running for mints to work. It validates requests and signs mint authorizations. ```bash SIGNER_PRIVATE_KEY=0x... CONTRACT_ADDRESS=0x... node scripts/server.js ``` Endpoints: - `POST /api/mint-signature` — Request a mint signature. Body: `{"wallet_address": "0x..."}` - `GET /api/status` — Check service status ## Contract Source: `references/FourClaw.sol` Key functions: - `mint(nonce, deadline, signature)` — Mint 100 tokens (requires valid signer signature) - `lastMintTime(address)` — Check when an address last minted - `mintRemaining()` — How many public mint tokens are left - `setSigner(address)` — Owner can update the signer address ## Setup for Deployer 1. Deploy `FourClaw.sol` to BSC with constructor args: `(signerAddress, lpWalletAddress)` 2. Set env vars and start the signer service 3. Share the skill — any OpenClaw agent with a BSC wallet can mint ## Error Handling - **Cooldown not elapsed**: Wait 15 minutes between mints - **Public mint exhausted**: All 600,000 tokens have been minted - **Signature expired**: Signature is valid for 5 minutes, retry - **Invalid signature**: Signer service may be misconfigured - **Insufficient BNB**: Agent wallet needs BNB for gas (~0.001 BNB per mint)

path: scripts/mint.js
content: #!/usr/bin/env node
// 4Claw Mint Script — called by OpenClaw agents via skill
// Usage: node mint.js <private_key> <server_url>
// Example: node mint.js 0xYOUR_PRIVATE_KEY http://43.160.201.224:3456

const { ethers } = require("ethers");
const https = require("https");
const http = require("http");

const PRIVATE_KEY = process.argv[2];
const SERVER_URL = process.argv[3] || "http://43.160.201.224:3456";
const BSC_RPC = "https://bsc-dataseed1.binance.org";

const CONTRACT_ABI = [
  "function mint(bytes32 nonce, uint256 deadline, bytes signature) external",
  "function lastMintTime(address) view returns (uint256)",
  "function totalMinted() view returns (uint256)",
  "function mintRemaining() view returns (uint256)",
  "function balanceOf(address) view returns (uint256)",
  "function decimals() view returns (uint8)"
];

function fetchJSON(url, body) {
  return new Promise((resolve, reject) => {
    const parsed = new URL(url);
    const mod = parsed.protocol === "https:" ? https : http;
    const data = JSON.stringify(body);
    const req = mod.request(parsed, {
      method: "POST",
      headers: { "Content-Type": "application/json", "Content-Length": data.length }
    }, (res) => {
      let buf = "";
      res.on("data", c => buf += c);
      res.on("end", () => {
        try { resolve({ status: res.statusCode, data: JSON.parse(buf) }); }
        catch(e) { reject(new Error("Invalid JSON: " + buf.slice(0, 200))); }
      });
    });
    req.on("error", reject);
    req.write(data);
    req.end();
  });
}

(async () => {
  if (!PRIVATE_KEY) {
    console.error("Usage: node mint.js <private_key> [server_url]");
    process.exit(1);
  }

  try {
    const provider = new ethers.JsonRpcProvider(BSC_RPC);
    const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
    console.log(`Wallet: ${wallet.address}`);

    // Step 1: Request mint signature from server
    console.log("Requesting mint signature...");
    const sigRes = await fetchJSON(`${SERVER_URL}/api/mint-signature`, {
      wallet_address: wallet.address
    });

    if (sigRes.status === 429) {
      console.log(`Cooldown active. Retry in ${sigRes.data.retry_after_seconds}s`);
      process.exit(0);
    }

    if (sigRes.status !== 200 || !sigRes.data.success) {
      console.error("Signature request failed:", sigRes.data);
      process.exit(1);
    }

    const { nonce, deadline, signature, contract } = sigRes.data;
    console.log(`Got signature. Contract: ${contract}, Deadline: ${deadline}`);

    // Step 2: Call mint on contract
    const fourClaw = new ethers.Contract(contract, CONTRACT_ABI, wallet);

    console.log("Sending mint transaction...");
    const tx = await fourClaw.mint(nonce, deadline, signature);
    console.log(`TX sent: ${tx.hash}`);

    const receipt = await tx.wait();
    console.log(`TX confirmed! Block: ${receipt.blockNumber}, Gas: ${receipt.gasUsed.toString()}`);

    // Step 3: Check balance
    const [balance, decimals, remaining] = await Promise.all([
      fourClaw.balanceOf(wallet.address),
      fourClaw.decimals(),
      fourClaw.mintRemaining()
    ]);
    console.log(`Balance: ${ethers.formatUnits(balance, decimals)} 4Claw`);
    console.log(`Remaining public mint: ${ethers.formatUnits(remaining, decimals)} 4Claw`);

    // Output JSON for skill parsing
    console.log(JSON.stringify({
      success: true,
      tx_hash: tx.hash,
      block: receipt.blockNumber,
      balance: ethers.formatUnits(balance, decimals),
      remaining: ethers.formatUnits(remaining, decimals)
    }));

  } catch(e) {
    console.error("Mint failed:", e.message);
    console.log(JSON.stringify({ success: false, error: e.message }));
    process.exit(1);
  }
})();


path: scripts/server.js
content: const http = require("http");
const { ethers } = require("ethers");
const crypto = require("crypto");

// === Config (set via env or edit here) ===
const PORT = process.env.PORT || 3456;
const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY || "";
const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS || "";
const CHAIN_ID = parseInt(process.env.CHAIN_ID || "56"); // BSC mainnet
const SIGNATURE_TTL = 300; // 5 minutes
const MINT_COOLDOWN = 15 * 60; // 15 minutes in seconds

// === State ===
const lastMintTime = {}; // address => timestamp

if (!SIGNER_PRIVATE_KEY) {
  console.error("ERROR: SIGNER_PRIVATE_KEY is required");
  process.exit(1);
}
if (!CONTRACT_ADDRESS) {
  console.error("ERROR: CONTRACT_ADDRESS is required");
  process.exit(1);
}

const signer = new ethers.Wallet(SIGNER_PRIVATE_KEY);
console.log(`[Server] Signer address: ${signer.address}`);
console.log(`[Server] Contract: ${CONTRACT_ADDRESS}`);
console.log(`[Server] Chain ID: ${CHAIN_ID}`);

function parseBody(req) {
  return new Promise((resolve, reject) => {
    let body = "";
    req.on("data", c => body += c);
    req.on("end", () => {
      try { resolve(JSON.parse(body)); } catch(e) { reject(e); }
    });
  });
}

async function handleMintRequest(req, res) {
  try {
    const { wallet_address } = await parseBody(req);

    if (!wallet_address || !ethers.isAddress(wallet_address)) {
      return respond(res, 400, { error: "Invalid wallet_address" });
    }

    const addr = wallet_address.toLowerCase();
    const now = Math.floor(Date.now() / 1000);

    // Check cooldown (server-side, contract also enforces)
    if (lastMintTime[addr] && now - lastMintTime[addr] < MINT_COOLDOWN) {
      const remaining = MINT_COOLDOWN - (now - lastMintTime[addr]);
      return respond(res, 429, {
        error: "Cooldown not elapsed",
        retry_after_seconds: remaining
      });
    }

    // Generate nonce and deadline
    const nonce = "0x" + crypto.randomBytes(32).toString("hex");
    const deadline = now + SIGNATURE_TTL;

    // Sign: keccak256(abi.encodePacked(minter, nonce, deadline, chainId, contract))
    const messageHash = ethers.solidityPackedKeccak256(
      ["address", "bytes32", "uint256", "uint256", "address"],
      [wallet_address, nonce, deadline, CHAIN_ID, CONTRACT_ADDRESS]
    );

    const signature = await signer.signMessage(ethers.getBytes(messageHash));

    // Update cooldown
    lastMintTime[addr] = now;

    return respond(res, 200, {
      success: true,
      nonce,
      deadline,
      signature,
      contract: CONTRACT_ADDRESS,
      chain_id: CHAIN_ID
    });
  } catch(e) {
    console.error("[Error]", e.message);
    return respond(res, 500, { error: "Internal server error" });
  }
}

function respond(res, status, data) {
  res.writeHead(status, {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "*"
  });
  res.end(JSON.stringify(data));
}

const server = http.createServer((req, res) => {
  // CORS preflight
  if (req.method === "OPTIONS") {
    res.writeHead(204, {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "POST, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type"
    });
    return res.end();
  }

  if (req.method === "POST" && req.url === "/api/mint-signature") {
    return handleMintRequest(req, res);
  }

  if (req.method === "GET" && req.url === "/api/status") {
    return respond(res, 200, {
      signer: signer.address,
      contract: CONTRACT_ADDRESS,
      chain_id: CHAIN_ID,
      cooldown_seconds: MINT_COOLDOWN
    });
  }

  respond(res, 404, { error: "Not found" });
});

server.listen(PORT, () => {
  console.log(`[Server] 4Claw mint signer running on port ${PORT}`);
});


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)