the skill
memegrid v0.1.0
A single self-contained SKILL.md file, written to the AgentSkills open standard. It's the only way tokens get deployed on Memegrid — no web form, no separate CLI to install first. Copy it, hand it to your agent, and the command surface /memegrid deploy token is live.
format
Single file, self-extracting
compatible with
Hermes, OpenClaw, Claude
commands
install · deploy · claim · status
environment
PINATA_JWT
Pins token logos to IPFS before a deploy.
MEMEGRID_API_KEY
Written automatically by "/memegrid install".
MEMEGRID_API_BASE
Overrides the default API base URL.
install
Pick the runtime your agent uses
- 01Copy the full SKILL.md content above
- 02Paste it into your Hermes chat and say: "Save this as a skill called memegrid"
- 03Hermes writes ~/.hermes/skills/memegrid/SKILL.md itself
No GitHub, no URL, no CLI — this is the fastest path for a single-file skill.
full source
SKILL.md
---
name: memegrid
description: Deploy a memecoin to the NOXA Fun launchpad on Robinhood Chain and claim creator fees, without ever handling a private key directly. Use this skill when the user asks to deploy or launch a token/memecoin, check tokens they've deployed, check wallet or fee balance, or claim creator fees. The command surface is /memegrid <install|deploy|claim|status|set-payout>. There is no web form for deploying — this skill is the only way in.
version: 0.1.0
metadata:
openclaw:
requires:
bins:
- node
primaryEnv: MEMEGRID_API_KEY
envVars:
- name: MEMEGRID_API_KEY
required: false
description: Written automatically by "/memegrid install". Only set by hand when migrating to a new host.
- name: PINATA_JWT
required: true
description: JWT from pinata.cloud, used to pin token logos to IPFS before a deploy.
- name: MEMEGRID_API_BASE
required: false
description: Overrides the default Memegrid API base URL (https://api.memegrid.xyz/v1).
emoji: "🧩"
homepage: https://memegrid.xyz
---
# Memegrid
This is a self-extracting skill: it's a single file so it can be pasted straight into chat and installed with
no GitHub repo and no separate CLI tool. The first time any `/memegrid` command runs, materialize the helper
files below under `~/.memegrid/lib/` **only if they don't already exist**, then run commands against them.
After the first run they're already on disk — don't rewrite them on every call.
## One-time setup: write these files if missing
Check whether `~/.memegrid/lib/api.mjs` exists. If it does not, create the following files with exactly this
content before doing anything else:
**`~/.memegrid/lib/config.mjs`**
```js
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
const CONFIG_DIR = path.join(os.homedir(), ".memegrid");
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
export function readConfig() {
if (!fs.existsSync(CONFIG_FILE)) return null;
try { return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8")); } catch { return null; }
}
export function writeConfig(data) {
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
}
export function requireConfig() {
const cfg = readConfig();
if (!cfg || !cfg.apiKey) {
console.error('Not registered yet. Run "/memegrid install" first.');
process.exit(1);
}
return cfg;
}
```
**`~/.memegrid/lib/ipfs.mjs`**
```js
import fs from "node:fs";
import path from "node:path";
const MAX_BYTES = 3 * 1024 * 1024;
const MIME_BY_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp", ".gif": "image/gif" };
export function validateLogoFile(filePath) {
if (!fs.existsSync(filePath)) throw new Error(`Logo file not found: ${filePath}`);
const ext = path.extname(filePath).toLowerCase();
if (!MIME_BY_EXT[ext]) throw new Error(`Unsupported logo format "${ext}". Allowed: png, jpg, jpeg, webp, gif`);
const { size } = fs.statSync(filePath);
if (size > MAX_BYTES) throw new Error(`Logo file is ${(size / 1024 / 1024).toFixed(2)}MB, exceeds the 3MB limit.`);
return { mime: MIME_BY_EXT[ext] };
}
export async function uploadLogoToIPFS(filePath) {
const { mime } = validateLogoFile(filePath);
const jwt = process.env.PINATA_JWT;
if (!jwt) throw new Error("PINATA_JWT is not set. Get a JWT from pinata.cloud first.");
const fileBuffer = fs.readFileSync(filePath);
const blob = new Blob([fileBuffer], { type: mime });
const form = new FormData();
form.append("file", blob, path.basename(filePath));
form.append("pinataMetadata", JSON.stringify({ name: `memegrid-logo-${path.basename(filePath)}` }));
const res = await fetch("https://api.pinata.cloud/pinning/pinFileToIPFS", {
method: "POST", headers: { Authorization: `Bearer ${jwt}` }, body: form,
});
if (!res.ok) throw new Error(`IPFS upload failed (${res.status}): ${await res.text().catch(() => "")}`);
const data = await res.json();
return { cid: data.IpfsHash, ipfsUri: `ipfs://${data.IpfsHash}`, gatewayUrl: `https://gateway.pinata.cloud/ipfs/${data.IpfsHash}` };
}
```
**`~/.memegrid/lib/api.mjs`**
```js
const API_BASE = process.env.MEMEGRID_API_BASE || "https://api.memegrid.xyz/v1";
async function request(pathName, { method = "GET", apiKey, body } = {}) {
const headers = { "Content-Type": "application/json" };
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
const res = await fetch(`${API_BASE}${pathName}`, { method, headers, body: body ? JSON.stringify(body) : undefined });
const text = await res.text();
let data; try { data = text ? JSON.parse(text) : {}; } catch { data = { raw: text }; }
if (!res.ok) throw new Error(`Memegrid API error (${res.status}): ${data.message || data.error || res.statusText}`);
return data;
}
export const register = (name) => request("/agents/register", { method: "POST", body: { name } });
export const deploy = (apiKey, payload) => request("/deploy", { method: "POST", apiKey, body: payload });
export const claim = (apiKey, tokenAddress) => request("/claim", { method: "POST", apiKey, body: tokenAddress ? { token_address: tokenAddress } : {} });
export const tokens = (apiKey) => request("/tokens", { apiKey });
export const balance = (apiKey) => request("/balance", { apiKey });
export const setPayoutAddress = (apiKey, address) => request("/agents/me/payout-address", { method: "PUT", apiKey, body: { address } });
```
Once those three files exist, every `/memegrid` command below is a short inline script run with `node --input-type=module -e "..."` (or written to a throwaway temp file first if the runtime prefers that) that imports from `~/.memegrid/lib/`.
## `/memegrid install`
Ask the user for an agent name if they haven't given one. Run:
```js
import { readConfig, writeConfig } from "os.homedir() + '/.memegrid/lib/config.mjs'"; // resolve the real path at runtime
import * as api from "<home>/.memegrid/lib/api.mjs";
const name = "<agent name>";
const existing = readConfig();
if (existing?.apiKey) {
console.log(`Already registered as "${existing.name}" — wallet ${existing.walletAddress}`);
} else {
const result = await api.register(name);
writeConfig({ name, apiKey: result.api_key, walletAddress: result.wallet_address });
console.log("Wallet address:", result.wallet_address);
}
```
Report the wallet address back to the user. Do not run `deploy`, `claim`, or `status` before this succeeds.
## `/memegrid deploy token`
Collect from the user before running anything — ask for anything missing, never invent values:
1. **Name** — max 60 characters.
2. **Symbol** — max 20 characters (upper-case it yourself).
3. **Description** — optional, max 256 characters.
4. **Socials** — Twitter, Telegram, website — all optional.
5. **Logo** — local file path. Required. PNG/JPEG/WebP/GIF, square, min 250×250px, **hard limit 3MB**. If the
file is too big, tell the user to compress it — do not deploy without a valid logo.
6. **Initial buy** — optional ETH amount.
Then run:
```js
import { requireConfig } from "<home>/.memegrid/lib/config.mjs";
import { uploadLogoToIPFS } from "<home>/.memegrid/lib/ipfs.mjs";
import * as api from "<home>/.memegrid/lib/api.mjs";
const cfg = requireConfig();
const logo = await uploadLogoToIPFS("<local logo path>");
console.log("Logo pinned:", logo.cid);
const result = await api.deploy(cfg.apiKey, {
name: "<name>", symbol: "<SYMBOL>", description: "<description>",
logo_url: logo.gatewayUrl, initial_buy: "<eth amount or 0>",
socials: { twitter: "<url>", telegram: "<url>", website: "<url>" },
});
console.log("Token:", result.token_address, "| Marketplace:", result.marketplace_url, "| Tx:", result.tx_hash);
```
Report token address, marketplace link, tx hash, and mention the ~1 hour anti-snipe window (wallet cap 2% of
supply).
## `/memegrid claim`
```js
import { requireConfig } from "<home>/.memegrid/lib/config.mjs";
import * as api from "<home>/.memegrid/lib/api.mjs";
const cfg = requireConfig();
const { claims } = await api.claim(cfg.apiKey, "<token address or omit>");
for (const c of claims || []) {
console.log(c.token_address, "gross:", c.gross_amount, "agent(70%):", c.agent_amount, "memegrid(30%):", c.memegrid_amount, "tx:", c.tx_hash);
}
```
Report the full breakdown exactly as returned — never round in the agent's favor or omit the Memegrid share.
## `/memegrid status`
```js
import { requireConfig } from "<home>/.memegrid/lib/config.mjs";
import * as api from "<home>/.memegrid/lib/api.mjs";
const cfg = requireConfig();
console.log(await api.tokens(cfg.apiKey)); // token list + accrued fees
console.log(await api.balance(cfg.apiKey)); // gas + unclaimed fee total
```
## `/memegrid set-payout`
```js
import { requireConfig } from "<home>/.memegrid/lib/config.mjs";
import * as api from "<home>/.memegrid/lib/api.mjs";
const cfg = requireConfig();
console.log(await api.setPayoutAddress(cfg.apiKey, "<external wallet address>"));
```
Tell the user about the 24-48 hour cooldown before the new address goes active.
## Boundaries to enforce
- Never fabricate a token address, transaction hash, or IPFS CID — only report what the scripts actually return.
- Never deploy without a valid logo file under 3MB.
- Never suggest sending funds to any wallet other than the one this skill provisioned.
- If a script errors, relay the error message rather than retrying silently in a loop.
- Deploying is agent-only by design — never point the user to a web form, because none exists.
Read the full walkthrough of what each command does on the how it works page.