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
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 asnative-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.
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)
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):
--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 deletepeaqos.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
- JavaScript
- Python
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 machinecommands plus--chain solana(status,transfer --unsafe,suspend,resume,did set-controller, the DID setters, and the Solana-onlyset-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 (transferrequires--unsafe,--data-hexis refused), andapprove/approve-alldo not exist; the SDKs refuseapproveMachine,setMachineApprovalForAllandsafeTransferMachineby name. Details: SDK JS, SDK Python. - Submit events with
submitEvent/submit_event: the SDK routes a Solana-homed machine to the SolanaEventRegistryprogram and signs with the owner’s or controller’s OWS Solana signer (the EVM operator does not qualify). In Python setclient.solana_signerto anOwsSolanaSigner(for example fromPeaqosClient.solana_signer_from_wallet(...)) before the call, orsubmit_eventraisesSIGNER_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: 5and arating_unavailablereason (any non-empty reason means the score is a floor, not a measurement). A freshly created machine readsmcr: "Provisioned",mcr_score: 0andrating_unavailable: "no_events_yet". See the API reference. - Monetization opt-in for a Solana-homed machine is refused with
SOLANA_MONETIZATION_UNVERIFIEDbefore 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.
Related
- Activate: the one-transaction flow on peaq
- Omni-chain: Solana: what is live on Solana
- Smart contracts: Solana mainnet: the eleven program IDs
- CLI: activate

