Full draft text below (KERNEL v4.3, frozen — SHA-256 779C7F9D…D274A68D). The preamble (frontmatter) will accompany the PR to ethereum/ERCs.
Abstract
This standard binds an ERC-721 token to an executable skill: a package of files, led by a primary Markdown document, that an off-chain runtime (including LLM-based agent runtimes) can fetch, verify byte-for-byte, and execute.
ERC-721 Asset Layer ── who owns this skill token
tokenId
├── owner
├── approved / operator
└── tokenURI (Metadata ext., optional) display identity only
Mandatory Skill Binding ── which version it binds; who may update; whether it may change
tokenId
├── mdHash plaintext primary-document commitment
├── packageHash published-package commitment (plaintext or ciphertext)
├── version content version
├── packageURI mutable transport hint
├── updateAuthority independent publication right
└── frozen permanently immutable content
Optional On-chain Document ── read the plaintext primary document from the chain itself
tokenId
├── hasOnchainSkillDocument() once true, permanently true
└── skillDocument() → exact plaintext primary-document bytes
Off-chain Normative Layer ── what the commitments protect, how to fetch and verify
packageHash
└── SkillRoot
├── md { path, cid } primary document: raw or ciphertext CID
├── manifest public, machine-readable, minimal schema below
├── entry / license / files files may be raw or ciphertext CIDs
├── confidentiality? OPTIONAL confidentiality descriptor
└── prev mandatory version chain (absent only at version 1)
External Scalable Rights Layer ── companion standards; not part of this interface
SkillRef = (chainId, skillContract, skillTokenId)
└── ERC-1155 rights contracts: licenses / editions / usage credits
Each token defines a canonical skill reference. Identity is the hashes; transport is a replaceable hint; publication is a right separate from ownership; freezing is an irreversible promise; confidentiality is a package property, not a token field. Rental, subscription, metering, delegation, gated upgrades, and license issuance are deliberately left to companion extensions.
This proposal standardizes executable-artifact identity, integrity, versioning, and publication governance. It intentionally does not define skill registries, discovery, installation state, usage attestations, reputation, access rights, licensing, or settlement.
Motivation
AI agents have on-chain identities (ERC-8004) and smart accounts (ERC-4337), but the skills they run live in centralized registries: unverifiable, unownable, silently mutable — the root condition of registry supply-chain attacks. tokenURI cannot fix this; it is a display pointer with no integrity guarantee.
Registry and package-manager protocols can identify, locate, and distribute a skill, but an execution runtime must answer a lower-level question: which exact document, file graph, package version, and publication history does a token commit to? Without a shared artifact-binding model, two clients may resolve the same registered skill while receiving different companion files, following different update histories, or interpreting confidentiality metadata differently. This proposal standardizes that artifact boundary independently of any particular registry, marketplace, agent identity system, or runtime.
Two hashes and a URI alone would be custom metadata. What makes this a standard is the pair of normative definitions beneath them: packageHash commits to a deterministic content-addressed object graph (the SkillRoot) covering every file the skill’s execution depends on, and mdHash commits to the plaintext primary document within it. Together they form a verifiable binding between a token and an executable artifact — publishable in the clear or as ciphertext, readable on-chain where the publisher chooses, and referenced by scalable rights systems built on top.
A skill’s token reference is singular; its usage value must be replicable, distributable, tiered, and metered. This standard defines the former. Multi-token rights contracts (a companion standard) reference it via (chainId, skillContract, skillTokenId) and handle the latter: ERC-721 makes a skill identifiable; ERC-1155 makes the skill economy scalable.
Specification
The key words “MUST”, “MUST NOT”, “SHOULD”, and “MAY” are to be interpreted as described in RFC 2119 and RFC 8174.
Core interface (mandatory)
interface IERCXXXXSkill /* standalone extension interface; see Compliance */ {
struct SkillBinding {
bytes32 mdHash; // SHA-256 digest of the plaintext primary Markdown document
bytes32 packageHash; // SHA-256 digest of the encoded SkillRoot as published
uint64 version; // content version, starts at 1
}
event SkillUpdated(uint256 indexed tokenId, bytes32 mdHash, bytes32 packageHash, uint64 version);
event SkillURIUpdated(uint256 indexed tokenId, string packageURI);
event SkillUpdateAuthorityChanged(uint256 indexed tokenId,
address indexed previousAuthority,
address indexed newAuthority);
event SkillFrozen(uint256 indexed tokenId);
function skillOf(uint256 tokenId) external view returns (SkillBinding memory);
function skillURI(uint256 tokenId) external view returns (string memory);
function updateAuthorityOf(uint256 tokenId) external view returns (address);
function isSkillFrozen(uint256 tokenId) external view returns (bool);
function updateSkill(uint256 tokenId, bytes32 mdHash, bytes32 packageHash) external;
function setSkillURI(uint256 tokenId, string calldata packageURI) external;
function setUpdateAuthority(uint256 tokenId, address newAuthority) external;
function freezeSkill(uint256 tokenId) external;
}
Confidentiality adds no field here: encryption is described inside the hash-protected package, not as a token flag. License supply, pricing, and mint mechanics likewise never enter this interface.
On-chain document interface (optional)
interface IERCXXXXOnchainSkillDocument /* own ERC-165 id */ {
/// @notice Whether a plaintext on-chain copy of the primary document exists for tokenId.
/// @dev MUST revert for nonexistent tokenId. Once true, MUST remain true permanently.
function hasOnchainSkillDocument(uint256 tokenId) external view returns (bool);
/// @notice The exact plaintext UTF-8 bytes of the primary Skill document.
/// @dev MUST revert when hasOnchainSkillDocument(tokenId) == false.
function skillDocument(uint256 tokenId) external view returns (bytes memory document);
/// @notice Atomically update the on-chain document together with the binding.
/// @dev mdHash is computed in-contract as sha256(document).
function updateSkillWithDocument(uint256 tokenId, bytes calldata document, bytes32 packageHash) external;
/// @notice Publish the current primary document on-chain without a version change.
/// @dev MUST revert unless sha256(document) == skillOf(tokenId).mdHash.
/// MUST NOT change packageHash or version. Sets hasOnchainSkillDocument permanently true.
function publishSkillDocument(uint256 tokenId, bytes calldata document) external;
event SkillDocumentPublished(uint256 indexed tokenId, bytes32 mdHash);
}
skillDocument returns bytes: the commitment is over exact raw bytes. When a copy exists, sha256(skillDocument(tokenId)) MUST equal skillOf(tokenId).mdHash at all times.
Existence is permanent. hasOnchainSkillDocument MAY transition from false to true — via publishSkillDocument (disclosing the current document without touching packageHash or version, since the package itself is unchanged) or via updateSkillWithDocument (when the document content actually changes) — and MUST NOT ever transition back: publishing the primary document on-chain is an irreversible disclosure, and the interface state reflects that. A token that has once published its document on-chain may later encrypt companion files, but its primary document can never return to genuine confidentiality (see Security Considerations).
Consistency with updates. When hasOnchainSkillDocument(tokenId) == true and a proposed update would change mdHash, plain updateSkill MUST revert; the update MUST go through updateSkillWithDocument, which computes mdHash = sha256(document) in-contract and updates document, both commitments, and version in one transaction.
The on-chain document is exclusively the primary Markdown document. No other package file may be published through this extension; the sha256(document) == mdHash invariant makes this structural rather than procedural. The on-chain document is always plaintext: for skills whose primary document is confidential, hasOnchainSkillDocument MUST return false and skillDocument MUST revert. Storage technique is implementation-defined and invisible to callers. An on-chain document never replaces mdHash.
Compliance
- The contract MUST implement ERC-721 and ERC-165.
supportsInterface MUST return true for IERCXXXXSkill, and additionally for IERCXXXXOnchainSkillDocument when implemented. The interfaces deliberately do not inherit IERC721.
- A compliant contract SHOULD implement the ERC-721 Metadata extension. If
tokenURI is implemented, it MUST NOT be used as a verification source; it MAY mirror facts such as encrypted: true or document_onchain: true for wallets, without authority.
Identity versus transport
The skill’s identity is (mdHash, packageHash, version). packageURI is a transport hint and is not part of the identity:
setSkillURI MUST NOT change version and MUST emit SkillURIUpdated. Replacing a dead endpoint never constitutes a new skill version. packageURI is a single URI; multi-mirror retrieval is expressed by URI resolvers or off-chain retrieval manifests, not by this field.
packageURI MUST NOT be treated as a trust input: consumers MUST verify all retrieved content against the commitments and MUST discard non-matching content, whatever its source.
Update authority
Publication is a right separate from ownership:
updateAuthorityOf(tokenId) is initialized at mint. Only the current update authority may call updateSkill, updateSkillWithDocument, setSkillURI, setUpdateAuthority, and freezeSkill.
setUpdateAuthority(tokenId, address(0)) MUST revert: abandoning maintenance is expressed by freezeSkill, never by burning the authority — a zero authority would leave packageURI permanently unmaintainable without setting frozen.
ownerOf(tokenId) does not automatically equal the update authority; ERC-721 transfers MUST NOT change the update authority; ERC-721 approved/operator approvals MUST NOT confer any of these powers.
Freezing
freezeSkill MUST be irreversible and MUST emit SkillFrozen. Once frozen, updateSkill and updateSkillWithDocument MUST revert forever.
- Freezing binds content —
mdHash, packageHash, version — not transport: setSkillURI and setUpdateAuthority remain callable, so a frozen skill’s retrieval hints can outlive any storage provider.
Existence and update behavior
skillOf, skillURI, updateAuthorityOf, isSkillFrozen on a nonexistent tokenId MUST revert.
- At mint the binding MUST be fully populated,
version MUST equal 1, and the contract MUST emit SkillUpdated and SkillUpdateAuthorityChanged(tokenId, address(0), initialAuthority).
mdHash and packageHash MUST NOT be bytes32(0).
- Updates MUST set
mdHash and packageHash atomically in one call. The new packageHash MUST differ from the current one. mdHash MAY remain unchanged when the primary document is unchanged — editing companion files or rotating encryption keys legitimately changes only packageHash.
- Callers never pass
version: the contract increments it by exactly 1 per successful update and MUST revert on uint64 overflow. Versions are append-only.
Hash definitions (normative)
mdHash = SHA-256(plaintext primary Markdown document raw bytes). No line-ending, whitespace, BOM, or Unicode normalization; no salting or keying of any kind. The commitment is always to the exact plaintext, even when the published copy is encrypted.
packageHash = SHA-256(deterministically encoded SkillRoot bytes) — the package as published, which under confidentiality means the ciphertext objects. It is never the hash of a zip, tar, or .skill archive.
Package paths (normative)
All paths in this standard (md.path, entry.path, files keys, confidentiality objects keys) MUST be relative, case-sensitive UTF-8 POSIX paths. Paths MUST NOT begin with /; MUST NOT contain \, empty segments, or the segments . or ... No path normalization is performed: paths match byte-for-byte or not at all.
SkillRoot (normative off-chain object)
SkillRoot MUST be encoded as deterministic DAG-CBOR per the DAG-CBOR specification and RFC 8949 core deterministic encoding: definite lengths, shortest-form integers, and map keys sorted by the bytewise lexical order of their encoded bytes (string keys therefore sort effectively length-first due to the CBOR length prefix).
skill-root = {
"md": md-entry, ; primary document
"manifest": link, ; machine-readable self-description; MUST be public
"entry": entry,
? "license": license-entry, ; legal/commercial terms
? "files": { * tstr => link }, ; remaining files; raw or ciphertext CIDs
? "confidentiality": link, ; OPTIONAL confidentiality descriptor; absent = fully public package
? "prev": link ; version chain, rules below
}
md-entry = { "path": tstr, "cid": link }
license-entry = { "path": tstr, "cid": link }
entry = { "path": tstr, "cid": link, "profile": tstr }
link = #6.42(bytes .size (37))
; DAG-CBOR link: tag 42 over a byte string of exactly 37 bytes:
; 0x00 identity multibase prefix
; 0x01 CIDv1
; 0x71 (dag-cbor) / 0x55 (raw) codec varint
; 0x12 0x20 sha2-256 multihash prefix
; 32-byte digest
- Optional fields MUST be omitted when absent;
null MUST NOT appear. SkillRoot, md-entry, license-entry, and entry are closed maps (extensibility lives in the manifest).
- Primary document: a SkillRoot has exactly one primary document.
md.path names it within the package namespace and MUST NOT appear as a key of files. Likewise license.path, when present, names the license file, MUST differ from md.path, and MUST NOT appear as a key of files — every addressable object in the SkillRoot carries an explicit path, so confidentiality objects resolve unambiguously; md.cid MUST identify the exact published bytes of that document. The publisher MAY choose the filename; SKILL.md SHOULD be used as the conventional default. The document MUST be UTF-8 encoded text and SHOULD be Markdown; its path SHOULD end in .md or .markdown, and consumers MUST NOT determine document type from the extension alone.
- Version chain (
prev): at version == 1, prev MUST be omitted; at version > 1, prev MUST be present and the digest inside its CID MUST equal the packageHash of the immediately preceding version.
- If
entry.path == md.path, entry.cid MUST equal md.cid; otherwise files[entry.path] MUST exist and equal entry.cid.
entry.profile is an opaque identifier of the execution environment; unknown profiles MUST NOT be executed by guesswork.
- Package boundary: everything execution depends on MUST be reachable from the SkillRoot; unreachable content enjoys no protection under
packageHash.
Manifest (normative minimal schema)
The manifest is a public, machine-readable JSON document. REQUIRED fields: schemaVersion, name, skillVersion, summary, entrypoint. manifest.entrypoint MUST equal SkillRoot.entry.path. Consumers MUST ignore unknown manifest fields; extension fields SHOULD use namespaced keys (e.g. x-vendor/*). Human-readable version strings live here (skillVersion), never on-chain.
Confidentiality descriptor (optional, in-package; normative schema)
When the confidentiality link is present, it MUST resolve to a public JSON descriptor. REQUIRED fields: schemaVersion, mode, profile, objects. policyHash and keyManagement are profile-specific: their presence and semantics are defined by the declared profile, not by this standard.
{
"schemaVersion": "1.0",
"mode": "encrypted",
"profile": "x-lit-threshold-access-v1",
"objects": {
"SKILL.md": { "ciphertext": "bafy…", "plaintextHash": "0x…" },
"src/main.py": { "ciphertext": "bafy…", "plaintextHash": "0x…" }
},
"keyManagement": { "type": "threshold-network" }
}
- Every encrypted object MUST appear in
objects under its package path (per the path rules above). objects[path].ciphertext MUST equal the corresponding CID in the SkillRoot: md.cid when path == md.path, license.cid when a license entry exists and path == license.path, and files[path] otherwise. plaintextHash MUST be the 32-byte SHA-256 digest of the plaintext, hex-encoded.
- If the primary document is encrypted,
objects[md.path].plaintextHash MUST equal the token’s mdHash.
profile is an opaque identifier of the confidentiality scheme. A runtime encountering an unknown confidentiality profile MUST NOT attempt to obtain keys or decrypt by guesswork.
- The manifest and the confidentiality descriptor themselves MUST remain unencrypted.
- Partial encryption is expected and legitimate (e.g. public
SKILL.md, manifest, and examples; encrypted source and configuration).
- Key rotation re-encrypts content, changing ciphertext CIDs and therefore
packageHash: this MUST be published as a version update. Plaintext-unchanged rotation leaves mdHash unchanged — the version history makes this legible.
- Execution-privacy targets (TEE, FHE, ZK-proof profiles) are distinct from storage encryption and MAY be declared in manifest extension namespaces; standardizing them is future work.
Verification
Public mode (no confidentiality link) — a verifier MUST: read skillOf(tokenId); retrieve the SkillRoot bytes from any source; check SHA-256(bytes) == packageHash; decode, re-encode under the deterministic rules, and check byte equality; check the schema, path rules, and version-chain rules; check digest(md.cid) == mdHash; retrieve leaves as needed, checking each against its CID; deliver only fully verified content.
Confidential mode — the same through schema checking, then: verify each retrieved ciphertext against its CID (integrity of the published package needs no keys); confirm execution rights through whatever rights layer applies; obtain keys per the declared profile; decrypt; check sha256(plaintext primary document) == mdHash and each decrypted object against its plaintextHash; deliver only fully verified plaintext to the runtime.
In both modes, partial retrieval (root → manifest → primary document → full package) is a first-class workflow. Runtimes MUST pin the versions they execute by hash, MUST NOT automatically follow updates, and SHOULD require explicit re-authorization after observing SkillUpdated.
Scalable rights (companion standards)
This interface defines one canonical skill reference per token. Scaling distribution is the job of rights contracts — typically ERC-1155 — that reference the skill externally:
struct SkillRef { uint256 chainId; address skillContract; uint256 skillTokenId; }
Duplicating the ERC-721 skill token itself for sales is NOT RECOMMENDED: copies fragment the reference, version stream, and update authority. Sell rights that point at the skill, not copies of the skill. The rights interface (skillRefOf, issuance, supply, pricing) is deliberately outside this standard.
Rationale
Two hashes and a URI alone would be metadata; the normative SkillRoot is what makes this a binding standard. The chain holds commitments; the object graph defines exactly what they commit to, byte for byte.
Why packageHash must change on every update but mdHash may not. Every update publishes a new package (new SkillRoot, new prev link); but the primary document legitimately survives edits to companion files and key rotations.
Why the primary document carries an explicit path. Confidentiality objects, entry resolution, and tooling all address files by path; leaving the primary document’s path implicit would smuggle a naming convention into a byte-precise standard. md { path, cid } mirrors entry { path, cid, profile }.
Why the on-chain document is permanent once published. Chain history preserves the bytes regardless of later state; an interface that could report false after a disclosure would misrepresent reality. The monotone rule makes the state truthful.
Why zero-address authority is forbidden. Abandonment has a dedicated, honest expression — freezeSkill — which sets the flag consumers actually check. A zero authority would strand transport maintenance while leaving frozen == false, the worst of both.
Why confidentiality is a package property, not a token flag. A boolean cannot express which files are encrypted, under which scheme, with which custody, or how keys rotate — and a token field would sit outside the hash protection. A buyer can verify the published ciphertext package before paying; verifying plaintext against mdHash necessarily follows decryption.
Why mdHash stays mandatory even with an on-chain document. The hash is the document’s identity — linking the on-chain copy, the in-package copy (or its plaintextHash under encryption), and every version comparison to the same document.
Transport outside identity; publication outside ownership; freezing in the kernel, gates in extensions; contract-incremented versions — gateways die without version bumps; marketplaces trade without publishing; an irreversible freeze costs one boolean; counters owned by contracts make monotonicity a property, not a convention.
Why rights scale in a companion layer. ERC-721 answers which skill this is; ERC-1155 rights contracts answer who holds which class of rights, and how many; execution-rights extensions answer who may run it now; confidentiality profiles answer how content is obtained after rights are proven.
Relation to prior art. ERC-4907 is the formal model as a minimal 721 extension but governs usage roles, not what the token is. ERC-5169 attaches contract-level client scripts; here each token binds a content-addressed, versioned, per-file-verifiable executable artifact. ERC-8004 anchors who an agent is; this standard anchors what a capability is.
Relationship to skill and tool registries. ERC-8239 defines an ERC-721-based Agent Skill Registry together with skill-manifest commitments and agent-skill installation and usage attestations. ERC-8257 defines a mapping-based Agent Tool Registry with canonical tool metadata, creator and origin binding, and optional predicate-based access control.
Both proposals operate primarily at the registration, discovery, access, or attestation layers. This proposal does not define any of those layers. It defines the deterministic executable artifact and version history bound to an ERC-721 token: registry-level commitments establish where an artifact is described; this proposal additionally standardizes what exact executable package, file graph, plaintext primary document, and version history a token commits to.
Because ERC-8239 represents each registered skill as an ERC-721 token, an ERC-8239 SkillRegistry can also implement this interface for the same token identifier. In that composition, ERC-8239 supplies registration, discovery, installation, and attestation semantics, while this proposal supplies package identity, per-file integrity, version chaining, publication authority, freezing, and optional confidentiality. ERC-8257 does not require ERC-721 representation; an ERC-8257 tool manifest can instead reference a token-bound executable artifact as an external artifact dependency.
An ERC-8239 manifest hash, an ERC-8257 metadata hash, and this proposal’s packageHash commit to different objects and are not interchangeable.
Backwards Compatibility
Fully compatible with ERC-721 infrastructure: ownership, approvals, and transfers are untouched. Wallets unaware of this extension display skill tokens as ordinary NFTs; they will not display update authority, frozen status, on-chain documents, or confidentiality facts.
Reference Implementation
To be provided in assets/ before submission: Solidity contracts; a skill pack packaging tool (including confidential packaging); the two ERC-165 interface identifiers; JSON Schemas for the manifest and the confidentiality descriptor; path-validation test cases; and fixed test vectors for the deterministic encoding — including public v1, a v2 update changing only a companion file (constant mdHash), an encrypted key-rotation update, and an on-chain-document atomic update — so independent implementations can be checked byte-for-byte.
Security Considerations
Updates are the attack surface. Substitution beneath an unchanged hash is computationally infeasible; risk concentrates in the update functions. Mitigations: the independent update authority (never reachable via ERC-721 approvals), irreversible freezing, append-only versions with a closed prev chain, runtime pinning norms. Companion extensions add timelocked and attested gates.
Buyers must inspect publication control. ERC-721 ownership transfer does not transfer update authority. Purchasers MUST inspect updateAuthorityOf and isSkillFrozen before acquisition: buying a token whose update authority remains with a third party means buying an asset whose content that party can still change.
Skill content is untrusted input. Prompt injection targets LLM runtimes through the primary document. This standard anchors integrity, not benignity: runtimes MUST sandbox execution with least-privilege tooling; vetting belongs to attestation and reputation layers.
On-chain plaintext is irreversible disclosure. A document published via the on-chain document extension can never be re-secreted; the permanent hasOnchainSkillDocument state reflects this. Converting a skill to encrypted mode in a later version does not erase historical plaintext from chain history.
Plaintext commitments reveal equality. A public mdHash (and plaintextHash entries) confirms content equality: an adversary who can guess a document’s exact bytes can confirm the guess against the hash. Publishers of short or highly predictable confidential documents should weigh this exposure; salted or private commitment schemes are outside this standard and left to future extensions.
Ownership is not confidentiality — and confidentiality is not this standard’s key custody. Public packages are downloadable by anyone knowing the CID. Confidential packages anchor ciphertext integrity on-chain, but key release depends on the declared profile’s custodian: its availability, honesty, and revocation behavior are outside this standard and MUST be documented by implementations. A compromised custodian can leak plaintext but can never alter the skill undetected.
Key rotation and stale ciphertext. Rotation MUST surface as a version update; availability of old plaintext to consumers pinned on old versions is a custodial policy question, not an integrity one.
Unprotected content. Only content reachable from the SkillRoot is protected; out-of-graph files are not part of the skill.
Availability. Anchors outlive any storage provider; publishers SHOULD store redundantly; frozen skills retain a live setSkillURI precisely so transport can outlive providers.
Appendix A: Lifecycle example (informative)
A creator with my-skill/ (SKILL.md, manifest.json, runtime.yaml, src/main.py, config/schema.json) runs a packaging tool that: reads all file bytes → (optionally encrypts selected files and writes the confidentiality descriptor) → computes leaf CIDs → builds the SkillRoot (linking prev to the previous version’s packageHash when updating) → computes packageHash over its canonical bytes and mdHash over the plaintext primary document → uploads to content-addressed storage → mints or updates with (mdHash, packageHash, packageURI).
A consumer’s agent, given chainId + contract + tokenId: checks ERC-165 → reads skillOf/skillURI → fetches and verifies the SkillRoot → in public mode verifies and loads content directly; in confidential mode verifies ciphertext, proves rights, obtains keys per the declared profile, decrypts, and verifies plaintext hashes → builds a sandboxed environment per entry.profile → runs the skill. Tooling can wrap this as skill install|verify|run eip155:1/0xSkill/42 — tool-layer conveniences, not contract functions.
Copyright
Copyright and related rights waived via CC0.