Concepts
| Term | Meaning |
|---|---|
| Stream | A named KV collection identified by a 32-byte hex stream_id |
| Version | Monotonically increasing number attached to each write batch |
| Access control | Per-stream admin/writer roles, per-key “special key” flag |
| Special key | A key whose writes require an explicit GRANT_SPECIAL_WRITE_ROLE |
MAX_KEY_SIZE— 16,777,216 bytes (16 MiB)MAX_SET_SIZE— 65,536 entries per batchMAX_QUERY_SIZE— 262,144 bytes per read chunk
Read values
KvClient wraps a storage node’s KV RPC. Use the indexer to dynamically discover a storage node, then point KvClient at it:
get_value() returns a Value with data (base64-encoded bytes), size (total byte count), and version (write version), or None if the key doesn’t exist.
Range reads
Traverse keys in order withget_next, get_prev, get_first, get_last:
KeyValue (key + partial value) or None.
Iterator
For cursor-style traversal, useKvIterator:
seek_to_last(), seek_before(key), seek_after(key), prev().
Write values
Writing to KV is a storage operation — the SDK bundles writes into a stream-data blob, encodes it, and uploads it via the normal Flow contract + storage node flow. There are two APIs:StreamDataBuilder— low-level: build aStreamDatablob and upload it yourselfBatcher— high-level: accumulate writes and call.exec()to upload in one shot
Using the Batcher
exec() returns the normal upload result (txHash + rootHash).
Using StreamDataBuilder directly
When you need fine control over read/write/access-control mixing, build the stream data yourself:encoded via ZgFile.from_bytes(encoded) + indexer.upload(file, ..., upload_opts={"tags": tags, ...}).
Access control
KV streams support per-account and per-key permissions. Access control is encoded as a list ofAccessControl operations in the same stream data blob as writes.
Operation types (AccessControlType):
| Value | Operation |
|---|---|
GRANT_ADMIN_ROLE | Make an account admin of the stream |
RENOUNCE_ADMIN_ROLE | Drop your admin role |
SET_KEY_TO_SPECIAL | Mark a key as requiring GRANT_SPECIAL_WRITE_ROLE to write |
SET_KEY_TO_NORMAL | Revert a special key back to normal |
GRANT_WRITE_ROLE | Grant stream-wide write access to an account |
REVOKE_WRITE_ROLE | Revoke stream-wide write access |
RENOUNCE_WRITE_ROLE | Drop your own write access |
GRANT_SPECIAL_WRITE_ROLE | Grant write access to a specific special key |
REVOKE_SPECIAL_WRITE_ROLE | Revoke write access to a specific special key |
RENOUNCE_SPECIAL_WRITE_ROLE | Drop your own special-write access |
Permission checks
Query permissions without writing:Other utilities
kv.get_transaction_result(tx_seq)— fetch the result of a specific KV transactionkv.get_holding_stream_ids()— list streams held by the connected node
API at a glance
KvClient
| Method | Returns |
|---|---|
KvClient(rpc) | client instance |
kv.get_value(stream_id, key, version=None) | Value | None |
kv.get(stream_id, key, start_index, length, version=None) | Value | None |
kv.get_first/last(stream_id, start_index, length, version=None) | KeyValue | None |
kv.get_next/prev(stream_id, key, start_index, length, inclusive, version=None) | KeyValue | None |
kv.new_iterator(stream_id, version=None) | KvIterator |
kv.has_write_permission(account, stream_id, key, version=None) | bool |
kv.is_admin(account, stream_id, version=None) | bool |
kv.is_special_key(stream_id, key, version=None) | bool |
kv.is_writer_of_key/stream(...) | bool |
kv.get_transaction_result(tx_seq) | str | None |
kv.get_holding_stream_ids() | list[Hash] |
StreamDataBuilder
| Method | Returns |
|---|---|
StreamDataBuilder(version) | builder instance |
builder.set(stream_id, key, data) | None |
builder.add_stream_id(stream_id) | None |
builder.build(sorted_=False) | StreamData |
builder.build_tags(sorted_=False) | bytes |
Batcher
| Method | Returns |
|---|---|
Batcher(version, clients, flow, provider) | batcher instance |
batcher.set(stream_id, key, data) | None |
batcher.exec(opts=None) | ({'txHash', 'rootHash'}, err) |
KvIterator
| Method | Returns |
|---|---|
it.seek_to_first() / seek_to_last() | Exception | None |
it.seek_before(key) / seek_after(key) | Exception | None |
it.next() / prev() | Exception | None |
it.valid() | bool |
it.get_current_pair() | KeyValue | None |
Next steps
Merkle trees & proofs
Understand how KV batches are committed to the chain.
Error handling
Exception hierarchy and retry logic.