Skip to main content
peaq-os-sdk on PyPI is the Python equivalent of @peaqos/peaq-os-sdk. Every method and type mirrors the JS SDK; the key differences are listed below. Pass tokenomics20=Tokenomics20Config(deployment_id=...) to the constructor to enter Tokenomics mode: one-transaction activation, machine management, and the 2.0 monetization client. In that mode the legacy registration, mint, bridge, and DID-helper methods raise typed errors instead of running (full list). Without tokenomics20 the client behaves as before. Full-width machine IDs are Python int; serialize them as decimal strings. Tokenomics20Config(deployment_id="peaq-mainnet", creation_home="solana", solana_rpc_url=...) and the [solana] extra cover machines homed on Solana: see Solana.

Differences from the JavaScript SDK

Install

  • Python: ≥ 3.10
  • Dependencies: installed automatically; the main ones are web3>=6.0, eth-account and requests>=2.33.1,<2.34, and some (for example cryptography==50.0.0) are pinned exactly
  • Optional wallet dependency: open-wallet-standard through the [ows] extra
  • Optional Solana dependency: the [solana] extra, needed for Solana reads and writes (account decoding and transaction encoding); a plain import peaq_os_sdk loads no Solana package, and keyless MCR HTTP reads need nothing (see Solana)
  • Virtualenv recommended.
python-dotenv is optional but recommended: PeaqosClient.from_env() reads os.environ and does not load .env itself, so call load_dotenv() at the top of your entry file first.

Environment variables

Same set as the JS SDK: 8 required core vars (RPC URL, private key, and the 6 contract addresses), plus PEAQOS_MCR_API_URL (defaults to http://127.0.0.1:8000), plus 2 optional vars for smart-account deploy (MACHINE_ACCOUNT_FACTORY_ADDRESS) and cross-chain Machine NFT bridging (MACHINE_NFT_ADAPTER_ADDRESS). See JS environment variables and peaq mainnet contracts. For agung testnet addresses see Install → Agung testnet contracts. Bridging is mainnet-only since LayerZero has no DVN routes to agung. For OWS wallet lifecycle helpers, pass a passphrase argument or set OWS_PASSPHRASE. PEAQOS_SVM_RPC_URL and PEAQOS_SVM_NETWORK are CLI variables; from_env() keeps its EVM configuration and does not read them. Pass the Solana endpoint as Tokenomics20Config(deployment_id="peaq-mainnet", solana_rpc_url=...) (see Solana). For Scale, set PEAQOS_ORCHESTRATION_URL (and optionally PEAQOS_API_KEY) before PeaqosClient.from_env() to enable client.orchestration. Full method reference: Orchestration (Python).

Client

PeaqosClient

Tokenomics20Config | None
Tokenomics20Config(deployment_id="peaq-mainnet") or "agung-2026-08-28". Selects the Economics 2.0 deployment and puts the client in Tokenomics mode. The seven contract addresses come from the SDK’s snapshot, resolved once in the constructor with no network call; an unknown ID raises TokenomicsConfigError DEPLOYMENT_UNKNOWN. Addresses are never accepted here. from_env() reads TOKENOMICS_DEPLOYMENT_ID (see from_env). creation_home="solana" and solana_rpc_url= select Solana as the home for new machines and enable Solana reads: see Solana.
str
required
RPC endpoint.
str | None
0x + 64 hex. Required, except on a tokenomics20 client with creation_home="solana": its Solana writes take their signer as an argument (the onboarding script runs keyless), and only register_solana_operator and the other peaq-side writes need it.
str
required
Identity Registry contract address.
str
required
Identity Staking contract address.
str
required
Event Registry contract address.
str
required
Machine NFT contract address (ONFT).
str
required
DID Registry precompile address.
str
required
Batch precompile address.
str | None
MachineAccountFactory contract address. Required only for deploy_smart_account and get_smart_account_address.
str | None
MachineNFTAdapter (LayerZero ONFT adapter) contract address on peaq. Required only for bridge_nft when source="peaq".
str
Defaults to DEFAULT_API_URL. A tokenomics20 client does not read it: the MCR server comes from the deployment record.
OperationalLimits | None
Per-tx and rate-limit caps.
Returns a PeaqosClient instance. __repr__ redacts the private key. Other RPC endpoints are available. See Public RPC endpoints. Errors: ValidationError for a missing rpc_url or a missing or malformed private_key; a malformed contract address raises web3’s ValueError; an unknown tokenomics20 deployment raises TokenomicsConfigError.

from_env

from_env() reads TOKENOMICS_DEPLOYMENT_ID (same spelling as the JavaScript SDK): a non-empty value puts the client in Tokenomics mode, absent or empty stays legacy, an unknown or unreleased ID raises TokenomicsConfigError.
Errors: ValidationError: any required env var missing or empty.

from_wallet

Builds a client whose signing identity is an OWS vault wallet. Mirrors the JS SDK’s PeaqosClient.fromWallet. The remaining keyword arguments (rpc_url, contract addresses, etc.) match the regular PeaqosClient constructor. In OWS-native mode the SDK never holds the private key: OWS decrypts it inside the Rust FFI for each sign_hash call and wipes it immediately. A missing passphrase raises PeaqosError at construction; a wrong one is not detected until the first sign_hash call, because OWS has no verify-only endpoint (with ows_signing=False the key is decrypted at construction, so a wrong passphrase fails there). Each transaction’s chainId (from the tx dict) drives both the CAIP-2 OWS arg and the EIP-155 v value, so the same client transparently signs both peaq (eip155:3338) and Base (eip155:8453) bridge transactions. Errors: PeaqosError when the wallet is missing, the passphrase is wrong (eager-mode) or missing, or OWS itself rejects the request. See OWS signing error codes for the canonical 5 codes mapped from OWSAccount.sign_transaction.

Wallets (OWS)

Wallet lifecycle helpers (create_wallet, import_wallet, import_wallet_mnemonic, list_wallets, get_wallet, export_wallet, delete_wallet) back the Open Wallet Standard integration: mnemonic-backed encrypted vault, multi-chain accounts (peaq, Base, Ethereum, Solana, Bitcoin, etc.). Available under peaq_os_sdk.wallet and as @staticmethods on PeaqosClient. Install with the optional [ows] extra (pip install 'peaq-os-sdk[ows]'). The raw-key constructor and from_env flow keep working unchanged. Full reference on the Wallets page. Wallet returns are typed as WalletInfo (frozen dataclass) with an accounts: list[AccountInfo] field; account_id is CAIP-10 and chain_id is CAIP-2. Both classes are re-exported from the package root.

generate_keypair

Returns tuple (address, private_key). Both are 0x-prefixed hex strings; Address is a NewType over str (checksummed).

OWS wallet lifecycle

OWS wallet helpers are available as static PeaqosClient methods. They derive multi-chain accounts, keep wallet material in an encrypted OWS vault, and return public WalletInfo metadata.
create_wallet, import_wallet, import_wallet_mnemonic, and export_wallet require a passphrase argument or OWS_PASSPHRASE. vault_path can point at a custom OWS vault directory.
dataclass
Frozen dataclass with id, name, created_at, key_type, peaq_address, and accounts. Each account has account_id, address, chain_id, network, and derivation_path. Supported EVM accounts include peaq, Ethereum, Base, Polygon, Arbitrum, and Optimism; missing EVM chain entries are synthesized from the first available EVM address.
export_wallet returns mnemonic or private-key material. Keep it in local administrative tooling; do not expose it through robot control channels.

Accessors (Python-only)


Tokenomics 2.0

Available on a client constructed with tokenomics20. Every machine operation below is a PeaqosClient method and a package-root function taking the client as its first argument; the deployment and machine-ID helpers are standalone package-root functions. Concepts: Economics 2.0.

Deployments

resolve_tokenomics20_deployment(deployment_id, overrides=None) cross-checks an optional role -> address mapping against the snapshot and raises ADDRESS_OVERRIDE_MISMATCH on any difference. Overrides never introduce addresses. The PEAQ token is resolved at call time from InfoDesk.peaqToken().

activate_machine

One transaction to MachineStateAndSync.activateMachine: mints the ERC-721 in MachineRegistry (token ID equals machine ID), stores the DID document, bonds the tier in MachineSubscription, and records the home chain. The signer becomes owner and bond payer.
Returns ActivateMachineResult with machine_id, owner, controller, tier, bond_amount, voucher_credit_applied, net_peaq_amount, transaction_hash, is_homed_locally, the receipt, and the correlated events. Amounts come from the receipt, not the preflight quote. Success requires MachineOnboarded, MachineMinted, and Activated from the right contracts plus a matching post-state read. Sequence: local validation, chain and bytecode checks, InfoDesk.peer(role) match, isEconomicAuthority() (False raises NOT_ECONOMIC_AUTHORITY), compute ID, both MachineStateAndSync technical pause flags (TECHNICALLY_PAUSED before any approval), quote, resolve PEAQ, balance and allowance, approve exactly the net if short (spender MachineSubscription), re-read everything, simulate, submit once, correlate, reconcile. The write is never retried. cancel is honoured up to submission. Errors: ValidationError, TokenomicsConfigError, TokenomicsActivationError (codes below), TokenomicsPendingTransactionError (PENDING_TRANSACTION, carries .submitted).

activate_machine_with_usdt

Same activation, bond settled in USDT through SubscriptionTokenProvisionPool (the allowance spender). Not usable on peaq mainnet: the pool has no USDT token configured, so the USDT quote and the write both revert. Requires max_usdt_amount, taken from preview_machine_activation_with_usdt(...).max_usdt_amount after applying your slippage_bps. The bond, credit, and net stay in PEAQ; a fully credited bond converts nothing.

preview_machine_activation, preview_machine_activation_with_usdt

Runs the same validation and reads without signing, approving, or writing. Returns machine_id, bond_amount, voucher_credit, net_peaq_amount, balance, approval_required. Raises MACHINE_ID_MISMATCH when expected_machine_id disagrees and MAX_NET_PEAQ_EXCEEDED when the bound is already exceeded; a low balance is returned, not raised.

compute_machine_id

uint256(keccak256(abi.encode(machine_type, credential_subject))), read from MachineRegistry.computeTokenId.

Reads

A machine-keyed read that finds no peaq record but a machine homed on another chain raises MACHINE_HOMED_ELSEWHERE naming the chain, not MACHINE_NOT_FOUND: see Solana.

Lifecycle and subscription

RenewMachineParams, RenewMachineWithUsdtParams and PreviewMachineRenewalParams take an optional tier (0 entry, 1 basic, 2 pro), but neither peaq-mainnet nor agung-2026-08-28 enables tier selection on renewal: passing any tier, even the stored one, raises RENEWAL_TIER_UNSUPPORTED before an RPC call. Omit tier to renew at the stored tier. On a deployment that enables it, the preview reports stored_tier, requested_tier and tier_change_allowed (true from Grace or Runoff), a change while Active raises TIER_CHANGE_WHILE_ACTIVE, and a tier change is priced at the full bond of the new tier. PEAQ writes require max_net_peaq_amount, USDT writes max_usdt_amount; take both from the matching preview. USDT writes are not usable on peaq mainnet (no USDT token configured in the pool). The SDK re-quotes before simulation and never raises an accepted bound. Owner or controller may sign; credits accrue to the owner.

Ownership (ERC-721)

Transfer changes the owner and retains the DID controller. Blocked while relocating.

DID updates

Setters replace whole arrays. Shrinking verification methods below a live authentication index raises AUTHENTICATION_REWRITE_REQUIRED before submission.

Previews and reconciliation

Reconciliation is read-only and returns pending | confirmed | failed | conflicting. A hash with no receipt is always pending; the SDK never infers a dropped transaction from age, mempool absence, or nonce.

Machine-ID helpers

Canonical base-10 strings at every JSON, URL, log, and cache boundary. bool is rejected wherever an int machine ID is expected.

Disabled in Tokenomics mode

Enabled in Tokenomics mode: submit_event, batch_submit_events (Events) and query_mcr, query_machine, query_operator_machines (Queries). Still available in Tokenomics mode: stream, provisioning, wallets, heartbeat, orders, other orchestration calls. wait_for_bridge_arrival is not gated (it takes no client) and only emits a DeprecationWarning. peaq_os_sdk.bridge.quote_send takes a bare contract, is not gated and emits no warning.

Solana

Machines can be homed on Solana. There is no separate Solana client and no machine-keyed method gained a chain argument: the existing Tokenomics 2.0 methods resolve the machine’s home from its ID, and a solana= options object selects the native path where a write needs a Solana signer. Requires pip install "peaq-os-sdk[solana]" for anything that encodes a Solana transaction; a plain import peaq_os_sdk loads no Solana package. Walkthrough: Onboard a machine on Solana.

Configuration

A Solana-capable client is the ordinary client with creation_home="solana" and the Solana endpoint in Tokenomics20Config. This is how the runner builds it, keyless, from the machine directory’s .env (need reads one variable or stops):
Of the public records, only peaq-mainnet has a Solana half (agung-2026-08-28 has none, DEPLOYMENT_UNAVAILABLE); resolve_tokenomics20_solana_deployment("peaq-mainnet", solana_rpc_url) from peaq_os_sdk.tokenomics.deployments returns it (protocol_chain_id == 5, layerzero_eid == 30168). The RPC endpoint is never defaulted, and the SDK compares its getGenesisHash with the record before the first account read, because the same program IDs exist on more than one cluster. private_key may be omitted for a client that only previews or reads. Anchor IDLs for the programs ship as package data with a MANIFEST.json (source commit, sha256 per file); layout skew is detected at decode time (discriminator plus exact byte consumption), never zero-filled. Machine IDs stay full-width int, cross to Solana as 32-byte big-endian words, and the DID is did:peaq:<decimal machine id>.

Reads

A machine-keyed read that finds no peaq record asks peaq where the machine lives and raises TokenomicsActivationError with code="MACHINE_HOMED_ELSEWHERE" naming the chain, instead of MACHINE_NOT_FOUND. A machine that exists nowhere still raises MACHINE_NOT_FOUND with its decoded revert; an unreachable node still raises READ_FAILED. Solana reads wait out a rate limit (HTTP 429) and a server behind the slot a read needs (JSON-RPC -32016) and retry, up to five attempts, never for a send; a server that stays behind is named in the final READ_FAILED. get_machine_owner keeps its EVM return type: a 32-byte Solana key is never cast into a 20-byte address. Native identity comes from peaq_os_sdk.tokenomics.svm_results: MachineHomeChain (chain, protocol_chain_id, state, provenance), MachineNativeIdentity (owner, did, controller, evm_operator, manufacturer, machine_type, is_relocating), MachineMirrorEvidence. Resolution states: resolved, reserved, in_flight, unknown, conflict, unavailable (with reason: missing_configuration, missing_dependency, missing_capability, conflicting_evidence). Absent is not withdrawn, not zero, not disabled; unavailable is not unknown.

Onboarding

Terminal-first (SDK 0.11.2 and newer): the Solana owner pays the bond on Solana, in PEAQ or USDC, through machine_subscription_terminal, and peaq books it. The link push back to peaq is delivered by peaq’s Trust Validator node (the node), with no messaging fee. Both rails run on production. The phases, in order: The example is the script that onboarded the proven Python machines, given in full on the guide’s Python tab (the collapsed block under its run command, to copy): one loop over get_machine_activation_state that does whatever next_step names until complete, journaling every write. Its two write calls:
COMPUTE_UNIT_LIMIT is the mint’s compute unit limit. Since 0.11.2 the value to pass is the plan’s suggestion, plan.native.suggested_compute_unit_limit; before, scripts passed 600,000 (see The mint below).
  • Registration. register_solana_operator spends peaq gas only (about 0.01 PEAQ; 98,688 and 105,432 gas measured) and waits for the node to mirror the binding (1 to 2 minutes measured). A wallet already bound to the signer is reconciled, and nothing is sent. A wallet bound to another peaq address is refused OPERATOR_BOUND_ELSEWHERE. get_solana_operator_registration(wallet, peaq_operator=None) reads the state keyless: unbound, bound, bound_elsewhere or mirroring. Confirmation is the OperatorAddressBound event in a final block, never receipt status.
  • The quote. quote_solana_activation(identity, *, tier, pay_in, wallet, slippage_bps=100, peaq_operator=None) is keyless and refuses in the program’s order (OPERATOR_NOT_REGISTERED, REQUEST_ALREADY_OPEN, MACHINE_ALREADY_ACTIVE, ROUTE_NOT_WHITELISTED, INSUFFICIENT_BALANCE …). It returns the bond, max_in (the suggested escrow: the bond plus slippage_bps; on USDC the pool’s spot estimate with its fee plus the cushion), the wallet’s balance, the rents and peaq’s preflight. Passing peaq_operator quotes a wallet not registered yet, for that operator (quote.registration == "assumed").
  • The priority price. ActivateSolanaTerminalParams, quote_solana_link_push and preview_solana_settlement default compute_unit_price_micro_lamports to 0, and PreviewSolanaMachineActivationParams requires it. Pass 10_000, as the script does: a write without priority can be dropped by Solana’s public endpoint under load. Since 0.11.2 the SDK re-sends a write that was lost on its way. While the write’s receipt wait sees the signature unknown and the blockhash still valid, the SDK sends the same signed bytes again every 2 s, at most 40 times. The same bytes carry the same signature, so the cluster processes the write at most once and nothing can be spent twice. The bytes stay in this process’s memory: a rerun in a new process re-sends nothing and reconciles the journal row. A re-send’s errors are ignored; the receipt decides. A mint that never lands at all is still proven dead by the recovery (SDK 0.11.1, NativeOnboardingReconciliation.may_resubmit) and minted again. At 10,000, with the mint’s limit sized from its simulation, the whole onboarding pays about 4,900 lamports of priority fees on PEAQ and about 7,900 on USDC.
  • request. Escrows max_in_amount (the quote’s suggestion when omitted) in the pay-in token and freezes the bond. On PEAQ the escrow must cover the bond (ESCROW_BELOW_BOND). Rent: the request (2,169,160 lamports), its escrow account (1,488,440) and, on the machine’s first request, its SubscriptionTerminal (1,071,880), against max_native_rent_lamports. One request per machine: a rerun with one open reconciles it. A request the node does not credit within its hour ends exited with REQUEST_EXPIRED. The node cancels that request once it can, returning the escrow and the request’s rents, or the owner cancels it with cancel_request. A rerun after the request was cancelled (by the owner, or by the node once it expired) or refunded opens a new request at the next nonce (0.11.2): the request phase reads the machine’s terminal record first, and a request that is gone, with its nonce no longer open and no live subscription, is spent, not taken for a live one. A request closed after the mint stays done.
  • finalise. The request decides the path: finalise_in_peaq (the bond to the terminal’s vault, the rest back in PEAQ), finalise_without_swap (a credit covers the bond), or on USDC finalise_activation, a v0 transaction against the record’s finalise lookup table that buys exactly the bond through the provision pool’s Raydium route. The swap is quoted by simulating the exact instruction; the cap sent is that cost plus slippage_bps, never above the escrow, and the USDC remainder comes back in USDC. result.settlement (SwapSettlementQuote: simulated_in_amount, max_in_amount, escrow_amount, slippage_bps, lookup_table, lock_in_table) says how a USDC settlement was priced; on production the swap paid exactly its simulation (200,807 USDC base units). preview_solana_settlement(identity, wallet=...) runs the same preparation keyless; verify_solana_finalise_table() checks the table alone (FinaliseTableCheck: address, entries, lock_in_table). Refused before signing: REQUEST_CLOSED (the request was cancelled or refunded; 0.11.2), REQUEST_NOT_COMMITTED, COMMIT_EXPIRED, NOT_ACTIVATION_PAYER, FINALISE_TABLE_MISSING / FINALISE_TABLE_STALE, TICK_ACCOUNTS_INVALID, SLIPPAGE_EXCEEDED. After a finalise there is no cancel: if peaq refuses the bond, the refund comes back in PEAQ on both rails (exit == "ACTIVATION_REFUNDED", with result.refund).
  • Results. Each terminal call returns SolanaTerminalActivationResult: status is complete, pending (unresolved, advice) or exited (a named exit such as REQUEST_EXPIRED, with the way out in advice). confirmation.spend is what the write moved for its payer (lamports with the fee, token deltas per mint). confirmation.send_count (SendCount(sends, resend_errors), 0.11.2) counts the sends this process made for the write; it is None for a row this process did not send. cancel_request returns the escrow and the request’s rents from a Pending or Committed request. On a request already cancelled or refunded, cancel_request run alone is refused with REQUEST_CLOSED; a cancel this journal recorded still reconciles complete.
  • REQUEST_CLOSED (0.11.2). A phase run alone on a request that was cancelled or refunded is refused with this code before anything is signed: the credit and bond waits, finalise and preview_solana_settlement, and cancel_request. It replaces REQUEST_NOT_COMMITTED, STATE_MISMATCH or REQUEST_NOT_CANCELLABLE in that situation. Nothing is left to wait for, settle or cancel; the request phase opens a new request at the next nonce. A refund still ends the bond wait as ACTIVATION_REFUNDED.
  • Inputs and plan. SolanaOnboardingInputs is the machine as the mint and the link push need it: owner, controller, manufacturer, DID lists, identity, the two native ceilings, and optional expected_machine_id and evm_operator assertions. No operator, tier or funding: the registration and the request carry them. inputs.identity is the SolanaMachineIdentity the terminal phases take. preview_machine_activation(PreviewSolanaMachineActivationParams(...)) returns SolanaOnboardingPlan, read at one pinned finalized peaq block, with seven stages (registration … linkage), each with action (estimate, reconcile, observe, blocked, unavailable), signer, unresolved and expected_wait_seconds.
  • The mint. native_onboarding mints against the Bonded request at the terminal’s last_nonce; the record’s evm_operator is the request’s peaq operator. Refused before signing: SUBSCRIPTION_NOT_ELIGIBLE, ACTIVATION_NOT_BONDED, NOT_ACTIVATION_PAYER, EVM_OPERATOR_MISMATCH, TECHNICALLY_PAUSED. Get signers with get_solana_onboarding_signer_requirements(inputs, plan, phase) and solana_signer_from_wallet(name, passphrase, chain_id=required.chain_id). plan.native.suggested_compute_unit_limit (0.11.2) is the value to pass as the mint’s compute_unit_limit: the plan’s simulated units × 1.3, rounded up to the next 1,000, at least 50,000 and at most the cluster maximum. Before, scripts passed 600,000; on production the mint used 99,009 of them on 2026-10-08. A mint whose receipt failed with ComputationalBudgetExceeded (reason compute_budget_exceeded), with the Machine and MachineState accounts absent, is minted again by the rerun, like a mint that never landed: the reconciliation marks it failed_without_effect, and no_effect covers both cases. Every other failed mint stays failed.
  • The link push. The SDK reads the route from the chain before every push. quote_solana_link_push(machine_id, payer=...) quotes it keyless: transport is trust_validator (production: native_fee_lamports 0, compute_unit_limit 120,000, the push account’s one-time rent of 1,005,840 lamports) or layerzero; status already_pushed (code LINK_ALREADY_PUSHED) means nothing is left to send. On the node’s route max_link_push_fee_lamports is not needed; on a LayerZero route it is, and a push without it raises ValidationError on that field before signing. Refused before signing: TV_OUTBOX_MISSING (no outbox for peaq on the adapter), TV_INBOUND_REFUSED (peaq refuses the node’s deliveries), FEE_LIMIT_EXCEEDED, RENT_LIMIT_EXCEEDED, INSUFFICIENT_NATIVE_BALANCE (the owner cannot pay the push and keep its own rent-exempt minimum, 650,240 lamports for a plain wallet), and on a LayerZero route BRIDGING_DISABLED or LINK_CONFIG_UNAVAILABLE. The confirmed push carries the node’s queue entry: transport, tv_nonce, sequence.
  • Complete. When peaq’s MachineBridgeAdapter.lastAppliedMirrorPushSeq for the machine reaches the onboarding push’s sequence: a status read’s next_step.phase == "complete". reason is link_applied, or later_push_pending while a later Solana push is in flight; both are complete. state.tv_relays lists the pushes that went over the node (TvRelayEvidence: signature, dst_eid, nonce, applied); state.messages lists LayerZero messages and is empty on the node’s route. The Solana receipt of the push proves the send, not the delivery.
  • Status read and recovery. Two option classes for get_machine_activation_state: SolanaMachineActivationStatusParams(inputs, records=...) names the next step from the chain (the loop’s status read), and SolanaOnboardingRecoveryParams(inputs, records) reconciles the journal’s rows into recovery.history.records, which the native phases take. Both take every journal row; neither signs.
  • Journal and recovery. One journal, peaq-native-onboarding-attempt/v2: PeaqRegistrationTransaction, TerminalActivationAttempt, NativeOnboardingTransaction, LinkPushTransaction. The three Solana rows are handed to on_transaction_submitted before broadcast, the registration row right after its broadcast, before its receipt is read; an uncertain broadcast raises TokenomicsPendingTransactionError with the row on .submitted. serialize_solana_onboarding_attempt / decode_solana_onboarding_attempt / restore_solana_onboarding_history in peaq_os_sdk.tokenomics.solana_onboarding_attempts; every integer a decimal string, no keys or payloads. Pass every row as records to status reads and the native phases. A sent transaction is waited for and reconciled. The process that signed it may re-send the same signed bytes while the write can still land (0.11.2); a rerun in a new process re-sends nothing. get_machine_activation_state(machine_id, solana=SolanaMachineActivationStatusParams(inputs, records=...)) names the next step from the chain (registration, request, credit, finalise, bond, cancel_request, native_onboarding, linkage, complete) and its actor. After an earlier request was cancelled, refunded or closed (the status read’s observations.terminal.request_state), the request phase reads the machine’s terminal record first and opens a new request at the next nonce (0.11.2); before 0.11.2 it took the old row for a live request. The script’s for_request also leaves that request’s rows (nonce at or below observations.terminal.terminal.last_nonce) out of the new call’s records.
  • What ran. get_solana_onboarding_history(machine_id, *, timeout_seconds=...) rebuilds the onboarding from the chains alone, keyless: SolanaOnboardingHistory with rows (OnboardingHistoryRow: step, actor owner / operator / node / layerzero / other, slot or block, cost to its payer, the owner’s own change whoever paid in owner_lamports_delta and owner_token_deltas, explorer links) and totals per payer (OnboardingPayerTotal: payer, chain, lamports_delta, token_deltas, gas_wei, transactions; the owner’s is its net change, refunds from others’ transactions included). The CLI’s machine history --json adds a role per payer; the Python object has none. A cancel the node sends reads actor node. A later machine of the same owner lists the registration and binding with reused=True, outside its totals. Sources it could not read are named in unread; a rerun reads them. Right after a run the public endpoints are busy: the script allows 600 s. confirm_solana_onboarding_receipts(records) reads back the registration’s and the terminal writes’ receipts from a journal (SolanaOnboardingReceipt, with gas_wei or the terminal spend), for a cost report after a resume.
  • Migrating from 0.10.x. reservation, subscription, from_block, PeaqOnboardingFunding, UsdtOnboardingFunding, SolanaReservationStatusParams and the operator, tier and funding fields of SolanaOnboardingInputs are gone. v1 journal rows restore as read-only history and are never resumed.

Management

The module-level functions exported from peaq_os_sdk take the native path: transfer_machine, suspend_machine, resume_machine, set_machine_controller, set_machine_verification_methods, set_machine_authentication, set_machine_service_endpoints and set_machine_evm_operator accept solana=SolanaMachineWriteParams(signer=..., max_fee_lamports=...) with the client as first argument, for example transfer_machine(client, from_pubkey, to_pubkey, machine_id, solana=SolanaMachineWriteParams(...)). The PeaqosClient methods of the same names are the peaq path and take no solana argument; set_machine_evm_operator exists only as the module-level function and requires solana=. The signer is an argument, not client configuration, because Solana’s key space and the EVM account are unrelated. approve_machine and safe_transfer_machine run the EVM write preflight first (signer, chain, bytecode, peers) and raise TokenomicsUnsupportedError SOLANA_OPERATION_UNSUPPORTED once the machine resolves to Solana; no write happens, but a signer, deployment or RPC problem surfaces before that code. set_machine_approval_for_all is owner-wide, has no machine to resolve and keeps its EVM behaviour: with an EVM signer it submits setApprovalForAll on peaq. Results report instead of raising once a transaction landed: status is confirmed, conflicting (with conflicts), failed or pending; pending means not yet known. set_machine_evm_operator returns local and synchronization (synchronized, pending, unavailable) separately; only peaq’s lastAppliedMirrorPushSeq for source chain 5 proves application. Clearing a controller needs the default key plus confirm_clear_controller=True; the operator link has no clear. DID setters replace whole lists (8 / 8 / 8, 128 UTF-8 bytes per string) and resize the account, so rent moves to or from whoever signed (preview.rent). Journal the NativeManagementTransaction from on_transaction_submitted before broadcast (serialize_native_management_transaction); reconcile with SolanaManagementReconciliationContext, where expired is the only status with may_resubmit=True, and hand a known unreconciled attempt back as SolanaMachineWriteParams(previous=attempt) so the next write refuses instead of doubling.

Events

submit_event and batch_submit_events resolve each machine and route the batch to its home chain; a Solana-homed machine is written to the Solana EventRegistry program with the owner’s or controller’s OWS Solana signer (assign it first: client.solana_signer = PeaqosClient.solana_signer_from_wallet(name, passphrase), otherwise SIGNER_UNAVAILABLE), needs the [solana] extra, and a batch mixing peaq-homed and Solana-homed machines is refused before either chain is written. The native pipeline reads the gate accounts in one batch, prices the rent shortfall (the first event for a machine funds its event-log account once), simulates, signs, broadcasts and reports completion only when the emitted EventSubmitted events correlate with the instructions. Program errors decode by name from the packaged IDL (NotOwnerOrController, SubscriptionNotEligible, Paused, MachineRelocating). submit_event keeps the EVM return shape, a (signature, data_hash) tuple with the base58 signature in place of the transaction hash; batch_submit_events returns the base58 signatures as strings. The typed SolanaEventSubmissionResult (slot, confirmation level, finalized only for finalized evidence, event index, event log, submission ID) is not returned by either entry point: submit_native_event(client, params, on_broadcast=..., cancel=...) from peaq_os_sdk.events returns it for one event, preview_event_submission(client, params, submitter=...) prices and checks an event without unlocking a wallet, and resolve_event_submission(client, attempt) reconciles an attempt journaled from on_broadcast, read-only (confirmed, expired_unlanded, unresolved). Walkthrough: Submit events: machines homed on Solana. An unavailable confirmation is unresolved with its signature preserved, never a failure. Rate-limit usage is held as per-attempt reservations shared by both chains.

MCR API compatibility

Three breaking changes, required by the 2.0 server’s contract and not optional in Tokenomics mode: revenue_trend and average_revenue_per_event may be None (keys always present; None means not computable, never substitute "stable" or 0; total_revenue stays non-null), a None or non-integer mcr_score raises ApiError BAD_RESPONSE, and MonetizationState.signer accepts a base58 key. Additive Tokenomics-only fields, absent when the server omits them: home_chain (protocol ordinal, Solana is 5, SOLANA_PROTOCOL_CHAIN_ID; not 3338, not 30168, not the InfoDesk ChainKind 3), rating_unavailable, and on the profile owner (base58 for a Solana machine, never pass it to an EVM validator), controller, machine_type, relocating. operator stays a did:peaq:0x… DID or None on every home. ApiError gains http_status, server_code, server_message, operation, home_protocol_chain_id, retryable; 400, 401/403, 404, 409 and 503 map to INVALID_REQUEST, UNAUTHORIZED, NOT_FOUND, MACHINE_HOMED_ELSEWHERE or CONFLICT, SERVICE_UNAVAILABLE (MCR_HTTP_ERROR_CODE). Solana monetization signing (MonetizationConfig(solana_signer=...), five-line message with home_chain: 5, raw Ed25519 sign_message) is implemented. When solana_signer is set, the SDK reads the machine’s home chain first (a compatibility GET and a profile GET) and refuses a Solana-homed write with ValidationError SOLANA_MONETIZATION_UNVERIFIED (MONETIZATION_REFUSAL_CODE) before signing. Without a Solana signer or a SetMonetizationOptions.accepted_context (from peaq_os_sdk.client import SetMonetizationOptions), the SDK does not check the home chain and sends the EIP-191 write; with accepted_context it reads the profile and checks home and owner before every attempt, EVM writes included.

Registration

This is the Tokenomics 1.0 path. register_machine and register_for address IdentityRegistry (1 PEAQ native bond, separate Machine NFT). They emit DeprecationWarning and raise TokenomicsUnsupportedError on a client constructed with tokenomics20. New machines use activate_machine.

register_machine

Returns the newly allocated machine_id. The SDK decodes it from the Registered event in the transaction receipt. 1 PEAQ is sent as msg.value: the payable register() function auto-bonds the machine. Errors: RpcError: chain revert (AlreadyRegistered), insufficient balance for gas + 1 PEAQ bond, or any other on-chain failure. See errors.

register_for

str
required
Machine EOA to register. The caller acts as proxy operator.
Returns the newly allocated machine_id for the proxied machine. 1 PEAQ is sent as msg.value. Errors: ValidationError on invalid machine_address. RpcError on chain revert (AlreadyRegistered, InvalidMachineAddress).

Gas Station

setup_faucet_2fa

str
required
Owner to enroll.
str
required
Gas Station base URL.
'svg' | 'png'
QR format. Defaults to "svg".
TypedDict
Keys: owner_address: str, otpauth_uri: str (OTP auth URI for authenticator apps), qr_image_url: str (expires after ~2 minutes).
Errors: ValidationError if owner_address or faucet_base_url is empty. ApiError for INVALID_OWNER_ADDRESS, QR_GENERATION_FAILED, NETWORK_ERROR, or an unexpected response envelope. See errors.

confirm_faucet_2fa

Errors: ValidationError, ApiError (INVALID_2FA, 2FA_NOT_CONFIGURED, 2FA_LOCKED).

fund_from_gas_station

str
required
2FA-enrolled owner address.
str
required
Machine EOA to fund.
str
required
Chain identifier configured on the faucet, e.g. "peaq".
str
required
Current TOTP.
str
required
Gas Station base URL.
str | None
UUID idempotency key. Auto-generated if omitted.
union
Discriminated union on status. Either a FundedResponse ({status: "success", tx_hash: str, funded_amount: str}, funded_amount is decimal wei) or a SkippedResponse ({status: "skipped", current_balance: str, min_gas_balance: str}). request_id is a request-side idempotency key passed by the caller; the response does not echo it back.
The JS SDK does echo requestId back, in both success and skipped responses. Do not read it from the response in code that has to work with both SDKs. Errors: ValidationError, ApiError (the /faucet/fund codes from the faucet table, plus NETWORK_ERROR and UNEXPECTED_RESPONSE). See errors.

NFT & DID

This is the Tokenomics 1.0 path. In Tokenomics mode mint_nft and token_id_of raise TokenomicsUnsupportedError (minting happens inside activate_machine; the machine ID is the token ID) and the DID writers raise TokenomicsIntegrationUnavailableError. Use the DID setters instead. Machine NFT minting, token-ID lookup, and the two canonical DID attribute writers. The DID writes are submitted as a single atomic batchAll transaction via the peaq Batch precompile.

mint_nft

Mints a Machine NFT on the MachineNFT contract for a registered, bonded machine.
int
required
Registered machine ID. Must be a positive integer.
str
required
0x-prefixed 20-byte hex address that will receive the NFT.
str
Transaction hash as a hex string. With web3 7 or later (what pip install resolves today) it has no 0x prefix; add it before passing the hash to an RPC call or explorer.
Errors: ValidationError on non-positive machine_id or invalid recipient. RpcError on chain revert (MachineNotBonded, AlreadyMinted, NotMachineOwner) or transaction failure.

token_id_of

Reads the NFT token ID assigned to a registered machine via a view call.
int
required
Registered machine ID. Must be a positive integer.
int
The NFT token ID, or 0 when no NFT has been minted for this machine.
Errors: ValidationError if machine_id is not positive. Like the JS SDK, Python returns 0 when the machine has no NFT; check for 0 in both.

write_machine_did_attributes

Atomically writes the six canonical Machine DID attributes to the caller’s DID via a single batchAll transaction.
int
required
Registered machine ID.
int
required
NFT token ID assigned to the machine.
str
required
Operator DID reference. May be an empty string. ASCII, ≤ 2560 bytes.
str
required
Non-empty ASCII URL, ≤ 2560 bytes.
str
required
Non-empty ASCII URL for the machine’s data API, ≤ 2560 bytes.
'public' | 'private' | 'onchain'
required
Visibility setting.

write_proxy_did_attributes

Atomically writes the two canonical Proxy DID attributes (machineId, machines) to the caller’s DID.
int
required
The proxy operator’s registered machine ID.
list[int]
required
Non-empty list of positive machine IDs. The JSON-encoded array must be ≤ 2560 bytes.

read_attribute

Reads a single DID attribute directly from the peaq DID precompile. Not exported from the package root: import from peaq_os_sdk.did.did_precompile.
str
required
The machine address whose DID is being read.
str
required
Attribute key, e.g. "machineId", "data_visibility", "machines".
TypedDict
Keys: name: str, value: str, validity: int (the absolute block number the attribute is valid until, not the valid_for duration), created: int (block timestamp). A missing attribute raises the underlying web3 exception (for example web3.exceptions.Web3RPCError with revert Cannot find the item), not RpcError.

Smart accounts

ERC-4337 smart accounts deployed via the MachineAccountFactory. Requires the client to be constructed with machine_account_factory (or MACHINE_ACCOUNT_FACTORY_ADDRESS when using from_env).

deploy_smart_account

Deploys a smart account via MachineAccountFactory.createAccount and returns the deployed address.
str
required
EOA that will own the smart account.
str
required
Machine EOA the account is scoped to.
int
required
Non-negative CREATE2 salt.
Errors: ValidationError on invalid param or client constructed without machine_account_factory. RpcError on revert or missing AccountCreated event.

get_smart_account_address

Read-only equivalent: computes the CREATE2 address without deploying. Identical result to deploy_smart_account for the same inputs.
Same parameters as deploy_smart_account. No transaction, no gas.

Bridge

Tokenomics 1.0 path. bridge_nft and wait_for_bridge_arrival move Tokenomics 1.0 Machine NFTs between peaq and Base. They serve machines already onboarded under Tokenomics 1.0. New machines use activate_machine, and a 2.0 machine is not bridged.
LayerZero v2 Machine NFT bridging between peaq and Base. Requires machine_nft_adapter (or MACHINE_NFT_ADAPTER_ADDRESS) when sending from peaq. In Tokenomics mode bridge_nft raises MACHINE_RELOCATION_UNAVAILABLE: Economics 2.0 relocates whole machine records and that is disabled on chain. A 2.0 machine that should live on Solana is created there, not bridged: see Solana. Portability of the 1.0 NFT: Machine NFT cross-chain portability.

bridge_nft

Bridges a Machine NFT from source to destination. On the peaq→Base path the SDK runs an ERC-721 approval pre-flight: it reads MachineNFT.getApproved(token_id) and submits a one-shot approve(adapter, token_id) if the token isn’t already cleared for the adapter. The Base→peaq path uses burn-and-unlock and needs no approval. Callers don’t handle approvals themselves.
int
required
Positive NFT id to bridge.
'peaq' | 'base'
required
Origin chain.
'peaq' | 'base'
required
Target chain. Must differ from source.
str
required
Destination-chain recipient address.
str | None
Base RPC URL. Required only when source == "base".
str | None
MachineNFTBase address on Base. Required only when source == "base".
bytes
Raw LayerZero v2 extraOptions bytes.

wait_for_bridge_arrival

Static method that polls the destination chain’s MachineNFT.ownerOf(token_id) every 10 seconds until a non-zero owner returns or the timeout elapses. Does not require a client instance.
str
required
Destination-chain RPC endpoint.
str
required
MachineNFT contract address on the destination.
int
required
The NFT id expected to arrive.
int
Wait budget in seconds. Defaults to 300 (5 min).

Events (Qualify)

Both calls write to the event_registry address the client was constructed with (EVENT_REGISTRY_ADDRESS); the SDK adds no 2.0-specific address. They resolve each machine’s home and write a Solana-homed machine’s events to the Solana EventRegistry program, which needs the [solana] extra: see Solana.
For Economics 2.0 machines point EVENT_REGISTRY_ADDRESS at the 2.0 EventRegistry 0xA1e7F1d7B24dAb55Dc92491e6d9B89F6E925Ad1e; Tokenomics 1.0 machines keep 0x43c6AF2E14dc1327dc3cc6c7117D1CD72fffEcbA. Both contracts share the submitEvent selector, so a wrong address is not caught before sending: the 1.0 contract reverts with MachineNotFound and the event is not recorded.

submit_event

Submits a single event to EventRegistry. Returns (tx_hash, data_hash).
Param shape mirrors validate_submit_event_params below. value is an ISO 4217 minor-unit integer: cents for USD/HKD, whole units for JPY/KRW/VND. The MCR pipeline FX-normalizes to USD cents using the rate at timestamp and counts revenue only in its 25 supported currencies, listed on Submit events. currency is a 3-10 char uppercase alphanumeric code on revenue events and "" on activity events; omitting the kwarg applies the smart default for a peaq-homed machine. For a Solana-homed machine pass currency explicitly: the omitted default raises ValidationError (field="currency") on that path. batch_submit_events is strict: currency must be supplied per event.
tuple[str, bytes]
tx_hash is the transaction hash as a hex string, without a 0x prefix with web3 7 or later (a base58 signature for a Solana-homed machine); data_hash is exactly 32 bytes.
Errors: ValidationError, TypeError when value is not a plain int, ValueCapExceeded, RateLimitExceeded, RpcError.

batch_submit_events

Submits multiple events atomically through the peaq Batch precompile. All succeed or all revert.
list[dict | SubmitEventParams]
required
Non-empty list of event payloads. Items may be SubmitEventParams instances or dicts with matching keys.
list[str]
One transaction hash per input event. All hashes are identical (same batch tx).
Errors: ValidationError on empty list or invalid event; TypeError when an event’s value is not a plain int. ValueCapExceeded / RateLimitExceeded when operational limits hit. RpcError on revert or transport failure.

validate_submit_event_params

SubmitEventParams is a frozen dataclass (imported from peaq_os_sdk.types.events):
The validator uses attribute access, so callers must construct a SubmitEventParams instance. Dict literals will raise AttributeError. Errors: ValidationError: any field out of range. TypeError when value is not a plain int.

compute_data_hash

Returns the 32-byte keccak256 digest of raw_data. Pair with submit_event’s data_hash output (also bytes) to compare on-chain payloads.

check_operational_limits

SubmitEventParams
required
Event with machine_id and value.
OperationalLimits
required
Configured max_value_per_tx, rate_limit_max_events, rate_limit_window_seconds.
EventTracker | None
required
Current rate-tracking state for the machine, or None when tracking is disabled.
Errors: ValueCapExceeded if value > max_value_per_tx. RateLimitExceeded when the event window is exceeded.

Queries

Read-only helpers backed by the off-chain MCR API server. Each function validates the DID, issues a single GET through client.session, and returns a shape-checked TypedDict. The default per-request timeout is 30 seconds, enforced inside peaq_os_sdk.query.http_client.get_json; the public query_* functions do not expose an override. On a tokenomics20 client all three go to the deployment’s 2.0 MCR server (mcr.peaq.xyz for peaq-mainnet) and client.api_url is not read. Machine DIDs are did:peaq:<decimal machine id>, operator DIDs stay did:peaq:0x<address>, machine_id is a lossless int. A malformed or non-canonical machine_id, or a response about a different machine or operator, raises ApiError BAD_RESPONSE. A deployment without a paired MCR (agung-2026-08-28) raises TokenomicsConfigError DEPLOYMENT_UNAVAILABLE before any HTTP. The MCR helpers need a tokenomics20 client. See API reference.

query_mcr

Fetches the Machine Credit Rating for a machine DID. See GET /mcr/{did}.
str
required
Machine DID, did:peaq:<decimal machine id>.
TypedDict
Snake_case keys: did: str, machine_id: int, mcr_score: int (0–100), mcr: str ("AAA" | "AA" | "A" | "BBB" | "BB" | "B" | "NR" | "Provisioned"), bond_status: str ("bonded" | "unbonded"), negative_flag: bool, event_count: int, revenue_event_count: int, activity_event_count: int, revenue_trend: str | None ("up" | "stable" | "down" | "insufficient"; None means not computable, never substitute "stable"), total_revenue: float (USD cents, the sum of the revenue events worth at least 1,000 USD cents; divide by 100 for display; never None), average_revenue_per_event: float | None (USD cents, rounded to 2 decimals; None when not computable), last_updated: int | None, mcr_degraded: bool (True when revenue for a peaq-homed machine was converted at a stale stored FX rate; always False for a Solana-homed machine). Both nullable keys are always present; test the value, not membership. On a tokenomics20 client two optional keys appear only for a machine homed away from peaq: home_chain (protocol chain ordinal, Solana is 5, SOLANA_PROTOCOL_CHAIN_ID) and rating_unavailable (any non-empty value means the score is a floor). Details: MCR API compatibility.
mcr_score is always an int; the MCR API returns 0 for a Provisioned or NR machine. A None or non-integer mcr_score raises ApiError BAD_RESPONSE; rating_unavailable carries the reason an unscored machine has no rating. Errors: ValidationError if did is not a valid machine DID (did:peaq:<decimal machine id>). ApiError with code in NOT_FOUND (HTTP 404), SERVICE_UNAVAILABLE (503), SERVER_ERROR (other 5xx), HTTP_ERROR (other non-2xx), BAD_RESPONSE (malformed body), TIMEOUT, NETWORK_ERROR. 400 is INVALID_REQUEST, 401 and 403 are UNAUTHORIZED, 409 is MACHINE_HOMED_ELSEWHERE or CONFLICT (MCR_HTTP_ERROR_CODE), and the error carries http_status, server_code, server_message, operation, home_protocol_chain_id, retryable. See errors.

query_machine

Fetches the full machine profile (NFT Metadata JSON v1.0) and validates the response shape against a strict TypedDict. The SDK raises BAD_RESPONSE if the server payload does not match the schema. See GET /machine/{did}.
str
required
Machine DID, did:peaq:<decimal machine id>.
TypedDict
Top-level keys: schema_version: str, name: str, peaqos: PeaqosData. PeaqosData always carries machine_id: int, did: str, operator: str | None, mcr: str, mcr_score: int, bond_status: str, negative_flag: bool, event_count: int, data_visibility: str, documentation_url: str | None. The MCR API returns data_visibility: "private" for every machine and no event rows or partner data; data_api: str is present when the machine’s DID carries a peaq-data-api service endpoint. event_data: list[EventEntry], partner_data: dict[str, Any], and partner_data_error: str stay optional in the type; the MCR API does not return them. Use "data_api" in profile["peaqos"] (etc.) to test for presence rather than truthiness. On a tokenomics20 client a machine homed away from peaq adds optional home_chain, rating_unavailable, owner (base58 on Solana, never pass it to an EVM validator), controller, machine_type, relocating; operator stays a did:peaq:0x… DID or None. Details: MCR API compatibility.
Errors: ValidationError on invalid DID. ApiError with the same codes as query_mcr; BAD_RESPONSE if any required field is missing, has the wrong type, or is out of range.

query_operator_machines

Fetches the fleet of machines managed by a proxy operator. Each entry is individually validated. See GET /operator/{did}/machines.
str
required
Operator DID. Must start with did:peaq:0x.
TypedDict
Keys: operator_did: str, machines: list[OperatorMachine], and pagination: Pagination. Each OperatorMachine has did: str, machine_id: int, mcr_score: int (0–100; a None or non-integer score raises BAD_RESPONSE), mcr: str, and negative_flag: bool. Pagination carries offset: int, limit: int, and total: int.
Errors: ValidationError on invalid DID. ApiError with the same codes as query_mcr; BAD_RESPONSE if the body or any machines entry is malformed.

Verify

Experimental, beta: the Verify surface lives in peaq_os_sdk.verify, is not re-exported from the package root (there is no client.verify), and may change without a major version bump. It ships an API read client and three pure chip preflight functions, with no attester, submission, anchoring or hardware code. Reads need no [solana] extra. Verify on peaq mainnet serves peaq-homed (EVM) machines; a Solana-homed machine returns 503 VERIFY_READ_UNAVAILABLE, raised as VerifyTransportError with cause_category == "unavailable". Available from peaq-os-sdk 0.10.0.

VerifyReadClient

Reads one machine’s record from the Verify API. The API is authoritative: the client validates the response and returns it as frozen dataclasses; it does not read the registry, an RPC or a DID service.
str
required
HTTPS origin of the Verify API, no path, query or credentials. There is no default. Construction opens no connection; call close() when done.
RegistryMachineId
required
From registry_machine_id(123): an int in 1..2**256-1. A DID, an address or 0 raises VerifyValidationError before any request.
threading.Event
Set it from another thread to abort the call; the result is VerifyTransportError with cause_category == "canceled".
EvmMachineVerification | SolanaMachineVerification
Frozen. machine_id, machine_did, home_chain (EvmHomeChain(chain_id) or SolanaHomeChain(genesis_hash)), did_controller and operator (kind plus address in the home chain’s native kind), and kyb.status / chip.status, each "unverified" | "verified" | "expired" | "revoked". Python exposes the two topics directly on the record where the wire format and the JS SDK nest them under verification. No aggregate flag.machine_id, machine_did, did_controller.address and operator.address are typed wrapper objects (RegistryMachineId, MachineDid, DidControllerAddress, OperatorAddress), not plain strings: read .value for the string (machine_did.value, did_controller.address.value, operator.address.value) and machine_id.value for the int.
Each call makes exactly one GET /v1/verify/machines/{machineId} with a five-second deadline and a 4096-byte response ceiling, follows no redirect, sends no credentials and keeps no cache. Nothing retries.

Chip preflight

Three functions that run on the machine and turn a challenge plus the chip’s leaf certificate and two signatures into the canonical peaq.verify.chip-evidence/1 document. They need no chip access, no network, no key and no file of yours: the pinned Infineon ECC Root CA 2 and CA306 intermediate certificates ship inside the package. A platform adapter reads the certificate from the OPTIGA Trust M Express object 0xE0E0 and asks key 0xE0F0 to sign; the DID controller’s wallet signs the controller message. Fixed profile infineon-optiga-trust-m-express-ca306/1, trust bundle infineon-optiga-trust-m-express-ca306-roots/1; no other chip, root or curve. EVM controller and chain ID only.
sign_chip and sign_controller are yours: the SDK never invokes the chip or a wallet. ChipPreflightEvidence exposes the fixed identifiers (protocol, profile, evidence_schema, trust_bundle), revocation_status == "not_evaluated" (the SDK checked no revocation list), evidence_hash, chip_ref and evidence_bytes(). Every stage rechecks now < expires_at <= now + 300 and the certificate validity, so run all three right after the challenge. A VerifyFreshnessError needs a new challenge, a new ChipPreflightContext and all three stages again; after any other failure, correct the rejected input and call the failed stage again.
Preflight success means the material is locally valid. It does not consume the challenge, does not submit anything, and does not set chip.status to verified: the onboarding service submits the bytes with POST /v1/verify/chip/evidence, and chip.status reads verified once peaq records the chip attestation. The full flow is in Verify a chip, end to end.

Error classes

See errors for the full hierarchy, the 20-code faucet table, and the Tokenomics 2.0 code tables.

Constants

See Solana and MCR API compatibility.
Identical to the JS SDK: EVENT_TYPE_REVENUE, EVENT_TYPE_ACTIVITY, TRUST_SELF_REPORTED, TRUST_ON_CHAIN_VERIFIABLE, TRUST_HARDWARE_SIGNED, DID_ATTR_*, DID_MAX_NAME_BYTES, DID_MAX_VALUE_BYTES, SUPPORTED_CHAINS, LAYERZERO_EIDS, DEFAULT_API_URL. Same values, Python-idiomatic names.
Pass to data_visibility= on write_machine_did_attributes. The SDK rejects any other value with ValidationError before sending; using one of these constants avoids typos.
Used by import_wallet(..., chain=IMPORT_CHAIN_SOLANA). The JS SDK exports identical constants. See JS Constants.