Skip to main content
A machine can live on instead of peaq. Its identity record, DID document and Machine NFT are then Solana accounts owned by a Solana key, while the subscription and the peaq-side registry entry stay on peaq. Onboarding therefore happens in two places and three phases, in a fixed order: reserve the machine ID on peaq, activate its subscription on peaq, wait for peaq to mirror both to Solana, then create the machine on Solana. The CLI runs one phase per invocation; the SDKs expose the same phases.
Solana support requires CLI 0.0.12 (pip install -U 'peaq-os-cli[solana,ows]') and SDK 0.8.0 (npm install @peaqos/peaq-os-sdk @solana/web3.js @coral-xyz/anchor or pip install -U 'peaq-os-sdk[solana]').

What you need

Mainnet only. The Solana half of the deployment record exists for peaq-mainnet; agung-2026-08-28 has none, and there is no testnet walkthrough.

Configure

Every CLI client, keyless previews included, also needs the six Tokenomics 1.0 contract addresses: run peaqos init in this directory first (or copy a .env that has them), otherwise the first preview exits 3 naming IDENTITY_REGISTRY_ADDRESS. --svm-rpc-url and --svm-network before the command name override the environment, which overrides .env. peaqos whoami shows the cluster and a redacted RPC origin once either variable is set. Keep the SDK journal peaqos.log and the original inputs in this directory for every later phase: the journal is what lets a rerun reconcile instead of resubmitting. Use one directory per machine. The Solana journal check collects unparseable lines before it filters by machine, so a single bad line, whichever machine wrote it, refuses every machine in that directory. The peaq journal skips such lines instead. Create the two wallets in the vault and note their public addresses. Fund them yourself; wallet creation does not.

The DID document

Save as native-did.json. authentication entries are zero-based indices into verificationMethods. The optional machine-level controller is a separate flag (--solana-controller), not a field here.
Limits come from the Solana program: 8 verification methods, 8 authentication indices, 8 service endpoints, 128 UTF-8 bytes per string field, 64 bytes for the machine type, 512 bytes of credential subject. Oversized input is rejected before anything is signed; nothing is truncated.

The argument set

Every phase takes the same complete arguments. A changed machine type or credential computes a different machine ID, so the CLI treats it as a new attempt with its own journal entries instead of continuing this one; a changed wallet or ceiling on the same machine ID is refused. Keep the original inputs. Ceilings are explicit integers in base units; 0 is a strict cap, never “unlimited”; the USDT cap is enforced on chain, the PEAQ cap is checked before signing and against the mined receipt. Set the ten shell variables first (OWNER, OPERATOR, MANUFACTURER, CREDENTIAL_SUBJECT_HEX, PEAQ_CAP, FEE_CAP, RENT_CAP, HISTORY_START = the peaq block number just before your reservation, COMPUTE_LIMIT, PRIORITY_PRICE); the :? guards make the shell stop on an unset one instead of passing an empty argument.
Tier entry is not offered on Solana: basic or pro. --for, --machine-key, and --slippage-bps are rejected with --chain solana. The machine ID is the same derivation as on peaq, uint256(keccak256(abi.encode(machineType, credentialSubject))), a decimal string in JSON and the DID did:peaq:<decimal id>.

What the CLI fingerprints

Every phase rechecks a fingerprint of the accepted plan. It covers both wallet addresses, the manufacturer, the machine type and credential subject, the tier, the payment rail, the DID controller if you set one, the contents of the DID document, and the three ceilings (--max-net-peaq-amount or --max-usdt-amount, --max-native-fee-lamports, --max-native-rent-lamports). A changed machine type or credential subject computes a different machine ID and starts a new attempt. Changing any of the others, including raising a ceiling or moving from basic to pro, stops the remaining phases for this machine until you put the original values back. The fingerprint also covers the deployment record that ships inside the installed SDK, including its record_source and recorded_at fields. An SDK or CLI upgrade in the middle of an attempt can therefore stop a resume that restoring your own values would have fixed. Finish the machine first, then upgrade. Once the subscription phase has paid the bond, cancelReservation reverts with SubscriptionExists and there is no refund path, so a machine whose original inputs are gone cannot be finished; its ID frees again only after the subscription terminates. Settle the tier and the ceilings at the preview, and store the values and the DID document together. Three flags sit outside the fingerprint and are safe to change on a later run: --from-block, --compute-unit-limit and --compute-unit-price-micro-lamports. For a first attempt take --from-block from the finalized peaq block, not the chain head: a start ahead of finalized is rejected with STATE_MISMATCH. On a resume it must also stay at or below the block your reservation landed in, or reservation discovery finds nothing.

Run the three phases

1

Preview

Keyless. Reports each stage’s unresolved prerequisites and the native fee and rent estimate. A preview is not transaction evidence, and did is null before the machine exists.
2

Reserve on peaq (operator wallet)

Reserves the machine ID for the Solana owner. --yes accepts the displayed terms for this phase only.
3

Activate the subscription on peaq (operator wallet)

Bonds the tier. Approval and activation are separate transactions; an approval receipt alone is not a subscription. The preflight requires isEconomicAuthority() on peaq and reads both technical pause flags before the approval; a pause before the first approval stops with TECHNICALLY_PAUSED and spends nothing; a pause that starts after the approval confirmed ends with the same code, the approval gas spent and the allowance in place, so read the retained transactions before assuming nothing moved.
4

Wait for the reservation mirror and the subscription status

peaq pushes the reservation to Solana, and the subscription status reaches the machine’s SubscriptionTerminal account (it must read Active or Grace with a non-zero sequence that matches peaq’s subscription signal, and its tier must match your plan). Delivery is external to the CLI; a peaq receipt does not prove it. Repeat the native preview until the SDK reports both ready.
5

Create the machine on Solana (owner wallet)

Exit 0 means the Solana transaction confirmed. It does not mean onboarding is complete: peaq still has to apply the link. Rerun the same phase without --yes to reconcile; done is onboarding_state.evidence.stage.phase equal to complete.

Read the state

With the machine ID alone, no wallet, journal or DID document (MACHINE_ID is the machine_id from any phase’s JSON output, the preview included):
Two read modes. Without --chain solana, the CLI falls back to the Solana read when the peaq record is missing or homed elsewhere and PEAQOS_SVM_RPC_URL is configured; that result has status: "observed" and a native_current_state with separate peaq (finalized) and Solana (confirmed) observations, not an atomic snapshot. Native states: absent, reserved, pending_external, present, conflict, in_flight, unavailable. With --chain solana the CLI reads the native management state instead and prints a different object (execution_chain, identity, mirrors with subscription_source, subscription_terminal_status and subscription_terminal_last_seq, peaq with subscription_status, operator_synchronization, pause flags), not native_current_state; it needs the native machine to exist, so before native creation use the fallback read. present proves the accounts exist, not that this attempt created them or that the link is complete. Pending or conflicting state exits 0; an unavailable configuration exits 3; read failures exit 2.

Resume after an interruption

Never delete peaqos.log, never change the inputs, never submit a replacement transaction on a hunch. The SDK records peaq hashes and nonces before it waits for a receipt, and Solana signatures with their blockhash validity before broadcast. Exit codes: 1 input or consent, 2 chain, read or transaction failure, 3 configuration or missing dependency. With --json, read the error_code and the retained evidence, not only the exit code: a phase failure sits in errors[] with its own error_code, and a native transaction can be confirmed at top level while the linkage read afterwards fails with exit 1, 2 or 3, so a nonzero exit is not proof that nothing was created.

The same flow in the SDKs

Store the accepted plan and the serialized progress; restore them with restoreSvmOnboardingProgress(plan, json) before the next phase. In JS, activateMachine previews, reconciles and reports the Solana flow but does not submit its write phases: the public path carries reads and recovery only; a phase that would otherwise be ready comes back blocked with nextAction: "verified_onboarding_deployment_required" and nothing is signed. getMachineActivationState and activateMachine with chain: "solana" read the reservation history from CrossChainMirror with eth_getLogs from block 0 to the current block, with no fromBlock option in the public types; the public peaq RPCs reject that range, so both fail with READ_FAILED. previewMachineActivation does not need that read and works. Run the phases, status and recovery from the CLI or Python; JS against a public RPC is preview only. Reference: SDK JS: Solana.

After onboarding

  • Manage the machine with the same peaqos machine commands plus --chain solana (status, transfer --unsafe, suspend, resume, did set-controller, the DID setters, and the Solana-only set-evm-operator), or the same SDK methods; the SDK resolves the home chain from the machine ID and refuses the wrong chain. Transfer, controller and the peaq operator link need the Solana owner signature; DID lists, suspend and resume take owner or controller. There is no safe transfer on Solana (transfer requires --unsafe, --data-hex is refused), and approve / approve-all do not exist; the SDKs refuse approveMachine, setMachineApprovalForAll and safeTransferMachine by name. Details: SDK JS, SDK Python.
  • Submit events with submitEvent / submit_event: the SDK routes a Solana-homed machine to the Solana EventRegistry program and signs with the owner’s or controller’s OWS Solana signer (the EVM operator does not qualify). In Python set client.solana_signer to an OwsSolanaSigner (for example from PeaqosClient.solana_signer_from_wallet(...)) before the call, or submit_event raises SIGNER_UNAVAILABLE; the CLI attaches it itself. The first event for a machine also pays rent for its event-log account. A confirmed event is not yet a rating; the 2.0 MCR indexes Solana events asynchronously.
  • Credit rating: the 2.0 MCR scores a Solana-homed machine once its Solana indexer has the machine’s events; until then the response carries home_chain: 5 and a rating_unavailable reason (any non-empty reason means the score is a floor, not a measurement). A freshly created machine reads mcr: "Provisioned", mcr_score: 0 and rating_unavailable: "no_events_yet". See the API reference.
  • Monetization opt-in for a Solana-homed machine is refused with SOLANA_MONETIZATION_UNVERIFIED before anything is signed. Rating reads and peaq-homed opt-in are unaffected.
  • Relocation between peaq and Solana stays switched off on chain. Onboarding on Solana is not a relocation: the machine is created there.