Bytecraft and Sylvia#



Bytecraft is a WASM smart contract development scaffold, similar to hardhat, the EVM development tool. It can help developers quickly create WASM contract development templates, speed up contract development, deployment and testing.


Environment installation#

Install the rust environment, Node 16 and Node Package Manager (npm).

Note: Node 16 is required here. If the version is different, you can use n or npv to switch the Node version.

// Install the rust
rustup default stable

// Add support for WASM compilation
rustup target add wasm32-unknown-unknown

// Install the necessary dependent libraries to generate contracts.
cargo install cargo-run-script

Install bytecraft#

// Install  bytecraft
npm install -g @okexchain/bytecraft

Create a new project#

// Create a new folder and open the terminal.
mkdir bytecraftTest

// Go to the specified directory
cd bytecraftTest

// Create a new project
bytecraft new my-wasm-dapp

// go to the specified directory
cd my-wasm-dapp

The contract directory is as follows:

├─ .gitignore
├─ config.json // Configure network connections and contract deployment related
├─ contracts  // Smart contract directory
│  └─ my-wasm-dapp  // Smart contract template
│     ├─ .cargo
│     │  └─ config
│     ├─ Cargo.lock
│     ├─ Cargo.toml  //Configuration file for rust contract
│     ├─ examples
│     │  └─  // Used to generate schema directory
│     ├─ interfaces // interfaces directory
│     │  └─ cw1
│     │     ├─ Cargo.toml
│     │     └─ src
│     │        └─
│     └─ src   //  Contract code directory
│        ├─
│        ├─
│        ├─
│        ├─
│        ├─
│        ├─
│        └─
├─ keys.js  // Private key saved file
├─ lib  // Pre-defined task and console related functions
│  └─ index.ts
├─ package-lock.json
├─ package.json
├─ refs.json  // Deployed smart contracts and related references
└─ tasks  //Pre-defined tasks
└─ template.ts

Compile contract#

Similar to Solidity, Rust is a statically typed language and requires compilation to determine variable types.

$ bytecraft contract:build [CONTRACT] [--config-path <value>]

--config-path=<value>  [default: ./config.json]

Build wasm bytecode.

$ bytecraft contract:build my-wasm-dapp

Optimize contract#

Wasm contracts have optimization options. Because the wasm virtual machine supports uploading contract files up to 800K at most. If it exceeds this limit, it will fail. Bytecraft has optimization functions to help optimize the size of uploaded contract files.

note:The optimization operation requires opening Docker. Essentially, the original wasm file is taken into Docker for optimized compilation.

$ bytecraft contract:optimize [CONTRACT] [--config-path <value>]

--config-path=<value>  [default: ./config.json]

Optimize wasm bytecode.

$ bytecraft contract:optimize my-wasm-dapp

Upload contract#

Wasm contracts need to upload the contract binary file before deployment, which consumes gas. After uploading the contract, a codeId will be generated. This Id is an accumulated id across the network. If there are contract functions with the same requirements, you can deploy and reuse directly according to the codeId.

$ bytecraft contract:store [CONTRACT] [--signer <value>] [--network <value>] [--no-rebuild] [--config-path
<value>] [--refs-path <value>] [--keys-path <value>]

--config-path=<value>  [default: ./config.json]
--keys-path=<value>    [default: ./keys.js]
--network=<value>      [default: localnet] network to deploy to from config.json
--no-rebuild           deploy the wasm bytecode as is.
--refs-path=<value>    [default: ./refs.json]
--signer=<value>       [default: test]

Store code on chain.

$ bytecraft contract:store my-wasm-dapp

Deploy contract#

$ bytecraft contract:instantiate [CONTRACT] [--signer <value>] [--network <value>] [--instance-id <value>] [--code-id
<value>] [--config-path <value>] [--refs-path <value>] [--keys-path <value>]

--code-id=<value>      specific codeId to instantiate
--config-path=<value>  [default: ./config.json]
--instance-id=<value>  [default: default]
--keys-path=<value>    [default: ./keys.js]
--network=<value>      [default: localnet] network to deploy to from config.json
--refs-path=<value>    [default: ./refs.json]
--signer=<value>       [default: test]

Instantiate the contract.

//for example
bytecraft contract:instantiate my-wasm-dapp

If a single contract is deployed, all operations (compilation, optimization, uploading and deployment) can be performed with one command.

$ bytecraft deploy [CONTRACT] [--signer <value>] [--network <value>] [--no-rebuild] [--instance-id
<value>] [--admin-address <value>] [--config-path <value>] [--refs-path <value>] [--keys-path <value>]

--admin-address=<value>  set custom address as contract admin to allow migration.
--config-path=<value>    [default: ./config.json]
--instance-id=<value>    [default: default] enable management of multiple instances of the same contract
--keys-path=<value>      [default: ./keys.js]
--network=<value>        [default: localnet] network to deploy to from config.json
--no-rebuild             deploy the wasm bytecode as is.
--refs-path=<value>      [default: ./refs.json]
--signer=<value>         [default: test]

Build wasm bytecode, store code on chain and instantiate.

$ bytecraft deploy my-wasm-dapp --signer test --network testnet

Custom script deployment#

The above deployment method is very convenient when using a single contract, but when encountering multiple contracts, it is still troublesome to upload and deploy at one time. Bytecraft has task functionality that can execute custom scripts.

// create task
$ bytecraft task:new [TASK]

create new task

//run predefined task
$ bytecraft task:run [TASK] [--signer <value>] [--network <value>] [--config-path <value>] [--refs-path
<value>] [--keys-path <value>]

--config-path=<value>  [default: config.json]
--keys-path=<value>    [default: keys.js]
--network=<value>      [default: localnet]
--refs-path=<value>    [default: refs.json]
--signer=<value>       [default: test]

run predefined task



Sylvia builds a contract development framework on this basis and encapsulates some functions and macros. It can help contract developers quickly develop contracts.

The advantages of sylvia:

  • Can perform a batch of complex operations, such as automatically converting variable definitions to snake case naming, serialization and deserialization, simplifying the user experience.
  • Implements the dispatch function, omitting the definition of the entry function, automatically matching the function you need to call for you.
  • Added interfaces to reduce redundant code references


Build environment#

//Add reference packages. If it fails, you can switch to git installation (add sylvia = {git = ""} in cargo.toml)
$ cargo add sylvia@0.3.1

//Install the Sylvia dependencies
$ cargo add sylvia-derive@0.3.1

// Pull the dependent packages in the configuration file
$ cargo build

Functional introduction#

  • Macro command overview
Macro commandDescription
#[contract]Define contract
#[msg(instantiate)]Define initialization function
#[msg(exec)]Define executable function
#[msg(query)]Define query function
#[interface]Define contract interface
#[error(_)]Define Custom error
#[messages(cw1 as Cw1)]Reuse implemented contracts
#[msg(migrate)]Migrate contract
  • Example 1: Initialize contract
take initializing the contract as an example

// Official contract initialization
pub fn instantiate(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: Empty,

// Using sylvia
#[contract]       //The first macro definition initializes the contract state.
impl AdminContract {
#[msg(instantiate)]     //The second macro definition generates a large number of template files about initializing contract message generation and sending.
pub fn instantiate(&self,
_ctx: (DepsMut, Env, MessageInfo)
) -> StdResult<Response> {
  • Example 2: Execute function
Taking the transfer function of a 20 token contract as an example

// origin
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::Transfer { recipient, amount } => {
execute_transfer(deps, env, info, recipient, amount)
ExecuteMsg::Burn { amount } => execute_burn(deps, env, info, amount),
ExecuteMsg::Send {
} => execute_send(deps, env, info, contract, amount, msg),
ExecuteMsg::Mint { recipient, amount } => execute_mint(deps, env, info, recipient, amount),

// After importing the sylvia library, there is no entry function to match the selection. The function property is defined as an executable function directly using the macro #[msg(exec)]
    fn transfer(
        ctx: ExecCtx,
        recipient: String,
        amount: Uint128,
    ) -> Result<Response, ContractError> {
            amount != Uint128::zero(),
            ContractError::InvalidZeroAmount {}

        let rcpt_addr = ctx.deps.api.addr_validate(&recipient)?;

            .update(, &, |balance| {
                Ok::<_, StdError>(balance.unwrap_or_default().checked_sub(amount)?)
            .update(, &rcpt_addr, |balance| {
                Ok::<_, StdError>(balance.unwrap_or_default().checked_add(amount)?)

        let res = Response::new()
            .add_attribute("action", "transfer")
            .add_attribute("to", recipient)
            .add_attribute("amount", amount);

bytecraft and sylvia combined example#

(A whitelist sample contract using the Sylvia programming framework and bytecraft’s compilation, deployment and task testing)