Changelog

All notable changes to this project are documented here. The format follows Keep a Changelog, and the project uses Semantic Versioning.

[Unreleased]

Changed

  • The development-chain tests (scripts/devchain.sh, -m integration) run on the Robonomics runtime v52 wasm, the newest release that ships one, instead of v50. It has no Sudo, so scripts/seed_auctions.py writes RWS auctions into the genesis state, and the RWS tests are rebuilt around one user story: a new account without XRT is funded by transfer, bids in an auction, waits for the rotation, sets devices and writes datalog through the subscription, for both key types. The para id of the dev spec is now 2000, the one the preset writes into the genesis; with another one v52 panics on the first block. The library itself is unchanged.

[3.1.0] — 2026-10-28

The release of the 3.1 line; the code is that of 3.1.0rc1 (see below for what 3.1 adds).

Checked

  • On Polkadot with 3.1.0rc1 installed from PyPI: an sr25519 account wrote two datalog records, read them back, and an sr25519 integrator opened an encrypted report whose id it read from the datalog. A report for a recipient of the other key type is refused.

  • CI installs the wheel with [sr25519] in python:3.13-alpine on aarch64 and x86_64 without a compiler, and runs the core without the extra.

[3.1.0rc1] — 2026-10-28

sr25519 next to ED25519. The 3.0 API only grows; Keypair.from_mnemonic(m) and every ED25519 package, signature and extrinsic are byte for byte what 3.0.0 produced. The core installs and works without the new extra.

Added

  • The [sr25519] extra (py-sr25519-bindings, rbcl; wheels for musl and aarch64, no compiler), imported lazily: without it, an sr25519 key raises an ImportError that names the extra.

  • SR25519 next to ED25519, Keypair.crypto_type, and a keyword-only crypto_type= (the constant or "sr25519") on from_seed, from_mnemonic, from_uri, from_secret, from_public_key and from_address. Addresses, derivation paths (//, /, ///password) and signatures match substrate-interface and polkadot.js. The default stays ED25519.

  • Extrinsics signed with sr25519 keys; the MultiSignature variant comes from the key.

  • Keypair.encrypt_message / decrypt_message for sr25519: the ECIES scheme of py-sr25519-encryption (Ivan Berman) and SubMessage, the same bytes, so blobs open in both and in polkadot.js. Every failure is a DecryptionError; the MAC is checked before decrypting. The scheme does not authenticate the sender.

  • The report envelope for sr25519: the type of a package is the type of the sender’s key; a mixed recipient list raises RecipientError, and opening with a key of the wrong type raises PayloadError that names the mismatch.

  • Keypair.from_json(backup, password): import of a polkadot.js keyring backup (version 3), ED25519 or sr25519 as the file says. Scrypt parameters are limited to those polkadot.js accepts.

Changed

  • The development-chain tests run for both key types and include an encrypted report end to end. They still use the runtime v50 wasm (Sudo is needed to set the RWS oracle); mainnet runs spec 53, where Sudo is gone, OpenGov and CPS v1.1 are in, and Datalog, RWS, System, Balances and the signed extensions are unchanged. The mainnet smoke test passes on 53 for both key types.

  • CI runs the tests with and without the extra, and in python:3.13-alpine on aarch64 and x86_64 without a compiler.

[3.0.0] — 2026-10-27

The release of the 3.0 line; the API is the one of 3.0.0rc2.

Fixed

  • A request timeout no longer drops the connection when the node still answers a system_health ping. Dropping it ended every subscription on the shared connection, so one slow read (query_map on a Raspberry Pi, say) turned a pending author_submitAndWatchExtrinsic into ExtrinsicOutcomeUnknown. A timeout on a live node is raised as RequestTimeout and is not retried on another endpoint; a node that answers neither is dropped and failed over as before.

Added

  • The Sphinx documentation is back: API reference built from the docstrings, plus the README, MIGRATION.md and the changelog, published to GitHub Pages from main.

Changed

  • Checked against the Robonomics runtime spec_version 52 on Polkadot (Sudo removed from production, OpenGov, CPS v1.1): the metadata, signed extensions, and the Datalog, RWS, System and Balances pallets are unchanged for this library. The offline fixtures are recorded from spec 52. The development-chain tests still run on the runtime v50 wasm, because the production runtime no longer has Sudo.

[3.0.0rc2] — 2026-09-27

From three days of rc1 in rrs-connector, rrs-admin and rrs-ha-integration.

Fixed

  • The default TLS context for wss:// endpoints is built once per client, in a worker thread, and shared by every connection. Before, asyncio built one per connection on the event loop, reading the CA store from disk — Home Assistant reported a blocking call. Passing ssl= works as before.

Added

  • Block hashes given as at= or to client.chain.block_number() are checked (0x + 64 hex digits) before any request; a bad one raises EncodeError.

  • A test pins the API the Report Service projects use; MIGRATION.md lists it under “Stable API”.

  • Integration tests that send transactions refuse a non-loopback node unless ROBONOMICS_DEV_ALLOW_REMOTE=1, and refuse production chains outright.

  • A weekly read-only smoke test against mainnet in CI.

Changed

  • Robonomics on Kusama is legacy and is shutting down. A client refuses a Kusama node by default with “switch to Polkadot” in the reason; with genesis_hash=None it connects and logs a warning. No Kusama constant is provided.

[3.0.0rc1] — 2026-09-24

A rewrite for the current Robonomics runtime. Not compatible with 2.x: see MIGRATION.md for the mapping and the behaviour that changed.

Added

  • Keypair (ED25519) from a mnemonic (with optional password), a raw seed, a Substrate secret URI with hard derivation (//Alice, <phrase>//a//b///password), a public key or an address. from_secret() accepts only what a person should paste: a mnemonic or a seed, never a derivation path.

  • Message encryption (encrypt_message / decrypt_message) byte-compatible with substrate-interface.

  • Multi-recipient envelope: encrypt_for_recipients, decrypt_package, parse_decrypted, in the format Robonomics Report Service publishes.

  • SS58 helpers: encode_address, decode_address, address_format, is_valid_address (format 32 by default).

  • BIP39 English: generate_mnemonic, validate_mnemonic.

  • A typed exception hierarchy rooted at RobonomicsError; input errors are also ValueError. No exception message contains secret material.

  • Runtime metadata (V14) decoded from the chain itself: Runtime describes one runtime version (pallets, storage entries, constants, SCALE encode/decode); RuntimeCache fetches metadata per (genesis_hash, spec_version), parses it off the event loop and reloads it after a runtime upgrade.

  • Storage: every hasher (Identity, Twox64Concat, Twox128, Twox256, Blake2_128, Blake2_256, Blake2_128Concat), multi-key maps and maps keyed by a tuple; query, query_map (paged, pinned to one block, keys decoded back) and constant. An absent value is None for an Optional item and the declared default otherwise.

  • New errors: MetadataError, NoSuchPallet, NoSuchStorage, NoSuchConstant, EncodeError, DecodeError.

  • RobonomicsClient, an asyncio client over one multiplexed WebSocket: endpoints in priority order (e.g. a LAN node first, the public node as fallback), a node is used only if its genesis hash matches Robonomics Polkadot and it is neither syncing nor without peers, failback to a preferred endpoint, a timeout on every request, automatic retry of reads on another endpoint (retry=False for requests with side effects), subscriptions, and runtime-upgrade tracking through state_subscribeRuntimeVersion. query, query_map and constant on the client.

  • Transport errors, all TransportError (retryable): ConnectionFailed, ConnectionLost, RequestTimeout, AllEndpointsFailed (with the reason for each endpoint). RpcError for errors returned by the node.

  • Extrinsics: compose_call checks arguments against the metadata and takes natural values (bytes, Keypair, nested Call, a plain list for BoundedVec — no more [[a, b]] for RWS.set_devices); signed extensions come from the metadata, in its order, and an unknown one that carries data is refused (UnsupportedExtension); mortal era by default (Era); sign, validate (the pool’s own check via state_call), submit (waits for the block and reads its events) and submit_nowait. One submission per account at a time, so nonces never collide.

  • ExtrinsicResult with the block, index and events of the extrinsic.

  • Transaction errors, all TransactionError: InvalidTransaction (kind plus a plain explanation — Payment says the account does not exist on chain), ExtrinsicFailed (pallet error name and docs, e.g. RWS.FreeWeightIsNotEnough), ExtrinsicDropped, ExtrinsicOutcomeUnknown (with the hash to look up).

  • Pallet helpers on the client:

    • client.datalog: index, item (slot 0 is slot 0 — in 2.x get_item(index=0) meant “latest”), latest (also correct after the ring buffer wraps to 0, where 2.x returned None), items (all live records in one request), record (bytes or UTF-8 text, size checked, optionally through an RWS subscription), erase. Records come back as bytes (DatalogItem), never as text-or-hex.

    • client.rws: ledger (Ledger with is_active, days_left, expires_at), devices, is_device, max_devices, set_devices (plain list, checked and de-duplicated, TooManyDevices past the limit), add_devices, remove_devices, call, bid, auction reads.

    • client.system (account → AccountInfo with exists, next_nonce), client.balances (existential_deposit, transfer_keep_alive, transfer_allow_death), client.chain (block hashes and numbers, runtime version). XRT = 10**9.

  • RobonomicsSync, a blocking wrapper for scripts and cron jobs: the async client on an event loop in a background thread, with the same methods, arguments, errors and timeouts (a test keeps the two APIs in step). query_map becomes a plain iterator. Calling it from inside a running event loop raises RuntimeError rather than stalling the loop; closing it stops every thread it started.

  • Tests: offline suites on recorded mainnet data and local fake nodes; integration tests against a development node running the mainnet runtime (scripts/devchain.sh), covering transfers, datalog, sudo, RWS subscriptions and their failures; a read-only mainnet smoke test.

  • py.typed.

Changed

  • Python 3.12+; built with hatchling.

  • Core dependencies are pynacl, scalecodec, websockets and xxhash only; every one installs from a wheel or as pure Python on musl/aarch64.

  • ED25519 is the only key type. sr25519 returns as an optional extra in a later release.

  • Extrinsic failures raise instead of returning a hash: inclusion in a block is checked against the block’s events.

  • Keypair.sign() signs a str as its UTF-8 bytes. substrate-interface decoded a str starting with 0x as hex first; pass bytes.fromhex(...) for that.

Removed

  • The dependency on substrate-interface and, with it, ecdsa, the Ethereum libraries, smoldot-light and the Rust bindings.

  • The 2.x API: Account, ServiceFunctions, BaseClass, ChainUtils, CommonFunctions, the per-pallet classes, Subscriber/SubEvent, TYPE_REGISTRY, and the robonomics_interface CLI.

  • PubSub and ReqRes: the node RPCs behind them are gone since collators moved to polkadot-omni-node.

  • IPFS helpers (web_3_auth, ipfs_32_bytes_to_qm_hash, ipfs_qm_hash_to_32_bytes).