Skip to main content

Build, Deploy, and Test

Most Lyquid development starts on a local Lyquor devnet. Use a single node while you are iterating on exported functions, state changes, and UI packaging. Move to a four-node devnet when the Lyquid's behavior depends on multiple hosts, such as UPC, oracle committees, or node-local instance state that differs across nodes.

Once the devnet is running, the workflow is the same across these setups. shaker builds the Lyquid crate, makes the build artifact available to the nodes that need it, deploys or updates the Lyquid on the local sequence backend, and can expose any hosted UI or HTTP routes through a local proxy.

Start with Quick Start if the LDK is not installed yet:

curl ldk.lyquor.dev -L | bash

The installer places the LDK, example Lyquids, shaker, ladle, and the Rust/Foundry toolchains under ~/.shakenup.

Local Devnets

You can start local devnets either with Docker Compose or with native shell scripts. The Compose path requires Docker Engine and Docker Compose v2. The native shell scripts run the released Lyquor binaries directly and require the LDK tools plus Foundry on your PATH.

Single-Node

The single-node Docker Compose setup starts one Lyquor node and a local Anvil-backed sequence backend. It also bootstraps bartender, the registry and control-plane Lyquid associated with that sequence backend, so shaker deploy can register and look up Lyquids:

docker compose -f ~/.shakenup/ldk/docker/single/docker-compose.yaml up -d

The native single-node script exposes the same endpoint and keeps the node running until you press Ctrl-C:

~/.shakenup/ldk/scripts/localnet.sh single

Both single-node setups expose:

EndpointURL
APIhttp://127.0.0.1:10087/api
WebSocketws://127.0.0.1:10087/ws

Multi-Node

Use a four-node devnet when you need several Lyquor hosts.

Docker Compose starts a shared Anvil-backed sequence backend, four connected Lyquor nodes, a local OCI registry, and one bartender registry Lyquid:

docker compose -f ~/.shakenup/ldk/docker/multi/docker-compose.yaml up -d

Both Compose setups start setup-devnet asynchronously; wait for it to finish before deploying. To follow bootstrap, run docker compose -f <compose-file> logs -f setup-devnet.

The native shell scripts provide the same four-node setup without Docker. They run released lyquor and shaker binaries directly on your machine and keep the network alive until you press Ctrl-C:

~/.shakenup/ldk/scripts/localnet.sh multi

The native multi script exposes the same node ports as Docker Compose. Instead of running a separate registry container, it serves a local OCI registry endpoint from node 1 backed by that node's image directory.

Both four-node setups expose:

NodeAPIWebSocket
Node 1http://127.0.0.1:10087/apiws://127.0.0.1:10087/ws
Node 2http://127.0.0.1:11087/apiws://127.0.0.1:11087/ws
Node 3http://127.0.0.1:12087/apiws://127.0.0.1:12087/ws
Node 4http://127.0.0.1:13087/apiws://127.0.0.1:13087/ws

Stop a native devnet with Ctrl-C. Stop a Docker Compose devnet with the corresponding docker compose ... down command.

agent-consensus is a bundled hosted UI demo that needs four nodes because it configures an oracle committee across node-local agents. To run the whole flow, use its demo script:

~/.shakenup/ldk/lyquid-examples/agent-consensus/scripts/run.sh

The script starts the native four-node topology, deploys agent-consensus with shaker, activates its oracle committee, and serves the hosted UI with shaker serve. The deploy step uses node 1's local registry endpoint with a reference like http://127.0.0.1:10087/lyquids:agent-consensus-local. The script also requires jq and python3 and serves the UI at http://127.0.0.1:8080/.

Build and Deploy

To build and deploy a Lyquid, pass its Cargo.toml to shaker deploy. It compiles the crate, packages a Lyquid pack, publishes it where the target Lyquor nodes can fetch it, and reports the resulting Lyquid-* ID.

For local devnets, pass --endpoint explicitly. The examples use node 1's WebSocket endpoint, which is also the single-node endpoint. LYQUOR_ENDPOINT is only used to keep the commands short:

export LYQUOR_ENDPOINT=ws://127.0.0.1:10087/ws

Here is a single-node deploy using the bundled hello example. For project layout and the template, see Project Anatomy:

shaker deploy \
--endpoint "$LYQUOR_ENDPOINT" \
~/.shakenup/ldk/lyquid-examples/hello/Cargo.toml

Single-node deploys do not need an explicit registry reference; shaker can use the target node's local registry path. For a multi-node devnet, pass --reference so the other local nodes know where to fetch the built Lyquid. The value is an OCI image reference, which in this local workflow means a registry location plus a tag in the shape <registry>/<repo>:<tag>. In the examples below, hello-local is just the local tag for the hello example; use a different tag for a different project.

The repository and tag stay the same in both examples; only the registry location differs.

For Docker Compose multi-node, use the separate registry container:

export LYQUID_REFERENCE=http://127.0.0.1:8000/lyquids:hello-local

For native shell-script multi-node, use node 1's local registry endpoint:

export LYQUID_REFERENCE=http://127.0.0.1:10087/lyquids:hello-local

Then deploy with that reference:

shaker deploy \
--endpoint "$LYQUOR_ENDPOINT" \
--reference "$LYQUID_REFERENCE" \
~/.shakenup/ldk/lyquid-examples/hello/Cargo.toml

Most constructors need no extra input. If yours does, pass encoded bytes with --input. When using the generated Ethereum-compatible ABI, cast abi-encode gives the expected encoding:

INPUT=$(cast abi-encode "constructor(string)" "Hello, World!")

shaker deploy \
--endpoint "$LYQUOR_ENDPOINT" \
--input "$INPUT" \
~/.shakenup/ldk/lyquid-examples/hello/Cargo.toml

Add --reference "$LYQUID_REFERENCE" to that command when deploying to a multi-node devnet.

The deploy output reports whether a Lyquid was created or updated, plus two identifiers. Save the Lyquid-* ID; it is the stable ID you use with shaker console, shaker serve, updates, and other Lyquor tooling. The hex address is backend-specific. In a local Anvil devnet, it is the current EVM contract address, but most application workflows should track the Lyquid ID. To list deployed Lyquids later, query the target node:

shaker list --endpoint "$LYQUOR_ENDPOINT"

Updating Deployed Code

To keep the same Lyquid ID while changing code, run shaker deploy with the crate's Cargo.toml and --update; shaker rebuilds the crate and deploys the new pack:

shaker deploy \
--endpoint "$LYQUOR_ENDPOINT" \
--update <LYQUID_ID> \
path/to/Cargo.toml

Use --input on updates too when the new constructor needs arguments. Today, an update that changes the Lyquid code digest starts from fresh state: network and instance state for that Lyquid are reset, and the constructor initializes the new deployment state.

If the update only changes packaged UI/static assets and the Lyquid code digest stays the same, the runtime does not reset state and does not invoke the constructor again.

State-preserving code upgrades are planned through migration hooks. Those hooks will let new code read old persistent memory objects during migration, but they are not available yet. For the current state rules, see the memory model.

Serve a Hosted UI

A Lyquid can include a static frontend in its assets/ directory and can expose HTTP routes with export = http; see External Access. Local devnets do not normally provide wildcard DNS for per-Lyquid virtual hosts, so use shaker serve after deployment:

shaker serve <LYQUID_ID> --endpoint "$LYQUOR_ENDPOINT"

Open the local URL printed by shaker serve. The proxy forwards browser requests to the selected node using the Lyquid host identity, so clients can use /lyquid/info, /lyquid/statusz, /lyquid/api, /lyquid/ws, exported HTTP routes, and packaged static assets through that localhost URL.

On a multi-node devnet, the endpoint selects which node serves the UI. This matters when the page reads instance state, calls node-local HTTP exports, or acts as a proposer in a UPC or oracle flow. To serve from node 2 instead of node 1, for example:

shaker serve <LYQUID_ID> --endpoint ws://127.0.0.1:11087/ws

Packaging

shaker build produces a local Lyquid pack: the artifact that nodes run and registries store. shaker push uploads that pack to an OCI registry, while shaker deploy can either build and publish a pack from a manifest or deploy one from an existing OCI reference. The pack contains the Lyquid WASM layer, generated sequencing stub bytecode, metadata, and optional layers such as auxiliary sequencing bytecode or hosted static assets. The local .lyquid.pack records the SHA-256 digest of its canonical OCI manifest.

shaker packages static files from an assets/ directory next to the Lyquid manifest. Projects may keep UI source elsewhere, but packaging reads assets/; agent-consensus uses build.rs to recreate assets/ by copying its UI files and src/ tree from ui/. The node serves those files from the Lyquid virtual host after HTTP export route matching. Asset lookup maps / to index.html, so a single-page app can usually ship its production build output by placing it in assets/.

Build artifacts are written under lyquid_tools_target/ in the current working directory. The most commonly inspected paths are:

ArtifactPath
Raw WASMlyquid_tools_target/wasm32-unknown-unknown/release/<crate>.wasm
Sequencing stub sourcelyquid_tools_target/solidity/<crate>/<crate>.sol
Sequencing stub bytecodelyquid_tools_target/solidity/<crate>/<crate>.bin
Lyquid packlyquid_tools_target/release/<crate>/lyquid.pack

Use --debug on build or deploy when you want Cargo's dev profile. Debug artifacts are written under the corresponding debug directories.

Inspect a local pack, a raw WASM binary, or an OCI reference with:

shaker inspect path/to/pack-or-wasm

Use inspection when you need to confirm metadata, image digests, or the constructor argument types before encoding --input.

Testing and Debugging

Keep pure application logic in ordinary Rust functions and, from the Lyquid crate directory, unit-test it with the standard Rust toolchain:

cargo test

Use a single-node devnet for end-to-end checks that only need one host: constructor behavior, Ethereum exports, HTTP exports, static assets, and basic state changes. Deploy the Lyquid, interact with it through its generated Ethereum ABI or hosted routes, then confirm state through a read-only exported function (ctx: &_) or HTTP endpoint.

Use a multi-node devnet for behavior that depends on several hosts: node-local instance state differences, committee-based certification, UPC calls, or hosted UI flows where each node acts differently. In those tests, deploy with a shared --reference and choose the serving or calling node by changing --endpoint.

Use Lyquid console output for application-level traces:

lyquid::println!("handling request {}", request_id);

Stream that output from a running Lyquid with:

shaker console --endpoint "$LYQUOR_ENDPOINT" <LYQUID_ID>

Node logs depend on how you started the devnet. For Docker Compose, follow the node service:

# single-node Docker
docker compose -f ~/.shakenup/ldk/docker/single/docker-compose.yaml logs -f node

# multi-node Docker
docker compose -f ~/.shakenup/ldk/docker/multi/docker-compose.yaml logs -f node1 node2 node3 node4

For native shell scripts, set LOCALNET_WORK_DIR when you start the devnet if you want stable log paths, then tail the node logs from another terminal:

# terminal 1
LOCALNET_WORK_DIR=/tmp/lyquor-localnet ~/.shakenup/ldk/scripts/localnet.sh multi

# terminal 2
tail -f /tmp/lyquor-localnet/node*/lyquor.log

To increase node verbosity, set LYQUOR_LOG=debug where your devnet starts:

  • Docker Compose: add LYQUOR_LOG=debug to the environment for the node service in single-node, or for the node1/node2/node3/node4 services you want to inspect in multi-node, then recreate the containers.
  • Native shell scripts: prefix the script command:
LYQUOR_LOG=debug LOCALNET_WORK_DIR=/tmp/lyquor-localnet ~/.shakenup/ldk/scripts/localnet.sh multi

When debugging a hosted UI, separate host routing from asset packaging. First request a reserved Lyquid path through the shaker serve URL:

CheckMeaning
/lyquid/infoBrowser discovery metadata is reachable
/lyquid/statuszThe selected node is hosting the Lyquid
/lyquid/apiThe per-Lyquid API route is alive

If those paths work but / or /index.html returns 404, inspect the pack and the project's assets/ directory first. If reserved paths fail, check that you are serving the right Lyquid ID, that --endpoint points at a node hosting that Lyquid, and that the devnet is still running.