Skip to main content

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:

FieldMeaning
methodHTTP method enum, including Other(String) for methods outside the built-in set
urlabsolute request URL when supplied, otherwise reconstructed from the Host header and path/query
headersrequest headers as names plus raw byte values
bodyNone 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:

PrefixMatchesDoes not match
/api/api, /api/, /api/users/apiary
/any non-reserved pathreserved /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:

PathServes
/lyquid/infoJSON metadata for browser clients: lyquid_id, node_base_url, backend_contract, sequence_backend
/lyquid/statuszNode status endpoint: lyquid_id, image_digest, sequence_backend, backend_contract, lyquid_number
/lyquid/apiPer-Lyquid HTTP JSON-RPC endpoint
/lyquid/wsPer-Lyquid WebSocket JSON-RPC endpoint
other /lyquid/...reserved (returns 404)
any other pathfirst 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 typeETH ABI type
U256uint256
U128uint128
U64uint64
u64uint64
u32uint32
u16uint16
u8uint8
boolbool
Bytesbytes
Stringstring
Addressaddress
B256bytes32
LyquidIDaddress
RequiredLyquidaddress
NodeIDbytes32
Vec<T> where T: EthAbiTypedynamic array
[T; N] where T: EthAbiTypefixed 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.