ownable-std is the shared Rust standard library for Ownables contracts.
It provides:
- common types/utilities used by Ownables contracts
- optional message-shaping proc macros (
ownable-std-macros) - a stable Host ABI v1 for wasm runtime calls that does not depend on
wasm-bindgenJS glue compatibility - split external-event types and helpers for public events and cross-ownable events
[dependencies]
ownable-std = "0.7.0"macros is enabled by default. To disable proc macros:
[dependencies]
ownable-std = { version = "0.7.0", default-features = false }ownable-std has no wasm-bindgen or js-sys dependency in runtime contract usage.
Constants:
ownable_std::abi::HOST_ABI_VERSION="1"ownable_std::abi::HOST_ABI_MANIFEST_FIELD="ownablesAbi"ownable_std::abi::HOST_ABI_WIRE_FORMAT="cbor"ownable_std::abi::HOST_ABI_WIRE_FORMAT_MANIFEST_FIELD="wireFormat"
Stable exports:
ownable_alloc(len: u32) -> u32ownable_free(ptr: u32, len: u32)ownable_instantiate(ptr: u32, len: u32) -> u64ownable_execute(ptr: u32, len: u32) -> u64ownable_query(ptr: u32, len: u32) -> u64ownable_register(ptr: u32, len: u32) -> u64ownable_ingest(ptr: u32, len: u32) -> u64ownable_encode_public_event(ptr: u32, len: u32) -> u64
Return strategy:
- entrypoints return packed
u64 - low 32 bits = output pointer
- high 32 bits = output length
Wire format:
- input: CBOR bytes
- output: CBOR envelope bytes
At the ABI transport layer, three calls use contract-defined CBOR payloads:
instantiate: CBOR document bytesexecute: CBOR document bytesquery: CBOR document bytes
register uses a standardized CBOR-encoded ownable_std::register::PublicEvent payload.
ingest uses a standardized CBOR-encoded ownable_std::ingest::OwnableEvent payload.
encode_public_event uses a standardized CBOR-encoded
ownable_std::register::EncodePublicEventRequest payload and returns raw ABI bytes.
Required ABI-level keys:
- none (the full request shape is contract-defined)
Recommended convention for cross-contract consistency:
{
abi_version: "1",
payload: <contract-defined value>
}
If you use the convention above:
abi_version: required string, must be"1"payload: required contract-defined CBOR value
Response envelope schema (decoded structure):
{
success: bool,
payload: bytes,
error_code: string | null,
error_message: string | null
}
Use the ownable_host_abi_v1! macro to export all required ABI symbols.
use ownable_std::abi::HostAbiError;
use ownable_std::ownable_host_abi_v1;
fn instantiate_handler(input: &[u8]) -> Result<Vec<u8>, HostAbiError> {
let v: serde_cbor::Value = serde_cbor::from_slice(input)?;
serde_cbor::to_vec(&v).map_err(HostAbiError::from)
}
fn execute_handler(input: &[u8]) -> Result<Vec<u8>, HostAbiError> {
let v: serde_cbor::Value = serde_cbor::from_slice(input)?;
serde_cbor::to_vec(&v).map_err(HostAbiError::from)
}
fn query_handler(input: &[u8]) -> Result<Vec<u8>, HostAbiError> {
let v: serde_cbor::Value = serde_cbor::from_slice(input)?;
serde_cbor::to_vec(&v).map_err(HostAbiError::from)
}
fn register_handler(input: &[u8]) -> Result<Vec<u8>, HostAbiError> {
let event: ownable_std::register::PublicEvent = ownable_std::abi::cbor_from_slice(input)?;
let response = match event.event_type.as_str() {
"consume" => {
type ConsumeEvent = alloy_sol_types::sol!((address consumer, uint256 amount));
let (_consumer, _amount) =
ownable_std::register::decode_abi_for::<ConsumeEvent>(&event, "consume")?;
ownable_std::abi::AbiResponse {
attributes: vec![],
events: vec![],
}
}
other => {
return Err(HostAbiError::new(format!(
"unsupported public event type: {other}"
)));
}
};
ownable_std::abi::cbor_to_vec(&response).map_err(HostAbiError::from)
}
fn ingest_handler(input: &[u8]) -> Result<Vec<u8>, HostAbiError> {
let event: ownable_std::ingest::OwnableEvent =
ownable_std::abi::cbor_from_slice(input)?;
ownable_std::ingest::require_ownable_event_type(&event, "consume")
.map_err(HostAbiError::from_display)?;
let amount = event
.attributes
.get("amount")
.and_then(|value| value.as_u64())
.ok_or_else(|| HostAbiError::new("missing amount attribute"))?;
let response = ownable_std::abi::AbiResponse {
attributes: vec![ownable_std::abi::AbiAttribute {
key: "amount".to_string(),
value: amount.to_string(),
}],
events: vec![],
};
ownable_std::abi::cbor_to_vec(&response).map_err(HostAbiError::from)
}
fn encode_public_event_handler(input: &[u8]) -> Result<Vec<u8>, HostAbiError> {
let request: ownable_std::register::EncodePublicEventRequest =
ownable_std::abi::cbor_from_slice(input)?;
match request.event_type.as_str() {
"consume" => {
#[derive(serde::Deserialize)]
struct ConsumePayload {
amount: u32,
active: bool,
}
type ConsumeEvent = alloy_sol_types::sol!((uint32, bool));
let payload: ConsumePayload =
ownable_std::abi::cbor_from_slice(request.data.as_slice())?;
Ok(ownable_std::register::encode_abi::<ConsumeEvent>(&(
payload.amount,
payload.active,
)))
}
other => Err(HostAbiError::new(format!(
"unsupported public event type: {other}"
))),
}
}
ownable_host_abi_v1!(
instantiate = instantiate_handler,
execute = execute_handler,
query = query_handler,
register = register_handler,
ingest = ingest_handler,
encode_public_event = encode_public_event_handler,
);Handler signature expected by the macro:
fn handler(input: &[u8]) -> Result<Vec<u8>, E>
where
E: Into<ownable_std::abi::HostAbiError>;For each call (instantiate, execute, query, register, ingest, encode_public_event):
- Serialize request object to CBOR bytes.
- Call
ownable_alloc(len). - Write bytes into wasm memory at the returned pointer.
- Call the selected
ownable_*entrypoint with(ptr, len). - Unpack
(out_ptr, out_len)from returnedu64. - Read
out_lenbytes atout_ptr. - Parse CBOR envelope and check
success. - Call
ownable_free(out_ptr, out_len).
ownable_alloc(len)allocates wasm-side memory for host writes.- Host owns writing input bytes into this allocated buffer.
- Entrypoints consume input bytes immediately and copy them internally; host may treat input buffer as transient.
- Entrypoints allocate output envelope bytes and return
(ptr,len)packed inu64. - Host must call
ownable_free(out_ptr, out_len)exactly once for every non-zero output buffer. ptr=0,len=0means empty output.- Passing
ptr=0,len>0to entrypoints yields structured ABI error (INVALID_POINTER), not panic. - This crate does not enforce a hard max payload size; host/runtime should enforce practical limits.
Current ABI-level error_code values:
INVALID_POINTER: null pointer with non-zero length inputINVALID_CBOR: request bytes failed CBOR parsing in contract handlerSERIALIZATION_FAILED: ABI envelope serialization failedHANDLER_PANIC: contract handler panicked; panic is converted to structured error
Handlers may return additional domain-specific error codes via HostAbiError::with_code.
Non-UTF8 input must not panic.
Expected behavior:
- handler CBOR parse fails (
serde_cbor::from_slice) - error is mapped to structured ABI error envelope (
success=false,error_code=INVALID_CBOR)
Build command:
cargo build --target wasm32-unknown-unknown --releaseThis produces a wasm module consumable by a host/runtime that implements Host ABI v1.
Contract-side requirements:
- do not export runtime entrypoints with
#[wasm_bindgen] - do not rely on generated
*.jsglue for host calls - export only the stable
ownable_*ABI symbols (viaownable_host_abi_v1!)
Example contract handler:
fn instantiate_handler(input: &[u8]) -> Result<Vec<u8>, ownable_std::abi::HostAbiError> {
Ok(input.to_vec())
}Host request bytes (CBOR, decoded form shown):
{ ping: "pong" }
Expected output envelope bytes decode to the same payload bytes.
{
success: true,
payload: <CBOR bytes for { ping: "pong" }>
}
MemoryStorage: in-memory storage implementation for testing/off-chain executioncreate_env/create_ownable_env: env buildersregister/ingest: public and cross-ownable event transport types plus helpersEncodePublicEventRequest: JS-to-wasm request payload for public-event ABI encodingpackage_title_from_name, color helpers, metadata/shared message structsownable-std-macros: attribute macros to extend execute/query/instantiate messages
- set your package manifest field
ownablesAbito"1"for exported symbol/signature ABI compatibility - set your package manifest field
wireFormatto"cbor"for payload envelope encoding compatibility - host/runtime should reject incompatible versions
- non-interface dependency changes should not require runtime changes if ABI version stays the same