Security and Patterns
Security and Correctness
Determinism
Network methods must be deterministic. Do not use node-local time, randomness, HTTP, local files, environment variables, or host-specific data to decide network state changes.
The runtime enforces this for the host APIs that are inherently nondeterministic: systime,
random_bytes, http_request, sign, submit_call, upc, and fetch_oracle_info are instance
only, and calling them from a network context returns LyquidError::LyquorRuntime instead of
executing. See Host APIs for the full context map. Determinism hazards that the
runtime cannot see — local files, environment variables, or nondeterministic third-party crates —
are still your responsibility.
If a state change depends on off-chain data, use an oracle/certified-call flow so validators approve the data before it reaches network state.
Secrets
Do not put secrets in network state. Use instance state for node-local credentials such as API keys. Remember that instance state is persistent on the node.
Input Validation
Validate at the boundary:
- addresses are not
Address::ZEROunless that is intentional - amounts and indexes are in range
- required Lyquids are configured
- callback Lyquid IDs and target addresses are authorized
- oracle-certified calls target the expected method/input
- UPC responses are from expected nodes
Caller and Origin
ctx.origin is the original external origin. ctx.caller is the direct caller. Use the field that
matches the trust boundary. Do not use origin as a replacement for capability checks without a
reason.
Interactions
Network call! has atomic abort semantics. Treat it like part of the same sequenced transition.
UPC is instance-side and not a direct network mutation mechanism. If UPC results should affect network state, route them through a network call, certified call, or another sequenced path.
Compatibility
Lyquor currently prioritizes architectural cleanliness and correctness over preserving legacy interfaces for the Network API, Lyquid syntax, UPC, and Rust API. Pin the LDK version your Lyquid builds against, and re-check your code against this reference and the Rustdoc when you upgrade.
Patterns
Read-Only Network Query
#[method::network(export = eth)]
fn balanceOf(ctx: &_, account: Address) -> LyquidResult<U256> {
Ok(ctx.network.balances.get(&account).cloned().unwrap_or_default())
}
Use ctx: &_ when the method should not mutate network state.
Node-Local Cache
state! {
network tracked_symbols: Vec<String> = Vec::new();
instance latest_prices: HashMap<String, U256> = new_hashmap();
}
#[method::instance(export = eth)]
fn cached_price(ctx: &_, symbol: String) -> LyquidResult<U256> {
Ok(ctx.instance.latest_prices.read().get(&symbol).cloned().unwrap_or_default())
}
Use instance state for data that can differ by node.
Network to Instance Follow-Up
#[method::network(export = eth)]
fn request_refresh(ctx: &mut _, symbol: String) -> LyquidResult<bool> {
trigger!(refresh_price(symbol: String = symbol), TriggerMode::Commit);
Ok(true)
}
#[method::instance]
fn refresh_price(ctx: &mut _, symbol: String) -> LyquidResult<bool> {
let mut prices = ctx.instance.latest_prices.write();
prices.insert(symbol, U256::ZERO);
Ok(true)
}
Use commit-time triggers for work that should start after a network state transition commits.
ERC20-Compatible Shape
Use export = eth and Solidity-compatible names/types:
#[method::network(export = eth)]
fn transfer(ctx: &mut _, to: Address, amount: U256) -> LyquidResult<bool> {
let from = ctx.caller;
transfer_balance(&mut ctx.network, from, to, amount)?;
Ok(true)
}
#[method::network(export = eth)]
fn totalSupply(ctx: &_) -> LyquidResult<U256> {
Ok(*ctx.network.total_supply)
}
Oracle-Certified State Update
Use this shape when local nodes observe data and certify a network update:
- keep local observations in
instancestate - declare
network oracle <topic>; - implement validation/aggregation instance methods
- implement
oracle::certified::<topic>network update methods - submit the certified call through the oracle protocol
Reference Tables
Prelude Exports
The LDK prelude exports common types, helpers, and macros:
| Kind | Items |
|---|---|
| Primitive IDs/types | Address, LyquidID, NodeID, LyquidNumber, RequiredLyquid |
| Integer/bytes types | Bytes, U64, U128, U256, Hash |
| Call & trigger types | CallParams, TriggerMode, ChainPos |
| Literal macros | address!, uint! |
| Encoding helpers | encode_by_fields!, decode_by_fields!, encode_object, decode_object |
| ETH ABI helpers | encode_eth_call_params!, decode_eth_call_params! |
| Collections | HashMap, HashSet, new_hashmap, new_hashset |
| Sync types | Mutex, RwLock, guards |
| Hashing | blake3 |
| Results/errors | LyquidResult, LyquidError |
| Method macros | method::network, method::instance, state! |
| Runtime macros | call!, upc!, trigger!, submit_certified_call! |
| Output/logging | print!, println!, eprint!, eprintln!, log! |
| Oracle types | CertifiedCallParams, OracleTarget, OracleServiceTarget |
| HTTP model types | http module types for host HTTP requests |
Method Support Matrix
| Capability | Network | Instance |
|---|---|---|
| Read network state | yes | yes |
| Write network state | mutable context only | no |
| Read instance state | no | yes |
| Write instance state | no | mutable context only |
Use call! | yes | no |
Use upc! | no | yes |
Use log! | yes | no |
Use trigger! | yes, including TriggerMode::Commit | yes, for Once, Recurrent, and Stop |
| Export as ETH ABI | yes | yes |
| Use nondeterministic host APIs | no | yes, where supported |
Unsupported Method Syntax
Do not use these for Lyquid methods:
async fn bad(...) -> LyquidResult<bool> { ... }
const fn bad(...) -> LyquidResult<bool> { ... }
extern "C" fn bad(...) -> LyquidResult<bool> { ... }
fn bad<T>(...) -> LyquidResult<T> { ... }
fn bad(ctx: Context, ...) -> LyquidResult<bool> { ... }
fn bad(_: &mut _, ...) -> LyquidResult<bool> { ... }
fn bad(&self, ...) -> LyquidResult<bool> { ... }
Use ordinary non-generic top-level functions with named context references.
Documentation References
Useful docs:
- Quick Start
- Tutorial
- Build and Deploy
- State Model
- Functions
- Function Group and UPC
- Direct Memory Architecture
Glossary
| Term | Meaning |
|---|---|
| Lyquid | WASM smart contract/service written with the LDK and hosted by Lyquor |
| LDK | Lyquor Development Kit, exposed through the lyquid crate's ldk feature |
| Network method | Deterministic, sequenced method that can mutate network state |
| Instance method | Node-local method that can mutate instance state |
| Network state | Globally agreed persistent Lyquid state |
| Instance state | Node-local persistent Lyquid state |
| LyteMemory | Fixed linear-memory layout backing volatile, network, and instance memory |
| LyquidNumber | Version number for network state |
| UPC | Universal Procedure Call, the instance-side inter-Lyquid call mechanism |
| Certified call | Sequenced call authorized by oracle certificate |
| Oracle topic | Named oracle state and validation domain |
RequiredLyquid | Typed LyquidID wrapper marking a dependency Lyquid; ABI-encoded as address |
export = eth | Method export metadata for Ethereum ABI compatibility |
shaker | Lyquor tooling binary used to build, deploy, and inspect Lyquids |
| Lyquid pack | OCI-style package containing WASM, EVM bytecode, metadata, and assets |