feat: state-init command#560
Conversation
0925efe to
b0260a4
Compare
There was a problem hiding this comment.
- Can we also add support for adding more actions after StateInit? On it's own StateInit is not that useful, the most interesting stuff is when you combine it in one receipt
near contract state-init ... [[add-action...] skip] sign-as ...
- It would be nice if, in interactive mode, after
state-init use-global-account-id global.near data-json { ... }I can see the derived0s...address, because further function calls might require to specify it in args
What do you think?
vsavchyn-dev
left a comment
There was a problem hiding this comment.
LGTM! Thanks for contributing!
|
@mitinarseny any other suggestions or things that are needed to be changed? Pics are just for reference that it works. |
vsavchyn-dev
left a comment
There was a problem hiding this comment.
From what I see it should work as intended, but I just have one question about Inspect for state-init - not sure if it should be there.
@frol - any other suggestions/remarks?
| Parser::parse_from(cmd) | ||
| // Backward compat: old `construct-transaction <sender> <receiver>` syntax | ||
| // Insert "account-id" subcommand before the bare receiver account ID. | ||
| if let Some(rewritten) = try_rewrite_construct_transaction_args() { |
There was a problem hiding this comment.
I am curious if this parsing of new construct-transaction will collide with near-cli-js commands that we try to parse?
From what I see in near-cli-js it shouldn't, but maybe someone can spot an issue
|
@vsavchyn-dev Thanks for pining. No additional comments from my side. Thank you for the empathy to the CLI users and aligning the messages / naming / outputs so they look consistent! |
|
@pityjllk it might make sense to also add Now I'm thinking if |
|
i think the argument was that since borsh is natvie serialization used at the protocol level then it feels right to support it, it might be useful for debugging etc. also effort has been made already so i would keep it .... |
6f0e5cb to
a81302b
Compare
mitinarseny
left a comment
There was a problem hiding this comment.
few more comments on JSON part
| println!( | ||
| "{}", | ||
| serde_json::to_string_pretty(&previous_context.state_init).map_err( | ||
| |e| color_eyre::eyre::eyre!("Failed to serialize state-init to JSON: {e}") | ||
| )? | ||
| ); |
There was a problem hiding this comment.
Maybe only prettify if stdout is tty?
There was a problem hiding this comment.
actually in the whole repo to_string_pretty is used, so it would be insconsisted, so ideally that could be addressed in separate pr?
| #[strum_discriminants(strum( | ||
| message = "from-borsh-base64 - Provide borsh serialized base64 encoded state init" | ||
| ))] | ||
| FromBorshBase64(StateInitFromBorshBase64), | ||
| #[strum_discriminants(strum( | ||
| message = "from-borsh-file - Read borsh-serialized state init from a file" | ||
| ))] | ||
| FromBorshFile(StateInitFromBorshFile), |
There was a problem hiding this comment.
Maybe also add from-json[-file] for the sake of roundtrip with near contract state-init ... inspect json?
And same for near transaction construct-transaction receiver.near state-init from-json[-file]?
mitinarseny
left a comment
There was a problem hiding this comment.
I hope these are final nits from me, sorry 🙃
| #[strum_discriminants(strum( | ||
| message = "from-borsh-base64 - Provide borsh serialized base64 encoded state init" | ||
| ))] | ||
| FromBorshBase64(StateInitFromBorshBase64), | ||
| #[strum_discriminants(strum( | ||
| message = "from-borsh-file - Read borsh-serialized state init from a file" | ||
| ))] | ||
| FromBorshFile(StateInitFromBorshFile), | ||
| #[strum_discriminants(strum( | ||
| message = "from-json - Provide JSON-serialized state init inline" | ||
| ))] | ||
| FromJson(StateInitFromJson), | ||
| #[strum_discriminants(strum( | ||
| message = "from-json-file - Read JSON-serialized state init from a file" | ||
| ))] | ||
| FromJsonFile(StateInitFromJsonFile), |
There was a problem hiding this comment.
Maybe switch places and put json-related first, since they would be more frequently used in the interactive mode?
| #[strum_discriminants(strum(message = "borsh - Borsh-serialized base64"))] | ||
| /// Borsh-serialized base64 | ||
| Borsh(InspectStateInitBorsh), | ||
| #[strum_discriminants(strum(message = "json - JSON-serialized"))] | ||
| /// JSON-serialized | ||
| Json(InspectStateInitJson), |
There was a problem hiding this comment.
Also here: put json first?
|
Note: before merging it we need to upgrade |
| if deterministic_account_state_init_v1.data.is_empty() { | ||
| ret.push_str(&format!("\n{:>31} {:<13} {{}}", "", "data:")); | ||
| } else { | ||
| ret.push_str(&format!("\n{:>31} {:<13} {{", "", "data:")); | ||
| let last_idx = deterministic_account_state_init_v1.data.len() - 1; | ||
| for (i, (key, value)) in deterministic_account_state_init_v1.data.iter().enumerate() { | ||
| let comma = if i < last_idx { "," } else { "" }; | ||
| ret.push_str(&format!( | ||
| "\n{:>45} \"{}\" : \"{}\"{}", | ||
| "", near_primitives::serialize::to_base64(key), near_primitives::serialize::to_base64(value), comma, | ||
| )); | ||
| } | ||
| ret.push_str(&format!("\n{:>31} {:<13} }}", "", "")); | ||
| } | ||
| ret.push_str(&format!("\n{:>31} {:<13} {}", "", "code:", match deterministic_account_state_init_v1.code { | ||
| GlobalContractIdentifier::CodeHash(hash) => { | ||
| format!("use global <{hash}> code to deploy from") | ||
| format!("hash: {hash}") | ||
| } | ||
| GlobalContractIdentifier::AccountId(ref account_id) => { | ||
| format!("use global <{account_id}> code to deploy from") | ||
| format!("account ref: {account_id}") |
There was a problem hiding this comment.
Since we've standardized JSON representation of StateInit, then maybe print it as a JSON instead of writing custom human-readable serialization?
There was a problem hiding this comment.
│ -- create deterministic account <0s2de1179d14651ce32c16bca892d2e804057b6fcb>:
│ deposit : 0 NEAR
│ state-init : {
│ "V1": {
│ "code": {
│ "AccountId": "0s29e346108955b88c2d180a4ba17662b1f2cc1028"
│ },
│ "data": {
│ "": "GAAAAGludGVudHMuc3B1dG5pay1kYW8ubmVhcgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAs4NZ2fFCre1avLgVH/oTQj8p5Mx4pXhGj5Xov8nIgpDI="
│ }
│ }
│ }
│
There was a problem hiding this comment.
how about printing whole state init as json?
There was a problem hiding this comment.
Yes, that's exactly what I was meant, nice!
mitinarseny
left a comment
There was a problem hiding this comment.
Thanks @pityjllk, LGTM!
vsavchyn-dev
left a comment
There was a problem hiding this comment.
Stamp, thanks for the new command and hard work!
|
@pityjllk Thank you for your contribution! Your pull request is now a part of the Race of Sloths! Current status: executed
Your contribution is much appreciated with a final score of 13! @vsavchyn-dev received 25 Sloth Points for reviewing and scoring this pull request. Congratulations @pityjllk! Your PR was highly scored and you completed another monthly streak! To keep your monthly streak make another pull request next month and get 8+ score for it What is the Race of SlothsRace of Sloths is a friendly competition where you can participate in challenges and compete with other open-source contributors within your normal workflow For contributors:
For maintainers:
Feel free to check our website for additional details! Bot commands
|
|
@race-of-sloths score 13 |


Add StateInit support for deterministic account creation (NEP-616)
This PR adds support for creating deterministic accounts via DeterministicAccountStateInit as specified in https://github.com/near/NEPs/blob/master/neps/nep-0616.md.
New command: contract state-init
Creates a deterministic account by providing a state init. The state init can be specified in three ways:
For the first two modes, the initialization data (a key-value map with base64-encoded keys and values) is provided separately:
The receiver account ID is automatically derived from the state init contents (deterministic keccak256-based address with 0s prefix).
Changes to transaction construct-transaction
The receiver is now selected via a ReceiverMode enum:
This was achieved by splitting the old ConstructTransactionContext into two stages: ConstructTransactionSenderContext (sender only) → ConstructTransactionContext (sender + receiver +
actions). The receiver selection step sits between the two.
Example usage
Create a deterministic account and deploy via state-init:
Call a function on a deterministic account (receiver specified directly):
The account-id keyword is optional — the old positional syntax still works:
Backward compatibility
The old CLI syntax (construct-transaction add-action ...) continues to work — it's treated as the
account-id receiver variant. Snapshot tests verify that both old and new syntaxes produce identical transactions (same hash,
same serialized bytes).