Skip to content

Commit 6431aa3

Browse files
committed
Add NFT collection bootstrap path
1 parent 77edbae commit 6431aa3

9 files changed

Lines changed: 152 additions & 39 deletions

‎docs/CELLSCRIPT_EXAMPLE_BUSINESS_FLOWS.md‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ examples on a local CKB chain:
1515
- `token.cell`
1616
- `vesting.cell`
1717

18-
For those examples, all 43 business actions are strict-compiled, deployed, dry-
18+
For those examples, all 44 business actions are strict-compiled, deployed, dry-
1919
run, committed, and measured with builder-generated CKB transactions. The report
2020
also records cycles, consensus transaction size, occupied capacity, malformed
2121
transaction rejection, and output-capacity sufficiency.
@@ -211,6 +211,10 @@ and batch mint flows.
211211

212212
```mermaid
213213
flowchart TD
214+
AO["Collection parameters"] --> AP["create_collection"]
215+
AP --> AQ["Check max_supply bounds"]
216+
AQ --> AR["Create Collection output Cell"]
217+
214218
A["Collection input Cell"] --> B["mint"]
215219
B --> C["Check supply below max"]
216220
C --> D["Update Collection total_supply"]
@@ -264,10 +268,15 @@ flowchart LR
264268
I["collection_creator"] --> J["Collection creator predicate"]
265269
```
266270

267-
CKB acceptance status: all nine actions are builder-backed and run on-chain. The
271+
CKB acceptance status: all ten actions are builder-backed and run on-chain. The
268272
five locks strict-compile and have builder-backed valid-spend and invalid-spend
269273
cases.
270274

275+
Bootstrap note: `mint` and `batch_mint` require an existing `Collection` input
276+
Cell. Use `create_collection` to materialise the first live `Collection` Cell;
277+
the stateful NFT listing scenario now proves `create_collection -> mint ->
278+
create_listing -> buy_from_listing` from committed devnet Cells.
279+
271280
## `timelock.cell`
272281

273282
Business purpose: absolute and relative time locks, locked assets, normal

‎docs/releases/CELLSCRIPT_0_13_2_ACCEPTANCE_COMMUNITY_POST.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ supported as a compatibility wrapper.
3232
The current release evidence includes:
3333

3434
- 7 production bundled examples.
35-
- 43/43 production acceptance actions covered.
36-
- 16 builder-backed lock valid/invalid spend cases.
35+
- 44/44 production acceptance actions covered.
36+
- 17 builder-backed lock valid/invalid spend cases.
3737
- 27 stateful local CKB scenarios.
38-
- 46 committed stateful steps.
38+
- 47 committed stateful steps.
3939
- 7 end-to-end business-flow scenarios.
4040
- 20 stateful action-branch scenarios.
4141

‎docs/releases/CELLSCRIPT_0_13_2_RELEASE_NOTES.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,14 +264,14 @@ New at the 0.13.2 release cutoff:
264264
minting, AMM pool lifecycle, vesting revoke, and multisig execution flows;
265265
- stateful action-branch scenarios cover every production acceptance action not
266266
already covered by those end-to-end flows;
267-
- the stateful report must cover 43/43 production acceptance actions with no
267+
- the stateful report must cover 44/44 production acceptance actions with no
268268
missing action IDs or missing action artifacts;
269269
- every stateful step must have dry-run evidence, committed transaction
270270
evidence, consumed-input liveness checks, output liveness checks, measured
271271
cycles, consensus-serialized transaction size, occupied-capacity evidence, and
272272
no under-capacity outputs.
273273

274-
The current release gate expectation is 27 stateful scenarios, 46 committed
274+
The current release gate expectation is 27 stateful scenarios, 47 committed
275275
stateful steps, 7 end-to-end business scenarios, and 20 stateful action-branch
276276
scenarios. If any new production action is added later, the stateful coverage
277277
gate must fail until that action is covered.

‎docs/releases/CELLSCRIPT_0_13_RELEASE_SCOPE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,5 +286,5 @@ python3 scripts/validate_ckb_cellscript_production_evidence.py \
286286

287287
The stateful section is intentionally stricter than a few happy-path flows:
288288
the current production scope requires 7 end-to-end business scenarios, 20
289-
stateful action-branch scenarios, 46 committed stateful steps, and 43/43
289+
stateful action-branch scenarios, 47 committed stateful steps, and 44/44
290290
production acceptance actions covered with no missing action IDs.

‎examples/nft.cell‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ resource NFT has store, create, consume, replace, burn, relock, read_ref {
2121
royalty_bps: u16,
2222
}
2323

24-
resource Collection has store, replace {
24+
resource Collection has store, create, replace {
2525
name: String,
2626
symbol: String,
2727
creator: Address,
@@ -52,6 +52,12 @@ receipt RoyaltyPayment has create {
5252
amount: u64,
5353
}
5454

55+
action create_collection(creator: Address, max_supply: u64, name: String, symbol: String, base_uri: String) -> collection: Collection
56+
where
57+
assert(max_supply > 0, "Max supply must be positive")
58+
assert(max_supply <= MAX_SUPPLY, "Max supply too high")
59+
create collection = Collection { name, symbol, creator, total_supply: 0, max_supply, base_uri } with_lock(creator)
60+
5561
action mint(collection_before: Collection, to: Address, metadata_hash: Hash) -> (collection_after: Collection, nft: NFT)
5662
where
5763
assert(collection_before.total_supply < collection_before.max_supply, "Max supply reached")

‎scripts/ckb_cellscript_acceptance.sh‎

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ NFT_TYPES_SOURCE = """resource NFT has store, create, consume, replace, burn, re
505505
royalty_bps: u16
506506
}
507507
508-
resource Collection has store, replace {
508+
resource Collection has store, create, replace {
509509
creator: Address
510510
total_supply: u64
511511
max_supply: u64
@@ -533,6 +533,18 @@ receipt RoyaltyPayment has create {
533533
"""
534534
535535
NFT_ACTION_SOURCES = {
536+
"create_collection": """
537+
action create_collection(creator: Address, max_supply: u64) -> collection: Collection
538+
where
539+
assert_invariant(max_supply > 0, "max supply must be positive")
540+
assert_invariant(max_supply <= 10000, "max supply too high")
541+
542+
create collection = Collection {
543+
creator: creator,
544+
total_supply: 0,
545+
max_supply: max_supply
546+
} with_lock(creator)
547+
""",
536548
"mint": """
537549
action mint(collection_before: Collection, to: Address, metadata_hash: Hash) -> (collection_after: Collection, nft: NFT)
538550
where
@@ -1460,6 +1472,7 @@ for action, source in LAUNCH_ACTION_SOURCES.items():
14601472
14611473
ORIGINAL_SCOPED_ACTIONS = {
14621474
"nft.cell": [
1475+
"create_collection",
14631476
"mint",
14641477
"transfer",
14651478
"create_listing",
@@ -1512,6 +1525,7 @@ ORIGINAL_SCOPED_LOCK_FAIL_CLOSED = {}
15121525
EXPECTED_SOURCE_ACTIONS = {
15131526
"token.cell": ["mint_with_authority", "transfer_token", "burn", "merge"],
15141527
"nft.cell": [
1528+
"create_collection",
15151529
"mint",
15161530
"transfer",
15171531
"create_listing",
@@ -2538,6 +2552,9 @@ def molecule_u32(value):
25382552
def molecule_bytes(data):
25392553
return molecule_u32(len(data)) + data
25402554
2555+
def molecule_string_witness(data):
2556+
return molecule_bytes(molecule_bytes(data))
2557+
25412558
def molecule_fixvec(items):
25422559
out = bytearray(molecule_u32(len(items)))
25432560
for item in items:
@@ -3920,7 +3937,36 @@ def build_nft_action_case(action_record, cellscript_lock, cellscript_type, desti
39203937
original_scoped = action_record.get("kind") == "original-scoped-action-strict"
39213938
flow_state = 0 if original_scoped else None
39223939
3923-
if action == "mint":
3940+
if action == "create_collection":
3941+
name = b"Acceptance Collection"
3942+
symbol = b"ACPT"
3943+
base_uri = b"ckb://cellscript/nft/"
3944+
max_supply = 200
3945+
valid_collection_payload = (
3946+
collection_molecule_data(current_owner, 0, max_supply, name, symbol, base_uri)
3947+
if original_scoped
3948+
else collection_data(current_owner, 0, max_supply)
3949+
)
3950+
malformed_collection_payload = (
3951+
collection_molecule_data(current_owner, 1, max_supply, name, symbol, base_uri)
3952+
if original_scoped
3953+
else collection_data(current_owner, 1, max_supply)
3954+
)
3955+
witness = (
3956+
entry_witness(current_owner, max_supply, molecule_string_witness(name), molecule_string_witness(symbol), molecule_string_witness(base_uri))
3957+
if original_scoped
3958+
else entry_witness(current_owner, max_supply)
3959+
)
3960+
initial = create_script_locked_cells(
3961+
"nft.create_collection",
3962+
[{"capacity": 1000 * 100_000_000, "lock": cellscript_lock, "type": None, "data": b""}],
3963+
cell_deps,
3964+
)
3965+
input_cell = initial["cells"][0]
3966+
outputs = [{"capacity": hex_u64(1000 * 100_000_000), "lock": cellscript_lock, "type": cellscript_type}]
3967+
valid_tx = transaction(input_cell, outputs, ["0x" + valid_collection_payload.hex()], cell_deps, [witness])
3968+
malformed_tx = transaction(input_cell, outputs, ["0x" + malformed_collection_payload.hex()], cell_deps, [witness])
3969+
elif action == "mint":
39243970
input_collection_payload = (
39253971
collection_molecule_data(current_owner, 10, 1000)
39263972
if original_scoped
@@ -5855,7 +5901,7 @@ def run_stateful_nft_listing_sale(always_success_dep):
58555901
scenario = "nft.mint-list-transfer-by-listing"
58565902
actions = {
58575903
name: deploy_stateful_action(action_record_by(nft_action_artifacts, name), always_success_dep)
5858-
for name in ("mint", "create_listing", "buy_from_listing")
5904+
for name in ("create_collection", "mint", "create_listing", "buy_from_listing")
58595905
}
58605906
collection_type = always_success_lock("0xc1")
58615907
nft_type = always_success_lock("0xc2")
@@ -5864,7 +5910,12 @@ def run_stateful_nft_listing_sale(always_success_dep):
58645910
seller = bytes([0x11]) * 32
58655911
buyer_lock = always_success_lock("0xc5")
58665912
buyer = decode_hex(script_hash(buyer_lock), 32)
5867-
royalty_recipient = seller
5913+
collection_creator = actions["mint"]["lock_hash"]
5914+
royalty_recipient = collection_creator
5915+
collection_name = b"Stateful Collection"
5916+
collection_symbol = b"SNFT"
5917+
collection_base_uri = b"ckb://cellscript/stateful-nft/"
5918+
max_supply = 200
58685919
metadata_hash = bytes([0x33]) * 32
58695920
token_id = 1
58705921
price = 10_000
@@ -5874,32 +5925,51 @@ def run_stateful_nft_listing_sale(always_success_dep):
58745925
steps = []
58755926
58765927
initial = create_script_locked_cells(
5877-
"stateful.nft.collection",
5928+
"stateful.nft.collection_seed",
58785929
[{
58795930
"capacity": 900 * 100_000_000,
5880-
"lock": actions["mint"]["lock"],
5881-
"type": collection_type,
5882-
"data": collection_molecule_data(seller, 0, 1000),
5931+
"lock": actions["create_collection"]["lock"],
5932+
"type": None,
5933+
"data": b"",
58835934
}],
5884-
actions["mint"]["cell_deps"],
5935+
actions["create_collection"]["cell_deps"],
58855936
)
5886-
collection0 = initial["cells"][0]
5937+
collection_seed = initial["cells"][0]
58875938
tx1 = transaction(
5939+
collection_seed,
5940+
[{"capacity": hex_u64(800 * 100_000_000), "lock": actions["mint"]["lock"], "type": collection_type}],
5941+
["0x" + collection_molecule_data(collection_creator, 0, max_supply, collection_name, collection_symbol, collection_base_uri).hex()],
5942+
actions["create_collection"]["cell_deps"],
5943+
[
5944+
entry_witness(
5945+
collection_creator,
5946+
max_supply,
5947+
molecule_string_witness(collection_name),
5948+
molecule_string_witness(collection_symbol),
5949+
molecule_string_witness(collection_base_uri),
5950+
)
5951+
],
5952+
)
5953+
step = run_stateful_step(scenario, "create_collection_for_live_mint", tx1, [collection_seed])
5954+
steps.append(step)
5955+
collection0 = output_cell_from_tx(step["commit"], tx1, 0)
5956+
5957+
tx2 = transaction(
58885958
collection0,
58895959
[
58905960
{"capacity": hex_u64(500 * 100_000_000), "lock": actions["mint"]["lock"], "type": collection_type},
58915961
{"capacity": hex_u64(300 * 100_000_000), "lock": actions["buy_from_listing"]["lock"], "type": nft_type},
58925962
],
58935963
[
5894-
"0x" + collection_molecule_data(seller, token_id, 1000).hex(),
5964+
"0x" + collection_molecule_data(collection_creator, token_id, max_supply, collection_name, collection_symbol, collection_base_uri).hex(),
58955965
"0x" + nft_data(token_id, seller, metadata_hash, royalty_recipient, 250).hex(),
58965966
],
58975967
actions["mint"]["cell_deps"],
58985968
[entry_witness(seller, metadata_hash)],
58995969
)
5900-
step = run_stateful_step(scenario, "mint_nft_for_listing_sale", tx1, [collection0])
5970+
step = run_stateful_step(scenario, "mint_nft_for_listing_sale", tx2, [collection0])
59015971
steps.append(step)
5902-
nft_for_sale = output_cell_from_tx(step["commit"], tx1, 1)
5972+
nft_for_sale = output_cell_from_tx(step["commit"], tx2, 1)
59035973
nft_dep = cell_dep_for(nft_for_sale)
59045974
59055975
listing_initial = create_script_locked_cells(
@@ -5908,7 +5978,7 @@ def run_stateful_nft_listing_sale(always_success_dep):
59085978
actions["create_listing"]["cell_deps"],
59095979
)
59105980
listing_input = listing_initial["cells"][0]
5911-
tx2 = transaction(
5981+
tx3 = transaction(
59125982
listing_input,
59135983
[
59145984
{"capacity": hex_u64(300 * 100_000_000), "lock": actions["buy_from_listing"]["lock"], "type": listing_type},
@@ -5921,11 +5991,11 @@ def run_stateful_nft_listing_sale(always_success_dep):
59215991
[nft_dep] + actions["create_listing"]["cell_deps"],
59225992
[entry_witness(price, created_at)],
59235993
)
5924-
step = run_stateful_step(scenario, "create_listing_from_live_nft_dep", tx2, [listing_input])
5994+
step = run_stateful_step(scenario, "create_listing_from_live_nft_dep", tx3, [listing_input])
59255995
steps.append(step)
5926-
listing = output_cell_from_tx(step["commit"], tx2, 0)
5996+
listing = output_cell_from_tx(step["commit"], tx3, 0)
59275997
5928-
tx3 = transaction(
5998+
tx4 = transaction(
59295999
[nft_for_sale, listing],
59306000
[
59316001
{"capacity": hex_u64(300 * 100_000_000), "lock": buyer_lock, "type": nft_type},
@@ -5940,7 +6010,7 @@ def run_stateful_nft_listing_sale(always_success_dep):
59406010
actions["buy_from_listing"]["cell_deps"],
59416011
[entry_witness(buyer, seller, price), "0x"],
59426012
)
5943-
step = run_stateful_step(scenario, "buy_listing_from_live_nft_and_listing", tx3, [nft_for_sale, listing])
6013+
step = run_stateful_step(scenario, "buy_listing_from_live_nft_and_listing", tx4, [nft_for_sale, listing])
59446014
steps.append(step)
59456015
59466016
return {
@@ -6561,6 +6631,7 @@ try:
65616631
"burn",
65626632
"buy_from_listing",
65636633
"cancel_listing",
6634+
"create_collection",
65646635
"create_listing",
65656636
"create_offer",
65666637
"mint",

‎scripts/validate_ckb_cellscript_production_evidence.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"v0_15_identity_lifecycle.cell",
4848
"v0_15_scoped_invariant.cell",
4949
]
50-
EXPECTED_ACTION_COUNT = 43
50+
EXPECTED_ACTION_COUNT = 44
5151
EXPECTED_STATUS = "passed"
5252
EXPECTED_MODE = "production"
5353
EXPECTED_LOCK_SPEND_MATRIX = {
@@ -76,6 +76,7 @@
7676
EXPECTED_ACTIONS_BY_RUN_KEY = {
7777
"token_action_runs": ["mint_with_authority", "transfer_token", "burn", "merge"],
7878
"nft_action_runs": [
79+
"create_collection",
7980
"mint",
8081
"transfer",
8182
"create_listing",

‎tests/backend_shape_baseline.json‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@
3737
},
3838
{
3939
"example": "nft.cell",
40-
"line_count": 13935,
41-
"text_size": 54380,
40+
"line_count": 14851,
41+
"text_size": 57812,
4242
"relaxed_branch_count": 1,
4343
"max_cond_branch_abs_distance": 7276,
44-
"machine_block_count": 2256,
44+
"machine_block_count": 2398,
4545
"max_machine_block_size": 276,
46-
"machine_cfg_edge_count": 3855,
47-
"machine_call_edge_count": 538,
48-
"unreachable_machine_block_count": 1841
46+
"machine_cfg_edge_count": 4078,
47+
"machine_call_edge_count": 557,
48+
"unreachable_machine_block_count": 2217
4949
},
5050
{
5151
"example": "timelock.cell",

0 commit comments

Comments
 (0)