Ask a capable coding model to write a Supra lottery contract with on-chain randomness and you will get something that reads beautifully. It imports the framework, defines a state struct, requests random numbers, handles the callback. Then you try it. The randomness call is the VRF 2.x shape that 3.0 replaced. The deploy instructions send you into Docker when a browser IDE would do. The REST call hits /rpc/v1/, which is deprecated. The automation step uses a generic supra move tool run command instead of the dedicated subcommand. Every line is plausible. Almost none of it is current.
That is the problem supra-dev-skills exists to solve. It is a public, MIT-licensed Claude Code plugin that ships three skills: Supra Move development (including dVRF 3.0, oracles, automation, and deploying from ide.supra.com), StarKey wallet connect for Next.js, and the supra-ts-sdk TypeScript SDK. The README says the goal in one line: no more hallucinated APIs, no more wrong module names, no more debugging code Claude made up.
This post is about what we learned building it with colleagues at Supra, and why I think the pattern generalises to any ecosystem that lives in the long tail of a model’s training data. It is a sibling to the harness argument from my sovereign coding agent post: the model is rarely the hard part.
Table of contents
Open Table of contents
Why a niche chain is the worst case for a model
A model that knows nothing about a platform is easy to deal with. It says so, or it produces something obviously wrong. The dangerous case is a platform that is almost something the model knows well.
Supra’s Move environment is close kin to Aptos. That gives a model a strong prior that is right most of the time, which is exactly what makes it wrong in confident, hard-to-spot ways. It knows aptos_framework::coin exists, so it assumes the Supra equivalent is a mechanical rename. It has seen older Supra docs, so it reproduces older APIs. It has seen generic wallet code, so it writes a wallet integration that works on the developer’s laptop and fails on a phone.
Near-neighbour hallucinations are worse than random ones because they survive review. A reviewer who knows Aptos will nod along. The failure only shows up at compile time, at deploy time, or in production.
Where it came from, and when
A colleague started the repo in late March 2026 as a single Move skill. I picked it up at the start of April 2026 and did most of the correctness work over the following two weeks, which the CHANGELOG records as versions 2.0.0, 2.1.0 and 2.2.0, all dated April 2026. On 13 April it was readied for public release, with branch protection and issue templates.
The wallet work came later. Version 3.1.0, dated August 2026, is a StarKey runtime correctness pass whose CHANGELOG says it came “from defects found while shipping a production Starkey dApp.” In September 2026 I cut 4.0.0, the first release as a proper Claude Code plugin with all three skills bundled under one version number. That release also carried the security fix I describe further down.
The catalogue of things the model gets wrong
The most useful artifact in the repo is not any single skill. It is the accumulated list of specific, reproducible mistakes. Here are the ones I think are most instructive, grouped by where they bite.
Move and the framework.
- The rename that must not happen. Supra uses
supra_framework::where Aptos usesaptos_framework::. A model that learns that rule over-applies it and writesuse supra_std::smart_table::SmartTable, which does not compile.aptos_std::keeps its prefix on Supra. The 2.1.0 notes are blunt about why this mattered: a reference file stated the blanket “replace all aptos* with supra*” rule without qualification, so “any Claude session reading only this file would produce broken SmartTable/Table imports.” - A module that does not exist. The oracle lives in
supra_oracle::supra_oracle_storage, notsupra_oracle::oracle, andget_pricereturns four values (price, decimal, timestamp, round). The skill says “always destructure all 4.” - Compiler rules that break every first contract. The Move skill opens with four rules it says “cause compile errors on every single contract”: no
///doc comments under the Supra CLI, ASCII only even inside comments, casts always written as(expr as T)with the parentheses, and an exactacquireslist including transitive ones. The ASCII rule includes a table of characters models love to emit, and the em dash is the first entry. I found that funnier than I should have.
Native features.
- VRF 2.x versus 3.0. Models generate the old request shape, passing a sender and callback addresses directly. dVRF 3.0 uses a
SupraVRFPermit<T>capability that the module acquires once at init and stores, then passes as&state.permit_capon every request. The type parameter ties access control to the calling module. Two setup steps (whitelisting and funding a deposit account) are required before any of it works. - Automation registration. The 2.2.0 notes list this as a critical fix: the model’s
supra move tool run --function-id '...automation_registry::register_task'was replaced with the dedicatedsupra move automation registersubcommand and its real flags, plus a--simulatedry run. - Deployment. Older material sends everyone to a Docker container. Since 4.0.0 the skill defaults to the browser IDE at ide.supra.com, which compiles, tests and deploys a package signed by StarKey on Testnet or Mainnet, and keeps the Docker CLI as the second option for CI and scripting.
APIs and SDKs.
- Endpoint versions.
/rpc/v1/and/rpc/v2/are deprecated; the current REST API is/rpc/v3/. A model trained on older docs gets a 404 and no explanation. - Methods that never existed. The 2.1.0 notes record that the Python SDK examples used a “fabricated API” (
SupraAccount.from_private_key,bcs.encode_u64), thatinvokeContractFunctiondoes not exist in the TypeScript SDK, and that a pinned version@2.0.0does not exist on npm because published versions start at 3.0.0.
The wallet.
- Late injection. StarKey injects
window.starkeyafter page scripts run, so a single detection read on page load finds nothing on a browser that has the extension installed. The skill says to poll and never give the poll a deadline, because users install or unlock the wallet minutes later. - An empty first read. Even once the provider exists, the first
accounts()call after a page load routinely returns an empty list on a connected wallet. Treating that as “signed out” logs the user out on every refresh. The fix in the template is eight attempts, 250 ms apart, first non-empty result wins. - A call that lies. In StarKey’s mobile dApp browser,
changeNetworkrejects withUnrecognized chain ID.after it has already switched. The rule the skill teaches is to read the chain back and let that decide, never the call’s own answer. - The wrong event transport. The extension’s
starkey-*window messages look like an event API but are its internal bridge. Account switches arrive onprovider.on('accountChanged'). A project that listens only to the window messages “only updates when I refresh.”
None of these are exotic. Each one is the kind of thing a developer loses an afternoon to, and each one is something a model will produce with total confidence.
How the skills are designed to stop them
Writing correct documentation is not enough. A model with a strong wrong prior will read correct documentation and still drift back to the prior. A few design choices did most of the work.
Show the wrong answer, labelled. Almost every rule in the Move skill is a WRONG block next to a CORRECT block. That looks redundant to a human reader. For a model it is the point: the wrong pattern is the one it is going to generate, so naming it explicitly gives the model something to match against and reject. The README’s “key gotchas” section is the same idea compressed into four pairs.
Put the rules before the content. The compiler rules sit at the very top of the Move skill under the heading “apply to every file you write,” ahead of setup, syntax, and examples. Rules buried in section nine get read after the model has already decided how the file will look.
Ship code, not descriptions of code. The wallet skill does not describe how to build a wallet hook. It ships a production-tested one in assets/, roughly 950 lines by the skill’s own count, and instructs the model to read each file and write it into the project verbatim: “Do not try to reconstruct them from memory.” The skill even warns the model not to clean the hook up, because “most of what looks redundant is a wallet quirk with a comment above it explaining which one.” Some knowledge is too fiddly to survive paraphrase. Copying is the reliable transfer.
Load depth on demand. Each SKILL.md carries the rules and the common path, and points to reference files for the deep material: a StarKey runtime quirks document, the auth architecture, SupraNS resolution, the IDE deploy walkthrough. The skill says when to read each one, for example “before debugging any connect, switch, or network problem.” That keeps the always-loaded context small and the rarely needed detail one read away.
Cite a source for every fact. The README says the skills are “verified against live docs, SDK source, and on-chain behavior,” and the contributing section asks that any factual change document its source. That rule exists because of the next point.
The skill hallucinated too
The most humbling thing in the CHANGELOG is how often the fixes themselves were wrong.
In 2.1.0 we added a TypeScript derivation example using AccountAddress.fromDerivationPath. In 2.2.0 that entry reads: “Method does not exist.” In 2.2.0 we replaced a nonexistent simulateTx(account, rawTxn) with simulateTxUsingSerializedRawTransaction(serializedRawTx, account). In 4.0.0 the notes record that this call had its arguments reversed, and it was replaced again with the fluent rawTxn.simulate(account). A BCS encoding fix from 2.1.0 now carries an inline note marking it superseded, because the method it relied on does not exist either.
Whatever the cause in each case, it taught me that a skill is not a one-time knowledge dump. It is a codebase that rots like any other, and it needs the same discipline: a changelog, a version, a source for every claim, and a way to re-sync. The TypeScript SDK skill now says in its own description that it mirrors the skill bundled inside the supra-ts-sdk npm package and should be re-synced from there when the SDK releases. The closer a skill lives to the thing it describes, the slower it rots.
The sign-in bug we shipped to ourselves
Skills that ship code have a property documentation does not: their bugs get copied into every project that installs them. The 4.0.0 release opened with a security section, and the first entry is worth retelling at the level the CHANGELOG states it.
The wallet template supports sign-in with wallet. The server issues a nonce, the user signs a message, the server checks the signature and mints a JWT. The client signed a constant AUTH_MESSAGE and sent the nonce alongside it. The create-jwt route validated the nonce, but StarKey signs only the message bytes, so nothing tied the nonce to the signature. In the CHANGELOG’s words, “one captured { signature, publicKey } minted a JWT for that address with any fresh nonce, indefinitely.”
The fix was small once seen. lib/auth-constants.ts now exports authMessageFor(nonce), which appends the nonce to the message, and every client sign-in path signs that composed string while create-jwt verifies the same composition. The nonce is inside the signed bytes, so a captured signature is good for exactly one nonce.
Two related fixes went out with it. signIn() had been writing the JWT to localStorage and setting a cookie from JavaScript with an HttpOnly flag, which browsers ignore when the cookie is set from script. It now posts to /api/auth/wallet-login like every other path, so the only copy of the token is a server-set httpOnly cookie. And signature verification stopped relying on Node’s Buffer, which the edge runtime does not guarantee.
This was not the first auth fix. The 3.1.0 notes record that signature verification had not bound the public key to the claimed address, with the address check left as a TODO. That was fixed by deriving the address from the key and comparing. The skill now carries both lessons as standing rules: “the nonce must be inside the signed bytes,” and “a valid signature is not proof of an address.”
The general point: when you teach a model to drop in your template, you are teaching it to reproduce your template’s security properties, good and bad, at scale. Template code deserves more review than application code, not less.
Grounding a model in the long tail
Stripped of Supra specifics, here is the approach I would use for any niche SDK, chain, or internal platform.
- Collect the mistakes before writing the docs. Ask the model to do real tasks cold and record exactly what it gets wrong. That list is the spec. Our README’s “why this exists” section is literally that list.
- Teach against the prior, not into a vacuum. For each mistake, show the wrong form and the right form together. The model is not missing knowledge so much as holding the wrong knowledge confidently.
- Order by blast radius. Rules that break every file go first. Rare depth goes in reference files the model reads when a trigger says to.
- Prefer verbatim artifacts for fiddly code. If the correct behaviour depends on runtime quirks, ship the working file and tell the model to copy it.
- Version it and cite it. Every claim needs a source, every change needs a changelog entry, and the skill should live as close to the upstream SDK as you can get it.
- Review template code like production code. It is production code, in every project that installs it.
What I took away from it
- Near-misses are the real hallucination problem. A model that confuses Supra with Aptos is more dangerous than one that knows nothing, because its output survives review.
- Context is a codebase. A skill rots, accumulates bugs, and needs versioning. Ours contradicted itself more than once before the CHANGELOG caught it.
- Copying beats explaining. For the wallet layer, the most reliable instruction we found was “do not reconstruct this from memory.”
- Security bugs in templates are multiplied bugs. The replayable sign-in would have shipped into every dApp that used the skill. Binding the nonce into the signed message took one function.
The plugin is MIT-licensed and installs with two commands from the README. If Claude still gets something about Supra wrong with it loaded, that is exactly the bug report the repo asks for.