External Access
Lyquid functions have two externally callable export surfaces:
- HTTP endpoints, declared with
export = http, - Ethereum ABI functions, declared with
export = eth.
Use HTTP exports when a hosting node should answer web or API requests directly with an instance
function. Use Ethereum exports when callers should interact through the generated contract ABI. A
Lyquid can also ship hosted web routes for static assets and node-owned /lyquid/... paths.
A single Lyquid function can have only one export surface. Choose either export = eth or
export = http; the macro rejects duplicate export arguments. To expose the same application
logic through both surfaces, put the logic in an ordinary Rust helper and define separate exported
function wrappers for Ethereum and HTTP.
On this page, "custom section" means a custom section in the compiled Lyquid WASM module. It is not
an OCI image section, layer, or annotation. When shaker packages a Lyquid as an OCI image, the
WASM module carries these sections inside the package, and the toolchain extracts them from the
WASM.
HTTP Endpoints
Add export = http to an instance function to serve it as a request/response HTTP endpoint from each
node hosting the Lyquid.
use lyquid::prelude::*;
#[method::instance(export = http, method = "GET", path_prefix = "/health")]
fn health(_ctx: &_, _req: http::Request) -> LyquidResult<http::Response> {
Ok(http::Response {
status: 200,
headers: vec![http::Header {
name: "content-type".into(),
value: b"application/json".to_vec(),
}],
body: br#"{"ok":true}"#.to_vec(),
})
}
The endpoint is served from the Lyquid virtual host:
GET https://<lyquid-id-without-prefix>.<node-id-without-prefix>[.<suffix>]/health
The host name uses the DNS-safe ID strings without display prefixes. For example, display IDs
Lyquid-abc and Node-def use host prefix abc.def, not Lyquid-abc.Node-def.
On localnet, use shaker serve to expose that same virtual-host route through localhost:
shaker serve <LYQUID_ID> --endpoint ws://127.0.0.1:10087/ws
Then open the printed local URL and call /health relative to that URL.
Definition Rules
HTTP exports are intentionally instance-only. Network functions are globally sequenced and deterministic; HTTP ingress runs against one hosting node and may read node-local instance state, perform local computation, or use external APIs.
The function signature must have exactly one request parameter and return an HTTP response:
#[method::instance(export = http, method = "POST", path_prefix = "/api/messages")]
fn messages(ctx: &_, req: http::Request) -> LyquidResult<http::Response> {
// ...
}
Supported method values are GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, and *.
Use * to match any inbound HTTP method.
path_prefix must be an absolute path beginning with /. It cannot contain a query string,
fragment, path parameters, wildcards, regex syntax, or empty path segments. A trailing slash is
canonicalized away except for /, so path_prefix = "/api/" is recorded as /api.
The proc macro emits HTTP export metadata into a WASM custom section named
lyquor.method.export.http. The toolchain reads that section from the compiled WASM module. The
metadata records:
- function category, currently always instance,
- Lyquid function group,
- Lyquid function name,
- inbound HTTP method, or
*, - canonical path prefix.
The WASM image extractor rejects duplicate HTTP exports with the same (HTTP method, path_prefix)
pair.
Request and Response
The host passes the inbound request to the Lyquid function as http::Request:
| Field | Meaning |
|---|---|
method | HTTP method enum, including Other(String) for methods outside the built-in set |
url | absolute request URL when supplied, otherwise reconstructed from the Host header and path/query |
headers | request headers as names plus raw byte values |
body | None for an empty body, otherwise the buffered bytes |
Inbound HTTP export bodies are capped at 4 MiB. If buffering fails, the host returns 400; if the
body exceeds the cap, it returns 413.
Return http::Response with a status code, headers, and body bytes. The host ignores a returned
content-length header and sets it from the returned body. Invalid status codes, invalid header
names or values, and the hop-by-hop response headers connection, transfer-encoding, and
upgrade produce 502. If the function returns an error or aborts, the host returns 500.
For HEAD requests, the selected export still runs, but the host sends the response headers and
status with an empty body.
Route Matching
HTTP exports live at top-level paths on the Lyquid virtual host, not under /lyquid.
Path prefixes are segment-aware:
| Prefix | Matches | Does not match |
|---|---|---|
/api | /api, /api/, /api/users | /apiary |
/ | any non-reserved path | reserved /lyquid/... paths |
When multiple HTTP exports match, the host chooses the longest matching prefix. Exact-method routes
and * wildcard routes are both considered; a longer wildcard prefix can beat a shorter
exact-method prefix. If no HTTP export matches, the node falls back to static asset serving.
Hosted Web Routes
A Lyquid can ship a static frontend in its assets/ directory (see
Packaging). A hosting node serves it under a per-Lyquid virtual host. The
first host label is the Lyquid ID without Lyquid-, and the second host label is the node ID
without Node-:
<lyquid-id-without-prefix>.<node-id-without-prefix>[.<suffix>]
Under that host the node reserves a few control paths and serves everything else from the packaged asset bundle:
| Path | Serves |
|---|---|
/lyquid/info | JSON metadata for browser clients: lyquid_id, node_base_url, backend_contract, sequence_backend |
/lyquid/statusz | Node status endpoint: lyquid_id, image_digest, sequence_backend, backend_contract, lyquid_number |
/lyquid/api | Per-Lyquid HTTP JSON-RPC endpoint |
/lyquid/ws | Per-Lyquid WebSocket JSON-RPC endpoint |
other /lyquid/... | reserved (returns 404) |
| any other path | first HTTP exports, then static assets from the packaged assets/ bundle |
Static asset lookup maps / to index.html and a trailing slash such as /docs/ to
docs/index.html. If an HTTP export and a static asset use the same path, the HTTP export wins.
When debugging a hosted site, hit /lyquid/info, /lyquid/statusz, or /lyquid/api first to
confirm routing is alive before suspecting asset packaging; see
Testing and Debugging.
Ethereum ABI
Add export = eth to expose a function through the generated Ethereum-compatible contract ABI.
#[method::network(export = eth)]
fn balanceOf(ctx: &_, account: Address) -> LyquidResult<U256> {
Ok(*ctx.network.balances.get(&account).unwrap_or(&U256::ZERO))
}
#[method::instance(export = eth)]
fn local_status(ctx: &_) -> LyquidResult<String> {
Ok(format!("node={}", ctx.node_id))
}
The proc macro emits export metadata into a WASM custom section named
lyquor.method.export.eth. The toolchain reads that section from the compiled WASM module and uses
the metadata to expose the function as an ETH-compatible contract function.
Export metadata records:
- function category: network or instance,
- mutability from the context reference,
- group name,
- function name,
- parameter ABI types,
- return ABI types.
eth_guard = creator can be added to mutable network functions in the main or node group when
the generated EVM transaction wrapper should require msg.sender == creator. This check happens in
the generated Ethereum wrapper before it submits the call to the sequencer. It protects that
Ethereum transaction path only. If the function must also reject non-Ethereum or internal Lyquor
calls, validate ctx.origin, ctx.caller, or application-specific authority inside the function.
For mutable network exports, the Ethereum transaction receipt confirms that the sequencer contract accepted the call into the sequence. The Lyquid function body runs later on hosting nodes, so frontends should confirm application effects through a read function or hosted endpoint when needed.
ABI Types
Every parameter and every returned ABI value of an export = eth function must be representable as
an Ethereum ABI type. A () return produces no ABI output values. The LDK checks ABI values through
the EthAbiType trait while compiling the Lyquid. Most common Ethereum-facing Rust types already
implement it, so typical function signatures work without extra code. If a parameter, single return
value, or tuple return element does not implement EthAbiType, the Lyquid crate fails to compile
instead of producing an unusable ABI. For project-specific wrapper types, implement EthAbiType to
define how that type appears in the generated Ethereum ABI.
Built-in implementations include:
| Rust type | ETH ABI type |
|---|---|
U256 | uint256 |
U128 | uint128 |
U64 | uint64 |
u64 | uint64 |
u32 | uint32 |
u16 | uint16 |
u8 | uint8 |
bool | bool |
Bytes | bytes |
String | string |
Address | address |
B256 | bytes32 |
LyquidID | address |
RequiredLyquid | address |
NodeID | bytes32 |
Vec<T> where T: EthAbiType | dynamic array |
[T; N] where T: EthAbiType | fixed array |
Ethereum exports can take 0 to 16 parameters. They can return no ABI values by using Rust (), one
ABI value, or 2 to 16 ABI values as a Rust tuple.
Encoding Helpers
Use the ETH ABI helpers when building or decoding call data inside Lyquid code:
let input = encode_eth_call_params!(Address::ZERO, U256::from(1));
let decoded = decode_eth_call_params!(&input, to: Address, amount: U256);
For Lyquor ABI encoding, use encode_by_fields!, decode_by_fields!, encode_object, and
decode_object from the prelude. Lyquor ABI is the native Lyquid/Lyquor field encoding; ETH ABI is
only the Ethereum-compatible surface described above.