CellScript AMM Builder Integration Log from Bootstrap Friction to Devnet Transactions

Hi Arthur and everyone,

I have been working on a real local-devnet transaction path for the CellScript amm_pool.cell example, and I’ve hit a fundamental bootstrapping issue. I want to share what I’ve built so far, document the exact constraint, and ask for guidance before building on the wrong assumption.

What I have built so far

I created a Rust-based swap builder that encodes token data, pool data, swap math, and WitnessArgs for the CellScript AMM. It does not yet produce a fully accepted transaction on CKB, but it correctly handles the encoding and transaction shape. The code is open source:

link

In addition, I have:

  • Compiled and deployed amm_pool.elf, token.elf, and an always_success.elf on a local devnet (via offckb).

  • Built CCC scripts for deployment, funding, signing, and test-cell creation.

  • Confirmed the signing flow with prepareTransaction() and the cellDep wrapper shape.

  • Successfully created devnet test cells with secp256k1 locks.

  • Used always_success.elf as a stand-in type script to test the swap path without real token validation.

The blocker: token.cell bootstrap

The intended flow is:

text

deploy contracts → create real token cells → seed_pool → swap_a_for_b

The first action for token.cell is mint:

text

mint(auth_before: MintAuthority, to: Address, amount: u64) -> (auth_after: MintAuthority, token: Token)

This requires an existing MintAuthority input. But I cannot find where the very first MintAuthority cell is supposed to be created. Without that, I cannot honestly create real token.elf token cells, which blocks seed_pool and swap_a_for_b.

I have considered the following possibilities but do not want to assume:

  • There is a launch/setup contract (like launch.cell) involved.

  • The deploy transaction itself creates the first authority cell.

  • The create capability on MintAuthority has a bootstrap meaning I’m missing.

Related builder friction points

  1. Creation vs mutation dispatch
    When I tested a variant with swap_a_for_b first, it failed on creation. That makes sense if creation tries to execute the first action (swap) without an existing pool input. Is “first action runs on creation” a guaranteed CellScript behavior, or just an implementation detail?

  2. EntryWitness encoding
    I found the prefix CSARGv1\0 and wrapped it in WitnessArgs.input_type. It is very easy to mis‑encode (e.g., wrong Molecule offsets). What is the canonical way for off‑chain builders to encode CellScript EntryWitness payloads? Should we use cellc entry-witness, a library API, metadata from .meta.json, or manual ABI encoding?

  3. ProofPlan builder assumptions
    The compiled metadata exposes ProofPlan fields. Which ones are intended to be enforced by the builder before signing, and which are purely explanatory or runtime‑facing?

My direct questions for you (Arthur)

  1. How should the first MintAuthority cell be created for token.cell?
    Is there a planned bootstrap transaction or a separate contract that issues the initial authority?

  2. Is “first action runs on creation” a guaranteed CellScript rule across all contracts?

  3. What is the canonical builder path for EntryWitness encoding?
    (CLI tool, library, or manual ABI?)

  4. What ProofPlan assumptions must an off‑chain builder enforce before signing?

  5. Is there a recommended local dev fixture pattern for testing token.cell without using always_success as a fake token type?
    Or should I continue with the fake token path until the bootstrap is clarified?

Why I am asking

I want to continue with the real path, not a workaround:
MintAuthority bootstrap → mint token cells → seed_pool → swap_a_for_b

Any guidance on the intended token.cell bootstrap flow would unblock the next step and allow me to produce a fully working swap builder that others can reuse.

Thank you for your work on CellScript, and I look forward to your insights.

Here are the links to the sources that informed my thinking:

6 Likes

Thanks again for the detailed builder-friction report. I have gone through the issues and fixed the example-side blockers in a new release v0.16.1.

To give you a full picture:

1. token/AMM/launch bootstrap

I fixed the token/AMM/launch bootstrap path first:

  • token.cell::mint is now mint_with_authority, so it is clear that it requires an existing MintAuthority Cell.
  • launch.cell::simple_launch is now bootstrap_token.
  • launch_token and bootstrap_token now provide the explicit path for creating the first MintAuthority and initial Token Cells.
  • launch_token materialises the Pool and LP receipt topology directly; it does not rely on calling amm_pool.seed_pool implicitly.

I also expanded the audit across the bundled production examples and found the same bootstrap pattern in nft.cell: mint and batch_mint required an existing Collection Cell, but there was no first transaction to create that Collection. I fixed that with create_collection, and the stateful NFT scenario now proves create_collection -> mint -> create_listing -> buy_from_listing using committed local CKB/devnet Cells.


2. builder integration

For builder integration, I would currently treat the CLI as the canonical interface:

  • compile/deploy a scoped artifact by selecting the entry explicitly with --entry-action <action>
  • inspect the generated entry ABI with cellc abi <file> --target-profile ckb --action <action>
  • generate the _cellscript_entry / CSARGv1 witness bytes with cellc entry-witness <file> --target-profile ckb --action <action>
  • before signing, inspect builder assumptions with cellc explain-assumptions ... --primitive-strict 0.16 and validate the transaction JSON with cellc validate-tx --against <metadata.json> --primitive-strict 0.16 <tx.json>

So the canonical path today is CLI-first. A reusable Rust SDK wrapper can mirror this ABI, but I do not want to imply that a stable builder library API already exists.

3. reusable Rust swap builder follow-up

What I would like to treat as the next collaborative follow-up is the reusable off-chain Rust swap builder. You were already trying to build exactly that, so my goal with this fix was to remove the example-contract lifecycle blockers and give you a stable, accepted path to build against.

Looking forward to any follow-up.

3 Likes

Thanks Arthur, this clarifies the exact boundary I was stuck on.

The key takeaway for me is that the bootstrap issue was real and is now addressed explicitly in v0.16.1 through launch_token / bootstrap_token, while builder integration should remain CLI-first for now rather than assuming a stable Rust SDK API.

My next step will be to update the repo to v0.16.1, rebuild the flow around the explicit launch/bootstrap path, and treat cellc abi, cellc entry-witness, cellc explain-assumptions, and cellc validate-tx as the canonical builder interface. After that I can continue the Rust swap builder as a thin deterministic layer around those checked artifacts.

Really appreciate the fast fix and the clear direction.

2 Likes

No worries at all, glad it was useful.
Feel free to reach out any time.

1 Like

Thanks, this got launch_token to commit locally. Before I continue deeper, I want to avoid building on fixture assumptions.

  1. In an external builder repo, should MintAuthority, Token, Pool, and LPReceipt outputs use simple fixture type scripts like the acceptance harness, or should they use specific CellScript scoped artifacts?
  2. What is the expected builder_assumption_evidence shape for cellc validate-tx, especially the capacity policy evidence?
  3. For scoped action artifacts used as lock scripts, should cellc entry-witness bytes go directly in witnesses[0], while type-script actions use WitnessArgs.input_type?
  4. What is the canonical way for builders to derive resource/shared/receipt data layout?

I got the CKB transaction accepted, but cellc validate-tx still fails on missing capacity evidence. I want the next step to follow the intended builder path, not just pass devnet.

1 Like

Cool. I can see you are trying to avoid baking fixture assumptions into a reusable builder.

1. Type Scripts For Token / Pool / Receipt Cells

For now, treat the simple type scripts used by the acceptance harness as fixtures, not as the long-term semantic source of truth.

The important builder boundary is:

  • compile each CellScript action as an explicit scoped artifact with --entry-action;
  • bind the transaction to the intended artifact / CellDep / script identity;
  • encode output data according to the compiler-published layout;
  • keep the simple fixture scripts only for local scaffolding where they are explicitly marked as fixtures.

So for an external reusable builder, I would not silently inherit the acceptance harness’ fixture type-script pattern as ‘the protocol’. It is fine for local dev fixtures, but the builder should make the artifact/script identity explicit.

2. builder_assumption_evidence Shape

Use cellc solve-tx ... --json or cellc explain-assumptions ... --json as the source of truth.

For each non-structural builder assumption, validate-tx expects an evidence object shaped like:

{
  "assumption_id": "...",
  "kind": "...",
  "origin": "...",
  "feature": "...",
  "proof_plan_status": "...",
  "evidence": {
    "source": "builder",
    "checked": true
  }
}

(The evidence field must be a non-empty object or array. )

For capacity evidence specifically, the current validator checks the evidence schema, while the builder should keep concrete final-transaction measurements such as:

{
  "consensus_serialized_tx_size_bytes": 1234,
  "occupied_capacity_shannons": 6200000000,
  "output_occupied_capacity_shannons": [6200000000],
  "output_capacity_shannons": [7000000000],
  "capacity_is_sufficient": true,
  "under_capacity_output_indexes": []
}

In other words, validate-tx is the pre-signing shape/evidence gate and CKB dry-run and occupied-capacity measurement remain the production evidence. The builder should still supplies live cell selection, final witnesses, fee/change, capacity measurement, and signatures.

3. Entry Witness Placement

cellc entry-witness emits the raw CSARGv1 bytes consumed by the generated _cellscript_entry wrapper.

Please do not wrap those bytes in WitnessArgs.input_type by default.

The current entry wrapper loads the raw witness payload with LOAD_WITNESS:

  • normal action / lock execution: Source::GroupInput, group index 0;
  • output-only type-script action case: action entries can fall back to Source::GroupOutput, group index 0.

Here, index 0 is script-group-relative, not necessarily the transaction-global witnesses[0].

WitnessArgs.input_type and WitnessArgs.output_type are separate explicit CKB witness surfaces. Use those only when the CellScript source itself calls helpers like witness::input_type(...) or witness::output_type(...).

4. Canonical Data Layout

Please note that the canonical layout should come from compiler artifacts, not from hand-copied harness structs.

Use the compiler outputs as the builder contract:

cellc abi ...
cellc constraints ...
cellc entry-witness ... --json
cellc explain-assumptions ... --json
cellc validate-tx ... --json

My Suggested Next Steps

Try continuing with this sequence:

launch_token or bootstrap_token
  -> live MintAuthority
  -> mint_with_authority
  -> live Token cells
  -> seed_pool, unless launch_token already materialised the Pool
  -> swap_a_for_b
  1. generate all scoped artifacts with explicit --entry-action;
  2. generate entry witnesses through cellc entry-witness;
  3. attach structured builder_assumption_evidence;
  4. run cellc validate-tx;
  5. keep the CKB accepted transaction plus capacity / tx-size / cycle evidence.

Theoretically that would give us a builder path that is reusable, not just a devnet workaround. One important caveat though, I have validated this path through production-mode acceptance tests against a local CKB devnet, but I have not run this exact builder flow on mainnet yet.

So your external builder work is still very valuable: it may still reveal real integration friction that the in-repo acceptance harness does not expose.

Please do keep sharing what you find. If validate-tx, witness placement, capacity evidence, or layout derivation still feels awkward from a real builder repo, that is useful feedback, not noise.

I am also thinking about a more ergonomic transaction-construction layer around these compiler artifacts. Your work is already close to the kind of practical builder loop I want CellScript users to have, so your next round of findings may actually help shape that work.

Really appreciate you pushing on the real path. :hot_beverage:

2 Likes

Thanks Arthur, this clears up the exact boundary I was missing.

My main correction from this is:

  • treat the acceptance harness type scripts as fixtures only
  • compile every real action as an explicit scoped artifact with --entry-action
  • bind the transaction to the intended artifact / CellDep / script identity
  • stop wrapping cellc entry-witness bytes in WitnessArgs.input_type by default
  • use compiler outputs as the builder contract instead of copying harness layouts

The script-group-relative witness point is especially useful. I had mentally collapsed “group index 0” into transaction-global witnesses[0], which is the kind of mistake an external builder can easily make.

For the next pass I am going to update the builder repo around this flow:

launch_token or bootstrap_token
  -> live MintAuthority
  -> mint_with_authority
  -> live Token cells
  -> seed_pool, unless launch_token already materialised the Pool
  -> swap_a_for_b
I will generate scoped artifacts with explicit --entry-action, generate witnesses through cellc entry-witness, attach structured builder_assumption_evidence from explain-assumptions / solve-tx, then run cellc validate-tx before signing.
I will also keep recording the extra production-side evidence from CKB dry-run: tx size, capacity, fee/change, and cycles.
This gives me a much better target: not just “make CKB accept a tx”, but make the external builder pass the CellScript pre-signing checks using compiler artifacts as the source of truth.
I’ll report back with the next friction points once I try this path in the repo.
1 Like

The current blocker:

  • We need real MintAuthority, Token, Pool, and LPReceipt cells with proper CellScript identity.
  • But if we use an action artifact like token_mint_with_authority.elf directly as the type script for a MintAuthority cell, CKB runs that script immediately when the cell is created.
  • That script expects action witness bytes, so it fails during creation with entry-witness-abi-invalid.
    In simple terms:
    We can build and validate the transaction shape, but we do not yet know the correct “ID badge” each resource cell should wear on-chain.
    always_success was a fake ID badge. The scoped action artifact is not the right passive ID badge either, because it tries to execute as an action.
    So the blocker is: how does an external builder create real resource cells with the correct CellScript identity without triggering the wrong action verifier?
1 Like

Good. I think you have found another real UX design gap here.

A scoped action artifact indeed should not be used as the passive type script identity for MintAuthority, Token, Pool, or LPReceipt. It is an active verifier. If CKB runs it during output creation, it will expect action witness bytes, so the entry-witness-abi-invalid failure makes sense.

I am currently thinking about a possible fix plan:

Concretely:

  1. Make the docs explicit: always_success is only a fixture badge, and scoped action artifacts are active verifiers.
  2. Add CLI/backend guardrails so validate-tx can warn or fail when an action artifact is used as a passive resource type identity.
  3. Add a compiler-supported resource identity artifact, or a lifecycle-stable dispatcher artifact, so external builders know exactly what “ID badge” each resource cell should wear.
  4. Add an external-builder devnet acceptance case that creates real MintAuthority, Token, Pool, and LPReceipt cells without relying on always_success.

I’ll think this over tonight and try to address it elegantly, with ergonomics in scope. I do not want this to become another confusing builder convention. If you have a proposal for the resource identity shape, I would really love to hear it.

3 Likes

Ok now, quick update: I pushed v0.16.2 to address this.

What changed:

  • always_success is now documented as fixture-only.
  • scoped action artifacts are documented as active verifiers, not passive resource identities.
  • added cellc resource-identity for real resource cell identity plans.
  • added cellc builder manifest and cellc builder check as the builder-facing path.
  • builder contract commands now emit JSON by default, so builders do not need to remember --json.

( If you are using AI-assisted tools you may include new release note into the context in your process.)

The old flow made it too easy to confuse an action verifier with a resource cell identity. That is what caused the entry-witness-abi-invalid failure: CKB executed an active action artifact during cell creation, but the transaction was only trying to create a passive resource cell.

The goal is to make the compiler artifacts the source of truth for builders, instead of relying on hand-copied harness conventions.

2 Likes