Skip to main content

Example cookbook

These examples favor explicit inputs and cleanup. Online examples read trusted endpoint and account values from environment variables; PyZNN does not provide or select a public node.

Choose the right entry point

GoalRecommended entry point
Read RPC data over HTTPHttpClient plus an API facade
Read RPC data and subscribeWsClient plus API facades
Manage connection and submit a blockZenon
Prepare and publish in separate policy stepsTransact
Build an embedded contract call without I/OAn embedded API builder
Test application code without a nodeA fake transport passed to an API facade

Read the frontier momentum over HTTP

Required environment variable: ZNN_HTTP_URL, containing an http:// or https:// endpoint you operate or trust.

import asyncio
import os

from znn.api.ledger import LedgerApi
from znn.client.http import HttpClient


async def main() -> None:
client = HttpClient(os.environ["ZNN_HTTP_URL"], timeout=15)
try:
momentum = await LedgerApi(client).get_frontier_momentum()
print(
{
"height": momentum.height,
"hash": str(momentum.hash),
"timestamp": momentum.timestamp,
}
)
finally:
await client.disconnect()


asyncio.run(main())

Result type: znn.model.models.Momentum. A non-nullable null, missing field, wrong field type, malformed primitive, or non-canonical decimal string is rejected during response parsing.

Run concurrent typed RPC calls over WebSocket

Required environment variable: ZNN_WS_URL, containing a ws:// or wss:// endpoint.

import asyncio
import os

from znn.api.ledger import LedgerApi
from znn.api.stats import StatsApi
from znn.client.websocket import WsClient


async def main() -> None:
client = WsClient(
os.environ["ZNN_WS_URL"],
connect_timeout=15,
reconnect=True,
reconnect_interval=1,
maximum_attempts=10,
)
await client.connect()
try:
momentum, sync = await asyncio.gather(
LedgerApi(client).get_frontier_momentum(),
StatsApi(client).sync_info(),
)
print(momentum.height)
print(sync.state, sync.currentHeight, sync.targetHeight)
finally:
await client.disconnect()


asyncio.run(main())

The persistent client correlates response IDs, so concurrent requests can complete in a different order from the order in which they were sent.

Read an optional RPC result

Required environment variables: ZNN_HTTP_URL and ZNN_BLOCK_HASH.

import asyncio
import os

from znn.api.ledger import LedgerApi
from znn.client.http import HttpClient
from znn.model.nom.account_block import AccountBlock


async def main() -> None:
client = HttpClient(os.environ["ZNN_HTTP_URL"])
try:
block = await LedgerApi(client).get_account_block_by_hash(
os.environ["ZNN_BLOCK_HASH"]
)
if block is None:
print("block not found")
return
assert isinstance(block, AccountBlock)
print(block.height, block.hash, block.address)
finally:
await client.disconnect()


asyncio.run(main())

get_account_block_by_hash() is nullable. By contrast, get_frontier_momentum() is not nullable and rejects null.

Consume a subscription

Required environment variable: ZNN_WS_URL.

import asyncio
import os

from znn.api.subscribe import SubscribeApi
from znn.client.websocket import WsClient


async def main() -> None:
client = WsClient(os.environ["ZNN_WS_URL"], maximum_attempts=10)
await client.connect()
try:
subscription = await SubscribeApi(client).to_momentums()
async for update in subscription:
print(update)
finally:
await client.disconnect()


asyncio.run(main())

Each yielded value is one item from the notification's result array. The JSON-RPC envelope and subscription identifier are normalized away. On reconnect, the client restores the complete active subscription set before delivery continues.

Subscribe for one account

Required environment variables: ZNN_WS_URL and ZNN_ADDRESS.

import asyncio
import os

from znn.api.subscribe import SubscribeApi
from znn.client.websocket import WsClient
from znn.model.primitives.address import Address


async def main() -> None:
address = Address.parse(os.environ["ZNN_ADDRESS"])
client = WsClient(os.environ["ZNN_WS_URL"])
await client.connect()
try:
subscription = await SubscribeApi(client).to_account_blocks_by_address(
str(address)
)
async for update in subscription:
print(update["hash"])
finally:
await client.disconnect()


asyncio.run(main())

Parsing the address before sending catches invalid prefixes, payload lengths, and checksums locally.

Test typed RPC behavior without a node

API facades accept any transport with an async send_request(method, params) method. This makes application tests deterministic and network-free.

import asyncio

from znn.api.stats import StatsApi
from znn.model.models import SyncInfo, SyncState


class FakeTransport:
async def send_request(self, method, params):
assert method == "stats.syncInfo"
assert params == []
return {
"state": 2,
"currentHeight": 123,
"targetHeight": 123,
}


async def main() -> None:
result = await StatsApi(FakeTransport()).sync_info()
assert isinstance(result, SyncInfo)
assert result.state is SyncState.SyncDone
assert result.currentHeight == 123
assert result.targetHeight == 123


asyncio.run(main())

Return raw wire data from the fake. ApiClient still applies the production result parser, so tests exercise the same required fields and nested types.

Parse and serialize a strict model

from znn.model.models import SyncInfo, SyncState

wire = {
"state": 1,
"currentHeight": 120,
"targetHeight": 123,
}

sync = SyncInfo.from_json(wire)
assert sync.state is SyncState.Syncing
assert sync.currentHeight == 120
assert sync.to_json() == wire

Unknown Python attributes are not populated from wire keys, required fields cannot be removed, and nested models are constructed recursively.

Convert display amounts exactly

from decimal import Decimal

from znn.amount import from_base_units, to_base_units

assert to_base_units("1.23456789", 8) == 123_456_789
assert to_base_units(Decimal("0.00000001"), 8) == 1
assert from_base_units(123_456_789, 8) == "1.23456789"

Do not use binary float for token amounts. Transaction and builder amounts are integer base units.

Create and derive a wallet

from znn.wallet.keystore import KeyStore

store = KeyStore.new_random(passphrase="optional BIP39 passphrase")
account_0 = store.get_key_pair(0)
account_1 = store.get_key_pair(1)

print(store.mnemonic) # back up securely; do not log in production
print(account_0.address)
print(account_1.address)

The derivation paths are m/44'/73404'/0' and m/44'/73404'/1'.

Import canonical entropy

Required environment variable: ZNN_ENTROPY_HEX, containing exactly 16 or 32 bytes of hexadecimal entropy.

import os

from znn.wallet.keystore import KeyStore

store = KeyStore.from_entropy(os.environ["ZNN_ENTROPY_HEX"])
print(store.get_key_pair(0).address)

Other entropy sizes are outside the stable SDK profile and raise ValueError.

Sign text and protocol hashes

Required environment variable: ZNN_PRIVATE_KEY, containing a 32-byte Ed25519 private key as hexadecimal.

import os

from znn.model.primitives.hash import Hash
from znn.wallet.keypair import KeyPair, verify_signature

keypair = KeyPair(os.environ["ZNN_PRIVATE_KEY"])

text_signature = keypair.sign("Hello, aliens").decode()
verify_signature(keypair.public_key, text_signature, "Hello, aliens")

block_hash = Hash.parse("00" * 32)
raw_signature = keypair.sign_hash(block_hash)
assert len(raw_signature) == 64

Text signing preserves the legacy base64 behavior. Account-block signing signs the raw 32-byte hash and returns 64 raw signature bytes.

Encrypt and decrypt a key file

Required environment variables: ZNN_MNEMONIC and ZNN_KEYFILE_PASSWORD.

import json
import os

from znn.wallet.keyfile import decrypt, encrypt, needs_upgrade
from znn.wallet.keystore import KeyStore

store = KeyStore.from_mnemonic(os.environ["ZNN_MNEMONIC"])
password = os.environ["ZNN_KEYFILE_PASSWORD"]

document = encrypt(store, password)
serialized = json.dumps(document)
restored = decrypt(json.loads(serialized), password)

assert restored.get_key_pair(0).address == store.get_key_pair(0).address
assert not needs_upgrade(document)

Store the entire JSON document. Removing the version, timestamp, base address, Argon2 parameters, nonce, cipher name, KDF name, or ciphertext makes the key file invalid.

Build an embedded contract call without I/O

from znn.api.embedded.stake import StakeApi
from znn.model.nom.account_block import AccountBlock
from znn.model.primitives.address import STAKE_ADDRESS
from znn.model.primitives.token_standard import ZNN_ZTS

block = StakeApi().stake(
duration_in_sec=30 * 24 * 60 * 60,
amount=100_000_000,
)

assert isinstance(block, AccountBlock)
assert block.to_address == STAKE_ADDRESS
assert block.token_standard == ZNN_ZTS
assert block.amount == 100_000_000
assert block.data

The builder encodes ABI data and returns an unsigned block. It neither connects to a node nor publishes the block.

Inspect ABI call data

from znn.embedded.definitions import STAKE_ABI

duration = 30 * 24 * 60 * 60
payload = STAKE_ABI.encode("Stake", [duration])
name, parameters = STAKE_ABI.decode_call_data(payload)

assert name == "Stake"
assert parameters == {"durationInSec": duration}

decode_call_data() identifies the function by its four-byte selector and returns decoded custom Zenon types in their canonical string forms.

Prepare, inspect, and publish a send block

Required environment variables: ZNN_WS_URL, ZNN_PRIVATE_KEY, and ZNN_TO_ADDRESS.

import asyncio
import os

from znn.api.embedded.plasma import PlasmaApi
from znn.api.ledger import LedgerApi
from znn.client.websocket import WsClient
from znn.model.nom.account_block import AccountBlock
from znn.model.primitives.address import Address
from znn.model.primitives.token_standard import ZNN_ZTS
from znn.wallet.transact import Transact


async def main() -> None:
client = WsClient(os.environ["ZNN_WS_URL"])
await client.connect()
try:
block = AccountBlock.send(
Address.parse(os.environ["ZNN_TO_ADDRESS"]),
ZNN_ZTS,
100_000_000,
)
transaction = Transact(
os.environ["ZNN_PRIVATE_KEY"],
ledger_api=LedgerApi(client),
plasma_api=PlasmaApi(client),
)

prepared = await transaction.prepare_block(block)
print(prepared.to_json()) # policy or user review point
published = await transaction.publish_block(prepared)
print(published.hash)
finally:
await client.disconnect()


asyncio.run(main())

Do not blindly retry publication after an ambiguous transport failure. Query the account chain by the prepared hash first so the same intent is not submitted twice by application logic.

Submit an embedded builder through the high-level SDK

Required environment variables: ZNN_WS_URL and ZNN_PRIVATE_KEY.

import asyncio
import os

from znn import Zenon
from znn.api.embedded.stake import StakeApi


async def main() -> None:
sdk = Zenon()
await sdk.initialize(os.environ["ZNN_WS_URL"])
try:
unsigned = StakeApi().stake(
duration_in_sec=30 * 24 * 60 * 60,
amount=100_000_000,
)
published = await sdk.send(unsigned, os.environ["ZNN_PRIVATE_KEY"])
print(published.hash)
finally:
await sdk.disconnect()


asyncio.run(main())

Zenon.send() applies the SDK chain identifier, prepares the block, and then publishes it.

Use a custom asynchronous PoW provider

from znn import Zenon
from znn.pow import generate


async def local_worker(data_hash: str, difficulty: int) -> str:
# Replace this with a trusted worker if required. The SDK verifies its result.
return generate(data_hash, difficulty)


sdk = Zenon()
sdk.set_pow_provider(local_worker)

The provider must return exactly 16 lowercase hexadecimal characters. PyZNN verifies the nonce against the data hash and required difficulty before signing.

Generate and verify PoW directly

from znn.pow import generate, verify

data_hash = "00" * 32
nonce = generate(data_hash, difficulty=100, start_nonce=bytes(8))

assert len(nonce) == 16
assert verify(data_hash, difficulty=100, nonce_hex=nonce)

Difficulty zero uses the canonical nonce 0000000000000000.