Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.peaq.xyz/llms.txt

Use this file to discover all available pages before exploring further.

One owner, many machines. The proxy operator pattern lets a single wallet register and control a fleet of machines. Each machine gets its own peaqID on registration; the Machine NFT is minted in a follow-up mintNft call. The proxy operator holds the on-chain ownership of every identity.

When to use proxy registration

Use registerFor when:
  • You operate physical hardware that cannot hold its own keys (offline sensors, embedded devices)
  • You manage a fleet and want centralized identity control
  • You need batch onboarding of many machines from a single script. The IdentityRegistry contract puts no cap on machines per operator, but the operator’s machines DID attribute is capped at 100 entries (newer registrations push older ones out of the index), and the MCR API operator endpoint paginates with limit ≤ 20 per request — iterate via offset against pagination.total to walk the full set, or enumerate against the contract directly for fleets that need more than the indexed 100.
Use self-managed onboarding when the machine holds its own key and registers itself.

Prerequisites

  • Node.js ≥ 22 or Python 3.10+
  • A funded proxy operator wallet with at least (N * 1.1) PEAQ (1 PEAQ bond + gas per machine)
  • The proxy operator must be self-registered first: registerMachine from the proxy’s own key, exactly as in self-managed onboarding. The proxy’s machineId is required to publish the fleet under its DID.
  • Environment variables configured per the install guide
  • 2FA set up and confirmed on the operator wallet (see self-managed onboarding steps 3 and 4)
JS examples load .env via import "dotenv/config". Python’s from_env() reads from the shell, so export the file first with set -a && source .env && set +a.

Single machine registration

1

Create the proxy client

The proxy operator’s private key signs all transactions on behalf of the fleet.
import "dotenv/config";
import { PeaqosClient } from "@peaqos/peaq-os-sdk";

const proxy = PeaqosClient.fromEnv();
console.log("Proxy operator:", proxy.address);
2

Generate a keypair for the machine

Each machine needs its own address. Generate one per device.
const machine = PeaqosClient.generateKeypair();
console.log("Machine address:", machine.address);
// Store machine.privateKey securely if the device needs to sign later.
3

Register the machine via proxy

registerFor registers the machine address on the IdentityRegistry. The proxy’s signing address (msg.sender) becomes the on-chain owner. The call sends 1 PEAQ as the bond.
const machineId = await proxy.registerFor(machine.address);
console.log("Registered machine", machine.address, "with machine ID", machineId);
4

Mint the Machine NFT (proxy)

Registration only mints the Identity NFT. The proxy mints the Machine NFT to the machine’s address.
await proxy.mintNft(machineId, machine.address);
const nftTokenId = await proxy.tokenIdOf(machineId);
5

Fund the machine wallet

The machine signs its own DID writes, so its wallet needs a small amount of PEAQ for gas. Use the Gas Station from the proxy (2FA-gated), or top it up directly from any PEAQ-holding address.
const FAUCET_URL = "https://depinstation.peaq.network";

await proxy.fundFromGasStation(
  {
    ownerAddress: proxy.address,
    targetWalletAddress: machine.address,
    chainId: "peaq",
    twoFactorCode: "654321", // Fresh TOTP code from the proxy operator's authenticator
    // requestId is recommended for idempotent reruns
  },
  FAUCET_URL,
);
6

Machine writes its own DID attributes

writeMachineDIDAttributes always writes to the caller’s DID. The proxy can’t write to the machine’s DID for it. Spin up a separate client backed by the machine’s key and have the machine sign its own attribute writes. Skip this step and the machine is unreachable through the MCR API: GET /machine/{did} and GET /mcr/{did} will 404 because no machineId attribute is bound to the machine’s address.
import { PeaqosClient } from "@peaqos/peaq-os-sdk";

// Build a per-machine client by reusing the proxy's RPC + contracts
// but signing with the machine's own key.
const machineClient = new PeaqosClient({
  rpcUrl: proxy.rpcUrl,
  privateKey: machine.privateKey,
  contracts: proxy.contracts,
});

await machineClient.writeMachineDIDAttributes({
  machineId,
  nftTokenId,
  operatorDid: `did:peaq:${proxy.address}`,
  documentationUrl: "https://example.com/docs",
  dataApi: "https://example.com/events",
  dataVisibility: "public",
});
7

Proxy publishes the fleet on its DID

writeProxyDIDAttributes writes the proxy’s machineId and the list of machine IDs it controls onto the proxy’s DID. This is what the GET /operator/{did}/machines endpoint reads from.
// proxyMachineId is the proxy operator's own machineId from its
// self-registration (registerMachine). Persist it once and reuse.
const proxyMachineId = /* number, e.g. 7 */ 7;

await proxy.writeProxyDIDAttributes({
  proxyMachineId,
  machineIds: [machineId], // grow this list as you add machines
});
See Machine NFT ownership for the full rationale.

Batch registration (10 machines)

Register multiple machines sequentially. Each iteration runs the full activate path (register, mint NFT, fund the machine, machine writes its own DID); then the proxy publishes the full fleet on its DID at the end.
import "dotenv/config";
import { PeaqosClient } from "@peaqos/peaq-os-sdk";

const proxy = PeaqosClient.fromEnv();
const proxyMachineId = 7; // the proxy's own machineId from its self-registration
const FLEET_SIZE = 10;
const FAUCET_URL = "https://depinstation.peaq.network";

const fleet = [];

for (let i = 0; i < FLEET_SIZE; i++) {
  const machine = PeaqosClient.generateKeypair();

  try {
    const machineId = await proxy.registerFor(machine.address);
    await proxy.mintNft(machineId, machine.address);
    const nftTokenId = await proxy.tokenIdOf(machineId);

    await proxy.fundFromGasStation(
      {
        ownerAddress: proxy.address,
        targetWalletAddress: machine.address,
        chainId: "peaq",
        twoFactorCode: "654321", // Fresh TOTP code per call
      },
      FAUCET_URL,
    );

    const machineClient = new PeaqosClient({
      rpcUrl: proxy.rpcUrl,
      privateKey: machine.privateKey,
      contracts: proxy.contracts,
    });
    await machineClient.writeMachineDIDAttributes({
      machineId,
      nftTokenId,
      operatorDid: `did:peaq:${proxy.address}`,
      documentationUrl: "https://example.com/docs",
      dataApi: "https://example.com/events",
      dataVisibility: "public",
    });

    fleet.push({
      address: machine.address,
      privateKey: machine.privateKey,
      machineId,
    });
    console.log(`[${i + 1}/${FLEET_SIZE}] Activated ${machine.address} (machineId ${machineId})`);
  } catch (err) {
    console.error(`[${i + 1}/${FLEET_SIZE}] Failed for ${machine.address}:`, err);
    // Decide: skip this machine and continue, or abort the batch.
  }
}

// Publish the proxy's full fleet in one DID write.
await proxy.writeProxyDIDAttributes({
  proxyMachineId,
  machineIds: fleet.map((m) => m.machineId),
});

console.log("Fleet:", fleet.length, "machines activated");
// Persist fleet to a JSON file or database for later use.
The CLI does this whole flow in one command: peaqos activate --for 0xMachineAddress --machine-key ./machine.key. See CLI reference.

Per-machine key vs shared key

Two approaches for managing machine keys in a proxy fleet:
ApproachHow it worksTrade-offs
Per-machine keyGenerate a unique keypair per device. Store each key on-device or in a secrets vault.Strongest isolation. If one key leaks, only one machine is compromised. Requires per-device key distribution.
Shared proxy keyAll machines share the proxy operator’s key. Machines never sign their own transactions.Simpler operationally. Single point of failure: if the proxy key leaks, the entire fleet is exposed.
Recommendation: Use per-machine keys when the device can store secrets (HSM, TEE, encrypted filesystem). Use a shared proxy key for offline or passive hardware (sensors, meters) that never needs to sign.
On the roadmap. Proxy key management for offline devices is evolving. The current SDK supports both patterns above. Watch the roadmap for additional device-key options as they land.

Fleet queries

After registration, query the MCR API to list all machines under a proxy operator.
curl
curl "${PEAQOS_MCR_API_URL}/operator/did:peaq:0xProxyAddress/machines?offset=0&limit=20"
Response shape:
{
  "operator_did": "did:peaq:0xProxyAddress",
  "machines": [
    { "did": "did:peaq:0xMachine1", "machine_id": 101, "mcr_score": 62, "mcr": "BB", "negative_flag": false },
    { "did": "did:peaq:0xMachine2", "machine_id": 102, "mcr_score": 45, "mcr": "B", "negative_flag": false }
  ],
  "pagination": { "offset": 0, "limit": 20, "total": 2 }
}
See GET /operator/{did}/machines for full details.

Error handling

ErrorCauseResolution
ValidationError: machineAddress must be a valid 0x-prefixed Ethereum addressMalformed address stringCheck the address format (0x prefix, 40 hex characters)
RuntimeError: AlreadyRegistered / RpcError: already registeredThe machine address is already on the IdentityRegistrySkip this address, or query the MCR API to find the existing machine ID
RuntimeError: InvalidMachineAddress / RpcError: zero addressAttempted to register the zero addressPass a valid machine address
Insufficient balanceProxy wallet does not hold enough PEAQ for bond + gasTop up the proxy wallet. For a batch of N machines, ensure at least N * 1.1 PEAQ.