Viewkit
Viewkit is a CLI tool for creating, testing, and deploying views. It does not process or serve data (hosts do that). It packages a view into a binary bundle on your machine and submits a transaction to ShinzoHub.
What viewkit does and does not do
What it does:
- Define GraphQL SDL schemas for view output.
- Configure WASM lens transforms for data mapping.
- Pre-validate transforms locally using Wasmer runtime.
- Package everything into a ViewBundle (VWL binary format).
- Deploy to ShinzoHub.
What it does not do:
- Process data (hosts do this).
- Store data (DefraDB does this).
- Serve queries (hosts do this).
- Run as a daemon (it is a CLI tool).
View definition
A view has three pieces:
- A query that picks which primitive data to select (e.g., "all Logs from the USDC contract").
- An SDL, the GraphQL schema defining the output shape.
- A lens (optional), a WASM module that transforms the data.
The @materialized directive
In the SDL, @materialized(if: true) tells DefraDB to pre-compute and store the view data. @materialized(if: false) computes it on query.
@materialized(if: true) is recommended for now. Queries are faster because data is already materialized when the query arrives. The tradeoff is more storage on the Host client.
Important: the limit parameter should be on the source query (e.g., Log(limit: 100)), not on the materialized view collection.
Command reference
| Command | Purpose |
|---|---|
viewkit view init <name> | Create a new view bundle |
viewkit view inspect <name> | Show the current view definition |
viewkit view inspect <name> --verbose | Show full revision history |
viewkit view add query '<q>' --name <name> | Set or update the query |
viewkit view add sdl '<sdl>' --name <name> | Set or update the SDL |
viewkit view add lens --label <l> --url <u> --args '<a>' --name <n> | Attach a WASM lens |
viewkit view remove query --name <name> | Remove the query |
viewkit view remove sdl --name <name> | Remove the SDL |
viewkit view remove lens --label <l> --name <n> | Remove a lens by label |
viewkit view test <name> | Validate the view compiles locally |
viewkit view deploy <name> --target local | Deploy to a local DefraDB instance |
viewkit view deploy <name> --target devnet --rpc <url> | Deploy to devnet |
viewkit view rollback <name> | Revert to the previous version |
viewkit view rollback <name> --version <N> | Revert to a specific version |
viewkit view delete <name> | Delete the local view bundle |
viewkit wallet generate | Create a new signing wallet |
viewkit wallet inspect | Show the current wallet address |
viewkit wallet import <mnemonic> | Import a wallet from a mnemonic |
Filter operators
When querying a deployed view's output collection, DefraDB supports these filter operators:
| Operator | Meaning | Example |
|---|---|---|
_eq | Equal | { logAddress: { _eq: "0x..." } } |
_ne | Not equal | { event: { _ne: "Approval" } } |
_gt / _gte | Greater than / greater than or equal | { blockNumber: { _gte: 19540000 } } |
_lt / _lte | Less than / less than or equal | { blockNumber: { _lte: 19541000 } } |
_and | Logical AND | { _and: [{ logAddress: { _eq: "0x..." } }, { event: { _eq: "Transfer" } }] } |
_or | Logical OR | { _or: [{ from: { _eq: "0x..." } }, { to: { _eq: "0x..." } }] } |
_like | Substring match (strings) | { arguments: { _like: "%0xAddress%" } } |
_any | Any element in array matches | { topics: { _any: { _eq: "0xddf252..." } } } |
What happens during deploy
When you run viewkit view deploy, the following happens inside core/service/deploy.go:
- Tests the view locally (builds a temporary DefraDB instance, applies the lens, validates output).
- Derives an ECDSA private key from the wallet.
- For each lens: reads the WASM file and base64-encodes it.
- Calls
viewbundle.NewBundler().BundleView(view)to produce compressed VWL wire bytes. - Computes viewID:
keccak256(sender.Bytes(), wireBytes). - ABI-encodes the transaction: method selector
keccak256("register(bytes)")[:4]+ ABI-encoded bytes. - Sends EVM transaction to
0x0000000000000000000000000000000000000210(View Registry precompile). - Polls for receipt and checks status.
VWL wire format
VWL (View Wire Language) is the binary format for view bundles.
Byte layout
The bundle is a single byte stream written in the order below. Each section is appended directly after the previous one.
| # | Section | Contents |
|---|---|---|
| 1 | Header | "VWL" magic (3 bytes), version (1 byte, currently 0x01) |
| 2 | Query | length (u32), GraphQL query string |
| 3 | SDL | length (u32), GraphQL schema string |
| 4 | Lens metadata | count (u16); for each lens: ID (u32), args length (u32), JSON args bytes |
| 5 | Lens blob | codec byte (0 = none, 1 = zstd), blob length (u32), WASM count (u16); for each WASM: length (u32) and bytes |
WASM binaries can be 70-200+ KB each. zstd compression is applied before the bundle goes on chain. The Host client decompresses when applying.
Two implementations of the wire format exist:
| Implementation | Language | Side | Used by |
|---|---|---|---|
| viewbundle-go | Go | Server-side | viewkit, precompile |
| viewbundle | TypeScript | Client-side | Browser-based tooling |
Key functions:
// Encode a view into wire bytes
wireBytes := viewbundle.BundleView(view)
// Decode just the header (without loading the full WASM body)
header := viewbundle.DecodeHeader(encodedBytes)
// Re-encode a modified header
reEncoded := viewbundle.EncodeHeader(decodedValue)
There is a known issue with v0.6.2 of the Host client: it uses viewbundle.UnbundleView() with zstd decompression. Views registered before v0.6.2 may be stored in uncompressed format. If the Host client tries to decompress an uncompressed payload, it fails.
View ID computation
View IDs are deterministic. The same computation runs on the client (viewkit) and on chain (precompile):
viewID = typeName + "_" + keccak256(senderAddress, wireBytes)
Example:
TestView_0xae1bd91e83f5a71ed4c34e18470ea3c12b9ba3d4a69cfd98717e23cf27f4eccb
Because the same computation runs in both places, the client can predict the view ID before the transaction confirms.
Lens authoring
Lenses are WASM binaries, typically written in Rust or AssemblyScript, that transform raw primitive data into structured output.
A simplified Rust example:
fn transform(log: Log) -> Option<USDCTransfer> {
if log.address != USDC_ADDRESS { return None; }
if log.topics[0] != TRANSFER_SIG { return None; }
Some(USDCTransfer {
from: decode_address(log.topics[1]),
to: decode_address(log.topics[2]),
amount: decode_uint256(log.data),
})
}
Lenses must be deterministic. Any Host client running the same lens on the same data should produce identical results.
LensVM supports bidirectional transforms (the inverse() function in the WASM module), though most views use one-way transforms.
Binary size by language
| Language | Typical WASM size | Notes |
|---|---|---|
| Rust | ~200 KB | Preferred for production |
| AssemblyScript | ~73 KB | Easier if you know TypeScript, smaller binary |
Smaller binaries mean less P2P overhead.
Available lenses
Stored in the wasm-bucket repository. Currently available on main:
| Lens | Purpose | Arguments |
|---|---|---|
decode_log | ABI-decodes EVM log events into structured output | {"abi": "[...]"} |
decode_log_str | Same as decode_log but arguments is a JSON string for _like filtering | {"abi": "[...]"} |
decode_function_call | ABI-decodes function calls from transaction input data | {"function_abi": "[...]", "event_abi": "[...]"} |
decode_function_call_str | Same as decode_function_call but arguments is a JSON string | {"function_abi": "[...]", "event_abi": "[...]"} |
See the Lenses guide for details on each lens, output fields, and usage examples.
Writing new lenses
- Rust SDK:
source-gh/lens/sdk-rust/. - AssemblyScript example:
source-gh/lens/tests/modules/as_wasm32_simple/. - WASM runtime paths:
source-gh/lens/host-go/runtimes/wasmtime/,wasmer/,wazero/.
AssemblyScript lenses follow the same interface as Rust lenses. The Host client runtime does not care what language produced the WASM.
The view lifecycle across repos
Five repos are involved in the lifecycle from creation to query:
Generator clients are not involved in the View lifecycle. By the time a View is created and applied, the Generator client has already delivered raw data to the Host clients over P2P.
Key files
| What | Location |
|---|---|
| View deploy logic | shinzo-gh/shinzo-view-creator/core/service/deploy.go |
| VWL encoding/decoding | shinzo-gh/viewbundle-go/ |
| VWL bundler (compress/decompress) | shinzo-gh/viewbundle-go/bundler.go |
| VWL wire codec | shinzo-gh/viewbundle-go/codec.go |
| VWL header-only decode | shinzo-gh/viewbundle-go/header.go |
| WASM lenses | shinzo-gh/wasm-bucket/ |
| Precompile (decode/validate) | shinzohub/app/precompiles/viewregistry/methods.go |
| Rust lens SDK | source-gh/lens/sdk-rust/ |
| AssemblyScript lens example | source-gh/lens/tests/modules/as_wasm32_simple/ |