
Aicoin Market
Wire Claude Code, Cursor, or Codex agents to AiCoin Open Data v3 for signed crypto market and open-data API calls.
Overview
aicoin-market is an agent skill for the Build phase that connects solo-builder agents to AiCoin Open Data v3 with HMAC-SHA1 signed requests and a unified JSON envelope.
Install
npx skills add https://github.com/aicoincom/coinos-skills --skill aicoin-marketWhat is this skill?
- AiCoin Open Data v3 client with HMAC-SHA1 header authentication and configurable base URL
- Unified response envelope with ok, data, error, and meta fields for consistent agent parsing
- Loads AICOIN_ACCESS_KEY_ID from cwd .env, OpenClaw workspace .env, or defaults.json fallbacks
- Designed for OpenClaw exec contexts where parent processes do not inject environment variables
- Catalog-driven endpoint surface documented alongside the shared request helper
- AiCoin Open Data v3 API with HMAC-SHA1 signed header authentication
- Unified JSON envelope fields: ok, data, error, and meta
- Auto-loads .env from up to three common paths including OpenClaw workspace locations
Adoption & trust: 541 installs on skills.sh; 42 GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
You need reliable crypto market data inside an agent workflow but do not want to rebuild HMAC signing, env loading, and response parsing for every AiCoin endpoint.
Who is it for?
Indie builders shipping crypto agents, OpenClaw skills, or Node automation that must call AiCoin market APIs with header auth and shared error handling.
Skip if: Teams with no AiCoin credentials, non-crypto products, or builders who only need static mock data without live API access.
When should I use this skill?
You are implementing or debugging Node agents that must call AiCoin market or open-data endpoints with ACCESS_KEY_ID credentials and consistent response parsing.
What do I get? / Deliverables
Your agent issues authenticated AiCoin Open Data v3 calls and reads normalized ok/data/error/meta responses so you can plug quotes, catalogs, or feeds into bots and apps faster.
- Authenticated AiCoin Open Data v3 HTTP requests from the bundled client
- Parsed unified envelope responses for agent-friendly ok/data/error handling
- Configured environment loading for OpenClaw and local workspace .env files
Recommended Skills
Journey fit
The skill ships a Node client and auth flow for an external market-data API, which solo builders add while implementing product or agent integrations. Canonical shelf is integrations because the artifact is HMAC-signed HTTP access to AiCoin endpoints, not frontend UI, launch SEO, or production incident response.
How it compares
Use this signed REST skill integration instead of ad-hoc fetch snippets or assuming a separate MCP server already wraps AiCoin.
Common Questions / FAQ
Who is aicoin-market for?
aicoin-market is for solo and indie developers building agents, scripts, or small services that need AiCoin Open Data v3 market endpoints with proper HMAC-SHA1 authentication in Node.
When should I use aicoin-market?
Use it during Build → integrations while wiring trading bots, portfolio tools, or research agents to live crypto data, and whenever OpenClaw or child processes fail to inherit API keys from your shell.
Is aicoin-market safe to install?
Review the Security Audits panel on this Prism page for published findings, rotate AiCoin keys if you expose .env files, and never commit access keys to git.
SKILL.md
READMESKILL.md - Aicoin Market
#!/usr/bin/env node // AiCoin Open Data v3 API client — HMAC-SHA1 signed, header auth. // One unified envelope {ok, data, error, meta}; see catalog for all endpoints. import { createHmac, randomBytes } from 'node:crypto'; import { readFileSync, existsSync, writeFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; const __dirname = dirname(fileURLToPath(import.meta.url)); // ── .env auto-load (OpenClaw exec may not inject env into child processes) ── const ENV_FILES = [ resolve(process.cwd(), '.env'), resolve(process.env.HOME || '', '.openclaw', 'workspace', '.env'), resolve(process.env.HOME || '', '.openclaw', '.env'), ]; for (const file of ENV_FILES) { if (!existsSync(file)) continue; try { for (const line of readFileSync(file, 'utf-8').split('\n')) { const t = line.trim(); if (!t || t.startsWith('#')) continue; const eq = t.indexOf('='); if (eq < 1) continue; const k = t.slice(0, eq).trim(); let v = t.slice(eq + 1).trim(); if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1); if (!process.env[k]) process.env[k] = v; } } catch { /* ignore unreadable .env */ } } const defaults = JSON.parse(readFileSync(resolve(__dirname, 'defaults.json'), 'utf-8')); export const BASE = process.env.AICOIN_BASE_URL || 'https://open.aicoin.com'; export const KEY = process.env.AICOIN_ACCESS_KEY_ID || defaults.accessKeyId; const SECRET = process.env.AICOIN_ACCESS_SECRET || defaults.accessSecret; export const USING_OWN_KEY = !!(process.env.AICOIN_ACCESS_KEY_ID && process.env.AICOIN_ACCESS_SECRET); // HMAC-SHA1(signStr, secret) → hex → base64. The 4 values ride in X-Aic-* headers. function authHeaders(keyId = KEY, secret = SECRET) { const nonce = randomBytes(8).toString('hex'); const ts = Math.floor(Date.now() / 1000).toString(); const signStr = `AccessKeyId=${keyId}&SignatureNonce=${nonce}&Timestamp=${ts}`; const hex = createHmac('sha1', secret).update(signStr).digest('hex'); return { 'X-Aic-AccessKey-Id': keyId, 'X-Aic-Signature-Nonce': nonce, 'X-Aic-Timestamp': ts, 'X-Aic-Signature': Buffer.from(hex).toString('base64'), }; } // Normalize a user-supplied endpoint to a full /api/v3/... path. // "market/ticker" / "/market/ticker" / "/api/v3/market/ticker" → "/api/v3/market/ticker" export function normalizePath(ep) { let p = String(ep || '').trim().replace(/^https?:\/\/[^/]+/, ''); if (p.startsWith('/api/v3/') || p === '/api/v3') return p; p = p.replace(/^\/?(api\/v3\/?)?/, ''); return '/api/v3/' + p; } // endpoints.json — a bundled catalog snapshot. Drives GET/POST selection and // offline `catalog`. Live catalog is still the source of truth (see fetchCatalog). let _snapshot = null; export function snapshotEndpoints() { if (_snapshot) return _snapshot; try { const j = JSON.parse(readFileSync(resolve(__dirname, 'endpoints.json'), 'utf-8')); _snapshot = j.endpoints || []; } catch { _snapshot = []; } return _snapshot; } // Pull the live catalog. Falls back to the bundled snapshot when offline. export async function fetchCatalog() { try { const { httpStatus, body } = await request('GET', '/api/v3/_catalog'); if (httpStatus === 200 && body?.data?.endpoints) return { endpoints: body.data.endpoints, live: true }; } catch { /* fall through to snapshot */ } return { endpoints: snapshotEndpoints(), live: false }; } // Core request. Returns { httpStatus, body }; body is the parsed envelope. export async function request(method, path, params = {}) { const full = normalizePath(path); const m = (method || 'GET').toUpperCase(); const headers = authHeaders(); let url = `${BASE}${full}`; const init = { method: m, headers, signal: AbortSignal.timeout(30000) }; if (m === 'GET' || m === 'DELETE') { const qs = new URLSearchParams(); for (const [k, v] of Object.entries(params || {})) { if (v