@peaqos/peaq-os-sdk is the opinionated TypeScript entry point for the Machine Financial Passport flow. Chain and API operations are exposed both as PeaqosClient instance methods and as standalone functions; wallet helpers and waitForBridgeArrival are static methods, and readAttribute, encodeAddAttribute and the validation and hashing helpers are standalone functions only.
Pass tokenomics20: { deploymentId } 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, and bridge methods throw typed errors instead of running. Add svm: { deploymentId: "solana-mainnet", rpcUrl } for machines homed on Solana: see Solana (SVM).
Install
- Node.js: ≥ 22
- TypeScript: ≥ 5
- Peer dependency:
viem(^2.54.2) - Economics 2.0: 0.8.0 or newer; Solana onboarding: 0.11.2 or newer, with the optional peers
@solana/web3.jsand@coral-xyz/anchor(the OWS signer’s@open-wallet-standard/coreships with the SDK) - Exports: ESM and CJS. No bundler workarounds.
dotenv is optional but recommended: PeaqosClient.fromEnv() reads from process.env, so import "dotenv/config" at the top of your entry file is the simplest way to load .env.
Environment variables
PEAQOS_SVM_RPC_URL and PEAQOS_SVM_NETWORK are CLI variables. The SDK takes the Solana endpoint through the svm constructor option, not from the environment: see Solana (SVM).
peaq mainnet contracts
Use these addresses for the legacy contract-address variables (IDENTITY_REGISTRY_ADDRESS and friends, note they are unprefixed) when pointing at peaq mainnet. IdentityRegistry, IdentityStaking, EventRegistry and MachineNFT are UUPS upgradeable proxies; treat those addresses as the current proxy pointers. MachineAccountFactory and MachineNFTAdapter are not proxies, and the DID and batch addresses are native runtime precompiles. EVENT_REGISTRY_ADDRESS here is the Tokenomics 1.0 registry; for Economics 2.0 machines set it to 0xA1e7F1d7B24dAb55Dc92491e6d9B89F6E925Ad1e (see Smart contracts).
For agung testnet addresses see Install → Agung testnet contracts. Bridging is mainnet-only: LayerZero has no DVN routes to agung.
Machine Markets orchestration
Scale lives underclient.orchestration, the Machine Markets surface (machine identity proofs, agent pairings, skill registry, market search, orders, payments). Reach it by setting PEAQOS_ORCHESTRATION_URL (and optionally PEAQOS_API_KEY) before PeaqosClient.fromEnv(). Full method reference: Orchestration (JS).
Client
PeaqosClient
The client is generic over its mode: PeaqosClient<M extends SdkMode = "legacy"> with readonly mode: M. Construct a 2.0 client as new PeaqosClient<"tokenomics20">({ ...config, tokenomics20: { deploymentId: "peaq-mainnet" } }); without the type argument TypeScript infers "legacy" and the constructor call is a compile error. Query results carry machineId: bigint on a tokenomics20 client and number on a legacy one. fromEnv() returns the union of both and fromWallet() preserves the config’s mode; narrow on client.mode. A config that carries svm takes a second type parameter, PeaqosClient<"tokenomics20", "svmEnabled">: see Solana (SVM).
string
required
RPC endpoint (non-empty).
string
required
0x + 64 hex characters.ContractAddresses
required
All six contract addresses.
string
Defaults to
DEFAULT_API_URL. A tokenomics20 client does not read it: the MCR server comes from the deployment record.OperationalLimits
Per-tx and rate-limit caps. All-zero disables limits.
PeaqosClient instance. toJSON() and util.inspect output redact the private key ("[REDACTED]").
Other RPC endpoints are available. See Public RPC endpoints.
Errors: ValidationError: empty rpcUrl, a privateKey that is not 0x + 64 hex, or a missing contract address (address format is checked by the calls that use it, not here). TokenomicsConfigError: unknown tokenomics20.deploymentId, or svm without tokenomics20.
fromEnv
fromEnv() reads TOKENOMICS_DEPLOYMENT_ID: a non-empty value selects Tokenomics mode, absent or empty stays legacy, an unknown or unreleased ID throws TokenomicsConfigError at construction. The return type is PeaqosClient<"legacy"> | PeaqosClient<"tokenomics20">; narrow on client.mode.
PeaqosClient. All required env vars must be set (see Environment variables).
Errors: ValidationError: any required env var missing or empty.
fromWallet
PeaqosClient from an OWS vault wallet. Overloads keyed on config narrow the return type: tokenomics20 plus svm returns PeaqosClient<"tokenomics20", "svmEnabled">, tokenomics20 alone PeaqosClient<"tokenomics20">, neither PeaqosClient<"legacy">. When owsSigning is true (default), signing routes through OWS: the key is decrypted only per-sign and wiped immediately after. When false, the key is decrypted at construction and signing uses viem directly.
string
required
Wallet name or UUID in the OWS vault.
string | undefined
required
Vault passphrase. Pass
undefined to fall back to the OWS_PASSPHRASE env var.boolean | undefined
required
Route signing through OWS. Defaults to
true when undefined.Omit<PeaqosClientConfig, 'privateKey'>
required
Client config without
privateKey (the wallet provides the signer).WalletOptions
Optional vault configuration (e.g. custom
vaultPath).PeaqosError: wallet not found, passphrase missing, or OWS signing failure. With owsSigning: false a wrong passphrase throws at construction (eager decrypt). With owsSigning: true a wrong passphrase surfaces on the first sign call, and key material is never decrypted at construction.
Wallets (OWS)
Wallet lifecycle helpers (createWallet, importWallet, importWalletMnemonic, listWallets, getWallet, exportWallet, deleteWallet, plus the extractPeaqAddress utility) back the Open Wallet Standard integration: mnemonic-backed encrypted vault, multi-chain accounts (peaq, Base, Ethereum, Solana, Bitcoin, etc.). Lifecycle helpers are available as module-level imports and as static methods on PeaqosClient; the PeaqosClient.fromWallet factory wires a vault wallet directly into a client (OWS-native signing by default). The JS package bundles @open-wallet-standard/core as a regular dependency; no separate peer install is required. The raw-key constructor and fromEnv flow keep working unchanged. Full reference on the Wallets page.
generateKeypair
secp256k1 privateKey and its derived address. No chain interaction. The private key never touches disk.
OWS wallet lifecycle
OWS wallet helpers are available as staticPeaqosClient methods and standalone functions. They derive multi-chain accounts, keep wallet material in an encrypted OWS vault, and return public WalletInfo metadata.
@peaqos/peaq-os-sdk. createWallet, importWallet, importWalletMnemonic, exportWallet, and fromWallet require a passphrase argument or OWS_PASSPHRASE. options.vaultPath can point at a custom vault directory. fromWallet can sign through OWS (owsSigning=true, default) so key material is decrypted only for the signing operation.
object
Frozen object with
id, name, createdAt, keyType, peaqAddress, and accounts. Each account has accountId, address, chainId, network, and derivationPath.Accessors
All accessors are read-only. The private key is held in an ECMAScript#private field: never exposed through the public surface and redacted in JSON.stringify and util.inspect.
Tokenomics 2.0
Available on a client constructed withtokenomics20: { deploymentId: "peaq-mainnet" | "agung-2026-08-28" }. The seven contract addresses come from the SDK’s snapshot (TOKENOMICS_2_0_DEPLOYMENTS, resolveTokenomics20Deployment), resolved at construction with no network call and verified against InfoDesk.peer(role) before every write. Addresses are never accepted from callers. Every function below is a PeaqosClient method and a package-root export taking the client first. Machine IDs are bigint; a number throws ValidationError. Concepts: Economics 2.0.
activateMachine
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. The preflight requires MachineSubscription.isEconomicAuthority() (NOT_ECONOMIC_AUTHORITY otherwise) and, once the machine ID is computed, both MachineStateAndSync technical pause flags clear (TECHNICALLY_PAUSED), before any approval. This is the peaq-homed flow; with chain: "solana" the same entry point runs the Solana phases described under Solana (SVM).
ActivateMachineResult: machineId, owner, controller, tier, bondAmount, voucherCreditApplied, netPeaqAmount, transactionHash, chainId, contract, method, receipt, events, and the re-read subscription state. Amounts come from the receipt. Success requires MachineOnboarded, MachineMinted, and Activated from the right contracts (matched by emitting address) plus a matching post-state read. The write is never retried; a receipt timeout throws RECEIPT_UNAVAILABLE carrying the hash.
activateMachineWithUsdt
Same activation, bond settled in USDT through SubscriptionTokenProvisionPool. Not usable on peaq mainnet: the pool has no USDT token configured, so the USDT quote and the write both revert. Requires maxUsdtAmount, taken from previewMachineActivationWithUsdt(...) after applying slippageBps.
previewMachineActivation, previewMachineActivationWithUsdt
machineId, bondAmount, voucherCredit, netPeaqAmount, balance, approvalRequired. Throws MACHINE_ID_MISMATCH and MAX_NET_PEAQ_EXCEEDED; a low balance is returned, not thrown.
computeMachineId
Reads
getMachineOwner, getMachineAvailability and getMachineManagementState return a tagged result (chain: "peaq" | "solana"), getMachineActivationState returns the Solana-aware state, and all four answer for a machine homed on Solana: see Solana (SVM).
Lifecycle and subscription
maxNetPeaqAmount, USDT writes maxUsdtAmount; 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)
DID updates
AUTHENTICATION_REWRITE_REQUIRED before submission. Ownership and DID writes accept a synchronous onTransactionSubmitted(submission) callback.
previewMachineAction
Side-effect-free preview for ownership and DID actions (not suspend/resume): contract, method, current state, intended effect. Never simulates, signs, or submits.
Machine-ID helpers
Number(bigint), no leading zeros except "0".
Disabled in Tokenomics mode
Still available in Tokenomics mode:
submitEvent and batchSubmitEvents (Events); queryMcr, queryMachine, queryOperatorMachines (Queries); orchestration health, skills, policies, market-service reads, audit reads, payment rails, delivery transports; heartbeat; provisioning; stream. waitForBridgeArrival is static and not gated; it is marked deprecated.
Solana (SVM)
Machines can be homed on Solana. The SDK keeps one entry point per operation: the samegetMachineOwner, activateMachine, transferMachine, submitEvent calls answer for a Solana-homed machine once the client opts in. Opting in changes what a read can answer, not how you call it. Walkthrough: Onboard a machine on Solana.
Opting in
@solana/web3.js and @coral-xyz/anchor are optional peer dependencies, loaded only when a machine turns out to live on Solana; an EVM-only client never imports them, and a Solana-homed machine met without them fails with SVM_PACKAGE_MISSING naming the package, except that a read which first has to resolve the home chain (getMachineOwner, getMachineAvailability, getMachineManagementState) throws HOME_CHAIN_UNRESOLVED with homeChainReason: "missing_dependency". The public Solana deployment record is solana-mainnet (SVM_DEPLOYMENTS, resolveSvmDeployment; the constant also carries an internal test record): cluster mainnet-beta, protocol chain ID 5 (SOLANA_PROTOCOL_CHAIN_ID), LayerZero EID 30168, the program IDs from the contracts page and the peering between peaq’s MachineBridgeAdapter and the Solana store PDA. Before any account is decoded the SDK checks the endpoint’s genesis hash against the record, that the programs the read needs are executable, and that the record’s peering is consistent (the Solana peer peaq stores is the adapter’s store account, the peaq peer is a 20-byte address); onboarding reads the live peer account before signing. Program IDs come from the record, never from an IDL’s address field.
Reads
getMachineOwner and getMachineAvailability return a tagged result:
evmOperator is the peaq operator the bond is booked under (the address the owner wallet was registered from). The subscription itself stays on peaq for every home: for a Solana-homed machine read it from getMachineManagementState(machineId).subscription; getMachineSubscription needs a peaq ERC-721 and throws MACHINE_NOT_FOUND for a Solana-homed machine. A location the SDK cannot establish throws TokenomicsActivationError HOME_CHAIN_UNRESOLVED with homeChainState (reserved, in_flight, unknown, conflict, unavailable) and homeChainReason (missing_configuration, missing_dependency, missing_capability, conflicting_evidence). A missing dependency or an unreachable RPC is never reported as a missing machine. 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.
Onboarding
Terminal-first (SDK 0.11.2 and newer), the same flow as the Python SDK in camelCase withbigint amounts: the Solana owner pays the bond on Solana, in PEAQ or USDC, through the 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 JavaScript machines, given in full on the guide’s JavaScript tab (the collapsed block under its run command, to copy): one loop over
getMachineActivationState that does whatever nextStep names until complete, journaling every write. Its two write calls, a terminal stage and a native one:
computeUnitLimit is the plan’s suggestion, plan.native.suggestedComputeUnitLimit: preview again with it and submit with it (before, scripts passed 600,000; see The mint below). getSigner resolves the owner’s signer once: getSolanaOnboardingSignerRequirements(inputs, plan, "native_onboarding", { candidateAddress }), then solanaSignerFromWallet(name, passphrase, true, undefined, required.chainId) and validateSolanaOnboardingSigner(inputs, plan, "native_onboarding", signer). The client needs both halves configured: tokenomics20: { deploymentId: "peaq-mainnet" } and svm: { deploymentId: "solana-mainnet", rpcUrl, commitment: "finalized" }; nothing on peaq is signed after the registration, so a client that only onboards can carry a throwaway privateKey.
- Registration.
registerSolanaOperatorspends peaq gas only (about 0.01 PEAQ; 98,688 and 105,432 gas measured) and waits for the node to mirror the binding. A wallet already bound to the signer isaction: "reconciled", and nothing is sent. A wallet bound to another address is refusedOPERATOR_BOUND_ELSEWHERE.getSolanaOperatorRegistration(wallet, { peaqOperator? })reads the state keyless:unbound,bound,bound_elsewhereormirroring. Confirmation is theOperatorAddressBoundevent in a finalized block, never receipt status. - The quote.
quoteSolanaActivation(identity, { tier, payIn, wallet })is keyless and refuses in the program’s order (OPERATOR_NOT_REGISTERED,MACHINE_ALREADY_ACTIVE,REQUEST_ALREADY_OPEN,INSUFFICIENT_BALANCE…). It returnsbond,maxIn(the suggested escrow, the bond plus the slippage cushion; on USDC the swap’s estimate plus the cushion),walletBalanceandpriceAgeSecs. - The priority price.
computeUnitPriceMicroLamportsdefaults to0non the terminal phases; the plan takes it inpreviewMachineActivation’s budget. Pass10_000n, 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,mayResubmit) 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. EscrowsmaxInAmountin the pay-in token and freezes the bond; on PEAQ the escrow must cover the bond (ESCROW_BELOW_BOND). The rents (2,169,160 lamports for the request, 1,488,440 for its escrow account and, on the machine’s first request, 1,071,880 for itsSubscriptionTerminal) count againstmaxNativeRentLamports. A request the node does not credit within its hour endsstatus: "exited"withexit: "REQUEST_EXPIRED". The node cancels that request once it can, returning the escrow and the request’s rents, or the owner cancels it withcancel_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. On a request already cancelled or refunded,cancel_requestrun alone is refused withREQUEST_CLOSED; a cancel this journal recorded still reconcilescomplete.finalise. The request decides the path: in PEAQ to the terminal’s vault, or on USDC a swap that buys exactly the bond through the provision pool, against the record’s finalise lookup table. The swap is quoted by simulating the exact instruction; the cap sent is that cost plus the slippage, never above the escrow.result.settlement(SwapSettlementQuote:simulatedInAmount,maxInAmount,escrowAmount,slippageBps,lookupTable,lockInTable) says how a USDC settlement was priced.previewSolanaSettlementruns the same preparation keyless;verifySolanaFinaliseTablechecks the table alone. A request already cancelled or refunded is refusedREQUEST_CLOSEDbefore signing (0.11.2), byfinaliseand bypreviewSolanaSettlement. After a finalise there is no cancel: if peaq refuses the bond, the refund comes back in PEAQ on both rails (exit: "ACTIVATION_REFUNDED", withresult.refund).- Results. Each terminal call returns
statuscomplete,pending(unresolved,advice) orexited(a namedexit, with the way out inadvice). A signed send whose outcome is unknown throwsTokenomicsPendingTransactionError(TRANSACTION_PENDING); its row is journaled, and a rerun reconciles it. Each confirmation carriessendCount({ sends, resendErrors }, 0.11.2): the sends this process made for the write; it isundefinedfor a row this process did not send. 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: thecreditandbondwaits,finaliseandpreviewSolanaSettlement, andcancel_request. It replacesREQUEST_NOT_COMMITTED,STATE_MISMATCHorREQUEST_NOT_CANCELLABLEin 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 thebondwait asACTIVATION_REFUNDED.- The mint.
native_onboardingmints against the bonded request withinputs(owner, controller, manufacturer, machine type, credential subject, the DID lists, the two native ceilings, optionalevmOperatorassertion) and theplanfrompreviewMachineActivation({ chain: "solana", inputs, computeUnitLimit, computeUnitPriceMicroLamports }).plan.native.suggestedComputeUnitLimit(0.11.2) is the value to pass ascomputeUnitLimit: 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 withComputationalBudgetExceeded(reasoncompute_budget_exceeded), with theMachineandMachineStateaccounts absent, is minted again by the rerun, like a mint that never landed: the reconciliation marks itfailedWithoutEffect, andnoEffectcovers both cases. Every other failed mint staysfailed. - The link push. The SDK reads the route from the chain before every push.
quoteSolanaLinkPush(machineId, { payer })is keyless:transportistrust_validator(production:nativeFeeLamports0n,computeUnitLimit120,000,pushAccountRentLamports1,005,840) orlayerzero;statusalready_pushedmeans nothing is left to send. On the node’s routemaxLinkPushFeeLamportsis not needed; on a LayerZero route a push without it throwsValidationErroron that field before signing. Refused before signing:TV_OUTBOX_MISSING,TV_INBOUND_REFUSED,SVM_FEE_EXCEEDED(a fee or the push account’s rent over its ceiling), andINSUFFICIENT_BALANCEwhen the owner cannot pay the push and keep its own rent-exempt minimum (650,240 lamports for a plain wallet). The confirmation carriestransport,tvNonceandsequence. - Complete. When peaq’s
MachineBridgeAdapter.lastAppliedMirrorPushSeqfor the machine reaches the push’s sequence:getMachineActivationState(machineId, { chain: "solana", inputs, records })readsnextStep.phase === "complete".tvRelayslists the pushes that went over the node (signature,dstEid,nonce,applied);messageslists LayerZero messages and is empty on the node’s route. - Journal and recovery. The three Solana rows are handed to
onTransactionSubmittedbefore broadcast, the registration row right after its broadcast; a write that stays unconfirmed throwsTokenomicsPendingTransactionError(TRANSACTION_PENDING, the row on.submitted) and is reconciled on the next run. 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. Store each row as oneserializeSolanaOnboardingAttempt(row, client.tokenomics20)line and read them back withdecodeSolanaOnboardingAttempt. Pass every row to status reads and the native phases. After an earlier request was cancelled, refunded or closed (observations.terminal.requestState), 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’srequestRowsalso leaves that request’s rows (nonce at or belowobservations.terminal.terminal.lastNonce) out of the new call’srecords. - What ran.
getSolanaOnboardingHistory(machineId, { timeoutSeconds })rebuilds the onboarding from the chains alone, keyless:rows(step,actorowner/operator/node/layerzero/other, slot or block, cost to its payer, the owner’s own change whoever paid inownerLamportsDeltaandownerTokenDeltas, explorerlinks) andtotalsper payer (the owner’s is its net change, refunds from others’ transactions included). A cancel the node sends reads actornode. Sources it could not read are named inunread; a rerun reads them. Right after a run the public endpoints are busy: the script allows 600 s.confirmSolanaOnboardingReceipts(records)reads back the registration’s and the terminal writes’ receipts for a cost report after a resume. - Migrating from 0.10.x. The reservation-first API (
SvmOnboardingInputwithfunding, thereservationandsubscriptionphases,serializeSvmOnboardingProgress) is gone; onboard with the phases above.
Management
Requests carryingchain: "solana" take the native path through the entry points they already have; the client never reroutes an EVM call because Solana is configured.
A write is preview, decide, write:
previewMachineAction({ chain: "solana", action, machineId, signer, ..., accepted: { maxFeeLamports, maxRentLamports } }), then transferMachine({ chain: "solana", preview, request, rpc, native: { computeUnitLimit, computeUnitPriceMicroLamports }, commitment, getSigner }). The write-capable rpc is yours to supply; the client’s Solana connection is read-only. approveMachine and safeTransferMachine have no Solana semantics: once the home-chain reads resolve the machine to Solana they throw TokenomicsUnsupportedError SVM_APPROVAL_UNSUPPORTED / SVM_SAFE_TRANSFER_UNSUPPORTED; setMachineApprovalForAll carries no machine ID and throws SVM_APPROVAL_FOR_ALL_UNSUPPORTED only when called with chain: "solana" (the Python code for the first two is SOLANA_OPERATION_UNSUPPORTED; Python’s set_machine_approval_for_all keeps its EVM behaviour). Results report confirmed, unconfirmed, blocked or failed; unconfirmed is not a failure, reconcile it. setMachineEvmOperator reports sync separately (pending until peaq’s lastAppliedMirrorPushSeq for source chain 5 equals that exact source sequence and the latest source snapshot still describes the current state; a synchronized result carries reusedExistingPush: true, the SDK never sends the message itself); matching operators on both chains prove nothing on their own. DID setters resize the account, so rent moves to or from whoever signed; the preview reports it separately from fees.
Events
submitEvent and batchSubmitEvents write to the Solana EventRegistry program for a Solana-homed machine. They need the SVM-enabled client and two options: getSigner, resolving an OwsSolanaSigner (PeaqosClient.solanaSignerFromWallet(name, passphrase)), and commitment ("confirmed" or "finalized"). Params are SubmitSvmEventParams: machineId (bigint), eventType (0 revenue, 1 activity), value and timestamp (bigint or safe integer), rawData, trustLevel, sourceChainId, sourceTxHash, metadata, currency (smart default for a single event, required per event in a batch). A batch is one Solana transaction; every machine in it must be homed on the same deployment. The timestamp must not exceed the observed cluster time (STATE_MISMATCH otherwise). The first event for a machine also pays rent for its MachineEventLog account; the fee payer is the signer. The result (SvmEventSubmissionResult) carries executionChain: "solana", machineId (bigint), signature, submissionId, slot, confirmationLevel, dataHash, eventIndex and optional computeUnits. onTransactionSubmitted receives a payload-free NativeEventSubmissionAttempt (signature, lastValidBlockHeight, submission ID) before the broadcast. reconcileSvmEventSubmission (complete, failed, expired, unresolved) and rebroadcastSvmEventTransaction are exported, but in 0.11.2 the first needs the packaged IDL, which the package does not export, and the second needs signed bytes the SDK never returns; check an uncertain attempt’s signature on the cluster instead. Replace only an expired attempt via options.replace. An uncertain wait throws TRANSACTION_PENDING (default timeoutMs 120 s), not TokenomicsPendingTransactionError. Other options: timeoutMs, signal, computeUnitPriceMicroLamports, computeUnitLimit, acceptedCostBounds (maxFeeLamports, maxRentLamports; refused before signing above them). There is no event preview in JS. Walkthrough: Submit events: machines homed on Solana. A confirmed event is not an MCR update; the 2.0 MCR indexes Solana events asynchronously.
MCR API compatibility
The query and monetization clients follow the 2.0 MCR server’s contract for Solana-homed machines.revenueTrend is RevenueTrend | null and averageRevenuePerEvent is number | null (null means not computable, never substitute "stable" or 0; totalRevenue stays non-null), a null or fractional mcr_score throws BAD_RESPONSE, and MonetizationState.signer is string | null because a Solana owner’s key is base58. Optional fields, present only for a machine homed away from peaq: homeChain (protocol chain ordinal, Solana is 5; compare against SOLANA_PROTOCOL_CHAIN_ID, never against 3338 or 30168), ratingUnavailable (any non-empty value means the score is a floor), and on the profile owner (home-chain address format), controller, machine_type, relocating. operator stays an EVM address DID on every home. HTTP, transport and non-JSON failures throw McrApiError with code (see MCR API error codes), serverCode, httpStatus, homeProtocolChainId, retryable (a classification; the transport never retries); response shape and correlation failures throw RuntimeError with code BAD_RESPONSE. Monetization opt-in signed by a Solana owner is implemented (five-line message with home_chain: 5, plain Ed25519 signMessage, body with signer_pubkey). When solanaSigner 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 MonetizationValidationError SOLANA_MONETIZATION_UNVERIFIED before signing. Without a Solana signer the SDK does not check the home chain and sends the EIP-191 write.
Registration
registerMachine
Registers the caller’s own address as a machine. Reads minBond from the IdentityRegistry contract (currently 1 PEAQ) and sends that value with the transaction.
number
The newly allocated machine ID, decoded from the
Registered event in the transaction receipt.registerFor
Registers a machine on behalf of another address. The caller becomes the proxy operator and supplies the current minBond (read from the IdentityRegistry contract) as msg.value.
0x${string}
required
Machine EOA. The client’s signing address becomes the operator and pays the bond.
number
The newly allocated machine ID for the proxied machine.
Gas Station
1
Setup 2FA
Call
setupFaucet2FA to enroll the owner.2
Confirm 2FA
Call
confirmFaucet2FA with a TOTP from the authenticator.3
Fund
Call
fundFromGasStation to send gas to a machine wallet.setupFaucet2FA
Enrolls an owner address for 2FA with the Gas Station. Returns a QR code URL (expires after ~2 minutes).
string
required
Owner to enroll (SS58 or hex).
string
required
Gas Station base URL.
'svg' | 'png'
QR format. Defaults to
"svg".object
ValidationError on empty args. RuntimeError for INVALID_OWNER_ADDRESS, INVALID_PAYLOAD, QR_GENERATION_FAILED, unexpected envelope, or HTTP failure. See errors.
confirmFaucet2FA
Confirms 2FA enrollment with a fresh TOTP code.
string
required
Owner address being confirmed.
string
required
Gas Station base URL.
string
required
Fresh 6-digit TOTP.
void on successful activation.
Errors: ValidationError on any empty argument. RuntimeError for INVALID_2FA, 2FA_NOT_CONFIGURED, 2FA_LOCKED, unexpected envelope, or transport failure.
fundFromGasStation
Sends gas tokens to a machine wallet. Returns a discriminated union on status.
string
required
2FA-enrolled owner (SS58 or hex).
string
required
Machine EOA to fund.
string
required
Faucet-configured chain identifier (e.g.,
"peaq").string
required
Current TOTP.
string
UUID idempotency key. Auto-generated if omitted.
FaucetFundSuccessResponse | FaucetFundSkippedResponse
requestId back in both success and skipped responses. The Python SDK does not include request_id in the response at all: it is a request-side idempotency key only. Do not write code that reads requestId from the response and expects it to work in both SDKs.
NFT & DID
Machine NFT minting, token-ID lookup, and the two canonical DID attribute writers. The DID writes batch six (machine) or two (proxy) attributes into a single atomicbatchAll transaction via the peaq Batch precompile.
mintNft
Mints a Machine NFT on the MachineNFT contract for a registered, bonded machine. Returns the transaction hash.
number
required
Registered machine ID. Must be a positive integer.
0x${string}
required
Address that will own the minted NFT.
tokenIdOf
Reads the NFT token ID assigned to a registered machine via a view call.
number
required
Registered machine ID. Must be a positive integer.
number
The NFT token ID, or
0 if no NFT has been minted for this machine.0 when no NFT has been minted for the machine: the contract returns 0 instead of reverting.
writeMachineDIDAttributes
Atomically writes the six canonical Machine DID attributes (machineId, nftTokenId, operator, documentation_url, data_api, data_visibility) to the caller’s DID via a single batched transaction.
number
required
Registered machine ID.
number
required
NFT token ID assigned to the machine.
string
required
Operator DID reference. May be an empty string. ASCII, ≤ 2560 bytes.
string
required
Non-empty ASCII URL, ≤ 2560 bytes.
string
required
Non-empty ASCII URL for the machine’s data API, ≤ 2560 bytes.
'public' | 'private' | 'onchain'
required
Visibility setting.
writeProxyDIDAttributes
Atomically writes the two canonical Proxy DID attributes (machineId, machines) to the caller’s DID.
number
required
The proxy operator’s registered machine ID.
readonly number[]
required
Non-empty list of positive machine IDs managed by this proxy. The JSON-encoded array must be ≤ 2560 bytes.
readAttribute
Reads a single DID attribute directly from the peaq DID precompile.
Address
required
The DID account address whose attribute is being read (typically a machine address, but any DID-bearing EOA works).
string
required
Attribute key, e.g.
"machineId", "data_visibility", "machines".object
{ name: string; value: string; validity: number; created: bigint }. validity is the absolute block number the attribute is valid until, not the validFor duration.encodeAddAttribute
Encodes ABI call data for the DID precompile’s addAttribute(didAccount, name, value, validFor) function. Useful when constructing smart-account executeBatch calls that touch the DID precompile alongside other contracts. writeMachineDIDAttributes and writeProxyDIDAttributes use this helper internally; reach for it directly only when composing custom batch flows.
Address
required
Address of the DID account being written. On-chain this must equal
msg.sender of the resulting precompile call.string
required
Attribute name. ASCII only, ≤ 64 bytes.
string
required
Attribute value. ASCII only, ≤ 2560 bytes.
number
required
Validity period in blocks.
0 means no expiry. Must be a non-negative integer in the uint32 range.string
ABI-encoded call data. Throws
ValidationError if any constraint is violated.Smart accounts
ERC-4337 smart accounts deployed via theMachineAccountFactory. Requires the client to be constructed with a machineAccountFactory address (or the MACHINE_ACCOUNT_FACTORY_ADDRESS env var via fromEnv).
deploySmartAccount
Deploys a smart account via MachineAccountFactory.createAccount and returns the deployed address.
Address
required
EOA that will own the smart account.
Address
required
Machine EOA the account is scoped to.
number
required
Non-negative CREATE2 salt.
getSmartAccountAddress
Read-only equivalent: computes the CREATE2 address for the given (owner, machine, salt) without deploying. Returns the same address deploySmartAccount would produce.
deploySmartAccount. No transaction, no gas.
Bridge
Route: peaq ↔ Base. In Tokenomics modebridgeNft throws MACHINE_RELOCATION_UNAVAILABLE: Economics 2.0 relocates whole machine records, and relocation is disabled on chain. A 2.0 machine that should live on Solana is created there, not bridged: see Solana (SVM). Portability of the 1.0 NFT: Machine NFT cross-chain portability.
LayerZero v2 Machine NFT bridging between peaq and Base. Requires the machineNftAdapter address (or MACHINE_NFT_ADAPTER_ADDRESS) when sending from peaq. The SDK’s source / destination literal union expands as peer contracts deploy on new chains.
bridgeNft
Bridges a Machine NFT from source to destination. When source === "base", baseRpcUrl and baseNftAddress are required so the SDK can build a per-call viem client for the Base side.
On the peaq→Base path the SDK runs an ERC-721 approval pre-flight: it checks MachineNFT.getApproved(tokenId) and submits a one-shot approve(adapter, tokenId) if the token isn’t already cleared for the adapter. The Base→peaq path uses burn-and-unlock and needs no approval. Either way, callers don’t handle approvals themselves.
number
required
Positive NFT id to bridge.
'peaq' | 'base'
required
Origin chain.
'peaq' | 'base'
required
Target chain (must differ from
source).Address
required
Destination-chain recipient.
Hex
Raw LayerZero v2
extraOptions bytes. Defaults to "0x" (the contract’s enforced options).string
Base RPC URL. Required only when
source === "base".Address
MachineNFTBase address on Base. Required only when source === "base".Hex
The source-chain transaction hash.
waitForBridgeArrival
Static method that polls the destination chain’s MachineNFT.ownerOf(tokenId) every 10 seconds until a non-zero owner returns or the timeout elapses. No PeaqosClient instance required.
string
required
Destination-chain RPC endpoint.
Address
required
MachineNFT contract address on the destination.number
required
The NFT id expected to arrive.
number
Wait budget in seconds. Defaults to 300 (5 min).
AbortSignal
Optional abort signal. When aborted, the poll stops immediately with a
RuntimeError code ABORTED.Events (Qualify)
machineId on SubmitEventParams and MachineEvent is bigint in every mode (1024n, not 1024). On an SVM-enabled client the same two calls write a Solana-homed machine’s events to the Solana EventRegistry program instead: see Solana (SVM).
submitEvent
Submits a single event to EventRegistry. Validates and normalizes the payload, then calls the contract. Returns the transaction hash and the computed dataHash.
validateSubmitEventParams below. value is an ISO 4217 minor-unit integer (cents for USD/HKD, whole units for JPY/KRW/VND). The MCR counts revenue only in its 25 supported currencies, listed on Submit events. currency is required on revenue events (^[A-Z0-9]{3,10}$) and must be "" on activity events; the SDK applies a smart default (revenue → "USD", activity → "") when omitted on submitEvent. batchSubmitEvents is strict: every event must carry currency explicitly.
batchSubmitEvents
Submits multiple events atomically through the peaq Batch precompile. All events land in the same transaction: all succeed or all revert.
readonly SubmitEventParams[]
required
Non-empty list of event payloads. Each element is validated individually before submission.
Hex[]
One transaction hash per input event. All hashes are identical (same batch tx).
validateSubmitEventParams
bigint
required
Machine ID:
bigint in every mode (the MachineRegistry token ID under Economics 2.0, the ID returned by registerMachine / registerFor under Tokenomics 1.0).0 | 1
required
0 revenue, 1 activity.number
required
Non-negative ISO 4217 minor-unit integer. Cents for USD/HKD, whole units for JPY/KRW/VND. Activity events: any non-negative integer or
0.string
Revenue: 3-10 uppercase alphanumeric (e.g.
"USD", "HKD", "JPY"). Activity: must be "". Omit to apply the SDK smart default (revenue → "USD", activity → ""); batchSubmitEvents requires it explicitly.number
required
Unix seconds.
Uint8Array | null
required
Off-chain payload hashed into
dataHash.0 | 1 | 2
required
0 self-reported, 1 on-chain verifiable, 2 hardware-signed.number
required
Originating chain. Use
SUPPORTED_CHAIN_IDS.peaq for local.Hex | null
required
Cross-chain tx hash when applicable.
Uint8Array
required
Arbitrary bytes stored on-chain alongside the event. Use empty bytes when no metadata is needed.
void. Throws ValidationError on any invariant violation, and a native TypeError when value is not an integer (fractional or wrong type).
computeDataHash
Uint8Array
required
Off-chain payload bytes to hash.
keccak256 hash as 0x + 64 hex characters.
checkOperationalLimits
{ machineId: bigint; value: number }
required
Machine ID and event value.
OperationalLimits
required
Configured
maxValuePerTx, rateLimitMaxEvents, rateLimitWindowSeconds.EventTracker | null
required
Current rate-tracking state for the machine. Pass
null if you are not tracking window state. EventTracker is { machineId: bigint; count: number; windowStart: number }.void. Throws ValueCapExceeded or RateLimitExceeded on limit violation.
Queries
On atokenomics20 client all three go to the deployment’s 2.0 MCR server (mcr.peaq.xyz for peaq-mainnet); client.apiUrl is not read. Machine DIDs are did:peaq:<decimal machine id>, operator DIDs stay did:peaq:0x<address>, machineId in results is bigint. A malformed or non-canonical machine_id, or a response about a different machine or operator, throws RuntimeError BAD_RESPONSE. A deployment without a paired MCR (agung-2026-08-28) throws TokenomicsConfigError DEPLOYMENT_UNAVAILABLE before any HTTP. The MCR helpers need a tokenomics20 client. See API reference.
Read-only helpers backed by the off-chain MCR API server. Each function validates the DID, issues a single GET, and returns a frozen, shape-checked response. All three accept an optional GetJsonOptions with timeoutMs (default 30 000 ms) and a caller AbortSignal.
queryMcr
Fetches the Machine Credit Rating for a machine DID. See GET /mcr/{did}.
string
required
Machine DID,
did:peaq:<decimal machine id>.number
Request budget in ms. Defaults to 30 000.
AbortSignal
Caller abort signal. Either signal aborting wins.
object
Frozen object with camelCase fields:
did, machineId, mcrScore (number, 0–100), mcr ("AAA" | "AA" | "A" | "BBB" | "BB" | "B" | "NR" | "Provisioned"), mcrDegraded (boolean: true when revenue for a peaq-homed machine was converted at a stale stored FX rate; always false for a Solana-homed machine), bondStatus ("bonded" | "unbonded"), negativeFlag (boolean: true when the machine has been flagged for negative behaviour; consumers should down-rank or alert independently of the numeric score), eventCount, revenueEventCount, activityEventCount, revenueTrend ("up" | "stable" | "down" | "insufficient", or null when not computable, never substitute "stable"), totalRevenue (USD cents, an integer sum of the revenue events worth at least 1,000 USD cents; divide by 100 for display; never null), averageRevenuePerEvent (USD cents, rounded to 2 decimals; null when not computable), lastUpdated (unix seconds or null). On a tokenomics20 client two optional fields appear only for a machine homed away from peaq: homeChain (protocol chain ordinal, Solana is 5; compare with SOLANA_PROTOCOL_CHAIN_ID) and ratingUnavailable (any non-empty value means the score is a floor, not a measurement). Details: MCR API compatibility.mcr_score: 0 while a machine is still Provisioned or has rating NR. A null or fractional mcr_score throws BAD_RESPONSE, and ratingUnavailable carries the reason an unscored machine has no rating.
queryMachine
Fetches the full machine profile (NFT Metadata JSON v1.0) for a DID. The SDK strictly validates the response against MachineProfileResponse and throws BAD_RESPONSE for any missing or malformed required field. See GET /machine/{did}.
string
required
Machine DID,
did:peaq:<decimal machine id>.GetJsonOptions
Optional
timeoutMs (default 30 000) and caller signal.object
Frozen, strictly-validated machine profile. Top-level fields:
schema_version (string) and name (string). The peaqos sub-object always carries machine_id, did, operator, mcr, mcr_score, bond_status, negative_flag, event_count, data_visibility, and documentation_url. The MCR API returns data_visibility: "private" for every machine and no event rows or partner data. data_api appears when the machine’s DID carries a peaq-data-api service endpoint. event_data, partner_data, and partner_data_error stay optional in the type; the MCR API does not return them. On a tokenomics20 client a machine homed away from peaq adds optional owner (home-chain address format, base58 on Solana, never an EVM address), controller, machine_type, relocating, home_chain and rating_unavailable (the profile’s spelling of the pair under MCR API compatibility); operator stays an EVM address DID. The SDK throws BAD_RESPONSE if the server returns anything that fails the schema guard. See GET /machine/{did} for full field semantics.queryOperatorMachines
Fetches the fleet of machines managed by a proxy operator. Each machine summary carries its DID, machine ID, score, rating tier, and negativeFlag; the response also includes pagination metadata. See GET /operator/{did}/machines.
string
required
Operator DID. Must start with
did:peaq:0x.GetJsonOptions
Optional
timeoutMs (default 30 000) and caller signal.object
Frozen object with
operatorDid, a frozen machines array, and a pagination object. Each machine entry exposes did, machineId, mcrScore (number, 0–100; a null or fractional score throws BAD_RESPONSE), mcr rating, and negativeFlag (boolean: true when the machine has been flagged for negative behaviour). pagination carries offset, limit, and total (all non-negative integers).Verify
Experimental, beta: the Verify surface lives on its own subpath,@peaqos/peaq-os-sdk/verify, is not exported from the package root (there is no client.verify), and may change without a major version bump. It ships two things: an API read client and three pure chip preflight functions. It has no attester, submission, anchoring or hardware code. Available from @peaqos/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 shape and returns it frozen; it does not read the registry, an RPC or a DID service, and never compares the result with local preflight evidence.
string
required
HTTPS origin of the Verify API, no path, query or credentials:
https://mcr.peaq.xyz on peaq mainnet. There is no default.VerifyReadFetch
required
A
fetch-compatible function. Pass the global fetch; construction never calls it.RegistryMachineId
required
From
registryMachineId(123n): a bigint in 1..2^256-1. A DID, an address or 0 throws VerifyValidationError before any request.object
Frozen.
machineId, machineDid (did:peaq:<decimal>), homeChain ({ kind: "evm", chainId } or { kind: "solana", genesisHash }), didController and operator ({ kind, address } in the home chain’s native kind), and verification.kyb.status / verification.chip.status, each "unverified" | "verified" | "expired" | "revoked". No aggregate flag.machineId, machineDid, didController.address and operator.address are typed wrapper objects ({ kind, value }), not plain strings: read .value for the string (machineDid.value, didController.address.value, operator.address.value) and machineId.value for the bigint.GET /v1/verify/machines/{machineId} with a five-second deadline and a 4096-byte response ceiling, rejects redirects, sends no credentials and keeps no cache. Nothing retries; a 429 is reported, not retried.
Chip preflight
Three functions that run on the machine and turn a challenge plus the chip’s leaf certificate and two signatures into the canonicalpeaq.verify.chip-evidence/1 document. They perform no I/O: no chip access, no network, no key, no file. 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.
ChipPreflightEvidence exposes the fixed identifiers (protocol, profile, evidenceSchema, trustBundle), revocationStatus: "not_evaluated" (the SDK checked no revocation list), evidenceHash, chipRef and toEvidenceBytes(). Every stage rechecks now < expiresAt <= 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 and the 20-code faucet table.Type exports
All exported types
All exported types
Constants
Tokenomics 2.0 tiers and activation defaults
Tokenomics 2.0 tiers and activation defaults
Event types & trust levels
Event types & trust levels
DID attributes & limits
DID attributes & limits
Chain IDs & LayerZero endpoints
Chain IDs & LayerZero endpoints
Solana and MCR API
Solana and MCR API
Wallet import chains, key types & passphrase env
Wallet import chains, key types & passphrase env
IMPORT_CHAIN_* is the union behind the ImportChain type alias used by importWallet. KEY_TYPE_* matches the KeyType field on WalletInfo.OWS signing error codes
OWS signing error codes
PeaqosClient.fromWallet(..., owsSigning: true). The matching OwsSigningErrorCode type is the union of all five string-literal codes.
