Lenses

Lenses are WebAssembly (WASM) modules that transform raw primitive data into structured View output. A lens takes a document in (e.g. a raw Log with topics and data) and produces a document out (e.g. an EthEvent with decoded event, signature, and arguments fields).

Every Host that runs a View executes the same lens on the same data, so the output is deterministic and verifiable. If two Hosts disagree, something is wrong.

When you need a lens

ScenarioLens needed?
ABI-decode event logs into named fieldsYes, decode_log or decode_log_str
Decode function calls from transaction input dataYes, decode_function_call or decode_function_call_str
Expose a subset of Transaction fieldsNo, query + SDL is enough
Filter transactions by to addressNo, use a GraphQL filter in the query
Filter decoded events by contract addressNo, filter on logAddress in GraphQL queries

If your View just selects and renames fields from a primitive collection, you don't need a lens. DefraDB handles that as a virtual projection. You need a lens when the transform requires logic that GraphQL can't express: ABI decoding, hex-to-decimal conversion, or field restructuring.

Available lenses

Lenses are stored in the wasm-bucket repository. Each lens is a compiled .wasm binary hosted at a raw GitHub URL.

decode_log

ABI-decodes EVM event logs. Takes a JSON array of Solidity event definitions, matches each log's topics[0] against the event signature hash, and decodes the log into structured output.

URL:

https://raw.githubusercontent.com/shinzonetwork/wasm-bucket/main/bucket/decode_log/decode_log.wasm

Arguments:

ArgumentTypeDescription
abiString (stringified JSON)Array of event ABI definitions

Output fields:

FieldTypeDescription
hashStringParent transaction hash
fromStringParent transaction sender
toStringParent transaction recipient
blockNumberIntBlock number
logAddressStringContract that emitted the log
eventStringDecoded event name (e.g. "Transfer")
signatureStringEvent signature (e.g. "Transfer(address,address,uint256)")
arguments[String]Decoded parameters as JSON array

The hash, from, and to fields come from the parent transaction. To populate them, include transaction { hash from to } in your View's source query.

Example:

viewkit view add lens \
  --label "decode-transfer" \
  --url "https://raw.githubusercontent.com/shinzonetwork/wasm-bucket/main/bucket/decode_log/decode_log.wasm" \
  --args '{"abi":"[{\"type\":\"event\",\"name\":\"Transfer\",\"inputs\":[{\"type\":\"address\",\"name\":\"from\",\"indexed\":true},{\"type\":\"address\",\"name\":\"to\",\"indexed\":true},{\"type\":\"uint256\",\"name\":\"value\",\"indexed\":false}]}]"}' \
  --name my-view

decode_log_str

Same as decode_log, but serializes arguments as a JSON string instead of a JSON array. This enables _like / _ilike filtering on decoded parameter values in DefraDB queries.

URL:

https://raw.githubusercontent.com/shinzonetwork/wasm-bucket/main/bucket/decode_log_str/decode_log_str.wasm

Arguments: same as decode_log (abi).

Output fields: same as decode_log, except arguments is a String (JSON string) instead of [String].

When to use: decode_log_str when you need to filter query results by decoded parameter values (e.g. "all transfers where a specific address is the sender"). decode_log when you only need to read the arguments and don't need server-side filtering on them.

decode_function_call

ABI-decodes function calls from a transaction's input data. Useful for Views that track specific contract method calls (e.g. all transfer calls to a token contract).

URL:

https://raw.githubusercontent.com/shinzonetwork/wasm-bucket/main/bucket/decode_function_call/decode_function_call.wasm

Arguments:

ArgumentTypeDescription
function_abiString (stringified JSON)Array of function ABI definitions
event_abiString (stringified JSON)Array of event ABI definitions (pass "[]" if unused)

Output fields:

FieldTypeDescription
hashStringTransaction hash
fromStringTransaction sender
toStringTransaction recipient
blockNumberIntBlock number
functionStringDecoded function name (e.g. "transfer")
signatureStringFunction signature (e.g. "transfer(address,uint256)")
arguments[String]Decoded parameters as JSON array

Documents whose input does not match any ABI function are passed through unchanged.

decode_function_call_str

Same as decode_function_call, but serializes arguments as a JSON string for _like filtering.

URL:

https://raw.githubusercontent.com/shinzonetwork/wasm-bucket/main/bucket/decode_function_call_str/decode_function_call_str.wasm

ABI format

All decode lenses take ABI definitions as stringified JSON arrays. Each event definition follows the standard Solidity ABI format:

FieldDescription
type"event" for events, "function" for functions
nameEvent or function name
inputsArray of parameter definitions
inputs[].typeSolidity type (e.g. "address", "uint256")
inputs[].nameParameter name
inputs[].indexedtrue if the parameter is in topics, false if in data (events only)

Common ABI snippets

ERC-20 Transfer:

[{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","indexed":true},{"type":"address","name":"to","indexed":true},{"type":"uint256","name":"value","indexed":false}]}]

ERC-20 Approval:

[{"type":"event","name":"Approval","inputs":[{"type":"address","name":"owner","indexed":true},{"type":"address","name":"spender","indexed":true},{"type":"uint256","name":"value","indexed":false}]}]

ERC-721 Transfer:

[{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","indexed":true},{"type":"address","name":"to","indexed":true},{"type":"uint256","name":"tokenId","indexed":true}]}]

Multiple events (Transfer + Approval):

[{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","indexed":true},{"type":"address","name":"to","indexed":true},{"type":"uint256","name":"value","indexed":false}]},{"type":"event","name":"Approval","inputs":[{"type":"address","name":"owner","indexed":true},{"type":"address","name":"spender","indexed":true},{"type":"uint256","name":"value","indexed":false}]}]

When you pass multiple event definitions, decode_log matches each log against all of them by comparing topics[0] (the event signature hash) and decodes accordingly.

Lens arguments

Lens arguments are passed as a JSON object via the --args flag. All values must be strings:

--args '{"src":"address", "value":"0x..."}'     # correct
--args '{"count": 5}'                            # incorrect, values must be strings
--args '{"count": "5"}'                          # correct

For complex arguments like ABI arrays, pass the inner JSON as a stringified value:

--args '{"abi":"[{\"type\":\"event\",...}]"}'

Determinism

Lenses must be deterministic: the same input document always produces the same output document. This is what makes Views verifiable. Any Host running the same lens on the same primitive data will produce identical results.

Things to avoid if you write your own lenses:

  • Reading the system clock or generating random numbers.
  • Making network calls.
  • Depending on file system state.
  • Using floating-point arithmetic (can vary across WASM runtimes).

Testing lenses locally

Before deploying, validate that your View (including lenses) compiles and runs:

viewkit view test my-view

This spins up a temporary local DefraDB instance, applies your schema, loads the WASM lens, and checks that everything builds. For a full end-to-end check with a GraphQL Playground, deploy locally:

viewkit view deploy my-view --target local

See the Quick Start for Wasmer runtime setup if you encounter libwasmer errors during local testing.

Authoring new lenses

If the available lenses don't cover your use case, you can write your own. Lenses are WASM modules, typically written in Rust or AssemblyScript, that implement a transform function.

Interface

A lens receives a document as input and returns a document (or None to drop it). A simplified Rust sketch for an event decoder:

fn transform(log: Log) -> Option<DecodedEvent> {
    if log.topics[0] != TRANSFER_SIGNATURE {
        return None;
    }
    Some(DecodedEvent {
        event: "Transfer".to_string(),
        from: decode_address(&log.topics[1]),
        to: decode_address(&log.topics[2]),
        arguments: decode_uint256(&log.data),
    })
}

Language comparison

LanguageTypical WASM sizeNotes
Rust~200-300 KBPreferred for production. Best tooling and optimization.
AssemblyScript~73 KBEasier if you know TypeScript. Smaller binaries.

Smaller binaries mean less P2P overhead when Hosts download the View bundle.

Where to find SDKs and examples

ResourceLocation
Rust lens SDKsource-gh/lens/sdk-rust/
AssemblyScript lens examplesource-gh/lens/tests/modules/as_wasm32_simple/
WASM runtime implementationssource-gh/lens/host-go/runtimes/

The Host client's LensVM runtime does not care what language produced the WASM. Any compliant WASM module will work.

WASM runtimes

The Host client runs lenses through LensVM, which supports three WASM runtimes:

RuntimeLanguageUsed by
WazeroGoProduction Host clients (pure Go, no CGo)
WasmtimeRustAlternative production runtime
WasmerRustViewkit local testing

For deeper technical details on the VWL wire format, view ID computation, and the deploy pipeline, see the Viewkit reference.

Need help

  • For onboarding and technical support, join the Shinzo Discord.
  • To report a documentation bug or request a feature, open an issue in the docs repo.
  • For a technical issue with the View Creator client, open an issue in the shinzo-view-creator repo.