LS-IDL: a Lock Script Interface Description Language for CKB (derive, validate, commit)

A quick update for everyone following LS-IDL: the Unified Registry now supports the LS-IDL workflow.

You can connect a CKB testnet or mainnet wallet and try it directly here:


How to Use the LS-IDL Registry Profile

So as @OWK50GA explains above, LS-IDL describes the witness fields expected by a CKB Lock Script.

The Unified Registry currently shipped along with CellScript’s CLI toolchain preserves that description as exact bytes and binds it to thedeployed executable with this commitment:

code Cell data = executable bytes || SHA-256(raw idl.json bytes)

This tutorial follows the complete implemented workflow:

raw idl.json
    -> cellc validate
    -> cellc bind to a clean CKB executable
    -> cellc bundle and publish
    -> deploy the bound executable
    -> record chain evidence
    -> cellc fetch the exact IDL by Script identity

The word raw matters. Whitespace, key order, and the final newline are part of the committed IDL identity. Parsing and reserialising equivalent JSON can produce different bytes and therefore a different SHA-256 digest.

What cellc Does

The LS-IDL command group is:

cellc artifact ls-idl <validate|bind|bundle|fetch>

It provides four bounded operations:

  • validate checks the supported LS-IDL 0.1 schema and reports the SHA-256 of
    the exact file bytes;
  • bind appends that 32-byte digest to a CKB executable;
  • bundle creates the immutable Registry bundle and Artifact.toml; and
  • fetch resolves an IDL by a chain-verified CKB Script identity, validates
    the Registry response contract, and writes the exact response bytes.

The checked-in walkthrough inputs are under examples/registry_ls_idl.

Prerequisites

Prepare all of the following before publishing:

  • CellScript 0.24 cellc;
  • the original LS-IDL 0.1 idl.json;
  • a clean, unbound CKB Lock Script executable;
  • the source file to include in the Registry bundle;
  • an immutable 40- or 64-hex source revision;
  • the intended CKB hash_type and dep_type; and
  • a compatible CKB wallet for Registry writes and deployment.

Validation and lookup are public read operations and do not require a wallet.
Wallet authorisation is required when publishing or attaching deployment
evidence.

1. Prepare the IDL Document

A small document looks like this:

{
  "idl_version": "0.1",
  "name": "demo_lock",
  "witness": [
    {
      "name": "signature",
      "type": "secp256k1_sig",
      "required": true,
      "description": "Recoverable CKB secp256k1 signature"
    },
    {
      "name": "nonce",
      "type": "uint64",
      "required": true
    },
    {
      "name": "memo",
      "type": "bytes",
      "required": false
    }
  ]
}

The Registry profile accepts a non-empty JSON object no larger than 256 KiB.
witness is required and may contain at most 256 fields. Field names must be
unique.

The implemented field types are:

LS-IDL type Linear encoding
uint8 one unsigned byte
uint32 four-byte little-endian unsigned integer
uint64 eight-byte little-endian unsigned integer
secp256k1_sig 65 bytes
secp256k1_pubkey 33 bytes
schnorr_sig 64 bytes
bytes four-byte little-endian length followed by the payload

2. Validate the Exact Bytes

Run validation before touching the executable:

cellc artifact ls-idl validate --idl idl.json --json

The JSON result includes:

{
  "status": "valid",
  "format": "ls-idl",
  "format_version": "0.1",
  "sha256": "<SHA-256 of the exact idl.json bytes>",
  "executable_suffix_bound": false
}

This step rejects malformed JSON, unknown keys, unsupported types, duplicate field names, missing required field properties, and profile budget violations.

It does not modify the IDL or executable.

Treat the reported digest as part of the release identity. If idl.json changes afterward, repeat validation and binding.

3. Bind the IDL to the Executable

Always bind from a clean build output:

cellc artifact ls-idl bind \
  --idl idl.json \
  --executable build/demo-lock \
  --output build/demo-lock.ls-idl \
  --json

bind validates the IDL, computes SHA-256(raw idl.json bytes), and writes:

build/demo-lock bytes || 32-byte IDL digest

Verify the final pair explicitly:

cellc artifact ls-idl validate \
  --idl idl.json \
  --executable build/demo-lock.ls-idl \
  --json

The result now reports executable_suffix_bound: true. A suffix mismatch is a
hard error.

4. Create the Registry Bundle

Create the immutable bundle only from the bound executable:

cellc artifact ls-idl bundle \
  --idl idl.json \
  --executable build/demo-lock.ls-idl \
  --source src/lib.rs \
  --namespace example \
  --name demo-lock \
  --release 0.1.0 \
  --language rust \
  --hash-type data1 \
  --dep-type code \
  --toolchain 'rustc 1.97.1 + ckb-std' \
  --source-revision <40-or-64-hex-immutable-revision> \
  --output artifact.bundle.json \
  --artifact-manifest-output Artifact.toml \
  --json

The accepted --language values are cellscript, rust, c, javascript, and other. --hash-type defaults to data1; --dep-type defaults to code. State both explicitly in release automation so deployment evidence cannot inherit an accidental default.

The command writes two files:

  • artifact.bundle.json contains exactly one source, executable, and abi
    object as Base64-encoded bytes;
  • Artifact.toml names the deployable_contract release and points to that
    bundle.

It also reports four different identities:

Output field Algorithm and object Purpose
source_hash CKB Blake2b-256 of the source object immutable source-object identity
artifact_hash CKB Blake2b-256 of the bound executable CKB executable/data identity
abi_hash CKB Blake2b-256 of the raw IDL object Registry ABI object identity
idl_sha256 SHA-256 of the raw IDL object LS-IDL executable-suffix commitment

5. Dry-Run and Publish

Validate the generated bundle locally before any Registry write:

cellc publish \
  --artifact-manifest Artifact.toml \
  --dry-run \
  --json

The dry-run rechecks the coordinate, object roles, size limits, profile contract, raw IDL schema and hashes, and executable suffix. It does not upload anything.

For a first production publish, let cellc create a scoped delegated key and open the wallet authorisation flow:

cellc publish \
  --artifact-manifest Artifact.toml \
  --authorise \
  --json

For the Pudge Testnet Registry, select its API explicitly:

cellc publish \
  --artifact-manifest Artifact.toml \
  --authorise \
  --api-url https://api.testnet.registry.cellscript.dev \
  --json

After admission, the release starts with independent states similar to:

verification_status = pending
deployment_status   = undeployed
availability_status = active

The Registry worker must accept the immutable bundle and promote its integrity evidence before deployment evidence can be attached. A hash_bound result is an identity/integrity statement, not a security review.

6. Deploy the Bound Artifact and Record Evidence

Deploy build/demo-lock.ls-idl, not build/demo-lock. Deployment transaction
construction, capacity, fees, witnesses, signing, and broadcast remain in the
external CKB builder and wallet.

After the deployment transaction is committed, attach its OutPoint to the published release. First authorise a delegated key with the exact deployment scope. The principal_id is the normalized identity binding derived from the
connected signer, not the displayed CKB address. Choose joyid_ckb or
ckb_secp256k1 for --principal-type:

cellc auth capability create \
  --principal-type <principal-type> \
  --principal-id <normalized-wallet-principal-id> \
  --scope deployment:example/demo-lock \
  --expires 90d \
  --json > deployment-capability.json

Sign the payload with the matching CKB wallet, save the wallet result as deployment-wallet-signature.json, and submit it:

cellc auth capability submit \
  --payload deployment-capability.json \
  --wallet-signature deployment-wallet-signature.json \
  --json

create stores the generated delegated private key in the local OS keychain; submit returns its cap_... key ID after the Registry accepts the wallet-rooted grant. For Testnet, add --registry-origin https://api.testnet.registry.cellscript.dev to create and --api-url https://api.testnet.registry.cellscript.dev to submit.

The manual flow may request publish: and deployment: together when one key must perform both operations, but neither scope implies the other. This tutorial keeps them separate so a deployment key cannot publish a new release.

Then record the Testnet deployment:

cellc artifact record-deployment example/[email protected] \
  --network testnet \
  --api-url https://api.testnet.registry.cellscript.dev \
  --code-hash 0x<64-hex> \
  --hash-type data1 \
  --dep-type code \
  --tx-hash 0x<64-hex-deployment-transaction-hash> \
  --index 0 \
  --capability-key-id cap_<deployment-key-id> \
  --json

For production, use --network mainnet and the default production Registry origin. The command signs the deployment record with the delegated key, while the Registry verifies the configured RPC network, live code Cell, committe creation transaction, confirmations, artifact data hash, Script identity, and declared deployment mode.

The immutable profile and the evidence command must agree on hash_type and dep_type:

  • for data, data1, or data2, code_hash identifies the bound code Cell
    data; and
  • for type, code_hash is the code Cell Type Script hash. Later LS-IDL
    lookup also needs the current code Cell data_hash to disambiguate the
    executable bytes.

Do not describe a locally generated deployment payload as chain evidence. The Registry lookup becomes available only after the release is active, public, and backed by accepted chain-verified deployment evidence.

7. Fetch the Exact IDL by Script Identity

For a mainnet data1 Script:

cellc artifact ls-idl fetch \
  --code-hash 0x<64-hex> \
  --hash-type data1 \
  --network mainnet \
  --output fetched-idl.json \
  --json

For Testnet, use the matching Registry API:

cellc artifact ls-idl fetch \
  --code-hash 0x<64-hex> \
  --hash-type data1 \
  --network testnet \
  --api-url https://api.testnet.registry.cellscript.dev \
  --output fetched-idl.json \
  --json

For a Type Hash deployment, add the live code Cell data hash:

cellc artifact ls-idl fetch \
  --code-hash 0x<64-hex-type-script-hash> \
  --hash-type type \
  --data-hash 0x<64-hex-code-cell-data-hash> \
  --network mainnet \
  --output fetched-idl.json \
  --json

fetch refuses to overwrite an existing output unless --force is explicit.

Before writing, it requires the LS-IDL content type and schema-and-suffix-bound verification header, enforces the 256 KiB limit, validates the LS-IDL schema, hashes the response body directly, and compares that digest with the Registry header.

The public browser flows expose the same lookup for Mainnet and the isolated Pudge Testnet.

Lookup and download are read-only; connecting a wallet is needed only for Registry writes.

Common Failures

The executable suffix does not match

The IDL was changed or reformatted after binding, or the unbound executable was selected. Return to the clean executable and bind the final IDL bytes again.

cellc refuses to overwrite a file

This is intentional. Use a new output path, or pass --force only after checking the exact target.

Type Hash lookup requires --data-hash

A Type Script hash may identify more than one executable data revision. Supply the current code Cell data hash; the Registry returns 409 rather than choosing an ambiguous deployment.

Fetch returns not found

Check the Registry environment, network, code_hash, hash_type, and optional data_hash. A published bundle is not enough: the release must also be active, public, and chain-verified on the requested network.

Publish succeeds but lookup is not ready

Publication, verification, and deployment are separate states. Wait for bundle verification, deploy the bound artifact, and record the committed deployment OutPoint before expecting Script-identity lookup to succeed.

6 Likes