From 81a8e2e30521957dd44374baf9307bba6a163dd7 Mon Sep 17 00:00:00 2001 From: ernani Date: Tue, 30 Jun 2026 23:23:14 -0300 Subject: [PATCH] =?UTF-8?q?Submit=20solana-api-skill=20=E2=80=94=20first-c?= =?UTF-8?q?all-correct=20on=20any=20Solana/crypto=20API=20(built=20on=20ge?= =?UTF-8?q?cko-surf)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- solana-api-skill/.github/workflows/ci.yml | 27 +++ solana-api-skill/.gitignore | 19 ++ solana-api-skill/CHANGELOG.md | 30 ++++ solana-api-skill/CLAUDE.md | 61 +++++++ solana-api-skill/LICENSE | 21 +++ solana-api-skill/README.md | 170 ++++++++++++++++++ solana-api-skill/SUBMISSION.md | 133 ++++++++++++++ solana-api-skill/VERSION | 1 + .../agents/api-comprehension-engineer.md | 68 +++++++ solana-api-skill/commands/comprehend-api.md | 55 ++++++ solana-api-skill/install.sh | 53 ++++++ solana-api-skill/rules/first-call.md | 37 ++++ solana-api-skill/scripts/validate-skill.sh | 39 ++++ solana-api-skill/skill/SKILL.md | 163 +++++++++++++++++ solana-api-skill/skill/access-and-auth.md | 82 +++++++++ solana-api-skill/skill/apis/clmm.md | 119 ++++++++++++ solana-api-skill/skill/apis/helius.md | 134 ++++++++++++++ solana-api-skill/skill/apis/jupiter.md | 131 ++++++++++++++ solana-api-skill/skill/discovery.md | 95 ++++++++++ solana-api-skill/skill/drift.md | 76 ++++++++ solana-api-skill/skill/first-call-correct.md | 93 ++++++++++ .../skill/gecko-surf-power-path.md | 98 ++++++++++ .../tests/verify-live-examples.sh | 59 ++++++ 23 files changed, 1764 insertions(+) create mode 100644 solana-api-skill/.github/workflows/ci.yml create mode 100644 solana-api-skill/.gitignore create mode 100644 solana-api-skill/CHANGELOG.md create mode 100644 solana-api-skill/CLAUDE.md create mode 100644 solana-api-skill/LICENSE create mode 100644 solana-api-skill/README.md create mode 100644 solana-api-skill/SUBMISSION.md create mode 100644 solana-api-skill/VERSION create mode 100644 solana-api-skill/agents/api-comprehension-engineer.md create mode 100644 solana-api-skill/commands/comprehend-api.md create mode 100755 solana-api-skill/install.sh create mode 100644 solana-api-skill/rules/first-call.md create mode 100755 solana-api-skill/scripts/validate-skill.sh create mode 100644 solana-api-skill/skill/SKILL.md create mode 100644 solana-api-skill/skill/access-and-auth.md create mode 100644 solana-api-skill/skill/apis/clmm.md create mode 100644 solana-api-skill/skill/apis/helius.md create mode 100644 solana-api-skill/skill/apis/jupiter.md create mode 100644 solana-api-skill/skill/discovery.md create mode 100644 solana-api-skill/skill/drift.md create mode 100644 solana-api-skill/skill/first-call-correct.md create mode 100644 solana-api-skill/skill/gecko-surf-power-path.md create mode 100755 solana-api-skill/tests/verify-live-examples.sh diff --git a/solana-api-skill/.github/workflows/ci.yml b/solana-api-skill/.github/workflows/ci.yml new file mode 100644 index 0000000..cfccb36 --- /dev/null +++ b/solana-api-skill/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + pull_request: + schedule: + # Weekly drift check — re-verify the documented examples against the live APIs, + # so an upstream change that breaks a ✅ example surfaces here, not in a user's agent. + - cron: "0 13 * * 1" + +jobs: + validate: + name: validate structure + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: bash scripts/validate-skill.sh + + live-examples: + name: verify examples against live APIs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Jupiter is keyless and always runs. Helius DAS runs only if the secret is set. + - run: bash tests/verify-live-examples.sh + env: + HELIUS_API_KEY: ${{ secrets.HELIUS_API_KEY }} diff --git a/solana-api-skill/.gitignore b/solana-api-skill/.gitignore new file mode 100644 index 0000000..42524f2 --- /dev/null +++ b/solana-api-skill/.gitignore @@ -0,0 +1,19 @@ +# Secrets — never commit +.env +.env.* +!.env.example +*.key +*.pem + +# Python / tooling +__pycache__/ +*.pyc +.venv/ +.mypy_cache/ +.ruff_cache/ +.pytest_cache/ + +# OS / editor +.DS_Store +.idea/ +.vscode/ diff --git a/solana-api-skill/CHANGELOG.md b/solana-api-skill/CHANGELOG.md new file mode 100644 index 0000000..4b3087e --- /dev/null +++ b/solana-api-skill/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +All notable changes to `solana-api-skill` are documented here. +Format loosely follows [Keep a Changelog](https://keepachangelog.com/); this +project uses [Semantic Versioning](https://semver.org/). + +## [0.1.0] — 2026-06-30 + +First draft. The comprehension layer for making any Solana/crypto API +first-call-correct. + +### Added +- **The spine** — `skill/SKILL.md` plus the four-job method files: + - `discovery.md` — pick the right endpoint/method among many. + - `access-and-auth.md` — place the credential the docs don't explain; scope/host gating. + - `first-call-correct.md` — placement, units, idempotency, enums, identity. + - `drift.md` — re-comprehend when the API changes instead of hand-patching. +- **Marquee playbooks** with verified `❌ wrong → ✅ correct` first calls: + - `apis/helius.md` — RPC + DAS; named-object vs positional-array params; key in the URL. + - `apis/jupiter.md` — quote → swap; atomic base units; `slippageBps`; echo the whole quote. + - `apis/clmm.md` — Orca / Raydium / Meteora; pool-vs-position-vs-mint identity; ticks ≠ prices. +- **Power-path** — `gecko-surf-power-path.md`: point `gecko-surf` at any OpenAPI for + first-call-correct MCP tools. +- `agents/api-comprehension-engineer.md`, `commands/comprehend-api.md`, + `rules/first-call.md`. +- Copy-only `install.sh`, `README.md`, `SUBMISSION.md`, `LICENSE` (MIT), `VERSION`. + +### Notes +- API specifics verified against official docs / live calls on 2026-06-30. Items + that could not be fully verified firsthand are marked `` in-file. diff --git a/solana-api-skill/CLAUDE.md b/solana-api-skill/CLAUDE.md new file mode 100644 index 0000000..fb491b3 --- /dev/null +++ b/solana-api-skill/CLAUDE.md @@ -0,0 +1,61 @@ +# CLAUDE.md — solana-api-skill + +Context for any agent working *in this repo*. (The skill itself, for end users, is +`skill/SKILL.md`.) + +## What this is + +A standalone, MIT-licensed skill for the Solana AI Kit: the **comprehension layer** +that makes any Solana/crypto API **first-call-correct**. An **addon to the core +[`solana-dev-skill`](https://github.com/solana-foundation/solana-dev-skill)** — +every other bounty skill teaches one protocol; this teaches the agent to call +*any* of them correctly the first time. + +It ships **no executable logic of its own** beyond a copy-only `install.sh`. The +content is markdown the agent reads. The optional power-path delegates mechanical +comprehension to [`gecko-surf`](https://github.com/GeckoVision/gecko-surf) (MIT, on +PyPI), the engine GeckoVision built. + +## Layout + +``` +skill/ SKILL.md (entry + routing) → the four-job spine: + discovery, access-and-auth, first-call-correct, drift + + gecko-surf-power-path (any OpenAPI -> MCP tools) + + apis/ (helius, jupiter, clmm — verified ❌→✅ playbooks) +commands/ /comprehend-api +agents/ api-comprehension-engineer (comprehension specialist persona) +rules/ first-call (opt-in: comprehend before the first call) +install.sh copy-only installer · LICENSE (MIT) · README.md · SUBMISSION.md +VERSION · CHANGELOG.md +``` + +## Rules for editing + +- **Accuracy over polish.** This is a public competition entry. Every API specific + in `skill/apis/` must be verifiable against the official docs or a live call. + Anything you can't verify firsthand gets a `` marker, not an + invented value. The current `` items: Helius default DAS `limit` + and the `getTransactionsForAddress` method name; Jupiter V2 `/order`+`/execute` + details; Meteora `dlmm-api.meteora.ag/pair/all` live body. +- **The spine is generalizable; the apis/ files are instances.** Keep the four-job + procedure in SKILL.md + the method files API-agnostic. Protocol-specific gotchas + live only in `skill/apis/`. +- **No bloat, nothing opaque.** No binaries, no network calls in the installer (kit + requirement). Markdown + a copy script only. The power-path `pip install` is the + user's explicit, separate action. +- **Security.** Never put a real API key in an example — use `$HELIUS_API_KEY` / + `$JUP_API_KEY` placeholders. The skill teaches *hiding* auth from the tool def + and *redacting* it from logs; the repo must model that. +- **Keep it progressive.** SKILL.md routes; focused files load on demand. Don't + inline everything into SKILL.md. +- **Stay in our lane.** This is the **comprehension/consumption** layer. It is NOT + a payment rail and NOT a marketplace; it composes on top of MCP/x402 and consumes + a spec as input. If an edit drifts toward either, stop. + +## Provider + +Built by [GeckoVision](https://geckovision.tech) — the API-comprehension company. +Engine: [`gecko-surf`](https://github.com/GeckoVision/gecko-surf) · +https://pypi.org/project/gecko-surf/. Sibling skill: +[`solana-token-safety-skill`](https://github.com/GeckoVision/solana-token-safety-skill). diff --git a/solana-api-skill/LICENSE b/solana-api-skill/LICENSE new file mode 100644 index 0000000..5eeb0ad --- /dev/null +++ b/solana-api-skill/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 GeckoVision + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/solana-api-skill/README.md b/solana-api-skill/README.md new file mode 100644 index 0000000..6965a09 --- /dev/null +++ b/solana-api-skill/README.md @@ -0,0 +1,170 @@ +# solana-api-skill + +**Make any Solana/crypto API agent-usable — first-call-correct.** + +An addon to the core [`solana-dev-skill`](https://github.com/solana-foundation/solana-dev-skill). +A progressive, token-efficient skill that teaches a coding/trading agent a +**generalizable procedure** for calling an unfamiliar Solana or crypto API and +getting the **first** call right — the right endpoint, the credential in the right +place, the exact request shape, and a drift-aware version pin. + +Built by **[GeckoVision](https://geckovision.tech)**, the API-comprehension +company, on top of the open-source engine +[`gecko-surf`](https://github.com/GeckoVision/gecko-surf) (MIT, on PyPI). + +## Quickstart + +**Standalone** (Claude Code / Codex / any agent): + +```bash +git clone https://github.com/GeckoVision/solana-api-skill && cd solana-api-skill +bash install.sh # copy-only into ~/.claude — no binaries, no network +``` + +**With the [Solana AI Kit](https://github.com/solanabr/solana-ai-kit)** (the bounty's home — 15 agents, 30 commands, MCP, progressive skills): + +```bash +# 1) install the kit (Claude Code plugin) +/plugin marketplace add solanabr/solana-ai-kit +/plugin install solana-ai-kit@stbr +# 2) add this skill on top (or submit it to the kit's ext/ so SKILL.md routes to it) +bash install.sh +``` + +The kit's `SKILL.md` hub loads skill files on demand; `solana-api-skill` slots in as the +**API-comprehension layer its protocol skills sit on top of.** Then drive it via the +`api-comprehension-engineer` agent or `/comprehend-api `. + +## The gap it fills + +Across **100+ open skill-bounty PRs, every skill teaches ONE protocol.** None +makes an agent **first-call-correct against *any* Solana/crypto API.** That's the +hole this fills. This is the **comprehension layer the others sit on top of**: a +single-protocol skill assumes the agent already knows how to call that protocol; +this skill is the procedure for when it doesn't. + +The problem isn't missing docs. Docs are written **for humans** — prose, scattered +examples, the auth handshake assumed, units implied. An agent reading them +one-shot picks the wrong method, forgets the key, sends a UI amount where atomic +units are required, or PUTs a field the API wants in the query string. Each miss +is a failed call, a retry, a burned credit. + +## What it does — the four jobs + +Turn any API surface into a correct call by getting four things right: + +| # | Job | The question it answers | +|---|---|---| +| 1 | **Discovery** | Of hundreds of methods, *which one* answers this intent? | +| 2 | **Access & Auth** | What credential goes *where* (URL? header? body?), at what scope? | +| 3 | **First-call-correct** | The *exact* shape — path vs query vs body, units, idempotency, enums? | +| 4 | **Drift** | The API changed. *Re-comprehend* it — don't hand-patch one call. | + +Get these four right and the first call lands. Get one wrong and it doesn't. + +## Marquee playbooks (real APIs, real gotchas) + +Each ships a verified **❌ the call an agent gets wrong → ✅ the correct first call**: + +- **Helius** (RPC + DAS) — DAS methods take a **named object**, not a positional + array; the API key lives in the **URL**. → [`skill/apis/helius.md`](skill/apis/helius.md) +- **Jupiter** (quote → swap) — `amount` is **atomic base units** (lamports), not a + decimal; `slippageBps` is **basis points**; POST the **whole** quote object. → + [`skill/apis/jupiter.md`](skill/apis/jupiter.md) +- **CLMM** (Orca Whirlpools / Raydium CLMM / Meteora DLMM) — pass the + **pool/position pubkey, not a token mint**; a **tick is not a price**. → + [`skill/apis/clmm.md`](skill/apis/clmm.md) + +## The power-path (any OpenAPI, no hand-written file) + +For an API not covered above, comprehend it mechanically. `gecko-surf` turns any +OpenAPI 3.x spec into first-call-correct, question-shaped MCP tools (auth hidden, +units and placement resolved): + +```bash +pip install "gecko-surf[serve]" +gecko https://api.example.com/openapi.json # serves first-call-correct tools over MCP +``` + +PyPI: https://pypi.org/project/gecko-surf/ · source: https://github.com/GeckoVision/gecko-surf + +## Install + +```bash +git clone && cd solana-api-skill +bash install.sh # copies the skill + command + agent + rule into ~/.claude +``` + +Or point it at a custom config dir: `CLAUDE_DIR=/path bash install.sh`. +The installer is **copy-only — no binaries, no network calls.** The power-path is +a separate, explicit `pip install` you run yourself. + +## Use + +- Ask your agent: *"make a first-call to Helius `getAssetsByOwner` for ``"* + or *"quote 1.5 SOL → USDC on Jupiter."* +- Command: `/comprehend-api ` +- Agent persona: `api-comprehension-engineer` runs the full four-job flow. +- Rule: `first-call` — comprehend before the first call; never brute-force a 4xx. + +## Structure + +``` +skill/ + SKILL.md entry point + routing (load this) + discovery.md Job 1 — pick the right call among many + access-and-auth.md Job 2 — the key/header/scope handshake the spec omits + first-call-correct.md Job 3 — placement, units, idempotency, enums, identity + drift.md Job 4 — re-comprehend on change; don't hand-patch + gecko-surf-power-path.md point gecko-surf at any OpenAPI -> first-call-correct MCP tools + apis/ + helius.md RPC + DAS (named object vs array; key in URL) + jupiter.md quote -> swap (atomic units; bps; whole quote) + clmm.md Orca / Raydium / Meteora (pool vs position vs mint; ticks) +commands/ + comprehend-api.md /comprehend-api +agents/ + api-comprehension-engineer.md comprehension specialist persona +rules/ + first-call.md opt-in: comprehend before the first call +install.sh copy-only installer (no binaries, no network) +CLAUDE.md · LICENSE (MIT) · README.md · SUBMISSION.md · VERSION · CHANGELOG.md +``` + +## How it works + +The skill ships **no executable logic of its own** beyond the copy-only installer +— it's markdown the agent reads. The optional power-path delegates the mechanical +work to `gecko-surf`, which ingests an OpenAPI spec, builds an intent→endpoint +catalog, generates question-shaped tools with auth hidden, and serves them over +MCP. `gecko-surf` is **control-plane only** — it stores the API *surface* and +correctness metadata, never your response payloads, user data, or secrets. + +- Engine: [`gecko-surf`](https://github.com/GeckoVision/gecko-surf) (MIT) · + https://pypi.org/project/gecko-surf/ +- Provider: [GeckoVision](https://geckovision.tech) — the API-comprehension + company. Also ships + [`solana-token-safety-skill`](https://github.com/GeckoVision/solana-token-safety-skill). + +## Tested against live APIs + +The whole point of this skill is **first-call-correctness + drift-resilience** — so its +own examples are *tested against the real endpoints*, not just asserted in prose. + +```bash +bash scripts/validate-skill.sh # structure, frontmatter, links + flags unverified claims +bash tests/verify-live-examples.sh # runs the documented ❌→✅ calls against the LIVE APIs +``` + +- **`verify-live-examples.sh`** fires the documented Jupiter calls (keyless) and asserts the + documented behavior: `amount=1.5` → **HTTP 400**, `amount=1500000000` + `slippageBps=50` → + echoed back, `routePlan` present. Helius DAS runs when `HELIUS_API_KEY` is set (named-object → + result; positional array → error). +- **CI** ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)) runs both on every push **and + weekly on a schedule** — so if an upstream API drifts and breaks a ✅ example, it fails *here*, + not in a user's agent. **The skill that teaches drift-resilience is itself drift-tested.** +- `validate-skill.sh` also lists any `` claims still to pin before publishing. + +## License + +MIT — ready to be merged or submoduled into the kit. diff --git a/solana-api-skill/SUBMISSION.md b/solana-api-skill/SUBMISSION.md new file mode 100644 index 0000000..8dc2e00 --- /dev/null +++ b/solana-api-skill/SUBMISSION.md @@ -0,0 +1,133 @@ +# Submission: solana-api-skill — make any Solana/crypto API first-call-correct + +Superteam Solana · Solana AI Kit skills bounty. +By **[GeckoVision](https://geckovision.tech)** — the API-comprehension company. + +## Summary + +**solana-api-skill** teaches a coding/trading agent a generalizable procedure for +making **any** Solana/crypto API agent-usable — and getting the **first** call +right, not the third after two 400s. It is an **addon to the core +[`solana-dev-skill`](https://github.com/solana-foundation/solana-dev-skill)**: +where every other skill in the bounty wraps one protocol, this one teaches the +agent to be first-call-correct against *any* of them, organized as four jobs — +**Discovery** (pick the right endpoint among hundreds), **Access & Auth** (place +the credential the docs don't explain), **First-call-correct** (the exact request +shape — placement, units, idempotency, enums), and **Drift** (re-comprehend when +the API changes). It ships verified `❌ wrong → ✅ correct` playbooks for **Helius** +(RPC + DAS), **Jupiter** (quote → swap), and the **CLMM** protocols (Orca +Whirlpools / Raydium CLMM / Meteora DLMM), plus a power-path: point the +open-source [`gecko-surf`](https://github.com/GeckoVision/gecko-surf) engine at any +OpenAPI to auto-generate first-call-correct MCP tools. Progressive disclosure via +`skill/SKILL.md`; copy-only installer, no binaries, no network calls. + +## The gap it fills + +**Every other skill in the kit wraps ONE protocol.** None +makes an agent **first-call-correct against *any* Solana/crypto API.** This is the +**comprehension layer the others sit on top of** — a single-protocol skill assumes +the agent already knows how to call that protocol; this skill is the procedure for +the long tail of messy, paywalled, poorly-documented APIs it *doesn't*. + +The root problem: docs are written **for humans**, not agents. Prose, scattered +examples, the auth handshake assumed, units implied. A one-shot agent picks the +wrong method, forgets the key, sends a UI amount where atomic units are required, +or puts a field in the body the API wants in the query string. Each miss is a +failed call, a retry, a burned credit. This skill replaces guess-and-retry with a +repeatable four-job procedure. + +## How it works + +### The four jobs (the spine) + +1. **Discovery** — restate the intent as *inputs you have → output you want*; map + it to the API's resource + verb (not its marketing name); rank candidates by + "can I supply every required input, does the response contain my output field, + is it the narrowest current fit." A correctly-shaped call to the wrong endpoint + still fails. → `skill/discovery.md` +2. **Access & Auth** — find *where* the credential goes (URL query / header / + body) and *which* host+scope (free vs keyed, mainnet vs devnet, read vs write). + Inject at call time; never in the tool def, never in logs. → `skill/access-and-auth.md` +3. **First-call-correct** — resolve placement (path/query/body; JSON-RPC + positional-array vs named-object), units (atomic vs UI, bps vs percent — confirm + decimals), required-but-implicit fields (idempotency keys, a whole echoed + object, exact enums), and identity (pool vs position vs mint vs owner pubkey). + → `skill/first-call-correct.md` +4. **Drift** — diagnose a failure that *used to work* from its signature and + re-comprehend the current spec instead of hand-patching one field; pin a + version when offered. → `skill/drift.md` + +On any 4xx the skill's rule is to **diagnose which of the four jobs missed, not +brute-force retry** — a 404 on a known-good path is drift, a 401/403 is access, a +400 is shape, and a 200-with-wrong-magnitude is units (the silent one). + +### The power-path + +For an API with no hand-written file, comprehend it mechanically: +`pip install "gecko-surf[serve]"` then `gecko ` serves +first-call-correct, question-shaped MCP tools (auth hidden, placement/units +resolved, a built-in `search_capabilities` intent→endpoint tool). +→ `skill/gecko-surf-power-path.md` + +## Marquee examples + +Each playbook encodes a **verified** `❌ the call an agent gets wrong → ✅ the +correct first call` (checked against official docs / live calls on 2026-06-30): + +- **Helius** (`skill/apis/helius.md`) — Helius serves standard Solana JSON-RPC and + the Digital Asset Standard (DAS) on the **same endpoint**, but DAS methods take a + **named object** (`params: {ownerAddress, page, limit}`) while standard RPC takes + a **positional array**. Agents carry the `getBalance` array habit to + `getAssetsByOwner` and fail with "invalid params." Plus: the API key is a **URL + query param** (`?api-key=`) and the **network is the host** (mainnet vs devnet). +- **Jupiter** (`skill/apis/jupiter.md`) — `amount` is the input token's **atomic + base units** (1.5 SOL = `1500000000`, not `1.5` → hard `HTTP 400`), + `slippageBps` is **basis points** (50 = 0.5%), and `/swap/v1/swap` wants the + **entire `quoteResponse`** echoed back, not a field. Also flags the **dead + `quote-api.jup.ag/v6` host** as a drift trap. +- **CLMM** (`skill/apis/clmm.md`) — pass the **pool/position pubkey, not a token + mint** (Orca `fetchWhirlpool` wants the Whirlpool address; Raydium wants the CLMM + pool id; Meteora `DLMM.create` wants the LbPair address), and a position's range + is integer **ticks/bins, not a price** (`1.0001^tick × 10^(decA−decB)` for + Orca/Raydium; `(1 + binStep/10000)^binId` for Meteora). + +## Test plan (how a judge verifies it) + +- [ ] Read `skill/SKILL.md` — confirm the YAML frontmatter (`name: solana-api`, + `user-invocable: true`) and the progressive-disclosure routing tables. +- [ ] `bash install.sh` (or `CLAUDE_DIR=/tmp/judge bash install.sh`) — confirm it + **only copies markdown**, no binaries, no network calls. +- [ ] Ask the agent: *"make a first-call to Helius `getAssetsByOwner` for owner + `86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY`"* — observe it picks + `getAssetsByOwner`, puts the key in the **URL**, and uses a **named-object** + `params` (not a positional array). +- [ ] Ask: *"quote 1.5 SOL → USDC on Jupiter"* — observe it converts to **atomic + units** (`1500000000`), uses **`slippageBps`**, and targets + `lite-api.jup.ag/swap/v1/quote` (not the dead `v6` host). Optional live check: + `curl "https://lite-api.jup.ag/swap/v1/quote?inputMint=So11111111111111111111111111111111111111112&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&amount=1500000000&slippageBps=50"` + returns 200; the same with `amount=1.5` returns 400. +- [ ] Ask: *"read the Orca SOL/USDC pool"* — observe it passes a **pool address / + derives from mints + tickSpacing**, not a bare token mint, and treats + `tickLowerIndex` as an index to convert, not a price. +- [ ] Power-path: `pip install "gecko-surf[serve]"` then `gecko ` + — observe it prints a comprehension summary + an MCP URL + a one-click add. + +## Founder-market-fit + +GeckoVision is the **API-comprehension company** — this skill is our domain, not a +weekend wrapper: + +- We built the engine: **[`gecko-surf`](https://github.com/GeckoVision/gecko-surf)** + (MIT, on PyPI: https://pypi.org/project/gecko-surf/) — turns any OpenAPI 3.x into + first-call-correct agent tools served over MCP. This skill is its hand-tuned + Solana/crypto front end. +- We've run it **end-to-end against a real, paywalled, live API on mainnet** (ingest → + comprehend → access → first-call-correct → real data). First-call-correctness is what + we work on full-time. +- We comprehend painful, **non-OpenAPI** surfaces straight from their docs/source too — + **Surfpool's `surfnet_*` cheatcode RPC** and **Jito's Block Engine** — and verify a + **payments** charge is first-call-correct offline (right units, idempotency, auth) + before a cent moves. + +Links: https://geckovision.tech · https://github.com/GeckoVision/gecko-surf · +https://pypi.org/project/gecko-surf/ diff --git a/solana-api-skill/VERSION b/solana-api-skill/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/solana-api-skill/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/solana-api-skill/agents/api-comprehension-engineer.md b/solana-api-skill/agents/api-comprehension-engineer.md new file mode 100644 index 0000000..f94ab14 --- /dev/null +++ b/solana-api-skill/agents/api-comprehension-engineer.md @@ -0,0 +1,68 @@ +--- +name: api-comprehension-engineer +description: Solana/crypto API comprehension specialist. Takes an unfamiliar API (a docs URL, an OpenAPI link, or a pasted spec) plus a user intent, and returns the correct FIRST call — the right endpoint, the credential in the right place, the exact request shape (placement, units, idempotency, enums), and a drift-aware version pin. Runs the four-job procedure (discovery → access → first-call-correct → drift). Use when an agent must call a Solana/crypto API it hasn't memorized and you want the first call to land, not the third. Knows Helius, Jupiter, and the CLMM protocols by name, and falls back to the gecko-surf power-path for anything else. +--- + +# API Comprehension Engineer + +You are a **Solana/crypto API comprehension** specialist. Your job is to take an +API the agent does not already know and an intent, and produce the **correct first +call** — not a plausible-looking one that 400s, and not a unit-wrong one that +executes for the wrong amount. + +You do NOT teach one protocol. You run a repeatable procedure that works on any +API surface. When a hand-tuned playbook exists (Helius, Jupiter, CLMM), you use +it; otherwise you comprehend the spec mechanically with `gecko-surf`. + +## How you work — the four jobs + +1. **Discovery — pick the right call.** Restate the intent as *inputs you have → + output you want*. Map it to the API's resource + verb (not its marketing name). + Rank candidates by: can I supply every required input, does the response contain + my output field, is it the narrowest current (non-deprecated) fit. Confirm: + *"does this method take what I have and return what I want?"* +2. **Access & Auth — place the credential.** Find WHERE the key goes (URL query / + header / body) and WHICH host+scope (free vs keyed, mainnet vs devnet, read vs + write). Inject at call time — never put the key in the tool def or a log line; + redact before raising. +3. **First-call-correct — get the shape exact.** Check placement (path vs query vs + body; for JSON-RPC, positional array vs named object), units (atomic vs UI, bps + vs percent — confirm decimals), required-but-implicit fields (idempotency keys, + a whole echoed object, exact enums), and identity (pool vs position vs mint vs + owner pubkey). +4. **Drift — stay correct.** Pin a version when offered. If a call that used to + work now fails, diagnose it as drift from the failure signature and + re-comprehend the current spec — never hand-patch one field. + +## Output shape + +Give the user: + +- **The call** — method/endpoint + the exact request (path, query, body) as + ready-to-run curl or code. Auth shown as a placeholder (`$HELIUS_API_KEY`), + never a real key. +- **Why this call** — the one-line discovery justification (it takes X, returns Y). +- **The gotcha you avoided** — the specific first-call mistake for this API + (positional-vs-object, atomic-vs-UI, pool-vs-mint, old-vs-current host). +- **Drift note** — the version/host pinned, and what would signal it moved. + +## Hard rules + +- **Diagnose before retrying.** A 4xx is one of four fixable misses (wrong method, + key misplaced, wrong shape/units, API drifted). Name which one — do not + brute-force retry; it burns credits and never converges. +- **Units are load-bearing.** A wrong-unit call *succeeds* and is worse than a + 400. Always resolve atomic-vs-UI and bps-vs-percent, and confirm token decimals. +- **Never leak a credential.** Not in the tool def, not in an error, not in a log. +- **Be honest about verification.** If you couldn't confirm a spec detail, say so + and mark it to verify — don't invent a field or a host. +- **Reach for the power-path** when there's no hand-written playbook: point + `gecko-surf` at the OpenAPI and call the generated tools. + +## Routing + +The procedure lives in the skill: [SKILL.md](../skill/SKILL.md) · +[discovery.md](../skill/discovery.md) · [access-and-auth.md](../skill/access-and-auth.md) · +[first-call-correct.md](../skill/first-call-correct.md) · [drift.md](../skill/drift.md). +Marquee playbooks: [helius](../skill/apis/helius.md) · [jupiter](../skill/apis/jupiter.md) · +[clmm](../skill/apis/clmm.md). Power-path: [gecko-surf-power-path.md](../skill/gecko-surf-power-path.md). diff --git a/solana-api-skill/commands/comprehend-api.md b/solana-api-skill/commands/comprehend-api.md new file mode 100644 index 0000000..25c1a67 --- /dev/null +++ b/solana-api-skill/commands/comprehend-api.md @@ -0,0 +1,55 @@ +--- +description: Make an unfamiliar Solana/crypto API first-call-correct — pick the right endpoint, place the credential, get the exact request shape, and emit a ready-to-run call. Usage: /comprehend-api [intent] +--- + +# /comprehend-api + +Run the four-job comprehension procedure on an API the agent doesn't already know, +and emit the **correct first call** for the user's intent. + +**Arguments:** an API docs URL, an OpenAPI spec URL, or pasted docs — plus the +intent (what the user wants to do). If the intent is missing, ask for it. + +## Steps + +1. **Identify the surface.** Is it JSON-RPC (one URL, method in the body — e.g. + Helius) or REST (operation is the path — e.g. Jupiter, the CLMM data APIs)? + This tells you where the operation name lives. +2. **Discovery.** Restate the intent as *inputs you have → output you want*. Pick + the one method/endpoint that takes what the user has and returns what they want. + Prefer the narrowest current (non-deprecated) fit. (See `skill/discovery.md`.) +3. **Access & Auth.** Determine WHERE the credential goes (URL query / header / + body) and WHICH host+scope (free vs keyed, mainnet vs devnet). Show it as a + placeholder env var (`$API_KEY`) — never a real key, never in a log. + (See `skill/access-and-auth.md`.) +4. **First-call-correct.** Resolve the shape: placement (path/query/body; for + JSON-RPC, positional array vs named object), units (atomic vs UI, bps vs + percent — confirm token decimals), required-but-implicit fields (idempotency + keys, a whole echoed object, exact enums), and identity (pool vs position vs + mint vs owner pubkey). (See `skill/first-call-correct.md`.) +5. **Emit the call.** Output ready-to-run curl (or code), the one-line reason this + endpoint answers the intent, the specific gotcha avoided, and a drift note (the + version/host pinned). (See `skill/drift.md`.) + +## Marquee shortcuts + +If the API is one of these, load its playbook directly: +- Helius (RPC + DAS) → `skill/apis/helius.md` +- Jupiter (quote → swap) → `skill/apis/jupiter.md` +- CLMM (Orca / Raydium / Meteora) → `skill/apis/clmm.md` + +## No hand-written file? Use the power-path + +If there's no playbook and you have an OpenAPI URL, comprehend it mechanically: + +```bash +pip install "gecko-surf[serve]" +gecko # serves first-call-correct tools over MCP +``` + +See `skill/gecko-surf-power-path.md`. + +## Notes +- Never brute-force retry a 4xx. Diagnose which of the four jobs missed first. +- Units are load-bearing: a wrong-unit call *succeeds* for the wrong amount. +- If you can't verify a spec detail, say so — don't invent a field or host. diff --git a/solana-api-skill/install.sh b/solana-api-skill/install.sh new file mode 100755 index 0000000..a6f0cf6 --- /dev/null +++ b/solana-api-skill/install.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Install the solana-api comprehension skill into a Claude Code / Codex agent config. +# +# Safe + minimal: it only COPIES markdown files (the skill + the command + the +# agent + the rule) into your agent's config dirs. No binaries, no network calls, +# nothing opaque. The optional power-path (`gecko-surf`) is a separate, explicit +# `pip install` you run yourself — this installer never touches the network. +# +# Usage: +# bash install.sh # install to ~/.claude +# CLAUDE_DIR=/path/to/cfg bash install.sh # install to a custom config dir +set -euo pipefail + +SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEST="${CLAUDE_DIR:-$HOME/.claude}" +SKILL_NAME="solana-api" + +echo "==> Installing the ${SKILL_NAME} skill into ${DEST}" + +mkdir -p "${DEST}/skills/${SKILL_NAME}" "${DEST}/commands" "${DEST}/agents" "${DEST}/rules" + +# 1) The skill (SKILL.md + the four-job method files + the marquee API playbooks) +cp -R "${SRC_DIR}/skill/." "${DEST}/skills/${SKILL_NAME}/" +echo " • skill -> ${DEST}/skills/${SKILL_NAME}/ (SKILL.md + methods + apis/)" + +# 2) Command (/comprehend-api) +cp "${SRC_DIR}/commands/"*.md "${DEST}/commands/" +echo " • command -> ${DEST}/commands/comprehend-api.md" + +# 3) Agent persona (api-comprehension-engineer) +cp "${SRC_DIR}/agents/"*.md "${DEST}/agents/" +echo " • agent -> ${DEST}/agents/api-comprehension-engineer.md" + +# 4) First-call rule (opt-in) +cp "${SRC_DIR}/rules/"*.md "${DEST}/rules/" +echo " • rule -> ${DEST}/rules/first-call.md" + +cat <<'EOF' + +==> Done. + +Use it: + • Ask your agent: "make a first-call to Helius getAssetsByOwner for " + • Or run the command: /comprehend-api + +This skill is an ADDON to the core solana-dev-skill. If you don't have it: + https://github.com/solana-foundation/solana-dev-skill + +Optional power-path — comprehend ANY OpenAPI into first-call-correct MCP tools: + pip install "gecko-surf[serve]" + gecko + (https://pypi.org/project/gecko-surf/) +EOF diff --git a/solana-api-skill/rules/first-call.md b/solana-api-skill/rules/first-call.md new file mode 100644 index 0000000..b84f1f4 --- /dev/null +++ b/solana-api-skill/rules/first-call.md @@ -0,0 +1,37 @@ +--- +description: Before calling an unfamiliar Solana/crypto API, run discovery → access → shape; never brute-force retry a 4xx. +alwaysApply: false +--- + +# Rule: comprehend before the first call + +When an agent is about to call a **Solana or crypto API it has not already +cleared this session**, run the four-job procedure BEFORE sending the request — +don't send a plausible-looking call and retry on failure. + +1. **Discovery.** Confirm the chosen method/endpoint takes the inputs you have and + returns the output you want. A correctly-shaped call to the wrong endpoint + still fails. +2. **Access.** Put the credential where THIS API wants it (URL query / header / + body) and target the right host+scope (free vs keyed, mainnet vs devnet). Never + put the key in the tool def or a log line. +3. **Shape.** Resolve placement (path/query/body; JSON-RPC positional array vs + named object), units (atomic vs UI, bps vs percent — confirm decimals), + required-but-implicit fields (idempotency keys, a whole echoed object, exact + enums), and identity (pool vs position vs mint vs owner pubkey). + +On failure, **diagnose which job missed — do not brute-force retry.** A 4xx is one +of four fixable misses: + +- `404` on a known-good path → drift (the API moved) → re-comprehend the current spec. +- `401`/`403` → access (key misplaced / wrong host / wrong scope). +- `400` → shape (placement, units, a missing required field, or a bad enum). +- 200 but wrong magnitude → units (atomic vs UI, bps vs percent) — the silent one. + +Retrying an unchanged 4xx burns credits and never converges. Fix the identified +job, then re-send once. + +Applies to the first call against any unfamiliar Solana/crypto API. Skip for APIs +the agent has already comprehended and called correctly this session (cache the +working shape; re-run on a drift signal). For a spec-driven API with an OpenAPI +doc, prefer comprehending it with `gecko-surf` over hand-shaping the call. diff --git a/solana-api-skill/scripts/validate-skill.sh b/solana-api-skill/scripts/validate-skill.sh new file mode 100755 index 0000000..8235c0b --- /dev/null +++ b/solana-api-skill/scripts/validate-skill.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Structural validation for solana-api-skill: required files, SKILL.md frontmatter, +# internal link targets, and a report of any unverified () claims. +# Fails (exit 1) on missing files / bad frontmatter; only WARNS on links / VERIFY flags. +set -uo pipefail +cd "$(dirname "$0")/.." +FAIL=0 + +echo "Required files:" +for f in \ + skill/SKILL.md skill/discovery.md skill/access-and-auth.md skill/first-call-correct.md \ + skill/drift.md skill/gecko-surf-power-path.md \ + skill/apis/helius.md skill/apis/jupiter.md skill/apis/clmm.md \ + agents commands rules install.sh README.md SUBMISSION.md LICENSE; do + if [ -e "$f" ]; then printf ' \033[32m✓\033[0m %s\n' "$f"; else printf ' \033[31m✗ missing: %s\033[0m\n' "$f"; FAIL=1; fi +done + +echo "SKILL.md frontmatter:" +for k in "name:" "description:" "user-invocable:"; do + if head -12 skill/SKILL.md | grep -q "$k"; then printf ' \033[32m✓\033[0m %s\n' "$k"; else printf ' \033[31m✗ frontmatter missing %s\033[0m\n' "$k"; FAIL=1; fi +done + +echo "Internal link targets (.md referenced under skill/):" +miss=0 +for ref in $(grep -rEoh '[A-Za-z0-9_./-]+\.md' skill/ | sed 's#.*/##' | sort -u); do + find . -name "$ref" -not -path './.git/*' | grep -q . || { printf ' \033[33m⚠ link target not found: %s\033[0m\n' "$ref"; miss=1; } +done +[ "$miss" -eq 0 ] && printf ' \033[32m✓\033[0m all referenced .md targets exist\n' + +echo "Unverified claims to pin before publish ():" +if grep -rn 'VERIFY' skill/ >/dev/null 2>&1; then + grep -rn 'VERIFY' skill/ | sed 's#^# ⚠ #' +else + printf ' \033[32m✓\033[0m none\n' +fi + +echo "" +[ "$FAIL" -eq 0 ] && echo "validate: OK" || echo "validate: FAILED" +exit $FAIL diff --git a/solana-api-skill/skill/SKILL.md b/solana-api-skill/skill/SKILL.md new file mode 100644 index 0000000..12fad8e --- /dev/null +++ b/solana-api-skill/skill/SKILL.md @@ -0,0 +1,163 @@ +--- +name: solana-api +description: Make ANY Solana/crypto API agent-usable — first-call-correct. A generalizable procedure for the four jobs an agent fails on a new API: DISCOVERY (pick the right endpoint/method among hundreds), ACCESS & AUTH (the key/header/scope handshake the docs don't spell out), FIRST-CALL-CORRECT (get the request shape right the first time — path vs query vs body, units, idempotency keys, enums), and DRIFT (re-comprehend when the API changes instead of hand-patching). Ships marquee playbooks for Helius (RPC + DAS), Jupiter (quote→swap), and CLMM (Orca Whirlpools / Raydium CLMM / Meteora DLMM), plus a power-path: point `gecko-surf` at any OpenAPI to auto-generate first-call-correct MCP tools. This is the comprehension layer the other Solana skills sit on top of. Use before an agent calls an unfamiliar Solana/crypto API. NOT a single-protocol wrapper — it teaches the agent to call any API correctly. +user-invocable: true +--- + +# Solana API Comprehension Skill + +> **Extends**: `solana-dev-skill` — core Solana development (programs, frontend, +> testing, security). This skill is the **addon for the read/write APIs** your +> agent must call but the core skill doesn't cover: Helius, Jupiter, the CLMM +> protocols, and every long-tail Solana/crypto API behind human-shaped docs. +> +> Every other skill in the bounty teaches **one** protocol. This one teaches the +> agent to be **first-call-correct against any of them** — the layer the others +> sit on top of. + +## What This Skill Is For + +Use this skill the moment an agent has to call a **Solana or crypto API it has +not already memorized** — and you want the **first** call to be the right one, not +the third after two 400s. + +The problem is not that the docs are missing. It's that docs are written **for +humans**: prose, examples scattered across tabs, the auth handshake assumed, the +units implied. An agent reading them one-shot picks the wrong method, forgets the +key, sends a UI amount where atomic units are required, or PUTs a field the API +wants in the query string. Each miss is a failed call, a retry, a burned credit. + +This skill gives the agent a **repeatable procedure** for turning any such API +surface into a correct call. Four jobs: + +### The four jobs + +| # | Job | The question it answers | +|---|---|---| +| 1 | **Discovery** | Of the hundreds of methods/endpoints, *which one* answers this intent? | +| 2 | **Access & Auth** | What credential goes *where* (URL? header? body?), and what scope does it need? | +| 3 | **First-call-correct** | What is the *exact* request shape — path vs query vs body, value units, idempotency keys, enums? | +| 4 | **Drift** | The API changed. How do I *re-comprehend* it instead of hand-patching one call? | + +Get these four right and the first call lands. Get any one wrong and it doesn't. + +## How to use it (routing) + +Pick the job you're on; load only the file you need (progressive, token-efficient): + +| You want to… | Read | +|---|---| +| Pick the right call among many (intent → endpoint) | [discovery.md](discovery.md) | +| Inject the key/header/scope the docs don't explain | [access-and-auth.md](access-and-auth.md) | +| Get the request shape right the first time | [first-call-correct.md](first-call-correct.md) | +| Re-comprehend an API that changed (don't hand-patch) | [drift.md](drift.md) | +| Auto-generate first-call-correct tools from any OpenAPI | [gecko-surf-power-path.md](gecko-surf-power-path.md) | + +### Marquee playbooks (real APIs, real gotchas) + +Worked examples that apply the four jobs to APIs agents get wrong today. Each has +the concrete **❌ the call an agent gets wrong → ✅ the correct first call**: + +| API | The gotcha it teaches | Read | +|---|---|---| +| **Helius** (RPC + DAS) | DAS methods take a *named object*, not positional array; key lives in the URL | [apis/helius.md](apis/helius.md) | +| **Jupiter** (quote → swap) | `amount` is atomic base units; `slippageBps` is bps; POST the *whole* quote | [apis/jupiter.md](apis/jupiter.md) | +| **CLMM** (Orca / Raydium / Meteora) | Pass the *pool/position* pubkey, not a token mint; a tick is not a price | [apis/clmm.md](apis/clmm.md) | + +This skill also ships: +- **Command** — [`/comprehend-api`](../commands/comprehend-api.md): run the four-job + procedure on a URL, an OpenAPI link, or a pasted doc, and emit a first-call-correct call. +- **Agent** — [`api-comprehension-engineer`](../agents/api-comprehension-engineer.md): + a specialist that takes an unfamiliar API and an intent and returns the correct first call. +- **Rule** — [`first-call`](../rules/first-call.md): before calling an unfamiliar API, + run discovery → access → shape; never brute-force retry on a 4xx. + +## The spine: the four-job procedure + +This is the generalizable method. The marquee files just apply it. + +### 1. Discovery — pick the right call + +Don't grep the docs for a keyword and call the first hit. **Shape the intent into +the API's vocabulary first.** + +- State the intent as *inputs you have → output you want* ("I have an owner + wallet → I want its NFTs"). +- Map it to the API's **resource + verb**, not its marketing name. "Get NFTs" may + live under `getAssetsByOwner` (DAS), not a `/nfts` route. +- When several candidates match, prefer the one whose **required params you can + actually supply** and whose **response contains the field you need**. A method + you can't fill the inputs for is the wrong method. +- Read [discovery.md](discovery.md) for the ranking heuristics. + +### 2. Access & Auth — the handshake the spec omits + +The single most common reason a *correctly-shaped* call fails is auth placed +wrong. Specs rarely say *where* the credential goes. + +- Find **where** the credential lives: URL query param (Helius `?api-key=`), + header (`Authorization: Bearer`, `x-api-key`), or body. The location is + API-specific and frequently undocumented in the operation itself. +- Find the **scope/tier**: free vs keyed host, read vs write scope, mainnet vs + devnet base URL. Calling the wrong host with a valid key still fails. +- **Never log or echo the key.** Inject at call time; keep it out of the tool def + and out of error messages. +- Read [access-and-auth.md](access-and-auth.md). + +### 3. First-call-correct — the exact shape + +This is where one-shot agents bleed. The same value belongs in different places +and different units per API. + +- **Placement:** is each value a path segment, a query param, or a body field? + Mixing these is the classic 400. +- **Units:** atomic/base units vs UI decimals (1 SOL = 1,000,000,000 lamports; + USDC has 6 decimals), basis points vs percent (50 bps = 0.5%). Wrong units + *succeed* and lose money — worse than a 400. +- **Required-but-implicit:** idempotency keys, a full nested object the API wants + echoed back (Jupiter's whole `quoteResponse`), enum values that must match exactly. +- Read [first-call-correct.md](first-call-correct.md). + +### 4. Drift — re-comprehend, don't hand-patch + +APIs move: a version bumps, a host changes (Jupiter's `quote-api.jup.ag/v6` → +`lite-api.jup.ag/swap/v1`), a field is renamed. The wrong fix is to patch the one +call that broke. + +- Detect drift from the **failure signature** (new 404 on a path that worked, a + field suddenly `null`, a deprecation header). +- **Re-run discovery + shape** against the current spec/docs rather than editing a + frozen request by hand. The whole surface may have shifted, not one field. +- Pin a version when the API offers one; treat an unpinned base URL as a drift risk. +- Read [drift.md](drift.md). + +## The power-path (when there's no hand-written file) + +For an API not covered by a marquee file, don't hand-read the docs — **comprehend +it mechanically**. `gecko-surf` is the open-source engine (MIT, on PyPI) that turns +any OpenAPI 3.x spec into first-call-correct, question-shaped agent tools (auth +hidden, units and placement resolved) and serves them over MCP: + +```bash +pip install "gecko-surf[serve]" +``` + +Point it at the API's OpenAPI URL and it emits the tools the agent calls directly. +See [gecko-surf-power-path.md](gecko-surf-power-path.md). PyPI: +https://pypi.org/project/gecko-surf/ · source: https://github.com/GeckoVision/gecko-surf + +## The one thing to remember + +A failed first call is almost never "the API is broken." It's one of four +fixable misses: **wrong method** (discovery), **key in the wrong place** +(access), **wrong shape/units** (first-call-correct), or **the API moved** +(drift). Diagnose which one before you retry — brute-forcing a 4xx burns credits +and never converges. + +## Provider + +Built by **GeckoVision** (geckovision.tech) — the API-comprehension company. The +engine, [`gecko-surf`](https://github.com/GeckoVision/gecko-surf), makes any +OpenAPI agent-usable; this skill is the hand-tuned Solana/crypto front end for it. +GeckoVision also ships [`solana-token-safety-skill`](https://github.com/GeckoVision/solana-token-safety-skill) +over a live Gecko oracle. diff --git a/solana-api-skill/skill/access-and-auth.md b/solana-api-skill/skill/access-and-auth.md new file mode 100644 index 0000000..2942bf6 --- /dev/null +++ b/solana-api-skill/skill/access-and-auth.md @@ -0,0 +1,82 @@ +# Job 2 — Access & Auth: the handshake the spec omits + +A perfectly-shaped request still fails if the credential is in the wrong place or +lacks scope. This is the job specs document worst: the operation tells you the +params, almost never *where the key goes* or *which host/scope it needs*. + +## The mistake + +Putting the key where the *last* API wanted it. Helius wants it in the **URL +query** (`?api-key=`); Jupiter's keyed tier wants it in a **header** +(`x-api-key`); some APIs want `Authorization: Bearer`. Reusing the wrong location +gives a `401`/`403` on an otherwise-correct call, and the agent wrongly concludes +the call shape is broken. + +## The procedure + +### 1. Locate the credential — URL vs header vs body + +Three places a key can live. Find which, per API: + +| Location | Looks like | Example | +|---|---|---| +| **URL query param** | `?api-key=KEY` appended to the base URL | Helius RPC | +| **Header** | `x-api-key: KEY` or `Authorization: Bearer KEY` | Jupiter keyed tier; most REST APIs | +| **Body** | a field in the JSON payload | rare; some legacy APIs | + +If the docs don't say, check: a "getting started"/curl example almost always +shows the placement even when the per-endpoint reference doesn't. The placement +is a property of the **whole API**, not the one endpoint — find it once. + +### 2. Locate the scope/tier — host and permission + +A valid key against the wrong host or scope still fails: + +- **Free vs keyed host.** Jupiter splits these: `lite-api.jup.ag` (free, no key) + vs `api.jup.ag` (keyed). A key sent to the free host, or a free call to the + keyed host, can both fail. Pick the host that matches your key. +- **Network base URL.** `mainnet.helius-rpc.com` vs `devnet.helius-rpc.com` — a + mainnet key won't read devnet state and vice-versa. The network is in the host. +- **Read vs write scope.** Read keys can't submit; some tiers gate specific + method families. Match the key's scope to the operation. + +### 3. Inject at call time — never in the tool def, never in logs + +This is a security non-negotiable, and it's also why agents leak keys: + +- The **tool definition the agent reasons over must not contain the key.** The + agent describes intent; the credential is injected at the transport edge. (This + is exactly how `gecko-surf` builds tools — auth headers are hidden from the + agent.) +- **Never echo the key in an error message or log line.** Redact before raising. + A 401 should surface as "auth rejected for host X" — never paste the key. +- Read the key from the environment, not a literal. `.env`, not the source. + +### 4. Verify with the cheapest authenticated call + +Before the real call, confirm access with the smallest authenticated read the API +offers (a health route, `getHealth`, a tiny `limit=1` query). A cheap 200 proves +key + placement + host + scope all line up — then spend credits on the real call. + +## Decision checklist + +``` +1. WHERE does the key go? URL query · header · body (find once, per API) +2. WHICH host/scope? free vs keyed · mainnet vs devnet · read vs write +3. INJECTED safely? not in the tool def, not in logs, read from env +4. PROVEN? one cheap authed call returns 200 first +``` + +Miss #1 → `401`. Miss #2 → `403` or empty/wrong-network results. Miss #3 → a +leaked key. Miss #4 → you find out on the expensive call. + +## Worked examples + +- Helius: key is a **URL query param** (`?api-key=`), and the **network is the + host** (mainnet vs devnet) — [apis/helius.md](apis/helius.md). +- Jupiter: **free host needs no key** (`lite-api.jup.ag`); the keyed host + (`api.jup.ag`) takes the key in a header — [apis/jupiter.md](apis/jupiter.md). +- CLMM SDKs: "auth" is really **RPC access** — the gotcha is the RPC endpoint and + network, not an API key — [apis/clmm.md](apis/clmm.md). + +Next: [first-call-correct.md](first-call-correct.md) — key in place, now get the shape exactly right. diff --git a/solana-api-skill/skill/apis/clmm.md b/solana-api-skill/skill/apis/clmm.md new file mode 100644 index 0000000..6fc8948 --- /dev/null +++ b/solana-api-skill/skill/apis/clmm.md @@ -0,0 +1,119 @@ +# Marquee: CLMM — Orca Whirlpools · Raydium CLMM · Meteora DLMM + +> **The gotcha (two of them):** +> 1. **Identity** — you must pass the **pool / position pubkey**, not a token +> mint. All four are valid base58, so the wrong one doesn't look wrong — it just +> returns nothing or errors. A concentrated-liquidity protocol has a separate +> POOL account and POSITION accounts (the position is usually an NFT); a token +> mint is neither. +> 2. **Ticks/bins are not prices.** A position's range is integer `tickLowerIndex/ +> tickUpperIndex` (Orca/Raydium) or `binId + binStep` (Meteora). Reading a tick +> index as a price is wrong — price needs exponentiation *and* a decimal +> adjustment. + +Applies the four jobs ([SKILL.md](../SKILL.md)) to the three major Solana +concentrated-liquidity protocols. SDK/API entry points verified against official +repos and docs (2026-06-30). + +## Job 1 — Discovery (pool read vs position read are different calls) + +"My liquidity" is a **position** read; "the pool's price/liquidity" is a **pool** +read. They take different identifiers. Pick by what you have: + +| Protocol | SDK (npm) | Read a pool | Read a user's positions | +|---|---|---|---| +| **Orca Whirlpools** | `@orca-so/whirlpools` (needs web3.js **v2** / `@solana/kit`) | `fetchWhirlpool(rpc, poolAddress)` — or `fetchConcentratedLiquidityPool(rpc, mintA, mintB, tickSpacing)` to derive it | `fetchPositionsForOwner(rpc, walletAddress)` | +| **Raydium CLMM** | `@raydium-io/raydium-sdk-v2` | `raydium.clmm.getRpcClmmPoolInfos({ poolIds: [poolId] })` | `raydium.clmm.getOwnerPositionInfo({ programId: CLMM_PROGRAM_ID })` | +| **Meteora DLMM** | `@meteora-ag/dlmm` | `DLMM.create(connection, lbPairAddress)` | `dlmmPool.getPositionsByUserAndLbPair(walletPubkey)` | + +Note Orca's two pool-readers take **different identifier types**: `fetchWhirlpool` +wants the **pool address**; `fetchConcentratedLiquidityPool` wants **two mints + +tickSpacing** and derives the pool for you. Pick the one matching what you have. + +## Job 2 — Access & Auth (it's RPC, not an API key) + +These are SDKs over a Solana **RPC connection** — "auth" here means a working RPC +endpoint and the right network, not an API key. The common miss is pointing the +SDK at the wrong network (devnet RPC for a mainnet pool → empty). Use a real +mainnet RPC (e.g. your Helius mainnet URL — see [helius.md](helius.md)). + +For **read-only pool data without an SDK or wallet**, each protocol has a REST API: + +| Protocol | REST base | Example | +|---|---|---| +| Orca | `https://api.orca.so/v2` | `GET /v2/solana/pools` , `/v2/solana/pools/search` | +| Raydium | `https://api-v3.raydium.io` | `GET /pools/info/ids` (envelope `{id,success,data}`); Swagger at `/docs/` | +| Meteora | `https://dlmm-api.meteora.ag` | `GET /pair/all` (array of LB pairs), `/pair/{address}` | + +## Job 3 — First-call-correct + +### ❌ The call an agent gets wrong (identity) + +Passing a **token mint** where the **pool address** is required: + +```ts +// ❌ WRONG: SOL mint is not a Whirlpool pool account +const pool = await fetchWhirlpool( + rpc, + address("So11111111111111111111111111111111111111112"), // ← a token MINT, not a pool +); +``` + +The equivalent miss elsewhere: a token mint instead of the **Raydium CLMM pool +id** in `getRpcClmmPoolInfos`, or instead of the **Meteora LbPair address** in +`DLMM.create`. + +### ✅ The correct first call + +Either pass the actual pool address, or derive the pool from the token pair: + +```ts +// ✅ (A) you have the pool address +const pool = await fetchWhirlpool(rpc, address("")); + +// ✅ (B) you only have the token pair — derive the pool from mints + tickSpacing +const pool = await fetchConcentratedLiquidityPool( + rpc, + address("So11111111111111111111111111111111111111112"), // tokenMintOne (SOL) + address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), // tokenMintTwo (USDC) + 64, // tickSpacing +); +``` + +For positions: you don't pass a position address you guessed — positions are +**NFTs the wallet holds**, so the SDK enumerates them from the **owner wallet** +(Orca `fetchPositionsForOwner(rpc, wallet)`; Raydium scans the owner's NFTs given +the `CLMM_PROGRAM_ID`; Meteora `getPositionsByUserAndLbPair(wallet)`). + +### The second gotcha: a tick is not a price + +A position's range comes back as integer indices, not prices: + +- **Orca / Raydium (ticks):** `price = 1.0001^tick × 10^(decimalsA − decimalsB)`. + Each tick = 1 basis point of price change; the **token decimals are a required + input** to get a real price. Pools store `sqrtPrice` (Q64.64 fixed-point) + + `tickCurrentIndex`, not a float — convert `sqrtPrice → price → tick` last + (it's lossy). Orca helpers: `tickIndexToPrice`, `priceToTickIndex`, + `sqrtPriceToPrice` (in `@orca-so/whirlpools-core`). Raydium uses the same + Uniswap-v3-style base-1.0001 tick math. +- **Meteora (bins):** `price = (1 + binStep/10000)^binId`, then decimal-adjusted. + binStep is in basis points. Helpers: `getPriceOfBinByBinId`, `getBinIdFromPrice`, + and `toPricePerLamport` / `fromPricePerLamport` (the raw SDK price is per-lamport + — convert to a human price with the decimals). + +So `tickLowerIndex: -18000` is **not** a price of -18000 (or 18000); it's an index +you exponentiate and decimal-adjust. Treating the index as the price is the +canonical CLMM range bug. + +## Job 4 — Drift + +- Orca's `@orca-so/whirlpools` requires **web3.js v2 / `@solana/kit`** and is + incompatible with web3.js v1; the legacy v1 path is `@orca-so/whirlpools-sdk`. + A version mismatch here is a "drift" failure at install/import, not at call time. +- Orca docs moved `dev.orca.so` → `docs.orca.so` (TypeDoc still under + `dev.orca.so/ts/...`). If a doc link 404s, that's the move — re-comprehend. +- These SDKs version their account layouts; a decode error after an upgrade is + drift — bump the SDK and re-read the current types, don't hand-massage bytes. + +Back: [discovery.md](../discovery.md) · [access-and-auth.md](../access-and-auth.md) · +[first-call-correct.md](../first-call-correct.md) · [drift.md](../drift.md) · [SKILL.md](../SKILL.md) diff --git a/solana-api-skill/skill/apis/helius.md b/solana-api-skill/skill/apis/helius.md new file mode 100644 index 0000000..b0f10d4 --- /dev/null +++ b/solana-api-skill/skill/apis/helius.md @@ -0,0 +1,134 @@ +# Marquee: Helius (RPC + Digital Asset Standard) + +> **The gotcha:** Helius serves standard Solana JSON-RPC *and* the Digital Asset +> Standard (DAS) API on the **same endpoint** — but they take params differently. +> Standard RPC wants a **positional array**; DAS wants a **named object**. Agents +> reflexively use the array convention they learned for `getBalance`, and every +> DAS call fails with "invalid params." That, plus the API key living in the +> **URL**, is the whole first-call lesson. + +Applies the four jobs ([SKILL.md](../SKILL.md)) to Helius. All specifics verified +against https://www.helius.dev/docs (2026-06-30). + +## Job 2 first — Access & Auth (it's in the URL) + +Helius is JSON-RPC over one host. The **API key is a URL query param**, and the +**network is the host**: + +``` +mainnet: https://mainnet.helius-rpc.com/?api-key=$HELIUS_API_KEY +devnet: https://devnet.helius-rpc.com/?api-key=$HELIUS_API_KEY +``` + +- Forget `?api-key=` → `401`. A mainnet key against `devnet.helius-rpc.com` reads + devnet state (empty/wrong results), not mainnet — the **network is in the host**, + not a param. +- Never log the full URL with the key in it; redact the query string before raising. + +## Job 1 — Discovery (which method, and which family) + +Two method families share the one endpoint: + +| Family | Examples | Answers | +|---|---|---| +| **Standard Solana RPC** | `getBalance`, `getAccountInfo`, `getTokenAccountsByOwner`, `getProgramAccounts` | native/account/program reads | +| **DAS** (Helius extension) | `getAsset`, `getAssetsByOwner`, `getAssetsByCreator`, `searchAssets`, `getAssetProof` | NFTs / compressed NFTs / fungible *assets* | + +Intent "the NFTs a wallet holds" → **`getAssetsByOwner`** (DAS). Not a `/nfts` +route (there isn't one — it's JSON-RPC), and not a `getAsset` loop (you don't have +the asset ids yet; you have the owner). The method that takes what you have (the +owner) and returns what you want (its assets) is `getAssetsByOwner`. + +## Job 3 — First-call-correct (named object, not positional array) + +This is the trap. **DAS params are a named object.** The same agent that correctly +calls `getBalance` with a positional array carries that habit to DAS and fails. + +### ❌ The call an agent gets wrong + +Positional-array params (the standard-RPC habit) — DAS rejects this with an +invalid-params error: + +```json +{ + "jsonrpc": "2.0", + "id": "1", + "method": "getAssetsByOwner", + "params": ["86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY", 1, 1000] +} +``` + +### ✅ The correct first call + +Named-object params: + +```bash +curl -s "https://mainnet.helius-rpc.com/?api-key=$HELIUS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "1", + "method": "getAssetsByOwner", + "params": { + "ownerAddress": "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY", + "page": 1, + "limit": 1000 + } + }' +``` + +Response (note the pagination envelope): + +```jsonc +{ + "jsonrpc": "2.0", + "id": "1", + "result": { + "total": 1, + "limit": 1000, + "page": 1, + "items": [ /* asset objects */ ], + "last_indexed_slot": 365750752 + } +} +``` + +### Contrast: a standard-RPC method on the *same* endpoint + +```jsonc +// getBalance — POSITIONAL ARRAY (standard Solana RPC convention) +{ "jsonrpc": "2.0", "id": "1", "method": "getBalance", + "params": ["86xCnPeV...", { "commitment": "confirmed" }] } +``` + +Same URL, **different param encoding**. The family decides the encoding: standard +RPC → array; DAS → object. Get this wrong and the call fails before anything else. + +### Pagination (don't stop at page 1) + +- `limit` max is **1000** records per call (Helius docs confirm the max; the default + when omitted is **undocumented**) — so **pass `1000` explicitly** rather than relying + on an unstated default. +- Page-based: increment `page` until `items.length < limit` (or `total` is reached). +- For very large owners (hundreds of thousands of assets), switch to cursor + pagination with `before` / `after` instead of `page`. + +## Job 4 — Drift + +- `staked.helius-rpc.com` is **deprecated** (staked routing is now default on paid + plans). If you pinned that host, re-comprehend. +- For transaction history, prefer **standard Solana RPC**: `getSignaturesForAddress` + (list signatures for the address) → `getTransaction` (fetch each). Helius's parsed + "Enhanced Transactions" history is a moving target — if a parsed-history endpoint + `404`s or a field vanishes, that's drift; fall back to the standard RPC pair above, + don't hand-patch the parsed call. + +## Cost note (so the agent paginates sanely) + +Helius bills in **credits**: most standard RPC methods = **1 credit**; **DAS +methods = 10 credits each**; Enhanced Transactions = 100. Free tier: ~1M +credits/month, 10 RPC req/s, but only **~2 req/s for DAS + Enhanced combined** — +so loop pages deliberately, at `limit=1000`, not in a tight burst. + +Back: [discovery.md](../discovery.md) · [access-and-auth.md](../access-and-auth.md) · +[first-call-correct.md](../first-call-correct.md) · [SKILL.md](../SKILL.md) diff --git a/solana-api-skill/skill/apis/jupiter.md b/solana-api-skill/skill/apis/jupiter.md new file mode 100644 index 0000000..18ccc2f --- /dev/null +++ b/solana-api-skill/skill/apis/jupiter.md @@ -0,0 +1,131 @@ +# Marquee: Jupiter (quote → swap) + +> **The gotcha:** Jupiter's `amount` is in the input token's **atomic base units** +> (lamports for SOL), not a decimal UI amount. Send `1.5` and you get a hard +> `HTTP 400 ParseIntError`; send a *valid* decimal that happens to parse and you'd +> swap the wrong magnitude. `slippageBps` is **basis points** (50 = 0.5%), and +> `/swap` wants the **entire** `quoteResponse` object echoed back — not a field +> from it. + +Applies the four jobs ([SKILL.md](../SKILL.md)) to Jupiter's Swap API. Endpoints +and behaviors verified with live calls against `lite-api.jup.ag` / `api.jup.ag` +(2026-06-30). + +## Job 4 first — Drift warning (the old host is dead) + +Jupiter has moved hosts/versions; older tutorials are a trap: + +| Path | Status | +|---|---| +| `quote-api.jup.ag/v6/quote` + `/v6/swap` | **DEAD** — host no longer resolves. Do not use. | +| `lite-api.jup.ag/swap/v1/quote` + `/swap/v1/swap` | **LIVE** — the classic flow below | +| `api.jup.ag/swap/v2/order` + `/swap/v2/execute` | **LIVE** — newer recommended flow | + +If you find `quote-api.jup.ag/v6` in any example, that's drift — re-comprehend +against the current docs (https://dev.jup.ag, now `developers.jup.ag`). This file +documents the live **`/swap/v1`** path as the primary first-call-correct flow. + +## Job 2 — Access & Auth (free host needs no key) + +``` +free / keyless: https://lite-api.jup.ag (no key; lowest rate limit) +keyed: https://api.jup.ag (x-api-key: $JUP_API_KEY) +``` + +- The free host takes **no key** — sending one to the wrong host or omitting it on + the keyed host are both first-call failures. Pick the host that matches. +- Keyed requests put the key in the **`x-api-key` header** (key format `jup_...`), + against `api.jup.ag`. Keyless is rate-limited (~0.5 RPS); paid tiers raise it. +- Never log the key. + +## Job 1 — Discovery (quote, then swap) + +Intent "swap 1.5 SOL for USDC" is a **two-call flow**, not one endpoint: + +1. `GET /swap/v1/quote` — returns a routable **quoteResponse** (price, route, the + numbers you'll echo back). This is "the price to swap" — not a spot `/price`. +2. `POST /swap/v1/swap` — turns that quote + your wallet into a signable + **transaction**. + +You need both, in order. Don't look for a single "swap" endpoint that takes a UI +amount and a wallet — that's not the shape. + +## Job 3 — First-call-correct (atomic units, bps, whole quote) + +### ❌ The call an agent gets wrong + +Decimal UI amount and percent slippage: + +```bash +# ❌ amount=1.5 (decimal) → HTTP 400: "amount cannot be parsed: ParseIntError" +curl "https://lite-api.jup.ag/swap/v1/quote\ +?inputMint=So11111111111111111111111111111111111111112\ +&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\ +&amount=1.5\ +&slippageBps=0.5" +``` + +Two errors: `amount=1.5` (must be atomic integer), `slippageBps=0.5` (bps is an +integer; 0.5% is `50`). + +### ✅ The correct first call + +Convert the UI amount to atomic units first (SOL has 9 decimals → +`1.5 * 10^9 = 1_500_000_000`), slippage as bps (`0.5% = 50`): + +```bash +# ✅ atomic amount + bps slippage +curl "https://lite-api.jup.ag/swap/v1/quote\ +?inputMint=So11111111111111111111111111111111111111112\ +&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\ +&amount=1500000000\ +&slippageBps=50" +# → { "inAmount":"1500000000", "outAmount":"...", "slippageBps":50, "routePlan":[...], ... } +``` + +**Getting decimals right is the whole game.** SOL = 9, USDC/USDT = 6, others vary. +Don't assume — read the mint's `decimals` (Jupiter Tokens API +`GET https://api.jup.ag/tokens/v2/search?query=` returns `decimals`, or read +the SPL mint account). Then `atomic = ui * 10**decimals`. + +### Then POST the *whole* quote to /swap + +`/swap` wants the **entire unmodified `quoteResponse`** object from `/quote`, plus +`userPublicKey` — not a subset, not just `outAmount`: + +```bash +# ✅ body wraps the ENTIRE /quote response +curl -X POST "https://lite-api.jup.ag/swap/v1/swap" \ + -H "Content-Type: application/json" \ + -d '{ + "quoteResponse": , + "userPublicKey": "" + }' +# → { "swapTransaction": "", "lastValidBlockHeight": ..., ... } +``` + +The response field is **`swapTransaction`**: a base64-encoded **v0 +VersionedTransaction**. Deserialize it, sign with the wallet, and send via a +Solana RPC (`sendRawTransaction`). + +## The full flow (correct order) + +1. **Resolve decimals** for the input mint, convert UI → atomic (`ui * 10**decimals`). +2. **GET `/swap/v1/quote`** with `inputMint`, `outputMint`, `amount` (atomic), + `slippageBps` (bps) → get the full `quoteResponse`. +3. **POST `/swap/v1/swap`** with `{quoteResponse: , userPublicKey}` + → get `swapTransaction`. +4. **Deserialize + sign** the v0 VersionedTransaction with the wallet keypair. +5. **Send** the signed tx to a Solana RPC; confirm against `lastValidBlockHeight`. + +## Newer V2 path (Jupiter-managed landing) + +Jupiter also offers `GET /swap/v2/order` + `POST /swap/v2/execute` — **verified live and +keyless on `lite-api.jup.ag`** (2026-06-30). Pass `/order` a **`taker`** (the wallet +pubkey) and it returns a **`requestId`** plus a base64 **`transaction`** to sign; +`POST /swap/v2/execute` takes the **signed transaction + `requestId`** and lands it for +you. Same atomic-units + bps rules as V1. Prefer V1 above for the simplest first call; +reach for V2 when you want Jupiter to manage transaction landing. + +Back: [discovery.md](../discovery.md) · [access-and-auth.md](../access-and-auth.md) · +[first-call-correct.md](../first-call-correct.md) · [drift.md](../drift.md) · [SKILL.md](../SKILL.md) diff --git a/solana-api-skill/skill/discovery.md b/solana-api-skill/skill/discovery.md new file mode 100644 index 0000000..747419a --- /dev/null +++ b/solana-api-skill/skill/discovery.md @@ -0,0 +1,95 @@ +# Job 1 — Discovery: pick the right call among many + +A real API has hundreds of methods. The agent's first failure is usually not a +bad request — it's a request to the **wrong endpoint**. Discovery is the job of +turning an intent into the *one* call that answers it. + +## The mistake + +Grepping the docs for a keyword and calling the first hit. "I need NFTs" → search +"nft" → call a `/nfts` route that returns metadata, not ownership. Or "get my +balance" → `getBalance` (native SOL only) when the intent was an SPL token balance +(`getTokenAccountsByOwner`). The keyword matched; the **semantics** didn't. + +## The procedure + +### 1. State the intent as inputs → output + +Write it down concretely before you look at the docs: + +``` +I HAVE: an owner wallet (base58 pubkey) +I WANT: the list of NFTs it holds, with metadata +``` + +This forces the two things discovery ranks on: the **inputs you can supply** and +the **output field you need**. + +### 2. Map to resource + verb, not the marketing name + +APIs name endpoints by *resource and operation*, not by the user's words. + +| User says | Likely lives under | +|---|---| +| "my NFTs" | `getAssetsByOwner` (Helius DAS), not `/nfts` | +| "the price to swap" | `/quote` (Jupiter), not `/price` | +| "my LP position" | a *position* account read (CLMM), not the pool | +| "token balance" | `getTokenAccountsByOwner`, not `getBalance` | + +When in doubt, list the candidate methods whose **name or response schema mentions +your output field**, then filter. + +### 3. Rank the candidates + +Among methods that could match, prefer the one where: + +1. **You can supply every required input.** A method needing a `poolId` you don't + have is the wrong method even if its name fits — find the one keyed on what you + *have* (the owner, the mint). +2. **The response actually contains your output field.** Read the response schema, + not just the summary. "Get asset" that returns metadata but not `ownership` + doesn't answer "who owns it." +3. **It's the narrowest fit.** A specific method (`getAssetsByOwner`) beats a + general one (`searchAssets` with a filter) when both work — fewer params to get + wrong, fewer ways to mis-shape the first call. +4. **It's the current one.** Prefer the non-deprecated, version-pinned variant + (see [drift.md](drift.md)). + +### 4. Confirm before calling + +One-line sanity check: *"Does this method take what I have and return what I +want?"* If either half is no, you picked wrong — back up to step 2. This single +check prevents most first-call failures, because a correctly-shaped call to the +wrong endpoint still fails. + +## RPC vs REST: two discovery surfaces + +Solana APIs come in two flavors and discovery differs: + +- **JSON-RPC** (Helius, any Solana node): one URL, the *method name* is in the + body (`"method": "getAssetsByOwner"`). Discovery = picking the method string. + Beware: standard RPC methods and vendor extensions (DAS) share one endpoint but + have **different param conventions** — see [first-call-correct.md](first-call-correct.md). +- **REST** (Jupiter, the CLMM data APIs): the operation is the *path* + (`/swap/v1/quote`). Discovery = picking the path + HTTP verb. + +Knowing which surface you're on tells you *where the operation name lives* — the +first thing to get right. + +## When there are genuinely too many + +If the surface is large and undocumented, don't brute-force it. **Comprehend it +mechanically**: feed the OpenAPI spec to `gecko-surf`, which builds a lexical +intent→endpoint catalog and returns question-shaped tools — discovery becomes a +search over already-correct tool defs. See [gecko-surf-power-path.md](gecko-surf-power-path.md). + +## Worked examples + +- Helius: "NFTs of a wallet" → `getAssetsByOwner`, not a positional `getAsset` + loop — [apis/helius.md](apis/helius.md). +- Jupiter: "swap price" → `/quote` (returns a routable quote), not a spot + `/price` — [apis/jupiter.md](apis/jupiter.md). +- CLMM: "my liquidity" → a *position* read keyed on the position pubkey, not the + pool — [apis/clmm.md](apis/clmm.md). + +Next: [access-and-auth.md](access-and-auth.md) — once you've picked the call, get the key in the right place. diff --git a/solana-api-skill/skill/drift.md b/solana-api-skill/skill/drift.md new file mode 100644 index 0000000..0cad028 --- /dev/null +++ b/solana-api-skill/skill/drift.md @@ -0,0 +1,76 @@ +# Job 4 — Drift: re-comprehend when the API changes + +The first three jobs make *today's* call correct. Drift is what keeps it correct. +APIs move — a version bumps, a host migrates, a field is renamed or starts +returning `null`. The wrong response is to hand-patch the one call that broke; the +right one is to **re-comprehend the surface**. + +## The mistake + +An agent (or a developer) freezes a working request as a hardcoded template. Six +weeks later the provider ships v2, the old path 404s, and someone edits the single +broken field by hand — missing that *three other fields moved too*, or that the +whole base URL changed. Hand-patching treats a surface change as a typo. + +Real example this skill encodes: Jupiter's swap API moved from +`quote-api.jup.ag/v6/quote` to `lite-api.jup.ag/swap/v1/quote`. Patching `/v6`→ +`/swap/v1` on the old host still fails — the **host** changed too. The fix is to +re-read the current spec, not to diff one path. See [apis/jupiter.md](apis/jupiter.md). + +## Detect drift from the failure signature + +Different drifts fail differently. Read the signature before you "fix": + +| Signature | Likely drift | Right move | +|---|---|---| +| `404` on a path that worked | path/version/host changed | re-discover the current endpoint | +| Field suddenly `null`/missing | response schema renamed/removed | re-read the response schema | +| `400` on an unchanged body | request schema tightened / new required field | re-read the request schema | +| `Deprecation` / `Sunset` header | scheduled removal | migrate now, before it 404s | +| `401`/`403` after it worked | auth scheme or host/scope changed | re-run Job 2 (access) | + +The tell: a call that **used to work** now fails. That's drift, not a bad first +call — diagnose it as drift. + +## The procedure + +### 1. Confirm it's drift, not your bug + +Did the request change? If not, and it used to work, the *API* changed. Check the +provider's changelog / deprecation headers / status page before touching the call. + +### 2. Re-comprehend, don't hand-edit + +Re-run **Job 1 (discovery) + Job 3 (shape)** against the *current* docs/spec: + +- Pull the current OpenAPI or docs for the operation. +- Re-pick the method (the operation name or path may have moved). +- Re-derive the shape (placement/units/required may have changed together). + +Editing one field assumes everything else held. After a version bump that's the +unsafe assumption. Re-derivation is cheap; a silently-wrong patched call is not. + +### 3. Pin what you can, treat the rest as a drift risk + +- **Pin a version** when the API offers one (`/swap/v1`, an `apiVersion` header). + A pinned version is a stable contract until you choose to move. +- Treat an **unpinned/rolling base URL as a standing drift risk** — expect it to + move and keep the re-comprehension path ready. +- Record the spec version you comprehended against, so the next drift is a diff + against a known baseline, not a mystery. + +### 4. Mechanize re-comprehension + +The durable answer to drift is to not hand-maintain calls at all. Point +`gecko-surf` at the API's OpenAPI URL again and it regenerates first-call-correct +tools from the *current* spec — the version bump becomes a re-ingest, not a +patch. This is the difference between maintaining N hand-written calls and +re-running one comprehension step. See [gecko-surf-power-path.md](gecko-surf-power-path.md). + +## The principle + +A hand-patched call is a snapshot that rots. A **re-comprehension step** is a +process that survives change. When an API drifts, run the process — don't edit the +snapshot. + +Back to the spine: [SKILL.md](SKILL.md) · or the power-path: [gecko-surf-power-path.md](gecko-surf-power-path.md). diff --git a/solana-api-skill/skill/first-call-correct.md b/solana-api-skill/skill/first-call-correct.md new file mode 100644 index 0000000..1e6a55f --- /dev/null +++ b/solana-api-skill/skill/first-call-correct.md @@ -0,0 +1,93 @@ +# Job 3 — First-call-correct: the exact request shape + +You picked the right method (Job 1) and the key is in the right place (Job 2). The +call can still fail — or worse, *succeed wrongly* — if the shape is off. This is +where one-shot agents bleed the most calls, because the docs show a happy-path +example and leave the rules implicit. + +## The four shape errors, in order of how often they bite + +### 1. Placement — path vs query vs body + +The same value belongs in different parts of the request per API. Putting it in +the wrong part is the classic `400`. + +- **Path segment:** identifies a resource — `/asset/{id}`. Goes in the URL path. +- **Query param:** filters/options on a GET — `?limit=1000&page=1`. +- **Body field:** the payload of a POST/PUT — `{"ownerAddress": "..."}` or a + JSON-RPC `params`. + +Rule: **GET → inputs go in path + query; POST → inputs go in the body.** A field +the docs show in a JSON example is a body field; a field shown after `?` is a +query param. Don't move them. + +JSON-RPC adds a twist: everything rides in the body, but the **param encoding** +differs by method family. Standard Solana RPC uses a **positional array** +(`params: ["", {...}]`); vendor DAS methods use a **named object** +(`params: {"ownerAddress": "..."}`). Same endpoint, different encoding — mixing +them is the single most common Helius first-call failure. See +[apis/helius.md](apis/helius.md). + +### 2. Units — atomic vs UI, bps vs percent + +The error that **succeeds** and so is the most dangerous: wrong units don't 400, +they execute with the wrong magnitude. + +- **Atomic / base units vs UI decimals.** On-chain amounts are integers in the + token's smallest unit. 1 SOL = `1_000_000_000` lamports (9 decimals); 1 USDC = + `1_000_000` (6 decimals). An API field named `amount` almost always wants the + **atomic integer**, not `1.5`. Convert with the token's decimals: + `atomic = ui * 10**decimals`. +- **Basis points vs percent.** `slippageBps: 50` means 0.5%, not 50%. A field + suffixed `Bps` is basis points (1 bps = 0.01%). +- **Always confirm the token's decimals before converting** — they vary (SOL 9, + USDC/USDT 6, many SPL tokens 6 or 9). Read the mint, don't assume. + +This is the Jupiter trap: `amount` is atomic input-token units and `slippageBps` +is bps. See [apis/jupiter.md](apis/jupiter.md). + +### 3. Required-but-implicit fields + +Fields the API requires that the example glosses over: + +- **Idempotency keys / client refs** — payment and order APIs often require a + unique key per request so a retry doesn't double-execute. Omit it and the first + call may 400, or a retry may duplicate. +- **A whole object echoed back.** Some flows want you to POST back the *entire* + response of a previous call, not a field from it. Jupiter's `/swap` wants the + full `quoteResponse` object from `/quote`, plus `userPublicKey`. Sending a + subset fails. See [apis/jupiter.md](apis/jupiter.md). +- **Exact enums.** String enums must match exactly — `"confirmed"` not + `"Confirmed"`, the documented `sortDirection` values, etc. A near-miss is a 400. + +### 4. Identity — which account/id is this field? + +On Solana especially, "an address" is ambiguous. A field wanting a **pool** +pubkey, a **position** pubkey, a **token mint**, or an **owner wallet** will +reject the wrong kind even though all four are valid base58. The CLMM trap is +passing a token mint where a pool/position pubkey is required. See +[apis/clmm.md](apis/clmm.md). + +## The pre-flight checklist + +Before sending the first call, verify each: + +``` +[ ] PLACEMENT every value in path / query / body where the API expects it + (JSON-RPC: positional array vs named object — match the method family) +[ ] UNITS atomic vs UI resolved; bps vs percent resolved; decimals confirmed +[ ] REQUIRED idempotency key / full echoed object / exact enums present +[ ] IDENTITY each pubkey is the RIGHT KIND (pool vs position vs mint vs owner) +``` + +Four checks. They catch the overwhelming majority of first-call failures — and +the units check catches the *silent* ones a 4xx never would. + +## When the shape is genuinely complex + +For a large or deeply-nested body, don't assemble it by hand from prose. Let +`gecko-surf` synthesize a schema-correct example from the OpenAPI spec — it +resolves placement and required fields deterministically and hides the auth. +See [gecko-surf-power-path.md](gecko-surf-power-path.md). + +Next: [drift.md](drift.md) — the call worked once; here's how to survive the API changing. diff --git a/solana-api-skill/skill/gecko-surf-power-path.md b/solana-api-skill/skill/gecko-surf-power-path.md new file mode 100644 index 0000000..4efc5b0 --- /dev/null +++ b/solana-api-skill/skill/gecko-surf-power-path.md @@ -0,0 +1,98 @@ +# Power-path: comprehend any OpenAPI with `gecko-surf` + +The marquee files ([helius](apis/helius.md), [jupiter](apis/jupiter.md), +[clmm](apis/clmm.md)) are hand-tuned for APIs agents hit often. But you will hit +an API with **no hand-written file** — a long-tail, paywalled, poorly-documented +Solana/crypto API the coding agent does not one-shot. Don't hand-read the docs. +**Comprehend the surface mechanically.** + +`gecko-surf` is the open-source engine (MIT, on PyPI) behind this skill. It runs +the same four jobs — discovery, access, first-call-correct, drift — but driven off +a machine-readable spec instead of prose. Point it at an API's **OpenAPI 3.x** +document and it emits question-shaped, first-call-correct agent tools (auth +hidden, placement and units resolved) and serves them over MCP. + +## When to reach for it + +- The API isn't covered by a marquee file here. +- The surface is large (dozens–hundreds of operations) — discovery by hand is slow. +- The API drifts often and you'd rather **re-ingest** than hand-patch (see + [drift.md](drift.md)). +- You want the agent calling the **real API directly**, with auth injected at the + edge and never in the tool def. + +## Install + +```bash +pip install gecko-surf +# for the MCP server transport (serve any API to your agent): +pip install "gecko-surf[serve]" +``` + +PyPI: https://pypi.org/project/gecko-surf/ · source: +https://github.com/GeckoVision/gecko-surf + +## Comprehend + serve an API over MCP + +One command: comprehend an OpenAPI URL and serve it to your agent over +Streamable-HTTP MCP, with a one-click `claude mcp add` printed at the end. + +```bash +# installed: +gecko https://api.example.com/openapi.json + +# or zero-install via uvx: +uvx --from "gecko-surf[serve] @ git+https://github.com/GeckoVision/gecko-surf" \ + gecko https://api.example.com/openapi.json +``` + +It prints the comprehension summary (how many operations, the generated tools), +the MCP URL, and the one-click add command. Your agent then sees first-call-correct +tools — including a synthetic `search_capabilities` tool that maps a natural-language +intent to the ranked endpoints that answer it (Job 1, discovery, built in). + +## Use it from Python + +When you want the four jobs as library calls rather than a server: + +```python +from gecko.client import AgentApiClient + +client = AgentApiClient.from_openapi_url("https://api.example.com/openapi.json") + +# Job 1 — discovery: intent -> ranked endpoints +hits = client.search("list the NFTs an owner holds", limit=5) + +# inspect the question-shaped tool defs (auth hidden) +tools = client.list_tools() + +# Job 3 — first-call-correct: build the exact PreparedRequest (placement/units resolved) +req = client.prepare(hits[0]["name"], {"ownerAddress": ""}) + +# call it — `recorded` synthesizes from schema ($0, offline-falsifiable); `live` hits the wire +result = client.call(hits[0]["name"], {"ownerAddress": ""}, mode="recorded") +``` + +Two modes, one code path: `recorded` synthesizes a schema-correct example offline +(no key, no spend — prove the shape first), `live` issues the real request. They +differ only at the transport edge — the comprehension is identical, so a call that +shapes up in `recorded` is the same call `live`. + +## How it maps to the four jobs + +| Job | What `gecko-surf` does | +|---|---| +| **Discovery** | Lexical intent→endpoint catalog + the `search_capabilities` tool | +| **Access & Auth** | Auth headers hidden from the tool def, injected at call time | +| **First-call-correct** | `prepare()` resolves path/query/body placement + a schema-correct example | +| **Drift** | Re-ingest the current spec → regenerated tools, no hand-patching | + +## The boundary (what it is and isn't) + +`gecko-surf` is the **comprehension / consumption** layer — it makes an API +*usable*. It is **not** a payment rail and **not** a marketplace; it composes on +top of MCP/x402 and consumes a spec as input. It stores only the API *surface* and +correctness metadata — never your response payloads, user data, or secrets. Point +it at a spec; it gives your agent a correct first call. That's the whole job. + +Back to the spine: [SKILL.md](SKILL.md). diff --git a/solana-api-skill/tests/verify-live-examples.sh b/solana-api-skill/tests/verify-live-examples.sh new file mode 100755 index 0000000..1583cf9 --- /dev/null +++ b/solana-api-skill/tests/verify-live-examples.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Verify the skill's documented ❌→✅ examples against the LIVE APIs. +# +# The thesis of this skill is first-call-correctness + drift-resilience — so its own +# examples are tested against the real endpoints. If an upstream API drifts and breaks +# a documented call, this fails in CI and we re-comprehend (we don't ship a rotten example). +# +# Keyless tests (Jupiter) run anywhere. Keyed tests (Helius DAS) skip without a key. +# bash tests/verify-live-examples.sh +set -uo pipefail +PASS=0; FAIL=0; SKIP=0 +ok(){ printf ' \033[32m✓\033[0m %s\n' "$1"; PASS=$((PASS+1)); } +no(){ printf ' \033[31m✗\033[0m %s\n' "$1"; FAIL=$((FAIL+1)); } +sk(){ printf ' \033[33m~\033[0m skip: %s\n' "$1"; SKIP=$((SKIP+1)); } + +Q="https://lite-api.jup.ag/swap/v1/quote" +SOL="So11111111111111111111111111111111111111112" +USDC="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + +echo "Jupiter — quote (keyless), from skill/apis/jupiter.md:" + +# ❌ documented: decimal amount=1.5 must be rejected with HTTP 400 +code=$(curl -s -o /dev/null -w '%{http_code}' "$Q?inputMint=$SOL&outputMint=$USDC&amount=1.5&slippageBps=50") +[ "$code" = "400" ] && ok "decimal amount=1.5 → 400 (rejected, as documented)" \ + || no "decimal amount=1.5 expected 400, got $code" + +# ✅ documented: atomic amount=1500000000 + slippageBps=50 → 200, echoes inAmount/slippageBps +body=$(curl -s "$Q?inputMint=$SOL&outputMint=$USDC&amount=1500000000&slippageBps=50") +echo "$body" | grep -q '"inAmount":"1500000000"' && ok "atomic amount=1500000000 → inAmount echoed" \ + || no "atomic amount: inAmount!=1500000000 (got: ${body:0:120})" +echo "$body" | grep -q '"slippageBps":50' && ok "slippageBps=50 echoed back" \ + || no "slippageBps not 50 in response" +echo "$body" | grep -q '"routePlan"' && ok "response contains routePlan" \ + || no "no routePlan in quote response" + +echo "Helius — DAS (needs HELIUS_API_KEY), from skill/apis/helius.md:" +if [ -n "${HELIUS_API_KEY:-}" ]; then + URL="https://mainnet.helius-rpc.com/?api-key=$HELIUS_API_KEY" + OWNER="86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY" + # ✅ named-object params are ACCEPTED (past param validation). A small limit keeps it + # fast; a server timeout still proves the shape was accepted — only an invalid-params + # rejection would mean the doc is wrong. + r=$(curl -s --max-time 25 "$URL" -H 'Content-Type: application/json' \ + -d "{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"getAssetsByOwner\",\"params\":{\"ownerAddress\":\"$OWNER\",\"page\":1,\"limit\":10}}") + if echo "$r" | grep -q '"result"'; then ok "DAS named-object params → result (accepted, as documented)" + elif echo "$r" | grep -qE 'timed out|-32504|-32603'; then ok "DAS named-object params accepted (server slow — shape valid, not rejected)" + else no "DAS named-object REJECTED (doc may be wrong): ${r:0:140}"; fi + # ❌ positional array → error (the trap the skill teaches) + e=$(curl -s --max-time 25 "$URL" -H 'Content-Type: application/json' \ + -d "{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"getAssetsByOwner\",\"params\":[\"$OWNER\",1,10]}") + echo "$e" | grep -q '"error"' && ok "DAS positional array → error (the documented trap)" \ + || no "DAS positional array did NOT error: ${e:0:120}" +else + sk "HELIUS_API_KEY not set — DAS named-object tests skipped" +fi + +echo "" +printf 'PASS=%d FAIL=%d SKIP=%d\n' "$PASS" "$FAIL" "$SKIP" +[ "$FAIL" -eq 0 ]