Skip to main content

Lyquid Development Kit (LDK)

Lyquid is designed to be as close to "vanilla" Rust as possible. There's no special DSL to learn. Instead, you develop your smart contracts in plain Rust, compile them to WebAssembly (WASM) with our special code generation and context, and then deploy them on the Lyquor platform. All you need to master are two simple macros that integrate your code with Lyquid's execution model:

  • lyquid::state!
    This macro is used in your crate root (typically src/lib.rs) to define persistent state variables. It distinguishes between:

    • Network State: global, consensus-driven storage shared by all nodes.
    • Instance State: local, node-specific storage for off-chain or private data.
  • lyquid::method!
    This macro is used throughout your project to define callable functions from Lyquor platform. It supports three categories of methods:

    • Network Methods: Deterministic functions that modify network state and are executed across all nodes that host the same Lyquid.
    • Instance Methods: Functions that work with instance state and can only read network state.
    • UPC Methods: A specialized form of instance method that enables decentralized, off-chain coordination (UPC functions are defined within the same macro).

Your Lyquid project looks and feels like any other Rust WASM project, with the added benefit that the provided macros abstract away low-level storage and execution details. When compiled, your project is packaged into a format that the Lyquor platform can deploy and execute as a decentralized service.

For example, a basic "Hello World" Lyquid might look like this:

#![feature(allocator_api)]
use lyquid::runtime::*;

lyquid::state! {
network greeting: network::String = network::new_string();
network greet_count: u64 = 0;
// Off-chain (instance) state: maintained locally on each node.
instance per_user_count: instance::HashMap<Address, u64> = instance::new_hashmap();
}

lyquid::method! {
// Constructor: invoked atomically during deployment.
constructor(&mut ctx, greeting: String) {
*ctx.network.greeting = greeting.into();
}

// Network function: updates the global greeting.
network fn set_greeting(&mut ctx, greeting: String) -> LyquidResult<bool> {
*ctx.network.greeting = greeting.into();
Ok(true)
}

// Network function: increments the global greeting counter.
network fn greet(&mut ctx) -> LyquidResult<bool> {
*ctx.network.greet_count += 1;
Ok(true)
}

// Network (view) function: returns a formatted greeting message.
network fn get_greeting_message(&ctx) -> LyquidResult<String> {
Ok(format!("{} I've greeted {} times to on-chain users",
ctx.network.greeting, ctx.network.greet_count))
}

// Instance function: demonstrates off-chain state by tracking per-user counts.
instance fn greet_me(&mut ctx) -> LyquidResult<String> {
let mut per_user_count = ctx.instance.per_user_count.write();
let user = per_user_count.entry(ctx.caller).or_default();
*user += 1;
Ok(format!("{} I've greeted {} times to on-chain users, and {} times to you",
ctx.network.greeting, ctx.network.greet_count, *user))
}
}

In this example (the code in our tutorial), you define your persistent state and functions using familiar Rust syntax. The macros handle the integration with Lyquid's underlying Direct Memory Architecture (DMA) and execution model, so you can focus on building your application logic.

This Lyquid development reference will cover the detailed syntax and semantics for:

Also see LDK's Rust documentation.