@@ -5,17 +5,17 @@ use cellscript_ckb_adapter::{
55} ;
66use ckb_types:: {
77 bytes:: Bytes ,
8- core:: ScriptHashType ,
9- packed:: { CellInput , OutPoint } ,
8+ core:: { DepType , ScriptHashType } ,
9+ packed:: { CellDep , CellInput , OutPoint } ,
1010 prelude:: * ,
1111 H160 ,
1212} ;
13- use clap:: { Parser , Subcommand } ;
13+ use clap:: { Parser , Subcommand , ValueEnum } ;
1414use std:: path:: PathBuf ;
1515
1616#[ derive( Parser , Debug ) ]
1717#[ command( name = "cellscript-deploy" ) ]
18- #[ command( about = "CellScript CKB adapter CLI — deploy , act, and query on-chain state" ) ]
18+ #[ command( about = "CellScript CKB adapter CLI — build mainnet deployment transactions , act, and query on-chain state" ) ]
1919#[ command( version = env!( "CARGO_PKG_VERSION" ) ) ]
2020struct Cli {
2121 /// CKB node RPC URL
@@ -32,10 +32,11 @@ struct Cli {
3232
3333#[ derive( Subcommand , Debug ) ]
3434enum Commands {
35- /// Deploy a compiled artifact as an on-chain code cell with TYPE_ID
35+ /// Refuse unsigned submission and direct callers to the external-signing flow
36+ #[ command( hide = true ) ]
3637 Deploy ( DeployArgs ) ,
3738
38- /// Build a headless deploy transaction without submitting
39+ /// Build a mainnet unsigned deploy transaction for external signing
3940 BuildDeploy ( BuildDeployArgs ) ,
4041
4142 /// Build a transaction from an action plan
@@ -49,8 +50,8 @@ enum Commands {
4950}
5051
5152#[ derive( clap:: Args , Debug ) ]
52- struct DeployArgs {
53- /// Artifact binary file path (.s or .cell)
53+ struct DeploySpecArgs {
54+ /// Compiled RISC-V ELF artifact path
5455 #[ arg( long) ]
5556 artifact : PathBuf ,
5657
@@ -63,16 +64,30 @@ struct DeployArgs {
6364 name : String ,
6465
6566 /// Fee in shannons
66- #[ arg( long, default_value_t = 1_000 ) ]
67+ #[ arg( long, default_value_t = 10_000 ) ]
6768 fee : u64 ,
6869
6970 /// Capacity input out_point (format: 0x<TX_HASH>:<INDEX>)
7071 #[ arg( long) ]
7172 capacity_out_point : String ,
7273
73- /// Capacity input shannons
74- #[ arg( long, default_value_t = 200_000_000_000 ) ]
75- capacity_shannons : u64 ,
74+ /// Code reference hash type: type creates a TYPE_ID cell; data variants create an immutable data cell
75+ #[ arg( long, value_enum, default_value_t = DeploymentHashType :: Type ) ]
76+ hash_type : DeploymentHashType ,
77+
78+ /// CellDep out_point for the input lock script (format: 0x<TX_HASH>:<INDEX>)
79+ #[ arg( long, default_value = "0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c:0" ) ]
80+ lock_cell_dep_out_point : String ,
81+
82+ /// CellDep kind for the input lock script
83+ #[ arg( long, value_enum, default_value_t = CliDepType :: DepGroup ) ]
84+ lock_cell_dep_type : CliDepType ,
85+ }
86+
87+ #[ derive( clap:: Args , Debug ) ]
88+ struct DeployArgs {
89+ #[ command( flatten) ]
90+ spec : DeploySpecArgs ,
7691
7792 /// Max attempts to wait for commitment
7893 #[ arg( long, default_value_t = 30 ) ]
@@ -89,29 +104,42 @@ struct DeployArgs {
89104
90105#[ derive( clap:: Args , Debug ) ]
91106struct BuildDeployArgs {
92- /// Artifact binary file path
93- #[ arg( long) ]
94- artifact : PathBuf ,
95-
96- /// Deployer lock script args (hex, 20 bytes for secp256k1-sighash)
97- #[ arg( long) ]
98- lock_arg : String ,
107+ #[ command( flatten) ]
108+ spec : DeploySpecArgs ,
109+ }
99110
100- /// Name for the deployment
101- #[ arg( long, default_value = "cellscript-contract" ) ]
102- name : String ,
111+ #[ derive( Clone , Copy , Debug , Eq , PartialEq , ValueEnum ) ]
112+ enum DeploymentHashType {
113+ Type ,
114+ Data ,
115+ Data1 ,
116+ Data2 ,
117+ }
103118
104- /// Fee in shannons
105- #[ arg( long, default_value_t = 1_000 ) ]
106- fee : u64 ,
119+ impl From < DeploymentHashType > for ScriptHashType {
120+ fn from ( value : DeploymentHashType ) -> Self {
121+ match value {
122+ DeploymentHashType :: Type => Self :: Type ,
123+ DeploymentHashType :: Data => Self :: Data ,
124+ DeploymentHashType :: Data1 => Self :: Data1 ,
125+ DeploymentHashType :: Data2 => Self :: Data2 ,
126+ }
127+ }
128+ }
107129
108- /// Capacity input out_point (format: 0x<TX_HASH>:<INDEX>)
109- #[ arg( long) ]
110- capacity_out_point : String ,
130+ #[ derive( Clone , Copy , Debug , Eq , PartialEq , ValueEnum ) ]
131+ enum CliDepType {
132+ Code ,
133+ DepGroup ,
134+ }
111135
112- /// Capacity input shannons
113- #[ arg( long, default_value_t = 200_000_000_000 ) ]
114- capacity_shannons : u64 ,
136+ impl From < CliDepType > for DepType {
137+ fn from ( value : CliDepType ) -> Self {
138+ match value {
139+ CliDepType :: Code => Self :: Code ,
140+ CliDepType :: DepGroup => Self :: DepGroup ,
141+ }
142+ }
115143}
116144
117145#[ derive( clap:: Args , Debug ) ]
@@ -173,20 +201,13 @@ fn parse_lock_arg(s: &str) -> Result<H160> {
173201}
174202
175203/// Shared spec builder for deploy and build-deploy.
176- fn build_deploy_spec (
177- artifact : PathBuf ,
178- lock_arg : String ,
179- name : String ,
180- fee : u64 ,
181- capacity_out_point : String ,
182- capacity_shannons : u64 ,
183- ) -> Result < DeployArtifactSpec > {
184- let artifact_binary = std:: fs:: read ( & artifact) ?;
204+ fn build_deploy_spec ( adapter : & CellScriptAdapter , args : DeploySpecArgs ) -> Result < DeployArtifactSpec > {
205+ let artifact_binary = std:: fs:: read ( & args. artifact ) ?;
185206 let artifact_binary = Bytes :: from ( artifact_binary) ;
186207 let artifact_hash = ckb_hash:: blake2b_256 ( & artifact_binary) . iter ( ) . map ( |b| format ! ( "{:02x}" , b) ) . collect :: < String > ( ) ;
187208
188- let lock_arg = parse_lock_arg ( & lock_arg) ?;
189- // Construct secp256k1-sighash lock script (code_hash for mainnet/devnet) .
209+ let lock_arg = parse_lock_arg ( & args . lock_arg ) ?;
210+ // Construct the mainnet secp256k1-sighash lock script.
190211 let lock_script = cellscript_ckb_adapter:: construct_script ( & cellscript_ckb_adapter:: ScriptSpec :: new (
191212 [
192213 0x9b , 0x81 , 0x97 , 0x34 , 0x7e , 0x6e , 0x47 , 0x1d , 0x7e , 0xa2 , 0x8b , 0x52 , 0x0c , 0x45 , 0x3e , 0x18 , 0x54 , 0xf0 , 0x96 , 0x2e ,
@@ -196,97 +217,65 @@ fn build_deploy_spec(
196217 lock_arg. as_bytes ( ) . to_vec ( ) ,
197218 ) ) ;
198219
199- let capacity_out_point = parse_out_point ( & capacity_out_point) ?;
220+ let capacity_out_point = parse_out_point ( & args. capacity_out_point ) ?;
221+ let ( capacity_input_shannons, capacity_input_data) = adapter. resolve_pure_capacity_input ( & capacity_out_point, & lock_script) ?;
200222 let capacity_input = CellInput :: new_builder ( ) . previous_output ( capacity_out_point) . build ( ) ;
223+ let lock_cell_dep = CellDep :: new_builder ( )
224+ . out_point ( parse_out_point ( & args. lock_cell_dep_out_point ) ?)
225+ . dep_type ( DepType :: from ( args. lock_cell_dep_type ) )
226+ . build ( ) ;
201227
202228 Ok ( DeployArtifactSpec {
203- name,
229+ name : args . name ,
204230 artifact_binary,
205231 artifact_hash,
206232 deployer_lock : lock_script,
207233 capacity_input,
208- capacity_input_shannons : capacity_shannons ,
209- capacity_input_data : Bytes :: new ( ) ,
210- type_id_hash_type : ScriptHashType :: Type ,
234+ capacity_input_shannons,
235+ capacity_input_data,
236+ type_id_hash_type : args . hash_type . into ( ) ,
211237 type_script : None ,
212- cell_deps : Vec :: new ( ) ,
238+ cell_deps : vec ! [ lock_cell_dep ] ,
213239 header_deps : Vec :: new ( ) ,
214- fee_shannons : fee,
240+ fee_shannons : args . fee ,
215241 } )
216242}
217243
218- fn cmd_deploy ( rpc : & str , json : bool , args : DeployArgs ) -> Result < ( ) > {
219- let spec = build_deploy_spec ( args. artifact , args. lock_arg , args. name , args. fee , args. capacity_out_point , args. capacity_shannons ) ?;
220- let name = spec. name . clone ( ) ;
221-
222- let ( tx, deploy_evidence) = build_deploy_transaction ( & spec) ?;
223-
224- // Connect and submit.
225- let adapter = CellScriptAdapter :: connect ( rpc) ?;
226-
227- // Estimate cycles.
228- let estimate_cycles = adapter. estimate_cycles ( & tx) . ok ( ) . map ( |e| e. cycles . value ( ) ) ;
229-
230- // Test tx-pool acceptance.
231- let tx_pool_accepted = adapter. test_tx_pool_accept ( & tx) . is_ok ( ) ;
232-
233- // Submit.
234- let tx_hash = adapter. submit_transaction ( & tx) ?;
235- eprintln ! ( "submitted tx: 0x{}" , hex:: encode( tx_hash. as_bytes( ) ) ) ;
236-
237- // Wait for commitment.
238- let committed = adapter. wait_for_commitment ( & tx_hash, args. wait_attempts , args. wait_delay_ms ) ?;
239-
240- // Build manifest.
241- let mut hash_bytes = [ 0u8 ; 32 ] ;
242- hash_bytes. copy_from_slice ( tx_hash. as_bytes ( ) ) ;
243- let manifest = cellscript_ckb_adapter:: build_deployment_manifest_from_evidence ( & deploy_evidence, & hash_bytes, 0 ) ;
244-
245- // Write manifest if requested.
246- if let Some ( ref path) = args. manifest_out {
247- let manifest_json = serde_json:: to_string_pretty ( & manifest) ?;
248- std:: fs:: write ( path, & manifest_json) ?;
249- eprintln ! ( "manifest written to {}" , path. display( ) ) ;
250- }
251-
252- if json {
253- let output = serde_json:: json!( {
254- "tx_hash" : format!( "0x{}" , hex:: encode( tx_hash. as_bytes( ) ) ) ,
255- "committed" : true ,
256- "block_hash" : format!( "0x{}" , hex:: encode( committed. block_hash. as_bytes( ) ) ) ,
257- "estimate_cycles" : estimate_cycles,
258- "tx_pool_accepted" : tx_pool_accepted,
259- "manifest" : manifest,
260- } ) ;
261- println ! ( "{}" , serde_json:: to_string_pretty( & output) ?) ;
262- } else {
263- println ! ( "deployed {} at tx 0x{}" , name, hex:: encode( tx_hash. as_bytes( ) ) ) ;
264- println ! ( " committed in block 0x{}" , hex:: encode( committed. block_hash. as_bytes( ) ) ) ;
265- if let Some ( cycles) = estimate_cycles {
266- println ! ( " estimate_cycles: {cycles}" ) ;
267- }
268- }
269-
270- Ok ( ( ) )
244+ fn cmd_deploy ( _rpc : & str , _json : bool , args : DeployArgs ) -> Result < ( ) > {
245+ let _ = ( args. spec , args. wait_attempts , args. wait_delay_ms , args. manifest_out ) ;
246+ bail ! (
247+ "direct deploy is disabled because this CLI does not hold or invoke a signer; use build-deploy, sign the returned transaction with a CKB wallet, then broadcast it"
248+ )
271249}
272250
273251fn cmd_build_deploy ( rpc : & str , json : bool , args : BuildDeployArgs ) -> Result < ( ) > {
274- let spec = build_deploy_spec ( args. artifact , args. lock_arg , args. name , args. fee , args. capacity_out_point , args. capacity_shannons ) ?;
252+ let adapter = CellScriptAdapter :: connect ( rpc) ?;
253+ adapter. require_mainnet ( ) ?;
254+ let spec = build_deploy_spec ( & adapter, args. spec ) ?;
275255
276- let ( tx, _evidence ) = build_deploy_transaction ( & spec) ?;
256+ let ( tx, evidence ) = build_deploy_transaction ( & spec) ?;
277257
278- // Try to estimate cycles if node is available.
279- let estimate = CellScriptAdapter :: connect ( rpc) . ok ( ) . and_then ( |a| a. estimate_cycles ( & tx) . ok ( ) ) . map ( |e| e. cycles . value ( ) ) ;
258+ // An unsigned secp transaction is expected to fail script verification, so
259+ // cycle estimation remains informational until the external signer fills it.
260+ let estimate = adapter. estimate_cycles ( & tx) . ok ( ) . map ( |e| e. cycles . value ( ) ) ;
280261
281262 if json {
282263 let tx_json = serde_json:: to_value ( cellscript_ckb_adapter:: to_rpc_transaction ( & tx) ) ?;
283264 let output = serde_json:: json!( {
265+ "can_submit" : false ,
266+ "signing_required" : true ,
284267 "transaction" : tx_json,
285268 "estimate_cycles" : estimate,
269+ "evidence" : evidence,
286270 } ) ;
287271 println ! ( "{}" , serde_json:: to_string_pretty( & output) ?) ;
288272 } else {
289- println ! ( "built deploy transaction ({} bytes)" , tx. data( ) . serialized_size_in_block( ) ) ;
273+ println ! ( "built unsigned deploy transaction ({} bytes)" , tx. data( ) . serialized_size_in_block( ) ) ;
274+ println ! ( " can_submit: false" ) ;
275+ println ! ( " signing_required: true" ) ;
276+ println ! ( " hash_type: {}" , evidence. hash_type) ;
277+ println ! ( " code_hash: 0x{}" , hex:: encode( & evidence. code_hash) ) ;
278+ println ! ( " cell_deps: {}" , evidence. cell_deps) ;
290279 if let Some ( cycles) = estimate {
291280 println ! ( " estimate_cycles: {cycles}" ) ;
292281 }
@@ -407,3 +396,64 @@ fn cmd_info(rpc: &str, json: bool) -> Result<()> {
407396
408397 Ok ( ( ) )
409398}
399+
400+ #[ cfg( test) ]
401+ mod tests {
402+ use super :: * ;
403+
404+ const INPUT : & str = "0x1111111111111111111111111111111111111111111111111111111111111111:0" ;
405+
406+ fn parse_build_deploy ( extra : & [ & str ] ) -> Cli {
407+ let mut args = vec ! [
408+ "cellscript-deploy" ,
409+ "build-deploy" ,
410+ "--artifact" ,
411+ "registry-type-script" ,
412+ "--lock-arg" ,
413+ "0x2222222222222222222222222222222222222222" ,
414+ "--capacity-out-point" ,
415+ INPUT ,
416+ ] ;
417+ args. extend_from_slice ( extra) ;
418+ Cli :: try_parse_from ( args) . unwrap ( )
419+ }
420+
421+ #[ test]
422+ fn build_deploy_accepts_immutable_data1 ( ) {
423+ let cli = parse_build_deploy ( & [ "--hash-type" , "data1" ] ) ;
424+ let Commands :: BuildDeploy ( args) = cli. command else {
425+ panic ! ( "expected build-deploy command" ) ;
426+ } ;
427+ assert_eq ! ( args. spec. hash_type, DeploymentHashType :: Data1 ) ;
428+ assert_eq ! ( args. spec. fee, 10_000 ) ;
429+ assert_eq ! ( args. spec. lock_cell_dep_type, CliDepType :: DepGroup ) ;
430+ assert_eq ! ( args. spec. lock_cell_dep_out_point, "0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c:0" ) ;
431+ }
432+
433+ #[ test]
434+ fn build_deploy_defaults_to_type_id ( ) {
435+ let cli = parse_build_deploy ( & [ ] ) ;
436+ let Commands :: BuildDeploy ( args) = cli. command else {
437+ panic ! ( "expected build-deploy command" ) ;
438+ } ;
439+ assert_eq ! ( args. spec. hash_type, DeploymentHashType :: Type ) ;
440+ }
441+
442+ #[ test]
443+ fn build_deploy_rejects_unknown_hash_type ( ) {
444+ let error = Cli :: try_parse_from ( [
445+ "cellscript-deploy" ,
446+ "build-deploy" ,
447+ "--artifact" ,
448+ "registry-type-script" ,
449+ "--lock-arg" ,
450+ "0x2222222222222222222222222222222222222222" ,
451+ "--capacity-out-point" ,
452+ INPUT ,
453+ "--hash-type" ,
454+ "data3" ,
455+ ] )
456+ . unwrap_err ( ) ;
457+ assert ! ( error. to_string( ) . contains( "invalid value 'data3'" ) ) ;
458+ }
459+ }
0 commit comments