# peaqOS Source: https://docs.peaq.xyz/home peaqOS is the operating system that turns robots and machines into financial assets and economic actors. It gives every machine the identity, credit, and onchain rails to be underwritten, owned, traded, and access markets in its own right. Register your first machine in five minutes. See what ships next. ## Quickstart Let a coding agent drive the integration. The peaqOS skill walks any AI agent through machine registration, MCR queries, fleet management, and troubleshooting end to end. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install peaq-os-cli npx @peaqos/skills add peaqos ``` Scripted on the terminal. One command registers your first machine. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install peaq-os-cli peaqos init && peaqos activate --doc-url "https://example.com/docs" --data-api "https://example.com/events" ``` Full command reference on the [peaqOS CLI page](/peaqos/cli). Node ≥ 22, TypeScript ≥ 5, `viem` peer dependency. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install @peaqos/peaq-os-sdk viem dotenv ``` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient } from "@peaqos/peaq-os-sdk"; const client = PeaqosClient.fromEnv(); // Self-managed: the client's own keypair registers as the machine const machineId = await client.registerMachine(); console.log("Registered machine:", machineId); ``` Full walkthrough on [self-managed onboarding](/peaqos/guides/self-managed-onboarding). Python ≥ 3.10, `web3.py` dependency. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install peaq-os-sdk python-dotenv ``` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from dotenv import load_dotenv load_dotenv() from peaq_os_sdk import PeaqosClient client = PeaqosClient.from_env() # Self-managed: the client's own keypair registers as the machine machine_id = client.register_machine() print("Registered machine:", machine_id) ``` Full walkthrough on [self-managed onboarding](/peaqos/guides/self-managed-onboarding). **Coming soon.** Humble or Jazzy. Wraps the SDKs as ROS 2 services so a robot becomes an on-chain machine actor without leaking keys through ROS messages. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} git clone https://github.com/peaqnetwork/peaq-robotics-ros2.git cd peaq-robotics-ros2 source /opt/ros/jazzy/setup.bash colcon build --packages-select \ peaq_ros2_interfaces peaq_ros2_peaqos peaq_ros2_examples source install/setup.bash ``` ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Self-managed: register the local machine wallet ros2 service call /peaqos_node/machine/register \ peaq_ros2_interfaces/srv/PeaqosRegisterMachine \ "{address: ''}" ``` Full reference on [ROS 2](/peaqos/sdk-reference/ros2/overview). ## Who it's for Integrate peaqOS. Give your machines identity, credit ratings, and financial infrastructure. Register your fleet. One proxy, every machine on-chain. Query any machine's credit rating. Any chain. One API call. ## Functions **LIVE** Put your machine on-chain. DID, wallet, Machine NFT, bond. **LIVE** Credit rate your machine from its revenue and activity history. **Coming Soon** Pair an AI agent that transacts and consumes services on your machine's behalf. **Coming Soon** Prove your machine is real via hardware attestation and trusted third parties. **Coming Soon** List your machine's services. Other machines and agents discover and buy them. **Coming Soon** Fractionalize your machine into an investable asset via ERC-3643. ## Keep going What ships when. Query MCR from any chain. Source and SDKs. Builders, operators, OEMs. # Arcana Social Login Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/account-abstraction/arcana-social-login Web3 developers building on the **peaq** chain can onboard app users quickly via the [social login](https://docs.arcana.network/concepts/social-login) feature of the [Arcana Auth SDK](https://arcana.network/auth). Apps can integrate with this SDK to enable the in-app, non-custodial [Arcana wallet](https://docs.arcana.network/concepts/anwallet/) for authenticated users to sign blockchain transactions. To get started, see the [Auth Quickstart Guide](https://docs.arcana.network/quick-start/). Use the appropriate SDK flavor as per your [app type](https://docs.arcana.network/sdk-installation). ## In-app Arcana wallet The in-app, non-custodial [Arcana wallet](https://docs.arcana.network/concepts/anwallet/) is automatically available via the [Arcana Auth SDK](https://docs.arcana.network/concepts/authsdk). The SDK can be integrated with any app type, including [web, mobile, and gaming apps](https://docs.arcana.network/sdk-installation). The users of apps integrated with the Arcana Auth SDK can access the **peaq** chain via the in-app Arcana wallet, right out of the box. The peaq chain is preconfigured in the blockchain network list. Developers can choose to configure it as the [default active chain](https://docs.arcana.network/setup/config-dapp-with-db/) by using the [Arcana Developer Dashboard](https://dashboard.arcana.network/). arcana-1 App users don't need to install a browser extension to use the Arcana wallet or switch to the peaq chain. When a user logs into the app, they can instantly access the Arcana wallet and sign blockchain transactions on the peaq chain which is automatically selected as the active chain. This simplifies UX and speeds up app onboarding. arcana-2 Developers can tailor the user experience of signing blockchain transactions on the peaq chain. Besides the look and feel of the wallet, developers can also manage when the wallet UI is displayed within the app context to sign blockchain transactions. [Learn more](https://docs.arcana.network/user-guides/wallet-ui/). ## Enable Arcana wallet Users cannot directly access the Arcana wallet as a standalone application. App developers must integrate the app with the Arcana Auth SDK and enable users to access the Arcana Wallet within the app. Developers need to follow these steps for integration: 1. **Register the app** with the [Arcana Developer Dashboard](https://dashboard.arcana.network/), and copy the unique client identifier for the app. 2. **Configure Auth SDK usage** via the dashboard, specify social login options, wallet user experience settings, etc. 3. **Download and [install](https://docs.arcana.network/sdk-installation)** the Arcana Auth SDK, integrate the app, and add a single line code to onboard users by using the plug-and-play login ui via the`connect()` method. After a successful user login, *Arcana wallet will automatically display within the app context*, enabling the user to sign blockchain transactions instantly. The in-app Arcana Wallet supports [JSON/RPC calls and web3 wallet operations](https://docs.arcana.network/auth/web3-ops/evm/). ## References * [Social login providers](https://docs.arcana.network/web3-stack/auth/) * [Blockchain networks](https://docs.arcana.network/web3-stack/chains/) * [Browsers](https://docs.arcana.network/web3-stack/browsers/) * [Supported app types](https://docs.arcana.network/web3-stack/apps/) * [Arcana Developer Dashboard](https://dashboard.arcana.network/) * [API](https://docs.arcana.network/auth/auth-usage-guide/) # Particle Network Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/account-abstraction/particle-network # What is it? [Particle Network](https://particle.network) is a L1 unifying all chains through Universal Accounts. They have deployed its complete Wallet Abstraction stack on peaq and agung, natively bringing social logins and account abstraction to the ecosystem. Using Particle Network's SDKs, applications built on peaq can onboard users into ERC-4337 smart contract wallets through Web2-adjacent mechanisms such as Google, Twitter, email, phone, and so on. ## Overview Developers can integrate and leverage Particle Network's Wallet Abstraction stack through a variety of SDKs spanning over nine different frameworks and platforms. This document will focus on the basic flow of building a React-based web application on peaq using Particle Network to onboard users into ERC-4337 smart accounts through social logins. To try Particle Network for yourself, head over to their [web demo](https://core-demo.particle.network). Additionally, a complete demo repository containing the code covered throughout this document can be found [here](https://github.com/TABASCOatw/particle-peaq-demo). By the end of this page, you'll understand the process of implementing an onboarding flow similar to the preview above. # Using Particle Network: Guide Particle Network's Wallet Abstraction stack can be leveraged through a variety of mechanisms, each varying in complexity and integration flow. These options are: * [Particle Connect](https://developers.particle.network/docs/particle-connect), a custom connection kit (similar to RainbowKit) that facilitates both social logins and Web3 wallet connections. Available for Web, Unity, Android, iOS, Flutter, and React Native. * [Particle Auth](https://developers.particle.network/docs/building-with-particle-auth), the primary library facilitating social logins through either a standard modal provided by Particle or lower-level shortcuts within your own interface. * External connection kits, such as [Web3Modal](https://developers.particle.network/docs/web3modal), [Web3-Onboard](https://developers.particle.network/docs/web3-onboard), and [RainbowKit](https://developers.particle.network/docs/rainbowkit). Any of the above libraries are capable of introducing social logins to your application with Particle Network. Tying in account abstraction (ERC-4337) into these approaches requires the usage of Particle Network's standalone [AA SDK](https://developers.particle.network/reference/aa-web). This SDK leverages Particle's native Bundler and Paymaster deployed on peaq and agung. For this guide, we'll be using Particle Auth alongside Particle's AA SDK to facilitate a standard implementation of social logins. Particle Auth can be integrated through a variety of platforms and frameworks. This guide will focus on Particle Auth Core, a React-based SDK for web applications. To explore the integration process for alternative platforms, head to [Particle Network's documentation](https://developers.particle.network/reference/introduction-to-particle-auth). ## Part 1: Installation Working with Particle Auth Core alongside Particle's AA SDK involves the installation of a few core libraries, including: * `@particle-network/auth-core-modal`, the primary mechanism for facilitating social logins. * `@particle-network/aa`, for generating and assigning ERC-4337 smart accounts to traditional accounts (EOAs) created and linked to the user’s identity through social login. * `@particle-network/chains`, for using peaq. To install these libraries, run one of the two following commands: ```shell theme={"theme":{"light":"github-light-default","dark":"github-dark"}} yarn add @particle-network/auth-core-modal @particle-network/aa @particle-network/chains # OR npm install @particle-network/auth-core-modal @particle-network/aa @particle-network/chains ``` ## Part 2: Configuration Both `@particle-network/auth-core-modal` and `@particle-network/aa` need to be configured independently, although a common denominator between the two is the need for three key values from the [Particle dashboard](https://dashboard.particle.network): * **projectId** * **clientKey** * **appId** Collectively, these values authenticate each SDK. The retrieval process is as follows: 1. Log in or sign up to the [Particle dashboard](https://dashboard.particle.network). 2. Create a new project. 3. Within this project, create an application. 4. Copy the **Project ID**, **Client Key**, and **App ID** from the dashboard. If applicable, save these to environment variables within your application. *** Configuring Particle Auth involves the initialization of its core React component, **AuthCoreContextProvider**. This component will wrap the application in which we intend to use Particle Auth and, beyond the aforementioned values, contain parameters for customizing the embedded wallet modal, specifying the smart account implementation you intend to use, and so on. After importing **AuthCoreContextProvider** from `@particle-network/auth-core-modal`, open it within your JSX. You'll need to use the **options** property for configuration. This takes the following values: * `projectId`, `clientKey`, and `appId`. These are the values you found on the [Particle dashboard](https://dashboard.particle.network). * `wallet`, a collection of properties for configuring the embedded wallet modal that optionally shows after a user logs in with their social account. `wallet` contains: * `visible`, a Boolean dictating whether the embedded wallet interface is shown post-login. If `true`, this materializes by default through a button placed near the bottom right of the application. * `customStyle`, which, in this example, takes `supportChains`, an array of chain objects that dictate the blockchains supported within the embedded wallet modal. To lock this to peaq, import `PeaqKrest` or `PeaqAgungTestnet` from `@particle-network/chains`. * `erc4337`, used for specifying a Smart Account implementation to be shown within the embedded wallet modal rather than the EOA. If `visible` is `false` on `wallet`, ignore this property. `erc4337` contains: * `name`, the name of the Smart Account implementation used within your application. For both peaq and agung, this should be `'SIMPLE'`. * `version`, the version of the Smart Account implementation referenced in `name`. Currently, only `'1.0.0'` is supported with `'SIMPLE'`. After defining these various parameters, **AuthCoreContextProvider** should look like the following example (which is the **index.tsx** file of the aforementioned **create-react-app** example): ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // index.tsx import React from 'react' import ReactDOM from 'react-dom/client' import { PeaqKrest, PeaqAgungTestnet } from '@particle-network/chains'; import { AuthCoreContextProvider } from '@particle-network/auth-core-modal'; import App from './App' ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( // Where Particle Auth will be used ) ``` *** Additionally, Particle Network's AA SDK needs to be configured in a very similar fashion. However, rather than being initialized through a React component, `@particle-network/aa` will need to be configured within the same component where you intend to leverage Particle Auth. Specifically, `@particle-network/aa` has a key "master" object, **SmartAccount**, which, once initialized, enables end-to-end management of the user's smart account. After configuring Particle Auth Core, you'll need to define **provider** from the **useEthereum** hook (imported through `@particle-network/auth-core-modal`) within your application. **provider** represents the EIP-1193 provider object we'll need to configure an attached smart account. Below is an example of this. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // App.tsx import { useEthereum, useConnect, useAuthCore } from '@particle-network/auth-core-modal'; import { PeaqAgungTestnet } from '@particle-network/chains'; import { AAWrapProvider, SmartAccount } from '@particle-network/aa'; import { ethers } from 'ethers'; const App = () => { const { provider } = useEthereum(); // EIP-1193 provider ... } ```
Using **provider**, you'll need to define a new instance of **SmartAccount**. In addition to **provider**, **SmartAccount** takes an object which contains: * `projectId`, `clientKey`, and `appId`, as was previously defined in `AuthCoreContextProvider`. * `aaOptions`, containing: * `accountContracts`, a collection of the Smart Account implementations you intend to support. For peaq, this should just be: * `SIMPLE`, an array of objects which takes: * `chainIds`, an array of chain IDs (integers) for the blockchain(s) you'll be using the Smart Account on. * `version`, the version of the Smart Account you'll use; in the case of `SIMPLE`, this should be `'1.0.0'`. Therefore, defining an instance of **SmartAccount** should look similar to the snippet below: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // App.tsx import { useEthereum, useConnect, useAuthCore } from '@particle-network/auth-core-modal'; import { PeaqAgungTestnet } from '@particle-network/chains'; import { AAWrapProvider, SmartAccount } from '@particle-network/aa'; import { ethers } from 'ethers'; const App = () => { const { provider } = useEthereum(); const smartAccount = new SmartAccount(provider, { projectId: process.env.REACT_APP_PROJECT_ID, clientKey: process.env.REACT_APP_CLIENT_KEY, appId: process.env.REACT_APP_APP_ID, aaOptions: { accountContracts: { SIMPLE: [{ chainIds: [PeaqAgungTestnet.id], version: '1.0.0' }] } } }); } ```
In this example, **smartAccount** is the central source for controlling and reading data from the smart account attached to the user's social login. ## Part 3: Social Login Using Particle Auth, users are onboarded through traditional Web2 social accounts such as Google, Twitter, email, and so on. To initiate social logins programmatically, you'll need to use the **useConnect** hook from `@particle-network/auth-core-modal`. **useConnect** exposes the **connect** function, which directly handles social logins. This takes the following parameters: * `socialType`, the specific social login mechanism to be opened. If this is left as a blank string (`''`), a generalized interface will open, allowing a user to enter their email, choose an external social account, etc. Otherwise, if a string such as `'google'` or `'twitter'` is used, these will be opened directly. * `chain`, the blockchain to be connected to. This should be an object from `@particle-network/chains`, either `PeaqKrest` or `PeaqAgungTestnet` in this case. Below is a snippet showcasing an example implementation of `connect`: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // App.tsx import { useEthereum, useConnect, useAuthCore } from '@particle-network/auth-core-modal'; import { PeaqAgungTestnet } from '@particle-network/chains'; import { AAWrapProvider, SmartAccount } from '@particle-network/aa'; import { ethers } from 'ethers'; const App = () => { const { connect, disconnect } = useConnect(); const handleLogin = async (authType) => { if (!userInfo) { await connect({ socialType: authType, // 'google', 'twitter', etc. - can also be '' chain: PeaqAgungTestnet, // or PeaqKrest }); } }; ... disconnect(); // Often mapped to an element within your JSX } ```
After logging in, **provider** will be populated and, by proxy, **SmartAccount** will be initialized. At this point, you'll be ready to execute transactions. ## Part 4: Transaction Execution Application interaction, or transaction execution, can be done through one of two ways with `@particle-network/aa`, either: 1. Through the instance of `SmartAccount` directly. 2. Using an external Web3 library such as Ethers or Web3.js. ### Option 1: Using `SmartAccount` Instances of **SmartAccount** have various methods capable of constructing and executing transactions, otherwise known as UserOperations (within the ERC-4337 standard). These methods are as follows: * `sendTransaction` * `sendUserOperation` * `sendSignedUserOperation` * `buildUserOperation` * `getFeeQuotes` * `signUserOperation` Both vary in granularity and operational significance; although for this example we'll focus on the most straightforward method, `sendTransaction`. For information about the others listed above, head over to [Particle Network's documentation](https://developers.particle.network/reference/aa-web). `sendTransaction` can be used to construct and execute any standard transaction; just as you would with Ethers, Web3.js, or any related library. Transactions should be constructed using typical parameters such as `to`, `value`, and `data`. Upon calling `{your SmartAccount instance}.sendTransaction`, the user will be asked to confirm the transaction through an in-app popup. Upon doing so, it'll be executed on-chain. The snippet below is an example of this approach: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // App.tsx import { useEthereum, useConnect, useAuthCore } from '@particle-network/auth-core-modal'; import { PeaqAgungTestnet } from '@particle-network/chains'; import { AAWrapProvider, SmartAccount } from '@particle-network/aa'; import { ethers } from 'ethers'; const App = () => { const { provider } = useEthereum(); // Smart Account configuration const smartAccount = new SmartAccount(provider, { projectId: process.env.REACT_APP_PROJECT_ID, clientKey: process.env.REACT_APP_CLIENT_KEY, appId: process.env.REACT_APP_APP_ID, aaOptions: { accountContracts: { SIMPLE: [{ chainIds: [PeaqAgungTestnet.id], version: '1.0.0' }] } } }); ... // Executing a burn of 0.001 AGUNG const executeUserOp = async () => { const tx = { to: "0x000000000000000000000000000000000000dEaD", value: ethers.utils.parseEther("0.001"), }; const txResponse = smartAccount.sendTransaction(tx) return txResponse; }; } ``` ### Option 2: Using Ethers More commonly, an instance of **SmartAccount** can be used in the construction of an instance of Ethers (or Web3.js, viem, and so on), allowing for a more standardized mechanism of programmatic interaction. This is done by building an intermediary EIP-1193 provider object using **AAWrapProvider** from `@particle-network/aa` within your instance of **SmartAccount**. After plugging this into an object such as **new ethers.providers.Web3Provider**, you’ll be able to interact with the smart account directly through Ethers. Below is an example of this configuration process: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // App.tsx import { useEthereum, useConnect, useAuthCore } from '@particle-network/auth-core-modal'; import { PeaqAgungTestnet } from '@particle-network/chains'; import { AAWrapProvider, SmartAccount } from '@particle-network/aa'; import { ethers } from 'ethers'; const App = () => { const { connect, disconnect } = useConnect(); const { provider } = useEthereum(); // Smart Account configuration const smartAccount = new SmartAccount(provider, { projectId: process.env.REACT_APP_PROJECT_ID, clientKey: process.env.REACT_APP_CLIENT_KEY, appId: process.env.REACT_APP_APP_ID, aaOptions: { accountContracts: { SIMPLE: [{ chainIds: [PeaqAgungTestnet.id], version: '1.0.0' }] } } }); // Ethers provider construction const customProvider = new ethers.providers.Web3Provider(new AAWrapProvider(smartAccount, SendTransactionMode.Gasless), "any"); } ```
From this point, your Ethers instance can be used to construct and execute transactions as normal, automatically routing signatures to the embedded wallet generated through social login. ## Conclusion Below is an example of an application component implementing all of the previously covered code snippets: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // App.tsx import { useEthereum, useConnect, useAuthCore } from '@particle-network/auth-core-modal'; import { PeaqAgungTestnet } from '@particle-network/chains'; import { AAWrapProvider, SmartAccount } from '@particle-network/aa'; import { ethers } from 'ethers'; const App = () => { const { connect, disconnect } = useConnect(); const { provider } = useEthereum(); // Smart Account configuration const smartAccount = new SmartAccount(provider, { projectId: process.env.REACT_APP_PROJECT_ID, clientKey: process.env.REACT_APP_CLIENT_KEY, appId: process.env.REACT_APP_APP_ID, aaOptions: { accountContracts: { SIMPLE: [{ chainIds: [PeaqAgungTestnet.id], version: '1.0.0' }] } } }); // Ethers provider construction const customProvider = new ethers.providers.Web3Provider(new AAWrapProvider(smartAccount, SendTransactionMode.Gasless), "any"); // Facilitating social login const handleLogin = async (authType) => { if (!userInfo) { await connect({ socialType: authType, chain: PeaqAgungTestnet, }); } }; // Executing a burn of 0.001 ETH const executeUserOp = async () => { const signer = customProvider.getSigner(); const tx = { to: "0x000000000000000000000000000000000000dEaD", value: ethers.utils.parseEther("0.001"), }; const txResponse = await signer.sendTransaction(tx); const txReceipt = await txResponse.wait(); return txReceipt; }; } ```
*Extending this tutorial, a demo application containing this same code can be found [here](https://github.com/TABASCOatw/particle-peaq-demo). You can try it within your own browser [here](https://particle-peaq-demo.replit.app).*
Using Particle Network's Wallet Abstraction stack, you can now implement Web2-like user onboarding flows and account abstraction capabilities on peaq with only a few lines of code, as demonstrated here. To learn more about Particle Network and its various SDKs, take a look at the following links: * [https://developers.particle.network](https://developers.particle.network) * [https://particle.network](https://particle.network) * [https://blog.particle.network](https://blog.particle.network)


**Documentation provided by [Particle](https://particle.network/), 2024.** # Introduction Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/erc-8004/intro # Overview peaq enables autonomous AI agents to operate in open machine economies. For agents to safely interact with each other (discovering services, evaluating trust, and validating capabilities) they need a standardized identity and reputation layer. **ERC-8004 on peaq provides this foundation**. It allows AI agents to: * Establish a verifiable on-chain identity * Publish service endpoints and metadata * Build and query reputation * Validate claims and capabilities * Make trust-based decisions autonomously Together, these components form the trust infrastructure for AI agents on peaq. The following documentation explains how to integrate ERC-8004 into your agent architecture and begin building trusted agent systems on peaq. ## Network Contract Addresses ### peaq (Mainnet) ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} IdentityRegistry: 0x2154317da929098A033ac1ef869d6A8bB771A0e3 ReputationRegistry: 0x3f68D8b74208023Bcb6617C305e22080eb2fF6C0 ValidationRegistry: 0x9E9463a65c7B74623b3b6Cdc39F71be7274e5971 ``` ### agung (Testnet) ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} IdentityRegistry: 0x567b6953f29Ef2F2f6a592fFeccCE4A7AbE35928 ReputationRegistry: 0xC4421B43917aF1eF2b352AE7dfcFef0Ff181409e ValidationRegistry: 0x6e4E6D77E6a3c704249db0f550C19E80821cbD7d ``` Developers are encouraged to integrate and test on Agung before deploying production agents on peaq mainnet. For more information on how to establish a peaq/agung connection please refer to the [connecting to peaq page](/peaqchain/build/getting-started/connecting-to-peaq). ## Source Code & ABIs The ERC-8004 implementation used on peaq is available in the official peaq [github repository](https://github.com/peaqnetwork/erc-8004-contracts/tree/peaq). This repository contains: * Smart contract source code * Deployment configuration * ABI files * Upgradeable implementation details Developers may reference the repository directly when integrating, verifying contracts, or building advanced tooling on top of ERC-8004. ## What ERC-8004 Enables ERC-8004 introduces three core registries that work together: | Component | Purpose | | ----------------------- | --------------------------------------- | | **Identity Registry** | Agent identity and discoverability | | **Reputation Registry** | Trust and feedback between agents | | **Validation Registry** | Verifiable claims and capability checks | These registries allow agents to operate in a decentralized environment where they can: * Discover other agents * Evaluate trustworthiness * Record interaction outcomes * Validate capabilities before transacting Ultimately, creating a trust layer for autonomous machine and agent economies. ## Core Architecture ### Identity Layer: Agent Discovery The Identity Registry enables an AI agent to publish: * Its unique identity * Metadata describing capabilities * Service endpoints * Public keys or verification data Once registered, an agent becomes discoverable by other agents and applications. ### Reputation Layer: Trust Between agents The Reputation Registry enables agents to leave feedback about interactions. This creates: * verifiable trust history * performance scoring * reliability metrics * interaction history Agents can use this data to determine: *Should I interact or transact with this agent?* Reputation becomes a programmable trust signal. ### Validation Layer: Verifiable Claims The Validation Registry enables structured validation requests and responses between agents. This allows agents to: * Verify capabilities * Confirm service delivery * Validate credentials * Record proof of execution This forms the basis of *agent-to-agent verification and trust automation.* ## How AI Agents Use ERC-8004 An AI agent operating on peaq typically follows this lifecycle: 1. **Register identity**: The agent publishes metadata and endpoints via the Identity Registry. 2. **Become discoverable**: Other agents can query and find the agent. 3. **Interact with other agents**: Agents exchange services, data, or value. 4. **Record reputation**: Agents leave feedback on interactions. 5. **Validate capabilities**: Agents can request or respond to validation checks. This loop creates a continuously improving trust graph across the agent ecosystem. ### Real-life example: peaq ERC-8004 Escrow To see ERC-8004 in action end-to-end—identity registration, claim creation and acceptance, staking, and cross-chain payment—see the **[peaq ERC-8004 Escrow](/peaqchain/build/advanced-operations/erc-8004/omnichain-escrow)** flow. It walks through a buyer machine (Unitree) and seller machine (Drone) registering on peaq, creating and accepting a service claim, funding escrow on Base (USDT via LayerZero), and releasing payment while peaq stays in sync. That page is the concrete implementation of the lifecycle above. We will present the quick start guide below to get you started with ERC-8004 on our testnet (agung). You must modify the code to use your own predefined business logic to be interoperable with systems you interact with. ## Quick Start The following is a quick start guide to get you started with ERC-8004 on our testnet (agung). ### 1. Install ethers & .env ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm init -y npm pkg set type=module npm install ethers dotenv ``` ### 2. Create a .env file ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} PRIVATE_KEY=0xYOUR_PRIVATE_KEY FEEDBACK_PRIVATE_KEY=FEEDBACK_PRIVATE_KEY RPC_URL=https://peaq-agung.api.onfinality.io/public IDENTITY_REGISTRY_ADDRESS= REPUTATION_REGISTRY_ADDRESS= VALIDATION_REGISTRY_ADDRESS= ``` ### 3. Set abis files The following cmd copies the abis files from [peaq's github fork](https://github.com/peaqnetwork/erc-8004-contracts/tree/peaq/abis) to the local abis directory. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} mkdir abis curl -o abis/IdentityRegistry.json https://raw.githubusercontent.com/peaqnetwork/erc-8004-contracts/peaq/abis/IdentityRegistry.json curl -o abis/ReputationRegistry.json https://raw.githubusercontent.com/peaqnetwork/erc-8004-contracts/peaq/abis/ReputationRegistry.json curl -o abis/ValidationRegistry.json https://raw.githubusercontent.com/peaqnetwork/erc-8004-contracts/peaq/abis/ValidationRegistry.json ``` ## Create AI Agent ### 1. Register Your AI Agent Create a filed called `register-agent.js` and add the following code: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from "ethers"; import "dotenv/config"; import IdentityABI from "./abis/IdentityRegistry.json" with { type: "json" }; const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); const identity = new ethers.Contract( process.env.IDENTITY_REGISTRY_ADDRESS, IdentityABI, wallet ); const agentURI = "ipfs://QmYourMetadata"; // or https://... const metadata = [ ["name", ethers.toUtf8Bytes("AgentName")], ["api", ethers.toUtf8Bytes("https://api.agentname.ai")], ["capabilities", ethers.toUtf8Bytes(JSON.stringify(["capability1", "capability2"]))], ]; async function main() { const tx = await identity["register(string,(string,bytes)[])"](agentURI, metadata); console.log("Transaction sent:", tx.hash); const receipt = await tx.wait(); console.log("Mined in block:", receipt.blockNumber); const reg = receipt.logs .map((l) => { try { return identity.interface.parseLog(l); } catch { return null; } }) .find((e) => e?.name === "Registered"); if (!reg) throw new Error("Registered event not found in receipt logs"); const agentId = reg.args.agentId; console.log("agentId:", agentId.toString()); console.log("tokenURI:", await identity.tokenURI(agentId)); } main().catch((e) => { console.error(e); process.exit(1); }); ``` Run the script: `node register-agent.js` ### 2. Query Your Agent Create a filed called `query-agent.js` and add the following code: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from "ethers"; import "dotenv/config"; import IdentityABI from "./abis/IdentityRegistry.json" with { type: "json" }; const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); const identity = new ethers.Contract( process.env.IDENTITY_REGISTRY_ADDRESS, IdentityABI, provider ); // replace with the agentId printed from register-agent.js const agentId = BigInt("1"); async function main() { const uri = await identity.tokenURI(agentId); console.log("tokenURI:", uri); const nameBytes = await identity.getMetadata(agentId, "name"); console.log("name:", ethers.toUtf8String(nameBytes)); const capsBytes = await identity.getMetadata(agentId, "capabilities"); console.log("capabilities:", JSON.parse(ethers.toUtf8String(capsBytes))); const agentWallet = await identity.getAgentWallet(agentId); console.log("agentWallet:", agentWallet); } main().catch((e) => { console.error(e); process.exit(1); }); ``` Run the script: `node query-agent.js`. Make sure you get the agentId from the register-agent.js script. ### 3. Update Agent Metadata Create a filed called `update-agent-metadata.js` and add the following code: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from "ethers"; import "dotenv/config"; import IdentityABI from "./abis/IdentityRegistry.json" with { type: "json" }; const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); const identity = new ethers.Contract( process.env.IDENTITY_REGISTRY_ADDRESS, IdentityABI, wallet ); const agentId = BigInt("1"); async function main() { const tx1 = await identity.setAgentURI(agentId, "ipfs://QmNewMetadata"); await tx1.wait(); console.log("Updated tokenURI:", await identity.tokenURI(agentId)); const tx2 = await identity.setMetadata( agentId, "api", ethers.toUtf8Bytes("https://api.agentname.ai/v2") ); await tx2.wait(); const apiBytes = await identity.getMetadata(agentId, "api"); console.log("api:", ethers.toUtf8String(apiBytes)); } main().catch((e) => { console.error(e); process.exit(1); }); ``` Run the script: `node update-agent-metadata.js`. Make sure you get the agentId from the register-agent.js script. ## Reputation Interactions ### 1. Leave One Reputation Feedback Record The ReputationRegistry writes feedback about an agentId, from the caller's wallet (the clientAddress in events). You cannot leave feedback for your own agentId, therefore another wallet must be used. Create a filed called `give-feedback.js` and add the following code: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from "ethers"; import "dotenv/config"; import ReputationABI from "./abis/ReputationRegistry.json" with { type: "json" }; const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); const wallet = new ethers.Wallet(process.env.FEEDBACK_PRIVATE_KEY, provider); const reputation = new ethers.Contract( process.env.REPUTATION_REGISTRY_ADDRESS, ReputationABI, wallet ); const agentId = BigInt("1"); // agent being reviewed // Example: a 1–5 rating stored as value=5 with valueDecimals=0 const value = 5; const valueDecimals = 0; // Tags are for grouping/aggregation in getSummary() // Keep tag1 consistent across your app (e.g. "quality", "latency", "reliability") const tag1 = "quality"; const tag2 = "v1"; // endpoint: the specific interface used const endpoint = "https://api.agentname.ai/v2"; // feedbackURI: off-chain JSON describing the interaction (ipfs or https) const feedbackURI = "ipfs://QmYourFeedbackJson"; // feedbackHash: hash of the feedback JSON bytes for integrity // In a real app: hash the canonical JSON bytes you publish at feedbackURI. const feedbackHash = ethers.keccak256(ethers.toUtf8Bytes("example-feedback-payload")); async function main() { const tx = await reputation.giveFeedback( agentId, value, valueDecimals, tag1, tag2, endpoint, feedbackURI, feedbackHash ); console.log("TX:", tx.hash); const receipt = await tx.wait(); console.log("Mined in block:", receipt.blockNumber); console.log("Feedback submitted for agentId:", agentId.toString()); console.log("Client (reviewer):", wallet.address); } main().catch((e) => { console.error(e); process.exit(1); }); ``` Run the script: `node give-feedback.js`. ### 2. Read Reputation Back Create a filed called `read-reputation.js` and add the following code: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from "ethers"; import "dotenv/config"; import ReputationABI from "./abis/ReputationRegistry.json" with { type: "json" }; const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); const reputation = new ethers.Contract( process.env.REPUTATION_REGISTRY_ADDRESS, ReputationABI, provider ); const agentId = BigInt("1"); const tag1 = "quality"; const tag2 = "v1"; async function main() { const clientsResult = await reputation.getClients(agentId); console.log("Clients who reviewed agent:", clientsResult); const clients = Array.from(clientsResult); // or [...clientsResult] // Summary for these clients and tags const [count, summaryValue, summaryDecimals] = await reputation.getSummary( agentId, clients, tag1, tag2 ); const scaled = Number(summaryValue) / 10 ** Number(summaryDecimals); console.log(`Summary (${tag1}/${tag2}) count=${count} value=${scaled}`); // Raw feedback rows const includeRevoked = false; const [ rowClients, feedbackIndexes, values, valueDecimals, tag1s, tag2s, revokedStatuses ] = await reputation.readAllFeedback(agentId, clients, tag1, tag2, includeRevoked); for (let i = 0; i < values.length; i++) { const v = Number(values[i]) / 10 ** Number(valueDecimals[i]); console.log( `#${feedbackIndexes[i]} client=${rowClients[i]} value=${v} revoked=${revokedStatuses[i]}` ); } } main().catch((e) => { console.error(e); process.exit(1); }); ``` Run the script: `node read-reputation.js`. ## Validation Request + Response Your ValidationRegistry flow is: 1. **Someone creates a request:** `validationRequest(validatorAddress, agentId, requestURI, requestHash)` 2. **The validator responds:** `validationResponse(requestHash, response, responseURI, responseHash, tag)` 3. **Anyone can query:** `getValidationStatus(requestHash) or getSummary(agentId, validators, tag)` **Who is the validator?** In this quickstart, simplest is: you validate yourself (validatorAddress = your wallet). In real apps: validator is a third party, a DAO, or a known attester. ### 1. Validation Request Create a filed called `validation-request.js` and add the following code: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from "ethers"; import "dotenv/config"; import ValidationABI from "./abis/ValidationRegistry.json" with { type: "json" }; const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); const validation = new ethers.Contract( process.env.VALIDATION_REGISTRY_ADDRESS, ValidationABI, wallet ); const agentId = BigInt("1"); // In quickstart, we self-validate. Replace with a 3rd-party validator in production. const validatorAddress = wallet.address; // Off-chain request payload (describe what’s being validated) const requestURI = "ipfs://QmYourNewValidationRequestJson"; const requestHash = ethers.keccak256(ethers.toUtf8Bytes("example-new-validation-request")); async function main() { const tx = await validation.validationRequest( validatorAddress, agentId, requestURI, requestHash ); console.log("TX:", tx.hash); await tx.wait(); console.log("Validation request created:"); console.log("requestHash:", requestHash); } main().catch((e) => { console.error(e); process.exit(1); }); ``` Run the script: `node validation-request.js`. Make sure to record the `requestHash` which will be used in the validation response. ### 2. Validation Response Create a filed called `validation-response.js` and add the following code: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from "ethers"; import "dotenv/config"; import ValidationABI from "./abis/ValidationRegistry.json" with { type: "json" }; const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); const validation = new ethers.Contract( process.env.VALIDATION_REGISTRY_ADDRESS, ValidationABI, wallet ); // must match requestHash used in validation-request.js const requestHash = "request_hash_from_request_js"; // e.g. 0xabc123... if (!requestHash) throw new Error("Set REQUEST_HASH=0x... before running."); const response = 1; // uint8: project-defined (e.g. 0=unknown, 1=pass, 2=fail) const responseURI = "ipfs://QmYourNewValidationRequestJson"; const responseHash = ethers.keccak256(ethers.toUtf8Bytes("example-new-validation-response")); const tag = "capability:capability1"; async function main() { const tx = await validation.validationResponse( requestHash, response, responseURI, responseHash, tag ); console.log("TX:", tx.hash); await tx.wait(); console.log("Validation response submitted for requestHash:", requestHash); const status = await validation.getValidationStatus(requestHash); console.log("Status:", status); } main().catch((e) => { console.error(e); process.exit(1); }); ``` Run the script: `node validation-response.js`. ### 3. Read Validation Status + Summary Create a filed called `read-validation-status.js`. Add the following code, and make sure the validator address is set to the one set in the validation request. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from "ethers"; import "dotenv/config"; import ValidationABI from "./abis/ValidationRegistry.json" with { type: "json" }; const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); const validation = new ethers.Contract(process.env.VALIDATION_REGISTRY_ADDRESS, ValidationABI, provider); const agentId = BigInt("1"); async function main() { const validatorAddresses = ["validatorAddress"]; const tag = "capability:capability1"; const [count, avgResponse] = await validation.getSummary(agentId, validatorAddresses, tag); console.log(`Validation summary tag=${tag} count=${count} avgResponse=${avgResponse}`); } main().catch(console.error); ``` Run the script: `node read-validation-status.js`. ## Final Notes ERC-8004 provides the foundational infrastructure for agent identity, reputation, and validation on peaq. However, it is intentionally flexible and unopinionated about how these signals are interpreted or enforced. Each application, agent framework, or marketplace must define its own business logic around: * how reputation scores are calculated and interpreted * what validation responses mean * which validators or reviewers are trusted * how feedback impacts agent permissions or interactions * what thresholds determine trust or access The contracts provide the **shared trust layer**, but your application must define how that trust is used. For example, your system may choose to: * Only interact with agents above a reputation threshold * Require validation from specific validators * Weight feedback differently depending on tags or source * Implement staking or slashing around reputation * Gate services based on validation outcomes These decisions live at the application layer and should reflect your specific use case, risk tolerance, and economic model. ERC-8004 is most powerful when combined with clear, intentional business logic that governs how agents interact within your ecosystem. Build thoughtfully, define your trust model carefully, and use these primitives to enable secure and autonomous agent interactions on peaq. # peaq ERC-8004 Escrow Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/erc-8004/omnichain-escrow This page is the **real-life example** of the [ERC-8004 agent lifecycle](/peaqchain/build/advanced-operations/erc-8004/intro) described in the introduction. This flow lets a **buyer machine** (Unitree) and a **seller machine** (Drone) register identities on peaq, create and accept a service claim on peaq, fund the claim from Base (USDT locked in a Base escrow), and then release payment on Base while peaq is kept in sync via LayerZero messages. Omnichain Escrow Purchase Flow ## Actors & Contracts ### Actors | Actor | Role | | ------------------- | --------------------------------- | | **Unitree (Buyer)** | Purchases a service | | **Drone (Seller)** | Provides the service | | **peaq Chain** | Identity + claim + state tracking | | **Base Chain** | Holds USDT escrow and pays seller | | **LayerZero** | Transports cross-chain messages | ### Key Components **peaq** * **Identity / registration** — `register(agent-card.json)` → creates machine/agent identity (`agent_id`) * **ClaimRegistry** — creates/accepts claims, locks stakes, tracks status * **PaymentReceiver** — receives LayerZero messages (`lzReceive`) and updates peaq state * **TransactionRegistry** — records funding/release events (auditability) **Base** * **BaseEscrow** — locks USDT, releases USDT to seller, emits cross-chain messages *** ## Phase 1 — Identity registration on peaq **Goal:** Both parties become on-chain actors on peaq. 1. **Unitree → peaq:** `register("unitree-agent-card.json")`\ **peaq → Unitree:** `agent_id` 2. **Drone → peaq:** `register("drone-agent-card.json")`\ **peaq → Drone:** `agent_id` **Result:** Both buyer and seller have peaq identities and can participate in claims. *** ## Phase 2 — Create & accept claim on peaq **Goal:** Create a service purchase intent on peaq and have the seller accept it with stake. ### Buyer (Unitree) 1. Ensure buyer has USDC on peaq for stake (mint if needed). 2. Approve staking: `approve(ClaimRegistry, stake)`. 3. Ensure buyer has USDT on Base (mint if needed). 4. Create the claim on peaq:\ `create_purchase_claim(seller=Drone, service_id, amount, deadline)`\ **peaq → Unitree:** `claim_id` 5. Share `claim_id` to Drone (off-chain / manual in this diagram). ### Seller (Drone) 6. Fetch details: `get_claim(claim_id)`. 7. Verify claim + seller address (local checks). 8. Ensure seller has USDC on peaq for stake (mint if needed). 9. Approve staking: `approve(ClaimRegistry, stake)`. 10. Accept claim: `accept_claim(claim_id)`\ **peaq:** locks seller stake\ **peaq → Drone:** status `Accepted`. **Result:** Claim exists and is accepted; both sides can poll status on peaq. *** ## Phase 3 — Cross-chain funding (Base → peaq) **Goal:** Lock funds in Base escrow and mark the claim as **Funded** on peaq via LayerZero. 1. **Unitree → peaq:** `get_claim(claim_id)` (to confirm deadline). 2. **Unitree → Base:** `approve(BaseEscrow, USDT amount)`. 3. **Unitree → Base:** `BaseEscrow.deposit(claim_id, amount, seller, deadline)`\ **BaseEscrow:** locks USDT. 4. **Base → LayerZero:** sends cross-chain “funded” message. 5. **LayerZero → peaq:** delivers to PaymentReceiver. 6. **peaq.PaymentReceiver:** `lzReceive()` * updates TransactionRegistry * updates claim status → **Funded**. **Result:** Funds are escrowed on Base; peaq reflects “Funded”. *** ## Phase 4 — Release payment on Base + finalize on peaq **Goal:** Seller performs the service off-chain; buyer releases escrowed payment; peaq finalizes status and returns stakes. 1. **Drone:** Performs the real-world service (off-chain). 2. **Unitree → Base:** `BaseEscrow.release(claim_id)`\ **BaseEscrow:** transfers USDT to Drone\ **Base → Drone:** USDT received. 3. **Base → LayerZero:** sends “completed” message. 4. **LayerZero → peaq:** delivers completion to PaymentReceiver. 5. **peaq.PaymentReceiver:** * updates TransactionRegistry * updates claim status → **Completed** * returns stakes to buyer & seller. **Result:** Payment settled on Base; peaq records completion and settles stakes. *** ## State machine (peaq claim status) **Created → Accepted → Funded → Completed** *** ## What to poll / verify * **Buyer & Seller** poll on peaq: `get_claim(claim_id)` to read status + deadline. * **Funding truth** lives on Base: USDT is locked/released by BaseEscrow. * **peaq** is the coordination ledger: identity, claim lifecycle, audit trail, stake settlement. * **LayerZero** is the sync bridge: delivers funding + completion events to peaq. *** ## Outcome A single claim ties together: * **Identity + staking + lifecycle** on peaq * **USDT custody + payout** on Base * **Cross-chain state synchronization** via LayerZero Transaction is complete when Base pays out and peaq marks **Completed** and returns stakes. *** ## Contract addresses ### peaq | Contract | Address | | -------------------- | -------------------------------------------- | | Identity Registry | `0x2154317da929098A033ac1ef869d6A8bB771A0e3` | | Claim Registry | `0x48E33cF40D427004313760F1E514A7488e8DF0Cc` | | Transaction Registry | `0xe2C58c1A0d18E87F0cD0f658E2f2806c3458C374` | | Mock USDC | `0x0Ad7E419358a462649d2D53b5C2B25cDf85Ca009` | ### LayerZero (peaq) | Contract | Address | | ---------------- | -------------------------------------------- | | Payment Receiver | `0x49850c56cf8AAF27Ee20f78753D6A4f7C73EBB81` | | peaq LZ Endpoint | `0x6F475642a6e85809B1c36Fa62763669b1b48DD5B` | ### Base | Contract | Address | | ---------------- | -------------------------------------------- | | Base Escrow | `0x76C73B6a1A145b9D578c474efeBA83191dF5DCC3` | | Base USDT | `0x280BEeeD2EAb5F661d664C26B2A808cEdf70be2d` | | Base LZ Endpoint | `0x1a44076050125825900e736c501f859c50fE728c` | # Sim by Dune Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/indexers/sim-by-dune ## Overview The following guide introduces how to work with peaq's developer tooling to access live onchain data, interact with machine accounts, and build production-ready applications without managing your own indexing or infrastructure. It is achieved through Sim by Dune. To understand more please refer to the [Sim By Dune Documentation](https://docs.sim.dune.com/). ## What you'll learn This guide walks through the core building blocks required to develop on peaq: * Getting Started Set up your development environment, confiugure API access, and connect to the peaq network. * Core data interfaces Learn how to query balances, transactions, and asset metadata ## Quick Start ### 1. Get API Key You will need an API from the Sim dashboard to get started. 1. Go to the [Sim dashboard](https://sim.dune.com/) 2. Click on your username in the top right corner (or create an account) 3. Click on `Keys` 4. Click on `New` 5. Enter a name for your API Key 6. Click on `Create Key` 7. Copy the API Key Save the API Key in a secure location and never share in public repositories or in your code. You will need it to access the Sim API. ### 2. Set up your development environment You will need to set up your development environment to get started. We will be using JavaScript to interact with the Sim API. Open a new terminal and run the following command to create a new project: ```JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm init -y npm pkg set type=module npm install axios dotenv ``` Create `.env` file: ``` API_KEY=your_sim_api_key_here WALLET_ADDRESS=0x...your_test_address CHAIN_ID=3338 ``` ### 3. API Call Create a new file called `sim.js` and add the following code: ```JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import axios from 'axios'; const SIM_API_BASE = 'https://api.sim.dune.com/v1'; const headers = { 'X-Sim-Api-Key': process.env.API_KEY, 'Content-Type': 'application/json' }; async function getPeaqBalances() { try { console.log('Fetching peaq balances...'); console.log(`Using wallet address: ${process.env.WALLET_ADDRESS}`); const response = await axios.get(`${SIM_API_BASE}/evm/balances/${process.env.WALLET_ADDRESS}?chain_ids=${process.env.CHAIN_ID}`, { headers }); const peaqBalances = response.data.balances.filter((balance) => balance.chain === 'peaq'); console.log('peaq Balances:'); peaqBalances.forEach((balance) => { console.log(` ${balance.symbol}: ${balance.amount / 10 ** balance.decimals} (${balance.value_usd} USD)`); }); } catch (error) { console.error('❌ Error fetching balances:', error.response?.data || error.message); } } getPeaqBalances(); ``` Execute with: ``` node sim.js ``` ## Additional API Examples ### Get Token Balances ```JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function getPeaqBalances() { try { console.log('Fetching peaq balances...'); console.log(`Using wallet address: ${process.env.WALLET_ADDRESS}`); const response = await axios.get(`${SIM_API_BASE}/evm/balances/${process.env.WALLET_ADDRESS}?chain_ids=${process.env.CHAIN_ID}`, { headers }); const peaqBalances = response.data.balances.filter((balance) => balance.chain === 'peaq'); console.log('peaq Balances:'); peaqBalances.forEach((balance) => { console.log(` ${balance.symbol}: ${balance.amount / 10 ** balance.decimals} (${balance.value_usd} USD)`); }); } catch (error) { console.error('❌ Error fetching balances:', error.response?.data || error.message); } } ``` ### Monitor Wallet Transactions ```JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function monitorPeaqTransactions() { try { console.log(`Monitoring transactions for ${process.env.WALLET_ADDRESS}...`); const response = await axios.get(`${SIM_API_BASE}/evm/transactions/${process.env.WALLET_ADDRESS}`, { headers, params: { chain: 'peaq', limit: 10 } }); const transactions = response.data.transactions; console.log('🔄 Recent Transactions:'); transactions.forEach((tx) => { console.log(` Hash: ${tx.hash}`); console.log(` From: ${tx.from} → To: ${tx.to}`); console.log(` Value: ${tx.value} PEAQ`); console.log(` Gas Used: ${tx.gas_used}`); console.log(` Status: ${tx.success}`); console.log(' ---'); }); } catch (error) { console.error('❌ Error monitoring transactions:', error.message); } } ``` ### Get Token Metadata ```JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function getTokenInfo(tokenAddress) { try { console.log('Fetching token information...'); const response = await axios.get(`${SIM_API_BASE}/evm/token-info/${tokenAddress}?chain_ids=${process.env.CHAIN_ID}`, { headers }); const tokenInfo = response.data.tokens[0]; console.log('Token Information:'); console.log(` Name: ${tokenInfo.name}`); console.log(` Symbol: ${tokenInfo.symbol}`); console.log(` Decimals: ${tokenInfo.decimals}`); console.log(` Total Supply: ${tokenInfo.total_supply}`); console.log(` Current Price: ${tokenInfo.price_usd}`); console.log(` Logo: ${tokenInfo.logo}`); return tokenInfo; } catch (error) { console.error('❌ Error fetching token info:', error.message); } } // Example usage with USDC address getTokenInfo('0xbba60da06c2c5424f03f7434542280fcad453d10'); ``` Can see exhaustive list of token addresses on [peaq's token list](https://tokenlist.peaq.xyz/) ### Find Top Holders ```JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function getTokenHolders(tokenAddress, limit = 10) { try { console.log('Fetching token holders...'); const response = await axios.get(`${SIM_API_BASE}/evm/token-holders/${process.env.CHAIN_ID}/${tokenAddress}`, { headers, params: { limit } }); const holders = response.data.holders; console.log(`Top ${holders.length} holders:`); holders.forEach((holder, index) => { console.log(`${index + 1}. ${holder.wallet_address}`); console.log(` Balance: ${parseFloat(holder.balance)}`); // Make sure to parse if you want human readable format console.log(` First Acquired: ${holder.first_acquired}`); console.log(' ---'); }); return holders; } catch (error) { console.error('❌ Error fetching token holders:', error.message); } } ``` ### Track Swaps and Transfers ```JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function getWalletActivity(walletAddress, limit = 10) { try { console.log('Fetching wallet activity on peaq...'); const response = await axios.get(`${SIM_API_BASE}/evm/activity/${walletAddress}?chain_ids=${process.env.CHAIN_ID}`, { headers, params: { limit } }); const activities = response.data.activity; console.log(`Recent activities (${activities.length}):`); activities.forEach((activity, index) => { console.log(`${index + 1}. Type: ${activity.type.toUpperCase()}`); console.log(` Hash: ${activity.tx_hash}`); console.log(` Time: ${new Date(activity.block_time).toLocaleString()}`); if (activity.type === 'receive' && activity.asset_type === 'erc20') { console.log(` Received: ${activity.value} ${activity.token_metadata?.symbol}`); console.log(` From: ${activity.from}`); } console.log(' ---'); }); return activities; } catch (error) { console.error('❌ Error fetching activity:', error.message); } } ``` ### Get NFT Collections ```JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function getNFTCollections(walletAddress) { try { console.log('Fetching NFT collections...'); const response = await axios.get(`${SIM_API_BASE}/evm/collectibles/${walletAddress}?chain_ids=${process.env.CHAIN_ID}`, { headers }); const collectibles = response.data.entries; console.log(`NFT Collections (${collectibles.length}):`); // Group by collection const collections = {}; collectibles.forEach((nft) => { if (!collections[nft.contract_address]) { collections[nft.contract_address] = { name: nft.collection_name, items: [] }; } collections[nft.contract_address].items.push(nft); }); Object.entries(collections).forEach(([address, collection]) => { console.log(`📚 ${collection.name} (${collection.items.length} items)`); console.log(` Contract: ${address}`); collection.items.slice(0, 3).forEach((nft) => { console.log(` • Token ID: ${nft.token_id}`); console.log(` Name: ${nft.name || 'Unnamed'}`); console.log(` Image: ${nft.image_url || 'No image'}`); }); if (collection.items.length > 3) { console.log(` ... and ${collection.items.length - 3} more`); } console.log(' ---'); }); return collectibles; } catch (error) { console.error('❌ Error fetching NFTs:', error.message); } } ``` ## Advanced Operations ### Portfolio Analytics ```JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import axios from 'axios'; const SIM_API_BASE = 'https://api.sim.dune.com/v1'; const headers = { 'X-Sim-Api-Key': process.env.API_KEY, 'Content-Type': 'application/json' }; async function buildAdvancedPortfolio(walletAddress) { try { console.log('📊 Building advanced portfolio analytics...'); // Get all data in parallel for better performance const [balancesRes, transactionsRes, activityRes, nftsRes] = await Promise.all([ axios.get(`${SIM_API_BASE}/evm/balances/${walletAddress}?chain_ids=${process.env.CHAIN_ID}`, { headers }), axios.get(`${SIM_API_BASE}/evm/transactions/${walletAddress}`, { headers, params: { limit: 50, chain: 'peaq' } }), axios.get(`${SIM_API_BASE}/evm/activity/${walletAddress}?chain_ids=${process.env.CHAIN_ID}`, { headers, params: { limit: 50 } }), axios.get(`${SIM_API_BASE}/evm/collectibles/${walletAddress}?chain_ids=${process.env.CHAIN_ID}`, { headers }) ]); const balances = balancesRes.data.balances; const transactions = transactionsRes.data.transactions; const activities = activityRes.data.activity; const nfts = nftsRes.data.entries; // Calculate comprehensive metrics const totalValue = balances.reduce((sum, asset) => sum + parseFloat(asset.value_usd || 0), 0); const peaqAssets = balances.filter((b) => b.chain === 'peaq'); const peaqValue = peaqAssets.reduce((sum, asset) => sum + parseFloat(asset.value_usd || 0), 0); console.log('💼 Advanced Portfolio Analytics:'); console.log(` Total Portfolio Value: $${totalValue.toFixed(2)}`); console.log(` PEAQ Network Value: $${peaqValue.toFixed(2)} (${((peaqValue / totalValue) * 100).toFixed(1)}%)`); console.log(` Total Assets: ${balances.length} tokens`); console.log(` NFT Collections: ${new Set(nfts.map((n) => n.contract_address)).size}`); console.log(` Total NFTs: ${nfts.length}`); return { totalValue, peaqValue, balances, transactions, activities, nfts }; } catch (error) { console.error('❌ Portfolio analytics error:', error.message); } } buildAdvancedPortfolio(process.env.WALLET_ADDRESS); ``` ### Token Analysis Tool: ```JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import axios from 'axios'; const SIM_API_BASE = 'https://api.sim.dune.com/v1'; const headers = { 'X-Sim-Api-Key': process.env.API_KEY, 'Content-Type': 'application/json' }; async function analyzeToken(tokenAddress) { try { console.log(`Analyzing token: ${tokenAddress}`); // Get comprehensive token data const [infoRes, holdersRes] = await Promise.all([ axios.get(`${SIM_API_BASE}/evm/token-info/${tokenAddress}?chain_ids=${process.env.CHAIN_ID}`, { headers }), axios.get(`${SIM_API_BASE}/evm/token-holders/${process.env.CHAIN_ID}/${tokenAddress}`, { headers, params: { limit: 100 } }) ]); const tokenInfo = infoRes.data.tokens[0]; const holders = holdersRes.data.holders; // Calculate holder distribution const totalSupply = parseFloat(tokenInfo.total_supply); const holderAnalysis = { whales: holders.filter((h) => parseFloat(h.balance) / totalSupply > 0.01).length, // >1% large: holders.filter((h) => { const pct = parseFloat(h.balance) / totalSupply; return pct > 0.001 && pct <= 0.01; // 0.1% - 1% }).length, medium: holders.filter((h) => { const pct = parseFloat(h.balance) / totalSupply; return pct > 0.0001 && pct <= 0.001; // 0.01% - 0.1% }).length }; // Top holder concentration const top10Concentration = holders.slice(0, 10).reduce((sum, holder) => sum + parseFloat(holder.balance) / totalSupply, 0) * 100; console.log('📊 Token Analysis Results:'); console.log(` Name: ${tokenInfo.name} (${tokenInfo.symbol})`); console.log(` Price: ${tokenInfo.price_usd}`); console.log(` Total Supply: ${parseFloat(tokenInfo.total_supply).toLocaleString()}`); console.log(` Total Holders: ${holders.length}`); console.log(` Whales (>1%): ${holderAnalysis.whales}`); console.log(` Large Holders (0.1-1%): ${holderAnalysis.large}`); console.log(` Medium Holders (0.01-0.1%): ${holderAnalysis.medium}`); console.log(` Top 10 Concentration: ${top10Concentration.toFixed(2)}%`); return { tokenInfo, holders, holderAnalysis }; } catch (error) { console.error('❌ Token analysis failed:', error.message); } } analyzeToken('0xbba60da06c2c5424f03f7434542280fcad453d10'); // USDC address ``` Can see exhaustive list of token addresses on [peaq's token list](https://tokenlist.peaq.xyz/) ## Conclusion You now have a complete, working foundation for accessing peaq onchain data using **Sim by Dune**. This guide demonstrated how to: * Connect to the peaq network without running your own indexers * Query balances, transactions, token metadata, and NFT holdings * Track wallet and token activity at scale * Build higher-level analytics such as portfolio view and token distribution insights These building blocks are sufficient for most production use cases, including dashboards, analytics services, DePIN backends, and automated agents. ### Where to go next From here, you can extend this setup in several directions: * **Integrate machine identities** - Combine onchain data with machine DIDs to build identity-aware applications. * **Build persistent analytics** - Store queried data in a database to support historical analysis, alerting, or reporting. * **Add automation** - Trigger workflows based on onchain activity, such as rewards distribution or machine state changes. * **Harden for production** - Implement caching, request batching, rate limiting, and monitoring to support higher throughput. * **Explore the broader peaq stack** - Pair Sim by Dune with peaq-native smart contracts, Machine Accounts, and DePIN primitives to build end-to-end systems. For deeper architectural concepts and advanced integrations, refer to the official peaq developer documentation and the Sim by Dune docs. # Subsquid Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/indexers/subsquid **Indexers** provide an easy solution to query on-chain data. They process smart contract events in the background and store them in a database, which you can query using GraphQL. This enables efficient access to all smart contract events and allows for filtering the data as needed. ## Prerequisites * **Node.js 20.x or later** is installed. * **Docker** is installed, and you have basic knowledge of using it. ## Instructions ### Install Subquid **Create a Squid:** Follow the instructions for [creating a Squid](https://docs.sqd.dev/squid-cli/installation/). * During setup, create a new Squid with a name of your choice. This will be used later. ### Create the Indexer #### Initialize the JavaScript environment ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm init ``` #### Install required packages ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm i dotenv typeorm @subsquid/evm-processor @subsquid/typeorm-store @subsquid/typeorm-migration @subsquid/graphql-server @subsquid/evm-abi npm i typescript @subsquid/typeorm-codegen @subsquid/evm-typegen --save-dev ``` #### Add `tsconfig.json` Create a `tsconfig.json` file in your project root with the following configuration: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "compilerOptions": { "rootDir": "src", "outDir": "lib", "module": "commonjs", "target": "es2020", "esModuleInterop": true, "skipLibCheck": true, "experimentalDecorators": true, "emitDecoratorMetadata": true } } ``` #### Define the schema Create a `schema.graphql` file and define the database schema. For example: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} type Transfer @entity { id: ID! from: String! @index to: String! @index value: BigInt! } ``` This schema represents the structure of the events you want to store in your database. #### Generate TypeORM entities Run the following command to generate TypeORM entities based on the schema: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx squid-typeorm-codegen ``` #### Create a `.env` file Define the database credentials in a `.env` file: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} DB_NAME=peaq DB_PORT=23798 ``` #### Create `docker-compose.yaml` Create a `docker-compose.yaml` file to set up a PostgreSQL database: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} version: "3" services: db: image: postgres:15 environment: POSTGRES_DB: "${DB_NAME}" POSTGRES_PASSWORD: postgres ports: - "${DB_PORT}:5432" ``` Start the database container: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} docker compose up -d ``` #### Compile TypeORM classes Compile the generated TypeORM classes: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx tsc ``` #### Generate and apply migrations * Generate the migration file: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx squid-typeorm-migration generate ``` * Apply the migration: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx squid-typeorm-migration apply ``` ### Tie the code together Create a `src/main.ts` file and add the following code: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { EvmBatchProcessor } from '@subsquid/evm-processor'; import { TypeormDatabase } from '@subsquid/typeorm-store'; import { DataStoredEvent } from './model'; const DataStoredEventTopic = '0x9455957c3b77d1d4ed071e2b469dd77e37fc5dfd3b4d44dc8a997cc97c7b3d49'; const CONTRACT_ADDRESS = '0xdbdac2ef52681230f996624a5fa2624b06972671'; const processor = new EvmBatchProcessor() .setRpcEndpoint({ url: 'https://rpcpc1-qa.agung.peaq.network', }) .setFinalityConfirmation(5) .setBlockRange({ from: 3564900 }) .addLog({ address: [CONTRACT_ADDRESS] }) .setFields({ log: { transactionHash: true, }, }); const db = new TypeormDatabase(); processor.run(db, async (ctx) => { const events: DataStoredEvent[] = []; for (let block of ctx.blocks) { for (let log of block.logs) { if (log.topics[0] === DataStoredEventTopic) { const data = BigInt(log.data); console.log("ID: ", log.id); console.log("DATA: ", data); console.log("BLOCK_NUMBER: ", block.header.height); console.log("TX_HASH: ", log.block.hash); events.push(new DataStoredEvent({ id: log.id, data: Number(data), blockNumber: block.header.height, transactionHash: log.block.hash, })); } } } await ctx.store.insert(events); }); ``` ### Compile and start the processor * Compile the code: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx tsc ``` * Start the processor: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} node -r dotenv/config lib/main.js ``` ### Start the GraphQL server In a separate terminal, start the GraphQL server: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx squid-graphql-server ``` # The Graph Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/indexers/the-graph Getting **historical** data on a smart contract can be frustrating when building a dapp. [The Graph](https://thegraph.com/) provides an easy way to query smart contract data through APIs known as **subgraphs**. The Graph's infrastructure relies on a decentralized network of indexers, enabling your dapp to become truly decentralized. Follow this [quick-start](https://thegraph.com/docs/en/quick-start/) guide to create, deploy, and query a subgraph within 5 minutes. ## Why The Graph? The Graph provides a robust solution for indexing and querying blockchain data. It effectively tackles the challenge of reading blockchain data without creating a centralized bottleneck. With its network of indexers, The Graph offers increased redundancy and **quicker query responses**. Using GraphQL queries, your dApp can pinpoint exactly the fields it requires. ## Key Features * **Decentralized Indexing**: Enables indexing blockchain data through multiple indexers, thus eliminating any single point of failure * **GraphQL Queries**: Provides a powerful GraphQL interface for querying indexed data, making data retrieval super simple. * **Customizable & Reusable**: Define your own logic for transforming & storing blockchain data. Reuse subgraphs published by other developers. * **Pay per Use**: No monthly plans. Pay only for [the queries you use](https://thegraph.com/studio-pricing/). # Building with AI Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/integrating-using-ai Use AI coding assistants to build with peaq faster # Building with AI peaq documentation is optimized for AI coding assistants. Whether you're using Claude, Cursor, GitHub Copilot, or other AI tools, you can reference our docs directly for accurate information about the peaq SDK and APIs. ## LLM-Optimized Documentation ### llms.txt Files peaq docs support the [llms.txt standard](https://llmstxt.org/), making our documentation easily accessible to AI tools: | File | URL | Description | | ----------------- | ------------------------------------- | ---------------------------------------- | | **llms.txt** | `https://docs.peaq.xyz/llms.txt` | Navigation structure with page summaries | | **llms-full.txt** | `https://docs.peaq.xyz/llms-full.txt` | Complete documentation in plain text | These files are automatically kept in sync with our documentation. ### Copy a Single Page Click the **Copy page** button at the top of any docs page to copy its contents in a clean, AI-friendly format, then paste it directly into your AI assistant's context window. ## Using Cursor ### MCP Server Install the peaq docs MCP server for queryable access to our documentation directly from Cursor, Claude Desktop, or any MCP-compatible client. **One-click install for Cursor:** ``` npx @mintlify/mcp add docs.peaq.xyz ``` ### Cursor @Docs Add peaq docs as a context source in Cursor: 1. Open Cursor Settings → Features → Docs 2. Add `https://docs.peaq.xyz` as a documentation source 3. Reference it in any Cursor chat by typing `@Docs` and selecting peaq ## Using Claude ### Claude Projects Create a dedicated Claude Project for peaq development: 1. Create a new project in [Claude](https://claude.ai) 2. Add `https://docs.peaq.xyz/llms-full.txt` as a knowledge source 3. Set custom instructions like *"Use the peaq documentation to answer questions about the peaq SDK and APIs"* ### Claude Code Add the following to your `.mcp.json` for context-aware assistance in your terminal: ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "mcpServers": { "peaq-docs": { "url": "https://docs.peaq.xyz/mcp" } } } ``` ## Prompting Tips When prompting any AI assistant about peaq: * **Specify the SDK** — Clarify if you're using the JavaScript SDK or interacting directly with the API so the model generates the right patterns * **Mention the function** — Be explicit about whether you're working with peaq ID, peaq access, peaq store, peaq verify, or UMT * **Include chain context** — Mention if you're targeting mainnet or testnet to get accurate endpoints and config values * **Paste the relevant page** — Use the **Copy page** button on specific docs pages and paste into your conversation for the most accurate results # Choosing Validator Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-delegator/choosing-validator Delegating your peaq tokens to a validator is a vital step in supporting the **security** and **stability** of the peaq network, whether you're interacting through the EVM side or Substrate side of peaq. By delegating, you **trust** a validator to participate in the network consensus on your behalf, and in return, you may earn a share of the rewards. However, knowing how to choose a validator is crucial to **maximizing** your rewards and **minimizing** the risk of downtime, slashing, or other penalties. This guide provides a high-level strategy for deciding who to delegate to. ## Prerequisites * **Basic Staking Knowledge**: You understand the fundamental concepts of staking and how it supports the blockchain network by securing and validating transactions. * **Wallet Setup**: You have a compatible wallet for the peaq network, with some peaq tokens available to stake. This could be a Substrate-compatible wallet (e.g., Polkadot.js) or an EVM-compatible wallet (e.g., MetaMask) depending on which chain you're interacting with. * **Network Familiarity**: You know how to access relevant blockchain explorers (e.g., [Subscan](https://peaq.subscan.io/validator)) to view validator statistics or use the [peaq app staking dashboard](https://portal.peaq.xyz/portal/staking). * **Risk Tolerance**: You understand that there is risk in choosing a validator, especially if they have poor performance or if they are penalized for misbehavior. ## Instructions ### 1. Research Validators * **Official Sources**: Start by looking for an official validator list from the peaq app or by querying the `CandidatePool` under `Chain State` in Polkadot.js . The peaq app (at this time) only shows validators in the active set, whereas the Polkadot.js `CandidatePool` will return the entire list of potential validators considered for candidacy in the active set of validators. * **Community Reputation**: Visit peaq's community forums or social channels ( [Discord](https://discord.com/invite/peaqnetwork) ) to get a sense of which validators are well-regarded. Word-of-mouth recommendations can be valuable, especially if these validators have a history of reliable performance. * **Block Explorer Metrics**: Use a block explorer (e.g., [Subscan's Staking page](https://peaq.subscan.io/validator)) to check each validator's uptime, total stake, commission rate, performance, and slashing history. You may also view their performance statistics by viewing peaq's [Telemetry](https://telemetry.polkadot.io/#list/0xd2a5d385932d1f650dae03ef8e2748983779ee342c614f80854d32b8cd8fa48c). ### 2. Evaluate Key Factors * **Commission Rate**: Validators charge a commission rate (a percentage of your rewards that they keep). A lower commission can mean higher rewards for delegators, but extremely low commissions aren't always optimal if they come with higher risk or less reliable infrastructure. * **Uptime and Performance**: Prioritize validators with near 100% uptime and consistent performance. Frequent downtime can reduce rewards and even trigger slashing events. * **Slashing History**: Avoid validators with a history of slashing events or repeated misbehavior, as they put your stake at greater risk. * **Stake Distribution**: Check if the validator already has a large share of the network stake. While popular validators may appear safer, it's good for the network to spread stake across multiple validators to maintain decentralization. * **Validator's Infrastructure**: Look for validators with robust infrastructure (e.g., cloud or dedicated servers, backups, monitoring tools). Although the details might not be publicly visible, many validators share information about their setup in community forums or on their websites. ### 3. Create a Shortlist * **Cross-Reference Data**: After you gather performance metrics and read community feedback, narrow down your list to a few validators that best align with your preferences (low commission, good reputation, reliable performance). * **Balance Your Stake**: Consider delegating across multiple wallets / validators to spread your risk. If one validator has issues, your stake on the other wallet(s) / validator(s) can continue earning rewards. ### 4. Delegate Your Tokens * **Access the Staking UI**: * **Substrate**: Use the native Substrate staking UI (e.g., Polkadot.js) to bond your peaq tokens and select your chosen validator(s). * **EVM**: Use an EVM-compatible wallet (e.g., MetaMask or another Web3 interface) to delegate your tokens through the peaq app. * **Confirm Bonding & Delegation**: Follow the on-screen instructions to specify the amount of peaq tokens you want to delegate. Double-check you're delegating to the correct validator addresses. * **Monitor Your Delegation**: Track your delegation on the block explorer or staking dashboard. Ensure that your chosen validator remains active and continues providing good performance. ### 5. Monitor and Adjust * **Ongoing Monitoring**: Keep an eye on the validator's health, performance, and commission changes. If you notice a decline in performance or a spike in commission rate, consider re-delegating to another validator. * **Stay Informed**: Be active in the community channels for any announcements regarding network upgrades, validator misbehavior, or new validator opportunities. ## Summary Choosing the right validator for your peaq token delegation is both an art and a science. By **researching** validator performance, commission rates, uptime, and history, you significantly **improve** your chances of earning consistent rewards while supporting a healthy, decentralized network. Always remember that validator reliability can shift over time, so it's wise to **monitor** and **adjust** your delegations as needed. Following these steps on both **Substrate-based** and **EVM-based** peaq networks will help you strike a balance between maximizing rewards and minimizing risk, ultimately fostering a more secure and robust ecosystem. # Adjust Delegator Stake Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-delegator/peaq-portal/adjust-delegator-stake ## Change your stake amount: ### 1. Find the validator to which you've staked your tokens and click **Manage** peaq-portal-8 ### 2. Then click **Stake more** peaq-portal-9 ### 3. Enter the additional amount of PEAQ you would like to stake in the pop-up window peaq-portal-10 ### 4. This is a success message you'll see once additional tokens are staked peaq-portal-11 # Join Delegator Set Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-delegator/peaq-portal/join-delegator-set ## Stake your PEAQ ### 1. Go to the [peaq app](https://portal.peaq.xyz/portal/staking) peaq-portal-1 ### 2. Connect your wallet peaq-portal-2 ### 3. This is the home page you'll see once connected peaq-portal-3 ### 4. Click on **Staking** in the left sidebar peaq-portal-4 ### 5. Scroll down to see the list of available validators and click **Stake** on the validator of your choice peaq-portal-5 ### 6. Enter the number of PEAQ tokens you would like to stake and click **Stake (Minimum 100 peaq)** peaq-portal-6 ### 7. This is a success message you'll see once staked peaq-portal-7 # Leave Delegator Set Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-delegator/peaq-portal/leave-delegator-set ## Unstake your PEAQ: 1. Go to the [**Staking**](https://portal.peaq.xyz/portal/staking) tab in peaq app and find validator to which you've delegated your tokens 2. Click **Unstake** peaq-portal-12 3. Confirm that you want to unstake your tokens by clicking **Unstake** peaq-portal-13 4. This is how your **Staking** tab will look like, while your tokens are being unstaked peaq-portal-14 # Adjust Delegator Stake Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-delegator/polkadot-js-org/adjust-delegator-stake ## Adjust Delegator Stake Open the **Developer** tab, and click **Extrinsics**. There, you'll need to submit the extrinsic with the amount you would like to add/subtract from your stake. ### To Increase Your Stake `Developer → Extrinsics → Submission → parachainStaking → delegatorStakeMore(more)` polkadot-js-delegator-2 Keep in mind that krest has 18 decimals, so if you want to stake 300 \$KREST you'll need to enter `300000000000000000000`. 1. Select `Id` in the dropdown list in the field `collator: MultiAddress (LookupSource)`. 2. Paste the same `Id` (which you used during the `joinDelegators` step) to the `Id:AccountId` field. 3. Enter the additional staking amount to the `more: u128 (BalanceOf)` field. 4. Click `Submit Transaction` ### To Decrease Your Stake `Developer → Extrinsics → Submission → parachainStaking → delegatorStakeLess(less)` polkadot-js-delegator-3 Keep in mind that krest has 18 decimals, so if you want to stake 100 \$KREST you'll need to enter `100000000000000000000`. 1. Select `Id` in the dropdown list in the field `collator: MultiAddress (LookupSource)`. 2. Paste the same `Id` (which you used during the `joinDelegators` step) to the `Id:AccountId` field. 3. Enter the staking amount to be subtracted in the `less: u128 (BalanceOf)` field. 4. Click `Submit Transaction` ## Unlock Your Unstaked Tokens `Developer → Extrinsics → Submission → parachainStaking → unlockUnstaked(target)` polkadot-js-delegator-4 * You will be able to unlock your tokens in 14 days after sending `delegatorStakeLess`. * To check when you'll be able to unstake, use: `Developer → Chain state → parachainStaking → unstaking` polkadot-js-delegator-5 1. Select `Id` in the dropdown list in the field `target: MultiAddress (LookupSource)`. 2. Use the address of your delegator account in the `Id:AccountId` field. 3. Click `Submit Transaction`. # Join Delegator Set Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-delegator/polkadot-js-org/join-delegator-set ## Join the Delegator Set Open the **Developer** tab, and click **Extrinsics**. There, you'll need to submit the extrinsic with the collator of choice and your stake: `Developer → Extrinsics → Submission → parachainStaking → joinDelegators(collator, amount)` polkadot-js-delegator-1 1. Select `Id` in the dropdown list in the field `collator: MultiAddress (LookupSource)`. 2. Paste the `Id` (which you copied during the previous step) to the `Id:AccountId` field. 3. Enter the staking amount to the `amount: u128 (BalanceOf)` field. * Keep in mind that peaq has 18 decimals, so if you want to stake 250 tokens you'll need to enter `250000000000000000000`. * The min stake amount for the delegator is `100 $PEAQ`. * 1 collator can be backed up by `100` delegators max. 4. Click `Submit Transaction` # Leave Delegator Set Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-delegator/polkadot-js-org/leave-delegator-set ## Leave Delegator Set Open the **Developer** tab, and click **Extrinsics**, there you'll need to send 2 extrinsics to leave the set of delegators and unlock your unstaked tokens. ### Request to Leave `Developer → Extrinsics → Submission → parachainStaking → leaveDelegators` It takes 14 days to unlock your stake after submitting `leaveDelegators` transaction. polkadot-js-delegator-6 Click `Submit Transaction`. ## Unlock Your Unstaked Tokens `Developer → Extrinsics → Submission → parachainStaking → unlockUnstaked(target)` polkadot-js-delegator-7 You will be able to unlock your tokens in 14 days ( `201600` blocks) after sending `leaveDelegators`. To check when you'll be able to unstake, use: `Developer → Chain state → parachainStaking -> unstaking` polkadot-js-delegator-8 1. Select `Id` in the dropdown list in the field `target: MultiAddress (LookupSource)`. 2. Use the address of your delegator account in the `Id:AccountId` field. 3. Click `Submit Transaction`. # Adjust Commission Rate Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-validator/adjust-commission-rate This document provides guidance on setting up and adjusting the commission rate for collators on the peaq network. It highlights key aspects of the new token economy introduced in runtime upgrade v0.0.104. ## Setting Up Commission Rate To set up your commission rate, follow these steps: 1. **Open polkadot.js** * Navigate to the [polkadot.js](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fpeaq.api.onfinality.io%2Fpublic#/explorer) interface. validator-14 2. **Navigate to Extrinsics** * Go to `Developer → Extrinsics → parachainStaking → setCommission`. validator-15 3. **Submit Transaction** * Enter your desired commission rate and submit the transaction. To set a 1% commission, enter `10000` in the commission field. For 25%, enter `250000`. ## Reward Calculation More information about the reward calculation can be found [here](/peaqchain/build/advanced-operations/node-operations/block-reward-distribution#reward-calculation). # Adjust Validator Stake Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-validator/adjust-validator-stake You can change your validator stake at any moment in time. ## To Increase Your Stake `Developer → Extrinsics → Submission → parachainStaking -> candidateStakeMore(more)` validator-7 Keep in mind that peaq has `18` decimals, so if you want to add `1,234` Tokens, you'll need to enter `1234000000000000000000`. 1. Enter the amount you would like to add to your stake. 2. Click `Submit Transaction`. ## To Decrease Your Stake `Developer → Extrinsics → Submission → parachainStaking -> candidateStakeLess(less)` validator-8 Keep in mind that peaq has `18` decimals, so if you want to add `1,234` Tokens, you'll need to enter `1234000000000000000000`. 1. Enter the amount you would like to subtract from your stake. 2. Click `Submit Transaction`. ### Unlock Your Unstaked Tokens `Developer → Extrinsics → Submission → parachainStaking → unlockUnstaked(target)` You will be able to unlock your tokens in 14 days after sending `candidateStakeLess`. To check when you'll be able to unstake, use: `Developer → Chain state → parachainStaking -> unlockUnstaked(target)` validator-9 1. Select `Id` in the dropdown list in the field `target: MultiAddress (LookupSource)`. 2. Use the address of your validator account in the `Id:AccountId` field. 3. Click `Submit Transaction`. # Choosing Node Provider Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-validator/choosing-node-provider Running a peaq validator requires a robust and reliable **Virtual Private Server** (VPS) or dedicated server to ensure optimal performance and uptime. You can also opt for a **managed node provider** like **OnFinality** to reduce operational overhead. This guide will help you choose a suitable provider, with examples from **AWS**, **DigitalOcean**, **Linode** (Akamai), and **OnFinality**. The recommended hardware specifications provided by the peaq engineering team serve as a baseline for reliable results. ## Prerequisites * Basic familiarity with setting up **virtual** servers. * Aim to run a peaq validator node using the **recommended** hardware. * Your goal is to balance **performance**, **cost-efficiency**, and **reliability** when selecting a VPS provider. * You are running **Ubuntu 20.04** as your operating system. ## Recommended Hardware Requirements * **OS**: Ubuntu 20.04 * **CPU**: 3.3 GHz AMD EPYC 7002 * **Storage**: 1TB SSD * **Memory**: 8GB RAM The above specifications have been **tested** and **proven** by peaq's engineering team to produce reliable results over time. While these are recommended, you can experiment with other configurations to **optimize** for cost and performance. ## Managed Node Provider (OnFinality) If you prefer a managed experience, **OnFinality** offers dedicated collator (validator) nodes for peaq. This approach removes most server administration while providing fast sync and production-grade monitoring. ### Why OnFinality * Managed provisioning, monitoring, and scaling for peaq nodes * Dedicated nodes with "Lightning Restore" for fast sync * Built-in secure API endpoints (HTTP/WebSocket) * Clear operational guidance for collator setup ### Quick start with OnFinality 1. Create an account and deploy a Dedicated Node for peaq (Collator Node Type). 2. Use the recommended image version and region; ensure resources meet peaq’s baseline specs. 3. During on-chain configuration, set `--rpc-methods=unsafe` (temporarily) and enable external RPC if needed. After setup, switch back to `--rpc-methods=safe` and disable unsafe external RPC. 4. Generate session keys via `author_rotateKeys` (UI or RPC), then set them on-chain (`session.setKeys`). 5. Stake the minimum required amount and join the validator candidate pool. Refer to the step-by-step guide for details: [How to Set Up a Validator for peaq on OnFinality](https://blog.onfinality.io/how-to-set-up-a-validator-for-peaq-on-onfinality/). ## Instructions for Choosing a VPS Provider ### 1. Evaluate the Recommended Hardware Ensure the chosen VPS provider offers a machine with: * **High-performance CPU** (preferably AMD EPYC 7002 or equivalent) * **Ample SSD storage** (1TB or more) * **Adequate RAM** (8GB or more) ### 2. Compare VPS Providers If you’re managing your own infrastructure, the following VPS providers are commonly used. Otherwise, see the managed option above for OnFinality. #### Amazon Web Services (AWS) * **Instance Recommendation**: `c5ad.xlarge` * **Specs**: * 4 vCPUs (AMD EPYC) * 8GB RAM * Up to 10 Gbps network bandwidth * Elastic Block Storage (EBS) for SSD storage * **Why AWS**: * High availability and reliability * Flexible scaling options * Extensive documentation and support * **Considerations**: * Pricing can be higher compared to other providers. #### DigitalOcean * **Instance Recommendation**: Premium AMD CPU Droplet (e.g., `CPU-Optimized 8GB`) * **Specs**: * 4 vCPUs * 8GB RAM * 160GB SSD local storage (option to scale to 1TB via storage volumes) * **Why DigitalOcean**: * Simplicity in UX for setup and management * Competitive pricing * Integrated monitoring tools * **Considerations**: * Limited to specific regions for Premium CPUs. #### Linode (Akamai) * **Instance Recommendation**: Dedicated CPU Plan (e.g., `Dedicated 8GB`) * **Specs**: * 4 Dedicated vCPUs * 8GB RAM * 160GB SSD (scale up with storage volumes) * **Why Linode**: * Affordable pricing * Strong performance for dedicated resources * User-friendly interface / UX * **Considerations**: * Storage upgrades may require additional configurations. #### OnFinality (Managed Provider) * **Offering**: Dedicated collator (validator) nodes for peaq * **Why OnFinality**: * Managed node lifecycle (deploy, monitor, scale) with rapid sync * Secure RPC endpoints and operational best practices * Reduces complexity vs self-managing VPS * **Considerations**: * Managed service pricing; fewer low-level customization options * Follow security guidance to revert RPC methods to safe after setup * Full guide: [How to Set Up a Validator for peaq on OnFinality](https://blog.onfinality.io/how-to-set-up-a-validator-for-peaq-on-onfinality/) ### 3. Cost and Regional Availability * Compare the monthly **pricing** for the recommended instances from AWS, DigitalOcean, and Linode. * Check for **regional** availability to ensure low latency, especially if you are running a collator in a specific geographic location. ### 4. Additional Factors to Consider * **Network Performance**: Ensure the VPS offers sufficient bandwidth and low latency. * **Support and SLAs**: Check if the provider offers technical support and Service Level Agreements (SLAs) for uptime. * **Scalability**: Choose a provider that allows easy scaling of resources in case your node requires higher specs. * **Backup and Recovery**: Opt for providers that offer automated backups and disaster recovery solutions. ## Summary | Provider | Recommended Plan | Specs | Pricing Notes | | ---------------- | ---------------------- | ------------------------------------------- | ------------------------------------------ | | **AWS** | `c5ad.xlarge` | 4 vCPUs, 8GB RAM, 1TB SSD | Higher cost, high reliability | | **DigitalOcean** | `CPU-Optimized 8GB` | 4 vCPUs, 8GB RAM, scalable SSD | Moderate cost, easy to use | | **Linode** | `Dedicated 8GB` | 4 vCPUs, 8GB RAM, scalable SSD | Affordable, solid performance | | **OnFinality** | Managed Dedicated Node | Managed collator with fast sync, secure RPC | Managed service (usage-based), minimal ops | By following this guide, you can choose a VPS provider that aligns with your technical requirements and budget to reliably run your peaq validator node. While AWS, DigitalOcean, and Linode are excellent options, the final choice depends on your specific needs and preferences. If you want to minimize operational overhead, consider the managed route with OnFinality and follow their peaq validator guide: [How to Set Up a Validator for peaq on OnFinality](https://blog.onfinality.io/how-to-set-up-a-validator-for-peaq-on-onfinality/). # Generate Session Key Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-validator/generate-session-key ## Generate the key 1. Make the request from your **VM** (where your validator node container is running) using the following command: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl -X 'POST' \ -H "Content-Type: application/json" \ -d '{"id":1, "jsonrpc":"2.0", "method": "author_rotateKeys", "params":[]}' http://localhost:9944 ``` 2. Copy the **key** from the response: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} {"jsonrpc":"2.0","result":"0xf22d82cfcf4402990a0bef3abefd1e58217ff3a86c548d30f1495fd529460d76","id":1} ``` 3. In this case, the **key** is: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} "0xf22d82cfcf4402990a0bef3abefd1e58217ff3a86c548d30f1495fd529460d76" ``` ## Associate your session key with your validator account Open the **Developer** tab, and click **Extrinsics**, there you'll need to submit the following extrinsic: `Developer → Extrinsics → session → setKeys(keys, proof)` You can use any peaq endpoint to submit `setKeys` extrinsic, including the endpoint from your VM with validator node. validator-3 1. Paste your session key into the `aura: SpConsensusAuraSr25519AppSr25519Public field` 2. Paste 0x00 into the field `proof: Bytes` 3. Click `Submit Transaction` After using setKeys extrinsic, you can connect back to the public endpoint. ### Double-check that your session key is associated with your wallet address Open the **Developer** tab, and click **Chain state**. There, you'll need to submit the following state query: `Developer → Chain state → Session → nextKeys[AccountId32]` validator-4 1. Toggle the switch `include option` and select your validator address in the `AccountId32` field. 2. Click the `+` button. 3. Confirm that your key is displayed. ## Remove unsafe rpc methods from your node Remove `--unsafe-rpc-external` and `--rpc-methods=unsafe` methods, which we needed in the beginning, and rerun the container. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} sudo docker run -d -v peaq-storage:/chain-data -p 9944:9944 peaq/parachain:peaq-v0.0.105 \ --collator \ --parachain-id 3338 \ --chain ./node/src/chain-specs/peaq-raw.json \ --base-path chain-data \ --port 30333 \ --rpc-port 9944 \ --rpc-cors=all \ --execution=wasm \ --out-peers 50 \ --in-peers 50 \ -- \ --execution wasm \ --port 30343 \ --sync fast \ --rpc-port 9977 ``` # Node Operator Introduction Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-validator/introduction ## Discussion peaq’s block production relies on **validators** and **delegators** working together to ensure that transactions are propagated into blocks in a fast, reliable, censorship-resistant manner. The **main role** of a validator is to **create blocks** and keep the **state** of the network up to date. After creation blocks are provided to validators on the **relay chain** for final approval. The role of validators can be compared to miners in networks that use **Proof of Work**. However, unlike miners, validators don't improve network safety, but they're essential for **maintaining** network activity, ensuring it's decentralized and censorship-resistant. In essence, validators generate proofs of state changes for validators, based on the **Polkadot** and **Kusama** relay chains. They also run a complete node on both the relay chain and parachain in their respective operations. peaq is using [NPoS](https://learn.bybit.com/glossary/definition-nominated-proof-of-stake-npos/) (Nominated Proof of Stake) for block production and relies on the Relay Chain for block **validation** and **finalization**. It's important to mention that due to the **security** design of the Relay Chain, dishonest validators can **never finalize** invalid blocks. Therefore, the maximum damage that dishonest validators can inflict is to **slow down** or **temporarily stop** the network. Provided there is a single honest validator, the parachain remains **secure** and **fully operational**. However, the speed at which blocks are created would be slower than when there's a complete group of honest and working validator nodes. Delegators play another crucial role in the block production process by **filtering** the pool of validator candidates for **honest, reliable validators**, who show steady performance over time. Also, delegator requirements are much **lower** than validator's, thus decreasing the entry barrier and allowing the wider community to participate in the block creation process. Besides, becoming a validator or delegator to participate in block production on peaq, users are **encouraged** to run a full node, in order to keep the network **decentralized** and **censorship-resistant**. ## What You Need * On-premises or Virtual Machine in the Cloud matching the requirements ([Hardware Requirements](/peaqchain/build/getting-started/connecting-to-peaq#node-hardware-requirements)) * A full node (synced with parachain and relay chain block history) * **Substrate** (SS58 format) account to stake funds and get rewards * Minimum staking balance to be included in the validator set `50,000 $PEAQ` * Session key validator-1 1. **Ensure** that you have a machine matching hardware requirements and an SS58 Substrate wallet with 50,000+ \$PEAQ tokens. 2. **Set up** a node. 3. **Generate** session key. 4. **Link** session key to validator account. 5. **Stake** tokens and join the validator candidates pool. 6. **Check** whether you were included in the active set. # Join Candidate Pool Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-validator/join-candidate-pool ## Request to Join a Validator Pool Go to the [polkadot.js portal](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fpeaq.api.onfinality.io%2Fpublic#/explorer), open the **Developer** tab, and click **Extrinsics**. There, you'll need to submit the extrinsic: `Developer → Extrinsic → parachainStaking → joinCandidates(stake)` The min stake amount for the validator is 50,000 \$PEAQ. validator-5 Keep in mind that peaq has `18` decimals, so if you want to stake `50,000` tokens, you'll need to enter `50000000000000000000000`. 1. Enter your stake into the `stake: u128 (BalanceOf)` field. 2. Click `Submit Transaction`. ### Check if You Are in the Active Set of Validators Open the **Developer** tab, and click **Chain state**. There, you will be able to check whether your stake was big enough to get a set in the active set of validators (top `42` validators by total stake). `Developer → Chain state → Storage → parachainStaking → topCandidates()` validator-6 Confirm that your validator account is in the result ### Confirm that Your Validator Node is Authoring Blocks Wait for **two sessions** (2400 blocks or \~4hrs) to see whether your node starts authoring blocks. You can **verify** it by going to the peaq block explorer and checking that your address started getting validator rewards. # Leave Validator Pool Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-validator/leave-validator-pool ## Initiate Leave Request `Developer → Extrinsics → Submission → parachainStaking → initLeaveCandidates` validator-10 1. Click `Submit Transaction`. ## Check Whether You Can Exit You need to wait for 5 min after sending `initLeaveCandidates`. `Developer → Chain state → parachainStaking → candidatePool()` validator-11 1. Toggle the switch to `include option` and select your validator address in the `AccountId32` field. 2. Click the `+` button. 3. Confirm that your status is `Leaving`. ## Execute Leave Request `Developer → Extrinsics → Submission → parachainStaking → executeLeaveCandidates` validator-12 1. Select `Id` in the dropdown list in the field `collator: MultiAddress (LookupSource)`. 2. Use the address of your validator account in the `Id:AccountId` field. 3. Click `Submit Transaction`. ## Unlock Your Unstaked Tokens `Developer → Extrinsics → Submission → parachainStaking → unlockUnstaked(target)` * You will be able to unlock your tokens in 14 days after sending `executeLeaveCandidates`. * To check when you'll be able to unstake, use: `Developer → Chain state → parachainStaking → unstaking` validator-13 1. Select `Id` in the dropdown list in the field `target: MultiAddress (LookupSource)`. 2. Use the address of your validator account in the `Id:AccountId` field. 3. Click `Submit Transaction`. # Setup a Node Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/becoming-a-validator/setup-a-node ## Recommended Hardware Requirements * **OS**: Ubuntu 20.04 * **CPU**: 3.3 GHz AMD EPYC 7002 * **Storage**: 1TB SSD * **Memory**: 8GB RAM For example, the `c5ad.xlarge` machine from AWS suffices these requirements. The above specifications have been **tested** and **proven** by peaq's engineering team to produce reliable results over time. While these are recommended, you can experiment with other configurations to **optimize** for cost and performance. ## Set up a Node 1. Stop your docker container if running an older version (remember to keep the docker volume). 2. Copy the docker image from below. 3. Remove `-unsafe-rpc-external` and `-rpc-methods=unsafe` from your node (if you already generated session keys previously). 4. Run your updated image again. 5. Install Docker on your VM ([how to install Docker on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)). 6. Create and run the container from **the validator node** image using the following command. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} sudo docker run -d -v peaq-storage:/chain-data -p 9944:9944 peaq/parachain:peaq-v0.0.110 \ --collator \ --parachain-id 3338 \ --chain ./node/src/chain-specs/peaq-raw.json \ --base-path chain-data \ --port 30333 \ --rpc-port 9944 \ --rpc-methods=unsafe \ --unsafe-rpc-external \ --runtime-cache-size 64 \ --rpc-cors=all \ --execution=wasm \ --out-peers 50 \ --in-peers 50 \ -- \ --execution wasm \ --port 30343 \ --sync fast \ --rpc-port 9977 ``` 7. Wait until your validator node **syncs** with the blockchain (both Polkadot Relay Chain and peaq parachain block history). It may take a **few hours** and will depend on your connection speed. A fully synced node will look like this: validator-2 # Block Reward Distribution Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/node-operations/block-reward-distribution ## Validator & Delegator Rewards peaq's tokenomics are designed in a way to **incentivize** different network participants. **Validators** play an important role in the network as they not only maintain the state by running a full node, but also propose blocks - which are later validated by the Relay Chain validators. * Validators together with delegators receive `40%` of the total rewards, consisting of new blocks being minted and transaction fees paid by the network users. * The active validator set consists of `42` validators, who are proposing new blocks in a round-robin manner. Any new block is being proposed by a single validator. * The active validator set renews every round (`2400` blocks or approximately every 4 hours). The mechanism of **selecting validators** in the active set is based on the **total stake** of the validator itself, **plus** the stake of all of its delegations. If the active set is full and the new candidate has the exact same stake as the last member of the set (by total stake); the system favors the validator that has been in the pool the **longest.** This way we can ensure that only the validators with the highest total stake are periodically selected to be eligible block authors. Delegators stake tokens on the validator of their **choice**, trying to select honest and well-performing validators. One validator can be supported by `100` delegators. If there is a 101st delegator who would like to delegate to the same validator, then `100` delegators are **selected** based on the **highest stake**, and the delegator with the lowest stake is **removed** from the delegator list. ### New Token Economy (v2) The new token economy introduces significant changes, including the ability for collators to set their commission rates, which can range from `0%` to `100%`. ### Reward Calculation The new token economy introduces a reward system based on total network stake rather than individual validator stakes. The following factors determine block rewards: 1. Validator commission rate 2. Number of blocks produced by the validator 3. Total stake of all collators and their delegators 4. Individual stake amount The reward calculation for **collators** and their **delegators** is detailed below. ``` PotBalance = (Block Reward Per Block * Blocks in Session) + TX Fees TotalStake = SUM(# of authored blocks * (author's stake + SUM(author's delegator's stake))) Delegator's Reward = PotBalance * (# of authored blocks * (1 - author's commission) * (Delegator's Stake / TotalStake)) Collator's Reward = PotBalance * (# of authored blocks * (Author's Stake / TotalStake)) + (PotBalance * (# of authored blocks * Author's Commission) * (total delegator's staking number in this validator / Total staking number)) ``` In other words, the calculations are carried out as follows: ``` Total session reward = Block reward * Collator_Delegator percentage * session length Total staking number = SUM(validator's generated blocks * (validator's stake + SUM(validator's delegators' stakes))) Delegator's reward = Total session reward * validator's generated blocks * (1 - validator's commission) * (delegator's stake / Total staking number) Validator's reward = (Total session reward * validator's generated blocks * validator's stake / Total staking number) + (Total session reward * validator's generated blocks * validator's commission * total delegators' stake / Total staking number) ``` ### Reward Snapshot In our design, no matter the commission rate, the change of the staking number, and the delegator joining or leaving, all of them will affect the next session - not this session. For example: 1. The validator changes the commission rate from 5% to 10% in session `X`. The reward calculation will follow 5% in session X, and the 10% will be affected in session `X+1`. 2. The delegator stakes 100 tokens more on session `Y`. The increment of 100 tokens takes into account the session `Y+1` only. 3. The delegator joins session `Z`; his staking number is involved in the session `Z+1`. ### Reward Distribution To enhance scalability, rewards are now distributed in **batches per session** rather than per block. Rewards are accumulated and calculated based on the session, then distributed in the following session. For instance, blocks created by collators `A`, `B`, and `C` during session `X` will have their rewards distributed in session `X+1`. # IPFS Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/off-chain-storage/ipfs ## What is it? IPFS is a **decentralized** protocol and peer-to-peer network that stores and shares hypermedia using content-addressing. It ensures **global**, **permanent** access to files by assigning unique cryptographic hashes as addresses, employs a distributed network for resilience, tracks file versions, organizes data with a Merkle DAG, supports offline sharing, and utilizes caching for efficient content retrieval. **IPFS** is widely used for decentralized file storage, web hosting, DApps, and ensuring data integrity in various domains, including **blockchain** and **cryptocurrency.** ## Integrating IPFS with a Substrate-based Blockchain Integrating IPFS with a Substrate-based blockchain allows for **decentralized storage** solutions in blockchain applications. This guide outlines the process to **connect** to IPFS, **store** data, **generate** a unique storage key, and **interact** with the blockchain to store and retrieve data CIDs. ### Connect to IPFS We will be using **Helia** to connect to a node. Helia gives developers the ability to run IPFS-compatible functionality without requiring a full IPFS daemon. It is a newer, modular, implementation for building IPFS-like networks. A main benefit of Helia is that it can operate in browser environments, enabling IPFS-based applications to run entirely in the browser without requiring an HTTP API connection to a remote IPFS node. First we will need to install the libraries that Helia depends on. You can do so using npm with the following commands: * `npm install helia` - Used to import the Helia package which is used in creating a new instance of a Helia node. * `npm install @helia/unixfs` - Installs the unixfs package from Helia that is used to create a filesystem. Import the two packages previously downloaded, connect to the node, and create a filesystem for that particular node: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { createHelia } from 'helia'; import { unixfs } from '@helia/unixfs'; // create node const helia = await createHelia(); // create filesystem const fs = unixfs(helia); ``` Next we provide a code snippet to show how to add data to the newly created Helia node: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // convert string to Uint8Array const encoder = new TextEncoder(); const bytes = encoder.encode('Hello peaq data'); // adds bytes to node and receives a CID back (content identifier) const cid = await fs.addBytes(bytes); console.log('CID of the data added:', cid.toString()); ``` The code snippet above encodes the string data into bytes and adds it to the filesystem (fs) initialized in the previous example. Take special note of the **CID** that is returned back after adding your data. That value will be needed when reading back from the node. ### Retrieve data from Node The next lines of code provides an example on how to **read** data back from the node using the cid that was returned previously: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // decoder converts Uint8Array to strings const decoder = new TextDecoder() let stored_data = '' for await (const data of fs.cat(cid)) { stored_data += decoder.decode(data, { stream: true }) } console.log('Read file contents from data store:', stored_data); helia.stop(); ``` ### Initialize Blockchain API Connection Next we will need to **install** the libraries that the Substrate-based blockchain depends on. You can do so using npm with the following commands: * `npm install @polkadot/api` - Used to import the **websocket provider** and **api promise** to communicate to the Substrate-based blockchain. Imports Keyring object to sign the write transaction to peaq storage. * `npm install @peaq-network/types` - Used to see the options that peaq offers in the types package. In this example we will use the testnet url, agung, to connect to the Substrate blockchain using Polkadot.js API: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ApiPromise, WsProvider, Keyring } from '@polkadot/api'; import { defaultOptions } from '@peaq-network/types'; const provider = new WsProvider(WSS_URL); const api = await ApiPromise.create({ provider, ...defaultOptions }); ``` ### Add CID to peaqStorage Store the previously created **CID** with an item\_type name for the data to be added in peaq Storage: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const item_type = "user-data"; const keyring = new Keyring({ type: 'sr25519' }); // Add Alice to our keyring with a hard-derivation path. // Can add agung funded wallet here with your substrate wallet's mnemonic phrase (recommended). const UserPair = keyring.addFromUri('//Alice'); await api.tx.peaqStorage.addItem(item_type, cid).signAndSend(UserPair); ``` ### Create a Unique Storage Key and Retrieve Data Import the following libraries that the Substrate-based blockchain uses to decode and generate a key for storage. * `npm install @polkadot/util-crypto` - Used to import the **decodeAddress** and **blake2AsHex** packages to successfully generate a storage key used in retrieving data from peaq Storage. * `npm install @polkadot/util` - Used to import polkadot-provided **conversion** mechanisms to read data from peaq Storage. The following code utilizes the user's address and data type to generate a unique key for identifying stored data on the blockchain. Then it fetches the corresponding data from IPFS: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { decodeAddress, blake2AsHex } from '@polkadot/util-crypto'; import { u8aToU8a, u8aToHex, u8aConcat, hexToString } from "@polkadot/util"; // generate storage key const storageKeyByteArray = []; const decodedAddress = decodeAddress(UserPair.address, false, 42); storageKeyByteArray.push(decodedAddress); const hashItemType = u8aToU8a(cid); storageKeyByteArray.push(hashItemType); const key = u8aConcat(...storageKeyByteArray); const storageKey = blake2AsHex(key, 256); const val = await api.query.peaqStorage.itemStore(storageKey); // convert u8a to hex to obtain data from peaq storage var retrieved_cid = hexToString(u8aToHex(val)); const decoder = new TextDecoder() let stored_data = '' for await (const data of fs.cat(retrieved_cid)) { stored_data += decoder.decode(data, { stream: true }) } console.log('Read file contents from data store:', stored_data); ``` ### Putting it all together The code snippet provides an overview of how to use this functionality with everything put together: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { createHelia } from 'helia'; import { unixfs } from '@helia/unixfs'; import { ApiPromise, WsProvider, Keyring } from '@polkadot/api'; import { defaultOptions } from '@peaq-network/types'; import { decodeAddress, blake2AsHex } from '@polkadot/util-crypto'; import { u8aToU8a, u8aToHex, u8aConcat, hexToString} from "@polkadot/util"; const provider = new WsProvider(WSS_URL); const api = await ApiPromise.create({ provider, ...defaultOptions }); // create node const helia = await createHelia(); // create filesystem const fs = unixfs(helia); async function createAddHelia() { // convert string to Uint8Array const encoder = new TextEncoder(); const bytes = encoder.encode('Hello peaq data'); // adds bytes to node and receives a CID back (content identifier) const cid = await fs.addBytes(bytes); console.log('CID of the data added:', cid.toString()); helia.stop(); return cid; } async function peaqStorageAdd(item_type, cid){ const keyring = new Keyring({ type: 'sr25519' }); // Add Alice to our keyring with a hard-derivation path. // Can add agung funded wallet here with your substrate wallet's mnemonic phrase (recommended). const UserPair = keyring.addFromUri('//Alice'); await api.tx.peaqStorage.addItem(item_type, cid).signAndSend(UserPair); return UserPair; } async function peaqStorageRetrieve(UserPair, item_type){ // generate storage key const storageKeyByteArray = []; const decodedAddress = decodeAddress(UserPair.address, false, 42); storageKeyByteArray.push(decodedAddress); const hashItemType = u8aToU8a(item_type); storageKeyByteArray.push(hashItemType); const key = u8aConcat(...storageKeyByteArray); const storageKey = blake2AsHex(key, 256); const val = await api.query.peaqStorage.itemStore(storageKey); // convert u8a to hex to obtain data from peaq storage var retrieved_cid = hexToString(u8aToHex(val)); return retrieved_cid } async function readHelia(returned_cid){ // decoder converts Uint8Array to strings const decoder = new TextDecoder() let stored_data = '' for await (const data of fs.cat(returned_cid)) { stored_data += decoder.decode(data, { stream: true }) } console.log('Read file contents from data IPFS store:\n', stored_data); } async function main() { const cid = await createAddHelia(); const item_type = 'user-data-1'; const UserPair = await peaqStorageAdd(item_type, cid); await sleep(25000); // 25 second delay to guarantee it has been added const returned_cid = await peaqStorageRetrieve(UserPair, item_type); // may have to wait until block has been appended before reading await readHelia(returned_cid); // disconnect to terminate the process await api.disconnect(); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } main(); ``` ## Summary This guide provides a **foundational** approach for leveraging **IPFS** with Substrate-based blockchains, emphasizing decentralized storage solutions and data integrity within blockchain applications. For any additional questions please see the [Helia Documentation](https://github.com/ipfs-examples/helia-examples/tree/main/examples/helia-101). # MongoDB Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/off-chain-storage/mongoDB ## What is it? MongoDB is a popular open-source NoSQL (non-relational) **database management system**. It is designed to handle large volumes of unstructured or semi-structured data, making it well-suited for applications with rapidly evolving schemas or complex data models. MongoDB stores data in **flexible,** JSON-like BSON (Binary JSON) documents, and its architecture allows for **horizontal scaling**, enabling efficient handling of growing datasets. It supports **dynamic queries, indexing**, and provides features like **replication** and **sharding** for high availability and scalability. MongoDB is commonly used in modern web development and other scenarios where flexible and scalable data storage is essential. The purpose of integration is that peaq storage does not store big data. It should be handled by your off-chain storage. On-chain storage data should be small so it doesn't delay execution. ## On-Chain Storage **Smart Contracts:** On-chain storage primarily involves the use of smart contracts. Smart contracts are self-executing contracts with the terms of the agreement directly written into code. They reside on the blockchain and can store small amounts of data. **State Variables:** State variables are used to store persistent data. These variables hold information that needs to be preserved across transactions and blocks. **Immutable Ledger:** The blockchain itself serves as an immutable ledger that records the history of all transactions. Each block contains a reference to the previous block, creating a chain of blocks that cannot be altered without changing all subsequent blocks. ## Off-chain Storage: **Databases:** Off-chain storage involves the use of traditional databases or distributed databases to store large amounts of data that do not need to be recorded on the blockchain itself. **Decentralized Storage Networks:** Some blockchain projects leverage decentralized storage networks like IPFS (InterPlanetary File System) or Swarm. These networks store data across multiple nodes, providing a decentralized and distributed storage solution. **Oracles:** Off-chain data can also be brought on-chain through oracles. Oracles are entities or services that provide external information to smart contracts or programs running on-chain, enabling them to interact with off-chain data. ## Architectural Flow: **Data Generation:** Data is generated either on-chain through transactions or off-chain through external sources. **On-chain Storage:** Relevant data is stored on-chain using smart contracts and state variables. This data is typically small and critical for the execution of on-chain logic and external services business logic that require data to be immutable. **Off-chain Storage:** Large or less critical data is stored off-chain in databases or decentralized storage networks. This data may include files, images, or other information not necessary for the consensus mechanism. **Interaction:** Smart contracts may interact with off-chain data using oracles, fetching external information and incorporating it into their logic. Or external services may interact with on-chain data to execute essential business logic. **Immutable Record:** All on-chain data is recorded in blocks on the blockchain, creating an immutable and transparent record of transactions and changes to on-chain state. off-chain-storage-1 ## Integration tutorial For data verification and immutability checks, you can store the **hash** of the data being stored off-chain on our chain (in the DID Document/peaq storage). By doing this, you can fetch the hash from the chain and use it to **verify** the data being fetched from your off-chain storage. You can also **map** the keys of the on-chain key/value pair with the off-chain storage key. Below is an example code of using our peaqStorage with MongoDB off-chain storage: ### 1. Install the required packages: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install mongodb @polkadot/api ``` ### 2. Create a tsconfig.json file in your project directory with the following configuration: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "compilerOptions": { "target": "es2018", "module": "commonjs", "strict": true, "esModuleInterop": true } } ``` ### 3. Create a file named app.ts with the following code: ```jsx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { MongoClient, Db, Collection, ObjectId } from 'mongodb'; class MongoDBStorage { private client: MongoClient; private db!: Db; private collection!: Collection; private collectionName: string; private dbName: string; constructor() { const connectionString = 'mongodb://localhost:27017'; this.client = new MongoClient(connectionString); this.dbName = 'yourDataBaseName'; this.collectionName = 'yourCollectionName'; } async connect() { try { await this.client.connect(); console.log('Connected successfully to MongoDB server'); this.db = this.client.db(this.dbName); this.collection = this.db.collection(this.collectionName); } catch (error) { console.error('Error connecting to MongoDB:', error); throw error; } } async disconnect() { try { await this.client.close(); console.log('Disconnected successfully from MongoDB server'); } catch (error) { console.error('Error disconnecting from MongoDB:', error); throw error; } } async insertDocument(document: any) { try { const result = await this.collection.insertOne(document); console.log('Document inserted:', result); return result; } catch (error) { console.error('Error inserting document:', error); throw error; } } async findDocumentById(id: string) { const result = await this.collection.findOne({ _id: new ObjectId(id) }); console.log('Found document by ID:', result); return result; } async updateDocument(id: string, update: any) { const result = await this.collection.updateOne({ _id: new ObjectId(id) }, { $set: update }); console.log('Updated document:', result.modifiedCount); return result.modifiedCount > 0; } async deleteDocument(id: string) { const result = await this.collection.deleteOne({ _id: new ObjectId(id) }); console.log('Deleted document:', result.deletedCount); return result.deletedCount > 0; } } export {MongoDBStorage} ``` ### 4. MongoDB Setup You either have the option to deploy your MongoDB database **locally** (self-managed), to the **cloud** (using MongoDB Atlas), **serverless** (pay-as-you-go), or you can use the **community edition**. The next step in the process is to download MongoDB to **set** your connection string and configure your database. * Follow the steps to download [MongoDB](https://www.mongodb.com/try/download/community) * Run the cmd `atlas setup` to create and configure an atlas account * After executing the cmd your terminal will output your cluster name, cloud provider location, and display the admin username and password. * Using the code from step 3 above, change the connection string to include your username and password. The [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/connection-string) gives an explanation on how to construct this string. * Run the cmd `tsc` to compile your typescript code. Change the generated `app.js` file to have the new name `app.cjs`. This will be needed for the next step when we import the MongoDB class into the script that interacts with peaq. ### 4. Connect with peaq network: * Create a new file in the same repository and call it `peaq.js` * In package.json add `"type": "module"` * Copy and paste the below code to initialize peaq network connection ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ApiPromise, WsProvider } from '@polkadot/api'; import { MongoDBStorage } from './app.cjs'; import { u8aToHex, stringToU8a, u8aToU8a, u8aConcat } from "@polkadot/util"; import Keyring from "@polkadot/keyring"; import { decodeAddress, blake2AsHex } from '@polkadot/util-crypto'; // using agung url to connect to provider. Change to peaq when necessary const provider = new WsProvider(WSS_URL); const api = await ApiPromise.create({ provider }); // creates a storage key to read data from chain function createStorageKey(address, itemType) { const storageKeyByteArray = []; const decodedAddress = decodeAddress(address, false, 42); storageKeyByteArray.push(decodedAddress); const hashItemType = u8aToU8a(itemType); storageKeyByteArray.push(hashItemType); const key = u8aConcat(...storageKeyByteArray); const storageKey = blake2AsHex(key, 256); return storageKey } // Store Big data in MongoDB, hash data on peaq Network, read to confirm data is the same const main = async () => { // Data to store in MongoDB const data = "Hello World"; // Store data in MongoDB const mongoDBStorage = new MongoDBStorage(); await mongoDBStorage.connect(); const insertedDocument = await mongoDBStorage.insertDocument({text: data}); await mongoDBStorage.disconnect(); // Hash data to store on chain const mongoID = insertedDocument.insertedId.toString(); const storeHashData = u8aToHex(stringToU8a(data)); // Create wallet from seed of a user funded wallet const keyring = new Keyring({ type: 'sr25519' }); const owner = keyring.addFromUri(owner_seed); // Add to MongoDB id and hashed data to peaq storage await api.tx.peaqStorage.addItem(mongoID, storeHashData).signAndSend(owner); await sleep(30000); // wait 30 seconds to guarantee it has been added on chain. // Retrieve data from off-chain storage based on the id await mongoDBStorage.connect(); const foundDocument = await mongoDBStorage.findDocumentById(mongoID); await mongoDBStorage.disconnect(); const readId = foundDocument._id.toString(); // Create a storage key and read hashed on-chain data storage const storageKey = createStorageKey(owner.address, readId); var onChainHashData = await api.query.peaqStorage.itemStore(storageKey); // Confirm data storage hashed value and text in mongoDB are the same onChainHashData = u8aToHex(onChainHashData); const hashData = u8aToHex(stringToU8a(foundDocument.text)); console.log(onChainHashData == hashData); await api.disconnect(); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } main(); ``` The code above creates an instance of the MongoDBStorage() Class that was created and generated in step 3. It stores the 'big data' in MongoDB. The hash of that data is sent to peaq storage as a pair of `MongoDB_Id: Hashed_Data`. The data is then retrieved from the MongoDB based on the ID and the hashed data is read, then compared, to show the immutability. ## Summary In this documentation, MongoDB is presented as the **off-chain storage solution** to complement the peaq network's on-chain storage, which is limited to small, critical data maintained via smart contracts and state variables on an immutable ledger. # DID Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/precompiles/did The following provides **simplified**, step-by-step instructions for interacting with the EVM-compatible Decentralized Identifier (DID) **smart contract** on the peaq network. It covers how to execute the primary functions of the DID smart contract. This documentation applies to **both** peaq and agung networks. The precompile is integrated into our JavaScript SDK, serving as the underlying mechanism that triggers the execution of the **corresponding** extrinsic on the substrate side. This seamless integration ensures **interoperability** between the EVM environment and our substrate-based framework. ## Prerequisites * Basic understanding of blockchain, EVM, and DID concepts. * Using the Remix IDE with MetaMask configured to interact with a peaq network. * You have token funds available in the wallet connected to the a peaq network. ## Instructions ### 1. Setup Remix IDE * Open the [Remix website](https://remix.ethereum.org/). * Create a new blank workspace for the precompiles. ### 2. Create a New File * In your Remix workspace, create a new file named `DID.sol`. * Copy the content of the DID precompile file from the peaq network node precompile page and paste it into the `DID.sol` file. The precompile is found at the [peaq node source code](https://github.com/peaqnetwork/peaq-network-node/blob/dev/precompiles/peaq-did/did.sol). ### 3. Compile the Contract * Compile the `DID.sol` file by clicking on the **Solidity Compiler** tab and selecting the appropriate compiler version. * Ensure the compilation is successful and generates the ABI file needed for interacting with the smart contract. ### 4. Deploy and Interact with the Contract 1. Under the **Deploy and Run Transactions** tab, select **Injected Provider - MetaMask** as the environment. 2. Ensure your chain ID matches the selected network (peaq/agung). 3. Choose the account with sufficient token funds. 4. Set the contract address to `0x0000000000000000000000000000000000000800` (found at the top of the copied file). 5. Click **At Address** to load the deployed contract into the interface. On the bottom left your the Remix interface you will get the following functions you can interact with: did-precompile-1 ### 5. Interact with the Functions #### addAttribute To add a DID Document as a value please generate the value via the Serialized DID section in the [JavaScript SDK](/peaqchain/sdk-reference/javascript/serialized-did) Reference. Otherwise you can set your own custom data to set with. * **Parameters:** * `did_account`: Public hex key (e.g., `0x…`). * `name`: Hex representation of the DID name data. * `value`: Hex representation of the DID value data. * `validity_for`: Set to 0. * **Behavior:** Adds a new attribute to the DID. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm EVM logs in the Remix terminal. * Check for the attribute on the Substrate side via recent events in polkadot.js. #### removeAttribute * **Parameters:** * `did_account`: Public hex key (e.g., `0x…`). * `name`: Hex representation of the DID name data to be removed. * **Behavior:** Removes an existing attribute from the DID. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm EVM logs in the Remix terminal. * Ensure the attribute is removed on the Substrate side. #### updateAttribute * **Parameters:** * `did_account`: Public hex key (e.g., `0x…`). * `name`: Hex representation of the DID name data to be updated. * `value`: Hex representation of the new DID value data. * `validity_for`: Set to 0. * **Behavior:** Updates an existing attribute of the DID. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm EVM logs in the Remix terminal. * Ensure the attribute is updated on the Substrate side. #### readAttribute * **Parameters:** * `did_account`: Public hex key (e.g., `0x…`). * `name`: Hex representation of the DID name data to be read. * **Behavior:** Reads the value of an existing DID attribute. * **Result:** * Confirm the attribute information in the `readAttribute` field. * Data is displayed as a tuple (e.g., `tuple: xxxx`).
For more exhaustive definitions please look at the [DID Operations](/peaqchain/sdk-reference/javascript/did-operations) in the SDK. # ERC-20 Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/precompiles/erc-20 peaq inherits the [ERC-20 Native Token precompile](https://github.com/peaqnetwork/peaq-network-node/blob/dev/precompiles/balances-erc20/ERC20.sol) to interact with the peaq token as is it were a native ERC-20. This prevents the need to have wrapped representations of the same token and allows for easy interaction with the token without the need to deploy another smart contract. This guide will show you how to interact with peaq's testnet token, agung, through this precompile using Remix. The same can be done on peaq mainnet. ## Prerequisites * Basic understanding of blockchain, EVM, and ERC-20 concepts. * Using the Remix IDE with MetaMask configured to interact with a peaq network. * You have token funds available in the wallet connected to the a peaq network. ## Instructions ### 1. Setup Remix IDE * Open the [Remix website](https://remix.ethereum.org/). * Create a new blank workspace for the precompiles. ### 2. Create a New File * In your Remix workspace, create a new file named `IERC-20.sol`. * Copy the content of the ERC-20 precompile file from the peaq network node precompile page and paste it into the `IERC-20.sol` file. The precompile is found at the [peaq node source code.](https://github.com/peaqnetwork/peaq-network-node/blob/dev/precompiles/balances-erc20/ERC20.sol) ### 3. Compile the Contract * Compile the `IERC-20.sol` file by clicking on the **Solidity Compiler** tab and selecting the appropriate compiler version. * Ensure the compilation is successful and generates the ABI file needed for interacting with the smart contract. ### 4. Deploy and Interact with the Contract 1. Under the **Deploy and Run Transactions** tab, select **Injected Provider - MetaMask** as the environment. 2. Ensure your chain ID matches the selected network (peaq/agung). 3. Choose the account with sufficient token funds. 4. Set the contract address to `0x0000000000000000000000000000000000000809` (found at the top of the copied file). 5. Click **At Address** to load the deployed contract into the interface. ### 5. Interact with the Functions erc-20-precompile-1 The contract that you are able to interact with will show up on the bottom the the sidebar. Here lie all the functions that are present in the interface. For detailed information about the functions it provides please see the [EIP-20 Token Standard](https://eips.ethereum.org/EIPS/eip-20). # Introduction to Precompiles Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/precompiles/introduction ## What is a Precompile? Precompiles are specialized **EVM smart contracts** with **fixed** addresses and **predefined** functionality. Unlike regular contracts, their code is embedded into the blockchain runtime, enabling highly efficient execution of specific tasks. ### Context in Our Ecosystem Our blockchain is built on a **Substrate-based** framework that utilizes pallets—modular components responsible for executing various aspects of the blockchain's logic. While our core functionality runs within these pallets, we also **support** an EVM environment. Precompiles serve as a **bridge** between our native extrinsics (triggered by pallets) and the EVM, allowing smart contracts to access underlying blockchain functions seamlessly. ## How Precompiles Work Each precompile encapsulates a set of operations (such as managing decentralized identities, storage, role-based access control, token standards like ERC-20, and vesting mechanisms) as **EVM-compatible smart contracts**. They are deployed with preset values and built-in functions that **convert** EVM calls into substrate extrinsics. This allows developers to call these functionalities using familiar EVM interfaces, while the heavy lifting is done by the **underlying** substrate pallets. ### Benefits 1. **Efficiency:** Direct integration into the runtime means that precompiles execute faster and more efficiently than equivalent logic implemented entirely in EVM bytecode. 2. **Security & Stability:** Because their functionality is defined at the protocol level, precompiles are rigorously tested and maintained, ensuring consistent performance and security. 3. **Enhanced Interoperability:** They allow for seamless interaction between native substrate features and EVM-based smart contracts, broadening the scope of decentralized applications built on our blockchain. ## Overview of Precompile Modules The subsequent pages in this documentation provide detailed information on some available precompiles: * **DID Precompile:** Manages decentralized identities (called directly in sdk). * **Storage Precompile:** Handles specialized storage operations (called directly in sdk). * **RBAC Precompile:** Implements role-based access control features (called directly in sdk). * **ERC-20 Precompile:** Facilitates standard ERC-20 token operations for native peaq. * **Vesting Precompile:** Manages token vesting schedules and related functionalities. For an exhaustive list of all of our precompiles please view the [peaq network node](https://github.com/peaqnetwork/peaq-network-node/tree/dev/precompiles). ## Summary This introductory page sets the stage for understanding how precompiles integrate deeply with our Substrate-based blockchain. The following sections will dive into each precompile's specifics, offering insights, usage examples, and best practices for harnessing their capabilities in your applications. # RBAC Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/precompiles/rbac This guide provides a walkthrough for interacting with the peaq **RBAC** (role-based access control) precompile. Applicable to all peaq networks. The RBAC precompile is integrated into our JavaScript SDK, serving as the underlying mechanism that triggers the corresponding substrate extrinsic. ## Prerequisites * Basic understanding of blockchain, EVM, and RBAC concepts. * Using the Remix IDE with MetaMask configured to interact with a peaq network. * You have token funds available in the wallet connected to the a peaq network. ## Instructions ### 1. Setup Remix IDE * Open the [Remix website](https://remix.ethereum.org/). * Create a new blank workspace for the precompiles. ### 2. Create a New File * In your Remix workspace, create a new file named `rbac.sol`. * Copy the content of the rbac precompile file from the peaq network node precompile page and paste it into the `rbac.sol` file. The precompile is found at the [peaq node source code.](https://github.com/peaqnetwork/peaq-network-node/blob/dev/precompiles/peaq-rbac/rbac.sol) ### 3. Compile the Contract * Compile the `rbac.sol` file by clicking on the **Solidity Compiler** tab and selecting the appropriate compiler version. * Ensure the compilation is successful and generates the ABI file needed for interacting with the smart contract. ### 4. Deploy and Interact with the Contract 1. Under the **Deploy and Run Transactions** tab, select **Injected Provider - MetaMask** as the environment. 2. Ensure your chain ID matches the selected network (peaq/agung). 3. Choose the account with sufficient token funds. 4. Set the contract address to `0x0000000000000000000000000000000000000802`. 5. Click **At Address** to load the deployed contract into the interface. On the bottom left your the Remix interface you will get the following functions you can interact with: rbac-precompile-1 ### 5. Interact with the Functions The RBAC smart contract provides extensive role-based access management capabilities. Below are some key functions categorized by their purpose: #### Role Management 1. **addRole** * **Parameters:** * `role_id`: A unique identifier for the role. * `name`: Bytes representing the name of the role. * **Behavior:** Adds a new role to the system. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm the **RoleAdded** event in the Remix terminal. 2. **updateRole** * **Parameters:** * `role_id`: Identifier of the role to update. * `name`: Updated name in bytes. * **Behavior:** Updates an existing role. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm the **RoleUpdated** event in the Remix terminal. 3. **disableRole** * **Parameters:** * `role_id`: Identifier of the role to disable. * **Behavior:** Disables an existing role. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm the **RoleRemoved** event in the Remix terminal. #### Permission Management 1. **addPermission** * **Parameters:** * `permission_id`: A unique identifier for the permission. * `name`: Bytes representing the name of the permission. * **Behavior:** Adds a new permission. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm the **PermissionAdded** event in the Remix terminal. 2. **assignPermissionToRole** * **Parameters:** * `permission_id`: Identifier of the permission. * `role_id`: Identifier of the role. * **Behavior:** Assigns a permission to a role. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm the **PermissionAssigned** event in the Remix terminal. #### Group Management 1. **addGroup** * **Parameters:** * `group_id`: A unique identifier for the group. * `name`: Bytes representing the group name. * **Behavior:** Adds a new group to the system. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm the **GroupAdded** event in the Remix terminal. 2. **assignUserToGroup** * **Parameters:** * `user_id`: Identifier of the user. * `group_id`: Identifier of the group. * **Behavior:** Assigns a user to a group. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm the **UserAssignedToGroup** event in the Remix terminal. After getting an understanding of these key functions, feel free to interact with the RBAC controls that were not mentioned in this document.
For more exhaustive definitions please look at the [RBAC Operations](/peaqchain/sdk-reference/javascript/rbac-operations) in the SDK. # Storage Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/precompiles/storage This guide provides a walkthrough for interacting with the peaq storage precompile. It includes detailed instructions on setting up the Remix IDE and utilizing the key functions of the **storage smart contract**. This guide is applicable to all peaq networks. Additionally, the storage precompile is **integrated** into our JavaScript SDK, serving as the underlying mechanism that triggers the corresponding substrate extrinsic. ## Prerequisites * Basic understanding of blockchain, EVM, and storage concepts. * Using the Remix IDE with MetaMask configured to interact with a peaq network. * You have token funds available in the wallet connected to the a peaq network. ## Instructions ### 1. Setup Remix IDE * Open the [Remix website](https://remix.ethereum.org/). * Create a new blank workspace for the precompiles. ### 2. Create a New File * In your Remix workspace, create a new file named `Storage.sol`. * Copy the content of the storage precompile file from the peaq network node precompile page and paste it into the `Storage.sol` file. The precompile is found at the [peaq node source code.](https://github.com/peaqnetwork/peaq-network-node/blob/dev/precompiles/peaq-storage/storage.sol). ### 3. Compile the Contract * Compile the `Storage.sol` file by clicking on the **Solidity Compiler** tab and selecting the appropriate compiler version. * Ensure the compilation is successful and generates the ABI file needed for interacting with the smart contract. ### 4. Deploy and Interact with the Contract 1. Under the **Deploy and Run Transactions** tab, select **Injected Provider - MetaMask** as the environment. 2. Ensure your chain ID matches the selected network (peaq/agung). 3. Choose the account with sufficient token funds. 4. Set the contract address to `0x0000000000000000000000000000000000000801`. 5. Click **At Address** to load the deployed contract into the interface. On the bottom left your the Remix interface you will get the following functions you can interact with: storage-precompile-1 ### 5. Interact with the Functions #### addItem * **Parameters:** * `item_type`: Bytes representing the type of item to add. * `item`: Bytes representing the item data to be added. * **Behavior:** Adds a new item to the storage. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm the **ItemAdded** event appears in the Remix terminal. #### updateItem * **Parameters:** * `item_type`: Bytes representing the type of item to update. * `item`: Bytes representing the new item data. * **Behavior:** Updates an existing item in the storage. * **Result:** * Verify the transaction in MetaMask or Remix. * Confirm the **ItemUpdated** event appears in the Remix terminal. #### getItem * **Parameters:** * `account`: Address of the account whose item you want to retrieve. * `item_type`: Bytes representing the type of item to retrieve. * **Behavior:** Reads an item stored in the contract. * **Result:** * The retrieved data is returned as bytes. * Verify the data in the Remix terminal. #### deleteItem Coming in next runtime upgrade.
For more exhaustive definitions please look at the [Storage Operations](/peaqchain/sdk-reference/javascript/storage-operations) in the SDK. # Vesting Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/precompiles/vesting The vesting interface is designed to manage the vesting of tokens. **Vesting** is a mechanism used to **lock** tokens for a certain period of time, or until certain conditions are met. This allows for the gradual **release** of tokens to beneficiaries. This page will show how to use the precompile to reference the deployed contract so users can directly interact with it. Works with the peaq or agung networks. ## Prerequisites * Basic understanding of blockchain, EVM, and vesting concepts. * Using the Remix IDE with MetaMask configured to interact with a peaq network. * You have token funds available in the wallet connected to the a peaq network. ## Instructions ### 1. Setup Remix IDE * Open the [Remix website](https://remix.ethereum.org/). * Create a new blank workspace for the precompiles. ### 2. Create a New File * In your Remix workspace, create a new file named `vesting.sol`. * Copy the content of the vesting precompile file from the peaq network node precompile page and paste it into the `vesting.sol` file. The precompile is found at the [peaq node source code](https://github.com/peaqnetwork/peaq-network-node/blob/dev/precompiles/vesting/vesting.sol). ### 3. Compile the Contract * Compile the `vesting.sol` file by clicking on the **Solidity Compiler** tab and selecting the appropriate compiler version. * Ensure the compilation is successful and generates the ABI file needed for interacting with the smart contract. ### 4. Deploy and Interact with the Contract 1. Under the **Deploy and Run Transactions** tab, select **Injected Provider - MetaMask** as the environment. 2. Ensure your chain ID matches the selected network (peaq/agung). 3. Choose the account with sufficient token funds. 4. Set the contract address to `0x0000000000000000000000000000000000000808` (found at the top of the copied file). 5. Click **At Address** to load the deployed contract into the interface. ### 5. Interact with the Functions vesting-precompile-1 #### vest * Allows the caller to vest their own vested funds. * The caller can trigger the release of their locked tokens according to the vesting schedule. #### vestedTransfer() * This function creates a new vesting schedule for a transfer. * It locks a specified amount of tokens (locked) for a target address (target), specifying the number of tokens to be released per block (perBlock) starting from a specified block (startingBlock). #### vestOther() * Allows someone to vest the funds of another account. -0 It enables a user to trigger the release of tokens for a specified beneficiary (target). # Introduction to the peaq RWA SDK Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/rwa/introduction The **peaq Real World Asset (RWA) Framework** is a comprehensive blockchain infrastructure that enables the tokenization of physical assets (primarily machines) on the peaq network. It provides a compliant, regulated pathway for transforming real-world assets into tradeable digital securities. Framework Overview At its core, the framework combines three key blockchain standards: * [**ONCHAINID**](https://github.com/onchain-id/solidity) - A decentralized identity protocol that links wallet addresses to verified identities, enabling Know-Your-Customer (KYC) compliance on-chain. * [**T-REX (ERC-3643)**](https://github.com/ERC-3643/ERC-3643) - The Token for Regulated EXchanges standard, which provides a security token framework with built-in compliance controls. * [**ERC-721 NFTs**](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol) - Non-fungible tokens that represent unique real-world assets (MachineNFT) and contractual agreements (ContractNFT). These standards were combined into the peaq-rwa-evm repository, where custom business logic is integrated into smart contracts. The framework enables asset owners to: 1. Register physical machines as on-chain NFTs with embedded DID documents 2. Package those assets into vaults 3. Fractionalize ownership into security tokens that can be sold to investors 4. Distribute yield from machine operations to token holders automatically An SDK has been deployed to the peaq npm registry which can be used to interact with the framework in an easy manner. You can checkout the [SDK Reference](/peaqchain/sdk-reference/rwa) for more details. ## Contract Addresses ### peaq ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} proxyAdmin: '0xaF4d82F70B29d58e87C69f0404d171E2e60a5913', infoDesk: '0x246eC0443D3017e319330e2B70F554B997a2Ec50', peaqRwaNft: '0x10a6734bfB7123fb0a43AaE870f116A3Af91539A', peaqVaultFactory: '0xF3e61Fa74FfA62a7de2791361536e4b1bccbFE34', onchainidAuthority: '0xE51F50bA6713CA71Ee987C3C305BE77EC502fdBC', onchainidFactory: '0x2f91c6db691D4b412976aDaB518D83723DC3A069', verifierKyc: '0xF1b050b943065002489796BFDF54E37B19101b3F', verifierMnftIssuer: '0x9C056EdCC77447c576d2b5808871d5305C58Bb03', verifierMnftRegulator: '0x823db6D7621857a6279B26102577D55EEE59A406', trexAuthority: '0x207d63D341fA6834E93606e3e66B9fe8f86A4966', trexFactory: '0x51c17AC87f44F492271B7A243D0f21855C593003', trexGateway: '0x9D671d1e2e621300312213CF24c767753c15ba9c', initialContractNft: '0xdA340e0EB025014107cd87035A6DAadD4c80989C' ``` ### Agung ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} infoDesk: '0x9f2bF4e338cCC48D1b7021494377907ea4a593F2', peaqRwaNft: '0x968b4520261C9c602a25448b75f3d4483D0a083B', peaqVaultFactory: '0x7809591A43449Ab57452c56Cf7d95b04Ef9886b6', onchainidAuthority: '0x84Df77FEa8398bcBCB18CE1165D89aEa02f8b67F', onchainidFactory: '0x6c2Cf1CD533C5c36C2d9918Ca00EAB800AE32d0A', verifierKyc: '0xb1b53EAD9E8aBa8eb061cC50a563FFd392C260d9', verifierMnftIssuer: '0xfbbb480a67A5CB746F9468d00D9b0F4106D04a32', verifierMnftRegulator: '0xEE3c15a286575620852d252F0BE1e1De0308C29f', trexAuthority: '0xA2b6B1d25b8072E6560bFDB0D09b1217368CBa4a', trexFactory: '0x2207DB2a167E31fF2d8E82E953ec0Bda942a8B71', trexGateway: '0x6c24606B129cD4e426c54628E0A0896985aEbca3', initialContractNft: '0x35D67095A5a6f00CBE288cF744b3efC48de3699a' ``` ## Next Steps * **[Learn more about the modules](/peaqchain/build/advanced-operations/rwa/modules)** - Detailed documentation for each module the RWA Framework is composed of. * **[Roles & Responsibilities](./roles/)** - Detailed documentation for each participant role * **[SDK Reference](/peaqchain/sdk-reference/rwa)** - Complete API documentation with code examples # Core Modules Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/rwa/modules ### OnChainID Module (`sdk.onchainid`) The identity module manages ONCHAINID identities and claims-the foundation of all compliance in the framework. **Purpose:** Create and manage on-chain identities, issue KYC claims, and handle claim lifecycle operations. **Key capabilities:** * **Create identities** - Deploy an `Identity` contract linked to a user's wallet address * **Retrieve identities** - Look up existing identity contracts by wallet address * **Issue KYC claims** - Generate and sign claims that verify a user's identity * **Issue Role claims** - Generate and sign claims that verify a particular role * **Add claims to identities** - Attach signed claims to identity contracts * **Remove claims** - Revoke claims when they are no longer valid Every participant in the framework-whether an investor, machine issuer, or regulator-must have a verified identity with appropriate claims before they can interact with the system. → See [Identity SDK Reference](/peaqchain/sdk-reference/rwa/identity) for implementation details. ### Machine NFT Module (`sdk.mnft`) The Machine NFT module handles the registration and management of tokenized physical assets. Machine NFT Flow **Purpose:** Register real-world machines as NFTs with embedded DID documents, and manage their lifecycle. **Key capabilities:** * **Ensure allowances** - Verify and approve ERC-20 allowances for registration fees * **Register machines** - Mint MachineNFTs with associated DID documents that describe the physical asset * **Read DID documents** - Retrieve the machine metadata stored on-chain Each MachineNFT contains a Decentralized Identifier (DID) document that uniquely identifies the physical machine, including details like manufacturer, model, serial number, and other relevant metadata. This creates an immutable link between the on-chain token and the real-world asset. → See [Machine NFT SDK Reference](/peaqchain/sdk-reference/rwa/mnft) for implementation details. ### Contract NFT Module (`sdk.cnft`) The Contract NFT module manages digital agreements between multiple parties. **Purpose:** Create, sign, and manage multi-party contracts that are represented as NFTs on-chain. **Key capabilities:** * **Create contracts** - Initialize a new contract with specified counterparties and document hash * **Sign contracts** - Allow counterparties to sign and finalize agreements * **Cancel drafts** - Allow initiators to cancel contracts before all parties have signed * **Verify contracts** - Validate that document content matches the on-chain hash ContractNFTs store a hash of the actual document (which may be stored off-chain, e.g., on IPFS) and a URL to retrieve it. This ensures document integrity while keeping large files off the blockchain. Contracts can also be deposited into vaults alongside MachineNFTs. → See [Contract NFT SDK Reference](/peaqchain/sdk-reference/rwa/cnft) for implementation details. ### RWA NFT Factory Module (`sdk.rwanft`) The RWA NFT factory module manages the top-level factory contract that coordinates machine issuers and regulators. **Purpose:** Administrative operations for the PeaqRwaNft factory contract. **Key capabilities:** * **Manage machine regulators** - Add or remove addresses authorized to approve machine issuers * **Manage machine issuers** - Register new issuers who can mint MachineNFTs * **Emergency controls** - Block or unblock machine issuers and NFT operations This module is primarily used by framework administrators rather than end users. → See [RWA NFT SDK Reference](/peaqchain/sdk-reference/rwa/rwanft) for implementation details. ### Vault Module (`sdk.vault`) The Vault module enables the fractionalization of assets into security tokens and manages yield distribution. Vault Operations **Purpose:** Create vaults that hold MachineNFTs and ContractNFTs, fractionalize them into security tokens, and distribute yield to token holders. **Key capabilities:** * **Create vaults** - Deploy new vault instances with associated security tokens and reward distributors * **Pause/unpause tokens** - Control token transferability for regulatory or emergency purposes * **Register identities** - Add verified identities to the vault's identity registry (required for token holders) * **Deposit and mint** - Lock NFTs in a vault and mint security tokens representing fractional ownership * **Deposit yield** - Add revenue to the vault for distribution to token holders * **Claim yield** - Allow token holders to withdraw their share of accumulated yield * **Transfer tokens** - Move security tokens between verified holders Security tokens issued by vaults comply with the T-REX (ERC-3643) standard, meaning they have built-in compliance checks that ensure only verified (KYC'd) addresses can hold and transfer them. → See [Vault SDK Reference](/peaqchain/sdk-reference/rwa/vault) for implementation details. ## Roles in the Ecosystem The RWA Framework defines several key roles, each with specific responsibilities and required claims: | Role | Description | Required Claim | | --------------------- | ----------------------------------------------------------------------------------- | -------------------------- | | **Framework Owner** | Administers the entire framework, manages trusted claim issuers, and creates vaults | Admin access | | **Claim Issuer** | Issues KYC and role claims to users after verification | Trusted by Framework Owner | | **Machine Regulator** | Approves machine issuers and oversees their operations | `CT_MNFT_REGULATOR` | | **Machine Issuer** | Registers physical machines as MachineNFTs | `CT_MNFT_ISSUER` | | **User / Investor** | Owns MachineNFTs, holds security tokens, and receives yield | `CT_KYC_APPROVED` | # Roles & Responsibilities Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/rwa/roles The peaq RWA Framework defines a structured hierarchy of roles, each with specific responsibilities, requirements, and capabilities. This document provides a comprehensive overview of every role in the ecosystem. ## Quick Reference | Role | Required Claim | Appointed By | Primary Responsibility | | ----------------- | -------------------------- | ----------------- | ------------------------- | | Framework Owner | Admin access | Deployment | Ecosystem administration | | Claim Issuer | Trusted by Framework Owner | Framework Owner | Issue KYC and role claims | | Machine Regulator | `CT_MNFT_REGULATOR` | Framework Owner | Approve machine issuers | | Machine Issuer | `CT_MNFT_ISSUER` | Machine Regulator | Mint MachineNFTs | | Vault Owner | Created via factory | Framework Owner | Manage vault operations | | User / Investor | `CT_KYC_APPROVED` | Claim Issuer | Hold assets and tokens | *** ## Framework Owner The **Framework Owner** is the top-level administrator of the entire RWA ecosystem. This role is typically held by the organization that deployed the framework (e.g., EoTLabs for the peaq network). ### Responsibilities 1. **Manage Trusted Claim Issuers** - Determine which entities are authorized to issue claims (KYC, roles) that the framework will recognize 2. **Appoint Machine Regulators** - Add or remove addresses authorized to approve machine issuers 3. **Create Vaults** - Deploy new vault instances with associated security tokens and reward distributors 4. **Administer the InfoDesk** - Update contract addresses, implementation contracts, and configuration values 5. **Emergency Controls** - Block or unblock machine issuers and pause/unpause security tokens when necessary ### Trust and Claim Issuers Claim issuers are crucial for the KYC process. While anyone can technically deploy a `ClaimIssuer` contract, only claims from issuers trusted by the Framework Owner are recognized by the framework's verifiers. The Framework Owner determines which claim topics each issuer can issue: * `CT_KYC_APPROVED` - For user identity verification * `CT_MNFT_ISSUER` - For machine issuer authorization * `CT_MNFT_REGULATOR` - For machine regulator authorization. ### Vault Factory Management The Framework Owner controls the `PeaqVaultFactory`, which orchestrates the deployment of complete vault setups: * **Vault** - Holds MachineNFTs and ContractNFTs * **Security Token** - T-REX compliant token representing fractional ownership * **Reward Distributor** - Handles yield distribution to token holders When creating a vault, the Framework Owner specifies: * The vault taker (who will own the vault) * Token name and symbol * Payout asset for yield distribution * KYC requirements and compliance modules ### InfoDesk Administration The InfoDesk is the central configuration hub for the framework. The sdk does not provide access. The Framework Owner can update: * **Contract addresses** - Locations of core framework contracts * **Implementation addresses** - For upgradeable proxy contracts * **Fee configurations** - Registration fees, transfer fees, and fee accounts * **Precompile addresses** - For peaq-specific functionality *** ## Claim Issuers A **Claim Issuer** is an entity that issues verifiable claims about users. These claims enable the framework to enforce compliance requirements without centralized identity storage. ### What Are Claims? Claims contain structured information that verifiers use to grant access to framework functionality: * **Topic** - The type of claim (e.g., KYC approved, machine issuer role) * **Scheme** - The claim's data format * **Signature** - Cryptographic proof from the issuer * **Data** - The actual claim payload * **URI** - Optional reference to additional information ### Types of Claims The RWA Framework uses two primary claim types: | Claim Type | Topic | Purpose | | ----------- | ------------------------------------- | --------------------------------------- | | KYC Claims | `CT_KYC_APPROVED` | Verify a user's identity for compliance | | Role Claims | `CT_MNFT_ISSUER`, `CT_MNFT_REGULATOR` | Authorize specific framework roles | ### Becoming a Claim Issuer To become a trusted claim issuer: 1. **Deploy a ClaimIssuer contract** - This contract will hold the keys authorized to sign claims 2. **Request trust from the Framework Owner** - The Framework Owner must add your contract to the list of trusted issuers for specific claim topics 3. **Configure signing keys** - Add wallet addresses that can sign claims on behalf of your ClaimIssuer contract ### Issuing Claims The claim issuance process: 1. **Receive identity information** from the user (wallet address, `Identity` contract address) 2. **Verify the information** through your KYC process (off-chain) 3. **Generate the claim** with the appropriate topic and data 4. **Sign the claim** with an authorized signing key 5. **Return the signature** to the user so they can add it to their Identity contract For role claims, the process is similar but verifies organizational authorization rather than personal identity. *** ## Machine Regulators A **Machine Regulator** oversees and authorizes machine issuers within the framework. They act as a quality control layer, ensuring that only legitimate entities can mint MachineNFTs. ### Prerequisites Before becoming a Machine Regulator, you must: 1. **Have an Identity contract** - Deploy an ONCHAINID `Identity` contract linked to your wallet 2. **Be KYC approved** - Have a `CT_KYC_APPROVED` claim added to your Identity 3. **Have the regulator claim** - Obtain a `CT_MNFT_REGULATOR` claim from a trusted Claim Issuer ### Becoming a Machine Regulator The process involves two steps: **Step 1: Obtain the Role Claim** Provide the following to a trusted Claim Issuer: * Your `Identity` contract address * The name or description of your organization The Claim Issuer will generate and sign a claim with topic `CT_MNFT_REGULATOR`. Add this signed claim to your Identity contract. **Step 2: Get Appointed by the Framework Owner** The Framework Owner must add your wallet address to the list of authorized machine regulators in the `PeaqRwaNft` contract. This requires: * Your public wallet address Once added, you can begin authorizing machine issuers. ### Responsibilities Machine Regulators are responsible for: 1. **Vetting Machine Issuers** - Verify that potential issuers are legitimate organizations with proper authority to tokenize machines 2. **Adding Machine Issuers** - Call `addMachineIssuer` on the `PeaqRwaNft` contract, which deploys a new `MachineNft` contract for the issuer 3. **Monitoring Issuers** - Oversee the activities of authorized issuers within their jurisdiction 4. **Reporting Issues** - Alert the Framework Owner if an issuer needs to be blocked ### Adding a Machine Issuer When you authorize a new machine issuer: 1. Verify they have a valid Identity with `CT_MNFT_ISSUER` claim 2. Call `addMachineIssuer` with their wallet address 3. A new `MachineNft` contract is deployed and assigned to them 4. The issuer can now mint MachineNFTs through their contract *** ## Machine Issuers A **Machine Issuer** is authorized to mint MachineNFTs representing real-world physical assets. Each issuer manages their own `MachineNft` contract instance. ### Prerequisites Before becoming a Machine Issuer, you must: 1. **Have an Identity contract** - Deploy an ONCHAINID `Identity` contract linked to your wallet 2. **Be KYC approved** - Have a `CT_KYC_APPROVED` claim added to your Identity 3. **Have the issuer claim** - Obtain a `CT_MNFT_ISSUER` claim from a trusted Claim Issuer ### Becoming a Machine Issuer The process involves two steps: **Step 1: Obtain the Role Claim** Provide the following to a trusted Claim Issuer: * Your `Identity` contract address * The name or description of your organization The Claim Issuer will generate and sign a claim with topic `CT_MNFT_ISSUER`. Add this signed claim to your Identity contract. **Step 2: Get Authorized by a Machine Regulator** A Machine Regulator must add you to the list of authorized issuers: * Provide your public wallet address to the regulator * The regulator calls `addMachineIssuer`, which deploys your `MachineNft` contract * The contract address is emitted in the transaction event ### Your MachineNft Contract Once authorized, you own a dedicated `MachineNft` contract. Key points: * **You are the only issuer** - Only you can mint NFTs from this contract * **Multiple owners** - NFTs you mint can be owned by any KYC'd user * **Unique per issuer** - Each issuer has their own contract instance * **Retrievable** - The contract address can be looked up from `PeaqRwaNft` using your wallet address ### Registering Machines To register a new machine as a MachineNFT: 1. **Collect machine information** from the owner (or yourself) 2. **Create a DID document** describing the machine (type, manufacturer, serial number, etc.) 3. **Ensure fee approval** - The machine owner must approve the registration fee 4. **Call registerMachine** with: * Machine owner's wallet address * Machine value (in PEAQ tokens) * Machine DID document (serialized) 5. **Return the token ID** - The minted NFT is transferred to the owner's wallet *** ## Vault Owners A **Vault Owner** manages a `PeaqVault` that holds MachineNFTs and ContractNFTs, fractionalizing them into security tokens. ### How Vaults Are Created Vaults are created by the Framework Owner through the `PeaqVaultFactory`. When created: * A new `PeaqVault` contract is deployed * An associated T-REX security token is deployed * A reward distributor is deployed for yield management * Ownership is transferred to the specified vault taker ### Vault Owner Responsibilities 1. **Configure accepted NFT collections** - Determine which MachineNft and ContractNft contracts can deposit into the vault 2. **Accept deposits** - Receive NFTs from authorized depositors 3. **Mint security tokens** - Issue fractional ownership tokens based on deposits 4. **Manage yield distribution** - Configure and trigger yield distribution to token holders 5. **Handle compliance** - Ensure only verified identities can hold tokens ### Depositing and Minting The vault supports a single deposit-and-mint operation: * NFTs (MachineNFT and/or ContractNFT) are transferred into the vault * Security tokens are minted to the depositor * This operation can only happen once per vault for security reasons ### Yield Distribution Vaults integrate with a `RewardDistributor` contract: * Anyone can deposit yield (revenue) into the vault * Yield is automatically allocated based on token holdings * Token holders can claim their proportional share at any time * Claims can be made to the holder's wallet or a specified recipient ### Identity Registry Each vault has an associated identity registry. Before an address can hold security tokens: * The address must have a valid Identity contract * The Identity must have the required KYC claim * The address must be registered in the vault's identity registry *** ## Users / Investors A **User** or **Investor** is any participant who wants to: * Own MachineNFTs representing physical assets * Hold security tokens representing fractional vault ownership * Receive yield from vault operations * Trade assets within the framework ### Prerequisites Every user must have: 1. **An Identity contract** - Deployed via ONCHAINID's `IdFactory` 2. **KYC approval** - A `CT_KYC_APPROVED` claim from a trusted Claim Issuer Without these, you cannot: * Own or transfer MachineNFTs * Hold or transfer security tokens * Receive yield distributions * Interact with vaults ### Onboarding Process **Step 1: Deploy Your Identity Contract** Create an Identity contract linked to your wallet: * Provide your wallet address to the identity factory * Use a unique salt value (can only be used once in the framework) * The deployed Identity contract becomes your on-chain identity **Step 2: Get KYC Approved** Obtain a KYC claim from a trusted Claim Issuer: 1. Contact a trusted Claim Issuer in the ecosystem 2. Provide required information: * Your `Identity` contract address * First name, last name * Date of birth (YYYY-MM-DD format) * Place of birth 3. Complete the issuer's verification process 4. Receive the signed claim 5. Add the claim to your Identity contract ### What You Can Do Once KYC approved, you can: | Action | Description | | ---------------------------- | ------------------------------------------------------------- | | **Own MachineNFTs** | Receive machines registered by Machine Issuers | | **Transfer MachineNFTs** | Send machines to other KYC'd addresses (fee applies) | | **Hold Security Tokens** | Own fractional vault shares (**must be registered in vault**) | | **Transfer Security Tokens** | Trade tokens with other verified holders (fee applies) | | **Claim Yield** | Withdraw your share of vault revenues | | **Create ContractNFTs** | Initiate multi-party agreements | | **Sign Contracts** | Participate in ContractNFT agreements | Ready to build? Checkout the [SDK Reference](/peaqchain/sdk-reference/rwa) for more details. # Sending Bulk Transactions Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/sending-bulk-transactions The following will provide a process for **sending bulk transactions programmatically**. The focus is on Ethereum-based blockchains, but the principles can be adapted for other platforms with minor adjustments. Using tools like **ethers.js, smart contracts, or batching servers,** you can streamline the process of handling multiple transactions efficiently. ## Prerequisites * You have **Node.js** environment set up. * You possess **wallet credentials** (private key or mnemonic for signing transactions). * Access to a peaq RPC endpoints * Basic familiarity with blockchain SDKs (e.g., ethers.js or web3.js) ## Instructions ### 1. Install Required Dependencies Install ethers.js to handle blockchain interactions programmatically. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install ethers ``` ### 2. Define Bulk Transactions Create and structure your transactions in an array to represent the recipient addresses and amounts to send. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const { ethers } = require("ethers"); require("dotenv").config(); // For environment variables const RPC_URL = process.env.PEAQ_RPC_URL; // Replace with your preferred peaq RPC URL const ETH_PRIVATE = process.env.ETH_PRIVATE; // Setup provider and wallet const provider = new ethers.JsonRpcProvider(RPC_URL); const wallet = new ethers.Wallet(ETH_PRIVATE, provider); const createTransactions = async () => { // Define the bulk transactions const transactions = [ { to: "0xRecipientAddress1", value: ethers.parseEther("0.1"), }, { to: "0xRecipientAddress2", value: ethers.parseEther("0.2"), }, { to: "0xRecipientAddress3", value: ethers.parseEther("0.3"), }, ]; } ``` ### 3. Putting it all together You can use the following JavaScript code to send batch transactions ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const { ethers } = require("ethers"); require("dotenv").config(); // For environment variables const RPC_URL = process.env.PEAQ_RPC_URL; // Replace with your preferred peaq RPC URL const ETH_PRIVATE = process.env.ETH_PRIVATE; const createTransactions = async () => { // Define the bulk transactions const transactions = [ { to: "0xRecipientAddress1", value: ethers.parseEther("0.1"), }, { to: "0xRecipientAddress2", value: ethers.parseEther("0.2"), }, { to: "0xRecipientAddress3", value: ethers.parseEther("0.3"), }, ]; } const sendBulkTransactions = async (txs) => { // connect to provider const provider = new ethers.JsonRpcProvider(RPC_URL); const wallet = new ethers.Wallet(ETH_PRIVATE, provider); // send transactions for (let tx of txs) { try { const transaction = await wallet.sendTransaction(tx); console.log(`Transaction sent: ${transaction.hash}`); await transaction.wait(); // Wait for confirmation console.log(`Transaction confirmed: ${transaction.hash}`); } catch (error) { console.error(`Failed to send transaction: ${error.message}`); } } }; const main = async () => { try { const txs = createTransactions(); sendBulkTransactions(txs); } catch (error) { console.error("Error in event listener:", error); process.exit(1); } }; main(); ``` # Tips 1. **Test with a small batch** before sending large-scale transactions. 2. **Monitor gas prices** and set appropriate limits to avoid failed transactions. 3. **Secure your private keys**—never expose them in code or logs. 4. **Implement error handling** to retry failed transactions where applicable. # x402 on peaq Source: https://docs.peaq.xyz/peaqchain/build/advanced-operations/x402-peaq **x402 enables blockchain-based micropayments over HTTPS, using the HTTP 402 Payment Required status code to gate access to APIs, digital content and machine-to-machine interactions, rather than traditional authentication or subscription systems.** With x402, a client can make an API request and automatically be prompted to authorize payment using any supported **ERC-3009** token on peaq, such as **USDC** or **bridged stgUSDT**. Once the facilitator confirms the payment transaction on-chain (via delegated or self-hosted verification), access is granted instantly, no account creation or manual verification required. In this guide, you'll learn how to implement a full x402 payment flow on peaq, using **Express** and **TypeScript**. You'll build the following components: 1. **Facilitator** - verifies and broadcasts the payment on-chain 2. **Resource Server** - defines which tokens are accepted and which APIs require payment 3. **Client(s)** - initiates payments and accesses paid endpoints, either through an automated backend wallet representing a machine identity, or a web wallet like MetaMask These components map directly to the x402 standard roles of Verifier (Facilitator), Resource Provider (Resource Server), and Client (Consumer). This setup demonstrates both **machine-to-machine** and **human-to-API** interactions. On peaq, machines can sign and authorize x402 transactions using their on-chain identity keys, enabling autonomous payment flows between themselves. # Native Integration In the following sections, we'll walk through a native integration on peaq, demonstrating how the payment negotiation and verification flow can be implemented directly on-chain. ## Facilitator The facilitator is responsible for verifying payment authorization and broadcasting transactions to the peaq blockchain. It acts as the verification layer between the client's signed request and the resource's API logic. There are two modes of operation: * **Delegated Verification** The resource server delegates payment verification to an external facilitator running on peaq. The facilitator validates transaction proofs and returns a signed receipt via HTTPS. This is ideal for smaller resource servers that prefer not to run blockchain nodes. For example, PayAI provides a hosted facilitator supporting peaq at: `https://facilitator.payai.network`. Please checkout the [x402 Facilitator List](https://www.x402.org/ecosystem?category=facilitators) for more providers. * **Self-Hosted Verification** The resource server deploys a facilitator service connected directly to peaq RPC node. This enables local validation of payment proofs and full control over on-chain interactions. While more complex, this reduces reliance on external services and grants full control over signing, verification, and transaction lifecycle management. It's the preferred configuration for enterprise or high-throughput API systems. ## Resource Server The resource server exposes one or more API endpoints protected by x402. When a client requests a protected resource, the server responds with a `402 Payment Required` response. The resource server specifies payment requirements per endpoint, including accepted EIP-3009 tokens using contract address and pricing units. These settings determine when the HTTP 402 response is triggered. Once the payment is confirmed via the facilitator, the resource server grants access and returns the API response. This layer bridges the facilitator verification with client authorization. ## Client Clients can be human users interacting through web wallets like MetaMask or Rabby, or autonomous agents using backend wallets linked to peaq machine identities. These clients sign and broadcast payment transactions as specified in the `Payment-Request` header, then resubmit the original API call with a `Payment-Receipt` proving settlement. We'll demonstrate the full flow with two example clients: * **Backend Autonomous Agent** - a programmatic client (with its own private key/machine identity) signs the authorization automatically, representing a “machine paying a machine” flow. x402-peaq-1 * **Frontend User (MetaMask)** - a human user signs a payment authorization using their wallet to unlock an API feature. x402-peaq-2 ## Project setup The [x402-peaq repository](https://github.com/peaqnetwork/x402-peaq/tree/dev) contains the complete implementation examples for: * **Facilitator** * **Resource Server** * **Clients (frontend + backend)** Each component below includes a code snippet and short explanation to help you understand the core logic without leaving this page. You can clone the repository and follow the instructions in the **README.md** file to get started. ### Facilitator Code Snippet: ```Typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import express from "express"; import { config } from "dotenv"; import { verify, settle } from "x402/facilitator"; import { PaymentRequirementsSchema, PaymentPayloadSchema, createConnectedClient, createSigner, SupportedEVMNetworks, } from "x402/types"; config(); const app = express(); app.use(express.json()); app.post("/verify", async (req, res) => { try { const paymentRequirements = PaymentRequirementsSchema.parse(req.body.paymentRequirements); const paymentPayload = PaymentPayloadSchema.parse(req.body.paymentPayload); const client = createConnectedClient(paymentRequirements.network); const valid = await verify(client, paymentPayload, paymentRequirements); res.json(valid); } catch (err) { res.status(400).json({ error: "Invalid request" }); } }); app.post("/settle", async (req, res) => { try { const signer = await createSigner( req.body.paymentRequirements.network, process.env.FACILITATOR_PRIVATE_KEY! ); const tx = await settle(signer, req.body.paymentPayload, req.body.paymentRequirements); res.json(tx); } catch (err) { res.status(400).json({ error: "Settlement failed" }); } }); app.listen(4020, () => console.log("Facilitator running on http://localhost:4020")); ``` #### Explanation * `verify()` - checks if the client's signed authorization is valid for the given payment terms. * `settle()` - broadcasts the authorized payment on-chain once verified. * `PaymentRequirementsSchema` / `PaymentPayloadSchema` - ensure incoming data follows the x402 spec. * `createSigner()` - connects the facilitator's private key to the correct peaq RPC endpoint. ### Resource Server USDC Code Snippet: ```Typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { config } from "dotenv"; import express from "express"; import { paymentMiddleware, type Resource } from "x402-express"; config(); const facilitatorUrl = process.env.FACILITATOR_URL as Resource; // e.g., "https://facilitator.payai.network" const payTo = process.env.MACHINE_B_ADDRESS as `0x${string}`; // receiver on peaq const port = process.env.SERVER_PORT ?? "4021"; const app = express(); // Protect routes with x402 app.use( // USDC on peaq (human-readable pricing) paymentMiddleware( payTo, { "GET /data": { price: "$0.01", // 1 cent in USDC network: "peaq", }, }, { url: facilitatorUrl } ) ); // Protected API: returns “secret” machine data once paid app.get("/data", (_req, res) => { res.json({ report: { machine: "machine B", data: "secret data" } }); }); app.listen(port, () => { console.log(`Resource Server listening at http://localhost:${port}`); }); ``` USDT Code Snippet: ```Typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { config } from "dotenv"; import express from "express"; import { paymentMiddleware, type Resource } from "x402-express"; config(); const facilitatorUrl = process.env.FACILITATOR_URL as Resource; const payTo = process.env.MACHINE_B_ADDRESS as `0x${string}`; const port = process.env.SERVER_PORT ?? "4021"; const app = express(); // Protect routes with x402 — stgUSDT (atomic units) app.use( paymentMiddleware( payTo, { "GET /data": { price: { amount: "10000", // atomic units (10,000 = 0.01 for 6 decimals) asset: { decimals: 6, address: "0xf4d9235269a96aadafc9adae454a0618ebe37949", // stgUSDT on peaq eip712: { name: "Bridged stgUSDT", version: "1" }, }, }, network: "peaq", }, }, { url: facilitatorUrl } ) ); // Protected API app.get("/data", (_req, res) => { res.json({ report: { machine: "machine B", data: "secret data" } }); }); app.listen(port, () => { console.log(`Resource Server listening at http://localhost:${port}`); }); ``` #### Explanation * `paymentMiddleware(payTo, rules, { url })` - protects routes with x402. On first request returns 402 Payment Required with how-to-pay details; after payment + receipt, it lets the request through. * `payTo` - the receiver address on peaq (e.g., your machine or service wallet) that will receive funds. * `rules map` - keys like "METHOD /path" (e.g., "GET /data") define which endpoints require payment and at what price/network. * `Human-price format` - price: "\$0.01" (USDC on peaq); easy to read and configure for standard ERC-3009 tokens. * `Atomic-price format` - price: `{ amount, asset }` for custom tokens (e.g., stgUSDT). Include `asset.address`, `asset.decimals`, and optional `eip712 { name, version }`. * `network` - which chain to use (here: "peaq"). Must align with the client and facilitator. * `{ url: facilitatorUrl }` - points to your Facilitator (delegated or self-hosted) that verifies/settles the payment. * `app.get("/data", ...)` - your protected handler. It only runs after a valid payment receipt is presented on the retried request. * `Env vars` - FACILITATOR\_URL, MACHINE\_B\_ADDRESS, SERVER\_PORT; keep the client's displayed price in sync with the server rule. ### Clients Code Snippet: ```Typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { peaq } from "viem/chains"; // or the chain you use (peaq/custom) import { wrapFetchWithPayment } from "x402-fetch"; // private key for Machine A that buys data from Machine B using USDC set in the server file const MACHINE_A_PRIVATE = process.env.MACHINE_A_PRIVATE_KEY!; // Create a wallet client const account = privateKeyToAccount(MACHINE_A_PRIVATE as `0x${string}`); const client = createWalletClient({ account, transport: http(), chain: peaq, }); // Wrap the fetch function with payment handling const fetchWithPay = wrapFetchWithPayment(fetch, client, 200000); // can define macValue in 3rd parameter. We set ours as $0.20 USDC // Make a request that may require payment const response = await fetchWithPay(url, { method: "GET", }); const paymentResponseHeader = response.headers.get("X-PAYMENT-RESPONSE"); if (!paymentResponseHeader) throw new Error("No payment response header"); const settlement = JSON.parse(atob(paymentResponseHeader)); const data = await response.json(); ``` #### Explanation * `privateKeyToAccount(MACHINE_A_PRIVATE)` - loads the machine identity (Machine A) as an EOA used to sign x402 authorizations on peaq. * `createWalletClient({ account, transport: http(), chain: peaq })` - creates a viem wallet client bound to peaq for signing the EIP-3009 style payment authorization. * `wrapFetchWithPayment(fetch, client, 200000)` - decorates fetch so that when a 402 Payment Required is returned, it: * reads the x402 payment requirements from the response * signs an authorization with client * sends it to the facilitator for verification/settlement * retries the original request with a Payment-Receipt. The 3rd argument caps the max value you're willing to authorize (here, “\$0.20 USDC” represented in atomic units set by your integration). * `await fetchWithPay(url, { method: "GET" })` - performs the request; if payment is needed, the wrapper handles the pay-then-retry flow automatically. * `response.headers.get("X-PAYMENT-RESPONSE")` - base64-encoded JSON “settlement receipt” returned by your server after successful payment; decode to inspect tx details. * `await response.json()` - the actual protected resource payload (e.g., machine data) you wanted after settlement. ## Conclusion With these three components - **Facilitator**, **Resource Server**, and **Client** - you now have a complete, native x402 payment flow on peaq. This setup enables secure, account-less micropayments for APIs, data streams, and machine interactions. To go deeper, explore the full examples and advanced configuration options in the [x402-peaq repository](https://github.com/peaqnetwork/x402-peaq/tree/dev) and the for more facilitator urls checkout the [x402 Facilitator List](https://www.x402.org/ecosystem?category=facilitators). # Block Explorers Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/block-explorers This guide will provide an introduction to the **block explorers** in peaq's ecosystem, which are **peaqscan**, **Blockscout**, **Polkadot.js**, and **Subscan**. Blockchain explorers are essential tools for interacting with and analyzing blockchain networks. They allow users to to view transaction histories, account details, network statistics, pallets, and much more. ## What you'll learn * How to use **peaqscan** as the primary explorer for EVM-focused debugging, simulation, and analytics. * How to use **Blockscout** for EVM-focused exploration and smart contract verification. * How to interact with pallets using **Polkadot.js** and leverage it for real-time extrinsic interactions. * How to explore transaction data, blocks, and accounts using **Subscan.** ## Links The following table provides the relevant links to the **peaqscan**, **Blockscout**, **Polkadot.js**, and **Subscan** explorers for each network so you can follow along. | **Network** | **peaqscan** | **Blockscout** | **Subscan** | **Polkadot.js** | | -------------- | -------------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | peaq (mainnet) | [peaqscan Explorer](https://peaqscan.xyz/) | [Blockscout Explorer](https://scout.peaq.xyz/) | [Subscan Explorer](https://peaq.subscan.io/) | [Polkadot Native Explorer](https://polkadot.js.org/apps/?rpc=wss://peaq.api.onfinality.io/public-ws#/explorer) | | peaq (testnet) | [peaqscan Explorer](https://testnet.peaqscan.xyz/) | **N/A** | [Subscan Explorer](https://agung-testnet.subscan.io/) | [Polkadot Native Explorer](https://polkadot.js.org/apps/?rpc=wss://wss-async.agung.peaq.network#/explorer) | The following will reference the **peaq testnet** and block explorers to prevent unwanted data to be added to the live **peaq mainnet**. Information referenced in this document is the same between the networks. ## Instructions ## peaqscan **peaqscan** is the primary EVM explorer for peaq, offering advanced debugging, simulation, and analytics tailored for Ethereum-compatible chains. It provides real-time visibility into transactions, deep execution traces, decoded input/output, and powerful tooling to test and monitor activity on peaq. ### Key Features of peaqscan #### 1. EVM Transaction Insights & Debugging * Decoded function inputs and event logs when ABI is available * Detailed execution traces with call tree, revert reasons, and stack traces * Gas profiler and opcode-level analysis to optimize contracts #### 2. Transaction Simulation & Forks * Simulate transactions against current or historical state before broadcasting * Create ephemeral forks to test contract changes and scenarios safely * What-if analysis for governance, upgrades, or parameter changes #### 3. Contract Intelligence * ABI-aware decoding and human-readable method names * Source and verified metadata display when available * Read/Write interfaces for verified contracts #### 4. Monitoring & Alerting * Real-time monitoring on addresses, contracts, and events * Alerts and webhooks for on-chain signals (errors, thresholds, activity) ### Using peaqscan for peaq #### Accessing peaqscan * Mainnet: [peaqscan.xyz](https://peaqscan.xyz/) * Testnet: [testnet.peaqscan.xyz](https://testnet.peaqscan.xyz/) tenderly-1 #### Navigation & Search 1. Use the global search to find EVM addresses, transactions, or blocks 2. Pin frequently monitored addresses and create custom views 3. Open any transaction to inspect decoded inputs, logs, traces, and gas usage tenderly-2 #### Contract Exploration 1. Contract Overview * Address, creator, deployment tx and block * Verification status and ABI availability * Read and Write tabs for interacting with verified contracts 2. Transactions * Decoded method names and parameters * Success/failure with revert reasons * Internal calls and value transfers 3. Analytics * Gas usage, call frequency, and top interacting accounts ### When to Use peaqscan vs Other Explorers * Use peaqscan when: * You need to debug failing transactions with full traces and revert reasons * You want to simulate transactions or test scenarios on forks before broadcasting * You require ABI-aware decoding, gas profiling, or alerting/monitoring * You want a consolidated EVM developer experience for peaq * Use Blockscout when: * You want a lightweight EVM explorer view and contract verification via its flow * You need a familiar interface for quick lookups * Use Polkadot.js when: * Interacting with Substrate pallets (peaqDID, peaqRBAC, peaqStorage) * Submitting custom extrinsics or testing runtime-specific calls * Use Subscan when: * You need a high-level network overview and statistics * You want historical account/contract activity with flexible filtering ## Blockscout **Blockscout** is an EVM-focused blockchain explorer that provides specialized tools for exploring smart contracts, EVM transactions, and DeFi activity on peaq. Unlike Polkadot.js (which focuses on Substrate pallets) and Subscan (which provides general blockchain data), Blockscout is optimized for Ethereum-compatible features. ### Key Features of Blockscout #### 1. **EVM Transaction Analysis** * **Detailed EVM Logs**: View comprehensive logs, events, and state changes from smart contract interactions * **Internal Transactions**: Track internal calls within smart contract executions * **Gas Usage Tracking**: Monitor gas consumption and optimization opportunities * **Method Decoding**: Automatic decoding of contract function calls when contracts are verified #### 2. **Smart Contract Verification & Interaction** * **Sourcify Integration**: Built-in support for decentralized contract verification * **Contract Source Code**: View verified contract source code directly in the explorer * **Read/Write Interface**: Interactive interface to query and execute contract functions * **Constructor Arguments**: View deployment parameters and contract initialization data ### Using Blockscout for peaq #### Accessing Blockscout Visit [scout.peaq.xyz](https://scout.peaq.xyz/) for mainnet: block-explorers-8 #### Navigation & Search 1. **Search Functionality**: * Enter contract addresses, * Search for token symbols (e.g., "PEAQ", "SLC", etc.) 2. **Main Dashboard**: * **Network Statistics**: Block height, average block time, total transactions * **Recent Blocks**: Latest blocks with validator information and transaction count * **Recent Transactions**: Live feed of EVM transactions with gas usage and status #### Contract Exploration **Example Contract Analysis**: When viewing a smart contract on Blockscout, you'll see: 1. **Contract Overview**: * Contract address and creation transaction * Creator address and deployment block * Current balance and transaction count * Verification status (verified ✅ or unverified) 2. **Transactions Tab**: * All transactions involving the contract * Method names (decoded for verified contracts) * Gas usage and transaction fees * Success/failure status with error messages 3. **Contract Tab** (for verified contracts): * **Source Code**: Full Solidity source with syntax highlighting * **Read Contract**: Query view/pure functions without gas costs * **Write Contract**: Execute state-changing functions (requires wallet connection) * **Events**: Historical event logs with decoded parameters 4. **Tokens Tab** (if applicable): * Token transfers involving the contract * Token holder distributions * Supply metrics and token economics #### Account Analysis **When viewing an EOA (Externally Owned Account)**: 1. **Balance Information**: * PEAQ balance and USD value (if available) * Token holdings (ERC-20, ERC-721, ERC-1155) * Portfolio overview and historical balance charts 2. **Transaction History**: * Chronological list of all transactions * Contract interactions with decoded function calls * Token transfers and NFT activities * Internal transactions from contract calls 3. **Analytics**: * Transaction patterns and frequency * Gas usage statistics * Most interacted contracts ### When to Use Blockscout * Developing or auditing smart contracts with a familiar EVM explorer interface * Verifying contract source code through the Blockscout/Sourcify flow * Tracking token transfers and NFT activities ## Polkadot.js Polkadot.js is a Substrate-focused blockchain explorer and interaction tool that provides direct access to peaq's native blockchain functions and pallets. Unlike Blockscout (which focuses on EVM features) and Subscan (which provides pre-indexed data visualization), Polkadot.js is optimized for real-time blockchain development and Substrate-specific operations. ### Polkadot.js for Block Information Please open up the **Polkadot.js** link referenced above. It will take you to the block explorer page for the network you selected. An image like the one displayed below will appear. block-explorers-1 #### Main Metrics * **Last Block:** Indicates the time taken to produce the most recent block. * **Target Block Time:** Displays the target block production time (`6 seconds`), showing whether the network is operating within expected parameters. * **Total Issuance:** Represents the total supply of the network's native currency (`427.7602 MAGUNG`), reflecting overall supply dynamics. * **Inactive Issuance:** Displays uncirculated or non-staked currency (`2.0105 MAGUNG`), potentially relevant for staking or inflation calculations. #### Recent Blocks * **Block List:** Shows a list of the most recently produced blocks. * **Block Numbers:** Clicking on a block number (e.g., `3,137,077`) navigates to details about that specific block. * **Block Hash:** Displays the unique hash for each block. * **Validator Address:** Identifies the account or node (e.g., `5FX8TwkFy...`) responsible for producing that block. #### Recent Events * Displays the latest events logged on the blockchain, such as: * **Extrinsics:** Events that are sent from the sdk or extrinsic explorer will show up here (`peaqStorage.ItemAdded`, `peaqDid.AttributeAdded`). * **Staking Rewards:** Events like `parachainStaking.Rewarded` indicate rewards issued to validators or delegators. * **Balance Transfers:** `balances.Transfer` shows transactions between accounts. * **Session/Block Rewards:** Events like `session.NewSession` or `blockReward.BlockRewardsDistributed` track staking sessions or block rewards. * **New Rounds:** `parachainStaking.NewRound` reflects the start of new staking rounds, useful for validators or nominators. ### Polkadot.js for Accounts Select the **Accounts** section from the blockchain explorer interface, specifically the **"My Accounts"** tab. It provides an overview of accounts, balances, and associated actions for managing wallet funds and interacting with the network. block-explorers-2 #### Account Categories 1. **Extension Accounts:** * Accounts accessible through browser extensions (e.g., Polkadot.js, Talisman, etc.). * **Example:** `TEST ACCOUNT (EXTENSION)` has `25.3887 AGUNG`. * **Transferable:** Amount of tokens that can be sent to another wallet. * **Reserved:** Amount of tokens locked after performing DID/RBAC/STORAGE operations to prevent spam. Refunded to the user after they use a remove() method to delete what they stored on-chain. * **Transactions:** Number of transactions the account has performed. * Actions available: * **Send:** Transfer funds from this account to another. 2. **Development Accounts:** * Accounts derived from seed phrases used for development purposes. * **Example Accounts:** * "ALICE" has `11.8386 AGUNG` available. * Actions available: * **Send:** Transfer funds from these accounts. ### Polkadot.js for Extrinsics Select the **Developer** section from the blockchain explorer interface, specifically the **"Extrinsics"** tab. This interface is helpful to those who want to understand what pallets peaq has and the capabilities they offer. It allows developers or advanced users to **manually** submit extrinsics (transactions or actions) to the blockchain. block-explorers-3 #### Key Components 1. **Using the Selected Account** * Displays the currently selected account from which the extrinsic will be submitted. * In this case, the account is `TEST ACCOUNT (EXTENSION)`, which has a **free balance** of `4.0377 AGUNG`. * Users can switch accounts if they have multiple accounts configured. 2. **Submit the Following Extrinsic** * **Pallet (Module):** `peaqStorage` * This refers to a custom pallet/module on the peaq network. The pallet handles specific logic or functionality, related to **storage** in this case. * **Function (Extrinsic):** `addItem(itemType, item)` * This function allows users to add an item to storage by specifying: * `itemType` (Bytes): Indicates the type or category of the item being stored (e.g., a label or identifier). * `item` (Bytes): The actual data or item being stored (e.g., a name, value, or identifier string). 3. **Input Fields** * **itemType (Bytes):** The user has entered `item001` as the type of the stored item. * **item (Bytes):** The user has entered `my_item_001` as the data or value to be stored. * These values will be encoded as hex bytes for submission to the blockchain. * **Encoded Call Data** * Displays the encoded transaction data to be sent to the blockchain. * Example: `0x6800106974656d2c6d795f6974656d5f313233` * This is the byte-encoded representation of the `addItem` extrinsic with its arguments. * **Encoded Call Hash** * A hash of the encoded call data: `0x30a89710e6440564c741b061c84cc328948b7d21869b70e24315191bd69771c`. * This hash is unique to this specific extrinsic and can be used for reference or validation purposes. * **Encoding Details** * **callIndex:** `6800` - Identifies the pallet and function within the runtime. * **itemType (Hex):** `6974656d303031` - Hex-encoded version of the input `itemType` ("item001"). * **item (Hex):** `6d795f6974656d5f303031` - Hex-encoded version of the input `item` ("my\_item\_001"). * **Link:** Provides a URL for decoding the extrinsic for validation. * **Submission Options** * **Submit Unsigned:** Submit the transaction without signing it, typically used for testing purposes. * **Submit Transaction:** Sends the transaction as a signed extrinsic from the selected account. #### Send Extrinsic After reading through and getting a deeper understanding of the pallet that you are using, you are free to use it to send a transaction. 1. In another tab, select the **Network** tab and click on the **Explorer** section. This will bring you to the block explorer page so you can see the extrinsic you send be appended on chain. 2. In your main tab open up the **Extrinsics** page. Create a transaction similar to the one above to store data at `peaqStorage` . Once you have connected your account and filled the pallet with relevant data, please click on the **Submit Transaction** button on the bottom right. 1. **Sign** the transaction with your password. 2. A **green checkmark** will appear in the upper right corner when it has been submitted to the chain. 3. Got back to the **Explorer** page. After a couple seconds a the extrinsic event you submitted will be appended. You can click on the block number on the right hand side to view more information about the transaction. block-explorers-4 #### Read Extrinsic Once a peaq storage transaction has been sent, you can also use the **Extrinsic Developer** section to read the data you added. 1. Open the **Polkadot.js Explorer** page 2. Select **peaqStorage getItem()** on the extrinsic page. 3. Submit the same itemType as what you just stored (e.g. `item`). 4. Sign transaction 5. Go to explorer to see the **ItemRead** return item. block-explorers-5 As you can see the **Polkadot.js Explorer** is a powerful tool for interacting directly with the blockchain, offering advanced features like submitting, reading, and analyzing extrinsics. However, for users who need a more streamlined and user-friendly experience to simply monitor on-chain activity, analyze transaction histories, or explore account details without diving into the complexities of extrinsics, the **Subscan Explorer** becomes an invaluable resource. ## Subscan Now we will explore **Subscan**. This explorer focuses on pre-indexed, easily accessible data. This makes it particularly useful for non-technical users or those who want a high-level view of blockchain activity without dealing with the complexities of runtime calls and extrinsics. Subscan also provides enhanced features like **account and contract lookups**, **detailed transaction filtering**, and **historical data exploration**, which Polkadot.js does not offer in as streamlined a manner. ### Using Subscan 1. **Visit Subscan's peaq Explorer.** * Using the table above, go to the Subscan explorer you would like to use (agung in these examples). * The dashboard provides a high-level overview of the network, including key metrics like finalized blocks, signed extrinsics, and active accounts. 2. **Search for Accounts or Contracts** * In the search bar enter an **account address** or **contract address** to quickly locate its details. **Example of an account search:** The account above was search by inputting `0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C` in the search bar. **Subscan** returned the account that is affiliated with this address. block-explorers-6 #### Account Details * **Account**: Includes the *address* of the account and the *nonce* which represents the number of transactions sent from this account. * **Balance:** Displays the amount of the native token an account has. Shows how much is transferable and how much is reserved (during pallet usage). * **EVM Transactions**: * Displays a list of transactions submitted by this account to the blockchain. * Each EVM Transaction contains the following: * **Tx Hash:** `0x0664....23c3c6` - The unique transaction hash used to identify this tx. * **From:** `0x9Eea....46641C` - Account used to send the tx. * **To:** `0x0000....000801` - Where the transactions was sent to (RBAC precompile in this case). * **Method:** `0x257c3c03` - Function method executed on the Smart Contract (human-readable when contract is verified). * **Time:** `2 days 19 hrs ago` - Indicates when the tx was added to the blockchain. * **Txn Fee:** `0.0000000000477PEAQ` - Cost to send the tx. * **Value:** `0.0000000000477PEAQ` - Acount of native token that was transferred during the operation. * **Result:** ✅ - Indicates that the extrinsic was successfully executed. * **ERC-20 Transfers**: Shows the fund transfers initiated by this account. * **EVM-NFT Transfers History:** Shows the NFT transfers initiated by this account.
**Example of an contract search:** The contract was found by submitted the deployed contract address of `0x17bD3d6639b28Ee774040D3aE2137F49390a584c` in the **Subscan** search bar. It took me to the page: block-explorers-7 #### Contract Details * **Account**: * **Unknown:** Indicates the account hasn't been assigned a custom or recognizable name by the user. * **Account Address:** Unique identifier of the EVM contract. * **Type:** EVM-based account, meaning it's a smart contract account created and executed within the EVM environment. * **Nonce**: Indicates that this contract account has performed `30` transactions so far. * **Contract Creator:** The EVM address of the account that deployed this contract. * **Created At:** Refers to the transaction hash where this contract was initially deployed. * **Balance:** * **Total Balance:** `9.57 AGUNG` – The contract currently has native AGUNG tokens. * **Transaction Details:** * **EVM Transactions (685):** Tracks interactions with this EVM contract. * **Txn Hash:** `0xcde5....de3647` - The unique identifier for the transaction involving this contract. Clicking this link shows more details about the transaction. * **From:** `0x9Eea...46641C` - The address of the sender who interacted with the contract (contract creator in this case). * **To:** `0x17bD....0a584c` - The address of this contract, which received the transaction. * **Method:** `executeTransaction()` - The method or function called within the contract. * **Time:** `6 days ago` = Indicates when this transaction was processed. * **Txn Fee:** `0.0000000000984PEAQ` - The transaction fee paid for executing this transaction on the network. * **Value:** `0 PEAQ` - No value (native tokens) was transferred with this transaction. * **Result:** ✅ - Indicates that the transaction was successfully executed. * **ERC-20 Transfers (22):** Indicates no transfers of ERC-20 tokens (standard fungible tokens) related to this contract. * **ERC-721 Transfers (0):** Indicates no transfers of ERC-721 tokens (standard NFTs) related to this contract. * **Contract:** Includes details on how to verify a contract. ## Summary The **peaqscan**, **Blockscout**, **Polkadot.js**, and **Subscan** explorers are essential tools for interacting with and analyzing the peaq blockchain ecosystem. Each tool serves different purposes and caters to different audiences: * **peaqscan**: * Primary choice for **EVM debugging, simulation, and developer analytics** on peaq. With execution traces, revert reasons, gas profiling, forks, and monitoring, it streamlines building and troubleshooting on peaq mainnet and testnet. * **Blockscout Explorer**: * Great for **EVM lookups** and **contract verification**, with readable logs and token/NFT activity. * **Polkadot.js Explorer**: * Ideal for **Substrate pallet interactions** and **real-time extrinsic development/testing**. * **Subscan Explorer**: * Best for **high-level overviews** and **historical analysis** with searchable, pre-indexed data. # Deploying ERC-20 Token Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/deploying-erc-20-token In this guide, you will learn how to deploy an **ERC-20** token smart contract on AGUNG using **Remix**, a browser-based Solidity development environment. This guide uses a boilerplate ERC-20 contract, and you will customize key parameters such as: * `Token Name` * `Symbol` * `Decimals` * `Total Supply` By the end, you'll have your very own **ERC-20** token deployed on our AGUNG testnet. ## Prerequisites * **Basic understanding** of blockchain and ERC-20 tokens. * **Installed MetaMask** browser extension with AGUNG network set-up already. * **Familiarity** with Remix IDE ([https://remix.ethereum.org/](https://remix.ethereum.org/)). * Using the **OpenZeppelin library** for the ERC-20 boilerplate. * A **wallet** with AGUNG network PEAQ for gas fees. ## Instructions ### 1. Open Remix IDE * Visit Remix and create a new workspace. * Click on **"File Explorers"** and select **"Create a New File"**. * Name the file: `MyERC20Token.sol`. erc-20-1 ### 2. ERC-20 Code Copy and paste the following boilerplate ERC-20 smart contract code into your newly created file: ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; // Import OpenZeppelin's ERC-20 implementation import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20 { // Here we are declaring the contract & inheriting ERC20 constructor(uint256 initialSupply) ERC20("MyToken", "MTK") { _mint(msg.sender, initialSupply * (10 ** uint256(decimals()))); } } ``` ### 3. Modify Token Details Update the following placeholders in the code: * **Token Name**: Replace `"MyToken"` with your token's name (e.g., `"ExampleToken"`). * **Symbol**: Replace `"MTK"` with a short symbol (e.g., `"EXT"`). * **Total Supply**: The `initialSupply` (input during deployment) defines the number of tokens created. **Example**: For a 1,000,000 total supply with 18 decimals, input **1,000,000** during contract deployment (Step 5). erc-20-2 ### 4. Compile the Contract In the **Solidity Compile** tab: * Select the Solidity version (e.g., `0.8.20+commit.a1b79de6`). * Click **Compile MyERC20Token.sol**. * Ensure there are no errors. erc-20-3 ### 5. Deploy the Contract * Go to the **Deploy & Run Transactions** tab in Remix. * Select **Injected Web3** as the environment to connect MetaMask. * Choose your **testnet wallet account** in MetaMask. * In the constructor field, enter your desired **Total Supply** (e.g., `1000000` for 1 Million). * Click **Transact** under **Deploy** and confirm the transaction in MetaMask. erc-20-4 ### 6. Verify Deployment erc-20-5 Remix IDE's console logging a successful execution of the transaction to deploy the smart contract ExampleToken (MyERC20Token.sol) to AGUNG network. * After deployment, your contract will appear under **Deployed Contracts** in Remix. * Use the **Read Functions (blue)** to verify details like `name()`, `symbol()`, and `totalSupply()`. * You can also **check your token balance** using the `balanceOf()` function by inputting your wallet address. erc-20-6 ## Summary 🎉 Congratulations! You have successfully deployed your first **ERC-20** token on our AGUNG testnet. You can now view your contract on a block explorer (like Subscan) and interact with it through the interface in Remix. For further enhancements to your ERC-20 token, explore features like **minting**, **burning**, or **transferring tokens**. # Deploying ERC-721 NFT Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/deploying-erc-721-nft Non-Fungible Tokens (NFTs) are **unique** digital assets that represent ownership of a specific item or piece of content on the blockchain. The **ERC-721** standard defines a common interface for **NFTs**, ensuring interoperability across platforms and marketplaces. Within the **peaq ecosystem**, NFTs can be more than digital collectibles. For example, organizations, innovators, and entrepreneurs building DePINs and dApps on the peaq network could use ERC-721s to: * Represent and manage the unique identity of connected devices in a Machine Economy (e.g., electric scooters, autonomous drones, or charging stations). * Facilitate digital identity (DID) whitelist solutions where each person or entity (like a vehicle or machine) is represented as a unique NFT, easing onboarding, access controls, and lifecycle management. * Tokenize real-world machine-based activities or digital certificates that attest to certain functionalities, ensuring authenticity and traceability within decentralized marketplaces. ## Prerequisites * **Basic Blockchain Knowledge:** You understand what the Ethereum Virtual Machine (EVM) and smart contracts are and have already deployed an ERC-20 token using Remix IDE before (see tutorial - [Deploying ERC-20 Token](/peaqchain/build/basic-operations/deploying-erc-20-token)). * **Access to Remix:** You have opened Remix in your browser and are familiar with its basic functionality. * **MetaMask & Test Network:** You have MetaMask or another Web3 wallet installed and connected to a test network (like AGUNG) or a local blockchain (e.g., Hardhat or Ganache) for testing. * **Standard ERC-721 Boilerplate:** You have a standard ERC-721 contract template (e.g., from OpenZeppelin’s library) ready to customize. ## Instructions ### 1. Obtain an ERC-721 Boilerplate * Navigate to the [OpenZeppelin ERC-721 GitHub repository](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol). * Copy or import this simple ERC-721 implementation (or a minimal example from the OpenZeppelin docs like `ERC721PresetMinterPauserAutoId.sol`). ### 2. Create a New File in Remix * In Remix, click on the **File explorer** icon (the first icon on the left) and create a new file named, for example, `MyFirstNFT.sol`. * Paste the boilerplate ERC-721 code into the file. ### 3. Fill in Relevant Details Within the constructor and contract parameters, you can set: * **Contract Name & Symbol:** Choose a name that represents your NFT collection (e.g., `MyFirstNFT`) and a symbol (e.g., `MFN`). * **Base URI:** If your contract includes a base URI, set it to a URL pointing to your NFT metadata storage location (e.g., `https://my-nft-metadata-api.com/metadata/`). IPFS storage systems like [Pinata](https://pinata.cloud/) can help store this metadata off-chain to be upgraded later if needed.

**Example Constructor Initialization:** ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} constructor() ERC721("MyFirstNFT", "MFN") { // Optional: _setBaseURI("https://my-nft-metadata-api.com/metadata/"); } ``` ### 4. Compile the Contract * Click on the **Solidity Compiler** icon (usually the second icon on the left). * Select the Solidity version that matches the pragma in your code. * Click the **Compile MyFirstNFT.sol** button. Ensure there are no compilation errors. ### 5. Deploy the Contract * Click on the **Deploy & Run Transactions** icon (usually the third icon on the left). * In the **Environment** dropdown, select your desired network (e.g., `Injected Web3` to use Metamask's currently selected network). * Click on the **Deploy** button. * Confirm the transaction in Metamask. Once the transaction is mined, your contract address will appear in Remix's deployed contracts section. ### 6. Minting and Interacting * Use the contract’s `mint` function (if included) or any custom function to create a new NFT. * After minting, you can view the token details (e.g., `ownerOf(tokenId)`, `tokenURI(tokenId)`) directly in Remix or using block explorers. ### 7. Testing & Next Steps * Verify your contract on a test network like AGUNG and ensure your NFT appears on NFT-supporting platforms or on block explorers like [Subscan (AGUNG)](https://agung-testnet.subscan.io/) / [Subscan (PEAQ)](https://peaq.subscan.io/) . * Once confident, you can migrate to mainnet or integrate with the peaq ecosystem's unique functionalities—such as representing machine IDs or tokenizing machine-based services—and leverage peaq’s infrastructure for future improvements, liquidity, and interoperability. ## Summary By following these steps, you've successfully **deployed** a ERC-721 NFT contract. Over time, you can refine the contract, incorporate advanced features (like access control, royalties, or custom metadata handling, unique mint/burn functions, etc. ), and build rich applications that harness the potential of **NFTs** in the peaq ecosystem's machine-centric marketplace. # Estimating Gas Fees Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/gas-operations/estimate-gas-fees On peaq and other EVM-compatible networks, transaction execution costs are measured in **gas**. Before sending a transaction on the network, it's often helpful to **estimate** the gas required so that you don't overpay, or risk your transaction running out of gas. Most EVM networks provide a built-in RPC method, `eth_estimateGas`, which allows you to programmatically estimate the gas usage for a given transaction. This estimation is done by **simulating** the transaction on a node, without actually broadcasting it. ## Prerequisites 1. **You have access to one of our EVM-Compatible RPC Endpoints:** * [Connecting to peaq](/peaqchain/build/getting-started/connecting-to-peaq) 2. **You know the Transaction Data you’re sending:** The parameters of the transaction you want to send are well-defined, including `from`, `to`, `data` (if interacting with a contract), and optionally `value` if sending PEAQ or the respective native token. You don't need the exact gas amount upfront; that's what you're estimating. 3. **JSON-RPC Client:** You can use a JSON-RPC client (e.g., `curl`, `Postman`, `web3.js`, `ethers.js`, or any library that supports JSON-RPC) to make requests to the RPC endpoint. In our example below we'll be sending a JSON payload using a `curl` request to an RPC Endpoint. ## Instructions ### 1. Prepare the JSON-RPC Request Use the `eth_estimateGas` method to request a gas estimate. The parameters should describe the transaction you intend to send. For example: ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "jsonrpc": "2.0", "method": "eth_estimateGas", "params": [{ "from": "0xYourFromAddress", "to": "0xRecipientOrContractAddress", "value": "0x0", // hex for 0 "data": "0xYourEncodedData" // if calling a contract function }], "id": 1 } ``` Addresses and values must be in hex format. If you don't need to send value (PEAQ), you can omit it or set it to `"0x0"` ### 2. Send the Request to the RPC Endpoint Using `curl` from your terminal, for example: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl -X POST \ -H "Content-Type: application/json" \ --data '{ "jsonrpc":"2.0", "method":"eth_estimateGas", "params":[{"from":"0xYourFromAddress","to":"0xRecipientOrContractAddress","data":"0xYourEncodedData"}], "id":1 }' \ https://quicknode.peaq.xyz //Your chosen RPC provider's URL ``` ### 3. Read the Response The node will return a JSON response. A successful response might look like: ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "jsonrpc":"2.0", "id":1, "result":"0x5208" } ``` The `"result"` field is a hexadecimal string representing the estimated gas limit. For example, `0x5208` (in hex) is `21000` in decimal—typical for a simple ETH transfer. ### 4. Convert and Use the Returned Value * **Conversion:** If the returned gas estimate is in hex, you can convert it to a decimal integer. In many programming languages, you can parse the hex string and turn it into an integer type. For example, in JavaScript: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const hexGasEstimate = "0x5208"; const gasEstimate = parseInt(hexGasEstimate, 16); // returns 21000 ``` * **Adjustment for Safety:** While `eth_estimateGas` gives a good baseline, you may want to add a buffer (e.g., add 10-20% more) to ensure your transaction doesn't fail if conditions change by the time it's mined. ### 5. Use the Estimated Gas in Your Transaction When constructing the final transaction, set the `gas` field to the estimated amount (plus any safety buffer). Consider the current gas price or fee structures (`base fee + priority fee`) when calculating the total transaction cost. ## Summary Making an `eth_estimateGas` call over a JSON-RPC endpoint and parsing the response, you can **accurately estimate** the **gas limit** needed for your transaction on an **EVM network**. This helps ensure you neither **waste gas** nor have insufficient gas for a successful execution. # Managing Gas Limits Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/gas-operations/managing-gas-limits When interacting with the peaq network, managing gas limits allows you to maintain **predictable** transaction costs, and safeguard against unexpected gas spikes. By explicitly setting a **gas limit**, you prevent overspending, if the network suddenly requires more gas than anticipated. Libraries like `ethers.js`, `web3.js`, etc. make it straightforward to configure this behavior. For the purpose of demonstration we'll be using `ethers.js`. ## Prerequisites * You understand basic concepts of EVM-like networks: transactions, gas, and gas pricing. * You have Node.js and `ethers.js` installed. * You have access to a **funded account** and the peaq RPC endpoint. * You know how to manage sensitive information using environment variables (e.g., `.env` files). ## Instructions ### 1. Setting Up the Environment * **Install Dependencies:** Ensure you install the necessary packages: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install ethers dotenv ``` * **Set ESM Module:** Add the following to your `package.json` to alllow for ESM modules. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} "type": "module", ``` * **Configure Environment Variables:** Create a `.env` file with the following content: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} PRIVATE_KEY=0xYOUR_PRIVATE_KEY PEAQ_RPC_URL=https://quicknode.peaq.xyz ``` Make sure your `.env` file is listed in `.gitignore` so it's not checked into version control. ### 2. Setting up the Provider and Wallet Create a `sendTransaction.js` file (for example) and load the environment variables using `dotenv`. Initialize the provider with the peaq RPC endpoint and load the wallet from the environment variable. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} //sendTransaction.js import dotenv from 'dotenv'; dotenv.config(); import { ethers } from 'ethers'; // Use environment variables from .env const provider = new ethers.providers.JsonRpcProvider(process.env.PEAQ_RPC_URL); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); ``` ### 3. Deciding on a Gas Limit Choose a suitable gas limit. Start with a safe upper bound, and adjust later as needed. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const gasLimit = 200000; // Example: Adjust as needed based on your transaction's complexity ``` ### 4. Crafting the Transaction Include the `gasLimit` in the transaction object. This ensures the transaction will revert if it exceeds the specified gas, protecting you from unexpected costs. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const tx = { to: '0xRECEIVER_ADDRESS', // Replace with the address you want to send to value: ethers.utils.parseEther('0.01'), // Example amount to send gasLimit: gasLimit }; ``` ### 6. Sending the Transaction Send the transaction and wait for it to be mined. If the transaction fails due to exceeding gas, you’ll catch the error in the `try/catch` block. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function sendTransaction() { try { const response = await wallet.sendTransaction(tx); console.log('Transaction hash:', response.hash); const receipt = await response.wait(); console.log('Transaction confirmed in block:', receipt.blockNumber); } catch (error) { // Failure could be due to insufficient gasLimit console.error('Transaction failed:', error); } } sendTransaction(); ``` ## 7. Adjusting Gas Limits Over Time * If your transaction often runs out of gas, consider **raising** the limit. * If you're consistently using less gas than your limit, you might **lower** it to be more cost-effective. * For automated scripts or systems that regularly send transactions, predefining a gas limit helps protect against sudden gas spikes. * Consider a dynamic approach like [Estimating Gas Fees](/peaqchain/build/basic-operations/gas-operations/estimate-gas-fees) in your calculation of a gas limit to prevent failed transactions. ## Summary By incorporating environment variables, you keep sensitive data secure and easily manage configurations for different environments. Using the `ethers.js` library, explicitly setting a **gas limit** gives you fine-grained control over your transaction costs on the peaq network. With a carefully chosen gas limit, you can execute transactions confidently, knowing you're safeguarded against unexpected fee changes. # Optimizing Gas Usage Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/gas-operations/optimizing-gas-usage On EVM networks, **gas fees** represent the **computational cost** of executing transactions and smart contract operations. Lowering these fees can help developers and users alike. Common strategies include **reducing unnecessary computations**, **optimizing data storage**, **leveraging efficient data structures**, **batching transactions**, and **offloading computation off-chain** when possible. Ultimately, the goal is to use fewer computational resources per transaction, thus lowering the gas cost and improving overall **efficiency**. ## Prerequisites 1. **EVM Compatibility:** Your project is deployed on peaq network, which has EVM compatibility, allowing you to apply common Ethereum-based **optimization** techniques. 2. **Basic Knowledge of Gas Mechanics:** You understand that gas fees increase with transaction complexity, storage operations, and on-chain computations. 3. **Developer-Level Access:** You have the ability to modify and deploy smart contracts, review their code, and adjust transaction creation parameters. 4. **Stable Contract Logic:** Your smart contract logic is largely **finalized**, enabling you to focus on gas optimization, without expecting major functional changes. ## Instructions for Optimizing Gas Fees ### 1. Minimize On-Chain Data Storage * **Data Compression:** Instead of storing raw large data sets, try to store **compressed** or hashed references. * **Hashing and Linking:** Use hashes (e.g., `Keccak256`) to reference large off-chain data, reducing on-chain storage operations. * **State Variable Efficiency:** Consolidate related data into fewer state variables or use bit-packing techniques to store multiple flags or small integers in a single variable. ### 2. Batch Transactions * **Multi-Call Transactions:** Combine **multiple** related operations into a single transaction when possible, reducing overhead and the total gas spent on repeated transaction components. * **Use Off-Chain Aggregation:** Aggregate user actions off-chain and submit them in **bulk** on-chain as a single batched update. ### 3. Optimize Smart Contract Logic * **Simplify Computations:** Remove redundant loops, pre-calculate results off-chain, and use efficient algorithms. * **Use Mappings and Arrays Wisely:** Accessing storage variables is expensive. Consider using more gas-efficient data structures (e.g., **mapping** instead of arrays for lookups) and keep arrays as short as possible. * **Leverage Immutable Variables and Constants:** Mark values that do not change as `constant` or `immutable` to reduce gas costs associated with lookups. ### 4. Regularly Audit and Test * **Iterative Testing:** Deploy test contracts on testnets to measure gas usage, iterating on code changes to verify improvements. * **Automated Tools:** Use gas profiling and analytics tools (e.g., `hardhat-gas-reporter`) to identify and track optimization progress. ### 5. Leverage Layered Architectures * **Off-Chain Computations and Oracles:** Perform complex calculations off-chain and feed only the necessary results into the contract. * **Rollups or Sidechains:** While peaq EVM may offer certain scaling capabilities, consider hybrid approaches that further reduce mainnet gas consumption. ## Summary By applying these strategies—reducing on-chain complexity, storing less data and opting for hashed data, batching operations, and thoroughly testing changes—you can **significantly lower gas fees** on the peaq EVM network without compromising your decentralized application's functionality. # chain_subscribeNewHeads Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/listening-parsing-chain-events/chain-subscribeNewHeads The peaq network exposes a **JSON-RPC interface** over WebSockets that allows clients to **subscribe** to various chain events. In this guide, we focus on the `chain_subscribeNewHeads` method. It is designed to notify you whenever a new **block header** is produced on the blockchain. When you call this method, the node returns a subscription ID. Subsequently, as new blocks are finalized or produced, the node pushes notifications to your client containing block header information (such as the **parent hash**, **block number**, **state root**, **extrinsics root**, and **digest logs**). Key points: * `chain_subscribeNewHeads` is a stable and well-supported JSON-RPC method for **header subscriptions**. * Once subscribed, the node sends notifications (commonly using the method `"chain_newHead"` or sometimes `"subscription"`) that include the block header data. * The subscription is maintained over the lifetime of the WebSocket connection. To clean up, you should unsubscribe using `chain_unsubscribeNewHeads` before closing the connection. ## Prerequisites Before proceeding, the following Prerequisites are made: 1. **WebSocket Endpoint:** * You have access to a peaq node via a WebSocket endpoint. The script expects this endpoint to be available via an environment variable (e.g., `PEAQ_WS_URL`). If not provided, it falls back to a default endpoint (e.g., `wss://quicknode.peaq.xyz`). 2. **JSON‑RPC Support:** * The node supports the standard JSON-RPC methods, including **`chain_subscribeNewHeads`** and **`chain_unsubscribeNewHeads`**. This guide assumes you are using the stable methods (rather than unstable alternatives that may not work reliably). 3. **Raw WebSocket Library (ws):** * To avoid any interference from additional wrappers (such as those found in some Web3 libraries), the example uses the `ws` package. This ensures that you receive the exact JSON-RPC messages directly from the node. 4. **Chain Activity:** * The network is producing new blocks. If no new blocks are produced after you subscribe, you may not see any notifications immediately. ## Instructions ### 1. Setting Up the Environment * **Install Dependencies:** Ensure you install the necessary Node.js packages: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install ws dotenv ``` * **Set ESM Module:** Add the following to your `package.json` to alllow for ESM modules. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} "type": "module", ``` * **Environment Variables:** Create a `.env` file in your project directory (if not already present) and set the WebSocket endpoint URL: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} PEAQ_WS_URL=wss://quicknode.peaq.xyz ``` ### 2. Understanding the Subscription Flow * **Initiating the Connection:** The script establishes a direct WebSocket connection to the peaq node. Once connected, it sends a JSON-RPC payload to subscribe to new block headers using the **`chain_subscribeNewHeads`** method. * **Subscription Request:** The JSON‑RPC request payload includes: * `"jsonrpc": "2.0"`: The protocol version. * `"method": "chain_subscribeNewHeads"`: The method to subscribe to block headers. * `"params": []`: No additional parameters are required. * `"id": 1`: A unique identifier for the request. * **Handling the Response:** The node responds with a JSON-RPC message that includes a subscription ID. This ID is saved locally and used to filter incoming notifications. * **Receiving Notifications:** After a successful subscription, the node pushes notifications (using either `"chain_newHead"` or the generic `"subscription"` method) that include a `params` object. The script checks if the incoming message's `subscription` field matches the stored subscription ID and then parses out the block header details. * **Graceful Shutdown:** When the client (or user) sends an interrupt (SIGINT, usually via Ctrl+C), the script sends an unsubscribe request using **`chain_unsubscribeNewHeads`** with the subscription ID. This cleanly ends the subscription and then closes the WebSocket connection. ### 3. Running the Boilerplate ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // chain_subscribeNewHeads.js import dotenv from 'dotenv'; dotenv.config(); import WebSocket from 'ws'; const WS_URL = process.env.PEAQ_WS_URL; const ws = new WebSocket(WS_URL); let subscriptionId = null; const SUBSCRIBE_REQUEST_ID = 1; const UNSUBSCRIBE_REQUEST_ID = 2; ws.on('open', () => { console.log("WebSocket connected"); // Send a subscription request using the stable method "chain_subscribeNewHeads" const subscribePayload = { jsonrpc: "2.0", method: "chain_subscribeNewHeads", params: [], id: SUBSCRIBE_REQUEST_ID, }; console.log("Sending subscription payload:", JSON.stringify(subscribePayload)); ws.send(JSON.stringify(subscribePayload)); }); ws.on('message', (message) => { try { const data = JSON.parse(message); console.log("Received message:", JSON.stringify(data, null, 2)); // Handle the subscription response (id: 1) if (data.id === SUBSCRIBE_REQUEST_ID && data.result) { subscriptionId = data.result; console.log("Subscribed to new heads with ID:", subscriptionId); } // Handle incoming notifications for new block headers // Some nodes may return notifications under the method "chain_newHead" or the generic "subscription" else if ((data.method === 'chain_newHead' || data.method === 'subscription') && data.params && data.params.subscription === subscriptionId) { console.log("New Block Header received:"); console.log(data.params.result); } } catch (e) { console.error("Error parsing message:", message); } }); ws.on('error', (error) => { console.error("WebSocket error:", error); }); ws.on('close', () => { console.log("WebSocket connection closed"); }); // Graceful shutdown: send an unsubscribe request and close the socket process.on('SIGINT', () => { console.log("\nGracefully shutting down..."); if (subscriptionId) { const unsubscribePayload = { jsonrpc: "2.0", method: "chain_unsubscribeNewHeads", params: [subscriptionId], id: UNSUBSCRIBE_REQUEST_ID, }; console.log("Sending unsubscribe payload:", JSON.stringify(unsubscribePayload)); ws.send(JSON.stringify(unsubscribePayload), () => { ws.close(); process.exit(); }); } else { ws.close(); process.exit(); } }); console.log("Listening for new block headers on the peaq network (chain_subscribeNewHeads) using ws..."); ``` * **Start the Script:** Run the script using Node.js: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} node chain_subscribeNewHeads.js ``` * You should see logs indicating that the WebSocket connection is established and that the subscription request has been sent. If new blocks are produced, you will see the raw notifications logged (including block header details). eth-subscribe-new-heads-1 * **Interpreting Logs:** * A log entry like `Subscribed to new heads with ID: ` confirms the subscription was successful. * When a new block header is received, the script prints the details under the message `New Block Header received:`. * If no notifications are received, double-check that the chain is active and that the node is emitting new block headers. * **Shutdown:** Press Ctrl+C to trigger the graceful shutdown. The script will send an unsubscribe request before closing the connection. ## Summary This guide walked you through the process of listening and parsing chain events (block headers) on the peaq network using the **`chain_subscribeNewHeads`** JSON-RPC method. Using this approach, you can reliably monitor **new block headers** on the peaq network and integrate these events into your blockchain applications or debugging workflows. # eth_newFilter + eth_getFilterChanges Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/listening-parsing-chain-events/eth-newFilter In blockchain systems, **logs** are low-level events generated by contracts and system actions that can provide insights into on-chain activity. Unlike block headers or pending transactions, logs can capture **specific events** or **state changes**, which are useful for debugging, monitoring, and analytics. Using the JSON-RPC method `eth_newFilter`, you can create a filter on the node that watches for new log events. Then, by periodically calling `eth_getFilterChanges`, you can poll the node for any new logs that match your filter criteria. This **push-poll model** (as opposed to a true push subscription) is especially useful when the node does not offer a native subscription for logs. In the provided **boilerplate**: * A **log filter** is created with the parameter `{ fromBlock: "latest" }` so that only logs from the current block onward are captured. * The code then polls for changes every 5 seconds by calling **`eth_getFilterChanges`** with the filter ID. * Each received log is then printed to the console. * The boilerplate also includes error handling and a graceful shutdown routine that uninstalls the filter and disconnects the WebSocket provider. ## Prerequisites * **Node.js Environment:** You are running this code in a Node.js environment. * **Dependencies:** The following packages are installed: * `web3` (v4.x) * `web3-providers-ws` * `dotenv` * **WebSocket Endpoint:** You are connecting to the peaq network via the WebSocket endpoint `https://quicknode.peaq.xyz`, configured via a `.env` file. * **Basic Understanding:** You have a basic understanding of JSON‑RPC, WebSocket connections, and blockchain logs. ## Instructions ### 1. Setting Up the Environment * **Install Dependencies:** Make sure you install the necessary packages: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install web3 dotenv web3-providers-ws ``` * **Set ESM Module:** Add the following to your `package.json` to alllow for ESM modules. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} "type": "module", ``` * **Configure Environment Variables:** Create a `.env` file in your project directory with: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} PEAQ_WS_URL=wss://quicknode.peaq.xyz ``` ### 2. Creating the Log Filter * **Using eth\_newFilter:** The method `eth_newFilter` creates a filter on the node to watch for specific log events. In our boilerplate, we call it with parameters like `{ fromBlock: "latest" }` to capture logs from the most recent block onward. The node responds with a filter ID that uniquely identifies this filter. ### 3. Polling for Log Changes * **Using eth\_getFilterChanges:** Once the filter is created, you poll for changes by calling `eth_getFilterChanges` with the **filter ID**. This method returns any logs that have been recorded since the last poll. The code uses a polling interval (5 seconds in this example) to check for new logs **continuously**. * **Processing Logs:** Each new log returned by `eth_getFilterChanges` is iterated over and printed to the console. This allows your application to process each event as needed. ### 4. Error Handling * **During Filter Creation and Polling:** Both the creation of the filter and the polling calls include error checks. If an error occurs (or if the node returns an error), the error is logged to the console. This ensures that issues such as network problems or incorrect parameters are surfaced immediately. ### 5. Graceful Shutdown * **Uninstalling the Filter:** Upon receiving a **shutdown signal** (e.g., Ctrl+C), the code sends a JSON‑RPC request using **`eth_uninstallFilter`** with the filter ID. This uninstalls the filter from the node, ensuring that resources are freed. * **Disconnecting the Provider:** After uninstalling the filter, the WebSocket provider is disconnected to cleanly close the connection before the process exits. ## Code Example ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // eth_filter_logs.js import dotenv from 'dotenv'; dotenv.config(); import Web3 from 'web3'; import WSProvider from 'web3-providers-ws'; const WS_URL = process.env.PEAQ_WS_URL; const provider = new WSProvider(WS_URL); const web3 = new Web3(provider); let logFilterId = null; // Create a log filter using a raw JSON-RPC call provider.send( { jsonrpc: "2.0", method: "eth_newFilter", params: [{ fromBlock: "latest" }], id: 1, }, (error, response) => { if (error || response.error) { console.error("Error creating log filter:", error || response.error); return; } logFilterId = response.result; console.log("Created log filter with ID:", logFilterId); // Start polling for log changes every 5 seconds setInterval(() => { provider.send( { jsonrpc: "2.0", method: "eth_getFilterChanges", params: [logFilterId], id: 2, }, (err, res) => { if (err || res.error) { console.error("Error polling log filter:", err || res.error); return; } if (res.result && res.result.length > 0) { res.result.forEach((log) => { console.log("Chain Log received:"); console.log(log); }); } } ); }, 5000); } ); // Graceful shutdown process.on('SIGINT', () => { console.log("\nGracefully shutting down..."); if (logFilterId) { provider.send( { jsonrpc: "2.0", method: "eth_uninstallFilter", params: [logFilterId], id: 3, }, (error, response) => { if (error || response.error) { console.error("Error uninstalling log filter:", error || response.error); } else { console.log("Uninstalled log filter:", logFilterId); } provider.disconnect(); process.exit(); } ); } else { provider.disconnect(); process.exit(); } }); console.log("Listening for chain logs on the peaq network (eth_newFilter + eth_getFilterChanges)..."); ``` ### Example logs: eth-new-filter-1 ## Summary This guide has demonstrated how to listen and parse chain log events on the peaq network using a combination of `eth_newFilter` and `eth_getFilterChanges`. By using these methods, you can efficiently monitor **chain logs**, enabling real-time analysis and responsive applications on the peaq network. This approach is especially useful when native log subscriptions are not available or when you prefer a controlled polling mechanism. Customize the provided boilerplate as needed to suit your application's requirements. # eth_subscribe Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/listening-parsing-chain-events/eth-subscribe In blockchain systems, pending transactions are those that have been submitted to the network but have **not yet been included** in a block. By subscribing to these events using `eth_subscribe`, your application can receive **real-time notifications** of transaction hashes as they enter the transaction pool (mempool). This push-based approach helps build responsive decentralized applications, monitor network activity, and provide transaction tracking or analytics in near real-time. In this guide, we focus on subscribing to pending transactions using **`eth_subscribe`**. The provided boilerplate uses a WebSocket provider (pointing to `wss://quicknode.peaq.xyz`), creates a subscription for pending transactions, handles incoming data and errors, and includes a graceful shutdown routine to properly clean up the subscription and disconnect the WebSocket provider. ## Prerequisites * **Node.js Environment:** You have Node.js installed on your system. * **Familiarity with JavaScript:** You are comfortable with JavaScript and asynchronous programming (using async/await). * **WebSocket Connection:** You will connect to a peaq network node via WebSocket (using `wss://quicknode.peaq.xyz`). * **Dependencies:** The required packages (`web3`, `dotenv`, and `web3-providers-ws`) are installed. * **Basic Blockchain Knowledge:** You understand the concepts of pending transactions, mempool, and real‑time event subscription. ## Instructions ### 1. Setting Up the Environment * **Install Dependencies:** Ensure you install the necessary packages: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install web3 dotenv web3-providers-ws ``` * **Set ESM Module:** Add the following to your `package.json` to alllow for ESM modules. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} "type": "module", ``` * **Configure Environment Variables:** Create a `.env` file with the following content: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} PEAQ_WS_URL=wss://quicknode.peaq.xyz ``` ### 2. Understanding `eth_subscribe` The **`eth_subscribe`** method is a JSON-RPC method available over **WebSocket** connections. It allows you to subscribe to various types of events on the blockchain in real-time. When subscribing to pending transactions, the node sends updates as soon as new transactions are detected in the **mempool.** Here's how it works: * **Subscription Request:** When you call `web3.eth.subscribe('pendingTransactions')`, the node sets up a listener for new pending transactions. * **Data Event:** The subscription object returns a push-based stream of events. Each event provides the transaction hash of a pending transaction. The code attaches an event handler on the `"data"` event to process each incoming transaction hash. * **Error Handling:** An `"error"` event is also attached to handle any issues (e.g., network errors or connection drops). ### 3. **Implementing the Boilerplate** The provided boilerplate performs the following steps: * **Initialize the Provider and Web3 Instance:** A WebSocket provider is created using `web3-providers-ws`, connecting to the peaq network RPC endpoint. * **Subscribe to Pending Transactions:** An asynchronous function is used to call `web3.eth.subscribe('pendingTransactions')`. Once the subscription is created, event handlers for `"data"` and `"error"` are attached. * **Graceful Shutdown:** A signal handler for `SIGINT` (e.g., when pressing Ctrl+C) is registered. Upon receiving the shutdown signal, the code unsubscribes from pending transactions and disconnects the WebSocket provider to ensure a clean exit. ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // eth_subscribe_pendingTx.js import dotenv from 'dotenv'; dotenv.config(); import Web3 from 'web3'; import WSProvider from 'web3-providers-ws'; const WS_URL = process.env.PEAQ_WS_URL; const provider = new WSProvider(WS_URL); const web3 = new Web3(provider); let pendingSubscription; (async () => { try { pendingSubscription = await web3.eth.subscribe('pendingTransactions'); pendingSubscription.on("data", (txHash) => { console.log("Pending Transaction Hash received:"); console.log(txHash); }); pendingSubscription.on("error", (error) => { console.error("Error in pendingTransactions subscription:", error); }); } catch (err) { console.error("Error subscribing to pendingTransactions:", err); } })(); // Graceful shutdown process.on('SIGINT', async () => { console.log("\nGracefully shutting down..."); try { if (pendingSubscription) { await pendingSubscription.unsubscribe(); console.log("Unsubscribed from pendingTransactions."); } } catch (err) { console.error("Error unsubscribing from pendingTransactions:", err); } provider.disconnect(); process.exit(); }); console.log("Listening for pending transactions on the peaq network (eth_subscribe)..."); ``` The terminal will output the pending transaction hash as follows: eth-subscribe-1 ## Conclusion This guide demonstrates how to use the `eth_subscribe` JSON-RPC method to listen to pending transactions on peaq network. Using this approach, your application can efficiently monitor the mempool for new pending transactions, enabling you to build responsive, data-driven decentralized applications on the peaq network. Feel free to customize and extend the boilerplate code to suit your specific requirements. # Introduction Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/listening-parsing-chain-events/introduction Listening and parsing chain events is a crucial aspect of blockchain development. By subscribing to or querying for specific events—like **new blocks**, **pending transactions**, or **contract logs**—you can build real-time applications that respond instantly to on-chain activity. Whether you're monitoring blocks to confirm transactions or listening for specific smart contract events, understanding how to capture and interpret these updates is key to creating interactive and dynamic blockchain services. # Introduction In this section, we explore various methods for receiving and processing chain events. You'll learn how to use subscription-based APIs (like [`eth_subscribe`](/peaqchain/build/basic-operations/listening-parsing-chain-events/eth-subscribe) and [`chain_subscribeNewHeads`](/peaqchain/build/basic-operations/listening-parsing-chain-events/chain-subscribeNewHeads)) for live notifications, as well as filter-based approaches ([`eth_newFilter` + `eth_getFilterChanges`](/peaqchain/build/basic-operations/listening-parsing-chain-events/eth-newFilter)) for more targeted event tracking. By mastering these techniques, you'll be able to develop robust applications that keep pace with on-chain changes as they happen. # Smart Contract Events Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/listening-parsing-chain-events/smart-contract-events Contract events are a key feature of blockchain systems, enabling applications to react to specific state changes in an efficient manner. On peaq network, **chain events** can be emitted during contract execution and logged to the blockchain. These events can be captured and parsed to **build responsive dApps**, **monitor activity**, or **trigger off-chain workflows**. This guide provides step-by-step instructions on how to listen to and parse **smart contract events** on the peaq network. ## Prerequisites 1. You have a working knowledge of Ethereum-based networks and EVM-compatible smart contracts. 2. You have deployed a smart contract on the peaq network that emits events. 3. You are using **ethers.js** or **web3.js** in a JavaScript or Node.js environment. 4. You have access to a **peaq network RPC endpoint** (e.g., [https://quicknode.peaq.xyz](https://quicknode.peaq.xyz)). 5. You know the [**ABI**](https://www.alchemy.com/overviews/what-is-an-abi-of-a-smart-contract-examples-and-usage) of the contract you are monitoring and the address of the deployed contract. ## Instructions ### 1. Set Up Your Project Ensure your environment is set up with the necessary libraries and RPC configuration: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install ethers ``` ### 2. Initialize Connection to the peaq Network Connect to the peaq network using ethers.js: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider("https://quicknode.peaq.xyz"); ``` ### 3. Load the Smart Contract To interact with the smart contract, load its address and ABI: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const contractAddress = "0xYourContractAddress"; // Replace with your smart contract's address const contractABI = [ // Add your contract's ABI here or import with require() instead ]; const contract = new ethers.Contract(contractAddress, contractABI, provider); ``` ### 4. Listen for Events Using ethers.js, you can set up listeners for specific events emitted by the contract: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} contract.on("YourEventName", (param1, param2, event) => { console.log(`Event received:`); console.log(`Param 1: ${param1}`); console.log(`Param 2: ${param2}`); console.log(`Full Event:`, event); }); ``` ### 5. Parse Events Ethers.js provides the raw event log data, which can be parsed for additional details: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} provider.on("logs", (log) => { try { const parsedLog = contract.interface.parseLog(log); console.log("Parsed Log:", parsedLog); } catch (err) { console.log("Not a relevant event:", err); } }); ``` ### 6. Filter Specific Events To reduce overhead, use filters to listen for specific events: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const filter = contract.filters.YourEventName(); // Add any indexed parameters if applicable provider.on(filter, (log) => { const parsedLog = contract.interface.parseLog(log); console.log(`Filtered Event: ${parsedLog.name}`); console.log(`Event Data:`, parsedLog.args); }); ``` ### 7. Testing the Listener Trigger events in your smart contract (e.g., using a wallet or script) and ensure the listener captures them correctly. **Example:** Emit an event in your smart contract: ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} event YourEventName(address indexed user, uint256 value); emit YourEventName(msg.sender, 1000); ``` Expected console output: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} Console Logs Example ................... Event received: Param 1: 0xYourWalletAddress Param 2: 1000 Full Event: {...} ``` Example final boilerplate incorporating previous steps, best practices for environment variables, and modular imports: ### Putting it all Together ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} //event-listener.js import dotenv from 'dotenv'; dotenv.config(); import { ethers } from "ethers"; // Load environment variables const RPC_URL = process.env.PEAQ_RPC_URL || "https://quicknode.peaq.xyz"; // Replace with your preferred peaq RPC URL const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS; // Replace with your contract address import CONTRACT_ABI from "./contract_abi.json"; // Replace with the path to your contract ABI JSON file // Initialize provider and contract const provider = new ethers.JsonRpcProvider(RPC_URL); const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider); // Function to listen for events const listenForEvents = () => { console.log("Listening for events on contract:", CONTRACT_ADDRESS); // Replace "YourEventName" with the event name from your contract ABI contract.on("YourEventName", (param1, param2, event) => { console.log("Event Received:"); console.log(`Param 1: ${param1}`); console.log(`Param 2: ${param2}`); console.log("Raw Event Data:", event); }); // Add a generic log listener (optional) provider.on("logs", (log) => { try { const parsedLog = contract.interface.parseLog(log); console.log("Parsed Event Log:"); console.log(parsedLog); } catch (error) { // Not a relevant event, ignore } }); }; // Error handling and script execution const main = async () => { try { listenForEvents(); } catch (error) { console.error("Error in event listener:", error); process.exit(1); } }; main(); ``` ## **Tips** 1. **Error Handling**: Always handle potential errors (e.g., connectivity issues, unexpected logs). 2. **Batch Processing**: Use a combination of historical event fetching (`getLogs`) and real-time listeners to ensure no data loss. 3. **Indexed Parameters**: Use indexed parameters in your contract events to filter by specific values (e.g., filter by user address). **Example:** ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} event YourEventName(address indexed user, uint256 value); ``` Then filter by the address: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const filter = contract.filters.YourEventName("0xSpecificAddress"); ``` 4. **Environment Variables**: Store sensitive information (e.g., RPC URLs) in environment variables using a `.env` file. Congratulations! 🎉 You now have the ability to efficiently listen to and parse smart contract events on the peaq network, empowering your dApp to respond dynamically to on-chain activity. # MultiTokenVesting Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/multitoken-vesting A gas-optimized, multi-token vesting contract for linear vesting schedules with optional cliffs. ## Overview `MultiTokenVesting` is a Solidity smart contract designed for **linear vesting** of **multiple ERC-20 tokens** from a single deployment. An Owner (admin) creates vesting schedules for beneficiaries, and beneficiaries claim vested tokens over time. Key behaviors: * Vesting is linear (optionally with a cliff) * Schedules can be **revoked** by the Owner * The contract enforces a **solvency check** by pulling tokens in at schedule creation ## Key features ### Multi-token support One contract can manage vesting schedules for many ERC-20 tokens (e.g., USDC, WETH, UNI), without redeploying per token. ### Revocable schedules When the Owner revokes a schedule: * The **vested** amount remains claimable by the beneficiary * The **unvested** portion is refunded back to the Owner ### Gas optimization The contract is optimized for production use through: * Packed data structures (addresses + flags) * Custom errors (cheaper than revert strings) * Direct claiming by schedule index ### Safety features * **Solvency check**: tokens required for a schedule are transferred into the contract at creation time * **Withdraw excess**: the Owner can withdraw only **excess tokens** accidentally sent to the contract (not those locked for active schedules) ## Supported tokens * **ERC-20** tokens are supported. Native gas tokens (e.g., using PEAQ as a native currency) are not supported directly by this contract. Wrap native tokens into an ERC-20 representation if needed. ## Vesting logic Vesting follows a simple timeline: * **Before cliff**: 0 claimable * **After cliff → end**: linear unlock * **After end**: 100% claimable Formula: `VestedAmount = (TotalAmount * (CurrentTime - StartTime)) / Duration` Example: * Total amount: 1000 * Duration: 1000 seconds * Cliff: 250 seconds Progression: * At 0s: 0 vested * At 250s: 250 vested * At 500s: 500 vested * At 1000s: 1000 vested ## Quickstart (peaq-friendly) You deploy `MultiTokenVesting` on peaq EVM like any Solidity contract: * Build and understand the contract: [Build Smart Contract](/peaqchain/build/basic-operations/smart-contracts/build-smart-contract) * Deploy it: [Deploy Smart Contract](/peaqchain/build/basic-operations/smart-contracts/deploy-smart-contract) * Interact with it: [Interact with Smart Contract](/peaqchain/build/basic-operations/smart-contracts/interact-with-smart-contract) If you prefer Foundry, these commands match common peaq setups: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} forge install OpenZeppelin/openzeppelin-contracts --no-commit forge build forge test -vv ``` ## How to use ### A) Approve tokens (ERC-20) Approve the vesting contract to pull tokens from the Owner’s wallet. This call is made on the **ERC-20 token contract**, not the vesting contract. ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} IERC20(tokenAddress).approve(vestingContractAddress, amount); ``` ### B) Create vesting schedule (Owner-only) Create a schedule that starts now, has a 30-day cliff, and vests linearly over 1 year. ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} vesting.createVestingSchedule( beneficiary, token, amount, block.timestamp, 2592000, // 30 days 31536000 // 1 year ); ``` ### C) Claim (Beneficiary-only) Beneficiaries claim by `scheduleIndex`: ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} vesting.claim(scheduleIndex); ``` The `scheduleIndex` is emitted by the `ScheduleCreated` event. In practice, your app should index this event and store schedule indices per beneficiary. ### D) Revoke (Owner-only) The Owner can revoke a schedule: ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} vesting.revoke(scheduleIndex); ``` On revoke: * Beneficiary keeps vested amount up to the revoke time * Remaining unvested amount returns to the Owner ### E) Withdraw excess (Owner-only) Withdraw tokens that are not locked in active schedules: ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} vesting.withdrawExcess(tokenAddress); ``` ## API reference ### VestingSchedule fields * `beneficiary` (address) * `start` (uint64) * `revoked` (bool) * `claimed` (bool) * `token` (address) * `duration` (uint64) * `cliff` (uint64) * `totalAmount` (uint256) * `amountClaimed` (uint256) ### Errors | Error | Meaning | | --------------------------- | ---------------------------------------------------------------- | | `InvalidAddress` | An address argument is zero or invalid | | `InvalidAmount` | Amount is zero or invalid | | `InvalidDuration` | Duration is zero or invalid | | `InvalidCliff` | Cliff is invalid (e.g., greater than duration) | | `Unauthorized` | Caller is not allowed to perform this action | | `ScheduleClaimed` | Schedule was already fully claimed | | `NothingToClaim` | No tokens are currently claimable | | `InvalidIndex` | Provided schedule index does not exist | | `ScheduleWasRevoked` | Schedule was revoked and no longer claimable in the expected way | | `InsufficientExcessBalance` | Not enough excess balance to withdraw | ## Security notes * Solidity `^0.8.20` includes overflow checks by default. * Uses `SafeERC20` to safely handle non-standard ERC-20 implementations. * Claim flows should follow **Checks-Effects-Interactions**. * Centralization risk: the Owner can revoke schedules (beneficiaries must trust the Owner). # On-chain vs Off-chain Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/on-chain-vs-off-chain Blockchain technology provides a decentralized and immutable ledger for recording transactions and storing data proofs. However, because blockchain data is publicly accessible, it is not designed to secure sensitive information by default. Instead, blockchains are primarily used for storing **hash representations** or **encrypted versions** of data. This ensures that the ledger remains **efficient**, **tamper-resistant**, and **verifiable**, while larger or more sensitive data is securely managed through off-chain storage solutions. 1. **Blockchain Efficiency:** Storing large data directly on-chain is resource-intensive and costly. Blockchains are designed to prioritize consensus, security, and scalability. 2. **Data Integrity:** Storing hash representations of data on-chain ensures that the integrity and authenticity of the off-chain data can be verified without directly storing it. 3. **Access Control:** Off-chain storage often incorporates mechanisms to ensure only verifiable entities can access sensitive or private metadata. 4. **Hybrid Storage:** Combining on-chain and off-chain storage provides a balance between decentralization, cost, and performance. # Key Concepts ### **On-Chain Data:** On-chain data refers to data stored directly on the blockchain. Some examples with the peaq blockchain include: **hashes of DID Documents**, **transaction metadata**, **storage configurations**, **RBAC data**, and **smart contract states**. Data stored on the blockchain is **immutable** and **publicly verifiable**. **Benefits:** * **Immutability**: On-chain data is designed to be **tamper-evident**—once recorded, it cannot be altered. However, **immutability does not automatically guarantee full security**; the overall security depends on the broader context and additional safeguards. * **Verification**: Storing a **hash** ensures the **authenticity** of off-chain data without compromising **privacy**. * **Transparency**: On-chain data is **publicly accessible** and **verifiable**, promoting openness and auditability. ### **Off-Chain Data:** Off-chain data refers to data stored outside the blockchain (e.g., in distributed storage systems like **IPFS**, traditional databases (**MongoDB**), or **cloud services**). This will typically include large files, metadata, or sensitive information since the data is too large or too sensitive to be stored on a public blockchain. Typically the off-chain storage is accessible and controlled through cryptographic mechanisms or decentralized identifiers. **Benefits:** * **Scalability**: Reduces blockchain bloat by storing large or complex data elsewhere. * **Flexibility**: Allows for dynamic updates to metadata without altering on-chain records. * **Privacy**: Sensitive data can be encrypted and shared with authorized entities only. ### Example Use Case: DID Document * **On-Chain:** Store the hash of the DID Document along with its basic details. * **Off-Chain:** Store the metadata (e.g., entity metadata too large for DID Document) in an off-chain storage solution. * **Access Control:** Ensure only authorized entities can access the off-chain services via verifiable credentials or permissions. To lean more how to use IPFS or MongoDB with a DID Document please checkout the tutorial: [Off-Chain Storage Solutions](/peaqchain/build/advanced-operations/off-chain-storage/ipfs) This hybrid approach ensures scalability, security, and privacy, enabling blockchain-based systems to handle complex data requirements efficiently. # Fetching Data Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/smart-contract-storage/fetching-data Smart contract storage can be used to **read** from smart contract state variables and to **retrieve** stored information. This could be anything from user balances, configuration parameters, or complex data structures such as mappings and arrays. In this guide, we will walk through: * How smart contract storage works in general. * Common scenarios where you need to retrieve data from contract storage. * Step-by-step **instructions** on how to fetch data, using **Solidity** and a client library (e.g., `web3.js` or `ethers.js`). * Best practices and potential pitfalls. By the end, you will understand how to confidently and securely fetch data from your peaq-based smart contracts. ## Prerequisites * Understanding of Solidity * You have a working development environment set up with: * A Solidity compiler (e.g., via Hardhat, Truffle, or Remix). * Access to the peaq network's EVM endpoint (or a local test EVM). * Using a library like `web3.js` or `ethers.js` to interact with your deployed contracts. Alternatively, any other JSON-RPC client can be used if you are comfortable with lower-level interactions. * Already have a deployed contract on the peaq network, or you have the means to deploy one during testing. * Access to contract's its Application Binary Interface (ABI). This is generally generated automatically when compiling your Solidity smart contract. ## Instructions ### 1. Understanding EVM Contract Storage In EVM-based networks like peaq, each contract has its own allocated storage. Storage is typically broken down into **slots** of 256 bits each: * **State Variables** store data in these storage slots. * **Mappings** are hashed to find their storage slots. * **Arrays** and other data structures have their own packing and storage rules. When writing or reading from storage, gas costs and efficient layout (for writes) can be important. However, retrieving data (a read) is **cheaper** than storing data (a write). For reads, you can **query** the node's state without incurring on-chain transaction costs. ### 2. Declaring and Storing Data in Solidity Let's look at a simple example contract that stores some data: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorage { uint256 public storedValue; mapping(address => uint256) public balances; constructor(uint256 initialValue) { storedValue = initialValue; balances[msg.sender] = 100; } // A simple setter function function setValue(uint256 newValue) external { storedValue = newValue; } // A simple function that updates balances function updateBalance(address account, uint256 amount) external { balances[account] = amount; } } ``` * `storedValue` is a public `uint256`. By making it public, Solidity automatically creates a getter function `storedValue()` that returns the value. * `balances` is a public mapping from `address` to `uint256`. Likewise, `balances(address)` becomes a getter function to read the balance for any given address. ### 3. Interacting With the Contract to Fetch Data #### Using a Script (JavaScript + ethers.js/web3.js) Below is an example of how to fetch data from `storedValue` and `balances` using `ethers.js`. **Ethers.js Example:** ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from 'ethers'; // 1. Connect to the peaq network (replace with actual provider endpoint) const provider = new ethers.JsonRpcProvider("https://your-peaq-rpc-endpoint"); // 2. Define the contract ABI (simplified for this example) const abi = [ "function storedValue() view returns (uint256)", "function balances(address) view returns (uint256)" ]; // 3. The deployed contract address const contractAddress = "0xYourContractAddressOnPeaq"; // 4. Create an instance of the contract const contract = new ethers.Contract(contractAddress, abi, provider); async function readData() { try { // 5. Call the view functions const value = await contract.storedValue(); console.log("storedValue:", value.toString()); const someAddress = "0x1234..."; // Replace with a valid address const balance = await contract.balances(someAddress); console.log(`Balance of ${someAddress}:`, balance.toString()); } catch (error) { console.error("Error fetching data:", error); } } readData(); ``` **Key Points** * We use the `view` functions (`storedValue()` and `balances(address)`) to retrieve data without sending a transaction. * This means there is no cost in terms of gas for these read operations; only the provider's request/response overhead applies. #### Using the Remix IDE If you prefer a browser-based approach: 1. Open Remix IDE. 2. Select the “Solidity” environment and load your contract code. 3. Compile and Deploy to a peaq network-compatible endpoint (via “Deploy & run transactions” panel). 4. After deployment, you will see the contract’s interface in Remix. * Clicking on `storedValue` will fetch and display the current `storedValue`. * Providing an address to `balances` will fetch and display the mapping value for that address. ### 4. Fetching Custom or Complex Data Types If you have **custom** data structures like **structs** or **nested mappings**, you can expose them through custom getter functions. For example: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ComplexStorage { struct User { uint256 id; uint256 balance; address userAddress; } mapping(address => User) private users; // Insert or update user function setUser(address _address, uint256 _id, uint256 _balance) external { users[_address] = User(_id, _balance, _address); } // Public function to read user data function getUser(address _address) external view returns (uint256, uint256, address) { User memory user = users[_address]; return (user.id, user.balance, user.userAddress); } } ``` Here, the struct `User` is stored in a private mapping. We expose a `getUser` function that returns the data in a single call. You can fetch it from your JavaScript code similarly with a function call: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const contractAbi = [ "function getUser(address _address) view returns (uint256, uint256, address)" ]; const contract = new ethers.Contract(contractAddress, contractAbi, provider); const userData = await contract.getUser(someUserAddress); console.log("User Data => ID:", userData[0].toString(), "Balance:", userData[1].toString(), "User Address:", userData[2]); ``` This approach helps keep your contract's data **private** while still allowing necessary read-access to data via dedicated functions. ## Summary Fetching data from smart contract storage on the peaq network (or any EVM-compatible network) follows the same paradigm as Ethereum: * You **define** state variables in Solidity. * You **expose** those variables either with the `public` keyword or dedicated getter functions. * You **interact** with them through a client library (e.g., `ethers.js`, `web3.js`) or Remix IDE. **Key Takeaways**: * **Public Variables**: Automatically generate getter functions, convenient for straightforward retrieval. * **View/Pure Functions**: Cost no gas when called off-chain, making them ideal for reading data. * **Custom Getter Functions**: Useful for complex data structures or additional data processing. * **Security**: Ensure that any data you do not want freely accessible remains in private variables, and only expose it through controlled view functions as needed. With these core principles in mind, you can confidently **fetch data** from your peaq-based Solidity contracts, enabling rich dApp functionality and user-friendly front-end experiences. # Optimizing Storage Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/smart-contract-storage/optimizing-storage Storage **optimization** in Solidity smart contracts is an essential consideration given the relatively high costs associated with writing data to the blockchain. Every write operation has a **gas fee**, and more extensive storage usage equates to **increased** deployment and execution costs. The peaq network provides EVM compatibility, so the same best practices that apply to Ethereum also apply here—but within peaq's unique economy and infrastructure. **Key topics covered in this guide include:** * How the EVM (on peaq) stores data in 256-bit storage slots. * Why **mappings** can be more efficient than arrays in certain scenarios. * Additional strategies like data packing, using `bytes` effectively, and using events instead of on-chain storage. * Practical coding examples in Solidity. ## Prerequisites * Basic Solidity Knowledge * An understanding of how the EVM handles storage, memory, and call data, as well as the associated gas costs for these operations. * Familiar that peaq network is EVM-compatible and that the best practices for storage optimization hold true in its environment, with potential additional benefits from the underlying Substrate-based infrastructure. ## Optimizations Below are some high-level strategies and examples to help you optimize your smart contract storage usage on the peaq network's EVM. ### Use Mappings Instead of Arrays (When Feasible) **Arrays** (especially dynamic arrays) often require more operations to manage (e.g., iteration, boundary checks) and can grow unbounded if not carefully restricted. **Mappings** store data more sparsely, **saving storage** when you do not need sequential elements. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // Example: Using a mapping instead of a dynamic array // Less optimal: using a dynamic array contract TestArray { uint256[] public items; function addItem(uint256 value) external { items.push(value); } // Accessing an element function getItem(uint256 index) external view returns (uint256) { return items[index]; } } // More optimal: using a mapping contract TestMapping { mapping(uint256 => uint256) public items; uint256 public itemCount; function addItem(uint256 value) external { items[itemCount] = value; itemCount++; } // Accessing an element function getItem(uint256 index) external view returns (uint256) { return items[index]; } } ``` **Explanation**: * In the above example, `TestMapping` is more flexible with sparse data and can save gas. * You can maintain a separate counter (`itemCount`) to track indices, mimicking array-like behavior. ### Use the Right Data Types and Pack Them The EVM stores data in 256-bit slots. When two or more variables fit into a **single** 256-bit slot, they can be **packed together** to reduce overall storage usage. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} contract DataPacking { // All of these will fit into a single 256-bit slot if ordered properly uint128 public valueA; // 128 bits uint64 public valueB; // 64 bits uint64 public valueC; // 64 bits // Another slot address public owner; // 160 bits bool public isActive; // 8 bits uint88 public counter; // 88 bits } ``` **Best Practice**: * Group smaller types **together** so they can occupy the same slot. * Reordering the variables to **minimize** wasted space in each 256-bit slot can substantially reduce gas costs. ### Store Data Off-Chain or Use Events When Appropriate If you only need data for historical or informational purposes (i.e., you don't need it to stay in contract storage for on-chain logic), consider storing it **off-chain** or **emitting** it in **events**. Events are cheaper than storing data on-chain and still let you retrieve the data from transaction logs. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} contract UseEvents { // Instead of storing all these logs on-chain... // mapping(uint256 => string) public logs; // This can grow unbounded // Emit an event to record data event LogRecorded(uint256 indexed id, string info); function recordLog(uint256 _id, string memory _info) external { emit LogRecorded(_id, _info); } } ``` **Note**: * Data in events is not accessible to contracts directly (only via off-chain **indexing**), so only move data to events if your contract does not need to rely on it for future state changes. * Checkout our [indexing solutions](/peaqchain/build/advanced-operations/indexing) to see how to query these events. ### Consider Using `bytes` Arrays for Packed Storage In some scenarios (especially with strings or variable-length data), storing in a single `bytes` array can be more **efficient** than storing an array of fixed-size data types, as it packs data continuously without leaving empty space. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} contract BytesStorage { bytes public data; function appendData(bytes memory newData) external { // Append in memory, then store data = abi.encodePacked(data, newData); } } ``` ### Carefully Manage Storage Reads and Writes Each storage write operation **costs gas**, so **optimizing** writes—and reducing the number of expensive `SSTORE` operations—can lower costs. **Strategies**: * **Minimize writes** by caching frequently updated values in memory and writing only once when necessary. * **Use local variables** (memory) rather than reading from storage multiple times in a function. Each storage read is more expensive than a memory read. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} contract StorageWrites { uint256 public counter; function incrementCounter(uint256 times) external { uint256 temp = counter; // Only one storage read for (uint256 i = 0; i < times; i++) { temp++; // Increment in memory } counter = temp; // Single storage write } } ``` ### Reset Storage Slots to Zero When Possible The EVM provides a **gas refund** for clearing storage slots (i.e., writing zero to a previously non-zero slot). Although refunds are capped, this can still result in **net savings** when performing large transactions. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} contract StorageCleanup { mapping(address => uint256) public balances; function withdrawAll() external { uint256 amount = balances[msg.sender]; require(amount > 0, "No balance"); // Clear the slot balances[msg.sender] = 0; // Transfer or other business logic // ... } } ``` ### Use Structs Wisely Grouping related variables into **structs** can help you manage your data more systematically. However, remember that each struct field occupies storage in **256-bit slots**, and you can still leverage the same packing principles within a struct. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} struct UserInfo { uint8 age; uint64 score; bool isActive; // Note: reordering could pack these into fewer slots } contract ManageUsers { mapping(address => UserInfo) public users; function setUserInfo(uint8 _age, uint64 _score, bool _active) external { // Single store operation if we read the struct first into memory, modify it, then write once UserInfo memory tempUser = users[msg.sender]; tempUser.age = _age; tempUser.score = _score; tempUser.isActive = _active; users[msg.sender] = tempUser; } } ``` ## Summary Optimizing storage in Solidity smart contracts not only **reduces** gas costs for your end-users but also contributes to the overall **efficiency** of the peaq network. By adopting these best practices—using mappings, packing data, leveraging events instead of persistent storage, and resetting unused slots—you can ensure your dApps remain economical and performant. **Key Takeaways**: * **Mappings** often outperform arrays, especially for sparse data. * **Data Packing** aligns smaller data types to minimize wasted storage space. * **Events** are a cheaper alternative to on-chain storage if you only need data for off-chain retrieval. * **Minimizing Writes** to contract storage lowers gas costs significantly. * **Storage Slot Cleanup** can yield gas refunds and improve contract efficiency. As you develop on the peaq network's EVM, keep these strategies in mind to create secure, cost-effective, and efficient smart contracts. By following these guidelines, you will **optimize** your contracts' storage usage, reducing costs and enhancing the user experience for your dApps. # Writing Data Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/smart-contract-storage/writing-data ## What is Smart Contract Storage? Smart contract storage refers to the **persistent** data layer of an Ethereum Virtual Machine (EVM) contract. It's where your variables and state information are stored on-chain. Unlike local or in-memory variables, data stored in contract storage remains on the blockchain and can be accessed or updated by functions within your smart contract—or external actors that call those functions. ## Why Does it Matter? * **Data Persistence**: Stored data is immutable once published on-chain (except when updated through contract logic). * **Security**: The data is secured by the blockchain network's consensus mechanism. * **Accessibility**: The data is publicly accessible, ensuring transparency. ## Potential Use Cases * **Token Balances**: ERC-20 tokens keep track of users' balances and allowances in contract storage. * **User Registries**: Store user information like IDs, addresses, or roles. * **Voting Systems**: Keep track of votes, proposals, or election results. * **Marketplace Data**: Save product listings, ownership information, or transaction details. ## Prerequisites * Familiar with Solidity syntax, how to write a simple contract, and how to deploy it. * You have a working development environment set up with: * A Solidity compiler (e.g., via Hardhat, Truffle, or Remix). * Access to the peaq network's EVM endpoint (or a local test EVM). * Have access to an account or wallet (e.g., MetaMask) funded with enough test tokens (for a testnet) or actual tokens (for mainnet) to pay for gas fees. * You know how to connect to the peaq network or you're following official peaq documentation for endpoint configuration. ## Instructions Below is a step-by-step process for writing data to contract storage using **Solidity**. We'll create a simple contract that demonstrates both storing a single variable and mapping key-value pairs. ### 1. Define the Contract ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract DataStorage { // Simple storage variable uint256 public storedNumber; // Mapping to store addresses and their associated data mapping(address => string) public userData; /** * @dev Constructor that sets an initial number * @param _initialNumber The initial number to store */ constructor(uint256 _initialNumber) { storedNumber = _initialNumber; } } ``` #### Explanation * `storedNumber` is a public **state variable** of type `uint256`. * `userData` is a public **mapping** from an Ethereum (or peaq EVM) address to a string. * State variables and mappings like these are stored in the contract's storage on-chain. ### 2. Creating Functions to Write Data We'll add two functions: 1. **`setNumber()`** - to update the stored number. 2. **`setUserData()`** - to write user-specific data into the `userData` mapping. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract DataStorage { // Simple storage variable uint256 public storedNumber; // Mapping to store addresses and their associated data mapping(address => string) public userData; /** * @dev Constructor that sets an initial number */ constructor(uint256 _initialNumber) { storedNumber = _initialNumber; } /** * @dev Updates the stored number in contract storage * @param _newNumber The new number to be stored */ function setNumber(uint256 _newNumber) external { storedNumber = _newNumber; } /** * @dev Writes or updates user data in the mapping * @param _data The data string associated with the caller’s address */ function setUserData(string calldata _data) external { userData[msg.sender] = _data; } } ``` #### Explanation * When `setNumber` is called, it changes the `storedNumber` variable. * When `setUserData` is called, it sets or updates the string in the mapping for the calling address (`msg.sender`). ### 3. Deploying the Contract * **Configure your deployment environment** * If using Hardhat, update the `hardhat.config.js` (or `truffle-config.js` for Truffle) to point to a peaq EVM endpoint (e.g. [https://quicknode.peaq.xyz](https://quicknode.peaq.xyz) ) * If using Remix, simply select the appropriate network in the environment settings. * **Deploy** * In Hardhat: `npx hardhat run scripts/deploy.js --network yourPeaqNetworkConfig` * In Truffle: `truffle migrate --network yourPeaqNetworkConfig` * In Remix: Use the “Deploy” button after selecting the correct network and providing the constructor argument `_initialNumber`. ### 4. Interacting with the Deployed Contract After deployment, you can write data to the contract storage: * **Update the Number** * Call the `setNumber` function with a new number. For example, `setNumber(42)`. * **Write User Data** * Call the `setUserData` function with a string. For example, `setUserData("Hello, peaq!")`. Each transaction will: * Consume gas. * Write data to the blockchain. * Persist the updated data in the contract's storage. ## Summary Writing data to a **smart contract's storage** is fundamental in building decentralized applications. By storing variables and mappings within your Solidity code, you create a transparent, tamper-resistant record of information on the blockchain. Here's what you've learned: 1. **Smart Contract Storage** * How state variables and mappings persist data on-chain. 2. **Use Cases** * Simple counters, user registries, voting systems, and more. 3. **Implementation Steps** * Defining state variables, writing setter functions, deploying to the peaq EVM, and interacting with your contract. With this knowledge, you can confidently manage on-chain data. Be mindful of **gas costs** and **storage optimization techniques**—especially for larger, more complex data structures. For further exploration, look into: * **Events** to emit data without storing it on-chain. * [Optimizing gas](/peaqchain/build/basic-operations/smart-contract-storage/optimizing-storage) with `structs` or storage packing. * [Upgradable Contracts](/peaqchain/build/basic-operations/smart-contracts/upgradable-smart-contracts) for long-term maintenance. # Build Smart Contract Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/smart-contracts/build-smart-contract Welcome to this comprehensive guide on deploying your first smart contract on an EVM-compatible blockchain network. A **smart contract** is a self-executing piece of code stored on the blockchain, where the contract's terms are directly written into code. This means that when the predefined conditions are met, the contract automatically executes without the need for intermediaries, ensuring secure, transparent, and trustless interactions. In this tutorial, you'll learn how to craft a smart contract using the `Solidity` language and deploy using **Remix IDE**, **Hardhat**, or **Foundry**. By understanding the inner workings of these contracts and how they operate on a decentralized network, you'll be well-equipped to build robust and efficient applications. By the end of this guide, you'll have developed a fully functional smart contract, ready for deployment on the peaq network. ## Prerequisites * You have basic programming knowledge (JavaScript is a plus) * You have [MetaMask wallet](/peaqchain/build/getting-started/get-test-tokens#create-evm-wallet) installed * You have access to [Remix](https://remix.ethereum.org/) * You have access to a code editor like Visual Studio Code * [Hardhat](https://hardhat.org/hardhat-runner/docs/getting-started) or [Foundry](https://getfoundry.sh/introduction/installation/) installed (for local development) * [Node.js](https://nodejs.org/en/download/package-manager) is installed * [Python](https://www.python.org/downloads/) is installed ## Remix The most simple way to write a smart contract is through a web-based IDE (Integrated Development Environment) called **Remix**. The software has been specifically tailored to the writing, development, and deployment of Solidity contracts. It contains certain features such as smart contract compilation and interaction with previously deployed contracts. Remix is the perfect platform for quick **plug and play** to get started with blockchain development. ### 1. Open Remix IDE Head over to the [Remix](https://remix.ethereum.org/) webpage. ### 2. Create a new File In the left-hand file explorer panel, click on the New File icon to generate a new file where the smart contract code will be written. Name the file `SimpleStorage.sol` (`.sol` being the standard extension for Solidity code). ### 3. Write the Code Here we will create a simple contract that stores and retrieves a number. Please copy and paste the following code in the file that you just created. ```Solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorage { uint256 private data; function set(uint256 _data) public { data = _data; } function get() public view returns (uint256) { return data; } } ``` #### Understanding the code **1. File License Declaration:** `// SPDX-License-Identifier: MIT` * Specifies the **license** under which the code is distributed, in this case **MIT**. It is important for open-source compliance and avoids compilation warnings. **2. Pragma Directive:** `pragma solidity ^0.8.0;` * Specifies the **version** of solidity that is required for the code. The `^` symbol indicates compatibility with versions 0.8.0 and above (up to but not including 0.9.0). **3. Contract Declaration:** `contract SimpleStorage { … }` * A **contract** is similar to a class in typical object oriented programming paradigms. It encapsulates state variables and functions. The contract name *SimpleStorage* is defined by the user and should represent its purpose. **4. State Variable:** `uint256 private data;` * State variables are stored **directly** on the blockchain, allowing for persistence and global access. `uint256` represents the variable type. In this particular case it is an unsigned integer that can hold large non-negative numbers. The identifier `private` restricts the access of the variable within the contract only. **5. Set Function** `function set(uint256 _data) public { … }` * Functions are used in Solidity to **interact** with the contact. The `set` function here allows users to update the value of `data`. The keyword `public` means that the function is accessible to everyone (any address can call it). The keyword parameter `_data` is used to represent the data a user sent to the contact. By convention these variables start with an underscore. **6. Get Function** `function get() public view returns (uint256) { … }` * Getter function that returns the value of the variable `data` that is stored on the blockchain. The keyword `view` is used to restrict the function to be **read-only** so it does not modify the state of the chain. `returns` specifies what type of value the function will return to the user. With that example, we now have a simple contract that stores an integer on a blockchain. For more complex examples and explanations what you can do with the **Solidity programming language** please look at their [documentation](https://docs.soliditylang.org/en/v0.8.28/). The following two sections will show you how to setup a local **JavaScript** and **Python** environment to write smart contracts. The deployment and interaction of the written contract is done in the subsequent sections. ## Hardhat Remix is not the only tool you can use to deploy your smart contracts. A popular tool used in local Ethereum development is **Hardhat**. It allows for the development, compilation, and interaction of smart contracts on a local IDE. Similar to Remix in many ways, Hardhat does allow for more **flexibility** and **configurability**. If you would like to learn more please read about [Hardhat](https://hardhat.org/docs). ### Install & Initialize Hardhat #### 1. Install Hardhat * **Create a New Repository and Install Hardhat:** * Open your terminal and navigate to your project directory. * Run the following command to install Hardhat: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install --save-dev hardhat ``` #### 2. Initialize Hardhat * Initialize a new Hardhat project using: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx hardhat init ``` * Select **Create an empty hardhat.config.js** when prompted. #### 3. Install Hardhat Toolbox * Install the toolbox dependency for testing, contract interaction, and deployment: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install @nomicfoundation/hardhat-toolbox ``` #### 4. Set Up Project Structure * Organize your project with the following standard folder structure: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} project-directory/ │ ├── contracts/ # Solidity files (e.g., SimpleStorage.sol) ├── ignition/modules/ # Deployment scripts ├── node_modules/ # Installed dependencies ├── scripts/ # Interaction scripts ├── .gitignore # Files to exclude from Git ├── hardhat.config.js # Hardhat configuration file ├── package.json # Node.js project configuration ├── package-lock.json # Dependency lock file └── .env # Environment variables (created in the next step) ``` ### Configure .env and Hardhat #### 1. Create `.env` File * In the root directory, create a `.env` file to store sensitive data. * Install dotenv to enable environment variable support: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install dotenv ``` * Add the following variables to the `.env` file: ```jsx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} RPC_URL="rpc_url" PRIVATE_KEY="wallet_private_key" ``` * Replace `rpc_url` with the [RPC URL](/peaqchain/build/getting-started/connecting-to-peaq#public-rpc-urls) of the peaq/agung test network. * Replace `wallet_private_key` with your wallet's private key. #### 2. Update hardhat.config.js * Update the `hardhat.config.js` file to include the network configuration * Networks added here are used in the deployment script so it knows what chain to send the bytecode to. The example below shows the test network agung. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} require("@nomicfoundation/hardhat-toolbox"); require('dotenv').config(); const RPC_URL = process.env.RPC_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY; module.exports = { solidity: "0.8.28", networks: { agung: { url: RPC_URL, chainId: 9990, // for agung accounts: [`0x${PRIVATE_KEY}`], }, }, }; ``` ### Create the SimpleStorage Smart Contract #### Write the Contract * Inside the `contracts/` folder, create a file named `SimpleStorage.sol`. * Copy the same code used above in the Remix contract and paste it into this newly created file: ```Solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorage { uint256 private data; function set(uint256 _data) public { data = _data; } function get() public view returns (uint256) { return data; } } ``` You have successfully created 2 separate environments that were used to write a smart contract. Please follow the next pages to see how to **deploy** and **interact** with the contract written on this page. ## Foundry Prefer Foundry? Use these steps to set up, configure, and compile with Foundry on peaq/agung. ### 1. Install Foundry * Follow the official instructions: [https://getfoundry.sh/introduction/installation/](https://getfoundry.sh/introduction/installation/) ### 2. Initialize a project ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} forge init my-project cd my-project ``` ### 3. Configure `foundry.toml` Create or update `foundry.toml` in the project root: ```toml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [profile.default] src = "src" out = "out" libs = ["lib"] evm_version = 'london' ``` Note: The `evm_version` is set to `london` for peaq-like networks, due to the fork used on the substrate-based network. ### 4. Add the example contract Create `src/SimpleStorage.sol` and paste the same contract used above: ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract SimpleStorage { uint256 private data; function set(uint256 _data) external { data = _data; } function get() external view returns (uint256) { return data; } } ``` ### 5. Compile ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} forge build ``` Make sure to remove the autogenerated script that references the foundry native test contract to use the `SimpleStorage.sol` instead. # Deploy Smart Contract Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/smart-contracts/deploy-smart-contract In the previous section you learned how to create a simple smart contract to store and retrieve an integer from the blockchain. Now it's time to **deploy** this contract to peaq network. Deployment is the process of broadcasting your contract's **bytecode** to to the blockchain - making it possible to interact with. This guide will show you how to deploy using **Remix**, **Hardhat**, and **Foundry**, three popular tools for EVM smart contract development. ## Prerequisites * Completion of the [Build Smart Contract](/peaqchain/build/basic-operations/smart-contracts/build-smart-contract) guide. * Access to a [MetaMask wallet](/peaqchain/build/getting-started/get-test-tokens#create-evm-wallet) connected to the peaq/agung network. ## Instructions ### Deploying with Remix #### 1. Compile the Smart Contract * **Open the Compile Sidebar in Remix:** * Select the **Solidity Compiler** from the left sidebar. * **Compile the Contract:** * Ensure the Solidity version matches the pragma directive in your contract (e.g., `^0.8.0`). * Click the **Compile SimpleStorage.sol** button. * Ensure green checkmark has appeared after successful compilation. #### 2. Deploy the Contract * **Open the Deploy & Run Transactions Sidebar:** * Select the **Deploy & Run Transactions** option in the Remix sidebar. * **Select the Environment:** * From the **Environment** dropdown, select **Injected Provider - MetaMask**. * Confirm that MetaMask is connected to the appropriate network by checking the **Chain ID**. * **Deploy the Contract:** * Click the orange **Deploy** button to initiate the transaction. * A MetaMask popup will appear for transaction confirmation. Ensure you have sufficient `$AGUNG/$PEAQ` tokens in your wallet for gas fees. #### 3. View Deployment Details Once the transaction is confirmed, the Remix console will display deployment logs, including: * **Transaction Hash:** Unique identifier for the transaction sent to the blockchain. The hash allows you to track and and verify the transaction on the chain. It acts like a deployment receipt. You can use this transaction hash in an explorer to view detailed information about the transaction. * **Block Number:** The number of the block your transaction was included on-chain. Blocks are used to group transactions together and the number is used to indicate where your contract deployment was recorded. * **Gas Used:** The amount of computational effort that the blockchain required to process your transaction. Deploying a contract consumes gas since it involves writing the bytecode on-chain. Gas is used to show how resource-intensive the deployment was. * **Contract Address:** The unique address assigned to your deployed smart contract on the blockchain. This address allows you and other users to interact with your contract. It is the location of the contract on the blockchain network. ### Deploying with Hardhat #### 1. Create a Deployment Script * **Set Up the Script:** * In the `ignition/modules/` folder, create a file named `SimpleStorage.js` * **Add Deployment Logic:** * Add the following code to deploy your `SimpleStorage` js file: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); module.exports = buildModule("SimpleStorageModule", (m) => { const SimpleStorage = m.contract("SimpleStorage", []); return { SimpleStorage }; }); ``` #### 2. Deploy the Contract * **Run the Deployment Command:** * In your terminal, navigate to the project's root directory * Execute the following command to deploy the contract to the agung test network: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx hardhat ignition deploy ./ignition/modules/SimpleStorage.js --network agung ``` * **View Deployment Output:** * Upon successful deployment, the terminal will display: * **Deployed Contract Address:** The unique address assigned to your deployed smart contract on the blockchain. Record the contract address for future interactions. ### Deploying with Foundry Use Foundry to deploy using `forge create` or a script. #### 1. Prepare environment Create a `.env` in your project root with: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} RPC_URL="https://..." # peaq or agung RPC URL PRIVATE_KEY="" # deployer key ``` #### 2. Option A — `forge create` ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} source .env forge create src/SimpleStorage.sol:SimpleStorage \ --rpc-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ --broadcast ``` #### 3. Option B — Deployment script Create `script/DeploySimpleStorage.s.sol`: ```solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity 0.8.25; import {Script} from "forge-std/Script.sol"; import {SimpleStorage} from "../src/SimpleStorage.sol"; contract DeploySimpleStorage is Script { function run() external returns (SimpleStorage) { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); SimpleStorage c = new SimpleStorage(); vm.stopBroadcast(); return c; } } ``` Run it: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} source .env forge script script/DeploySimpleStorage.s.sol:DeploySimpleStorage \ --rpc-url "$RPC_URL" \ --broadcast ``` #### Agung note If gas estimates are too low on Agung, append: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} --gas-estimate-multiplier 300 --slow ``` ## Post-Deployment Validation **Verify Deployment on Blockchain Explorer:** * Use the [peaq](https://peaq.subscan.io/) or [agung](https://agung-testnet.subscan.io/) block explorer to search for your transaction hash or the deployed smart contract address. * Confirm that your contract is successfully deployed. After confirming that the transaction has been submitted and the smart contract address is valid, please follow our next tutorial which shows how to **interact** with the Deployed Contract. # Interact with Smart Contract Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/smart-contracts/interact-with-smart-contract Now that your contract is deployed on the blockchain, the next step is to **interact** with it. Interacting with a smart contract involves calling its functions to read or modify its state. In this guide you will learn how to interact with your contract using **Remix**, **JavaScript**, and **Python**. ## Prerequisites * Successful smart contract deployment. * The **ABI** and **contract address** from the previous deployment are recorded. Generated after compilation and deployment. * Funded wallet connected to the appropriate network (peaq/agung). ## Instructions ### Remix #### 1. Access the Deployed Contract * In Remix, navigate to the **Deployed Contracts** section in the **Deploy & Run Transactions** sidebar. * You'll see your deployed contract listed, with an interactive UI displaying the contract's functions. #### 2. Perform Write Operations * In the Deployed Contracts interface, locate the **set** function. * Enter an integer as an input field (e.g. `10`) and click the set button. * A MetaMask popup will be triggered to confirm the transaction. Confirm the transaction and wait for it to be appended on-chain. * After completion the new value will be stored on the blockchain. #### 3. Perform Read Operations * Locate the **get** function in the Deployed Contracts drop down in the **Deploy & Run Transactions** tab. * Click the **get** button to fetch the current value stored in the contract. * The result will appear below the function, showing the updated value (e.g., `10`). ### JavaScript #### 1. Create an Interaction Script * Inside the `scripts/` directory of your project, create a new file named `interact.js` * Add the following script to interact with your deployed contract: ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const contractAddress = "YOUR_CONTRACT_ADDRESS"; // Replace with your contract address const ContractArtifact = require('../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json'); async function main() { const [deployer] = await ethers.getSigners(); const contract = new ethers.Contract(contractAddress, ContractArtifact.abi, deployer); // Perform a read operation const storedValue = await contract.get(); console.log('Stored Value:', storedValue.toString()); // Perform a write operation const tx = await contract.set(10); // Setting value to 10 await tx.wait(); // Wait for transaction to complete // Verify the new value const updatedValue = await contract.get(); console.log('Updated Value:', updatedValue.toString()); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); ``` #### 2. Execute the Interaction Script * In the root directory of your program run the cmd to execute the code written above. The code will deploy the contract on the agung network as defined in the `hardhat.config.js` file. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx hardhat run scripts/interact.js --network agung ``` 🎉 Congratulations! You have successfully written, deployed, and interacted with a smart contract! Now let's interact with this same contract, but now using another language - **Python**. ### Python #### 1. Install Dependencies * `web3` - Allows the interaction with EVM deployed contracts in Python. * `python-dotenv` - Provides basic protection for secret variables. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install web3 pip install python-dotenv ``` #### 2. Create Python File * Inside the `scripts/` directory of your project, create a new file named `interact.py` * Add the following script to interact with your deployed contract: ```py Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import json import os from web3 import Web3 from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() # Access environment variables RPC_URL = os.getenv('RPC_URL') PRIVATE_KEY = os.getenv('PRIVATE_KEY') CONTRACT_ADDRESS = "" # Your deployed contract address ABI = './artifacts/contracts/SimpleStorage.sol/SimpleStorage.json' # Path to Hardhat artifact JSON file def write(web3, contract, my_number): # Account and transaction details from_account = web3.eth.account.from_key(PRIVATE_KEY) # object representation of your account to sign transactions chain_id = web3.eth.chain_id # rpc_url chain id that is connected to web3 gas_price = web3.eth.gas_price # get current gas price from the connected network nonce = web3.eth.get_transaction_count(from_account.address) # obtain nonce from your account address # Obtain the estimated gas based on the expected transaction and your wallet address tx = contract.functions.set(my_number) estimated_gas = tx.estimate_gas({'from': from_account.address}) # Build transaction object tx = tx.build_transaction({ 'chainId': chain_id, 'gas': estimated_gas, 'gasPrice': gas_price, 'nonce': nonce }) # Sign the transaction from your account signed_tx = from_account.sign_transaction(tx) # Send the transaction tx_receipt = web3.eth.send_raw_transaction(signed_tx.rawTransaction) tx_hash = web3.to_hex(tx_receipt) # Wait for it to be added on chain receipt = web3.eth.wait_for_transaction_receipt(tx_hash) # Uncomment below to see completed transaction receipt # print("Receipt: ", receipt) def main(): # Create a Web3 connection instance web3 = Web3(Web3.HTTPProvider(RPC_URL)) # Load JSON to get ABI with open(ABI) as file: contract_json = json.load(file) contract_abi = contract_json['abi'] # Use contract address and abi to obtain an instance of the network deployed contract contract = web3.eth.contract(address=CONTRACT_ADDRESS, abi=contract_abi) # Write function is more complex as we have to build the transaction. # Put into a separate function my_number = 10 write(web3, contract, my_number) # Perform a simple read greeting = contract.functions.set().call() print(greeting) # change it again my_number2 = 20 write(web3, contract, my_number2) # Observe how the number has been changed print(contract.functions.set().call()) main() ``` #### 3. Execute the Interaction Script * To execute the code above you can use the following script: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} python scripts/interact.py ``` After doing so, the code inside the Python file interacts with the SimpleStorage contract you made earlier! Now you understand how to get a smart contract on chain and interact with it. However, there are ways to create **upgradeable smart contracts** which are talked about in the next section. # Upgradable Smart Contracts Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/smart-contracts/upgradable-smart-contracts Smart contracts are immutable by nature, meaning their code cannot be altered after deployment. While this immutability ensures security and trust in a blockchain, it also presents challenges when you need to fix bugs, add new features, or adapt to evolving regulations. **Upgradable smart contracts** address these challenges by allowing developers to update the logic of a deployed contract without affecting its state or address. This guide will show you how to build and deploy upgradable smart contracts using **OpenZeppelin's Upgrades Plugin** and **Hardhat** for deployment. ## Prerequisites * You have followed the previous tutorials and are confident on learning about new contract types. * You have an understanding on how to deploy contracts using Hardhat. * JavaScript programming language experience. * Understanding about upgradable smart contracts. ## Why Upgradable Smart Contracts? Traditional smart contracts are inherently limited in flexibility. Once deployed, they are **immutable** pieces of code residing on the blockchain. Only the state of the contract can change while the code itself remains fixed. This means that if bugs are discovered or if improvements are needed, you're left without an option to update the contract. Upgradable smart contracts address this limitation by offering **flexibility** and **adaptability** throughout the contract's lifecycle. They allow for seamless bug fixes and security patches on a deployed contract, helping to mitigate vulnerabilities and address security concerns. In an ever-evolving blockchain environment—where best practices and regulatory standards are **continuously being redefined**—using upgradable smart contracts enables you to update your code to comply with new regulations, ultimately facilitating broader adoption. ## Instructions ### Building an Upgradable Smart Contract For the creation of the upgradable smart contract we will be using **OpenZeppelin's** `deployProxy` plugin. #### 1. Install upgrades plugin * After creating a Hardhat environment you will need to download the **OpenZeppelin** package that contains the upgrade logic. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install --save-dev @openzeppelin/hardhat-upgrades ``` * Next you will need to add the following import in the `hardhat.config.js` to configure Hardhat to use this package ```jsx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // hardhat.config.js ... require('@openzeppelin/hardhat-upgrades'); ... module.exports = { ... }; ``` #### 2. Create the contract Now we need to create the contract that will be upgradable. For simplicity sake, let us again use the `SimpleStorage.sol` contract from before. Please place this file in the `/contracts` directory of your project. The code is as follows: ```Solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorage { uint256 private data; // Emitted when the stored value changes event ValueChanged(uint256 _data); function set(uint256 _data) public { data = _data; emit ValueChanged(_data); } function get() public view returns (uint256) { return data; } } ``` Please note how we added an event which will be helpful later. #### 3. Write the deployment script Next, we need to deploy the contract as upgradable. This uses a bit different logic so please make sure to take special note on the code below. Rather than putting the deployment script in the *ignition/modules/* directory path, we place it in the *scripts/*. * Create a new file in the `/scripts` directory called `upgrade_storage.js`. * Paste the following code in the file: ```jsx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // scripts/upgrade_storage.js const { ethers, upgrades } = require('hardhat'); async function main () { const SimpleStorage = await ethers.getContractFactory('SimpleStorage'); console.log('Deploying SimpleStorage...'); const simple_storage = await upgrades.deployProxy(SimpleStorage, [10], { initializer: 'store' }); await simple_storage.waitForDeployment(); console.log('SimpleStorage deployed to:', await simple_storage.getAddress()); } main(); ``` #### 4. Deploy the contract * Run the following cmd to deploy the contract. For the upgradable contract we use the `npx hardhat run` cmd instead of the `ignition deploy`. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx hardhat run --network agung scripts/upgrade_storage.js ``` * After successfully doing so, the deployed contract address will be displayed. Save this value so you can access the contract in the future. #### 5. Interact the contract * **Hardhat console** will be used to interact with the smart contract we have deployed. To launch the console you can run the cmd: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx hardhat console --network agung ``` * A new terminal will open up where you can execute the following the check the deployment: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} Welcome to Node.js v22.4.1. Type ".help" for more information. > const SimpleStorage = await ethers.getContractFactory('SimpleStorage') undefined > const simple_storage = await SimpleStorage.attach('0xd9b5c9Abb57175C2f4B1fE644ED0c24bF4c3c49D'); undefined > (await simple_storage.get()).toString(); '10' ``` * Confirms that the contract has been properly stored an initialized at `10`. ### Upgrade previously deployed contract Let us say down the line, we want to upgrade this smart contract to have a new feature - increment the stored value. #### 1. Create a new contract * Create a new Solidity contract in the `contracts/` directory with the name `SimpleStorageV2` ```Solidity theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorageV2 { uint256 private data; // Emitted when the stored value changes event ValueChanged(uint256 _data); function set(uint256 _data) public { data = _data; emit ValueChanged(_data); } function get() public view returns (uint256) { return data; } // new function added function increment() public { data = data + 1; emit ValueChanged(data); } } ``` Take special note how to contract is exactly the same, except for the name of the contract and the new increment function that was added. #### 2. Upgrade previously deployed contract Use the `updateProxy` function from **OpenZeppelin** to update the contract, * Create a new file at `scripts/` called `upgrade_storagev2`. Use the following code: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // scripts/upgrade_box.js const { ethers, upgrades } = require('hardhat'); async function main () { const SimpleStorageV2 = await ethers.getContractFactory('SimpleStorageV2'); console.log('Upgrading SimpleStorage...'); await upgrades.upgradeProxy('0xd9b5c9Abb57175C2f4B1fE644ED0c24bF4c3c49D', SimpleStorageV2); console.log('SimpleStorage upgraded'); } main(); ``` #### 3. Deploy upgradable contract Again use the `run` cmd to upgrade the contract from the set network. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} $ npx hardhat run --network agung scripts/upgrade_storagev2.js Upgrading SimpleStorage... SimpleStorage upgraded ``` The above will be displayed on a successful upgrade. The code have been updated to the latest version, while maintaining the same address and state from before. #### 4. Interact with the contract Again, let us use the Hardhat console to interact with the contract. Notice how we are able to call `increment()` that we added in our update. Launch the console: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx hardhat console --network agung ``` Terminal will open and execute the following: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} Welcome to Node.js v22.4.1. Type ".help" for more information. > const SimpleStorageV2 = await ethers.getContractFactory('SimpleStorageV2'); undefined > const simple_storage = await SimpleStorageV2.attach('0xd9b5c9Abb57175C2f4B1fE644ED0c24bF4c3c49D'); undefined > await simple_storage.increment(); ... > (await simple_storage.get()).toString(); '11' ``` 🎉 Congratulations! You have created a smart contract and successfully **upgraded** it. Notice how the address and the stayed stayed the same, until the increment() function was called. Now you have more flexibility when dealing with your smart contracts. If you would like to learn more about how these updates work, please checkout the [OpenZeppelin documentation](https://docs.openzeppelin.com/learn/upgrading-smart-contracts#how-upgrades-work). # Verify Smart Contract Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/smart-contracts/verify-smart-contract In this section, we will go over the process of verifying a smart contract on peaq network using **Sourcify** and **Blockscout**, as well as **Subscan**. Verifying your smart contract allows for readable and interpretable data (such as function calls and parameters) to be displayed **directly** in block explorers, making it easier to understand and debug interactions with the contract. ## Prerequisites * Familiar with **Block Explorers**. If not checkout the [Block Explorers](/peaqchain/build/basic-operations/block-explorers) section. * You have previously deployed a smart contract using one of the previous tutorials (e.g. `SimpleStorage.sol`). ## Method 1: Sourcify Verification **Sourcify** is a decentralized contract verification service that provides transparent and immutable verification for smart contracts. This is the recommended method for contract verification on peaq mainnet. ### 1. Navigate to Sourcify Go to [sourcify.dev](https://sourcify.dev/) and click **"Verify"**. ### 2. Select Network * From the network dropdown, select **"peaq"** (Chain ID: 3338) * Ensure you're verifying on the correct network where your contract is deployed ### 3. Enter Contract Information * **Contract Address**: Enter your deployed contract address * Sourcify will automatically detect if the contract is already verified * If unverified, you'll proceed to the verification form ### 4. Upload Contract Files Choose your preferred verification method: #### Option A: Upload Source Files * **Drag and drop** or **browse** to upload your main Solidity file (e.g., `SimpleStorage.sol`) * Upload any **imported dependencies** or libraries * Maintain the **correct folder structure** as used during compilation * Include any **configuration files** (e.g., `hardhat.config.js`, `foundry.toml`) #### Option B: Upload Metadata JSON * **Hardhat users**: Upload the complete metadata from `artifacts/contracts/YourContract.sol/YourContract.json` * **Foundry users**: Upload the metadata from `out/YourContract.sol/YourContract.json` * **Remix users**: Download and upload the metadata JSON from the artifacts folder ### 5. Verify Compilation Settings Sourcify will automatically extract and verify: * **Compiler Version**: Must match the version used during compilation * **EVM Version**: Should be set to `london` (required for peaq network) * **Optimization Settings**: Enabled/disabled status and number of runs * **Source Maps**: For debugging and verification accuracy ### 6. Submit for Verification * Review the **contract details** and **compilation metadata** * Click **"Verify"** to submit your contract for verification * Sourcify will compile the contract and compare bytecode * Wait for the verification process to complete (usually takes 1-2 minutes) ### 7. Verification Results Upon successful verification: #### Perfect Match * Your contract achieves **"Perfect Match"** status * Source code is **permanently stored** on IPFS * Contract is **automatically indexed** by Sourcify * Verification data is **accessible across all Sourcify-compatible explorers** #### Partial Match * If you get a **"Partial Match"**, the core logic matches but metadata differs * Check compilation settings, especially optimization and EVM version * Consider re-verifying with exact compilation parameters ### 8. View Verified Contract After verification: * Visit the **Sourcify repository** to view your contract's source code * Check **block explorers** like [scout.peaq.xyz](https://scout.peaq.xyz/) - your contract will now show as verified * Access the **contract's IPFS hash** for permanent source code storage * Use the **contract ABI** for frontend integration ## Method 2: Subscan Verification (Alternative) If you prefer to use Subscan for verification, follow these steps: ### 1. Prepare Metadata from Remix We will be using the same [Remix IDE](https://remix.ethereum.org/) workspace that was used to deploy the `SimpleStorage.sol` contract. Once you are there: * Open up the `artifacts/build-info/hex_value` file. This contains the content we need to move to the `SimpleStorage_metadata.json` file. verify-smart-contract-1 * Copy and paste the entire `content` line as shown in the screenshot above. ### 2. Paste content into SimpleStorage\_metadata After copying this content we will need to paste it into the `SimpleStorage_metadata.json` file. * Navigate to the `SimpleStorage_metadata.json` file. * Find the `sources` object where the data for contract `SimpleStorage.sol` is stored. * Paste the content after the `license` field. verify-smart-contract-2 ### 3. Download the SimpleStorage\_metadata.json file The next step is to simply download this file to your local machine. It will be used later in the verification process. ### 4. Navigate to Subscan Now go to [peaq.subscan.io](https://peaq.subscan.io/) or [agung-testnet.subscan.io](https://agung-testnet.subscan.io/) and search for your deployed contract address. Then go to the `Contract` tab. ### 5. Verify Contract * Confirm the `Contract Address` field matched your Smart Contract to verify. * Select **Solidity (Standard-JSON-Input)** as `Compiler Type`. * Pick whether or not you would like to `Include Nightly Builds`. * Choose the `Compiler Version` that was used when you compiled your smart contract. * Upload the file that we downloaded from Remix earlier. * Finally, click the `Verify & Publish` button. verify-smart-contract-3 ### 6. Confirm Verification A proper Contract Verification will have a ✅ next to **Contract**. verify-smart-contract-4 #### Contract Information Displayed * **Contract Name**: `SimpleStorage` * **Compiler Version**: `v0.8.26+commit.8a97fa7a` * **EVM Version**: `cancun` * **Optimization**: `false` This indicates the deployed contract was compiled using Solidity version `0.8.26`, with the Ethereum Virtual Machine (EVM) set to `cancun`, and without optimization enabled. #### Contract ABI * The **ABI** (Application Binary Interface) specifies the interface of the contract. * It includes the function details such as: * **Function `get`**: * Inputs: None. * Outputs: A single `uint256` value. * Mutability: `view` (read-only). * **Function `set`**: * Inputs: A single `uint256` value (`_data`). * Outputs: None. * Mutability: `nonpayable` (modifies the state). This confirms the contract has two primary functions: * `set(uint256 _data)`: Stores a value. * `get()`: Retrieves the stored value. #### Contract Source Code The source code of the contract is displayed as it was written: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} solidity Copy code // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorage { uint256 private data; function set(uint256 _data) public { data = _data; } function get() public view returns (uint256) { return data; } } ``` * **Logic**: * `set`: Allows the user to save a `uint256` value in the contract's state. * `get`: Returns the saved value without modifying the state. #### Contract Bytecode * The **bytecode** is the compiled version of the Solidity source code. * It is what gets deployed to the Ethereum blockchain. * This bytecode matches the source code provided in the screenshot. ## Summary ### Recommended Approach For peaq mainnet, use **Sourcify verification** ([sourcify.dev](https://sourcify.dev/)) as it provides: * **Decentralized verification** with IPFS storage * **Immutable source code** preservation * **Cross-explorer compatibility** (automatically appears on scout.peaq.xyz and other Sourcify-compatible explorers) * **Perfect Match** and **Partial Match** verification levels ### Alternative Method Subscan verification remains available as an alternative, particularly useful for detailed transaction analysis and historical data exploration. By completing either verification process, your contract will be displayed with all relevant details, ensuring transparency and enabling easier interaction with your deployed smart contract on the peaq network. # Verify Transaction Status Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/smart-contracts/verify-transaction-status After gaining an understanding of how smart contracts work and how to use blockchain explorers, verifying the **status of transactions** from these contracts becomes a straightforward process. In this section, we will focus on using the `SimpleStorage.sol` smart contract, which was created in the section: [Deploy Your First Smart Contract](/peaqchain/build/basic-operations/smart-contracts/build-smart-contract). We will use the **Subscan Explorer**, introduced in the section [Block Explorers](/peaqchain/build/basic-operations/block-explorers) to **track** and **verify** the **transactions** associated with deploying and interacting with the smart contract. This will include confirming the deployment, verifying function calls, and ensuring data was successfully stored and retrieved on-chain. ## Get Deployed Transaction hash From the contract you deployed at [Deploy Smart Contract](/peaqchain/build/basic-operations/smart-contracts/deploy-smart-contract) obtain the contract address that was deployed. We will use this to see if the operations that executed on the smart contract level have been correctly recorded on the online ledger. ## Using Subscan to Verify Smart Contract Transactions ### 1. Access Subscan Access the Subscan endpoint for the network you deployed your smart contract: | **Network** | **Subscan Link** | | ----------- | ---------------------------------------------------------------------- | | peaq | [https://peaq.subscan.io/](https://peaq.subscan.io/) | | agung | [https://agung-testnet.subscan.io/](https://agung-testnet.subscan.io/) | ### 2. Confirming Deployment Paste your deployed smart contract in the search bar. If deployed correctly it will route you to the contract address page. ### 3. Verifying function calls Use Remix, the deployment script you created, or Hardhat console to **interact** with your smart contract. **Verify** the operations that you performed were recorded in Subscan. The following case is shown below: * User executes a deployment script which performs a **write** transaction on the network. * Wait for Subscan to be **updated** to show the smart contract transaction. * Transaction appears in the EVM Transactions table. Click the arrow to ensure the data was stored. verify-transaction-status-1 ### 4. Ensure data was successfully stored After clicking the arrow on the above transaction it will redirect you to information about the transaction. Similar to the following: verify-transaction-status-2 #### Transaction Overview * **Status:** ✅ Confirmed * The transaction has been successfully processed and included in a block on the blockchain. * **EVM Txhash:** `0xa540d982649de379fa083867a96ab4eb85fc90718dfb92af3beb29c5a26597c2` * Unique identifier (hash) for the transaction. Can be used to locate and reference this transaction across blockchain explorers. * **From:** `0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C` * Sender's Ethereum-compatible account address that initiated the transaction. * **To:** `0x12e27eabb913acf6c0D109a062ba1e266bFd8CcF` * Smart contract address that executed the function call. * **Value:** `0 PEAQ` * No PEAQ tokens were transferred as part of this transaction. * **Result:** ✅ Success * The transaction was successfully executed without errors. * **Nonce:** `851` * Number of transactions sent from the sender's address (`From`). * **Txn Fee:** `0.000000000044744704 PEAQ` * The transaction fee paid for processing this transaction on the blockchain. * **Input Data:** `set` `_data` `10` * Information about the function call executed. ## Summary Verifying the status of transactions from deployed smart contracts is an essential step in ensuring that your operations are successfully recorded on the blockchain. By using Subscan Explorer, you can confirm the **deployment** of your smart contract, **track** its interactions, and **validate** the success of each transaction. The **detailed** transaction overview, including the status, transaction hash, gas fees, and input data, provides **transparency** and **accountability** for your on-chain activities. # Submitting Transactions Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/submitting-transactions Submitting transactions is a core task when interacting with blockchains, like peaq. Transactions include **transferring funds**, **interacting with smart contracts**, or **deploying contracts**. A transaction is required each time we want to **write data** to the blockchain and it's the responsibility of the developer to construct the transaction call data. This guide provides a structured approach to submitting transactions using `ethers.js`, ensuring efficiency and security. ## Prerequisites Before following the instructions, ensure the following: 1. **Environment Setup**: * **Node.js** and **npm** (or pnpm/yarn) are installed. * Ethers.js library is installed: `npm install ethers`. 2. **Account Setup**: * You have access to a private key or a connected **wallet** (e.g., MetaMask). * Your wallet has sufficient funds for **gas** fees. 3. **Blockchain Setup**: * You know the URL of the RPC you're interacting with. * You understand the transaction details, including recipient address, value, and any data (if applicable). 4. **Security Awareness**: * Never expose your private key or mnemonic in your code. * Use environment variables for sensitive information. ## Instructions ### 1. Install and Import ethers.js In your JavaScript file, import ethers.js: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from 'ethers'; ``` ### 2. Set Up a Provider Connect to an Ethereum-compatible network: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const provider = new ethers.JsonRpcProvider(HTTPS_URL); ``` ### 3. Configure a Signer Create a wallet instance to sign transactions: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const privateKey = process.env.PRIVATE_KEY; //Please store private keys in environment variables const wallet = new ethers.Wallet(privateKey, provider); ``` ### 4. Define the Transaction Set up the transaction details: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const tx = { to: "0xRecipientAddressHere", // Replace with the recipient's address value: ethers.parseEther("0.1"), // Amount in ETH to send (converted from Ether) gasLimit: 21000, // Gas limit (for simple transfers) maxFeePerGas: ethers.parseUnits("20", "gwei"), // Max fee per gas maxPriorityFeePerGas: ethers.parseUnits("2", "gwei"), // Priority fee }; ``` For interacting with smart contracts, include `data` in the transaction object. ### 5. Sign and Submit the Transaction Sign and send the transaction: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function sendTransaction() { try { const txResponse = await wallet.sendTransaction(tx); console.log("Transaction submitted:", txResponse.hash); const receipt = await txResponse.wait(); console.log("Transaction mined:", receipt); } catch (error) { console.error("Error submitting transaction:", error); } } sendTransaction(); ``` ### 6. Verify the Transaction Monitor the transaction using the `txResponse.hash` in a block explorer or programmatically check its status using the `provider.getTransactionReceipt` method. ## Notes * **Gas Estimation**: For more complex transactions, calculate gas estimates using `contract.estimateGas.methodName()`. * **Smart Contracts**: For interacting with contracts, set up the contract instance using its ABI and address: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const contract = new ethers.Contract(contractAddress, contractABI, wallet); ``` ## Putting it all Together Here's a complete boilerplate script that ties together all the steps for submitting a transaction using `ethers.js`. This script includes environment variable handling, transaction setup, and submission. ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // Import ethers.js import { ethers } from 'ethers'; require("dotenv").config(); // To use environment variables // Boilerplate Script async function main() { try { // Step 1: Set up the provider const rpcUrl = process.env.RPC_URL; // RPC URL for your blockchain if (!rpcUrl) throw new Error("RPC_URL is not defined in .env"); const provider = new ethers.JsonRpcProvider(rpcUrl); // Step 2: Configure the wallet (signer) const privateKey = process.env.PRIVATE_KEY; // Private key stored in environment variables if (!privateKey) throw new Error("PRIVATE_KEY is not defined in .env"); const wallet = new ethers.Wallet(privateKey, provider); console.log(`Connected wallet: ${wallet.address}`); // Step 3: Define the transaction const tx = { to: "0xRecipientAddressHere", // Replace with the recipient's address value: ethers.parseEther("0.1"), // Amount in ETH to send (e.g., 0.1 ETH) gasLimit: 21000, // Standard gas limit for a simple ETH transfer maxFeePerGas: ethers.parseUnits("20", "gwei"), // Max fee per gas maxPriorityFeePerGas: ethers.parseUnits("2", "gwei"), // Priority fee }; console.log("Prepared transaction:", tx); // Step 4: Sign and send the transaction console.log("Sending transaction..."); const txResponse = await wallet.sendTransaction(tx); console.log(`Transaction submitted: ${txResponse.hash}`); // Step 5: Wait for transaction to be mined console.log("Waiting for transaction to be mined..."); const receipt = await txResponse.wait(); console.log("Transaction mined:", receipt); console.log(`Transaction successful! Block Number: ${receipt.blockNumber}`); } catch (error) { console.error("Error occurred:", error.message); } } // Execute the script main(); ``` ### Instructions for Using the Boilerplate Script 1. **Install Dependencies**: Ensure you have the required packages installed: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install ethers dotenv ``` 2. **Set ESM Module:** Add the following to your `package.json` to alllow for ESM modules. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} "type": "module", ``` 3. **Set Up Environment Variables**: Create a `.env` file in the root directory of your project with the following keys: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} RPC_URL=https://quicknode.peaq.xyz PRIVATE_KEY=your-private-key ``` 4. **Run the Script**: Execute the script in your terminal: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} node script.js ``` 5. **Modify Transaction Details**: * Replace `0xRecipientAddressHere` with the recipient's address. * Adjust `value`, `maxFeePerGas`, and `maxPriorityFeePerGas` as needed. # Fireblocks Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/wallets/fireblocks **Type:** Custodian Wallet 1. Go to: [https://console.fireblocks.io/v2/welcome/login](https://console.fireblocks.io/v2/welcome/login) 2. Login 3. Go to Vaults 4. Select the vault you wanna create the peaq wallet address in 5. Click create wallet 6. Search “PEAQ” 7. Click “Create Wallet” # Ledger Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/wallets/ledger **Type:** Hardware Wallet ## Instructions: 1. Set up your Ledger device and install Ledger Live on your computer: [https://support.ledger.com/article/4404389503889-zd](https://support.ledger.com/article/4404389503889-zd). 2. Connect your Ledger Device to your computer and install peaq app on your ledger device through Ledger Live: [https://support.ledger.com/article/4404382258961-zd](https://support.ledger.com/article/4404382258961-zd). 3. Set up Metamask wallet and add peaq network to it by following this instruction: [Create Metamask wallet and add peaq mainnet.](/peaqchain/build/getting-started/get-test-tokens#create-evm-wallet) 4. Go to Metamask and click on the down arrow next to your account name at the top of the screen: ledger-peaq-1 5. Click **Add account or hardware wallet** ledger-peaq-2 6. Next, click **Add hardware wallet** ledger-peaq-3 7. Now, go back to your connected Ledger device, unlock it, and select peaq app ledger-peaq-4 8. Once selected click the right button until you see **Open application**, then click both buttons on the device ledger-peaq-5 9. Click right button until you see **Expert mode: disabled** ledger-peaq-6 10. Click both buttons to enable Expert mode, this is how your screen will look like after a successful change ledger-peaq-7 11. Now go back to your Metamask window, select Ledger, and then click **Continue** ledger-peaq-8 12. Next, select your Ledger device in the pop-up window and click **Connect** ledger-peaq-9 13. After that you will see this screen, we’ve previously added Ledger accounts to this Metamask, that's why we have non-zero balances in accounts 1 & 2. In your case, all accounts should display a 0 balance. Select any account and click **Unlock** (we've selected account 3). ledger-peaq-10 ## Receive \$PEAQ tokens with Ledger + MetaMask 1. This is your Ledger account, which we just created. Now we will copy the address, to send some \$PEAQ tokens to it. ledger-peaq-11 2. Once tokens are received you'll see them in the interface ledger-peaq-12 ## Send \$PEAQ tokens with Ledger + MetaMask 1. Now, let's try to send some PEAQ to another address, click **Send** ledger-peaq-13 2. Paste the address you would like to send $PEAQ tokens to and enter the amount in $PEAQ, then click **Next** ledger-peaq-14 3. Double-check transaction details and click **Confirm** ledger-peaq-15 4. This is the screen you’ll see next ledger-peaq-16 5. Verify and send the transaction on your Ledger (double-check all of the transaction details on your Ledger device screen and click Accept and send by clicking both buttons) # SAFE Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/wallets/safe **Type:** MultiSig Wallet ## Create new Safe Account 1. Go to [https://safe.peaq.xyz/welcome](https://safe.peaq.xyz/welcome), Click **Connect wallet** safe-peaq-1 2. Choose your wallet safe-peaq-2 3. For tutorial purposes we will be using Metamask 4. Make sure peaq is selected as a network and you’re using the correct account safe-peaq-3 5. Press **Continue with MetaMask** safe-peaq-4 6. Make sure peaq is selected as a network and choose the name for your Safe safe-peaq-5 7. Add additional owners if needed. For each additional owner: * Click Add new owner * Give the Owner a Name * Enter Owner's Address * Select how many owners will be required to confirm a transaction * Press Next safe-peaq-6 8\. Click **Create** and confirm the transaction. You will need small amount of \$PEAQ to sign the tx. safe-peaq-7 ## Receive \$PEAQ tokens with SAFE 1. Copy your Safe address on peaq safe-peaq-8 2. Send \$PEAQ tokens from your Metamask or any other EVM wallet to this address safe-peaq-9 ## Send \$PEAQ tokens with SAFE 1. To send PEAQ, click **Send** safe-peaq-10 2. Enter the recipient and the amount, then click **Next** safe-peaq-11 3. Review details of your transaction and click **Execute** safe-peaq-12 4. After clicking submit you will have to sign the transaction with your signer wallet. Depending on the threshold policy used, the transaction will either be executed immediately or require additional signatures from other signers of the Safe 5. This is what you’ll see once transaction is signed by every signer needed safe-peaq-13 # Checking Balances Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/working-with-wallet-addresses/checking-balances The following explains how to retrieve wallet balances on the peaq network using `ethers.js`. Developers can often reuse ethers-based approaches with the right configurations. If direct on-chain queries are difficult, we'll also explore how to fall back to `peaq.subscan.io`, a web-based explorer that allows you to look up balances via an HTTPS GUI. ## Prerequisites * Basic understanding of JavaScript and Node.js. * You have `ethers.js` installed and know how to initialize a provider. * Possess a target wallet address for which you want to check the balance. * Have access to a running peaq node's RPC endpoint or a compatible provider. * Understand how to use peaq.subscan.io as a fallback. ## Instructions ### 1. Set Up Your Environment: Ensure you have `node` and `npm` installed, then run: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install ethers ``` ### 2. Initialize the Provider: In your JavaScript file (e.g., `checkBalance.js`), import **ethers** and instantiate a provider. Point it to the RPC endpoint of the peaq network you're connecting to (this might be a testnet or mainnet endpoint—adjust accordingly): ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from "ethers"; // Recommended / Example endpoint: replace with your chosen peaq RPC URL const peaqProvider = new ethers.JsonRpcProvider("https://quicknode.peaq.xyz"); ``` ### 3. **Fetch the Wallet Balance**: Use the provider's `getBalance` method, passing in the wallet address. The address should be an Ethereum-compatible format (e.g., 0x...): ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function getPeaqBalance(address) { try { const balanceBigInt = await peaqProvider.getBalance(address); const balanceEth = ethers.formatEther(balanceBigInt); console.log(`Balance of ${address}: ${balanceEth} PEAQ`); } catch (error) { console.error('Error fetching balance:', error); console.log('Falling back to peaq.subscan.io...'); } } // Example usage: const myAddress = "0xYourPeaqAddress"; getPeaqBalance(myAddress); ``` ### 4. Fallback to peaq.subscan.io If the direct query via ethers fails (due to network incompatibilities at the time of querying), you can manually check the balance using the peaq Subscan explorer: * Navigate to: [peaq Subscan](https://peaq.subscan.io/) (mainnet) , [agung Subscan](https://agung-testnet.subscan.io) (testnet) * Enter the **wallet address** into the search bar. * Press "Enter" or click the search icon. * View the displayed account information and check the reported **balance**. ### 5. Interpreting the Results Whether you retrieved the balance programmatically via `ethers.js` or via `peaq.subscan.io`, you'll see the current token holdings associated with that address. Make sure you understand the units (often the smallest unit might be displayed, and you may need to convert them to a more human-readable format). ## Summary Checking **wallet balances** on the peaq network can be attempted through `ethers.js` if the network is EVM-compatible and you have the correct provider endpoint. In the event of issues, the `peaq.subscan.io` explorer provides a simple GUI fallback. By combining these approaches, developers and users can confidently monitor and verify the holdings in their peaq wallets. # Create and Manage Wallet Addresses Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/working-with-wallet-addresses/create-and-manage This guide is intended for developers who need to dynamically generate and manage **cryptocurrency wallet addresses** using the `ethers.js` library. Whether you are developing a decentralized application (dApp), implementing a user management system that provisions wallets for new sign-ups, or integrating with a service such as the Particle Network, this guide will demonstrate how to securely and efficiently **create** and **manage** **EVM-compatible** wallets within your JavaScript or TypeScript code. It outlines the prerequisites, provides detailed step-by-step instructions, and concludes with best practices for sustainable wallet management. ## Prerequisites * **Basic JavaScript/TypeScript knowledge:** You should know how to write and run JavaScript code in a Node.js environment. * **Familiarity with Node.js tooling:** You know how to install NPM packages and understand basic module imports. * **Ethers.js installed:** You have ethers.js available as a dependency in your project. * **Particle Network or similar integration:** If you're using Particle Network or another platform, you should have access to their SDK or integration documentation. This guide assumes you know how to authenticate and interact with that platform's services. * **Secure storage setup:** You have a strategy for securely storing private keys, such as an environment variable manager, a hardware security module (HSM), or a dedicated secret management service. ## Instructions ### 1. Generating a New Wallet Address `ethers.js` makes it easy to generate a new wallet address with a corresponding private key. The most straightforward method is to create a random wallet: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Wallet } from "ethers"; // Create a new random wallet const wallet = Wallet.createRandom(); console.log("New Wallet Address:", wallet.address); console.log("Wallet Private Key:", wallet.privateKey); ``` **Key points:** * `wallet.address` provides the **public** address. * `wallet.privateKey` is extremely sensitive and should be stored **securely**. * By default, `Wallet.createRandom()` uses a secure RNG to generate entropy. ### 2. Encrypting and Storing Wallets When managing a user's wallet, treat the private key as a secret that must be kept safe. `ethers.js` can **encrypt** wallets with a password: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} (async () => { const password = "superstrongpassword"; const encryptedJson = await wallet.encrypt(password); // Store this encrypted JSON in a secure database or secure storage solution console.log("Encrypted Wallet JSON:", encryptedJson); })(); ``` **Key points:** * The `encrypt` method returns a JSON Keystore V3 object. * This object can be safely stored and later **decrypted** using the password. * Consider integrating secure storage solutions such as AWS KMS, GCP KMS, or Vault for enterprise-level security. ### 3. Restoring an Existing Wallet To restore a wallet from an encrypted JSON, decrypt it with the **password**: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} (async () => { const password = "superstrongpassword"; const encryptedJson = "THE ENCRYPTED JSON FROM YOUR STORAGE"; const restoredWallet = await Wallet.fromEncryptedJson(encryptedJson, password); console.log("Restored Wallet Address:", restoredWallet.address); })(); ``` **Key points:** * The `fromEncryptedJson` method returns a fully functional Wallet instance. * Use this method whenever you need to perform transactions, sign messages, or display the user's address. ### 4. Integrating with Particle Network or Other Services If you are managing multiple user wallets and need to integrate with services like Particle Network for authentication or transaction relays, consider: * **User Authentication:** Leverage Particle Network's SDK to authenticate users and link their sessions to a particular wallet. * **Dynamic Wallet Creation:** On user registration or login, generate a new wallet on the fly and store the encrypted JSON associated with that user's account in your secure database. * **Transaction Management:** Use ethers.js methods (`wallet.sendTransaction`, `wallet.signTransaction`) to handle transactions programmatically. Particle Network or similar services may offer APIs to help broadcast these transactions or manage gas payments. Example snippet for integration logic (pseudo-code): ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function handleNewUserRegistration(userId, particleInstance) { // Authenticate user via Particle Network (pseudo-code) const userSession = await particleInstance.authenticateUser(userId); // Generate a new wallet const wallet = Wallet.createRandom(); // Encrypt and store it const encryptedJson = await wallet.encrypt(process.env.WALLET_ENCRYPTION_PASSWORD); await storeEncryptedWalletForUser(userId, encryptedJson); // Associate wallet address with user session in Particle Network await particleInstance.registerWalletAddress(userSession, wallet.address); } ``` **Key points:** * Always handle private keys and encrypted data within **secure** and **trusted** environments. * Consider **role-based access controls** (RBAC) and never expose raw private keys to the client side. ## Summary Creating and managing EVM-type wallet addresses with `ethers.js` is straightforward once you understand the building blocks: **generating a wallet**, **encrypting and storing keys**, and **restoring wallets** for future transactions. By combining these practices with secure storage solutions and optional integrations with services like Particle Network, you can confidently manage a dynamic userbase and ensure their funds remain secure. In the long run, following best practices for encryption, secure storage, and careful integration with authentication services will help you maintain trust, reliability, and seamless user experiences in your decentralized applications. # Multi-Sig Source: https://docs.peaq.xyz/peaqchain/build/basic-operations/working-with-wallet-addresses/multi-sig **Multi-signature** (multi-sig) wallets provide an added layer of security and decentralization by requiring **multiple approvals** for transactions. [Gnosis Safe](https://docs.safe.global/sdk/overview) is a widely used and trusted solution for implementing multi-sig wallets on EVM-compatible blockchains like the peaq network. This guide walks you through **creating a multi-sig wallet programmatically** using Gnosis Safe contracts and demonstrates some basic functions such as adding owners and executing transactions. ## Prerequisites * Basic knowledge of blockchain development and JavaScript/TypeScript. * Familiar with interacting with smart contracts using `ethers.js`. * Have access to a deployed instance of Gnosis Safe contracts on the peaq network or are able to deploy them yourself. * Have an RPC URL for the peaq network. * Have private keys for multiple signers to interact with the wallet. ### Instructions ### 1. Set Up Your Environment * Install the necessary dependencies: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install ethers @gnosis.pm/safe-core-sdk @gnosis.pm/safe-ethers-lib dotenv ``` * Set ESM Module: Add the following to your `package.json` to alllow for ESM modules. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} "type": "module", ``` * Configure a `.env` file with the following variables: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} RPC_URL=https://quicknode.peaq.xyz PRIVATE_KEY=your_private_key_here ``` ### 2. Connect to the Peaq Network Import dependencies and initialize the provider and signer: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers } from 'ethers'; import Safe from '@gnosis.pm/safe-core-sdk'; import SafeEthersLib from '@gnosis.pm/safe-ethers-lib'; import dotenv from 'dotenv'; dotenv.config(); const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); ``` ### 3. Deploy a New Multi-Sig Wallet Use the Gnosis Safe SDK to create a new multi-sig wallet: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const createSafe = async () => { const safeFactory = await Safe.SafeFactory.create({ ethAdapter: new SafeEthersLib.EthersAdapter({ ethers, signer }), }); const owners = [ '0xAddress1', '0xAddress2', '0xAddress3' ]; const threshold = 2; // Number of approvals required const safeAccountConfig = { owners, threshold }; const safe = await safeFactory.deploySafe({ safeAccountConfig }); console.log(`Multi-sig wallet deployed at: ${safe.getAddress()}`); return safe; }; createSafe(); ``` ### 4. Add an Owner Propose a transaction to add a new owner: ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const addOwner = async (safeAddress, newOwner, safe) => { const safeTransaction = await safe.createAddOwnerTx({ ownerAddress: newOwner, threshold: 2, }); const txHash = await safe.executeTransaction(safeTransaction); console.log(`Transaction hash: ${txHash}`); }; addOwner(safe.getAddress(), '0xNewOwnerAddress', safe); ``` ### **5. Execute a Transaction** Propose and execute a basic transaction (e.g., sending PEAQ): ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const executeTransaction = async (safeAddress, recipient, amount) => { const safe = await Safe.default.create({ ethAdapter: new SafeEthersLib.EthersAdapter({ ethers, signer }), safeAddress, }); const safeTransactionData = { to: recipient, value: ethers.parseEther(amount), // Amount in PEAQ data: '0x', }; const safeTransaction = await safe.createTransaction({ safeTransactionData }); const txHash = await safe.executeTransaction(safeTransaction); console.log(`Transaction executed. Hash: ${txHash}`); }; executeTransaction(safe.getAddress(), '0xRecipientAddress', '0.1'); ``` ## Summary Using **Gnosis Safe contracts** on the peaq network allows you to leverage the security of multi-signature wallets for DePIN projects. This guide demonstrated how to programmatically create a wallet, manage owners, and execute transactions. With these basics, you can **secure** your **assets** and **operations** on the peaq network with confidence. Expand further by integrating Gnosis Safe with front-end interfaces or automated scripts for enhanced usability and functionality. # Deploy your DePIN Source: https://docs.peaq.xyz/peaqchain/build/first-depin/deploy-your-depin After your DePIN is fully integrated with the peaq network—complete with machine wallets, DIDs, and verifiable data flows—the next step is to determine how your users or devices will interact with the system in production. There are several common deployment models depending on your use case, each with its own strengths: * **Server Implementation** * **Mobile Application** * **Web Application** Below is a high-level overview of how each approach works and what makes it unique. These models can also be combined depending on your architecture. ## Server Implementation A **server-side deployment** is the most flexible and common approach for DePINs, especially when dealing with multiple machines, background processes, or integrations with legacy infrastructure. ### Key Characteristics * **Control** of DID creation, wallet generation, and on-chain transactions. * Ideal for **IoT networks**, **machine fleets**, or **backend-only services**. * Can be scheduled, event-driven, or triggered via APIs from other systems. ### Typical Use Cases * Creating a machine DID as soon as a new device is provisioned. * Running cron jobs or real-time services to monitor machine behavior and update DIDs. * Hosting APIs that allow frontends (web/mobile) to interact with blockchain logic securely. ### Tech Stack * Node.js, Python, or Go backend * Cloud infrastructure (AWS, GCP, Azure) * Secure key management (e.g. HSMs, Vault, KMS) *Recommended when you need maximum control and scalability across a large number of machines or services.* ## Mobile Application A **mobile-based DePIN** enables end-users to manage their machine identities or interact with the network directly from their smartphone. ### Key Characteristics * Local wallet generation and signing using **device secure storage** (e.g. Keychain, Keystore). * Direct integration with peaq DID SDK and peaq storage via lightweight APIs. * Enables machines controlled or **managed via phones** (e.g. mobility services, consumer DePINs). * Integration with Account Abstraction solutions. ### Typical Use Cases * End-user onboarding flow to create a machine wallet and DID when setting up a device. * Enabling rideshare drivers, delivery partners, or remote workers to verify data from their device. * QR code scanning or Bluetooth-triggered DID operations. ### Tech Stack * React Native, Flutter, or native iOS/Android * Lightweight backend for transaction relaying (optional) * Integration with WalletConnect, push notifications, or secure enclave features *Recommended for DePINs targeting individual users, decentralized access, or mobile-first ecosystems.* ## Web Application A **web app-based DePIN** allows machines or users to interact with the peaq network through a browser interface, offering a lightweight and accessible entry point. ### Key Characteristics: * On-demand wallet generation using browser-based tools (e.g. `ethers.js`, `peaq-js-sdk`). * Great for **dashboard-style tools**, **data viewers**, or **admin portals**. * Ideal for browser-based configuration or onboarding flows. * Integration with Account Abstraction solutions. ### Typical Use Cases: * Admin portals that manage a fleet of machines via a web dashboard. * Frontend tools that allow users to submit or verify data tied to machine DIDs. * Public explorers, visualizations, or analytics for DePIN networks. ### Tech Stack: * React, Next.js, or Vue.js frontend * SDK integration for DID operations and storage writes * Optional backend for signing or relaying if key custody is needed *Recommended for data transparency, interactive dashboards, or lightweight onboarding experiences.* ## Choosing the Right Approach Each deployment method is valid and can be adapted to your specific needs. In many cases, projects combine two or more approaches. For example: * A **server** handles DID creation and background processes. * A **web app** provides admin controls. * A **mobile app** allows users to interact directly with machines. Once you've chosen your approach, continue to the Pre-deployment Checklist below to ensure a smooth launch to peaq mainnet. # Pre-deployment Checklist ### **Security Audits and Final Checks** * **Recommendation**: Before deploying to peaq mainnet, perform a final security audit of the smart contracts and DID implementation. Doing so will ensure there are no vulnerabilities that could compromise the integrity of the system or the users' assets. > Ensure all smart contracts and DIDs have passed a thorough security audit. Address any potential vulnerabilities and run additional stress tests to validate performance under mainnet conditions. ### **Community and Developer Engagement** * **Recommendation**: Engage with the community and developers during the launch phase. This will build trust and excitement while onboarding potential users and contributors. > Host an AMA (Ask Me Anything) or community event to showcase the deployment and answer questions about the project's functionality, vision, and roadmap. ### **Documentation and Tutorials** * **Recommendation**: Provide clear, user-friendly documentation and tutorials for interacting with the system to reduce friction for new users and developers while improving adoption. > Publish step-by-step guides and developer tutorials to help users and integrators interact with your DIDs, smart contracts, and token on peaq mainnet. ### **Incentive Programs** * **Recommendation**: Launch incentive programs such as bug bounties, user onboarding rewards, or machine data upload. This will further encourage participation and will help identify potential issues post-deployment. > Introduce a bug bounty program to reward security researchers for identifying vulnerabilities. Consider offering tokens or perks for early adopters who test or contribute to the system. ### **Monitoring and Analytics Setup** * **Recommendation**: Set up monitoring tools to track performance and detect anomalies on the mainnet to respond quickly to any issues. > Implement blockchain analytics and monitoring tools to track transaction volumes, smart contract activity, and any unusual patterns post-deployment. ### **Integration Announcements** * **Recommendation**: Publicize integrations or partnerships that will launch alongside the deployment to highlight the ecosystem's growth and potential. > Announce any collaborations, integrations, or ecosystem partnerships that will utilize your deployed DIDs or tokens on the peaq mainnet. ### **Governance and Tokenomics Introduction** * **Recommendation**: Provide detailed information on governance and token utility. This will help the users understand how to participate and what benefits the token brings. > Release governance details and tokenomics, including staking, voting mechanisms, or reward structures, to foster participation and transparency. ### **Post-Launch Support Plan** * **Recommendation**: Outline a plan for providing ongoing support and updates to build confidence in long-term reliability and engagement. > Share your post-launch roadmap and establish channels for technical support, including dedicated help desks, community moderators, or support emails. ### **Compliance and Regulatory Transparency** * **Recommendation**: Ensure the deployment complies with relevant laws and regulations. Doing so will mitigate the risks of legal issues that could arise post-deployment. > Provide transparency on regulatory compliance efforts and any legal frameworks adhered to during the deployment. ### **User Education Campaigns** * **Recommendation**: Educate end users about the system's benefits, features, and how to use it to facilitate adoption and trust in the platform. > Conduct user education campaigns, including webinars, videos, and blog posts, to familiarize users with the functionality of your deployed solutions. # Onboard a Machine Source: https://docs.peaq.xyz/peaqchain/build/first-depin/onboard-machine Onboarding a machine to the **peaq network** can be done in various ways, but the most straightforward approach is by using the **JavaScript SDK** to create a **DID Document** for the machine. Before deploying to the peaq mainnet, developers are strongly encouraged to test their implementations on the **agung testnet**. This ensures that machine onboarding, verification, and data storage mechanisms function correctly before deploying to an environment with real monetary value. ## Prerequisites Before onboarding a machine, it is recommended you have an understanding of: * **DID Documents** * See: [DID Document](/peaqchain/learn/peaqDID) from the learn section. ## Instructions The **JavaScript SDK** provides a developer-friendly way to: * Create a **DID Document** for a machine. * Define the **machine's cryptographic identity** using `EcdsaSecp256k1`, `Ed25519`, or `Sr25519` based on the wallet that will be used to create signatures. * Add **service endpoints** to link ownership and storage solutions. * Store and retrieve DID-related information from the **peaq blockchain**. ### 1. Installing ethers & peaq js To get started, install: * **ethers -** library used for wallet generation and token transfer. * **peaq network sdk -** generates DID Documents and offers peaq storage. In your local node environment run: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install ethers npm install @peaq-network/sdk ``` 📌 *If you are having trouble please refer to the [SDK Installation Guide](/peaqchain/build/getting-started/install-peaq-sdk).* ### 2. Creating a Machine Wallet Each machine onboarded to the peaq network needs a unique cryptographic identity. This is achieved by creating a dedicated wallet that represents the machine. The wallet enables the machine to sign data, authenticate itself, and perform transactions securely on the network. Since the peaq network is **EVM-compatible**, we use the **ECDSA (Elliptic Curve Digital Signature Algorithm)** scheme for key management. This allows machines to sign messages and execute transactions using EVM-standard tools. The example below shows how to generate a new wallet using the `ethers` npm package: ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Wallet } from "ethers"; // A simple keyring for the machine wallet class ECDSAKeyring { constructor(privateKey) { if (privateKey) { this.wallet = new Wallet(privateKey); } else { this.wallet = Wallet.createRandom(); // generates new key } } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } async function main(){ // Connect the device to your application logic here // Create a new EVM-compatible wallet for the machine const machineWallet = new ECDSAKeyring(); // Retrieve the wallet's public address (used to identify the machine on-chain) console.log("Machine Address:", machineWallet.getAddress()); // Retrieve the private key (keep this secure and never share it) console.log("Private Key:", machineWallet.getPrivateKey()); } main(); ``` The `ECDSAKeyring` class abstracts the creation and usage of an EVM-compatible wallet. It provides access to the machine's public address and private key, and exposes a `signMessage` method that will be used later for signing operations and authentication. Never share or expose the machine's private key. Possession of this key allows anyone to impersonate the machine, sign data, and perform transactions on its behalf. Losing the key means losing access and control over the machine's on-chain identity. This is not the only way to onboard machines of the network. Sometimes private keys are not necessary when using Machine Smart Accounts. Learn more about Machine Smart Accounts in the Machine Station Factory section. *Coming Soon* ### 3. Token Transfer In order for a machine to interact with the peaq network, it must be able to execute on-chain transactions — such as registering itself, signing data, or interacting with smart contracts. On EVM-compatible networks like peaq, every write operation to the blockchain requires a fee paid in the network's native token. This fee, often referred to as **gas**, compensates the network for computation and storage resources, and helps maintain network integrity by preventing spam or malicious activity. After creating a wallet for your machine, you must transfer a small amount of the native token from your admin wallet to the machine wallet. This enables the machine to independently perform transactions on the blockchain. If you have no tokens please checkout our [faucet page](/peaqchain/build/getting-started/get-test-tokens) to receive funds. The code below demonstrates how to transfer 1 token from an admin account to the machine's wallet: ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers, Wallet } from "ethers"; import { Sdk } from "@peaq-network/sdk"; // A simple keyring for the machine wallet class ECDSAKeyring { constructor(privateKey) { if (privateKey) { this.wallet = new Wallet(privateKey); } else { this.wallet = Wallet.createRandom(); // generates new key } } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } // Load the admin wallet async function getAdmin(){ const provider = new ethers.JsonRpcProvider("https://peaq-agung.api.onfinality.io/public"); // agung endpoint for testnet const adminPrivateKey = "0xADMIN_PRIVATE_KEY"; // Replace with admin's secure private key return new ethers.Wallet(adminPrivateKey, provider); } // Transfer native tokens from admin to machine async function transferNativeToken(adminWallet, machineWallet) { const tx = await adminWallet.sendTransaction({ to: machineWallet.getAddress(), value: ethers.parseEther("1") // transfers 1 token over }); console.log("Transaction sent:", tx.hash); await tx.wait(); console.log("Transaction confirmed."); } async function main(){ const MACHINE_PRIVATE = "0xMACHINE_PRIVATE_KEY"; // Replace with your machine's actual private key const machineWallet = new ECDSAKeyring(MACHINE_PRIVATE); const adminWallet = await getAdmin(); await transferNativeToken(adminWallet, machineWallet); } main(); ``` 🧪 *This example uses the agung testnet RPC endpoint. Be sure to switch to the peaq mainnet when you're ready to onboard real machines in production.* After running this script, your **machine wallet** will hold 1 native token, making it capable of executing transactions on the peaq network. ### 4. Creating a Machine DID Document Once a wallet and funding are in place, the next step is to **register a Decentralized Identifier (DID) Document** for the machine. This DID represents the machine’s **on-chain identity** on the peaq network. A DID Document serves as a verifiable data structure that links the machine’s cryptographic keys to a unique identifier. It allows machines to authenticate themselves, prove ownership of data, and interact securely within the network. In this example, we use the **peaq JavaScript SDK** to register a DID for the machine on the **agung testnet**. The DID Document contains: * **Verification Methods -** Public keys that allow others to verify signatures made by the machine. * **Service Endpoints -** Useful metadata such as the machine's admin wallet address or off-chain services. ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers, Wallet } from "ethers"; import { Sdk } from "@peaq-network/sdk"; class ECDSAKeyring { constructor(privateKey) { if (privateKey) { this.wallet = new Wallet(privateKey); } else { this.wallet = Wallet.createRandom(); // generates new key } } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } // Create a new DID on chain async function generateDID(machineWallet, adminWallet){ const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; // agung testnet endpoint const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const name = "project_name"; // Replace with your project/DePIN specific name const customFields = { verifications: [{ type: "EcdsaSecp256k1RecoveryMethod2020" }], services: [ { id: '#admin', type: 'admin', data: `${adminWallet.address}` } ] }; // Create a transaction object to register the DID const tx = await sdk.did.create({ name: name, address: machineWallet.getAddress(), customDocumentFields: customFields }); // Submit the transaction to the network using the machine’s private key const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: machineWallet.getPrivateKey() }); console.log("DID Document registered with receipt:", receipt); } async function main(){ const MACHINE_PRIVATE = "0xMACHINE_PRIVATE_KEY"; // Replace with your machine key const ADMIN_PRIVATE = "0xADMIN_PRIVATE_KEY"; // Replace with your admin key const machineWallet = new ECDSAKeyring(MACHINE_PRIVATE); const adminWallet = new ECDSAKeyring(ADMIN_PRIVATE); await generateDID(machineWallet, adminWallet); } main(); ``` The code above registers a DID Document that **links** the machine's address with a verification method based on the wallet the machine is tied to. A reference to the admin account responsible for it is added as well. By submitting this transaction from the machine's wallet, the peaq network **cryptographically** claims that the machine is the entity creating this identity. 📌 *For more advanced DID operations, including updates, deactivation, or retrieval, refer to the full reference:* [DID Operations](/peaqchain/sdk-reference/javascript/did-operations). ## Putting it all together The code below demonstrates the complete flow for onboarding a machine to the peaq network using the agung testnet. This full example includes: 1. Creating a **machine wallet**. 2. Loading an **administrator wallet** (with native tokens). 3. Transferring **tokens** to the machine wallet. 4. Registering a DID Document that represents the **machine's on-chain identity**. ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers, Wallet } from "ethers"; import { Sdk } from "@peaq-network/sdk"; // Simple keyring class for managing EVM-compatible wallets class ECDSAKeyring { constructor(privateKey) { this.wallet = privateKey ? new Wallet(privateKey) : Wallet.createRandom(); } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } // Load an admin wallet with tokens to fund new machine wallets async function getAdmin() { const provider = new ethers.JsonRpcProvider("https://peaq-agung.api.onfinality.io/public"); // Agung testnet const adminPrivateKey = "0xADMIN_PRIVATE_KEY"; // Replace securely return new Wallet(adminPrivateKey, provider); } // Transfer native tokens (gas) to the machine's wallet async function transferNativeToken(machineWallet, adminWallet) { const tx = await adminWallet.sendTransaction({ to: machineWallet.getAddress(), value: ethers.parseEther("1") // Transfer 1 native token }); console.log("Transaction sent:", tx.hash); await tx.wait(); console.log("Transaction confirmed."); } // Register a DID Document on the peaq network for the machine async function generateDID(machineWallet, adminWallet) { const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const name = "project_name"; // Customize per project const customFields = { verifications: [{ type: "EcdsaSecp256k1RecoveryMethod2020" }], services: [{ id: '#admin', type: 'admin', data: `${adminWallet.address}` }] }; const tx = await sdk.did.create({ name: name, address: machineWallet.getAddress(), customDocumentFields: customFields }); const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: machineWallet.getPrivateKey() }); console.log("peaq DID EVM receipt:\n", receipt); } // Main execution flow async function main() { // 1. Create machine wallet const machineWallet = new ECDSAKeyring(); // Optional: store these values securely // console.log("Machine Address:", machineWallet.getAddress()); // console.log("Private Key:", machineWallet.getPrivateKey()); // 2. Load admin wallet const adminWallet = await getAdmin(); // 3. Transfer tokens to the machine await transferNativeToken(machineWallet, adminWallet); // 4. Create machine DID Document await generateDID(machineWallet, adminWallet); } main(); ``` ## Summary By running this script, you've completed the foundational steps to **onboard a machine** to the peaq network. Your machine now has a cryptographic identity (via EVM wallet), the necessary native tokens to operate, and a DID Document that serves as its verifiable on-chain identity. This setup allows your machine to participate securely in decentralized physical infrastructure networks (DePINs), enabling services such as credential validation, data authentication, autonomous task execution, and more. For production use, always: * Store private keys **securely** (e.g., encrypted storage or secure enclaves). * **Avoid** hardcoding secrets into codebases. * Use the peaq **mainnet** endpoint instead of the agung testnet. Now that your machine has a verifiable on-chain identity, the next step is to learn how to store and retrieve data using [**peaq storage**](/peaqchain/sdk-reference/javascript/storage-operations) — a decentralized storage layer that enables machines to persist important information on-chain. # Run and Test Source: https://docs.peaq.xyz/peaqchain/build/first-depin/run-and-test Now that you've successfully created a machine ID, stored signed data, and linked everything to a DID Document, the final step is to **verify** that everything is functioning as expected. In this section, you will learn how to: * Retrieve and inspect a machine's DID Document. * Confirm that the **linked signature** and data storage are correct. * Verify that the signed message was indeed created by the **machine** and approved by the **administrator**. * Explore and confirm relevant transactions on a **block explorer**. These steps are essential to ensure the **integrity** of your machine's on-chain identity and data, and to build confidence in the end-to-end trust model of the peaq network. ## Prerequisites Before proceeding, ensure that you have: * Completed the [Onboard a Machine](/peaqchain/build/first-depin/onboard-machine) section. * Completed the [Store Machine Data](/peaqchain/build/first-depin/store-machine-data) section. * A basic understanding of public/private key **cryptography** and **ECDSA** signature verification. * Familiarity with using **blockchain explorers** to inspect transactions and on-chain data. ## Code Examples ### Resolve DID Document Now that the machine has created and registered a DID Document, we can query it from the blockchain to verify its contents. In this step, we'll use the same machine wallet to resolve the DID and read its document structure. The code below: 1. Instantiates the `peaq-js-sdk`. 2. Reads the DID Document associated with the machine wallet's address. 3. Logs the full DID Document to the console, which includes verification methods, services, and the signature metadata. ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Wallet } from "ethers"; import { Sdk } from "@peaq-network/sdk"; // Reuse the ECDSAKeyring class from previous page class ECDSAKeyring { constructor(privateKey) { if (privateKey) { this.wallet = new Wallet(privateKey); } else { this.wallet = Wallet.createRandom(); // generates new key } } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } async function readDID(machineWallet){ const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; // Agung testnet endpoint const WSS_BASE_URL = "wss://wss-async.agung.peaq.network"; // Used to read previously stored DID const name = "project_name"; // Same name as before const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const document = await sdk.did.read({ name: name, address: machineWallet.getAddress(), wssBaseUrl: WSS_BASE_URL }); console.log(document.document); } async function main(){ // Get the previously created wallet via the private key const machineWallet = new ECDSAKeyring(MACHINE_PRIVATE); // Same data value that was stored previously await readDID(machineWallet); } main(); ``` ### Return Object ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { id: 'did:peaq:0x1f7701CbdadDB1775C525Ca6FcF18ec4135D228B', controller: 'did:peaq:0x1f7701CbdadDB1775C525Ca6FcF18ec4135D228B', verificationMethods: [ { id: 'did:peaq:0x1f7701CbdadDB1775C525Ca6FcF18ec4135D228B#keys-1', type: 'EcdsaSecp256k1RecoveryMethod2020', controller: 'did:peaq:0x1f7701CbdadDB1775C525Ca6FcF18ec4135D228B', publicKeyMultibase: '0x1f7701CbdadDB1775C525Ca6FcF18ec4135D228B' } ], signature: { type: 'EcdsaSecp256k1RecoveryMethod2020', issuer: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', hash: '0x1d76e12aafe2701545e8f1a2be8854dcd0b209521aaef9d836548dc047650fad1af0fcb5922b125aa431a97be3eaf5d5703e86bed5e3163d59117cd832bb54651b' }, services: [ { id: '#unsignedMessage', type: 'peaqStorage', data: '392c33b7-aaec-490a-b392-97af8d1e3910' }, { id: '#admin', type: 'admin', data: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C' } ], authentications: [ 'did:peaq:0x1f7701CbdadDB1775C525Ca6FcF18ec4135D228B#keys-1' ] } ``` ### Explanation of the Returned DID Document `id` - The full Decentralized Identifier (DID) of the machine. It is based on the machine's EVM address. `controller` - The entity that controls this DID in this case, the machine itself. `verificationMethods` - Defines how the machine proves its identity. * Indicates that the DID can be authenticated using ECDSA signature generated by the machine's public key. * The key provided here (`publicKeyMultibase`) is the machine's wallet address. `signature` - Generated by the admin who signs a canonical representation of the machine's stored data. * **type -** `EcdsaSecp256k1RecoveryMethod2020` (the algorithm used for signature verification). * **issuer -** field identifies the admin wallet that signed the message. * **hash -** is the raw signature value (a string of bytes representing the signed message). > A verifier can use this field in combination with the unsigned message in peaq storage to confirm that the message was indeed signed by the admin & machine (see below). `services` - Links the DID Document to external data and related parties. * **unsignedMessage -** points to the key used in peaq storage for admin and machine wallets where the original (unsigned) message is stored. * **#admin -** links to the wallet that administer's or manages this machine (set during DID creation). `authentications` - Specifies the verification method to be used for authenticating the DID. * This field references `#keys-1` from the `verificationMethods` section, meaning that the machine's public key is used to authenticate operations associated with the DID. ## Verify Storage In this step, we verify that the machine-generated data in **peaq storage** is authentic and has been approved by the trusted administrator. Using the resolved DID Document, we will: 1. Retrieve the unsigned admin approval data from peaq storage using the stored UUID. 2. Confirm that the stored approval object correctly references the storage key. 3. Retrieve the unsigned machine data using the machine's public key. 4. Verify the machine's signature—embedded within the admin-approved content—by recovering the machine's public key from the unsigned machine data. 5. Verify the admin's signature on the canonical approval data, ensuring that the trusted authority indeed approved the machine data. The following code demonstrates these steps using **ECDSA signature verification** via the ethers library: ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Wallet, ethers } from "ethers"; import { Sdk } from "@peaq-network/sdk"; // Reuse the ECDSAKeyring class from previous page class ECDSAKeyring { constructor(privateKey) { if (privateKey) { this.wallet = new Wallet(privateKey); } else { this.wallet = Wallet.createRandom(); // generates new key } } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } async function readDID(machineWallet){ const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; // Agung testnet endpoint const WSS_BASE_URL = "wss://wss-async.agung.peaq.network"; // Used to read previously stored DID const name = "project_name"; // Same name as before const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const document = await sdk.did.read({ name: name, address: machineWallet.getAddress(), wssBaseUrl: WSS_BASE_URL }); return document.document; } async function readStorage(uuid, issuer){ const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; // Agung testnet endpoint const WSS_BASE_URL = "wss://wss-async.agung.peaq.network"; // Used to read previously stored DID const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const key = "my_key"; const pair = await sdk.storage.getItem({ itemType: uuid, address: issuer, wssBaseUrl: WSS_BASE_URL }); return pair[uuid]; } async function main(){ // Get previously created wallet via the private key const machineWallet = new ECDSAKeyring(MACHINE_PRIVATE); // Same data value that was stored previously const doc = await readDID(machineWallet); // Get public keys for admin and machine const machinePublicKey = doc.verificationMethods[0].publicKeyMultibase; const adminPublicKey = doc.signature.issuer; // Get signature for the admin approved message const adminSignature = doc.signature.hash; // Get uuid to read from the storages const uuid = doc.services[0].data; // Read peaq storage to get the unsigned admin message const unsignedAdminMessage = await readStorage(uuid, adminPublicKey); // Make sure the unsigned admin data is relevant to the DID context const adminSignedObject = JSON.parse(unsignedAdminMessage); if (adminSignedObject.storageKey != uuid){ throw new Error("UUIDs do not match"); }; // Get unsigned machine data const unsignedMachineMessage = await readStorage(uuid, machinePublicKey); // Verify machine data const recoveredAddress = ethers.verifyMessage(unsignedMachineMessage, adminSignedObject.machineSignature); console.log("Machine Signature valid:", machinePublicKey === recoveredAddress); // Verify admin const recoveredAddress2 = ethers.verifyMessage(unsignedAdminMessage, adminSignature); console.log("Admin Signature valid:", adminPublicKey === recoveredAddress2); } main(); ``` If the verification is successful, in the terminal you'll see: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} Machine Signature valid: true Admin Signature valid: true ``` It confirms that: * **Machine Verification:** The unsigned machine data stored in peaq storage was indeed signed by the machine (as the recovered address matches the machine's public key). * **Admin Verification:** The admin-approved content was signed by the trusted administrator (as the recovered address matches the admin's public key). 🎉 *Congratulations! You've successfully validated that the machine-generated data is both authored by the machine and approved by a trusted admin. This dual-layer signature verification ensures decentralized trust and accountability in your DePIN system.* ## Block Explorers To gain deeper insight into your machine's activity on the network, you can use a **block explorer**. Block explorers allow you to view all on-chain transactions associated with your wallet, track DID and storage interactions, and confirm whether data was successfully recorded. Depending on the network you're using, visit the appropriate **Subscan** explorer: | peaq | agung | | ----------------------------------- | -------------------------------------------- | | [Subscan](https://peaq.subscan.io/) | [Subscan](https://agung-testnet.subscan.io/) | In this guide, we'll focus on the **agung testnet explorer**. ### Viewing Your Wallet To begin, copy the **public address** of your machine wallet — the same one that executed your DID and storage transactions. For example: `0x1f7701CbdadDB1775C525Ca6FcF18ec4135D228B` Paste this address into the search bar at the top of the [agung explorer](https://agung-testnet.subscan.io/) homepage. You'll be directed to a detailed page for that wallet address, which looks like this: run-and-test-1 ### Token Balance Overview In the top-right section, you'll see the machine's **\$AGUNG token balance**. This is the native token used for transaction fees. In this example: * The account holds a total of **1.999 AGUNG** * **0.579 AGUNG** is currently *transferable* The remaining balance is locked as a **deposit** (or reserved fee), which occurs when interacting with peaq services like **creating a DID** or **storing data**. If needed, you can reclaim these deposits using appropriate "remove" or cleanup functions, depending on your use case. ### Viewing Transactions Scroll down to the **EVM Transactions** section. Here you'll see a list of all the actions executed by this wallet, such as: * `updateAttribute(...)` - Used to update the machine's DID Document. * `addStorage(...)` - Used to store a message in peaq storage. To view more details about a specific transaction, click the arrow on the far right. run-and-test-2 ### Viewing Transaction Events Once you click into a transaction, you'll see a breakdown of on-chain **events** triggered by the transaction. In the example below, you can see: * The extrinsic `peaqdid(AttributeUpdated)` was triggered. * The DID name `project_name` was updated. * The `BoundedVecValue` shows the actual value written — a serialized representation of the updated DID Document. This is especially useful for confirming what data was actually committed to the chain and debugging any mismatches between expected and actual values. run-and-test-3 ### Summary Block explorers like Subscan are essential tools for: * **Verifying** DID and storage transactions. * **Tracking** gas usage and deposit balances. * **Debugging** incorrect or failed writes. * **Confirming** that your machine's identity and data are verifiably recorded on-chain. Bookmark your machine's Subscan URL for quick reference during development and testing. 🛠️ # Store Machine Data Source: https://docs.peaq.xyz/peaqchain/build/first-depin/store-machine-data Once your machine has a wallet and a corresponding **DID Document** registered on the peaq network, the next step is to **associate metadata** with its on-chain identity. This enables verifiable storage of machine-generated data with admin approval and adds traceability to the machine's activity within a DePIN ecosystem. In this section, we'll demonstrate how to: * **Simulate** data generated by a machine. * **Sign** that data using the machine's private key. * **Store** the signed data using **peaq storage**. * **Update** the existing DID Document to include a reference to the signed data and its storage location. While peaq provides its own storage layer, you're free to use other decentralized or traditional storage backends. The key requirement is that any storage reference must be linked through the machine's DID Document, enabling others to verify the data's origin and integrity. For more information take a look at [Off-Chain Storage Solutions](/peaqchain/build/advanced-operations/off-chain-storage/ipfs). ## Prerequisites Before proceeding, ensure you have the following in place: * peaq JavaScript SDK installed. * A completed machine onboarding flow (see the previous page [Onboard a Machine](/peaqchain/build/first-depin/onboard-machine)). * A basic understanding of blockchain transactions, DIDs, and public/private key cryptography. ## Instructions In this guide, we'll build upon the code from the [Onboard a Machine](/peaqchain/build/first-depin/onboard-machine) tutorial. That section covered creating a wallet, transferring tokens, and registering a DID Document with an **ECDSA verification method**. Here, we'll **simulate data output** from a machine (e.g., a sensor reading or device log), sign the data with the **machine and admin** private key to ensure authenticity, and store the result using peaq storage. Finally, we'll update the DID Document to include a link to the signed data, allowing it to be verified and referenced by others in the network. ### 1. Generating Machine Data To start we will simulate a machine generating data and then store it using **peaq storage** as a key-value pair. We will reuse the previously created `ECDSAKeyring` class to access the machine's wallet instance. This allows us to: * Generate **mock data** (as if produced by a sensor or onboard system). * Store the **unsigned message** in peaq storage using a randomly generated **uuid** as the key. ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Wallet } from "ethers"; import { Sdk } from "@peaq-network/sdk"; // Reuse the ECDSAKeyring class from previous page class ECDSAKeyring { constructor(privateKey) { if (privateKey) { this.wallet = new Wallet(privateKey); } else { this.wallet = Wallet.createRandom(); // generates new key } } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } // Simulate machine-generated data and store it in peaq storage async function storeData(machineWallet){ const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; // agung testnet const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const key = crypto.randomUUID(); // Autogenerate a uuid that will be linked in DID Doc const message = "mock data to be stored"; // Replace with dynamic data if needed const tx = await sdk.storage.addItem({ itemType: key, item: message }); const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: machineWallet.getPrivateKey() }); console.log(`Stored message for ${key}: "${message}"`) return message; } async function main(){ // Get the previously created wallet via the private key const machineWallet = new ECDSAKeyring(MACHINE_PRIVATE); // Store 'generated' data at peaq storage const message = await storeData(machineWallet); } main(); ``` 📦 ***peaq storage** supports a simple structure: `64-byte key : 256-byte value`. For larger datasets or binary formats, please refer to [alternative storage solutions](/peaqchain/build/advanced-operations/off-chain-storage/ipfs) or consult our extended tutorials.* ### 2. Sign Machine Data After storing the message in peaq storage, the next step is to **sign** that message using the machine’s private key. This proves that the machine was indeed the originator of the data — a core requirement for decentralized, verifiable systems. We use the **ECDSA signature algorithm**, which is native to EVM ecosystems. The generated signature can later be embedded into the DID Document so that third parties can verify the message using: * The machine's public key (on-chain) * The unsigned message (from peaq storage) * The signature (in the DID Document) ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Wallet } from "ethers"; import { Sdk } from "@peaq-network/sdk"; class ECDSAKeyring { constructor(privateKey) { if (privateKey) { this.wallet = new Wallet(privateKey); } else { this.wallet = Wallet.createRandom(); // generates new key } } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } async function signData(message, machineWallet){ return await machineWallet.signMessage(message); } async function main(){ // Get the previously created wallet via the private key const machineWallet = new ECDSAKeyring(MACHINE_PRIVATE); // Same data value that was stored previously const message = "mock data to be stored"; // Sign data const signature = await signData(message, machineWallet); } main(); ``` ### 3. Admin Approve Machine Data After the machine has generated its data and signed it, the next step is for the **administrator**—acting as the **trusted authority**—to explicitly approve the machine-generated data. By signing a **canonical representation** of the stored data, the administrator creates a **verifiable link** between the **machine data** and the **authority** that controls the machine. This **dual-layer verification** is critical for ensuring **trust in decentralized systems**, as verifiers can confirm both the **origin of the data** and its **formal endorsement** by the responsible party. The administrator will perform the following tasks: * **Retrieve** the stored machine data (identified by the **UUID** used in peaq storage). * **Prepare** a canonical JSON representation of the data that the admin will sign. * **Sign** the canonical content using the administrator's **private key**. * **Send** the transaction to store the signed approval. ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Wallet } from "ethers"; import { Sdk } from "@peaq-network/sdk"; // Reuse the ECDSAKeyring class from previous sections class ECDSAKeyring { constructor(privateKey) { if (privateKey) { this.wallet = new Wallet(privateKey); } else { this.wallet = Wallet.createRandom(); // generates new key } } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } // Store machine data approval in peaq storage using the admin wallet async function storeData(adminWallet, machineWallet, key, machineSignature) { const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; // agung testnet // Prepare the content to be approved. Be wary about the 256 byte limit. const storageContent = { storageKey: key, machineSignature: machineSignature }; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // We reuse the same uuid key to link the machine data with the admin approval. const tx = await sdk.storage.addItem({ itemType: key, item: storageContent }); // The admin sends the transaction to store the approval. const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: adminWallet.getPrivateKey() }); console.log(`Admin stored approval for ${key}`); // Return the content that will be signed by the admin. return storageContent; } // The admin signs the canonical representation of the stored data. async function adminSignData(adminWallet, unsignedContent) { const canonicalContent = JSON.stringify(unsignedContent); return await adminWallet.signMessage(canonicalContent); } async function main() { // Get the machine wallet instance via its private key. const machineWallet = new ECDSAKeyring(MACHINE_PRIVATE); // Get the admin wallet instance (trusted authority). const adminWallet = new ECDSAKeyring(ADMIN_PRIVATE); // The key used to store the machine data (generated previously). const key = "uuidValue"; // Autogenerated uuid from before const machineSignature = "0xSIGNATURE_VALUE"; // Machine signature from previous step // Admin stores the approval data in peaq storage. const unsignedContent = await storeData(adminWallet, machineWallet, key, machineSignature); // The admin signs the canonical content to approve the machine-generated data. const adminSignature = await adminSignData(adminWallet, unsignedContent); console.log("Admin Signature:", adminSignature); } main(); ``` In this step, the administrator's signature is generated over the **canonical JSON representation** of the stored machine data (which includes the storage key and the machine's signature). This signature serves as verifiable proof that the trusted authority has approved the data, and will be incorporated into the machine's DID Document in subsequent processes. ### 4. Update DID Document Now that the machine & admin have generated a verifiable signatures and stored their unsigned messages in **peaq storage**, the final step is to update the machine's **DID Document**. This update embeds the administrators approval into the DID, linking the stored data to a trusted authority. With this update, external parties can resolve the DID, retrieve the unsigned message, and verify both the machine's signature and the admin's attestation. In this step, we will: * Reuse the machine’s original DID (same name and wallet). * Preserve existing fields such as the **verification method**. * Add a new `signature` field that includes: * The algorithm used (`EcdsaSecp256k1RecoveryMethod2020`). * The issuer (admin’s address). * The admin’s signature hash over the canonical data. * Update the `services` field to include a reference to the unsigned message stored in **peaq storage**, which is linked by a **UUID**. ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ethers, Wallet } from "ethers"; import { Sdk } from "@peaq-network/sdk"; // A simple keyring for the machine wallet class ECDSAKeyring { constructor(privateKey) { if (privateKey) { this.wallet = new Wallet(privateKey); } else { this.wallet = Wallet.createRandom(); // generates new key } } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } async function updateDID(machineWallet, adminWallet, key, adminSignature){ const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; // Agung testnet endpoint const WSS_BASE_URL = "wss://wss-async.agung.peaq.network"; // Used to read previously stored DID const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const name = "project_name"; // Same name as before const customFields = { verifications: [{ type: "EcdsaSecp256k1RecoveryMethod2020" }], signature: { type: "EcdsaSecp256k1RecoveryMethod2020", issuer: adminWallet.getAddress(), hash: adminSignature }, services: [ { id: '#unsignedMessage', type: 'peaqStorage', data: key // Reference to the stored unsigned admin & machine data in peaq storage }, { id: '#admin', type: 'admin', data: adminWallet.getAddress() }, ] }; // Create a transaction object to update the DID const tx = await sdk.did.update({ name: name, address: machineWallet.getAddress(), wssBaseUrl: WSS_BASE_URL, customDocumentFields: customFields }); // Submit the transaction to the network using the machine’s private key const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: machineWallet.getPrivateKey() }); console.log("DID Document updated with receipt:", receipt); } async function main(){ // Get previously created wallet via the private key const machineWallet = new ECDSAKeyring(MACHINE_PRIVATE); const adminWallet = new ECDSAKeyring(ADMIN_PRIVATE); const key = "uuidValue"; // Autogenerated uuid from before const adminSignature = "0xSIGNATURE_VALUE"; // admin signature from previous step // Same data value that was stored previously await updateDID(machineWallet, adminWallet, key, adminSignature); } main(); ``` By updating the DID Document with the administrator’s signature and linking to the unsigned machine data stored in peaq storage, you create a **fully verifiable data flow**. External parties can now: * Retrieve the DID Document to inspect the machine’s verification method and the **admin-approved signature.** * Fetch the corresponding **unsigned message** from peaq storage using the UUID reference. * Verify the authenticity of the machine data using **both** the machine's public key and the trusted admin’s attestation. This design pattern is a critical building block for establishing decentralized trust in machine-generated data, ensuring secure and auditable DePIN systems. ## Putting it all Together In this example, we tie all the steps into one complete flow. The script performs the following tasks: 1. **Initialize Wallets:** Create instances for both the machine and the admin using the ECDSAKeyring. 2. **Generate & Store Machine Data:** The machine generates mock data and stores it in **peaq storage** under a randomly generated UUID. 3. **Sign Machine Data:** The machine signs the generated data with its private key to create a verifiable signature. 4. **Admin Approval:** The admin (trusted authority) retrieves the machine data reference and stores an approval object in peaq storage. Then, the admin signs a canonical JSON representation of this approval content. 5. **Update DID Document:** The machine's DID Document is updated with the admin's signature and a reference to the stored data. Ultimately links the unsigned machine data (with its machine signature) to the trusted admin's attestation. ```javascript JavaScript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Wallet } from "ethers"; import { Sdk } from "@peaq-network/sdk"; // Reuse the ECDSAKeyring class from previous page class ECDSAKeyring { constructor(privateKey) { if (privateKey) { this.wallet = new Wallet(privateKey); } else { this.wallet = Wallet.createRandom(); // generates new key } } getAddress() { return this.wallet.address; } getPrivateKey() { return this.wallet.privateKey; } async signMessage(message) { return this.wallet.signMessage(message); } } // Simulate machine-generated data and store it in peaq storage async function storeMachineData(key, machineWallet){ const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; // agung testnet const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const message = "mock data to be stored"; // Replace with dynamic data if needed const tx = await sdk.storage.addItem({ itemType: key, item: message }); const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: machineWallet.getPrivateKey() }); console.log(`Stored message for ${key}: "${message}"`) return message; } async function signMachineData(message, machineWallet){ return await machineWallet.signMessage(message); } // Store machine data approval in peaq storage using the admin wallet async function storeAdminApproval(adminWallet, machineWallet, key, machineSignature) { const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; // agung testnet // Prepare the content to be approved. Be wary about the 256 byte limit. const storageContent = { storageKey: key, machineSignature: machineSignature }; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // We reuse the same key to link the machine data with the admin approval. const tx = await sdk.storage.addItem({ itemType: key, item: storageContent }); // The admin sends the transaction to store the approval. const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: adminWallet.getPrivateKey() }); console.log(`Admin stored approval for ${key}`); // Return the content that will be signed by the admin. return storageContent; } // The admin signs the canonical representation of the stored data. async function adminSignData(adminWallet, unsignedContent) { const canonicalContent = JSON.stringify(unsignedContent); return await adminWallet.signMessage(canonicalContent); } async function updateDID(machineWallet, adminWallet, key, adminSignature){ const HTTPS_BASE_URL = "https://peaq-agung.api.onfinality.io/public"; const WSS_BASE_URL = "wss://wss-async.agung.peaq.network"; // Used to read previously stored DID const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const name = "project_name"; // Same name as before const customFields = { verifications: [{ type: "EcdsaSecp256k1RecoveryMethod2020" }], signature: { type: "EcdsaSecp256k1RecoveryMethod2020", issuer: adminWallet.getAddress(), hash: adminSignature }, services: [ { id: '#unsignedMessage', type: 'peaqStorage', data: key // Reference to the stored unsigned admin & machine data in peaq storage }, { id: '#admin', type: 'admin', data: adminWallet.getAddress() }, ] }; // Create a transaction object to update the DID const tx = await sdk.did.update({ name: name, address: machineWallet.getAddress(), wssBaseUrl: WSS_BASE_URL, customDocumentFields: customFields }); // Submit the transaction to the network using the machine’s private key const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: machineWallet.getPrivateKey() }); console.log(`Successfully Updated DID Document`); } async function main(){ // 1. Initialize Wallets const machineWallet = new ECDSAKeyring(MACHINE_PRIVATE); const adminWallet = new ECDSAKeyring(ADMIN_PRIVATE); // 2. Generate & Store Machine Data const key = crypto.randomUUID(); const message = await storeMachineData(key, machineWallet); // 3. Sign Machine Data const machineSignature = await signMachineData(message, machineWallet); // 4. Admin Approval const unsignedContent = await storeAdminApproval(adminWallet, machineWallet, key, machineSignature); const adminSignature = await adminSignData(adminWallet, unsignedContent); // 5. Update DID Document await updateDID(machineWallet, adminWallet, key, adminSignature); } main(); ``` ## Summary After running this script, your machine's DID Document will be updated with: * A reference to the unsigned machine data stored in **peaq storage**. * The machine's signature over the generated data. * The admin's signature attesting to the data's validity. This end-to-end flow creates a fully verifiable data trail. External parties can resolve the **DID Document**, retrieve the stored data via its unique key, and verify that both the machine and the trusted admin have authenticated the data. This pattern is a critical building block for secure, transparent DePIN ecosystems. # Connecting to peaq Source: https://docs.peaq.xyz/peaqchain/build/getting-started/connecting-to-peaq On this reference page, you will find the network types, RPC/WSS URLs, chain identifiers, etc. to connect to peaq or agung. ## Networks: | Network | Network type | | ------- | ------------ | | peaq | Mainnet | | agung | Testnet | ### Chain ID ```bash peaq theme={"theme":{"light":"github-light-default","dark":"github-dark"}} 3338 ``` ```bash agung theme={"theme":{"light":"github-light-default","dark":"github-dark"}} 9990 ``` ### Public RPC URLs ```bash peaq theme={"theme":{"light":"github-light-default","dark":"github-dark"}} https://quicknode1.peaq.xyz https://quicknode2.peaq.xyz https://quicknode3.peaq.xyz # Secondary Fallback Option https://peaq.api.onfinality.io/public https://peaq-rpc.publicnode.com ``` ```bash agung theme={"theme":{"light":"github-light-default","dark":"github-dark"}} https://peaq-agung.api.onfinality.io/public https://wss-async-agung.peaq.xyz ``` ### Public WSS URLs ```bash peaq theme={"theme":{"light":"github-light-default","dark":"github-dark"}} wss://quicknode1.peaq.xyz wss://quicknode2.peaq.xyz wss://quicknode3.peaq.xyz # Secondary Fallback Option wss://peaq.api.onfinality.io/public-ws wss://peaq-rpc.publicnode.com ``` ```bash agung theme={"theme":{"light":"github-light-default","dark":"github-dark"}} wss://peaq-agung.api.onfinality.io/public-ws wss://wss-async-agung.peaq.xyz ``` ## Wallet Configuration Guides Complete guide with video tutorial on how to manually update your wallet's RPC settings for peaq network. Step-by-step guide with desktop and mobile video tutorials on removing and re-adding the peaq network from your wallet. ### Private URLs You can create your custom peaq RPC/WSS endpoint with QuickNode or OnFinality. To do so, follow one of the guides below: * [QuickNode guide](https://www.quicknode.com/guides/quicknode-products/how-to-use-the-quicknode-dashboard#create-a-quicknode-endpoint) * [OnFinality guide](https://documentation.onfinality.io/support/the-enhanced-api-service) ## Block explorers #### peaq | Block Explorer | Type | URL | | -------------- | --------------- | ------------------------------------- | | peaqscan | EVM | [peaqscan](https://peaqscan.xyz/) | | Blockscout | EVM | [Blockscout](https://scout.peaq.xyz/) | | Subscan | EVM & Substrate | [Subscan](https://peaq.subscan.io/) | #### agung | Block Explorer | Type | URL | | -------------- | --------------- | ------------------------------------------------- | | peaqscan | EVM | [peaqscan testnet](https://testnet.peaqscan.xyz/) | | Blockscout | EVM | N/A | | Subscan | EVM & Substrate | [Subscan](https://agung-testnet.subscan.io/) | ## Node setup ### Node hardware requirements ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} OS - Ubuntu 20.04 CPU - 3.3 GHz AMD EPYC 7002 Storage - 1TB SSD Memory - 8GB ``` ### Docker image peaq ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} docker run -v peaq-storage:/chain-data -p 9944:9944 peaq/parachain:peaq-v0.0.104 \ --parachain-id 3338 \ --chain ./node/src/chain-specs/peaq-raw.json \ --base-path chain-data \ --port 30333 \ --rpc-port 9944 \ --rpc-cors=all \ --execution wasm \ --state-pruning archive \ -- \ --execution wasm \ --port 30343 --rpc-port 9977 \ --sync warp ``` # DeFi on peaq Source: https://docs.peaq.xyz/peaqchain/build/getting-started/defi-guide # DEX Native DEX on peaq is provided by MachineX: [https://www.machinex.xyz/](https://www.machinex.xyz/) # Guide: DeFi Essentials on peaq How to access peaq on popular EVM-compatible wallets? ### Add peaq as a custom network on your wallet Most EVM wallets enable you to add a custom network — make sure to refer to their respective guides, such as: * [MetaMask](https://support.metamask.io/configure/networks/how-to-add-a-custom-network-rpc/) * [Rabby Wallet](https://www.quicknode.com/guides/ethereum-development/wallets/how-to-set-a-custom-provider-in-rabby) * [Trust Wallet](https://trustwallet.com/blog/guides/how-to-add-a-custom-network-on-the-trust-wallet-app) * [Base App](https://www.quicknode.com/guides/ethereum-development/wallets/how-to-setup-coinbase-wallet-with-quicknode) ### Use the following settings to add peaq: * RPC Endpoint: [https://quicknode3.peaq.xyz](https://quicknode3.peaq.xyz) * Chain ID: 3338 * Currency: PEAQ * BLock Explorer URL: [https://peaq.subscan.io](https://peaq.subscan.io) ### You can now access peaq and dApps on peaq with your wallet For more network configuration information please check out the [connecting to peaq page](/peaqchain/build/getting-started/connecting-to-peaq). # How to add custom tokens on peaq to my wallet? Depending on your wallet, sometimes, you may need to manually add specific tokens on peaq to your wallet as custom tokens. Please see this guide on adding custom tokens from [MetaMask](https://support.metamask.io/manage-crypto/tokens/how-to-display-tokens-in-metamask/) as an example. To find a token's address on a network, head to CoinMarketCap or CoinGecko, open the page of the token you want to add, and review the list of its Contracts in the menu on the left: defi-1 Important Tokens on peaq: | Block Explorer | Contract Address | Symbol | Decimals | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------- | -------- | | Bridged stgUSDT (peaq USDT) | [0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949](https://peaq.subscan.io/token/0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949) | USDT | 6 | | Wrapped Parasail Staked PEAQ | [0xE5330a9fBA99504C534127D39727729899c9a506](https://peaq.subscan.io/token/0xE5330a9fBA99504C534127D39727729899c9a506) | wstPEAQ | 18 | # How to bridge to peaq? For the best experience, please use the desktop applications and make sure you have the relevant wallet browser extensions installed and peaq is added as a custom network in your wallet. Stargate Bridge Example: Bridging USDT from Arbitrum to peaq with Metamask browser extension ### Step 1 Go to [https://stargate.finance/bridge](https://stargate.finance/bridge) (note: please make sure you have some ETH on Arbitrum One to cover the gas fee for bridging) and connect your wallet. ### Step 2 Select the correct token and networks to bridge from and to. In our example, we are bridging 3 USDT from Arbitrum to peaq. defi-2 If it's your first time bridging to peaq, head to `Advanced Settings` and enable `Gas on Destination`. You can choose `Auto`, `Medium`, or `Max` for your gas configuration. defi-3 ### Step 3 On MetaMask, approve the spending cap request. defi-4 ### Step 4 On MetaMask, approve the transaction request. defi-5 ### Step 5 Once the transaction is confirmed, a pop-up will show up alongside with a link to the transaction. defi-6 ### Step 6 Wait for LayerZero to complete the bridging (you can monitor the progress on this status bar). defi-7 ### Step 7 Bridging complete — you will see the green tick on the interface. You will also find a log of this activity in your MetaMask under the Activity tab on Arbitrum One network. defi-8 ### Step 8 For the bridged USDT balance to show on your MetaMask wallet, find peaq on your wallet's list of custom networks. Click on **Import Tokens → Custom Tokens**, select **peaq**, and add the following: * Address: `0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949` * Symbol: `USDT` * Decimals: `6` (if you have issues editing the decimal, please update or reinstall your browser extension). defi-9 Now your bridged USDT balance on peaq will show up in your wallet! defi-10 **Disclaimer:** The information in this guide is provided for educational purposes only and does not constitute financial, investment, legal, or tax advice. Bridging assets and interacting with blockchain networks and wallets carry risks including loss of funds due to smart-contract bugs, incorrect addresses, network congestion, and fees. Always double-check addresses and network settings, test with a small amount first, and consider consulting a qualified professional. We are not responsible for any loss, damage, or other consequences resulting from your use of this guide or third-party services. # EVM Onboarding Source: https://docs.peaq.xyz/peaqchain/build/getting-started/evm-onboarding ## 1. Working with Finality ### 1.1 Three Presets | Preset | Use-case | How to code it | Typical delay | | --------- | -------------------------- | -------------------------------- | ------------- | | **FAST** | UI refresh, analytics | `provider.getBlock("latest")` | *\~1 s* | | SAFE | Most user actions | `await tx.wait(7)` | *\~7-8 s* | | **FINAL** | Treasury, bridge, NFT mint | `provider.getBlock("finalized")` | *\~15-20 s* | > Golden rule: a latest block is optimistic; a finalized block is forever. ### 1.2 Decision matrix * **FAST (0 confirms, \~1 s)** – Great for real-time UI updates and analytics. Roll back if a reorg shows up. * **SAFE (\~7 authored blocks, \~7-8 s)** – Default for ordinary user actions (token transfers, approvals, simple swaps). Rarely reorgs yet still snappy. * **FINAL (relay-chain finalized, \~15-20 s)** – Use for anything that must be absolutely irreversible: treasury moves, bridge deposits, high-value NFT mints, etc. *** ## 2. Quick‑Start Checklist 1. **RPC & chain‑ID** * Mainnet RPC `https://quicknode1.peaq.xyz` ID `3338` * Testnet RPC `https://peaq‑agung.api.onfinality.io/public` ID `9990` 2. **Pin EVM version to `london`** ``` solidity: { version: "0.8.21", settings: { evmVersion: "london" } } ``` 3. **Estimate gas then add a ×2 buffer** ``` const gas = await provider.estimateGas(tx); tx.gasLimit = gas.mul(2); ``` 4. **Deploy as usual** `npx hardhat run scripts/deploy.js --network peaq` *** ## 3. Handling Reorgs & Using Finalized Blocks **What is a Reorg?** When two blocks land at almost the same time the chain briefly forks; the shorter fork is later dropped, so its last few blocks - and any transactions inside - vanish and must be re-included. **Why does using Finalized block help in that case?** Extra votes lock a block in permanently; once finalized, that block (and all earlier ones) can never be dropped, so reorgs can only touch the unfixed tip of the chain. ``` // Latest for optimistic UI const latest = await provider.getBlock("latest"); // Finalized for deterministic reads const finalized = await provider.send( "eth_getBlockByNumber", ["finalized", false] ); ``` When monitoring blocks: ``` let lastHash; provider.on("block", async (n) => { const blk = await provider.getBlock(n); if (lastHash && blk.parentHash !== lastHash) { // reorg detected – revert optimistic state } lastHash = blk.hash; }); ``` *** ## 4. peaq‑Specific Nuances | Topic | What’s different | What to do | | --------------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------- | | `block.difficulty` / `prevrandao` | Always `0` | Use Chainlink VRF or for low stakes`randomnessCollectiveFlip` queried at **finalized** height. | | Gas schedule | Mostly London‑equivalent; storage a bit pricier | Always run `eth_estimateGas`, never hard‑code. | | Account abstraction (ERC‑4337) | Not yet live | Wait for roadmap. | | Deep traces | `debug_*` RPCs only on tracing‑enabled nodes | Run a node with `--ethapi=debug,trace,txpool` & `--features evm-tracing`. | *** ## 5. Tracing & Debugging Enable in node startup: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ./peaq-node \ --ethapi=debug,trace,txpool \ --rpc-methods=Unsafe \ --state-pruning archive \ --wasm-runtime-overrides=parachain-peaq \ --runtime-cache-size 64 \ ``` Then: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl -X POST -H "Content-Type: application/json" -d \ '{"jsonrpc":"2.0","id":1,"method":"debug_traceTransaction","params":["",{"tracer":"callTracer"}]}' \ https://evm.yourevmnode.xyz ``` > **💡 Note:**\ > Before enabling tracing RPCs, you must build the runtime with the `evm-tracing` feature and place the resulting `.wasm` file in a folder used for overrides. Then, start the node with tracing RPCs enabled and point `--wasm-runtime-overrides` to that directory (not a file). The override is **not** included in the Docker image, so you must mount your own folder. If the path isn’t a real directory or the file doesn’t end in `.wasm`, the override will be ignored and tracing won’t work. > > For more information on building the runtime with tracing enabled, see the [peaq-node-builder repository](https://github.com/peaqnetwork/peaq-node-builder). *** ## 6. Frequently Asked Questions **How many confirmations are really “safe”?** Waiting for the **finalized** tag is bullet‑proof; seven authored blocks (≈ 7‑8 s) is the community standard for ordinary user actions. **Can a finalized block revert?** Only if ≥ ⅓ of stake is slashed — an economically irrational scenario. **My tx failed – how do I debug?** Simulate with `provider.call(tx)` first; for deep dives use `debug_traceTransaction` on a tracing node. *** ## 7. Resources * Docs [https://docs.peaq.xyz](https://docs.peaq.xyz/) * Explorer [https://peaq.subscan.io](https://peaq.subscan.io/) * Discord [https://discord.gg/peaqnetwork](https://discord.gg/peaqnetwork) * GitHub [https://github.com/peaqnetwork](https://github.com/peaqnetwork) *Happy building – and welcome to peaq!* # Get test tokens Source: https://docs.peaq.xyz/peaqchain/build/getting-started/get-test-tokens The following form can be used to receive 3 \$AGNG per day to a low funded **EVM** or **Substrate** wallet:
## Wallet Quick Start In order to perform transactions on the blockchain you need to have native tokens to pay gas for the network fees. Before a user can get the tokens, they must have a wallet for the faucet to know where to send them. Since peaq is a Substrate chain with the interoperability of EVM-style chains built in, you can either use: * EVM H160 address: `0x48C9774C88736F7c169D2598278876727AFD1476` * Substrate SS58 address: `5FEw7aWmqcnWDaMcwjKyGtJMjQfqYGxXmDWKVfcpnEPmUM3q` ### Create EVM Wallet 1. Go to the MetaMask wallet [extension page](https://chromewebstore.google.com/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=en) 2. Add extension 3. Create a new wallet 4. Add password for login on your connected device 5. Write down and store the mnemonic phrase 6. Complete reading the wallet on-boarding prompts After successfully creating a new wallet you will have obtained an EVM address that the faucet can send tokens to. ### Add peaq to Wallet After creating a MetaMask account you will need to add a custom network manually for it to appear in the wallet. 1. Open MetaMask and go to the network selector page 2. Click on `Add a custom network` 3. Input with the following information sourced from [Connecting to peaq](/peaqchain/build/getting-started/connecting-to-peaq). You can add the all networks in a similar fashion, just make sure to change the values for the relevant chain. | Network | RPC URL | Chain Id | Currency symbol | Block Explorer | | ------- | --------------------------------------------------------------------- | -------- | --------------- | ---------------------------------------------------------------------- | | peaq | [https://quicknode1.peaq.xyz](https://quicknode1.peaq.xyz) | 3338 | PEAQ | [https://peaq.subscan.io/](https://peaq.subscan.io/) | | agung | [https://wss-async-agung.peaq.xyz](https://wss-async-agung.peaq.xyz/) | 9990 | AGNG | [https://agung-testnet.subscan.io/](https://agung-testnet.subscan.io/) | 4. Click save and change to that network 5. PEAQ/AGNG appears as native token ## Create Substrate Wallet 1. Go to the polkadot.js wallet [extension page](https://polkadot.js.org/extension/) 2. Download for your browser 3. Click on the plus to create a new account 4. Safely store the mnemonic phrase, and continue 5. Allow for use on any chain 6. Create a name and password ### Substrate Wallet If you are using a substrate wallet, please click on the [peaq accounts page](https://polkadot.js.org/apps/?rpc=wss://peaq.api.onfinality.io/public-ws#/accounts) or [agung accounts page](https://polkadot.js.org/apps/?rpc=wss://wss-async.agung.peaq.network#/accounts) to be routed to the account management dashboard. On the top of the page is an extension pane which displays available accounts that are connected. If your wallet is not showing up in this location, first make sure you have the polkadot wallet extension from above downloaded, and a new wallet created. If the issue persists please manually connect the wallet by following the steps below: 1. Go to your extensions and open polkadot get-test-tokens-1 2. Click on the “Connect Accounts” tab on the top of the extension get-test-tokens-2 3. Next you are able to manually select which wallets you would like to appear in this pane on the accounts page. get-test-tokens-3 4. Then click connect and the wallets will appear! get-test-tokens-4 # EVM Token Transfer Source: https://docs.peaq.xyz/peaqchain/build/getting-started/how-to-send-receive-peaq/evm-token-transfer This guide provides a detailed breakdown of a \$PEAQ token transfer between two wallets on the EVM side of the chain. ## Prerequisites: * Download and install EVM- and Substrate-based wallets. * MetaMask and Polkadot.js will be shown in this tutorial. You can use your preferred wallets instead. * Review the previous [Token Guide](/peaqchain/build/getting-started/get-test-tokens). ## Receive \$PEAQ ### EVM → EVM 1. Open up your MetaMask account and copy your address. This is your public H160 (EVM address standard) address that can be used to receive \$PEAQ from another EVM address. evm-token-transfer-1 ### Substrate → EVM In order to receive tokens from a Substrate wallet, you need to convert your EVM address into a SS58 (Substrate address standard) address with the [Address Converter](https://snow-address-converter.netlify.app/). 1. On the Address Converter, select the "H160" address format, paste your EVM address into the Address field, and click the "Go!" button. 2. In the output, you will find your EVM address converted into different standards, including its SS58 representation. Use his address to receive \$PEAQ. from a Substrate wallet. ## Send \$PEAQ ### EVM → EVM 1. Open your MetaMask wallet and click on the "Send" button.evm-token-transfer-4 2. Acquire the recipient's EVM address and paste it in the 'to' field. 3. In the next window, set the amount of \$PEAQ you would like to transfer and click on "Continue". evm-token-transfer-5 4. Review the transaction details, then click on "Confirm" if everything is correct.evm-token-transfer-6 5. The transfer will execute, and the transaction will appear in your MetaMask's "Activity" tab. evm-token-transfer-7 ## **EVM → Substrate** Please follow the EVM → Substrate guide on the [Substrate Token Transfer Page](/peaqchain/build/getting-started/how-to-send-receive-peaq/substrate-token-transfer#evm-→-substrate) to use Address Unification and transfer \$PEAQ from a H160 wallet to a SS58. # Substrate Token Transfer Source: https://docs.peaq.xyz/peaqchain/build/getting-started/how-to-send-receive-peaq/substrate-token-transfer This guide provides detailed examples of transferring peaq between a sender and a recipient on the Substrate side of the chain. ## Prerequisites: * Downloaded and installed an EVM and Substrate based wallet. * MetaMask and Polkadot.js will be shown in this tutorial. You can swap for your preferred wallet of choice * Understanding of the previous [Token Guide](/peaqchain/build/getting-started/get-test-tokens). ## Receive \$PEAQ ### Substrate → Substrate 1. We'll first begin by heading over to the [Polkadot.js.org](https://polkadot.js.org/apps/?rpc=wss://peaq.api.onfinality.io/public-ws#/accounts) web app for peaq network. 2. Connect your wallet if not done so already as outlined in the [Token Guide](/peaqchain/build/getting-started/get-test-tokens). 3. Once your account(s) are connected, proceed to refresh the page. Your account(s) will now populate the page. substrate-token-transfer-1 4. Clicking on your wallet account's name will bring up a panel on the right that displays information about that wallet account. This public address (SS58 - Substrate) can be used to receive \$PEAQ. substrate-token-transfer-2 ### EVM → Substrate In the current system, sending tokens directly from an EVM-based wallet (which uses an H160 address) to a Substrate wallet (which uses an SS58 address) is not possible due to the differences in their address formats. However, we provide a solution called **Address Unification**, which creates a link between the two types of addresses. Through our specialized **pallets** (modular runtime components in Substrate), a Substrate wallet can generate a unique **linked H160 address**. Once this linkage is established, an EVM wallet can send tokens to the generated H160 address, and the funds will automatically appear in the corresponding Substrate wallet. If you want to unify your addresses and enable this transfer, you can do so now via our **Developer Extrinsics** page on [peaq.polkadot.js.org](https://polkadot.js.org/apps/?rpc=wss://peaq.api.onfinality.io/public-ws#/extrinsics) or [agung.polkadot.js.org](https://polkadot.js.org/apps/?rpc=wss://wss-async.agung.peaq.network#/extrinsics). This process ensures that your Substrate wallet can seamlessly receive tokens from EVM-based wallets while maintaining compatibility across both ecosystems. These instructions are shown below: 1. Navigate to the relevant polkadot.js.org link above for the network you are trying to unify addresses for. 2. On the `Developer->Extrinsics` tab, please select the following parameters: * The substrate account selected (e.g. **TESTER**) will be the recipient of the tokens. * submit the following extrinsic: **addressUnification claimDefaultAccount()** substrate-token-transfer-3 3. Once these parameters are set you can click on the Submit Transaction button. 4. An authorization prompt will need to be approved to execute the transaction. Click on the sign and submit button. substrate-token-transfer-4 5. Enter your wallet password and proceed to sign the transaction from your wallet extension. 6. You should see a green check mark notification indicating that the extrinsic was successful. substrate-token-transfer-5 7. In order to read the generated H160 address, you must go to the `Developer->Chain State` tab. Please select the following: * selected chain state query: **addressUnification evmAddress()** * The substrate account selected (e.g. **TESTER**) the previously used wallet. 8. Now click on the + to query the state. Your binded address will be displayed. substrate-token-transfer-6 9. To execute a transfer from EVM to Substrate open up your MetaMask wallet. 10. Click the send button to transfer tokens over. In the `to` field, copy and paste the H160 address that was read in step 8. substrate-token-transfer-7 11. Submit the transfer. ### Verify transfer Substrate Wallet before transfer: substrate-token-transfer-8 Substrate Wallet after transfer: substrate-token-transfer-9 As you can see the tokens were successfully transferred over! ## Send \$PEAQ ### Substrate → Substrate 1. To send, locate the “send” button on the account you wish to send from. substrate-token-transfer-10 2. Input the Substrate address of the user you wish to send to, the amount of \$PEAQ you wish to send to the recipient, and proceed to click the “Make Transfer” button. substrate-token-transfer-11 3. This transfer extrinsic needs to be signed, proceed to “Sign and Submit” the transaction. 4. Enter your wallet password and proceed to sign the transaction from your wallet extension. 5. You should see a green check mark notification indicating that the transfer was successful. substrate-token-transfer-12 ### Substrate → EVM When transferring funds from a Substrate Wallet to an EVM Wallet, the recipient H160 address must be converted to a SS58 Address. Please see the [EVM Token Transfer](/peaqchain/build/getting-started/how-to-send-receive-peaq/evm-token-transfer#substrate-→-evm) guide in the Substrate→EVM section to see how to convert. Once you have obtained the converted address, please follow the same procedure as indicated in the above section for [Substrate→Substrate](/peaqchain/build/getting-started/how-to-send-receive-peaq/substrate-token-transfer#substrate-→-substrate-2) transfers. # Installing peaq SDK Source: https://docs.peaq.xyz/peaqchain/build/getting-started/install-peaq-sdk The quickest way to get started building on peaq is to use one of our prebuilt SDKs. They offer robust capabilities in DID, RBAC, and Storage - all communicated via the peaq blockchain. Please checkout the official [SDK Reference](/peaqchain/sdk-reference/home) for more detailed information. ## Prerequisite: * [Node.js](https://nodejs.org/en/download/package-manager) version at least 16.x, recommended to use the latest one. ## Installation: Create a new JavaScript project and follow the steps below to set up a new instance of the peaq sdk. Run command`npm init -y` to create a Node.js project. ```bash npm theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm i @peaq-network/sdk ``` ```bash yarn theme={"theme":{"light":"github-light-default","dark":"github-dark"}} yarn add @peaq-network/sdk ``` In the `package.json`file, add `"type": "module"` to enable ES Modules. Create a new `.js` file. Write the JavaScript code that interacts with the SDK. Run command: `node file_name.js` to execute. After completing these steps the peaq sdk will have been successfully installed at your local Javascript Project. For more information about the capabilities it offers checkout the [JavaScript SDK Reference](/peaqchain/sdk-reference/javascript). # Python SDK Quickstart ## Prerequisite: * [Python](https://www.python.org/downloads/) Version 3.8+ installed on your system. ## Installation: Mac/Linux: ```bash npm theme={"theme":{"light":"github-light-default","dark":"github-dark"}} python -m venv peaq source peaq/bin/activate ``` Windows: ```bash npm theme={"theme":{"light":"github-light-default","dark":"github-dark"}} python -m venv peaq peaq\Scripts\activate ``` ```bash npm theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install peaq-sdk==0.2.1 ``` ```bash npm theme={"theme":{"light":"github-light-default","dark":"github-dark"}} touch requirements.txt ``` Add `python-dotenv==1.1.0` to this file to enable environment variables. Install with `pip install -r requirements.txt`. Create a new `.py` file. Write Python code that interacts with the SDK. Run command: `python file_name.py` to execute. For more information about the Python SDK, take a look at the [Python SDK Reference](/peaqchain/sdk-reference/python). # Token List Source: https://docs.peaq.xyz/peaqchain/build/getting-started/token-list The **peaq Token List** provides a canonical, up-to-date registry of ERC-20 and native tokens available on the **peaq Mainnet**. Developers, dApps, and wallets can reference this list to display verified tokens, ensure accurate metadata, and validate token contracts across the ecosystem. You can view the live JSON directly at: [https://tokenlist.peaq.xyz](https://tokenlist.peaq.xyz) ## Overview The token list follows the [Uniswap Token List standard](https://tokenlists.org/), making it compatible with most wallets, DEX interfaces, and indexers. Each entry includes: * **Token metadata** - name, symbol, decimals, and logo URI * **Tags** - classify the token (e.g., `Stablecoin`, `OFT`, `ERC20`, `peaqNative`) * **Extensions** - include helpful context such as Coingecko IDs, bridge information, and origin chain IDs This list ensures that applications using the peaq network can consistently identify tokens and display them with the correct attributes. ## JSON Structure | Key | Description | | ----------- | -------------------------------------------------------- | | `name` | Name of the token list (e.g., `"peaq Token List"`) | | `version` | Semantic version of the list (`major`, `minor`, `patch`) | | `timestamp` | Last update timestamp in ISO format | | `tags` | Classification of token types | | `tokens` | Array of token metadata objects | Each token entry contains: | Field | Description | | ------------ | -------------------------------------------------------------------------- | | `chainId` | Network chain ID (peaq = **3338**) | | `address` | Contract address of the token | | `symbol` | Short ticker symbol (e.g., `USDC`) | | `name` | Full token name | | `decimals` | Number of decimal places | | `logoURI` | IPFS link to the token logo | | `tags` | Categories describing the token | | `extensions` | Optional metadata (website, Coingecko ID, origin chain, bridge info, etc.) | ## Token Categories | Tag | Description | | -------------- | ---------------------------------------------------------------- | | **Stablecoin** | Tokens pegged to external assets such as USD. | | **OFT** | LayerZero Hydra Omnichain Fungible Token representation on peaq. | | **wNative** | ERC-20 representation of peaq’s native token. | | **peaqNative** | Tokens natively created on peaq. | | **ERC20** | Standard ERC-20 tokens deployed on peaq. | | **Precompile** | Native precompiled contracts for system tokens. | | **LST** | Liquid staking tokens (e.g., wrapped or derivative PEAQ). | ## Featured Tokens on peaq Mainnet | Symbol | Name | Category | Address | Notes | | ----------- | ---------------------------- | ----------------------- | -------------------------------------------- | ----------------------------------------------- | | **PEAQ** | peaq token | Native / Precompile | `0x0000000000000000000000000000000000000809` | Primary network token used for gas and staking. | | **WPEAQ** | Wrapped PEAQ | Wrapped Native / ERC-20 | `0x3cD66d2e1fac1751B0A20BeBF6cA4c9699Bb12d7` | ERC-20 Wrapped PEAQ. | | **USDC** | USD Coin | Stablecoin / OFT | `0xbba60da06c2c5424f03f7434542280fcad453d10` | Bridged via LayerZero Hydra from Ethereum. | | **USDT** | Tether | Stablecoin / OFT | `0xf4d9235269a96aadafc9adae454a0618ebe37949` | Bridged via LayerZero Hydra from Ethereum. | | **WETH** | Wrapped ETH | Bridged Native / OFT | `0x6694340fc020c5e6b96567843da2df01b2ce1eb6` | LayerZero Hydra OFT from Ethereum. | | **SLC** | Silencio | Native ERC-20 | `0x5c3126bfb9a68a7021d461230127470b3824886b` | Auditory layer for machine perception. | | **DEUS** | XMaquina (DEUS) | Native ERC-20 | `0x940a319b75861014a220d9c6c144d108552b089b` | Humanoid robotics and physical AI access. | | **RICE** | Rice Finance | Bridged ERC-20 / OFT | `0x1190fb50226b54958e284bd674eff346936af885` | Cross-chain AI research token from BNB Chain. | | **CODEC** | CodecFlow | Bridged ERC-20 / OFT | `0xee311e67825c22e5c7238546431651686ce91421` | Operator platform built on VLAs. | | **AUKI** | Auki Labs | Bridged ERC-20 / OFT | `0xf67db9d00401d9e883208882f5c100d7482b083d` | Spatial AI sensors for AR experiences. | | **OVR** | Over the Reality | Bridged ERC-20 / OFT | `0x08e6d50169863fd9beb88687ce1573cf9d6d1484` | Decentralized 3D mapping & physical AI. | | **ROBOT** | RoboStack | Bridged ERC-20 / OFT | `0xfd9d1b4a0384396a595345886d1e1d8abf439cef` | Robotics compute and environment modeling. | | **TOPS** | IG3 | Native ERC-20 | `0x6c1ca31a9f3a57bb680f82a8fe97fc00ac4aad21` | Edge network for real-time AI. | | **wstPEAQ** | Wrapped Parasail Staked PEAQ | LST / Native ERC-20 | `0xe5330a9fba99504c534127d39727729899c9a506` | Liquid staking representation of staked PEAQ. | | **ANYONE** | Anyone | Bridged ERC-20 / OFT | `0xe67F39FbE8C24ef8b3542efED1eE9963cEFC1f2a` | Decentralized VPN services. | | **BREW** | Homebrew Robotics Club | Bridged ERC-20 / OFT | `0x7f6c37d043d4c56371ab72f2f7c64d29e5793cdc` | Affordable robotics solutions. | > **Tip:** To verify any token address or metadata, refer directly to [peaqscan.xyz](https://peaqscan.xyz/) or [peaq.subscan.io](https://peaq.subscan.io/). ## How to Use the Token List You can integrate the token list in your dApp or service using standard JSON retrieval methods. ### Example (JavaScript) ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const TOKENLIST_URL = "https://tokenlist.peaq.xyz/"; async function fetchTokenList() { const response = await fetch(TOKENLIST_URL); const data = await response.json(); console.log("peaq tokens:", data.tokens); } fetchTokenList(); ``` # Tokenomics Source: https://docs.peaq.xyz/peaqchain/learn/tokenomics **Ticker:** PEAQ **Total supply at genesis:** 4,200,000,000 *Disclaimer: The earlier version of this page wrongfully referred to peaq’s* ***total*** *supply at genesis as its* ***max*** *supply. As the page also provided a breakdown of peaq’s inflation model, which wouldn’t have applied to a token with a capped supply, any confusion results from a simple and unfortunate copywriting error. With this disclaimer, the peaq foundation reaffirms its commitment to transparency and clarity of communication.* ## Utility Utility Like with any other Layer 1 blockchain's native asset, PEAQ’s primary utility is in enabling the most fundamental interactions on the network: paying transaction fees, producing blocks in a censorship-resistant way via a staking and slashing mechanism, and governing the network. Here is more information on these utilities: ### Transaction fees  PEAQ is used for transaction fee payment on the peaq blockchain. The amount of PEAQ needed for any particular transaction is calculated based on the weight, length, and other parameters of the transaction. As with other layer-1 blockchain, it is impossible to use the network without PEAQ, as no transactions can be carried out without paying the fee in PEAQ. Just like with dApps on Ethereum, all transactions from DePINs on peaq will require a fee in PEAQ. The millions of machines, vehicles, and sensors across these DePINs are expected to generate billions of transactions. ### Staking peaq relies on the work of Validators and Delegators for block production. To ensure that blocks are produced in an honest, censorship-resistant and reliable way, peaq has a staking mechanism, which incentivizes honest work from Validators and Delegators. Validators need to provide a staking deposit to run a Validator node to have “skin in the game”. Delegators can delegate their stake to Validators of their choice in order to back them. Only those Validators with enough backing (stake) are able to produce blocks. Thus Delegators actively govern which Validators are producing blocks and can thereby ensure that trusted and well operating Validators are active. If the Validator misbehaves or goes offline, its Delegator stops earning. Thus Delegators need to continuously check Validators’ behavior and manage their stake allocation actively. ### Governance Holding PEAQ will enable you to gradually guide the network by voting on key decisions via onchain governance concerning its development and future. **Initial Post-Launch Period:** Community suggestions are gathered via polls, AMAs, and calls. The foundation holds veto power to safeguard stability. **Midterm:** Governance shifts onchain. PEAQ holders, the Council and the Technical Committee can propose upgrades, parameter changes, or treasury spending. Council decisions can be overturned by the community; the Technical Committee can fast-track urgent or block risky proposals. **Longterm:** A fully open, advanced onchain governance model enables any PEAQ holder to propose changes and vote transparently onchain. Safeguards promote broad participation and approved proposals are implemented automatically. The community also manages funds via onchain governance. ## Inflation The inflation rate is initially set at 3.5%, ensuring sufficient incentives for early adopters. However, the inflation rate will decrease annually by 10% and will stabilize once the network reaches 1% inflation, following a disinflationary model. The first disinflation will occur at target block **7,890,590**. In the future, the community will be able to vote on adjustments to the inflation/deflation model via the network's governance. ### Disinflation Schedule The following table outlines the disinflation schedule, showing the inflation rate each year, newly minted tokens, and end-of-year supply: | Year | Inflation Rate | Newly Minted Tokens | End-of-Year Supply | | ---- | -------------- | ------------------- | ------------------ | | 0 | – | – | 4,200,000,000.00 | | 1 | 3.50% | 147,000,000.00 | 4,347,000,000.00 | | 2 | 3.15% | 136,930,500.00 | 4,483,930,500.00 | | 3 | 2.84% | 127,119,429.68 | 4,611,049,929.68 | | 4 | 2.55% | 117,650,939.63 | 4,728,700,869.31 | | 5 | 2.30% | 108,587,521.76 | 4,837,288,391.07 | | 6 | 2.07% | 99,998,971.96 | 4,937,287,363.03 | | 7 | 1.86% | 91,948,808.82 | 5,029,236,171.85 | | 8 | 1.67% | 84,516,259.87 | 5,113,752,431.72 | | 9 | 1.51% | 77,781,209.64 | 5,191,533,641.36 | | 10 | 1.36% | 71,819,757.27 | 5,263,353,398.63 | | 11 | 1.22% | 66,701,842.14 | 5,330,055,240.77 | | 12 | 1.10% | 62,490,937.16 | 5,392,546,177.93 | | 13 | 1.00% | 53,925,461.78 | 5,446,471,639.71 | | 14 | 1.00% | 54,464,716.40 | 5,500,936,356.11 | | 15 | 1.00% | 55,009,363.56 | 5,555,945,719.67 | | 16 | 1.00% | 55,559,457.20 | 5,611,505,176.87 | | 17 | 1.00% | 56,115,051.77 | 5,667,620,228.64 | The schedule above reaches a **maximum supply of approximately 5.67 billion PEAQ**. The allocation model follows a disinflationary supply framework: the total token supply increases gradually according to this predefined schedule until it reaches that capped amount. This mechanism supports predictable token issuance and prevents uncontrolled supply expansion. ## Allocation at genesis *The table is horizontally scrollable →* | **Allocation Category** | **Supply %** | **Distribution Address** | **Allocation Subcategory** | **Address Type** | **Supply %** | **Token Amount** | **Available at TGE** | **Lock-Up (Months)** | **Release after Lock-Up** | **Linear Vesting (Months)** | | ----------------------- | ------------ | ------------------------------------------------------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------ | ---------------- | -------------------- | -------------------- | ------------------------- | --------------------------- | | Investors | 34 | [5Ejz...rbk3](https://peaq.subscan.io/account/5EjzcRN7j5okZsQ9ujWTXZopzrxAdbAnbWTYWK6z7F74rbk3) | Funding (Pre Seed) | / | 7 | 294,000,000 | 11,025,000 | 6 | 11,025,000 | 24 | | | | | Funding (Seed) | / | 5 | 210,000,000 | 13,125,000 | 6 | 13,125,000 | 24 | | | | | Funding (Private) | / | 13 | 546,000,000 | 40,950,000 | 6 | 40,950,000 | 18 | | | | | Funding (Pre Launch Private) | / | 9 | 378,000,000 | 28,350,000 | 6 | 28,350,000 | 18 | | Community | 21 | [5FqM...61NJ](https://peaq.subscan.io/account/5FqM4JGTBE1upbmCX8GbRRdYKRgVixZF2bg1JJioh94161NJ) | Community (Community Sale) | LayerZero contract:[0xe410...D862](https://peaq.subscan.io/account/0xe4103e80c967f58591a1d7cA443ed7E392FeD862) | 6 | 252,000,000 | 40,950,000 | 0 | 0 | 6 | | | | | Community (Get Real Campaign) | Foundation managed:[0xB22E...097](https://peaq.subscan.io/account/0xB22E7ebC96b27d9074F6724D5300f964691FA097) | 5 | 210,000,000 | 0 | 3 | 0 | 0 | | | | | Community (Early Adopters Campaign) | Foundation managed:[0x1A4e...bf5b](https://peaq.subscan.io/account/0x1A4ec1fd72Eb76DBe6109038a93f456c7cc1bf5b) | 2.53 | 106,260,000 | 0 | 3 | 0 | 0 | | | | | Community (Initial Community Campaign) | Further distribution address:[5HbJd...JEP](https://peaq.subscan.io/account/5HbJdBpyBwWMo8do8qvts3eHXpgZWhReqBwCRDmypDAJeJEP) | 2 | 84,000,000 | 84,000,000 | 0 | 0 | 0 | | | | | Community (Community Reserve) | Foundation managed:[0xe0a4...9d8F](https://peaq.subscan.io/account/0xe0a4B8d19Fb1E80b4EFbBd30Bd3D3a4158Db9d8F) | 5.47 | 229,740,000 | 0 | 0 | 0 | 0 | | Core Contributors | 11.5 | [5FqVA...DymA](https://peaq.subscan.io/account/5FqVAJqB9y51KUvN8T53oykTo9xZcMuDqE3Miyw1wZygDymA) | Core Contributors | / | 11.5 | 483,000,000 | 0 | 9 | 0 | 36 | | | 8.5 | [5GEde...sJVY](https://peaq.subscan.io/account/5GEdexSEePuLC6PutqFtewhM5mvasKBTuN9vvybrR2gYsJVY) | EoT Labs | / | 8.5 | 357,000,000 | 0 | 9 | 0 | 36 | | Network Security | 5 | [5F4r...Xyy](https://peaq.subscan.io/account/5F4rNUSF2PfLZoB1RnhhYcSRXN3VjWpUR22YFjEg4wjUaXyy) | Network Security (Security Reserve) | Foundation managed:[0x355A...1913](https://peaq.subscan.io/account/0x355A1EC71E87777Cf77e3181f96A2825073f1913) | 3.4583 | 145,248,600 | 0 | 0 | 0 | 0 | | | | | Network Security (Genesis Node Set) | Further distribution address:[5GGkf...SZMx](https://peaq.subscan.io/account/5GGkfFzRctse5eXmZ99ic9XPaCw6zjd985fVYkg3hcSDSZMx) | 0.0417 | 1,750,140 | 1,750,140 | 0 | 0 | 0 | | | | | Network Security (Core Time Lease) | / | 1.5 | 63,000,000 | 0 | 0 | 0 | 24 | | Ecosystem & Treasury | 20 | [5FWuQ...Exdo](https://peaq.subscan.io/account/5FWuQcik2gqEUuhrFMLoEWTNUMmjcpCZfhYQGGNVy5pzExdo) | Ecosystem & Treasury (Market Making & Liquidity) | Further distribution address:[5GEdz...ANWAF](https://peaq.subscan.io/account/5GEdzozaR1sgdUiU6QYhmiVoaGYVYYMbrF1i6yHnso5ANWAF) | 3.75 | 157,500,000 | 157,500,000 | 0 | 0 | 0 | | | | | Ecosystem & Treasury (Reserve) | Foundation managed:[0x4b4c...D49](https://peaq.subscan.io/account/0x4b4c2aABaDA91534B217a5B70E3b43A678F88D49) | 11.18 | 469,560,000 | 0 | 0 | 0 | 0 | | | | | Ecosystem & Treasury (Capital Contributions) | Foundation managed:[0x45AD...33B](https://peaq.subscan.io/account/0x45ADD7eEcF69bB6dE394cDaF317aCA96a4F3b33B) | 0.3 | 12,600,000 | 0 | 0 | 0 | 0 | | | | | Ecosystem & Treasury (Grants) | Foundation managed:[0x46E9...83C0](https://peaq.subscan.io/account/0x46E967F90f9426823603db6765123Ba27AB983C0) | 1.77 | 74,291,837.77 | 0 | 0 | 0 | 0 | | | | | Ecosystem & Treasury (Expansion Reserve) | Foundation managed:[0x7B38...455](https://peaq.subscan.io/account/0x7B38b140d02635f5a983E14467D70e7fE829e455) | 3 | 126,000,000 | 0 | 0 | 0 | 0 | ## Unlocks | **Month** | **PEAQ Unlock** | **Inflation**\* | **Locked PEAQ** | **Unlocked Supply**\*\* | **% Unlocked** | **Total Supply** | | --------- | --------------- | --------------- | --------------- | ----------------------- | -------------- | ---------------- | | 0 | 377,650,940 | 0 | 3,822,349,060 | 377,650,940 | 8.99% | 4,200,000,000 | | 1 | 39,900,000 | 12,250,000 | 3,789,799,060 | 422,450,940 | 10.03% | 4,212,250,000 | | 2 | 39,900,000 | 12,250,000 | 3,757,249,060 | 467,250,940 | 11.06% | 4,224,500,000 | | 3 | 75,110,000 | 12,250,000 | 3,689,489,060 | 547,260,940 | 12.92% | 4,236,750,000 | | 4 | 75,110,000 | 12,250,000 | 3,621,729,060 | 627,270,940 | 14.76% | 4,249,000,000 | | 5 | 75,110,000 | 12,250,000 | 3,553,969,060 | 707,280,940 | 16.60% | 4,261,250,000 | | 6 | 73,010,000 | 12,250,000 | 3,488,309,060 | 785,190,940 | 18.37% | 4,273,500,000 | | 7 | 131,285,000 | 12,250,000 | 3,364,374,060 | 921,375,940 | 21.50% | 4,285,750,000 | | 8 | 100,455,833 | 12,250,000 | 3,271,268,226 | 1,026,731,773 | 23.89% | 4,298,000,000 | | 9 | 106,079,167 | 12,250,000 | 3,172,539,060 | 1,137,710,940 | 26.40% | 4,310,250,000 | | 10 | 106,079,167 | 12,250,000 | 3,073,809,893 | 1,248,690,107 | 28.89% | 4,322,500,000 | | 11 | 106,079,167 | 12,250,000 | 2,975,080,726 | 1,359,669,273 | 31.37% | 4,334,750,000 | | 12 | 106,079,167 | 12,250,000 | 2,876,351,560 | 1,470,648,440 | 33.83% | 4,347,000,000 | | 13 | 106,079,167 | 11,410,875 | 2,777,118,918 | 1,581,291,957 | 36.28% | 4,358,410,875 | | 14 | 106,079,167 | 11,410,875 | 2,677,886,276 | 1,691,935,474 | 38.72% | 4,369,821,750 | | 15 | 88,579,167 | 11,410,875 | 2,596,153,635 | 1,785,078,990 | 40.74% | 4,381,232,625 | | 16 | 88,579,167 | 11,410,875 | 2,514,420,993 | 1,878,222,507 | 42.76% | 4,392,643,500 | | 17 | 88,579,167 | 11,410,875 | 2,432,688,351 | 1,971,366,024 | 44.76% | 4,404,054,375 | | 18 | 88,579,167 | 11,410,875 | 2,350,955,709 | 2,064,509,540 | 46.76% | 4,415,465,250 | | 19 | 88,579,167 | 11,410,875 | 2,269,223,068 | 2,157,653,057 | 48.74% | 4,426,876,125 | | 20 | 88,579,167 | 11,410,875 | 2,187,490,426 | 2,250,796,574 | 50.71% | 4,438,287,000 | | 21 | 88,579,167 | 11,410,875 | 2,105,757,784 | 2,343,940,090 | 52.68% | 4,449,697,875 | | 22 | 88,579,167 | 11,410,875 | 2,024,025,143 | 2,437,083,607 | 54.63% | 4,461,108,750 | | 23 | 88,579,167 | 11,410,875 | 1,942,292,501 | 2,530,227,124 | 56.57% | 4,472,519,625 | | 24 | 88,579,167 | 11,410,875 | 1,860,559,859 | 2,623,370,641 | 58.51% | 4,483,930,500 | | 25 | 85,954,167 | 10,596,272 | 1,780,963,456 | 2,713,563,316 | 60.37% | 4,494,526,772 | | 26 | 42,320,833 | 10,596,272 | 1,745,000,385 | 2,760,122,658 | 61.27% | 4,505,123,043 | | 27 | 42,320,833 | 10,596,272 | 1,709,037,315 | 2,806,682,000 | 62.15% | 4,515,719,315 | | 28 | 42,320,833 | 10,596,272 | 1,673,074,245 | 2,853,241,342 | 63.04% | 4,526,315,587 | | 29 | 42,320,833 | 10,596,272 | 1,637,111,175 | 2,899,800,684 | 63.92% | 4,536,911,859 | | 30 | 42,320,833 | 10,596,272 | 1,601,148,104 | 2,946,360,027 | 64.79% | 4,547,508,131 | | 31 | 42,320,833 | 10,596,272 | 1,565,185,034 | 2,992,919,369 | 65.66% | 4,558,104,403 | | 32 | 23,333,333 | 10,596,272 | 1,548,209,464 | 3,020,491,211 | 66.11% | 4,568,700,675 | | 33 | 23,333,333 | 10,596,272 | 1,531,233,894 | 3,048,063,053 | 66.56% | 4,579,296,947 | | 34 | 23,333,333 | 10,596,272 | 1,514,258,323 | 3,075,634,895 | 67.01% | 4,589,893,218 | | 35 | 23,333,333 | 10,596,272 | 1,497,282,753 | 3,103,206,737 | 67.45% | 4,600,489,490 | | 36 | 23,333,333 | 10,596,272 | 1,480,307,183 | 3,130,778,579 | 67.90% | 4,611,085,762 | | 37 | 23,333,333 | 9,804,321 | 1,462,856,442 | 3,158,033,641 | 68.34% | 4,620,890,083 | | 38 | 23,333,333 | 9,804,321 | 1,445,405,701 | 3,185,288,703 | 68.79% | 4,630,694,404 | | 39 | 23,333,333 | 9,804,321 | 1,427,954,961 | 3,212,543,765 | 69.23% | 4,640,498,725 | | 40 | 23,333,333 | 9,804,321 | 1,410,504,220 | 3,239,798,826 | 69.67% | 4,650,303,046 | | 41 | 23,333,333 | 9,804,321 | 1,393,053,479 | 3,267,053,888 | 70.11% | 4,660,107,368 | | 42 | 23,333,333 | 9,804,321 | 1,375,602,739 | 3,294,308,950 | 70.54% | 4,669,911,689 | | 43 | 23,333,333 | 9,804,321 | 1,358,151,998 | 3,321,564,012 | 70.98% | 4,679,716,010 | | 44 | 23,333,333 | 9,804,321 | 1,340,701,257 | 3,348,819,074 | 71.41% | 4,689,520,331 | | 45 | 0 | 9,804,321 | 1,346,583,850 | 3,352,740,802 | 71.35% | 4,699,324,652 | | 46 | 0 | 9,804,321 | 1,352,466,442 | 3,356,662,531 | 71.28% | 4,709,128,973 | ### Inflation impact on circulating supply The inflation has the following impact on the circulating supply: * 40% of yearly inflation goes into circulation through rewards for Delegators and Validators. This portion directly increases the circulating supply. * 60% of yearly inflation goes to the below described treasury buckets, remaining outside of the circulating supply initially. ### Unlocked tokens ≠ tokens in circulation According to the methodologies of CoinMarketCap and CoinGecko, tokens distributed during a public sale are part of the circulating supply, even if they remain locked. As a result, the 6% CoinList sale allocation are added to the circulating supply as of December 6, 2024. This update increases the circulating supply from 376,976,863 to 624,514,387 PEAQ on that date. ### peaq foundation managed addresses The following addresses are managed by the peaq foundation and not considered part of the circulating supply: * Community (Community Reserve): [0xe0a4B8d19Fb1E80b4EFbBd30Bd3D3a4158Db9d8F](https://peaq.subscan.io/account/0xe0a4b8d19fb1e80b4efbbd30bd3d3a4158db9d8f) * Network Security (Security Reserve): [0x355A1EC71E87777Cf77e3181f96A2825073f1913](https://peaq.subscan.io/account/0x355a1ec71e87777cf77e3181f96a2825073f1913) * Ecosystem & Treasury (Reserve): [0x4b4c2aABaDA91534B217a5B70E3b43A678F88D49](https://peaq.subscan.io/account/0x4b4c2aabada91534b217a5b70e3b43a678f88d49) * Ecosystem & Treasury (Grants): [0x46E967F90f9426823603db6765123Ba27AB983C0](https://peaq.subscan.io/account/0x46e967f90f9426823603db6765123ba27ab983c0) * Ecosystem & Treasury (Expansion Reserve): [0x7B38b140d02635f5a983E14467D70e7fE829e455](https://peaq.subscan.io/account/0x7b38b140d02635f5a983e14467d70e7fe829e455) ### Important note: locked tokens can be staked The vesting and lockup schedule applies only to the initial allocations of the total supply at genesis and does not apply to newly minted tokens generated by inflation. Tokens, regardless of their status (locked, unlocked, or under vesting), are eligible for staking. Users can participate as validators or delegators and stake their tokens. Staking rewards are immediately available and fully unlocked upon receipt. ### Important note: the unlock process may vary slightly Certain allocations are not locked or vested immediately at genesis. Specifically, unlock schedules related to community and ecosystem growth campaigns are based on projections and cannot be precisely determined at the outset. These unlocks are calculated using estimates and may be subject to adjustments over time. ## Inflation and transaction fee distribution The PEAQ token follows a disinflationary model, starting with an inflation rate of 3.5%, which decreases by 10% annually until it reaches 1%. Newly minted tokens and transaction fees are allocated to: ### Security **1. Validators and Delegators** — 40% - [modlpoolStake](https://peaq.subscan.io/account/5EYCAe5cKPAoFh2HnQQvpKqRYZGqBpaA87u4Zzw89qPE58is) This pool ensures efficient, reliable, and censorship-resistant block production. All funds are distributed directly to validators and delegators based on the validator's total stake and those of the delegators. Validators can set custom delegator fees. **2. Security Treasury** — 10% - [modlpoolCoret](https://peaq.subscan.io/account/5EYCAe5cKPAm67h3SeVgRKhZNE8fFKZwRreJo3kvGZAFjRe3) Funds in this pool are used to add additional security to peaq, such as the purchase of Coretime, which significantly increases peaq’s security and censorship resistance and provides peaq with the highest Nakamoto Coefficients \[decentralization index] in the industry. ### General Treasury **3. General Treasury** — 25% - [modlpy/trsry](https://peaq.subscan.io/account/5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z) The funds of this pool finance the ongoing operations of the ecosystem, supporting further research and development around the network, its core function set, and other key features. ### Native Incentive Pools **4. DePIN Incentive Pool** — 20% - [modlpoolDPInc](https://peaq.subscan.io/account/5EYCAe5cKPAmD11bPWaQjEYQ1GV7H56nqBpYt7XN2ZfKigDg) & [modlpoolDPStk](https://peaq.subscan.io/account/5EYCAe5cKPAmD15dKS4VkZXmNmuHpuBdBDF3113P6gBLRTZE) Funds allocated here are dedicated to incentivizing and rewarding the on-chain expansion of DePINs building on peaq, supporting their liquidity across various Machine DeFi protocols, and financing dedicated tooling and infrastructure for their development. **5. Machine Subsidization Pool** — 5% - [modlpoolSubsi](https://peaq.subscan.io/account/5EYCAe5cKPAoFioiNofJ5BLTBbhsYn7anzfPTNUi1mvrMFdv) These funds are used to subsidize the onboarding of connected, revenue-generating machines, vehicles, robots, and devices onto peaq as Machine RWAs, and to support their liquidity across various Machine DeFi protocols. #### Initial control of treasury pools Until on-chain governance is introduced, all Treasury pools are managed by the peaq foundation. The Validators and Stakers pool is not managed by the peaq foundation. #### Updating the distribution The above distribution presents an initial proposal by the peaq foundation. With future updates, the community will be able to adjust the distribution and usage of funds by voting via on-chain governance mechanisms. ### Treasury Transparency Reporting The following tables track all treasury transactions across the various treasury pools. Currently, no transactions have been made from any treasury pool, so all tables are empty. These tables will be updated as transactions occur. **A. [General Treasury](https://peaq.subscan.io/account/5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z) Transactions** | Date | Amount | Destination | Purpose | Tx Hash | | ---- | ------ | ----------- | ------- | ------- | | | | | | | **B. [Security Treasury](https://peaq.subscan.io/account/5EYCAe5cKPAm67h3SeVgRKhZNE8fFKZwRreJo3kvGZAFjRe3) Transactions** | Date | Amount | Destination | Purpose | Tx Hash | | ---- | ------ | ----------- | ------- | ------- | | | | | | | **C. DePIN Incentive Pool ([modlpoolDPInc](https://peaq.subscan.io/account/5EYCAe5cKPAmD11bPWaQjEYQ1GV7H56nqBpYt7XN2ZfKigDg) & [modlpoolDPStk](https://peaq.subscan.io/account/5EYCAe5cKPAmD15dKS4VkZXmNmuHpuBdBDF3113P6gBLRTZE)) Transactions** | Date | Amount | Destination | Purpose | Tx Hash | | ---- | ------ | ----------- | ------- | ------- | | | | | | | **D. [Machine Subsidization Pool](https://peaq.subscan.io/account/5EYCAe5cKPAoFioiNofJ5BLTBbhsYn7anzfPTNUi1mvrMFdv) Transactions** | Date | Amount | Destination | Purpose | Tx Hash | | ---- | ------ | ----------- | ------- | ------- | | | | | | | # SDK Reference Source: https://docs.peaq.xyz/peaqchain/sdk-reference/home Welcome to the peaq SDK reference documentation. Our SDKs enable developers to build applications that interact with the peaq network, providing tools for creating, managing, and interacting with decentralized machine identities, services, and data. ## Available SDKs Currently, we offer the following SDK: Power any machine — from humanoids to drones — with on-chain identity, data, and payments on peaq. Start building using the JavaScript SDK. Our JavaScript SDK provides a comprehensive set of tools for web and Node.js applications to interact with the peaq network. New SDK to get you building on peaq with Python! Build regulated security tokens and digital assets on peaq. ## Core Features All peaq SDKs provide access to the following core functionalities: * **Identity Management**: Create and manage decentralized identities for machines, services, and users. * **Data Verification**: Validate and verify data integrity by reading DIDs running validation logic on the peaq network. * **Storage**: Store CIDs, hash values, etc. using peaq storage. * **Role Based Access Control**: Create your own management system by utilizing RBAC functions. (Python - coming soon) * **Precompile Interaction**: Interact with peaq pallets via precompiles for EVM transactions. * **Transaction Processing**: Create, sign, and submit transactions to the peaq network. ## Support If you have questions or need assistance with any of our SDKs, please: * Join our [Discord community](https://discord.com/invite/XhJwuFKAAD) * Check out our [GitHub repositories](https://github.com/peaqnetwork) ## Feedback We're continuously improving our SDKs based on developer feedback. If you have suggestions or encounter issues, please open an issue in the respective GitHub repository or contribute directly through pull requests. # Create Instance Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/create-instance The [JavaScript SDK](https://www.npmjs.com/package/@peaq-network/sdk) supports EVM and Substrate transactions. In order to communicate and generate transactions using the peaq blockchain an instance must be created. ## createInstance(baseUrl, chainType, seed) | Parameter | Type | EVM | Substrate | Description | | ------------- | ----------- | -------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **baseUrl** | `string` | Required | Required | RPC/WSS URL used to connect to the blockchain. Reference [Connecting to peaq](/peaqchain/build/getting-started/connecting-to-peaq) for supported URLs. Use **RPC** for EVM and **WSS** for Substrate (and EVM reads). | | **chainType** | `ChainType` | Required | Optional | Defines whether the instance will generate EVM or Substrate transactions (defaults to Substrate). Accessible via the SDK. | | **seed** | `string` | N/A | Optional | Mnemonic phrase used in Substrate instances to execute write transactions on behalf of the user. | ```javascript EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // No direct API connection made to EVM so disconnect is unnecessary. ``` ```javascript Substrate theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env['SUBSTRATE_SEED']; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // disconnect when finished await sdk.disconnect(); ``` Now you will have an **sdk** object that can be used to execute the [DID](/peaqchain/sdk-reference/javascript/did-operations), [RBAC](/peaqchain/sdk-reference/javascript/rbac-operations), and [Storage](/peaqchain/sdk-reference/javascript/storage-operations) operations on the peaq blockchain. The EVM instance only constructs transactions objects, and does not send the transactions itself. This gives the user the ability to send peaq transactions themselves without ever sending a private key through the sdk. However, if you would like this process abstracted away, we do provide a [Send Evm Transaction](/peaqchain/sdk-reference/javascript/send-evm-tx) function. To mitigate concerns on how we use your seed/private key we have made our [peaq js](https://github.com/peaqnetwork/peaq-js) SDK fully open source. # DID Operations Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/did-operations This concept has been absorbed into peaqOS. See [peaqID](/peaqos/concepts/peaqid) for the current identity model. Using the instance created on the previous pages you are able to execute the peaq DID functionalities that allow for the creation of a DID Document. ## create(name, address, seed, customDocumentFields) Enables the creation of a decentralized identity on-chain for your given entity. | Parameter | Type | EVM | Substrate | Description | | ------------------------ | ---------------------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | **name** | `string` | Required | Required | Name of the DID to be created. | | **address** | `string` | Required | Optional | Wallet address that is used to create the DID Document. If seed set for substrate, defaults to public address for that seed. | | **seed** | `string` | N/A | Optional | Mnemonic phrase used in Substrate instances to execute write transactions on behalf of the wallet. Not necessary when seed set at instance creation. | | **customDocumentFields** | `CustomDocumentFields` | Optional | Optional | Used to set specific fields of the DID Document. If none is set, an empty document will be generated with id and controller set to the signer's address. | ### customDocumentFields | Parameter | Type | EVM | Substrate | Description | | ----------------- | ---------------- | -------- | --------- | ------------------------------------------------------------------------------------------- | | **verifications** | `Verification[]` | Optional | Optional | Stores the verification method that is tied to the wallet used to create this DID Document. | | **signature** | `Signature` | Optional | Optional | Used to store an issuer's signature. | | **services** | `Services[]` | Optional | Optional | Links data, endpoints, or other metadata to the DID Document. | #### Verification | Parameter | Type | EVM | Substrate | Description | | ---------------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **type** | `string` | Optional | Optional | Allows user to manually set the type of verification to be used with the public key tied to the DID Document.

**EVM Wallets:**
- `EcdsaSecp256k1RecoveryMethod2020`
**Substrate Wallets:**
- `Ed25519VerificationKey2020`
- `Sr25519VerificationKey2020` | | **publicKeyMultibase** | `string` | Optional | Optional | Allows user to manually set the public Key MultiBase they would like to use for their verification method. | #### Signature | Parameter | Type | EVM | Substrate | Description | | ---------- | -------- | -------- | --------- | ----------------------------------------------- | | **type** | `string` | Required | Required | The algorithm used when generating a signature. | | **issuer** | `string` | Required | Required | Entity that issued the signature. | | **hash** | `string` | Required | Required | Hash value of the generated signature. | #### Services | Parameter | Type | EVM | Substrate | Description | | ------------------- | -------- | -------- | --------- | --------------------------------------------------------------- | | **id** | `string` | Required | Required | Identifier used to indicate what type of service is being used. | | **type** | `string` | Required | Required | Declares the type of service object being referenced. | | **serviceEndpoint** | `string` | Optional | Optional | URI/URL that is used to point to another data storage location. | | **data** | `string` | Optional | Optional | Data value that can be stored at this service. | ### Create DID Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const name = "DID_NAME"; // possible example of custom document fields a DePIN could create const customFields = { services: [{ id: "#ipfs", type: "machineData", serviceEndpoint: "https://ipfs.io/ipfs/QmYwAPJzv5CZsnAzt8auVTLv3jE9n1Tz8exzhTHtVMJd3t" }] }; const tx = await sdk.did.create({ name: name, address: EVM_ADDRESS, customDocumentFields: customFields }); // Create DID using sendEvmTx() function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.did.create({}) return object { to: '0x0000000000000000000000000000000000000800', data: '0xcc4a70ca0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084449445f4e414d45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018a3061333336343639363433613730363536313731336133303738333934353635363136323331363134333633363233313431333733303331363134353636343134323330333034363333363233383631333233373335363133333339333633343336333633343331343331323333363436393634336137303635363137313361333037383339343536353631363233313631343336333632333134313337333033313631343536363431343233303330343633333632333836313332333733353631333333393336333433363336333433313433326135393061303532333639373036363733313230623664363136333638363936653635343436313734363131613433363837343734373037333361326632663639373036363733326536393666326636393730363637333266353136643539373734313530346137613736333534333561373336653431376137343338363137353536353434633736333336613435333936653331353437613338363537383761363835343438373435363464346136343333373400000000000000000000000000000000000000000000' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000800', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x81d2c889f5563ff840ac05f5198a7bca7177aee11eb1dd6429cb53f171c42046', index: 0, blockHash: '0x800e6705226991336ccfc83207487567db7a3657a8f0ce3307221c6a4cf611cb', blockNumber: 4486798, logsBloom: '0x00000040000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 55057n, blobGasUsed: null, cumulativeGasUsed: 55057n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const name = "DID_NAME"; // possible example of custom document fields a DePIN could create const customFields = { services: [{ id: "#ipfs", type: "machineData", serviceEndpoint: "https://ipfs.io/ipfs/QmYwAPJzv5CZsnAzt8auVTLv3jE9n1Tz8exzhTHtVMJd3t" }] }; const response = await sdk.did.create({ name: name, customDocumentFields: customFields }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.did.create({}) return object { block_hash: '0x47bc5172ac83ac9088cb301e3072e114b0da174ac902afb5595422d2074f0376', unsubscribe: [Function (anonymous)] } ``` ## read(name, address, wssBaseUrl) Allows a user to read a previously created DID from the chain. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **name** | `string` | Required | Required | Name of the DID stored. | | **address** | `string` | Required | Optional | Address of the wallet that created the DID Document. Reading from an EVM tx requires an address to be explicitly set. If a seed has been set for Substrate, the address tied to that phrase will be used. | | **wssBaseUrl** | `string` | Required | Optional | WSS URL must be used to read DID Document. EVM must explicitly set WSS (Substrate has it stored during instance creation). | ### Read DID Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const name = "DID_NAME"; const document = await sdk.did.read({ name: name, address: EVM_ADDRESS, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.did.read({}) return object { name: 'DID_NAME', value: '0a336469643a706561713a30783945656162316143636231413730316145664142303046336238613237356133393634363634314312336469643a706561713a3078394565616231614363623141373031614566414230304633623861323735613339363436363431432a590a052369706673120b6d616368696e65446174611a4368747470733a2f2f697066732e696f2f697066732f516d597741504a7a7635435a736e417a7438617556544c76336a45396e31547a3865787a68544874564d4a643374', validity: '4,294,967,295', created: '1,742,919,588,000', document: { id: 'did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', controller: 'did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', verificationMethods: [], signature: undefined, services: [ [Object] ], authentications: [] } } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; // can use read only or a read/write instance to read const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); const name = "DID_NAME"; // when using read only an address must be defined const document = await sdk.did.read({ name: name, address: SUBSTRATE_ADDRESS }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.did.read({}) return object { name: 'DID_NAME', value: '0a396469643a706561713a35446634326d6b7a744c746b6b736751754c7934595636686d687a646a5976446b6e6f7848763151426b61593132506712396469643a706561713a35446634326d6b7a744c746b6b736751754c7934595636686d687a646a5976446b6e6f7848763151426b6159313250672a590a052369706673120b6d616368696e65446174611a4368747470733a2f2f697066732e696f2f697066732f516d597741504a7a7635435a736e417a7438617556544c76336a45396e31547a3865787a68544874564d4a643374', validity: '4,294,967,295', created: '1,742,920,026,000', document: { id: 'did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg', controller: 'did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg', verificationMethods: [], signature: undefined, services: [ [Object] ], authentications: [] } } ``` ## update(name, address, seed, customDocumentFields) Allows the owner of the DID Document to update a previously created one. Uses the same parameters as createDid() function above for the Custom Document Fields. | Parameter | Type | EVM | Substrate | Description | | ------------------------ | ---------------------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | **name** | `string` | Required | Required | Name of the DID to be updated. | | **address** | `string` | Required | Optional | Wallet address that has the authority to update the DID Document. If Substrate seed at initialization, will default to the public address for that seed. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL must be used to read a DID Document. Used to read the previous DID Document at that location when updating a DID Document. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | | **customDocumentFields** | `CustomDocumentFields` | Optional | Optional | Used to update specific fields of the DID Document. Overwrites the previous DID Document so make sure you keep track of data previously stored. | ### Update DID Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const name = "DID_NAME"; // add a verification method and include the previosly set service const customFields = { verificationMethods: [{ type: "EcdsaSecp256k1RecoveryMethod2020" }], services: [{ id: "#ipfs", type: "machineData", serviceEndpoint: "https://ipfs.io/ipfs/QmYwAPJzv5CZsnAzt8auVTLv3jE9n1Tz8exzhTHtVMJd3t" }] }; const tx = await sdk.did.update({ name: name, address: EVM_ADDRESS, wssBaseUrl: WSS_BASE_URL, customDocumentFields: customFields }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // read to see the updated document const document = await sdk.did.read({ name: name, address: EVM_ADDRESS, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.did.update({}) return object { to: '0x0000000000000000000000000000000000000800', data: '0x68b4b2c10000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084449445f4e414d45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038630613333363436393634336137303635363137313361333037383339343536353631363233313631343336333632333134313337333033313631343536363431343233303330343633333632333836313332333733353631333333393336333433363336333433313433313233333634363936343361373036353631373133613330373833393435363536313632333136313433363336323331343133373330333136313435363634313432333033303436333336323338363133323337333536313333333933363334333633363334333134333161626630313061336136343639363433613730363536313731336133303738333934353635363136323331363134333633363233313431333733303331363134353636343134323330333034363333363233383631333233373335363133333339333633343336333633343331343332333662363537393733326433313132323034353633363437333631353336353633373033323335333636623331353236353633366637363635373237393464363537343638366636343332333033323330316133333634363936343361373036353631373133613330373833393435363536313632333136313433363336323331343133373330333136313435363634313432333033303436333336323338363133323337333536313333333933363334333633363334333134333232326133303738333934353635363136323331363134333633363233313431333733303331363134353636343134323330333034363333363233383631333233373335363133333339333633343336333633343331343332613539306130353233363937303636373331323062366436313633363836393665363534343631373436313161343336383734373437303733336132663266363937303636373332653639366632663639373036363733326635313664353937373431353034613761373633353433356137333665343137613734333836313735353635343463373633333661343533393665333135343761333836353738376136383534343837343536346434613634333337343332336136343639363433613730363536313731336133303738333934353635363136323331363134333633363233313431333733303331363134353636343134323330333034363333363233383631333233373335363133333339333633343336333633343331343332333662363537393733326433310000000000000000000000000000000000000000000000000000' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000800', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0xc6a36b8a0b9be9bd5326b3f0c31c3d04450c92a50cce0582ee362d94cf914aba', index: 0, blockHash: '0x3c94593083019c0d586c09df6cc55453f71b1bf544e0b85ef2ba6882c3878bf6', blockNumber: 4487165, logsBloom: '0x00000040000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000004000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000', gasUsed: 48529n, blobGasUsed: null, cumulativeGasUsed: 48529n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const name = "DID_NAME"; // add a verification method and include the previosly set service const customFields = { verificationMethods: [{ type: "Ed25519VerificationKey2020" }], services: [{ id: "#ipfs", type: "machineData", serviceEndpoint: "https://ipfs.io/ipfs/QmYwAPJzv5CZsnAzt8auVTLv3jE9n1Tz8exzhTHtVMJd3t" }] }; const response = await sdk.did.update({ name: name, customDocumentFields: customFields }); // read to see the updated document const document = await sdk.did.read({ name: name }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.did.update({}) return object { log: 'Successfully updated the DID Document of name DID_NAME at address 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg', block_hash: '0x0586bf5d7cf76f75cde30f35a213ffaa4d3ec6ccc1e94d63c3353f92f95fb019', unsubscribe: [Function (anonymous)] } ``` ## remove(name, address, seed) Deletes a previously created DID Document from the chain. | Parameter | Type | EVM | Substrate | Description | | ----------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **name** | `string` | Required | Required | Name of the DID to be deleted. | | **address** | `string` | Required | Optional | Wallet address that has the authority to delete the DID Document. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Remove DID Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const name = "DID_NAME"; const tx = await sdk.did.remove({ name: name, address: EVM_ADDRESS }); // Send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.did.remove({}) return object { to: '0x0000000000000000000000000000000000000800', data: '0xe8a816900000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000084449445f4e414d45000000000000000000000000000000000000000000000000' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000800', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0xd5c548f2e5ded77441ba494eac7da77717ba24b4ee0a62fbd34baf5f2db34e56', index: 0, blockHash: '0x9d3c9356102d84ff7350104d5286c34f0db7f2a5e3e9356e98362dc96902475a', blockNumber: 4488660, logsBloom: '0x00000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000', gasUsed: 44223n, blobGasUsed: null, cumulativeGasUsed: 44223n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const name = "DID_NAME"; const response = await sdk.did.remove({ name: name }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.did.remove({}) return object { log: 'Successfully removed the DID of name DID_NAME from address 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg', block_hash: '0x3a1e33a2bbee8e73fc405910bc56693b0d65ce5077456f2afd60f55250318cfa', unsubscribe: [Function (anonymous)] } ``` # Group Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/rbac-operations/group ## createGroup(groupName, groupId, seed) Used to create a new group within the RBAC (Role-Based Access Control) system. | Parameter | Type | EVM | Substrate | Description | | ------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **groupName** | `string` | Required | Required | Name of the group to be created. | | **groupId** | `string` | Optional | Optional | ID of the group. If not provided, a new ID will be generated. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Create Group Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const groupName = "peaq-group-1"; const group = await sdk.rbac.createGroup({ groupName: groupName }); // Send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: group.tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // Log to see what the auto generated groupId is console.log(group.groupId); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.createGroup({}) return object { tx: { to: '0x0000000000000000000000000000000000000802', data: '0x65c1e09c35386135323638342d336163652d346537322d613032352d38353038356231640000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000c706561712d67726f75702d310000000000000000000000000000000000000000' }, groupId: '58a52684-3ace-4e72-a025-85085b1d' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x07eb9b0e02ad279b04c4bea1affcc23ba1f5fc2e97f946a05e3db6cb36f32338', index: 0, blockHash: '0x686188b5eefb03c7ea40621a0e7d72459f1fa8387c7eb2e0cbab92c6eaeff04f', blockNumber: 4490069, logsBloom: '0x00000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 34430n, blobGasUsed: null, cumulativeGasUsed: 34430n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const groupName = "peaq-group-1"; const response = await sdk.rbac.createGroup({ groupName: groupName }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.createGroup({}) return object { groupId: '63d6417e-1a03-4fd8-9439-295ce979' } ``` For the EVM tx it returns in a different form than what was used throughout the sdk. The purpose of this is to show the group-id that was autogenerated if it was not manually set. That way you are able to take note of the group-id for that group-name set. ## fetchGroup(owner, groupId, wssBaseUrl) Fetches group information from the RBAC system based on the provided group ID and owner's address. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | -------------------------------------------- | | **owner** | `string` | Required | Required | Address representing the owner of the group. | | **groupId** | `string` | Required | Required | ID of the group to be fetched. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. | ### Fetch Group Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // example of a previously created group id const groupId = "58a52684-3ace-4e72-a025-85085b1d"; const response = await sdk.rbac.fetchGroup({ owner: EVM_ADDRESS, groupId: groupId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchGroup({}) response object { id: '58a52684-3ace-4e72-a025-85085b1d', name: 'peaq-group-1', enabled: true } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); // example of a previously created group id const groupId = "63d6417e-1a03-4fd8-9439-295ce979"; const response = await sdk.rbac.fetchGroup({ owner: SUBSTRATE_ADDRESS, groupId: groupId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchGroup({}) response object { id: '63d6417e-1a03-4fd8-9439-295ce979', name: 'peaq-group-1', enabled: true } ``` ## fetchGroups(owner, wssBaseUrl) Used to fetch all the groups associated with the passed owner address. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | --------------------------------------------------------- | | **owner** | `string` | Required | Required | Address representing the owner of all the fetched groups. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. | ### Fetch Groups Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const response = await sdk.rbac.fetchGroups({ owner: EVM_ADDRESS, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchGroups({}) response object [ { id: 'eda650fa-ec62-4d09-849b-b66c7771', name: 'group-name-123', enabled: true }, ... { id: '58a52684-3ace-4e72-a025-85085b1d', name: 'peaq-group-1', enabled: true } ] ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); const response = await sdk.rbac.fetchGroups({ owner: SUBSTRATE_ADDRESS }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchGroups({}) response object [ { id: '9763c33e-2b1b-4834-a908-03f2a9e0', name: 'group-name-123', enabled: true }, ... { id: '63d6417e-1a03-4fd8-9439-295ce979', name: 'peaq-group-1', enabled: true } ] ``` ## updateGroup(groupName, groupId, seed) Allows the owner to update the group name. | Parameter | Type | EVM | Substrate | Description | | ------------- | ------ | -------- | --------- | -------------------------------------------------------------------------------------------- | | **groupName** | string | Required | Required | Updated group name. | | **groupId** | string | Required | Required | ID of the group (32 bytes) to be updated. | | **seed** | string | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Update Group Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // new group name that will be set const groupName = "peaq-group-new"; // example of a previously created group id const groupId = "58a52684-3ace-4e72-a025-85085b1d"; // build the evm tx const tx = await sdk.rbac.updateGroup({ groupName: groupName, groupId: groupId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // fetch Group to confirm the name change const response = await sdk.rbac.fetchGroup({ owner: EVM_ADDRESS, groupId: groupId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.updateGroup({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0xadb32ee135386135323638342d336163652d346537322d613032352d38353038356231640000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e706561712d67726f75702d6e6577000000000000000000000000000000000000' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0xf0f8c39077ae3c3d631579d9c71ef1d84729bb63bbc5ec8b75ed175f71034707', index: 0, blockHash: '0xdfcd14d7be74ef7680791ec7455821ffaa37d642f055d14331be791904535577', blockNumber: 4500445, logsBloom: '0x00000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000', gasUsed: 35283n, blobGasUsed: null, cumulativeGasUsed: 35283n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // new group name that will be set const groupName = "peaq-group-new"; // example of a previously created group id const groupId = "63d6417e-1a03-4fd8-9439-295ce979"; // build the evm tx const response = await sdk.rbac.updateGroup({ groupName: groupName, groupId: groupId }); // fetch Group to confirm the name change const fetchedGroup = await sdk.rbac.fetchGroup({ owner: SUBSTRATE_ADDRESS, groupId: groupId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.updateGroup({}) return object { message: 'Successfully update group 63d6417e-1a03-4fd8-9439-295ce979 with new name: peaq-group-new' } ``` ## disableGroup(groupId, seed) Disables a group within a permission management system. | Parameter | Type | EVM | Substrate | Description | | ----------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **groupId** | `string` | Required | Required | The unique identifier (ID) of the group to be disabled. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Disable Group Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // group id to disable const groupId = "58a52684-3ace-4e72-a025-85085b1d"; // build the evm tx const tx = await sdk.rbac.disableGroup({ groupId: groupId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // fetch group to confirm it has been changed to disabled const response = await sdk.rbac.fetchGroup({ owner: EVM_ADDRESS, groupId: groupId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.disableGroup({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0x47538f1235386135323638342d336163652d346537322d613032352d3835303835623164' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x4e70353fba441965af6fe54eb05c7968979f643892d7d02955c2cce78940cf7a', index: 0, blockHash: '0xe850a8b6aacf3ae4e06d127ae81b5420a3f0548ef41585eb1a7737768fa9d1e5', blockNumber: 4501188, logsBloom: '0x00000000000000000000000000000000000800000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 34680n, blobGasUsed: null, cumulativeGasUsed: 34680n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // group id to disable const groupId = "63d6417e-1a03-4fd8-9439-295ce979"; // execute the extrinsic const response = await sdk.rbac.disableGroup({ groupId: groupId }); // fetch group to confirm it has been changed to disabled const fetchedGroup = await sdk.rbac.fetchGroup({ owner: SUBSTRATE_ADDRESS, groupId: groupId, }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.disableGroup({}) return object { message: 'Successfully disable group 63d6417e-1a03-4fd8-9439-295ce979' } ``` ## assignRoleToGroup(groupId, roleId, seed) This function allows to assign a specific role to a group within a permission management system. | Parameter | Type | EVM | Substrate | Description | | ----------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **groupId** | `string` | Required | Required | The unique identifier of the group to which the role will be assigned. | | **roleId** | `string` | Required | Required | ID of the role that will be assigned to the group. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Assign Role to Group Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // group id (generated with createGroup) const groupId = "58a52684-3ace-4e72-a025-85085b1d"; // role id to assign to group const roleId = "ada650fa-ec62-4d09-849b-b66c7777"; // build the evm tx const tx = await sdk.rbac.assignRoleToGroup({ groupId: groupId, roleId: roleId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.assignRoleToGroup({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0x03c4d7fb62363861353538392d313238342d343965392d383237362d303335396134323935386135323638342d336163652d346537322d613032352d3835303835623164' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0xf65f894de591b5bbd7c3bb482c95ced55b7989ef07980cf7f469f19944ab02e1', index: 0, blockHash: '0x12c4ac10e6abed8148a7b6477b3c53cdabfbbfdb5abd42fa0f12aaba87aecf73', blockNumber: 4500565, logsBloom: '0x00000000000000000000000000000000000000000000080006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 31322n, blobGasUsed: null, cumulativeGasUsed: 31322n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // group id (generated with createGroup) const groupId = "63d6417e-1a03-4fd8-9439-295ce979"; // role id to assign to group const roleId = "bc3f20d5-c519-4048-8db1-4bbf48dc"; // execute extrinsic const response = await sdk.rbac.assignRoleToGroup({ groupId: groupId, roleId: roleId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.assignRoleToGroup({}) return object { message: 'Successfully assign role bc3f20d5-c519-4048-8db1-4bbf48dc to group 63d6417e-1a03-4fd8-9439-295ce979' } ``` ## fetchGroupRoles(owner, groupId, wssBaseUrl) Designed to retrieve roles associated with a specific group from the network's RBAC (Role-Based Access Control) system. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------------------- | | **owner** | `string` | Required | Required | Address of the owner of the roles. This is typically the account that manages the roles and permissions. | | **groupId** | `string` | Required | Required | Unique identifier of the group for whom roles are to be fetched. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. | ### Fetch Group Roles Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const groupId = "58a52684-3ace-4e72-a025-85085b1d"; const response = await sdk.rbac.fetchGroupRoles({ owner: EVM_ADDRESS, groupId: groupId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchGroupRoles({}) return object [ { role: 'b68a5589-1284-49e9-8276-0359a429', group: '58a52684-3ace-4e72-a025-85085b1d' } ] ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); const groupId = "63d6417e-1a03-4fd8-9439-295ce979"; const response = await sdk.rbac.fetchGroupRoles({ owner: SUBSTRATE_ADDRESS, groupId: groupId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchGroupRoles({}) return object [ { role: 'bc3f20d5-c519-4048-8db1-4bbf48dc', group: '63d6417e-1a03-4fd8-9439-295ce979' } ] ``` ## unassignRoleToGroup(groupId, roleId, seed) This function allows to unassign a specific role to a group in a permission management system. | Parameter | Type | EVM | Substrate | Description | | ----------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **groupId** | `string` | Required | Required | ID of the group from which the role should be unassigned. | | **roleId** | `string` | Required | Required | ID of the role that needs to be unassigned. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Unassign Role to Group Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // previously created groupId const groupId = "58a52684-3ace-4e72-a025-85085b1d"; // role id to unassign to group const roleId = "b68a5589-1284-49e9-8276-0359a429"; // build the evm tx const tx = await sdk.rbac.unassignRoleToGroup({ groupId: groupId, roleId: roleId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // fetch to see it has been unassigned const response = await sdk.rbac.fetchGroupRoles({ owner: EVM_ADDRESS, groupId: groupId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.unassignRoleToGroup({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0xad58943762363861353538392d313238342d343965392d383237362d303335396134323935386135323638342d336163652d346537322d613032352d3835303835623164' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x2a2134db9dd1e1ff3c70c5e350223c3d776e2e2822c8264bb2879a49b7d542da', index: 0, blockHash: '0x5e5dad9089357d1095b0b2177a26b13cce1f52f0e808f3f95260f588b4a9fe40', blockNumber: 4500781, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000040000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 29291n, blobGasUsed: null, cumulativeGasUsed: 29291n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // previously created groupId const groupId = "63d6417e-1a03-4fd8-9439-295ce979"; // role id to unassign to group const roleId = "bc3f20d5-c519-4048-8db1-4bbf48dc"; // send extrinsic const response = await sdk.rbac.unassignRoleToGroup({ groupId: groupId, roleId: roleId }); // fetch to see it has been unassigned (throws an error) const fetchedUserGroups = await sdk.rbac.fetchGroupRoles({ owner: SUBSTRATE_ADDRESS, groupId: groupId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.unassignRoleToGroup({}) return object { message: 'Successfully unassign role: bc3f20d5-c519-4048-8db1-4bbf48dc from group: 63d6417e-1a03-4fd8-9439-295ce979' } ``` ## assignUserToGroup(userId, groupId, address, seed) This function allows to assign a user to a group within the RBAC (Role-Based Access Control) system. | Parameter | Type | EVM | Substrate | Description | | ----------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------- | | **userId** | `string` | Required | Required | The unique identifier of the user to whom the group will be assigned. ID created by user and must be 32 bytes. | | **groupId** | `string` | Required | Required | ID of the group that will be assigned to the user. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Assign User to Group Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // user id to assign to group const userId = "9e8c7866-8435-4b76-8683-709a03c9"; // group id (generated with createGroup) const groupId = "58a52684-3ace-4e72-a025-85085b1d"; // build the evm tx const tx = await sdk.rbac.assignUserToGroup({ userId: userId, groupId: groupId, }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.assignUserToGroup({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0xa76110c439653863373836362d383433352d346237362d383638332d373039613033633935386135323638342d336163652d346537322d613032352d3835303835623164' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x9e1da68de86d6d1f68e600a6a525d117b4493be046be6fdfeda7a21a26c80cec', index: 0, blockHash: '0x24bc140d800cfc15d5c8fd45d201a8e4b31dfaa8aa044b1d799cb2a5cb7dc8d1', blockNumber: 4500957, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000001', gasUsed: 30248n, blobGasUsed: null, cumulativeGasUsed: 30248n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // user id to assign to group const userId = "9e8c7866-8435-4b76-8683-709a03c9"; // group id (generated with createGroup) const groupId = "63d6417e-1a03-4fd8-9439-295ce979"; // send the extrinsic const response = await sdk.rbac.assignUserToGroup({ userId: userId, groupId: groupId, }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.assignUserToGroup({}) return object { message: 'Successfully assign user 9e8c7866-8435-4b76-8683-709a03c9 to group 63d6417e-1a03-4fd8-9439-295ce979' } ``` ## fetchUserGroups(owner, userId, wssBaseUrl) Designed to retrieve groups associated with a specific user from the network's RBAC (Role-Based Access Control) system. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------------------- | | **owner** | `string` | Required | Required | Address of the owner of the roles. This is typically the account that manages the roles and permissions. | | **userId** | `string` | Required | Required | Unique identifier of the user for whom groups are to be fetched. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. | ### Fetch User Groups Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const userId = "9e8c7866-8435-4b76-8683-709a03c9"; const response = await sdk.rbac.fetchUserGroups({ owner: EVM_ADDRESS, userId: userId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchUserGroups({}) return object [ { user: '9e8c7866-8435-4b76-8683-709a03c9', group: '58a52684-3ace-4e72-a025-85085b1d' } ] ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); const userId = "9e8c7866-8435-4b76-8683-709a03c9"; const response = await sdk.rbac.fetchUserGroups({ owner: SUBSTRATE_ADDRESS, userId: userId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchUserGroups({}) return object [ { user: '9e8c7866-8435-4b76-8683-709a03c9', group: '63d6417e-1a03-4fd8-9439-295ce979' } ] ``` ## unassignUserToGroup(userId, groupId, address, seed) This function allows to unassign a specific user from a group in a permission management system. | Parameter | Type | EVM | Substrate | Description | | ----------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **userId** | `string` | Required | Required | ID of the user from which the group should be unassigned. | | **groupId** | `string` | Required | Required | ID of the group that needs to be unassigned. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Unassign User to Group Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // user id to unassign from group const userId = "9e8c7866-8435-4b76-8683-709a03c9"; // previously created groupId const groupId = "58a52684-3ace-4e72-a025-85085b1d"; // build the evm tx const tx = await sdk.rbac.unassignUserToGroup({ userId: userId, groupId: groupId, }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // fetch to see it has been unassigned const response = await sdk.rbac.fetchUserGroups({ owner: EVM_ADDRESS, userId: userId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.unassignUserToGroup({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0x0b9cc42839653863373836362d383433352d346237362d383638332d373039613033633935386135323638342d336163652d346537322d613032352d3835303835623164' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x1790d5bdc25d5ddd814e957a45a539400b4c717b5f0a25abec3d73896ac643ac', index: 0, blockHash: '0x3a862b4795987cdab60a62dd7d00fea2bd91e89b7a780a85af6c47c07b59ac65', blockNumber: 4501124, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000001000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 29169n, blobGasUsed: null, cumulativeGasUsed: 29169n, gasPrice: 100000943831n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // user id to unassign from group const userId = "9e8c7866-8435-4b76-8683-709a03c9"; // previously created groupId const groupId = "63d6417e-1a03-4fd8-9439-295ce979"; const response = await sdk.rbac.unassignUserToGroup({ userId: userId, groupId: groupId, }); // fetch to see it has been unassigned const fetchedUserGroups = await sdk.rbac.fetchUserGroups({ owner: SUBSTRATE_ADDRESS, userId: userId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.unassignUserToGroup({}) return object { message: 'Successfully unassign user: 9e8c7866-8435-4b76-8683-709a03c9 from group: 63d6417e-1a03-4fd8-9439-295ce979' } ``` # Permission Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/rbac-operations/permission ## createPermission(permissionName, permissionId, address, seed) Used to create a new permission within the RBAC (Role-Based Access Control) system. | Parameter | Type | EVM | Substrate | Description | | ------------------ | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **permissionName** | `string` | Required | Required | Name of the permission to be created. | | **permissionId** | `string` | Optional | Optional | ID of the permission (32 bytes). If not supplied one will be generated for you. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Create Permission Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const permissionName = "peaq-permission-1"; const permission = await sdk.rbac.createPermission({ permissionName: permissionName }); // Send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: permission.tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // log to see the generated id console.log(permission.permissionId); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.createPermission({}) return object { tx: { to: '0x0000000000000000000000000000000000000802', data: '0x67a2e51533316263386639372d633538372d343738342d613732352d623663373365656400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000011706561712d7065726d697373696f6e2d31000000000000000000000000000000' }, permissionId: '31bc8f97-c587-4784-a725-b6c73eed' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x66acb0dde4190d38536318a95376535b11d1c995dfa264e4c70690fa364b058d', index: 0, blockHash: '0xbe62e0bdb0b98645280bda895ff6d8a62ccc511d8c69c95d8c3494eb8be32be3', blockNumber: 4501439, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020008000000000000000000000000000000000008000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000', gasUsed: 34498n, blobGasUsed: null, cumulativeGasUsed: 34498n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const permissionName = "peaq-permission-1"; const response = await sdk.rbac.createPermission({ permissionName: permissionName }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.createPermission({}) return object { permissionId: '77395752-8588-47c5-8bd5-0d2bf273' } ``` The EVM tx returns in a different form than what was used throughout the sdk. The purpose of this is to show the permission-id that was autogenerated if it was not manually set. That way you are able to take note of the permission-id for that permission-name set. ## fetchPermission(owner, permissionId, wssBaseUrl) Fetch a permission at the given permissionId and address. | Parameter | Type | EVM | Substrate | Description | | ---------------- | -------- | -------- | --------- | ------------------------------------------------ | | **owner** | `string` | Required | Required | Address representing the owner of the permission | | **permissionId** | `string` | Required | Required | ID of the permission to be fetched. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. | ### Fetch Group Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // example of a previously created permission id const permissionId = "31bc8f97-c587-4784-a725-b6c73eed"; const response = await sdk.rbac.fetchPermission({ owner: EVM_ADDRESS, permissionId: permissionId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchPermission({}) response object { id: '31bc8f97-c587-4784-a725-b6c73eed', name: 'peaq-permission-1', enabled: true } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); const permissionId = "77395752-8588-47c5-8bd5-0d2bf273"; const response = await sdk.rbac.fetchPermission({ owner: SUBSTRATE_ADDRESS, permissionId: permissionId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchPermission({}) response object { id: '77395752-8588-47c5-8bd5-0d2bf273', name: 'peaq-permission-1', enabled: true } ``` ## fetchPermissions(owner, wssBaseUrl) Used to fetch all the permissions associated with the passed owner address | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | -------------------------------------------------------------- | | **owner** | `string` | Required | Required | Address representing the owner of all the fetched permissions. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. | ### Fetch Permissions Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const response = await sdk.rbac.fetchPermissions({ owner: EVM_ADDRESS, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchPermissions({}) response object [ { id: '88dec9d0-354e-4ab6-9856-7a7eb45c', name: 'permission-name-123', enabled: true }, ... { id: '31bc8f97-c587-4784-a725-b6c73eed', name: 'peaq-permission-1', enabled: true } ] ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); const response = await sdk.rbac.fetchPermissions({ owner: SUBSTRATE_ADDRESS }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchPermissions({}) response object [ { id: '21f7ad5f-0f41-40d5-b9f6-7ce0aad3', name: 'permission-name-123', enabled: true }, ... { id: '77395752-8588-47c5-8bd5-0d2bf273', name: 'peaq-permission-1', enabled: true } ] ``` ## updatePermission(permissionName, permissionId, address, seed) Allows the owner to update the permission name. | Parameter | Type | EVM | Substrate | Description | | ------------------ | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **permissionName** | `string` | Required | Required | Updated permission name. | | **permissionId** | `string` | Required | Required | ID of the permission (32 bytes) to be updated. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Update Permission Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // new permission name that will be set const permissionName = "peaq-permission-new"; // example of a previously created permission id const permissionId = "31bc8f97-c587-4784-a725-b6c73eed"; // build the evm tx const tx = await sdk.rbac.updatePermission({ permissionName: permissionName, permissionId: permissionId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // fetch permission to confirm the name change const response = await sdk.rbac.fetchPermission({ owner: EVM_ADDRESS, permissionId: permissionId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.updatePermission({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0xb0c5b5ad33316263386639372d633538372d343738342d613732352d623663373365656400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000013706561712d7065726d697373696f6e2d6e657700000000000000000000000000' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x5653326390b5277aad0e326eb9ecfe0a90bdef323c4e50c29be4a679044d3a30', index: 0, blockHash: '0xf95e4b31c28a23b6aff89d15411c9e36c833ec226ab3ced54325439124899458', blockNumber: 4501640, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000', gasUsed: 35429n, blobGasUsed: null, cumulativeGasUsed: 35429n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // new permission name that will be set const permissionName = "peaq-permission-new"; // example of a previously created permission id const permissionId = "77395752-8588-47c5-8bd5-0d2bf273"; const response = await sdk.rbac.updatePermission({ permissionName: permissionName, permissionId: permissionId }); // Fetch permission to confirm the name change const fetchedPermission = await sdk.rbac.fetchPermission({ owner: SUBSTRATE_ADDRESS, permissionId: permissionId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchPermission({}) return object { message: 'Successfully update permission 77395752-8588-47c5-8bd5-0d2bf273 with new name: peaq-permission-new' } ``` ## disablePermission(permissionId, address, seed) Disables a permission within a permission management system. | Parameter | Type | EVM | Substrate | Description | | ---------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **permissionId** | `string` | Required | Required | The unique identifier (ID) of the permission to be disabled. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Disable Permission Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // permission id to disable const permissionId = "31bc8f97-c587-4784-a725-b6c73eed"; // build the evm tx const tx = await sdk.rbac.disablePermission({ permissionId: permissionId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // Fetch permission to confirm it has been changed to disabled const response = await sdk.rbac.fetchPermission({ owner: EVM_ADDRESS, permissionId: permissionId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.disablePermission({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0x727e011e33316263386639372d633538372d343738342d613732352d6236633733656564' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0xb63e38df51375830ad61be4afa89b512a812375a0f84800c765821f82176f800', index: 1, blockHash: '0x13c29ec81311aad9dfce89630938885e1ca4f3f0b8a8fb81526072ba8fb05dc4', blockNumber: 4502232, logsBloom: '0x00000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000020000000000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 34682n, blobGasUsed: null, cumulativeGasUsed: 78842n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // permission id to disable const permissionId = "77395752-8588-47c5-8bd5-0d2bf273"; // execute the extrinsic const response = await sdk.rbac.disablePermission({ permissionId: permissionId }); // Fetch permission to confirm it has been changed to disabled const fetchedPermission = await sdk.rbac.fetchPermission({ owner: SUBSTRATE_ADDRESS, permissionId: permissionId, }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.disablePermission({}) return object { message: 'Successfully disable permission 77395752-8588-47c5-8bd5-0d2bf273' } ``` ## assignPermissionToRole(permissionId, roleId, address, seed) This function allows to assign a permission to a role in a permission management system. | Parameter | Type | EVM | Substrate | Description | | ---------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **permissionId** | `string` | Required | Required | ID of the permission to be assigned to the role. | | **roleId** | `string` | Required | Required | ID of the role to which the permission will be assigned. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Assign Permission to Role Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const permissionId = "31bc8f97-c587-4784-a725-b6c73eed"; const roleId = "b68a5589-1284-49e9-8276-0359a429"; // build the evm tx const tx = await sdk.rbac.assignPermissionToRole({ permissionId: permissionId, roleId: roleId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.assignPermissionToRole({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0x404147f133316263386639372d633538372d343738342d613732352d623663373365656462363861353538392d313238342d343965392d383237362d3033353961343239' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0xb3c8a7b2127754ef031cd3469afdec0aca6c8736e4b29373c9ad49a171a652c1', index: 2, blockHash: '0x2281ba090e6e1449ed5c5f0bd3ddff6231a4bf1782546a19cc1e0d7bcaa50eeb', blockNumber: 4501755, logsBloom: '0x00000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 31342n, blobGasUsed: null, cumulativeGasUsed: 73342n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const permissionId = "77395752-8588-47c5-8bd5-0d2bf273"; const roleId = "bc3f20d5-c519-4048-8db1-4bbf48dc"; const response = await sdk.rbac.assignPermissionToRole({ permissionId: permissionId, roleId: roleId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.assignPermissionToRole({}) return object { message: 'Successfully assign permission 77395752-8588-47c5-8bd5-0d2bf273 to role bc3f20d5-c519-4048-8db1-4bbf48dc' } ``` ## fetchRolePermissions(owner, roleId, wssBaseUrl) Designed to retrieve permissions associated with a specific role. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | ------------------------------------------------------ | | **owner** | `string` | Required | Required | Address of the owner of the roles. | | **roleId** | `string` | Required | Required | ID of the role for whom permissions are to be fetched. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. | ### Fetch Role Permissions Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const roleId = "b68a5589-1284-49e9-8276-0359a429"; const response = await sdk.rbac.fetchRolePermissions({ owner: EVM_ADDRESS, roleId: roleId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchRolePermissions({}) return object [ { permission: '31bc8f97-c587-4784-a725-b6c73eed', role: 'b68a5589-1284-49e9-8276-0359a429' } ] ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); const roleId = "bc3f20d5-c519-4048-8db1-4bbf48dc"; const response = await sdk.rbac.fetchRolePermissions({ owner: SUBSTRATE_ADDRESS, roleId: roleId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchRolePermissions({}) return object [ { permission: '77395752-8588-47c5-8bd5-0d2bf273', role: 'bc3f20d5-c519-4048-8db1-4bbf48dc' } ] ``` ## unassignPermissionToRole(permissionId, roleId, address, seed) This function allows to unassign a permission from a role in a permission management system. | Parameter | Type | EVM | Substrate | Description | | ---------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **permissionId** | `string` | Required | Required | ID of the permission to be unassigned. | | **roleId** | `string` | Required | Required | ID of the role from which to unassign the permission. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Unassign Permission from Role Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const permissionId = "31bc8f97-c587-4784-a725-b6c73eed"; const roleId = "b68a5589-1284-49e9-8276-0359a429"; const tx = await sdk.rbac.unassignPermissionToRole({ permissionId: permissionId, roleId: roleId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // fetch to see it has been unassigned const response = await sdk.rbac.fetchRolePermissions({ owner: EVM_ADDRESS, roleId: roleId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.unassignPermissionToRole({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0xba427c9e33316263386639372d633538372d343738342d613732352d623663373365656462363861353538392d313238342d343965392d383237362d3033353961343239' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x55678a0cbbbb5089acdaa15427b21879270d985f2bb82aaea331f5316d3ef566', index: 1, blockHash: '0x5cce714cf1c11ed6f65680458bf989e69b3749fa0b0b85124176119ae6cc1ea9', blockNumber: 4501891, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 29178n, blobGasUsed: null, cumulativeGasUsed: 68884n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const permissionId = "77395752-8588-47c5-8bd5-0d2bf273"; const roleId = "bc3f20d5-c519-4048-8db1-4bbf48dc"; const response = await sdk.rbac.unassignPermissionToRole({ permissionId: permissionId, roleId: roleId }); // fetch to see it has been unassigned const fetchedRolePermissions = await sdk.rbac.fetchRolePermissions({ owner: SUBSTRATE_ADDRESS, roleId: roleId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.unassignPermissionToRole({}) return object { message: 'Successfully unassign role: bc3f20d5-c519-4048-8db1-4bbf48dc from permission: 77395752-8588-47c5-8bd5-0d2bf273' } ``` ## fetchUserPermission(owner, userId, wssBaseUrl) Fetches the permissions associated with a user. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | ------------------------------------------------------ | | **owner** | `string` | Required | Required | Address of the owner of the permissions. | | **userId** | `string` | Required | Required | ID of the user for whom you want to fetch permissions. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. | In order to fetch a user permission the following flow must take place: 1. Create permission 2. Create role 3. Assign permission to role 4. Assign role to user 5. Fetch user permissions The following code shows this flow. ### Fetch User Permissions Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // 1. Create a permission const permissionName = "peaq-permission-2"; const txPermission = await sdk.rbac.createPermission({permissionName: permissionName}); await Sdk.sendEvmTx({tx: txPermission.tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE}); // 2. Create a role const roleName = "peaq-role-2"; const txRole = await sdk.rbac.createRole({roleName: roleName}); await Sdk.sendEvmTx({tx: txRole.tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE}); // 3. Assign permission to role const tx = await sdk.rbac.assignPermissionToRole({permissionId: txPermission.permissionId, roleId: txRole.roleId}); await Sdk.sendEvmTx({tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE}); // 4. Assign role to user const userId = "9e8c7866-8435-4b76-8683-709a03c9"; const tx2 = await sdk.rbac.assignRoleToUser({userId: userId, roleId: txRole.roleId}); await Sdk.sendEvmTx({tx: tx2, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE}); // 5. Fetch user permissions const response = await sdk.rbac.fetchUserPermissions({owner: EVM_ADDRESS, userId: userId, wssBaseUrl: WSS_BASE_URL}); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchUserPermissions({}) return object [ { id: '763207f6-32ef-4236-969d-d1b81ac4', name: 'peaq-permission-2', enabled: true } ] ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // 1. Create a permission const permissionName = "peaq-permission-2"; const txPermission = await sdk.rbac.createPermission({permissionName: permissionName}); // 2. Create a role const roleName = "peaq-role-2"; const txRole = await sdk.rbac.createRole({roleName: roleName}); // 3. Assign permission to role const p2rResp = await sdk.rbac.assignPermissionToRole({permissionId: txPermission.permissionId, roleId: txRole.roleId}); // 4. Assign role to user const userId = "9e8c7866-8435-4b76-8683-709a03c9"; const r2uResp = await sdk.rbac.assignRoleToUser({userId: userId, roleId: txRole.roleId}); // 5. Fetch user permissions const response = await sdk.rbac.fetchUserPermissions({owner: SUBSTRATE_ADDRESS, userId: userId}); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchUserPermissions({}) return object [ { id: 'fc49ee05-f0ad-47ce-b336-62efdfec', name: 'peaq-permission-2', enabled: true } ] ``` ## fetchGroupPermissions(owner, groupId, wssBaseUrl) Fetches the permissions associated with a group. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | ------------------------------------------------------- | | **owner** | `string` | Required | Required | Address of the owner of the permissions. | | **groupId** | `string` | Required | Required | ID of the group for whom you want to fetch permissions. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. | In order to fetch a group permission the following flow must take place: 1. Create permission 2. Create role 3. Assign permission to role 4. Create group 5. Assign role to group 6. Fetch group permissions The following code shows this flow. ### Fetch Group Permissions Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // 1. Create a permission const permissionName = "peaq-permission-2"; const txPermission = await sdk.rbac.createPermission({permissionName: permissionName}); await Sdk.sendEvmTx({tx: txPermission.tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE}); // 2. Create a role const roleName = "peaq-role-2"; const txRole = await sdk.rbac.createRole({roleName: roleName}); await Sdk.sendEvmTx({tx: txRole.tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE}); // 3. Assign permission to role const tx = await sdk.rbac.assignPermissionToRole({permissionId: txPermission.permissionId, roleId: txRole.roleId}); await Sdk.sendEvmTx({tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE}); // 4. Create group const groupName = "peaq-group-2"; const txGroup = await sdk.rbac.createGroup({groupName: groupName}); await Sdk.sendEvmTx({tx: txGroup.tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE}); // 5. Assign role to group const tx2 = await sdk.rbac.assignRoleToGroup({groupId: txGroup.groupId, roleId: txRole.roleId}); await Sdk.sendEvmTx({tx: tx2, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE}); // 6. Fetch group permissions const response = await sdk.rbac.fetchGroupPermissions({owner: EVM_ADDRESS, groupId: txGroup.groupId, wssBaseUrl: WSS_BASE_URL}); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchGroupPermissions({}) return object [ { id: '322f3070-826c-4dad-ba53-75fbfeee', name: 'peaq-permission-2', enabled: true } ] ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // 1. Create a permission const permissionName = "peaq-permission-2"; const txPermission = await sdk.rbac.createPermission({permissionName: permissionName}); // 2. Create a role const roleName = "peaq-role-2"; const txRole = await sdk.rbac.createRole({roleName: roleName}); // 3. Assign permission to role const p2rResp = await sdk.rbac.assignPermissionToRole({permissionId: txPermission.permissionId, roleId: txRole.roleId}); // 4. Create group const groupName = "peaq-group-2"; const txGroup = await sdk.rbac.createGroup({groupName: groupName}); // 5. Assign role to group const r2gResp = await sdk.rbac.assignRoleToGroup({groupId: txGroup.groupId, roleId: txRole.roleId}); // 6. Fetch group permissions const response = await sdk.rbac.fetchGroupPermissions({owner: SUBSTRATE_ADDRESS, groupId: txGroup.groupId}); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchGroupPermissions({}) return object [ { id: '158a1097-3ba4-4cae-b682-c58e7a4a', name: 'peaq-permission-2', enabled: true } ] ``` # Role Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/rbac-operations/role ## createRole(roleName, roleId, seed) Create a new role in the peaq network's Role-Based Access Control (RBAC) system. | Parameter | Type | EVM | Substrate | Description | | ------------ | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **roleName** | `string` | Required | Required | Name of the role to be created. | | **roleId** | `string` | Optional | Optional | ID of the role (32 bytes). If not supplied one will be generated for you. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Create Role Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const roleName = "peaq-role-1"; const role = await sdk.rbac.createRole({ roleName: roleName }); // Send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: role.tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // Log to see what the auto generated roleId is console.log(role.roleId); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.createRole({}) return object { tx: { to: '0x0000000000000000000000000000000000000802', data: '0xbca838d662363861353538392d313238342d343965392d383237362d30333539613432390000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b706561712d726f6c652d31000000000000000000000000000000000000000000' }, roleId: 'b68a5589-1284-49e9-8276-0359a429' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x9b3d1ae6b9045e36b0985de3f4704aa84e5fd0d02b6f84fc1091e124a7dad8ee', index: 0, blockHash: '0x62b334b67c157a754b76276244911a086fc13f0f951ee7a3d1d0277f74d393de', blockNumber: 4489206, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000002000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 34414n, blobGasUsed: null, cumulativeGasUsed: 34414n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const roleName = "peaq-role-1"; const response = await sdk.rbac.createRole({ roleName: roleName }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.createRole({}) return object { roleId: 'bc3f20d5-c519-4048-8db1-4bbf48dc' } ``` The EVM tx it returns in a different form than what was used throughout the sdk. The purpose of this is to show the role-id that was autogenerated if it was not manually set. ## fetchRole(owner, roleId, wssBaseUrl) Fetch a role at the given roleId and address associated. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | --------------------------------------------------------------------------------- | | **owner** | `string` | Required | Required | Address representing the owner of the role. | | **roleId** | `string` | Required | Required | ID of the role to be fetched. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. Substrate WSS is stored at instance creation. | ### Fetch Role Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // example of a previously created role id const roleId = "b68a5589-1284-49e9-8276-0359a429"; const response = await sdk.rbac.fetchRole({ owner: EVM_ADDRESS, roleId: roleId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchRole({}) response object { id: 'b68a5589-1284-49e9-8276-0359a429', name: 'peaq-role-1', enabled: true } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); // example of a previously created role id const roleId = "bc3f20d5-c519-4048-8db1-4bbf48dc"; const response = await sdk.rbac.fetchRole({ owner: SUBSTRATE_ADDRESS, roleId: roleId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchRole({}) response object { id: 'bc3f20d5-c519-4048-8db1-4bbf48dc', name: 'peaq-role-1', enabled: true } ``` ## fetchRoles(owner, wssBaseUrl) Used to fetch all the roles associated with the passed owner address. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | --------------------------------------------------------------------------------- | | **owner** | `string` | Required | Required | Address that represents the owner of all the fetched roles. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. Substrate WSS is stored at instance creation. | ### Fetch Roles Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const response = await sdk.rbac.fetchRoles({ owner: EVM_ADDRESS, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchRoles({}) response object [ { id: 'e09fe342-f8ee-46d1-82e2-a60a5b6e', name: 'role-name-123', enabled: true }, ... { id: 'b68a5589-1284-49e9-8276-0359a429', name: 'peaq-role-1', enabled: true } ] ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); const response = await sdk.rbac.fetchRoles({ owner: SUBSTRATE_ADDRESS }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchRoles({}) response object [ { id: 'bb3d5a05-3a4a-452b-a96d-372832ff', name: 'role-name-123', enabled: true }, ... { id: 'bc3f20d5-c519-4048-8db1-4bbf48dc', name: 'peaq-role-1', enabled: true } ] ``` ## updateRole(roleName, roleId, seed) Allows the owner to update the role name. | Parameter | Type | EVM | Substrate | Description | | ------------ | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **roleName** | `string` | Required | Required | Updated role name. | | **roleId** | `string` | Required | Required | ID of the role (32 bytes) to be updated. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Update Role Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // new role name that will be set const roleName = "peaq-role-new"; // example of a previously created role id const roleId = "b68a5589-1284-49e9-8276-0359a429"; // build the evm tx const tx = await sdk.rbac.updateRole({ roleName: roleName, roleId: roleId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // fetch Role to confirm the name change const response = await sdk.rbac.fetchRole({ owner: EVM_ADDRESS, roleId: roleId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.updateRole({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0x7cc0a21862363861353538392d313238342d343965392d383237362d30333539613432390000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000d706561712d726f6c652d6e657700000000000000000000000000000000000000' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x9fea31cc7edc8571f5236249b62e4fa45b5515a6c316fb62998bda01129f1236', index: 1, blockHash: '0x3e0a30ab7a32d3d9bb46bde23571ab0e5e5db1c428b4ab1585e0e76883b1a3be', blockNumber: 4489512, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 35281n, blobGasUsed: null, cumulativeGasUsed: 89785n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // new role name that will be set const roleName = "peaq-role-new"; // example of a previously created role id const roleId = "bc3f20d5-c519-4048-8db1-4bbf48dc"; const response = await sdk.rbac.updateRole({ roleName: roleName, roleId: roleId }); // fetch Role to confirm the name change const fetchedRole = await sdk.rbac.fetchRole({ owner: SUBSTRATE_ADDRESS, roleId: roleId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.updateRole({}) return object { message: 'Successfully update role bc3f20d5-c519-4048-8db1-4bbf48dc with new name: peaq-role-new' } ``` ## disableRole(roleId, seed) Disables a role within a permission management system. | Parameter | Type | EVM | Substrate | Description | | ---------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **roleId** | `string` | Required | Required | The unique identifier (ID) of the role to be disabled. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Disable Role Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // role id to disable const roleId = "b68a5589-1284-49e9-8276-0359a429"; // build the evm tx const tx = await sdk.rbac.disableRole({ roleId: roleId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // Fetch Role to confirm it has been changed to disabled const response = await sdk.rbac.fetchRole({ owner: EVM_ADDRESS, roleId: roleId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.disableRole({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0x7c4a13b162363861353538392d313238342d343965392d383237362d3033353961343239' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x9cd4cfecbdeac0fc5c501f26a1b1c52314bdf24731c26bf05797b2b6ca00588f', index: 0, blockHash: '0x90f3daabfb05d1e3ed83850398e8593ff8a08d59df3cf20db2b4538169491459', blockNumber: 4489862, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000020000000000000000000000020000020000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 34782n, blobGasUsed: null, cumulativeGasUsed: 34782n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // role id to disable const roleId = "bc3f20d5-c519-4048-8db1-4bbf48dc"; // execute the extrinsic const response = await sdk.rbac.disableRole({ roleId: roleId }); // Fetch Role to confirm it has been changed to disabled const fetchedRole = await sdk.rbac.fetchRole({ owner: SUBSTRATE_ADDRESS, roleId: roleId, }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.disableRole({}) return object { message: 'Successfully disable role bc3f20d5-c519-4048-8db1-4bbf48dc' } ``` ## assignRoleToUser(userId, roleId, seed) This function allows to assign a specific role to a user in a permission management system. | Parameter | Type | EVM | Substrate | Description | | ---------- | -------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------- | | **userId** | `string` | Required | Required | The unique identifier of the user to whom the role will be assigned. ID created by user and must be 32 bytes. | | **roleId** | `string` | Required | Required | ID of the role that will be assigned to the user. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Assign Role to User Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // self-generated userId const userId = "9e8c7866-8435-4b76-8683-709a03c9"; // role id to assign to user const roleId = "b68a5589-1284-49e9-8276-0359a429"; // build the evm tx const tx = await sdk.rbac.assignRoleToUser({ userId: userId, roleId: roleId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.assignRoleToUser({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0xae8cdd7962363861353538392d313238342d343965392d383237362d303335396134323939653863373836362d383433352d346237362d383638332d3730396130336339' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x71b13a8507b540937cbba58843497516edc505ce1af09a3e573cd5789c48cccf', index: 0, blockHash: '0xc44b43afbc16ecba36b2051a88a62f9b942293394ace5c193cbea73f8e9f2460', blockNumber: 4489679, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000004000000000000000000008000000000000000000000000000000000000000000000000000000', gasUsed: 31390n, blobGasUsed: null, cumulativeGasUsed: 31390n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // self-generated userId const userId = "9e8c7866-8435-4b76-8683-709a03c9"; // role id to assign to user const roleId = "bc3f20d5-c519-4048-8db1-4bbf48dc"; // build the evm tx const response = await sdk.rbac.assignRoleToUser({ userId: userId, roleId: roleId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.assignRoleToUser({}) return object { message: 'Successfully assign role bc3f20d5-c519-4048-8db1-4bbf48dc to user 9e8c7866-8435-4b76-8683-709a03c9' } ``` ## fetchUserRoles(owner, userId, wssBaseUrl) Designed to retrieve roles associated with a specific user from the network's RBAC (Role-Based Access Control) system. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------------------- | | **owner** | `string` | Required | Required | Address of the owner of the roles. This is typically the account that manages the roles and permissions. | | **userId** | `string` | Required | Required | Unique identifier of the user for whom roles are to be fetched. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL used to query RBAC storage. Substrate WSS is stored at instance creation. | ### Fetch User Roles Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const userId = "9e8c7866-8435-4b76-8683-709a03c9"; const response = await sdk.rbac.fetchUserRoles({ owner: EVM_ADDRESS, userId: userId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchUserRoles({}) return object [ { role: 'b68a5589-1284-49e9-8276-0359a429', user: '9e8c7866-8435-4b76-8683-709a03c9' } ] ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE }); const userId = "9e8c7866-8435-4b76-8683-709a03c9"; const response = await sdk.rbac.fetchUserRoles({ owner: SUBSTRATE_ADDRESS, userId: userId, }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.fetchUserRoles({}) return object [ { role: 'bc3f20d5-c519-4048-8db1-4bbf48dc', user: '9e8c7866-8435-4b76-8683-709a03c9' } ] ``` ## unassignRoleToUser(userId, roleId, seed) This function allows to unassign a specific role to a user in a permission management system. | Parameter | Type | EVM | Substrate | Description | | ---------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **userId** | `string` | Required | Required | ID of the user from which the role should be unassigned. | | **roleId** | `string` | Required | Required | ID of the role that needs to be unassigned. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Unassign Role to User Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // self-generated userId const userId = "9e8c7866-8435-4b76-8683-709a03c9"; // role id to unassign to user const roleId = "b68a5589-1284-49e9-8276-0359a429"; // build the evm tx const tx = await sdk.rbac.unassignRoleToUser({ userId: userId, roleId: roleId }); // send using Sdk function const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // fetch to see it has been unassigned (should throw an error) const response = await sdk.rbac.fetchUserRoles({ owner: EVM_ADDRESS, userId: userId, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.unassignRoleToUser({}) return object { to: '0x0000000000000000000000000000000000000802', data: '0x7663575062363861353538392d313238342d343965392d383237362d303335396134323939653863373836362d383433352d346237362d383638332d3730396130336339' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000802', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0xe128c68cd4ea9480c33d6fde4a5929d6d27b0ec8c20ddf1060505da2a9f31ab0', index: 1, blockHash: '0xcf871f18022d80ba2c3e627cb44f4d4ccae9ce3191ccd70518574a7019a3cf44', blockNumber: 4489791, logsBloom: '0x00000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000002000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 29181n, blobGasUsed: null, cumulativeGasUsed: 159305n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_ADDRESS = process.env["SUBSTRATE_ADDRESS"]; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); // self-generated userId const userId = "9e8c7866-8435-4b76-8683-709a03c9"; // role id to unassign to user const roleId = "bc3f20d5-c519-4048-8db1-4bbf48dc"; // send extrinsic const response = await sdk.rbac.unassignRoleToUser({ userId: userId, roleId: roleId }); // fetch to see it has been unassigned (should throw an error) const fetchedRoles = await sdk.rbac.fetchUserRoles({ owner: SUBSTRATE_ADDRESS, userId: userId }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.rbac.unassignRoleToUser({}) return object { message: 'Successfully unassign user: 9e8c7866-8435-4b76-8683-709a03c9 from role: bc3f20d5-c519-4048-8db1-4bbf48dc' } ``` # Send EVM TX Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/send-evm-tx When you would like to use the SDK to perform an EVM operation, you must first create a transaction object. The different classes of the SDK: [DID](/peaqchain/sdk-reference/javascript/did-operations), [RBAC](/peaqchain/sdk-reference/javascript/rbac-operations), and [Storage](/peaqchain/sdk-reference/javascript/storage-operations) all show how an EVM tx is generated. After receiving an EVM Transaction object back from the class you called, you are then able to use the SDK to send a transaction on behalf of the seed. ## sendEvmTx(tx, baseUrl, seed) | Parameter | Type | EVM | Substrate | Description | | ----------- | ---------------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **tx** | `EvmTransaction` | Required | N/A | EVM compatible transaction object returned from specific SDK method. Used to execute the corresponding peaq function. Gives the user the option to manually send the tx using ethers.js, web.js, or to use the sendEvmTx() in the peaq SDK to perform the operation. | | **baseUrl** | `string` | Required | N/A | RPC url used to connect to the blockchain. Reference [Connecting to peaq](/peaqchain/build/getting-started/connecting-to-peaq) for supported RPC urls. | | **seed** | `string` | Required | N/A | Ethereum private key linked to the wallet executing the transactions. Allows the backend to perform write transactions on behalf of the user. | ```javascript Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_PRIVATE = process.env['EVM_PRIVATE']; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); // e.g. sdk.did.create(), sdk.did.update(), sdk.storage.create() ... etc. // all return back a valid EVM tx that is used in sendEvmTx() const tx = await sdk.operation.function({}); const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); ``` ```javascript Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // receipt object of EVM Transaction for a create DID Operation TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000800', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x2eebdc26f2997cf1908bc742c8567ef607498880e5e603012836fcede9a481f3', index: 0, blockHash: '0x857356cffe490f429900d550b3f5fec7c3874c7de53c149116e834c4cc43d5dc', blockNumber: 2621813, logsBloom: '0x00000040000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 48090n, blobGasUsed: null, cumulativeGasUsed: 48090n, gasPrice: 1024n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` # Serialized DID Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/serialized-did Our SDK offers the ability to generate a serialized DID Document. On the blockchain itself we do not store the entire DID Document Object representation, rather we store the serialized document in hexadecimal. This is useful if you would like to manually send the document on-chain. To get a better understand about what is being stored please refer to the DID Document [Custom Document Fields](/peaqchain/sdk-reference/javascript/did-operations#customdocumentfields) section. ## generateDidDocument(address, customDocumentFields) | Parameter | Type | EVM | Substrate | Description | | ------------------------ | ---------------------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- | | **address** | `string` | Required | Required | Used for the id and controller fields in the generated DID Document. | | **chainType** | `ChainType` | Required | Required | Defines the expected type of address to be passed. H160 for EVM and SS58 for Substrate. Defaults expect SS58 address. | | **customDocumentFields** | `customDocumentFields` | Optional | Optional | Used to set specific fields of the DID Document. If none is set, an empty document will be generated with id and controller set. | ```javascript Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; // evm address example const address = '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C'; // possible example of custom document fields a DePIN could create const customFields = { services: [{ id: "#ipfs", type: "machineData", serviceEndpoint: "https://ipfs.io/ipfs/QmYwAPJzv5CZsnAzt8auVTLv3jE9n1Tz8exzhTHtVMJd3t" }] } const result = await Sdk.generateDidDocument({ address: address, chainType: Sdk.ChainType.EVM, customDocumentFields: customFields }); ``` ```javascript Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // result object of generated DID Document for the value stored on chain { value: '0a336469643a706561713a30783945656162316143636231413730316145664142303046336238613237356133393634363634314312336469643a706561713a3078394565616231614363623141373031614566414230304633623861323735613339363436363431432a590a052369706673120b6d616368696e65446174611a4368747470733a2f2f697066732e696f2f697066732f516d597741504a7a7635435a736e417a7438617556544c76336a45396e31547a3865787a68544874564d4a643374' } ``` # Storage Operations Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/storage-operations Another basic operation peaq provides is on-chain storage, allowing data to be saved using a `key: value` structure. ## addItem(itemType, item, seed) Adds an entry pair to the blockchain. | Parameter | Type | EVM | Substrate | Description | | ------------ | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **itemType** | `string` | Required | Required | Key that will be stored in peaq storage. Max 64 bytes. | | **item** | `object` | Required | Required | Value that will be stored in peaq storage. Max 256 bytes. | | **seed** | `string` | N/A | Required | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Add Item Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const key = "my_key"; const value = "my_value"; const tx = await sdk.storage.addItem({ itemType: key, item: value }); const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.storage.addItem({}) return object { to: '0x0000000000000000000000000000000000000801', data: '0x257c3c030000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000066d795f6b6579000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086d795f76616c7565000000000000000000000000000000000000000000000000' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000801', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x6fec47eadcba1d94e28a6d08b997aa717b69cd3c4319adb7459d21018866602a', index: 0, blockHash: '0x46a7dae54300988b4fdd42957e033d32219c2f09d683c932f00cd96d312f4f54', blockNumber: 4488814, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000800000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000010000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', gasUsed: 43014n, blobGasUsed: null, cumulativeGasUsed: 43014n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const key = "my_key"; const value = "my_value"; const result = await sdk.storage.addItem({ itemType: key, item: value }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.storage.addItem({}) return object { message: 'Successfully added the storage item type my_key with item my_value for the address 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg', block_hash: '0xafe84e572ad2ca33cbdad683dfcf2c8ac65bb9944c34304a81010212973739fa', unsubscribe: [Function (anonymous)] } ``` ## readItem(itemType, address, wssBaseUrl) Returns the `key: value` pair of what is stored on-chain. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | ----------------------------------------------------------------------------------------------------------------- | | **itemType** | `string` | Required | Required | Key that will be use to search peaq storage at the address. | | **address** | `string` | Required | Optional | Wallet address where the is key stored. | | **wssBaseUrl** | `string` | Required | N/A | WSS URL must be used to read from storage. EVM must explicitly set WSS (Substrate has it stored during creation). | ### Get Item Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_ADDRESS = process.env["EVM_ADDRESS"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const key = "my_key"; const pair = await sdk.storage.getItem({ itemType: key, address: EVM_ADDRESS, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.storage.getItem({}) return object { my_key: 'my_value' } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const key = "my_key"; const pair = await sdk.storage.getItem({ itemType: key }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.storage.getItem({}) return object { my_key: 'my_value' } ``` ## updateItem(itemType, item, seed) Updates the value in storage for the key passed. | Parameter | Type | EVM | Substrate | Description | | ------------ | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **itemType** | `string` | Required | Required | Key that is stored in peaq storage whose value will be updated. | | **item** | `string` | Required | Required | New value that will be stored in peaq storage. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Update Item Code Examples ```javascript EVM Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const HTTPS_BASE_URL = "https://quicknode.peaq.xyz"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const EVM_PRIVATE = process.env["EVM_PRIVATE"]; const sdk = await Sdk.createInstance({ baseUrl: HTTPS_BASE_URL, chainType: Sdk.ChainType.EVM }); const key = "my_key"; const value = "my_new_value"; const tx = await sdk.storage.updateItem({ itemType: key, item: value }); const receipt = await Sdk.sendEvmTx({ tx: tx, baseUrl: HTTPS_BASE_URL, seed: EVM_PRIVATE }); // read to see the change const pair = await sdk.storage.getItem({ itemType: key, address: EVM_ADDRESS, wssBaseUrl: WSS_BASE_URL }); ``` ```javascript EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.storage.updateItem({}) return object { to: '0x0000000000000000000000000000000000000801', data: '0x1cd4bf090000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000066d795f6b65790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c6d795f6e65775f76616c75650000000000000000000000000000000000000000' } // sendEvmTx({}) return object TransactionReceipt { provider: JsonRpcProvider {}, to: '0x0000000000000000000000000000000000000801', from: '0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', contractAddress: null, hash: '0x2fa941ecd230d6440f2421d80582dc38c53dd0b2ae9b8f55c1980f323bd25f7f', index: 18, blockHash: '0x7467a4a72e98c69e6cde157e6fa5d56455ecc7d23aae72129c9d4d9f6512ff63', blockNumber: 4488965, logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000800000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010', gasUsed: 32060n, blobGasUsed: null, cumulativeGasUsed: 15354515n, gasPrice: 100000000000n, blobGasPrice: null, type: 2, status: 1, root: undefined } ``` ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: Sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const key = "my_key"; const value = "my_new_value"; const response = await sdk.storage.updateItem({ itemType: key, item: value }); // read to see it has been changed const pair = await sdk.storage.getItem({ itemType: key }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.storage.updateItem({}) return object { message: 'Successfully updated the storage item type my_key to the new item my_new_value for the address 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg', block_hash: '0xb75cf6b47421c585772220bd99df1850a50e59ce506d6302eaba49db6e029288', unsubscribe: [Function (anonymous)] } ``` ## removeItem(itemType, seed) Removes the item type and item from peaq storage. | Parameter | Type | EVM | Substrate | Description | | ------------ | -------- | --- | --------- | -------------------------------------------------------------------------------------------- | | **itemType** | `string` | N/A | Required | The key representing the pair to be removed. | | **seed** | `string` | N/A | Optional | If not set at instance creation, allows user to define who sends the Substrate Transactions. | ### Remove Item Code Examples ```javascript Substrate Request theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from "@peaq-network/sdk"; const WSS_BASE_URL = "wss://quicknode.peaq.xyz"; const SUBSTRATE_SEED = process.env["SUBSTRATE_SEED"]; const sdk = await Sdk.createInstance({ baseUrl: WSS_BASE_URL, chainType: sdk.ChainType.SUBSTRATE, seed: SUBSTRATE_SEED }); const key = "my_key"; const response = await sdk.storage.removeItem({ itemType: key }); // disconnect when finished await sdk.disconnect(); ``` ```javascript Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // sdk.storage.removeItem({}) return object { message: 'Successfully removed the storage item type my_key from address 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg', block_hash: '0xb40d8934dc69c871b8ee36a34ef63ae5fb0b93c06b5d8d5c5df5244e001d3acb', unsubscribe: [Function (anonymous)] } ``` EVM delete storage precompile currently in development. When completed will add to the JavaScript SDK. # UMT Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/umt Universal Machine Time (UMT) is the first onchain implementation of the Precision Time Protocol (PTP), enabling robots, machines, drones, vehicles, and devices to achieve nanosecond-precise time synchronization. ## Why PTP on Blockchain? Traditional blockchain timestamps operate at second-level precision, limiting their utility for high-precision applications. By implementing PTP on peaq, we achieve: * Nanosecond-level precision timing * Distributed time synchronization * Blockchain-verified timestamps * Trustless time coordination for machine networks * Superior Network Time Accuracy: Unlike NTP, which allows for variable offset ranges, PTP provides deterministic and precise time synchronization ## Architecture ptp-image-1 ## Implementation Guide peaq's PTP implementation provides nanosecond-precise time synchronization with just a few lines of code. ### Usage This can be run on any JavaScript-based project (server or client). You would need the peaq-sdk to run it, please refer here for more information. #### subscribeToPtp(masterUrl, interval) **Parameters:** * **masterUrl** REQUIRED ``: The URL of the master clock server, which is used to send requests for synchronization. This is defined as a constant in the code, which is currently [https://ptp.peaq.xyz](https://ptp.peaq.xyz). This server is a central time source that fetches the timestamp from the network at regular block intervals using real-time updates and is responsible for synchronizing the clocks of connected systems to ensure accurate and consistent timekeeping. * **interval** OPTIONAL ``: Periodically synchronizes the clock at a fixed rate. Recorded in milliseconds, defaults to 1000. ```javascript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { Sdk } from '@peaq-network/sdk'; const MASTER_URL = 'https://ptp.peaq.xyz'; // ...contd. // Subscribe to PTP updates const unsubscribe = Sdk.subscribeToPtp( { masterUrl: MASTER_URL }, ({ offset, synchronizedTime }) => { console.log(`Clock Offset: ${offset} nanoseconds`); console.log(`Synchronized Time: ${synchronizedTime} nanoseconds`); } ); // To stop receiving updates unsubscribe(); ``` This is a simple subscription that, when subscribed, will return the results with a 1-second interval, until unsubscribed. ### Sample Output ``` Clock Offset: 1849833 nanoseconds Synchronized Time: 1740669653296849833 nanoseconds ``` The function returns a result that includes the time offset and the synchronized unix timestamp in nanoseconds based on the current local time. Both are computed in bigint format to be accuracy consistent. If an error occurs during synchronization, it returns nothing. The timestamp is in unix, you can verify it using any online unix timestamp conversion tool. ### Synchronization Result Details The synchronization result contains two key properties: * **Offset**: * Description: This represents the calculated time difference between the local clock and the master clock. * Purpose: The offset is used to adjust the local clock to align it with the master clock. A positive offset indicates that the local clock is behind the master clock, while a negative offset indicates it is ahead. * **Synchronized Time**: * Description: This is the local time adjusted by the calculated offset, effectively representing the current time as per the master clock. * Purpose: The synchronized time provides a corrected timestamp that can be used for time-sensitive operations, ensuring consistency with the master clock ## Synchronization Process The synchronization process involves the following steps: 1. **Sync Message**: * A request is sent to the master clock server. * The server returns a timestamp, and the local timestamp is recorded immediately after receiving the response. 2. **Delay Request**: * A local timestamp is recorded, and a request is sent to the master clock server. * The server returns another timestamp. 3. **Offset Calculation**: * The offset is calculated using the formula: `offset = ((local timestamp after sync - server timestamp) - (local timestamp before delay - delay)) / 2` * The synchronized time is calculated as: `synchronized time = local timestamp after sync + offset` ## Error Handling If any error occurs during the synchronization process, such as network issues or server errors, the function logs the error to the console and returns nothing. However, the ptp server is configured to be highly resilient and capable of handling a large volume of concurrent requests while maintaining availability, through auto-scaling and load balancing. ## Conclusion The synchronization function is essential for achieving precise time synchronization with a master clock server. By calculating the time offset and adjusting the local time, it ensures that the local system time is accurate and consistent with the master clock. The synchronization result provides both the offset and the synchronized time, which are crucial for maintaining time accuracy in distributed systems. # Tier 1 Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/verify/tier-1 This page delineates a procedure to affirm the integrity and origin of machine-generated data within a blockchain network, emphasizing "Machine-Origin Authentication" as Tier 1 verification in a Decentralized Physical Infrastructure Network (DePIN). ## System Overview The process involves the following steps: * **Registration**: The machine gets registered on the blockchain using a unique identifier. * **Data Generation & Signing**: The machine generates data, signs it, and sends the hash to the blockchain. * **Data Storage**: The hash gets stored on the blockchain. * **Data Retrieval & Verification**: Any party retrieves and verifies the data's validity from the blockchain. ## Prerequisites * Node.js environment setup. * Installed `@polkadot/api`, `@polkadot/util`, `@polkadot/util-crypto`, and `@peaq-network/sdk` packages for blockchain interaction. * A seeds.json file containing the seeds for the owner's and the machine's accounts. ### 1. Registration (Machine-Origin Authentication) The machine is registered on the blockchain by the owner, creating a digital identity that serves as the foundation for Tier 1 verification. ``` // import necessary libraries import { Sdk } from "@peaq-network/sdk"; import Keyring from "@polkadot/keyring"; import fs from "fs/promises"; const register = async () => { const seeds = JSON.parse(await fs.readFile("seeds.json", "utf8")); const ownerSeed = seeds.owner; const machineSeed = seeds.machine; const sdkInstance = await Sdk.createInstance({ baseUrl: "wss://wss-async.agung.peaq.network", seed: ownerSeed, }); const keyring = new Keyring({ type: "sr25519" }); const machinePair = keyring.addFromUri(machineSeed); // Register the machine's DID await sdkInstance.did.create( { name: `did:peaq:${machinePair.address}`, address: machinePair.address }, (result) => { // handle result } ); }; register(); ``` ### 2. Data Generation & Signing (Direct Device Data) The machine directly generates data, signs it using its secure private key, ensuring that the data is authentic and has not been altered. This constitutes a Tier 1 level of trust. **NOTE** Data signing is an internal process of the machine. Here is a hypothetical code snippet to illustrate this step. ``` // Hypothetical snippet for data generation and signing import { generateKeyPair } from "./utils.js"; import { hexToU8a, u8aToHex } from "@polkadot/util"; import { cryptoWaitReady } from "@polkadot/util-crypto"; // Simulated machine data generation and signing const generateAndSignData = async (machineSeed) => { await cryptoWaitReady(); const machineKeypair = generateKeyPair(machineSeed); const data = "Machine-generated data"; const dataHex = u8aToHex(JSON.stringify(data)); const signature = machineKeypair.sign(hexToU8a(dataHex)); return { dataHex, signature: u8aToHex(signature) }; }; ``` ### 3. Data Storage (Immutable Ledger Entry) Signed data is stored on the blockchain, providing an immutable record that can be publicly verified, which is consistent with the principles of Tier 1 verification ``` // import necessary libraries and functions import fs from "fs/promises"; import { generateKeyPair, makeExtrinsicCall } from "./utils.js"; import { hexToU8a, u8aToHex } from "@polkadot/util"; import { cryptoWaitReady } from "@polkadot/util-crypto"; const store = async () => { await cryptoWaitReady(); const seeds = JSON.parse(await fs.readFile("seeds.json", "utf8")); const machineSeed = seeds.machine; const machineKeypair = generateKeyPair(machineSeed); const dataHex = u8aToHex(JSON.stringify("test-data")); const signature = u8aToHex(machineKeypair.sign(hexToU8a(dataHex))); const payload = { data: dataHex, signature: signature, }; // Serialize payload into hex format for storage const payloadHex = u8aToHex(JSON.stringify(payload)); await makeExtrinsicCall( "peaqStorage", "addItem", [machineKeypair.address, payloadHex], true, machineKeypair ); }; store(); ``` ### 4. Data Retrieval & Verification (Trust Verification) Any network participant can retrieve the data and verify its authenticity using the public key associated with the machine's digital identity. This verification step confirms the Tier 1 status of the data. ``` // import necessary libraries and functions import fs from 'fs/promises'; import { hexToU8a } from '@polkadot/util'; import { signatureVerify } from '@polkadot/util-crypto'; import { getStorage, generateKeyPair } from './utils.js'; const verifyData = async (publicKey, dataHex, signatureHex) => { const dataU8a = hexToU8a(dataHex); const signatureU8a = hexToU8a(signatureHex); return signatureVerify(dataU8a, signatureU8a, publicKey).isValid; }; const verify = async () => { const seeds = JSON.parse(await fs.readFile('seeds.json', 'utf8')); const machineSeed = seeds.machine; const machineKeypair = generateKeyPair(machineSeed); const itemType = 'your_item_type_here'; const storedDataHex = await getStorage(itemType); if (!storedDataHex) { throw new Error('Data not found.'); } const { data: dataHex, signature: signatureHex } = JSON.parse(hexToU8a(storedDataHex).toString()); const isValid = await verifyData(machineKeypair.publicKey, dataHex, signatureHex); if (isValid) { console.log('Data verified successfully.'); } else { console.log('Verification failed.'); } }; verify(); ``` ## Conclusion The system described here provides a secure method for registering machines and verifying the origin and integrity of their data using cryptographic signatures. This approach aligns with Tier 1 verification in DePIN projects, ensuring the highest level of trust in the data's direct origin and secure transmission. ### Additional Considerations * Ensure that all key pairs and sensitive information are handled securely. * Conduct thorough testing in a controlled environment before deploying to production. * Always keep up to date with the SDK and API documentation from the blockchain network you're working with. Developers should refer to the official documentation of the Polkadot.js API and the specific blockchain network for detailed instructions and best practices. *** Building upon the established structure of "Machine Data Verification on Blockchain," we will now introduce the concept of Tier 2 verification, termed "Pattern Matching Validation." This tier involves validating data that may not originate directly from the device but is relevant to existing devices within the network. The verification in this tier is accomplished by comparing incoming data patterns against known patterns from devices already registered in the system. # Tier 2 Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/verify/tier-2 Tier 2 verification strengthens the data integrity framework by validating data through pattern matching. It is designed for scenarios where data does not originate directly from the device but is still crucial for the network's operations. ## Pattern Matching Validation Process * **Pattern Learning**: The system learns and stores patterns of data from registered devices. * **Data Submission**: A machine or an external source submits data to the blockchain. * **Pattern Comparison**: The submitted data is compared against learned patterns. * **Validation Outcome**: Data matching known patterns is marked as Tier 2 verified. ## Prerequisites * A pattern recognition module integrated into the blockchain network. * A dataset of known patterns from Tier 1 verified devices for comparison. ### 1. Pattern Learning The blockchain system uses historical Tier 1 verified data to learn and record data patterns associated with each registered machine. ``` // import necessary libraries and functions import { fetchTier1Data, patternRecognition } from './utils.js'; const learnPatterns = async () => { const tier1Data = await fetchTier1Data(); // Process and store patterns for each machine tier1Data.forEach((dataEntry) => { const patterns = patternRecognition(dataEntry); storePatterns(dataEntry.machineId, patterns); }); }; learnPatterns(); ``` ### 2. Data Submission Data is submitted to the blockchain, potentially originating from external sources or indirectly from machines. ``` // import necessary libraries and functions import { submitToBlockchain } from './utils.js'; const submitData = async (data) => { // Data submission can be from machines or external sources // Data is preprocessed and packaged for submission const submissionPackage = preprocessData(data); submitToBlockchain(submissionPackage); }; submitData(externalData); ``` ### 3. Pattern Comparison When new data is submitted, it is compared against the stored patterns to validate its trustworthiness. ``` // import necessary libraries and functions import { retrievePatterns, compareWithKnownPatterns } from './utils.js'; const validateData = async (submittedData) => { const knownPatterns = await retrievePatterns(submittedData.machineId); // Compare submitted data against known patterns const isValid = compareWithKnownPatterns(submittedData, knownPatterns); return isValid; }; ``` ### 4. Validation Outcome Data that matches the stored patterns is marked as Tier 2 verified, indicating a level of trustworthiness, though slightly less than Tier 1 verified data. ``` // import necessary libraries and functions import { updateDataStatus } from './utils.js'; const markAsTier2Verified = async (dataId, isValid) => { if (isValid) { // Update the data status on the blockchain to reflect Tier 2 verification updateDataStatus(dataId, 'Tier2Verified'); } else { // Handle unverified data accordingly handleUnverifiedData(dataId); } }; // Example usage markAsTier2Verified(submittedDataId, validationOutcome); ``` ## Conclusion Tier 2 verification augments the data assurance framework by leveraging pattern matching to validate data authenticity. It provides a method to establish trust in data that may not come directly from a device but still holds relevance within the network. ## Additional Considerations * Enhance the pattern recognition module to handle evolving data patterns. * Implement safeguards against false positives in pattern matching. * Continuously update the pattern database to include new data types and sources. For more detailed implementation strategies and best practices, developers should refer to the documentation specific to the pattern recognition technology used and the blockchain network's capabilities. *** This section expands the document to include Tier 2 verification, providing a logical extension to the robust verification system. It ensures that the DePIN's integrity is maintained even for data that cannot be verified at the Tier 1 level. The code snippets provided are hypothetical and meant to illustrate the proposed functionality. In a real-world scenario, the actual implementation may require more complex algorithms and integration with blockchain-specific services. Continuing the comprehensive structure for "Machine Data Verification on Blockchain," the next segment will incorporate Tier 3 verification, designated as "Oracle-Backed Authentication." This tier introduces an external validation mechanism where data is verified through oracles that serve as bridges between the blockchain and external data sources. # Tier 3 Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/verify/tier-3 Tier 3 verification introduces an extra layer of validation by employing oracles. This is particularly useful for data that requires external corroboration or originates from less secure or indirect sources. ## Oracle-Backed Verification Process 1. **Oracle Selection**: A trusted oracle is chosen to serve as an intermediary for data verification. 2. **Data Submission & Request**: Data is submitted to the blockchain, and a request is made to the oracle for verification. 3. **Oracle Verification**: The oracle verifies the data's authenticity against external sources or predefined criteria. 4. **Verification Result**: The oracle provides a verification result, which is then recorded on the blockchain. ## Prerequisites * Integration with a trusted oracle service that can provide external data verification. * Smart contracts or blockchain protocols capable of handling oracle responses. ### 1. Oracle Selection Identify and integrate a trusted oracle service into the blockchain system for external data verification. ``` // import necessary libraries and functions import { OracleService } from './oracleService.js'; const selectOracle = async () => { // Select a trusted oracle from a list of available services const oracle = new OracleService('trusted_oracle_url'); return oracle; }; const trustedOracle = selectOracle(); ``` ### 2. Data Submission & Request Data is submitted to the blockchain, and a verification request is sent to the chosen oracle. ``` // import necessary libraries and functions import { trustedOracle } from './selectOracle.js'; import { submitToBlockchain } from './utils.js'; const submitDataAndRequestVerification = async (data) => { // Data submission to the blockchain const submissionPackage = preprocessData(data); submitToBlockchain(submissionPackage); // Send a verification request to the oracle trustedOracle.requestVerification(data); }; submitDataAndRequestVerification(externalData); ``` ### 3. Oracle Verification The oracle conducts verification by comparing the submitted data against reliable external sources or predefined criteria. ``` // Assuming the oracle service provides a method to validate data import { trustedOracle } from './selectOracle.js'; const verifyDataWithOracle = async (data) => { const verificationResult = await trustedOracle.verify(data); return verificationResult; }; ``` ### 4. Verification Result The oracle's verification result is received and recorded on the blockchain, completing the Tier 3 verification process. ``` // import necessary libraries and functions import { updateDataStatusWithOracle } from './utils.js'; const recordVerificationResult = async (dataId, oracleResult) => { // Record the oracle's verification result on the blockchain updateDataStatusWithOracle(dataId, oracleResult); }; // Example usage recordVerificationResult(submittedDataId, oracleVerificationOutcome); ``` ## Conclusion Tier 3 verification ensures data authenticity by involving oracles for an added layer of external validation. This method is key for integrating data that requires external references or for which direct device-origin verification is not possible. ## Additional Considerations * Establish strict criteria for selecting oracles to ensure reliability and objectivity. * Regularly audit and monitor oracle performance to guard against any potential vulnerabilities. * Implement consensus mechanisms among multiple oracles to further bolster trust in the verification process. For practical application, developers should follow the guidelines provided by the blockchain network for integrating oracles and managing external data verification. *** This section concludes the "Machine Data Verification on Blockchain" document, providing a full spectrum of data verification tiers suitable for various trust levels within a DePIN. Tier 3 verification ensures that even data requiring external validation can be trusted within the network, thus maintaining the system's integrity. The provided code snippets are conceptual and should be adapted for specific use cases, oracle services, and blockchain network protocols. # peaq verify Source: https://docs.peaq.xyz/peaqchain/sdk-reference/javascript/verify/verify In order to establish a secure and decentralized method for verifying physical Machine data within a Decentralized Physical Infrastructure Network (DePIN), peaq works on a multi-tiered approach. The following elaborates on each tier, providing an overview for each step in the verification process. ## Tier 1 Verification: Machine-Origin Authentication * **Description**: Data originating directly from devices, can be verified by a shortly released SDK or by the functions outlined in the documentation. The predominant use cases within this tier primarily center around the communication capabilities of DePINs via cellular and smart devices. * **Trust Level**: Data verified under this tier is marked as the most trusted, given its direct origin and secure transmission. * **Implementation**: The system performs device detection and signs the data using a private key, ensuring its authenticity before transmitting it to the network. Integrated software solutions, including Home Assistance, will facilitate this process, maintaining the highest level of security and integrity. ## Tier 2 Verification: Pattern Matching Validation * **Description**: Data not directly from the device but is relevant to existing devices on the network is validated. * **Implementation**: Validation occurs by comparing data patterns from an existing device on the network. Similar patterns indicate trustworthiness. * **Trust Level**: Tier 2 data is considered trustworthy, though less so than Tier 1. ## Tier 3 Verification: Oracle-Backed Authentication * **Description**: Data is verified through an oracle, providing an additional layer of security. * **Implementation**: The oracle confirms the legitimacy of the data source, adding an extra level of validation. * **Trust Level**: Tier 3 data is considered secure, but may still be subject to potential vulnerabilities. # Create Instance Source: https://docs.peaq.xyz/peaqchain/sdk-reference/python/create-instance The [Python SDK](https://pypi.org/project/peaq-sdk/) supports **EVM** and **Substrate** transactions. The following code examples are for versions `0.2.1`, with a new reference coming soon for version `1.0.0`. When installing the SDK, use the following command: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install peaq-sdk==0.2.1 ``` It can either: * Return an unsigned transaction object for manual submission, or * Automatically submit the transaction if a **private key** (EVM) or **mnemonic seed** (Substrate) is provided. Currently, the SDK supports modules for **Decentralized Identifiers (DID)**, **Storage**, **RBAC** (Role-Based Access Control), and **Transfers**. With **Machine Station Factory** and **UMT** modules coming soon. To get started, you'll need to determine: * Whether you're building for **EVM** or **Substrate**, and * Whether you want the SDK to **sign and send transactions**, or just prepare them. ## create\_instance(base\_url, chain\_type, seed) Initializes the SDK and returns an instance ready to interact with the network. | Parameter | Type | EVM | Substrate | Description | | --------------- | ----------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **base\_url** | `string` | Required | Required | HTTPS/WSS URL used to connect to the blockchain. Reference [connecting to peaq](/peaqchain/build/getting-started/connecting-to-peaq) for supported URLs. Use **HTTPS** for EVM and **WSS** for Substrate (and EVM reads). | | **chain\_type** | `ChainType` | Required | Required | Defines whether the instance will generate EVM or Substrate transactions. | | **seed** | `string` | Optional | Optional | Private key (EVM) or mnemonic phrase (Substrate) used to execute write transactions on behalf of the user. If not set, the peaq transaction object is returned to the user for manual submission. | ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, ) ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) ``` # DID Operations Source: https://docs.peaq.xyz/peaqchain/sdk-reference/python/did-operations This concept has been absorbed into peaqOS. See [peaqID](/peaqos/concepts/peaqid) for the current identity model. Using the initialized SDK instance, you can create a **Decentralized Identifier (DID)** on-chain, enabling identity-based operations within the peaq. This operation generates a DID document and either: * Returns an unsigned transaction or extrinsic (if no signer is available), or * Submits the DID creation directly to the blockchain (if signing credentials are present). ## create(name, custom\_document\_fields, address) Enables the creation of a decentralized identity on-chain for your given entity. | Parameter | Type | EVM | Substrate | Description | | ---------------------------- | ---------------------- | ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------- | | **name** | `string` | Required | Required | Name of the DID to be created. | | **custom\_document\_fields** | `CustomDocumentFields` | Required | Required | Sets specific fields of the DID Document. | | **address** | `string` | Required\* | Required\* | Wallet address that is used to create the DID Document. If seed has been set, defaults to the corresponding public key address. | \* The address is required when no seed is set. ### CustomDocumentFields | Parameter | Type | EVM | Substrate | Description | | ----------------- | ---------------- | -------- | --------- | ------------------------------------------------------------------------------------------- | | **verifications** | `Verification[]` | Optional | Optional | Stores the verification method that is tied to the wallet used to create this DID Document. | | **signature** | `Signature` | Optional | Optional | Used to store an issuer's signature. | | **services** | `Services[]` | Optional | Optional | Links data, endpoints, or other metadata to the DID Document. | #### Verification | Parameter | Type | EVM | Substrate | Description | | ---------------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **type** | `string` | Optional | Optional | Allows user to manually set the type of verification to be used with the public key tied to the DID Document.

**EVM Wallets:**
- `EcdsaSecp256k1RecoveryMethod2020`
**Substrate Wallets:**
- `Ed25519VerificationKey2020`
- `Sr25519VerificationKey2020` | | **publicKeyMultibase** | `string` | Optional | Optional | Allows user to manually set the public Key MultiBase they would like to use for their verification method. | #### Signature | Parameter | Type | EVM | Substrate | Description | | ---------- | -------- | -------- | --------- | ----------------------------------------------- | | **type** | `string` | Required | Required | The algorithm used when generating a signature. | | **issuer** | `string` | Required | Required | Entity that issued the signature. | | **hash** | `string` | Required | Required | Hash value of the generated signature. | #### Services | Parameter | Type | EVM | Substrate | Description | | ------------------- | -------- | -------- | --------- | --------------------------------------------------------------- | | **id** | `string` | Required | Required | Identifier used to indicate what type of service is being used. | | **type** | `string` | Required | Required | Declares the type of service object being referenced. | | **serviceEndpoint** | `string` | Optional | Optional | URI/URL that is used to point to another data storage location. | | **data** | `string` | Optional | Optional | Data value that can be stored at this service. | ### Create DID Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType, CustomDocumentFields, Verification, Service, Signature load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) name = 'DID_NAME_001' response = sdk.did.create( name=name, custom_document_fields= CustomDocumentFields( verifications=[Verification(type='EcdsaSecp256k1RecoveryMethod2020')], signature=Signature(type='EcdsaSecp256k1RecoveryMethod2020', issuer='0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', hash='123'), services=[Service(id='#ipfs', type='peaqStorage', data='123')] ), address=EVM_ADDRESS ) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed DID create transaction for 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C of the name DID_NAME_001. You must sign and send it externally.", "tx": { "to": "0x0000000000000000000000000000000000000800", "data": "0xcc4a70ca00000000000000000..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType, CustomDocumentFields, Verification, Service, Signature load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) name = 'DID_NAME_001' response = sdk.did.create( name=name, custom_document_fields= CustomDocumentFields( verifications=[Verification(type='EcdsaSecp256k1RecoveryMethod2020')], signature=Signature(type='EcdsaSecp256k1RecoveryMethod2020', issuer='0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', hash='123'), services=[Service(id='#ipfs', type='peaqStorage', data='123')] ) ) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully added the DID under the name DID_NAME_001 for user 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C.", "receipt": { "transactionHash": "0xaf146b28b2e1d8dae3200cb8e0878c67ac8614f68fb68d43905fd84539b70175", "transactionIndex": 0, "blockHash": "0x9df027467fd945434ad7c5eb90e798f66b30650a203749c3cf0c2d021cadf009", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000800", "blockNumber": 5845831, "cumulativeGasUsed": 63881, "gasUsed": 63881, "contractAddress": null, "status": 1, "effectiveGasPrice": 102000000000, "type": 2, "logs": [ { "address": "0x0000000000000000000000000000000000000800", "topics": [ "0x13aef52bc4a99da04591533072e304017e3fb76f43e7fadd25eb7f514c5ef6e5" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c...", "blockHash": "0x9df027467fd945434ad7c5eb90e798f66b30650a203749c3cf0c2d021cadf009", "blockNumber": 5845831, "transactionHash": "0xaf146b28b2e1d8dae3200cb8e0878c67ac8614f68fb68d43905fd84539b70175", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x0000004000000000000000000000000000000000000000000000000000000400..." } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType, CustomDocumentFields, Verification, Service, Signature load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) name = 'DID_NAME_001' response = sdk.did.create( name=name, custom_document_fields= CustomDocumentFields( verifications=[Verification(type='Sr25519VerificationKey2020')], signature=Signature(type='Sr25519VerificationKey2020', issuer=SUBSTRATE_ADDRESS, hash='123'), services=[Service(id='#ipfs', type='peaqStorage', data='123')] ), address=SUBSTRATE_ADDRESS ) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed DID create call for 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg of the name DID_NAME_001. You must sign and send externally.", "call": { "call_module": "PeaqDid", "call_function": "add_attribute", "call_args": { "did_account": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "name": "DID_NAME_001", "value": "0a396469643a70656171...", "valid_for": null } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType, CustomDocumentFields, Verification, Service, Signature load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) name = 'DID_NAME_001' response = sdk.did.create( name=name, custom_document_fields= CustomDocumentFields( verifications=[Verification(type='Sr25519VerificationKey2020')], signature=Signature(type='Sr25519VerificationKey2020', issuer='5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg', hash='123'), services=[Service(id='#ipfs', type='peaqStorage', data='123')] ) ) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully added the DID under the name DID_NAME_001 for user 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg.", "receipt": { "extrinsic_hash": "0xf325d23c0c0531f41a22931c18b4d695956b1add7586ec2b76810fd0a7fdc80e", "block_hash": "0xd9ccec8fdb461e59d45beea52332e737a08bee268e1b29532651ad0ac1f53346", "finalized": false, "extrinsic_index": 2, "call": { "module": "PeaqDid", "function": "add_attribute", "args": { "did_account": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "name": "DID_NAME_001", "value": "0a396469643a70656171..." // Truncated hex blob } }, "events": [...], "is_success": true, "total_fee_amount": 2528404367045865 } } ``` ## read(name, address) Allows a user to read a previously created DID from the chain. | Parameter | Type | EVM | Substrate | Description | | ----------- | -------- | ---------- | ---------- | ---------------------------------------------------- | | **name** | `string` | Required | Required | Name of the DID stored. | | **address** | `string` | Required\* | Required\* | Address of the wallet that created the DID Document. | \* The address is required when no seed is set. ### Read DID Code Examples ```python EVM No Key Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) name = 'DID_NAME_001' document = sdk.did.read(name=name, address=EVM_ADDRESS) ``` ```python EVM No Key Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "name": "DID_NAME_001", "value": "0a336469643a70656171...", "validity": "4294967295", "created": "1751392704000", "document": { "id": "did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "controller": "did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "verificationMethods": [ { "id": "did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C#keys-1", "type": "EcdsaSecp256k1RecoveryMethod2020", "controller": "did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "publicKeyMultibase": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C" } ], "signature": { "type": "EcdsaSecp256k1RecoveryMethod2020", "issuer": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "hash": "123" }, "services": [ { "id": "#ipfs", "type": "peaqStorage", "data": "123" } ], "authentications": [ "did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C#keys-1" ] } } ``` ```python EVM Key Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) name = 'DID_NAME_001' document = sdk.did.read(name=name) ``` ```python EVM Key Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "name": "DID_NAME_001", "value": "0a336469643a706561713a307...", "validity": "4294967295", "created": "1751392704000", "document": { "id": "did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "controller": "did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "verificationMethods": [ { "id": "did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C#keys-1", "type": "EcdsaSecp256k1RecoveryMethod2020", "controller": "did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "publicKeyMultibase": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C" } ], "signature": { "type": "EcdsaSecp256k1RecoveryMethod2020", "issuer": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "hash": "123" }, "services": [ { "id": "#ipfs", "type": "peaqStorage", "data": "123" } ], "authentications": [ "did:peaq:0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C#keys-1" ] } } ``` ```python Substrate No Key Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) name = 'DID_NAME_001' document = sdk.did.read(name=name, address=SUBSTRATE_ADDRESS) ``` ```python Substrate No Key Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "name": "DID_NAME_001", "value": "0a396469643a70656171...", "validity": "4294967295", "created": "1751394168000", "document": { "id": "did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "controller": "did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "verificationMethods": [ { "id": "did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg#keys-1", "type": "Sr25519VerificationKey2020", "controller": "did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "publicKeyMultibase": "z6QNpkN4bjiLRd5wzb2jYKiaMNVVyPrw3SNkcpXX19LtTEUJ" } ], "signature": { "type": "Sr25519VerificationKey2020", "issuer": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "hash": "123" }, "services": [ { "id": "#ipfs", "type": "peaqStorage", "data": "123" } ], "authentications": [ "did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg#keys-1" ] } } ``` ```python Substrate Key Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) name = 'DID_NAME_001' document = sdk.did.read(name=name) ``` ```python Substrate Key Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "name": "DID_NAME_001", "value": "0a396469643a706561713a354...", "validity": "4294967295", "created": "1751394168000", "document": { "id": "did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "controller": "did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "verificationMethods": [ { "id": "did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg#keys-1", "type": "Sr25519VerificationKey2020", "controller": "did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "publicKeyMultibase": "z6QNpkN4bjiLRd5wzb2jYKiaMNVVyPrw3SNkcpXX19LtTEUJ" } ], "signature": { "type": "Sr25519VerificationKey2020", "issuer": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "hash": "123" }, "services": [ { "id": "#ipfs", "type": "peaqStorage", "data": "123" } ], "authentications": [ "did:peaq:5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg#keys-1" ] } } ``` ## update(name, custom\_document\_fields, address) Updates an existing DID identified by name, overwriting the entire DID document with new `custom_document_fields`. Use caution, as all existing data is replaced with the newly provided fields. Recommended to use the `read` method to view the current DID document before updating. | Parameter | Type | EVM | Substrate | Description | | ---------------------------- | ---------------------- | ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------ | | **name** | `string` | Required | Required | Name of the DID to be updated. | | **custom\_document\_fields** | `CustomDocumentFields` | Required | Required | New fields to embed in the DID Document. These fully replace the prior document. | | **address** | `string` | Required\* | Required\* | Wallet address that owns the DID Document. If seed has been set, defaults to the corresponding public key address. | \* The address is required when no seed is set. ### Update DID Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType, CustomDocumentFields, Verification, Service, Signature load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) name = 'DID_NAME_001' response = sdk.did.update( name=name, custom_document_fields= CustomDocumentFields( verifications=[Verification(type='EcdsaSecp256k1RecoveryMethod2020')], signature=Signature(type='EcdsaSecp256k1RecoveryMethod2020', issuer='0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', hash='456'), services=[Service(id='#updated-ipfs', type='peaqStorage', data='updated-data')] ), address=EVM_ADDRESS ) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed DID update transaction for 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C of the name DID_NAME_001. You must sign and send it externally.", "tx": { "to": "0x0000000000000000000000000000000000000800", "data": "0x68b4b2c10000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4449445f4e414d455f30303100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d2306133333634363936343361373036353631373133613330373833393435363536313632333136313433363336323331343133373330333136313435363634313432333033303436333336323338363133323337333536313333333933363334333633363334333134333132333336343639363433613730363536313731336133303738333934353635363136323331363134333" } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType, CustomDocumentFields, Verification, Service, Signature load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) name = 'DID_NAME_001' response = sdk.did.update( name=name, custom_document_fields= CustomDocumentFields( verifications=[Verification(type='EcdsaSecp256k1RecoveryMethod2020')], signature=Signature(type='EcdsaSecp256k1RecoveryMethod2020', issuer='0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C', hash='456'), services=[Service(id='#updated-ipfs', type='peaqStorage', data='updated-data')] ) ) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "transactionHash": "0x115cbb93e693d23b45c36016d7035d96aef01964f71b857303b2a9ade23504d1", "transactionIndex": 1, "blockHash": "0xc8dfbf2325cad44c8f3132d8a49a4e364397507bca5e720edd05b8b64039c431", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000800", "blockNumber": 5846312, "cumulativeGasUsed": 309376, "gasUsed": 49745, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000800", "topics": [ "0x018be4d5e2634aefdb51c4ddd186f33072cfb5f6baf685579c2e214995dc9e4f" ], "data": "0x0000000000000000...000000", "blockHash": "0xc8dfbf2325cad44c8f3132d8a49a4e364397507bca5e720edd05b8b64039c431", "blockNumber": 5846312, "transactionHash": "0x115cbb93e693d23b45c36016d7035d96aef01964f71b857303b2a9ade23504d1", "transactionIndex": 1, "logIndex": 4, "removed": false } ], "logsBloom": "0x0000004000000000...000000", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType, CustomDocumentFields, Verification, Service, Signature load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) name = 'DID_NAME_001' response = sdk.did.update( name=name, custom_document_fields= CustomDocumentFields( verifications=[Verification(type='Ed25519VerificationKey2020')], signature=Signature(type='Ed25519VerificationKey2020', issuer=SUBSTRATE_ADDRESS, hash='456'), services=[Service(id='#updated-ipfs', type='peaqStorage', data='updated-data')] ), address=SUBSTRATE_ADDRESS ) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "call_module": "PeaqDid", "call_function": "update_attribute", "call_args": { "did_account": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "name": "DID_NAME_001", "value": "0a39646964...736b6579732d31", "valid_for": null } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType, CustomDocumentFields, Verification, Service, Signature load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) name = 'DID_NAME_001' response = sdk.did.update( name=name, custom_document_fields= CustomDocumentFields( verifications=[Verification(type='Ed25519VerificationKey2020')], signature=Signature(type='Ed25519VerificationKey2020', issuer='5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg', hash='456'), services=[Service(id='#updated-ipfs', type='peaqStorage', data='updated-data')] ) ) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully updated the DID under the name DID_NAME_001 for user 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg.", "receipt": { "extrinsic_hash": "0x6f72e86e2c50424866195ab7e7c9f08cd9bcb51621d8dc0cd503320a36df8135", "block_hash": "0xca84e35ef7bc07ec487a132d9967a19e8a901ec7ed9e39fe43baa3b39a671132", "finalized": false, "extrinsic_index": 2, "extrinsic": { "address": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "module": "PeaqDid", "function": "update_attribute", "args": { "did_account": "5Df42mkzt...", "name": "DID_NAME_001", "value": "... (truncated)", "valid_for": null }, "nonce": 1878, "tip": 0 }, "events": [...], "success": true, "total_fee": 2013819302693817, "weight": { "ref_time": 315663944, "proof_size": 6153 } } } ``` ## remove(name, address) Removes an existing on-chain DID identified by name. Once removed, the DID data is no longer accessible via subsequent reads. | Parameter | Type | EVM | Substrate | Description | | ----------- | -------- | ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------ | | **name** | `string` | Required | Required | Name of the DID to be removed from the chain. | | **address** | `string` | Required\* | Required\* | Wallet address that owns the DID Document. If seed has been set, defaults to the corresponding public key address. | \* The address is required when no seed is set. ### Remove DID Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) name = 'DID_NAME_001' response = sdk.did.remove(name=name, address=EVM_ADDRESS) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed DID remove transaction for 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C of the name DID_NAME_001. You must sign and send it externally.", "tx": { "to": "0x0000000000000000000000000000000000000800", "data": "0xe8a816900000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000c4449445f4e414d455f3030310000000000000000000000000000000000000000" } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) name = 'DID_NAME_001' response = sdk.did.remove(name=name) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully removed the DID under the name DID_NAME_001 for user 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C.", "receipt": { "transactionHash": "0xfe305926a1a3fbfa2ed43d8d2795d8faefcee587dac1c9726943516e081359f0", "transactionIndex": 0, "blockHash": "0x8990405a02f54447595a1339b1a9952e58267a9a86044c9e7d8d23310dc98947", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000800", "blockNumber": 5846365, "cumulativeGasUsed": 44271, "gasUsed": 44271, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000800", "topics": [ "0x8d8507f4f79e585e0cbbdeff53c51bfb6ffa6d44fbcc86e46ece2fad02b5d902" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275...", "blockHash": "0x8990405a02f54447595a1339b1a9952e58267a9a86044c9e7d8d23310dc98947", "blockNumber": 5846365, "transactionHash": "0xfe305926a1a3fbfa2ed43d8d2795d8faefcee587dac1c9726943516e081359f0", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x000000400000000000000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) name = 'DID_NAME_001' response = sdk.did.remove(name=name, address=SUBSTRATE_ADDRESS) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed DID remove call for 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg of the name DID_NAME_001. You must sign and send externally.", "call": { "call_module": "PeaqDid", "call_function": "remove_attribute", "call_args": { "did_account": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "name": "DID_NAME_001" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) name = 'DID_NAME_001' response = sdk.did.remove(name=name) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully removed the DID under the name DID_NAME_001 for user 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg.", "receipt": { "extrinsic_hash": "0x554789ce0eb9c59f082867255e92a19c3e2dc6c54fff13f21c5f2892a3dd6dfd", "block_hash": "0xc936185f3cffca622b4b0e97dfd19ea3598252f133367d6ea1a8f8b3bd1b7221", "block_number": null, "finalized": false, "extrinsic_index": 2, "extrinsic": { "address": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "call": { "module": "PeaqDid", "function": "remove_attribute", "args": [ { "name": "did_account", "type": "AccountId", "value": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg" }, { "name": "name", "type": "BoundedVecName", "value": "DID_NAME_001" } ] }, "nonce": 1879 }, "events": [...], "success": true, "total_fee": 2387350825469955 } } ``` # Group Source: https://docs.peaq.xyz/peaqchain/sdk-reference/python/rbac-operations/group ## create\_group(group\_name, group\_id) Used to create a new group within the RBAC (Role-Based Access Control) system. | Parameter | Type | EVM | Substrate | Description | | --------------- | ----- | -------- | --------- | ----------------------------------------------------------------------------- | | **group\_name** | `str` | Required | Required | Name of the group to be created. | | **group\_id** | `str` | Optional | Optional | ID of the group. If not provided, a new ID will be generated (32 characters). | ### Create Group Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) group_name = "test_group_1" response = sdk.rbac.create_group(group_name=group_name) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC create group call for the group name of test_group_1 and group id of d07cd755-8f2f-497f-ad53-9d6cf828. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0x65c1e09c64303763643735352d386632662d34393766..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) group_name = "test_group_1" response = sdk.rbac.create_group(group_name=group_name) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully added the RBAC group under the group name of test_group_1 with the group id of 38c9ccb3-2346-4b25-ab28-51e1e2e3.", "receipt": { "transactionHash": "0x08a3a22c791596271d51e0c896e704efc28f360fa8912f1842ec3e6e4d2ef382", "transactionIndex": 0, "blockHash": "0x452c99c0e179f477d544eba3d9cea2566726582aaab420a0d896b2a1eed87b4a", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5847190, "cumulativeGasUsed": 44942, "gasUsed": 44942, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0xa02084845c66cf1021190e4778630b0a6de58f9606261968e3a6a203699cedd1" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c333...", "blockHash": "0x452c99c0e179f477d544eba3d9cea2566726582aaab420a0d896b2a1eed87b4a", "blockNumber": 5847190, "transactionHash": "0x08a3a22c791596271d51e0c896e704efc28f360fa8912f1842ec3e6e4d2ef382", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x0000000000000000000000000000000000000000000000400000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) group_name = "test_group_1" response = sdk.rbac.create_group(group_name=group_name) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC create group call for the group name of test_group_1 and group id of 4d56f1e4-b5bd-4a41-8766-1a02d322. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "add_group", "call_args": { "group_id": "4d56f1e4-b5bd-4a41-8766-1a02d322", "name": "test_group_1" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) group_name = "test_group_1" response = sdk.rbac.create_group(group_name=group_name) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully added the RBAC group under the group name of test_group_1 with the group id of 1801710e-b83b-4f1e-ba91-f7d53dd2.", "receipt": { "extrinsic_hash": "0x9c1b654a3d7ae7a58c8c210956ee3fdef9e0d17a3b14869dbfbb5f3e4167d1c8", "block_hash": "0xce4285fe64a7ff2d835031a0dec63b115d0617d3fa7b573148562e849baae3c4", "finalized": false, "extrinsic_index": 2, "call": { "module": "PeaqRbac", "function": "add_group", "args": { "group_id": "1801710e-b83b-4f1e-ba91-f7d53dd2", "name": "test_group_1" }, "call_hash": "0x1539dacdf787f8de1eb622d956caa7e9ccaaf1f3f005ffb25470d379481e447d" }, "events": [...], "success": true, "total_fee_amount": 2510624816339150 } } ``` ## fetch\_group(owner, group\_id) Fetches group information from the RBAC system based on the provided group ID and owner's address. | Parameter | Type | EVM | Substrate | Description | | ------------- | ----- | -------- | --------- | ---------------------------------------------- | | **owner** | `str` | Required | Required | Address representing the owner of the group. | | **group\_id** | `str` | Required | Required | ID of the group to be fetched (32 characters). | ### Fetch Group Code Examples ```python EVM Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) group_id = "38c9ccb3-2346-4b25-ab28-51e1e2e3" response = sdk.rbac.fetch_group(owner=EVM_ADDRESS, group_id=group_id) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "id": "38c9ccb3-2346-4b25-ab28-51e1e2e3", "name": "test_group_1", "enabled": true } ``` ```python Substrate Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) group_id = "1801710e-b83b-4f1e-ba91-f7d53dd2" response = sdk.rbac.fetch_group(owner=SUBSTRATE_ADDRESS, group_id=group_id) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "id": "1801710e-b83b-4f1e-ba91-f7d53dd2", "name": "test_group_1", "enabled": true } ``` ## fetch\_groups(owner) Used to fetch all the groups associated with the passed owner address. | Parameter | Type | EVM | Substrate | Description | | --------- | ----- | -------- | --------- | --------------------------------------------------------- | | **owner** | `str` | Required | Required | Address representing the owner of all the fetched groups. | ### Fetch Groups Code Examples ```python EVM Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) response = sdk.rbac.fetch_groups(owner=EVM_ADDRESS) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "id": "afd2ae0e-b1db-42b9-bca1-93e9328b", "name": "test_group_1", "enabled": true }, { "id": "58a52684-3ace-4e72-a025-85085b1d", "name": "peaq-group-1", "enabled": true } ] ``` ```python Substrate Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) response = sdk.rbac.fetch_groups(owner=SUBSTRATE_ADDRESS) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "id": "06856fab-c5ff-40cd-8e83-634264c6", "name": "test_group_1", "enabled": true }, { "id": "63d6417e-1a03-4fd8-9439-295ce979", "name": "peaq-group-1", "enabled": true } ] ``` ## update\_group(group\_id, group\_name) Allows updating the name of an existing group in the RBAC system. | Parameter | Type | EVM | Substrate | Description | | --------------- | ----- | -------- | --------- | ---------------------------------------------- | | **group\_id** | `str` | Required | Required | ID of the group to be updated (32 characters). | | **group\_name** | `str` | Required | Required | New name for the group. | ### Update Group Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) group_id = "d07cd755-8f2f-497f-ad53-9d6cf828" new_group_name = "updated_group_1" response = sdk.rbac.update_group(group_id=group_id, group_name=new_group_name) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC update group call for group d07cd755-8f2f-497f-ad53-9d6cf828 with name updated_group_1. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0xadb32ee164303763643735352d386632662d343937662d616435332d39643663663832380000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000f757064617465645f67726f75705f310000000000000000000000000000000000" } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) group_id = "38c9ccb3-2346-4b25-ab28-51e1e2e3" new_group_name = "updated_group_1" response = sdk.rbac.update_group(group_id=group_id, group_name=new_group_name) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully updated group 38c9ccb3-2346-4b25-ab28-51e1e2e3 with name updated_group_1.", "receipt": { "transactionHash": "0xcffd438327fdc964477ae78f3cc5d905d9d313f2978b6dc38ed7f4ed9647ff89", "transactionIndex": 0, "blockHash": "0xf130dd47b1a2c0e96a53e498d63bdbc911a087e53fec68b40162136e230558cf", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5847406, "cumulativeGasUsed": 34396, "gasUsed": 34396, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x874a12c9c30f960e54438e406d3f35a9c0b1e41f61c910516d816ec7ed9011c6" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a396..." "blockHash": "0xf130dd47b1a2c0e96a53e498d63bdbc911a087e53fec68b40162136e230558cf", "blockNumber": 5847406, "transactionHash": "0xcffd438327fdc964477ae78f3cc5d905d9d313f2978b6dc38ed7f4ed9647ff89", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x0000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) group_id = "4d56f1e4-b5bd-4a41-8766-1a02d322" new_group_name = "updated_group_1" response = sdk.rbac.update_group(group_id=group_id, group_name=new_group_name) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC update group call for group 4d56f1e4-b5bd-4a41-8766-1a02d322 with name updated_group_1. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "update_group", "call_args": { "group_id": "4d56f1e4-b5bd-4a41-8766-1a02d322", "name": "updated_group_1" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) group_id = "1801710e-b83b-4f1e-ba91-f7d53dd2" new_group_name = "updated_group_1" response = sdk.rbac.update_group(group_id=group_id, group_name=new_group_name) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully updated group 1801710e-b83b-4f1e-ba91-f7d53dd2 with name updated_group_1.", "receipt": { "extrinsic_hash": "0xd93d6afe32e8a494f6e4587c3ebcc28db447eb9dcb205f2b554ccb7f23fa7720", "block_hash": "0x734644badfe943015d101c44e287fc92f4b666285490eb8ed36f6fd5dcfcb2c9", "finalized": false, "extrinsic_index": 2, "call_module": "PeaqRbac", "call_function": "update_group", "call_args": { "group_id": "1801710e-b83b-4f1e-ba91-f7d53dd2", "name": "updated_group_1" }, "events": [...], "total_fee": 2139486851158385 } } ``` ## disable\_group(group\_id) Disables a group within the RBAC system. A disabled group cannot be used for new role assignments. | Parameter | Type | EVM | Substrate | Description | | ------------- | ----- | -------- | --------- | ----------------------------------------------- | | **group\_id** | `str` | Required | Required | ID of the group to be disabled (32 characters). | ### Disable Group Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) group_id = "d07cd755-8f2f-497f-ad53-9d6cf828" response = sdk.rbac.disable_group(group_id=group_id) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC disable group call for group d07cd755-8f2f-497f-ad53-9d6cf828. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0x47538f1264303763643735352d386632..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) group_id = "38c9ccb3-2346-4b25-ab28-51e1e2e3" response = sdk.rbac.disable_group(group_id=group_id) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully disabled group 38c9ccb3-2346-4b25-ab28-51e1e2e3.", "receipt": { "transactionHash": "0x3d28fb7e4afb225f04c7cb2bf28e0bdc7bd8bbc57b14a37dbc4e034e2b5d0949", "transactionIndex": 0, "blockHash": "0x0c2ad855d1d9a1a52a65e76f21cb20bbdefddeaa36768d6255d5a9230739f769", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5848348, "cumulativeGasUsed": 33784, "gasUsed": 33784, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x35ffdf1d57e58fa1ceb80230b9f8a6e07c11374d095968b31d6e2fdcc4c53d7c" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c...", "blockHash": "0x0c2ad855d1d9a1a52a65e76f21cb20bbdefddeaa36768d6255d5a9230739f769", "blockNumber": 5848348, "transactionHash": "0x3d28fb7e4afb225f04c7cb2bf28e0bdc7bd8bbc57b14a37dbc4e034e2b5d0949", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x000000000000000000000000000000000008000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) group_id = "4d56f1e4-b5bd-4a41-8766-1a02d322" response = sdk.rbac.disable_group(group_id=group_id) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC disable group call for group 4d56f1e4-b5bd-4a41-8766-1a02d322. You must sign and send externally.", "call": { "value": { "call_module": "PeaqRbac", "call_function": "disable_group", "call_args": { "group_id": "4d56f1e4-b5bd-4a41-8766-1a02d322" } } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) group_id = "1801710e-b83b-4f1e-ba91-f7d53dd2" response = sdk.rbac.disable_group(group_id=group_id) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully disabled group 1801710e-b83b-4f1e-ba91-f7d53dd2.", "receipt": { "extrinsic_hash": "0xfba3fc23dd47dc26f349a998b4e26b97bbfd7ed11bd3d6cc1c64af1de3b5e9f1", "block_hash": "0x6df45a60532c4a215366f34b60fb0934ac275fc3a5244c14bb007a2ad4a3304d", "finalized": false, "events": [...], "total_fee_amount": 2138625423184026 } } ``` ## assign\_role\_to\_group(group\_id, role\_id) Assigns a role to a group in the RBAC system. This grants all members of the group the permissions associated with the role. | Parameter | Type | EVM | Substrate | Description | | ------------- | ----- | -------- | --------- | ------------------------------------------------------ | | **group\_id** | `str` | Required | Required | ID of the group to assign the role to (32 characters). | | **role\_id** | `str` | Required | Required | ID of the role to be assigned (32 characters). | ### Assign Role to Group Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) group_id = "d07cd755-8f2f-497f-ad53-9d6cf828" role_id = "0bb60413-ebdd-44fb-ab2c-e7e0714f" response = sdk.rbac.assign_role_to_group(group_id=group_id, role_id=role_id) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC assign role to group call for role 0bb60413-ebdd-44fb-ab2c-e7e0714f and group d07cd755-8f2f-497f-ad53-9d6cf828. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0x03c4d7fb30626236303431332d656264642..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) group_id = "38c9ccb3-2346-4b25-ab28-51e1e2e3" role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" response = sdk.rbac.assign_role_to_group(group_id=group_id, role_id=role_id) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully assigned role afd2ae0e-b1db-42b9-bca1-93e9328b to group 38c9ccb3-2346-4b25-ab28-51e1e2e3.", "receipt": { "transactionHash": "0x72ded295dcbbef84c8cae67af567a769c778b6bc667d0d7713cc47f92b1d6f7d", "transactionIndex": 0, "blockHash": "0x46b99ed678287ed88a332131a8df6bd31c1ee53c50ffdcd8483de01c26e02eff", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5847482, "cumulativeGasUsed": 42080, "gasUsed": 42080, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0xd651bb257632b5e6dd788e141bf8eafe9bbaab70e59f796d0984cdb7a18c8cad" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c616...", "blockHash": "0x46b99ed678287ed88a332131a8df6bd31c1ee53c50ffdcd8483de01c26e02eff", "blockNumber": 5847482, "transactionHash": "0x72ded295dcbbef84c8cae67af567a769c778b6bc667d0d7713cc47f92b1d6f7d", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x000000000000000000000000000000..." "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) group_id = "4d56f1e4-b5bd-4a41-8766-1a02d322" role_id = "bc3a3e43-6f2e-4e55-83d3-72d5148a" response = sdk.rbac.assign_role_to_group(group_id=group_id, role_id=role_id) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC assign role to group call for role bc3a3e43-6f2e-4e55-83d3-72d5148a and group 4d56f1e4-b5bd-4a41-8766-1a02d322. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "assign_role_to_group", "call_args": { "role_id": "bc3a3e43-6f2e-4e55-83d3-72d5148a", "group_id": "4d56f1e4-b5bd-4a41-8766-1a02d322" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) group_id = "1801710e-b83b-4f1e-ba91-f7d53dd2" role_id = "06856fab-c5ff-40cd-8e83-634264c6" response = sdk.rbac.assign_role_to_group(group_id=group_id, role_id=role_id) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully assigned role 06856fab-c5ff-40cd-8e83-634264c6 to group 1801710e-b83b-4f1e-ba91-f7d53dd2.", "receipt": { "extrinsic_hash": "0x9bafb69fa2c5ff1de311ec6bbe3887265f52d48cc484729cfe81a7823d320090", "block_hash": "0xb34b3ba10f1eb53500d33866c660deae9ab78a6fe431e0bb136c5a9231f9f659", "finalized": false, "extrinsic_index": 2, "call": { "call_module": "PeaqRbac", "call_function": "assign_role_to_group", "call_args": [ { "name": "role_id", "type": "EntityId", "value": "0x30363835366661622d633566662d343063642d386538332d3633343236346336" }, { "name": "group_id", "type": "EntityId", "value": "0x31383031373130652d623833622d346631652d626139312d6637643533646432" } ] }, "events": [...], "total_fee_amount": 2411661613644938, "is_success": true } } ``` ## fetch\_group\_roles(owner, group\_id) Fetches all roles assigned to a specific group in the RBAC system. | Parameter | Type | EVM | Substrate | Description | | ------------- | ----- | -------- | --------- | ---------------------------------------------------- | | **owner** | `str` | Required | Required | Address of the group owner. | | **group\_id** | `str` | Required | Required | ID of the group to fetch roles from (32 characters). | ### Fetch Group Roles Code Examples ```python EVM Fetch theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() EVM_ADDRESS = os.getenv("EVM_ADDRESS") HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) group_id = "38c9ccb3-2346-4b25-ab28-51e1e2e3" response = sdk.rbac.fetch_group_roles(owner=EVM_ADDRESS, group_id=group_id) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "role": "afd2ae0e-b1db-42b9-bca1-93e9328b", "group": "38c9ccb3-2346-4b25-ab28-51e1e2e3" } ] ``` ```python Substrate Fetch theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) group_id = "1801710e-b83b-4f1e-ba91-f7d53dd2" response = sdk.rbac.fetch_group_roles(owner=SUBSTRATE_ADDRESS, group_id=group_id) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "role": "06856fab-c5ff-40cd-8e83-634264c6", "group": "1801710e-b83b-4f1e-ba91-f7d53dd2" } ] ``` ## unassign\_role\_to\_group(group\_id, role\_id) Removes a role assignment from a group in the RBAC system. This revokes the role's permissions from all group members. | Parameter | Type | EVM | Substrate | Description | | ------------- | ----- | -------- | --------- | ---------------------------------------------------------- | | **group\_id** | `str` | Required | Required | ID of the group to unassign the role from (32 characters). | | **role\_id** | `str` | Required | Required | ID of the role to be unassigned (32 characters). | ### Unassign Role to Group Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) group_id = "d07cd755-8f2f-497f-ad53-9d6cf828" role_id = "0bb60413-ebdd-44fb-ab2c-e7e0714f" response = sdk.rbac.unassign_role_to_group(group_id=group_id, role_id=role_id) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC unassign role from group call for role 0bb60413-ebdd-44fb-ab2c-e7e0714f and group d07cd755-8f2f-497f-ad53-9d6cf828. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0xad58943730626236303431332d6562646..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) group_id = "38c9ccb3-2346-4b25-ab28-51e1e2e3" role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" response = sdk.rbac.unassign_role_to_group(group_id=group_id, role_id=role_id) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully unassigned role afd2ae0e-b1db-42b9-bca1-93e9328b from group 38c9ccb3-2346-4b25-ab28-51e1e2e3.", "receipt": { "transactionHash": "0x36dd75c5b4826e9bb644f76b1729aac2f04b32bc8af579f57b67ac0ff92cbc53", "transactionIndex": 0, "blockHash": "0x5ed9f7af6fcc840cca9edc7d0e17cd6e60beff9e72bf561c8d1017e825d3a186", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5847564, "cumulativeGasUsed": 39887, "gasUsed": 39887, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x6673f6cc0e410bc95e511d3c8b391c75558f5f7cb06ce317a6e05934ea60cff6" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c616...", "blockHash": "0x5ed9f7af6fcc840cca9edc7d0e17cd6e60beff9e72bf561c8d1017e825d3a186", "blockNumber": 5847564, "transactionHash": "0x36dd75c5b4826e9bb644f76b1729aac2f04b32bc8af579f57b67ac0ff92cbc53", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x00000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) group_id = "4d56f1e4-b5bd-4a41-8766-1a02d322" role_id = "bc3a3e43-6f2e-4e55-83d3-72d5148a" response = sdk.rbac.unassign_role_to_group(group_id=group_id, role_id=role_id) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC unassign role from group call for role bc3a3e43-6f2e-4e55-83d3-72d5148a and group 4d56f1e4-b5bd-4a41-8766-1a02d322. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "unassign_role_to_group", "call_args": { "role_id": "bc3a3e43-6f2e-4e55-83d3-72d5148a", "group_id": "4d56f1e4-b5bd-4a41-8766-1a02d322" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) group_id = "1801710e-b83b-4f1e-ba91-f7d53dd2" role_id = "06856fab-c5ff-40cd-8e83-634264c6" response = sdk.rbac.unassign_role_to_group(group_id=group_id, role_id=role_id) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully unassigned role 06856fab-c5ff-40cd-8e83-634264c6 from group 1801710e-b83b-4f1e-ba91-f7d53dd2.", "receipt": { "extrinsic_hash": "0x2543c6fddadcbae7ea487da0e7e04760fa309dc4c02574dea9222f09249bbaef", "block_hash": "0x344a637b2f9482fa4a23022db2a26e9898eaf165ff1c30f7c076b0ef376ee281", "finalized": false, "call": { "module": "PeaqRbac", "function": "unassign_role_to_group", "args": { "role_id": "06856fab-c5ff-40cd-8e83-634264c6", "group_id": "1801710e-b83b-4f1e-ba91-f7d53dd2" } }, "events": [...], "success": true, "total_fee": 2334736376131475 } } ``` ## assign\_user\_to\_group(user\_id, group\_id) Assigns a user to a group in the RBAC system. The user will inherit all roles and permissions assigned to the group. | Parameter | Type | EVM | Substrate | Description | | ------------- | ----- | -------- | --------- | ----------------------------------------------------------- | | **user\_id** | `str` | Required | Required | ID of the user to be assigned to the group (32 characters). | | **group\_id** | `str` | Required | Required | ID of the group to assign the user to (32 characters). | ### Assign User to Group Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" group_id = "d07cd755-8f2f-497f-ad53-9d6cf828" response = sdk.rbac.assign_user_to_group(user_id=user_id, group_id=group_id) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC assign user to group call for user 9e8c7866-8435-4b76-8683-709a03c9 and group d07cd755-8f2f-497f-ad53-9d6cf828. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0xa76110c439653863373836362d383433352..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" group_id = "38c9ccb3-2346-4b25-ab28-51e1e2e3" response = sdk.rbac.assign_user_to_group(user_id=user_id, group_id=group_id) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully assigned user 9e8c7866-8435-4b76-8683-709a03c9 to group 38c9ccb3-2346-4b25-ab28-51e1e2e3.", "receipt": { "transactionHash": "0x03e12d10aeb79f1a46fcd0cd1b373ce6da3056659dc7752bcd3db4ac04804bee", "transactionIndex": 0, "blockHash": "0xde2e5d5fde16013727952341cc5f38c622794c9970215b642287f231f63aad9a", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5847609, "cumulativeGasUsed": 40896, "gasUsed": 40896, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x09eada7abd703e77ea8611684f278dfc3e735c19b0b17b32d1a51e23e9032514" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c39...", "blockHash": "0xde2e5d5fde16013727952341cc5f38c622794c9970215b642287f231f63aad9a", "blockNumber": 5847609, "transactionHash": "0x03e12d10aeb79f1a46fcd0cd1b373ce6da3056659dc7752bcd3db4ac04804bee", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" group_id = "4d56f1e4-b5bd-4a41-8766-1a02d322" response = sdk.rbac.assign_user_to_group(user_id=user_id, group_id=group_id) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC assign user to group call for user 9e8c7866-8435-4b76-8683-709a03c9 and group 4d56f1e4-b5bd-4a41-8766-1a02d322. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "assign_user_to_group", "call_args": { "user_id": "9e8c7866-8435-4b76-8683-709a03c9", "group_id": "4d56f1e4-b5bd-4a41-8766-1a02d322" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" group_id = "1801710e-b83b-4f1e-ba91-f7d53dd2" response = sdk.rbac.assign_user_to_group(user_id=user_id, group_id=group_id) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully assigned user 9e8c7866-8435-4b76-8683-709a03c9 to group 1801710e-b83b-4f1e-ba91-f7d53dd2.", "receipt": { "extrinsic_hash": "0x542e07579f5d802867505afd4adfb17ff207dade3caed8757b929500afac4e87", "block_hash": "0xec8ece52b4e616c0d1fae292eb56ff804601946045bd2dea686da02003174d97", "finalized": false, "sender": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "call": { "module": "PeaqRbac", "function": "assign_user_to_group", "user_id": "9e8c7866-8435-4b76-8683-709a03c9", "group_id": "1801710e-b83b-4f1e-ba91-f7d53dd2" }, "events": [...], "success": true, "fee": 2370129280868884 } } ``` ## fetch\_user\_groups(owner, user\_id) Fetches all groups that a specific user is assigned to in the RBAC system. | Parameter | Type | EVM | Substrate | Description | | ------------ | ----- | -------- | --------- | --------------------------------------------------- | | **owner** | `str` | Required | Required | Address of the group owner. | | **user\_id** | `str` | Required | Required | ID of the user to fetch groups for (32 characters). | ### Fetch User Groups Code Examples ```python EVM Fetch theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() EVM_ADDRESS = os.getenv("EVM_ADDRESS") HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.fetch_user_groups(owner=EVM_ADDRESS, user_id=user_id) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "user": "9e8c7866-8435-4b76-8683-709a03c9", "group": "38c9ccb3-2346-4b25-ab28-51e1e2e3" } ] ``` ```python Substrate Fetch theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.fetch_user_groups(owner=SUBSTRATE_ADDRESS, user_id=user_id) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "user": "9e8c7866-8435-4b76-8683-709a03c9", "group": "1801710e-b83b-4f1e-ba91-f7d53dd2" } ] ``` ## unassign\_user\_to\_group(user\_id, group\_id) Removes a user from a group in the RBAC system. The user will lose all roles and permissions associated with the group. | Parameter | Type | EVM | Substrate | Description | | ------------- | ----- | -------- | --------- | --------------------------------------------------------------- | | **user\_id** | `str` | Required | Required | ID of the user to be unassigned from the group (32 characters). | | **group\_id** | `str` | Required | Required | ID of the group to unassign the user from (32 characters). | ### Unassign User to Group Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" group_id = "d07cd755-8f2f-497f-ad53-9d6cf828" response = sdk.rbac.unassign_user_to_group(user_id=user_id, group_id=group_id) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC unassign user from group call for user 9e8c7866-8435-4b76-8683-709a03c9 and group d07cd755-8f2f-497f-ad53-9d6cf828. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0x0b9cc42839653863373836362d38343335..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" group_id = "38c9ccb3-2346-4b25-ab28-51e1e2e3" response = sdk.rbac.unassign_user_to_group(user_id=user_id, group_id=group_id) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully unassigned user 9e8c7866-8435-4b76-8683-709a03c9 from group 38c9ccb3-2346-4b25-ab28-51e1e2e3.", "receipt": { "transactionHash": "0x60f742420c44b769d1c0184c739f9bd4552363f56e6e8619f4ca03486ba35c72", "transactionIndex": 0, "blockHash": "0x77a9c51a20451ef6cd7ee32f440a76dd1a8d9bdf3bf17ff646d081ff17a02005", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5847665, "cumulativeGasUsed": 39867, "gasUsed": 39867, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x8ce274ea85050978acba6aae9ca3616564f6fd4e74829427f298d0507a609aa6" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275...", "blockHash": "0x77a9c51a20451ef6cd7ee32f440a76dd1a8d9bdf3bf17ff646d081ff17a02005", "blockNumber": 5847665, "transactionHash": "0x60f742420c44b769d1c0184c739f9bd4552363f56e6e8619f4ca03486ba35c72", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x0000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" group_id = "4d56f1e4-b5bd-4a41-8766-1a02d322" response = sdk.rbac.unassign_user_to_group(user_id=user_id, group_id=group_id) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC unassign user from group call for user 9e8c7866-8435-4b76-8683-709a03c9 and group 4d56f1e4-b5bd-4a41-8766-1a02d322. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "unassign_user_to_group", "call_args": { "user_id": "9e8c7866-8435-4b76-8683-709a03c9", "group_id": "4d56f1e4-b5bd-4a41-8766-1a02d322" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" group_id = "1801710e-b83b-4f1e-ba91-f7d53dd2" response = sdk.rbac.unassign_user_to_group(user_id=user_id, group_id=group_id) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully unassigned user 9e8c7866-8435-4b76-8683-709a03c9 from group 1801710e-b83b-4f1e-ba91-f7d53dd2.", "receipt": { "extrinsic_hash": "0x3060b1cf3c041a79cd992c9769297e1bdfc448e034723c2c1d92afc5f63f8b34", "block_hash": "0x824263ac6db7a08f1f8608b6b376e4b837b99fe15e79bec485d42dd3d8485c0f", "finalized": false, "extrinsic_idx": 2, "call": { "call_module": "PeaqRbac", "call_function": "unassign_user_to_group", "call_args": [ { "name": "user_id", "type": "EntityId", "value": "0x39653863373836362d383433352d346237362d383638332d3730396130336339" }, { "name": "group_id", "type": "EntityId", "value": "0x31383031373130652d623833622d346631652d626139312d6637643533646432" } ] }, "events": [...], "total_fee_amount": 2334013842895666 } } ``` # Permission Source: https://docs.peaq.xyz/peaqchain/sdk-reference/python/rbac-operations/permission ## create\_permission(permission\_name, permission\_id) Used to create a new permission within the RBAC (Role-Based Access Control) system. | Parameter | Type | EVM | Substrate | Description | | -------------------- | --------------- | -------- | --------- | ------------------------------------------------------------------------------------ | | **permission\_name** | `str` | Required | Required | Name of the permission to be created. | | **permission\_id** | `Optional[str]` | Optional | Optional | ID of the permission (32 characters). If not supplied one will be generated for you. | ### Create Permission Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) permission_name = "peaq-permission-1" response = sdk.rbac.create_permission(permission_name=permission_name) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC create permission call for the permission name of peaq-permission-1 and permission id of 307a081a-45e5-4ca3-b2b5-566ceb31. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0x67a2e51533303761303831612d343565352d..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) permission_name = "peaq-permission-1" response = sdk.rbac.create_permission(permission_name=permission_name) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully added the RBAC permission under the permission name of peaq-permission-1 with the permission id of 290506fd-b316-4a10-bd33-e8e2e047.", "receipt": { "transactionHash": "0xf79c1d4e036d3fa85671ec280760991a028f9bc36dc45a4adc1be6dab161bb63", "transactionIndex": 0, "blockHash": "0x752bf6f56b1d1a739025aa778818fd2e5fe8235b5dfd332aac057b18e602f3a8", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5847866, "cumulativeGasUsed": 45011, "gasUsed": 45011, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0xc6eed8c2ac8c5641c34df1a054ceb451fa58fcb301be09f2f304c01c24c7cde9" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c32393035..." "blockHash": "0x752bf6f56b1d1a739025aa778818fd2e5fe8235b5dfd332aac057b18e602f3a8", "blockNumber": 5847866, "transactionHash": "0xf79c1d4e036d3fa85671ec280760991a028f9bc36dc45a4adc1be6dab161bb63", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x000000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) permission_name = "peaq-permission-1" response = sdk.rbac.create_permission(permission_name=permission_name) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC create permission call for the permission name of peaq-permission-1 and permission id of 392a18cb-9de1-4f41-82fd-7ed1b1e8. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "add_permission", "call_args": { "permission_id": "392a18cb-9de1-4f41-82fd-7ed1b1e8", "name": "peaq-permission-1" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) permission_name = "peaq-permission-1" response = sdk.rbac.create_permission(permission_name=permission_name) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully added the RBAC permission under the permission name of peaq-permission-1 with the permission id of 6e100511-7cc1-4365-b18e-d7b0f3ce.", "receipt": { "extrinsic_hash": "0x0755042c17a5cc1cebae3f1061913ac610723392600fe5c2f861670287eeea4d", "block_hash": "0x3cef644279f0835d209b83bb715c8d7d091cdfeffa5fb07ef74839909701c940", "finalized": false, "extrinsic_idx": 2, "call": { "call_module": "PeaqRbac", "call_function": "add_permission", "call_args": { "permission_id": "6e100511-7cc1-4365-b18e-d7b0f3ce", "name": "peaq-permission-1" } }, "events": [...], "total_fee_amount": 2510946098108087 } } ``` ## fetch\_permission(owner, permission\_id) Used to fetch a permission in the RBAC system by its permission ID. | Parameter | Type | EVM | Substrate | Description | | ------------------ | ----- | -------- | --------- | ------------------------------------------------- | | **owner** | `str` | Required | Required | Address representing the owner of the permission. | | **permission\_id** | `str` | Required | Required | ID of the permission to be fetched. | ### Fetch Permission Code Examples ```python EVM Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) permission_id = "290506fd-b316-4a10-bd33-e8e2e047" response = sdk.rbac.fetch_permission( owner=EVM_ADDRESS, permission_id=permission_id ) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "id": "290506fd-b316-4a10-bd33-e8e2e047", "name": "peaq-permission-1", "enabled": true } ``` ```python Substrate Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) permission_id = "6e100511-7cc1-4365-b18e-d7b0f3ce" response = sdk.rbac.fetch_permission( owner=SUBSTRATE_ADDRESS, permission_id=permission_id ) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "id": "6e100511-7cc1-4365-b18e-d7b0f3ce", "name": "peaq-permission-1", "enabled": true } ``` ## fetch\_permissions(owner) Used to fetch all permissions associated with the passed owner address. | Parameter | Type | EVM | Substrate | Description | | --------- | ----- | -------- | --------- | ----------------------------------------------------------------- | | **owner** | `str` | Required | Required | Address that represents the owner of all the fetched permissions. | ### Fetch Permissions Code Examples ```python EVM Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) response = sdk.rbac.fetch_permissions(owner=EVM_ADDRESS) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "id": "88dec9d0-354e-4ab6-9856-7a7eb45c", "name": "Permission-name-123", "enabled": true }, { "id": "ae7eda2f-a0fd-46db-bac3-1e19ba87", "name": "permission-name-123", "enabled": true } ] ``` ```python Substrate Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) response = sdk.rbac.fetch_permissions(owner=SUBSTRATE_ADDRESS) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "id": "77395752-8588-47c5-8bd5-0d2bf273", "name": "peaq-permission-new", "enabled": false }, { "id": "fc49ee05-f0ad-47ce-b336-62efdfec", "name": "peaq-permission-2", "enabled": true } ] ``` ## update\_permission(permission\_id, permission\_name) Updates the name of an existing permission. | Parameter | Type | EVM | Substrate | Description | | -------------------- | ----- | -------- | --------- | ----------------------------------------------- | | **permission\_id** | `str` | Required | Required | ID of the permission to update (32 characters). | | **permission\_name** | `str` | Required | Required | New name for the permission. | ### Update Permission Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) permission_id = "307a081a-45e5-4ca3-b2b5-566ceb31" permission_name = "updated-permission-1" response = sdk.rbac.update_permission( permission_id=permission_id, permission_name=permission_name ) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC update permission call for permission 307a081a-45e5-4ca3-b2b5-566ceb31 with name updated-permission-1. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0xb0c5b5ad33303761303831612d34356535..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) permission_id = "290506fd-b316-4a10-bd33-e8e2e047" permission_name = "updated-permission-1" response = sdk.rbac.update_permission( permission_id=permission_id, permission_name=permission_name ) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully updated permission 290506fd-b316-4a10-bd33-e8e2e047 with name updated-permission-1.", "receipt": { "transactionHash": "0x422c4891625ecfb872bb3dd5acaf5acd9af9e3ed6d769300c32e3e81f7f63d66", "transactionIndex": 0, "blockHash": "0x91b560fbe6d99757bb698dcb189419d2ab14a416600f70c2337052bb4f54b536", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5847998, "cumulativeGasUsed": 34459, "gasUsed": 34459, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0xf61e9daf2fc29e541fe654014f4239a5f1bd8f8e5bfbadf9211407e876a7a591" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641...", "blockHash": "0x91b560fbe6d99757bb698dcb189419d2ab14a416600f70c2337052bb4f54b536", "blockNumber": 5847998, "transactionHash": "0x422c4891625ecfb872bb3dd5acaf5acd9af9e3ed6d769300c32e3e81f7f63d66", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x000000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) permission_id = "392a18cb-9de1-4f41-82fd-7ed1b1e8" permission_name = "updated-permission-1" response = sdk.rbac.update_permission( permission_id=permission_id, permission_name=permission_name ) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC update permission call for permission 392a18cb-9de1-4f41-82fd-7ed1b1e8 with name updated-permission-1. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "update_permission", "call_args": { "permission_id": "392a18cb-9de1-4f41-82fd-7ed1b1e8", "name": "updated-permission-1" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) permission_id = "6e100511-7cc1-4365-b18e-d7b0f3ce" permission_name = "updated-permission-1" response = sdk.rbac.update_permission( permission_id=permission_id, permission_name=permission_name ) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully updated permission 6e100511-7cc1-4365-b18e-d7b0f3ce with name updated-permission-1.", "receipt": { "extrinsic_hash": "0x394bc58eb8dba5b8564dbe3c821b0506b06241eea701e2bf8cf746a612014670", "block_hash": "0x39c9e892ec55ac8f02fa0442c3023deaddee4fe8985df03425e556acce641bcf", "block_number": null, "finalized": false, "extrinsic_index": 2, "call": { "call_index": "0x670b", "call_function": "update_permission", "call_module": "PeaqRbac", "call_args": [ { "name": "permission_id", "type": "EntityId", "value": "0x36653130303531312d376363312d343336352d623138652d6437623066336365" }, { "name": "name", "type": "BoundedVec", "value": "updated-permission-1" } ], "call_hash": "0xb8275befb4fc4106a9afa27fde41cdbb8d73889e13e6adea2df45721724c7bf3" }, "events": [...], "is_success": true, "error_message": null, "weight": { "ref_time": 405299000, "proof_size": 3890 }, "total_fee_amount": 2139575238719276 } } ``` ## disable\_permission(permission\_id) Disables a permission within the RBAC system. A disabled permission cannot be used for new role assignments. | Parameter | Type | EVM | Substrate | Description | | ------------------ | ----- | -------- | --------- | ------------------------------------------------ | | **permission\_id** | `str` | Required | Required | ID of the permission to disable (32 characters). | ### Disable Permission Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) permission_id = "307a081a-45e5-4ca3-b2b5-566ceb31" response = sdk.rbac.disable_permission(permission_id=permission_id) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC disable permission call for permission 307a081a-45e5-4ca3-b2b5-566ceb31. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0x727e011e33303761303831612d343565352d..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) permission_id = "290506fd-b316-4a10-bd33-e8e2e047" response = sdk.rbac.disable_permission(permission_id=permission_id) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully disabled permission 290506fd-b316-4a10-bd33-e8e2e047.", "receipt": { "transactionHash": "0x69a7a8ee26c1ff14a848f4eeb11b7754347f729ebe61fff223b42eb773e7064d", "transactionIndex": 0, "blockHash": "0xae3e35fb404bac3f87ceb5a8de6a1b7f557c25f7a4cd7d9cc26eb3891567641a", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5848315, "cumulativeGasUsed": 33794, "gasUsed": 33794, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x8eea071fb9bda659c167469ca707aae4d94d31d2c948eda86c1d686b018e87f8" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a...", "blockHash": "0xae3e35fb404bac3f87ceb5a8de6a1b7f557c25f7a4cd7d9cc26eb3891567641a", "blockNumber": 5848315, "transactionHash": "0x69a7a8ee26c1ff14a848f4eeb11b7754347f729ebe61fff223b42eb773e7064d", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x00000000000000000000001...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) permission_id = "392a18cb-9de1-4f41-82fd-7ed1b1e8" response = sdk.rbac.disable_permission(permission_id=permission_id) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC disable permission call for permission 392a18cb-9de1-4f41-82fd-7ed1b1e8. You must sign and send externally.", "call": { "value": { "call_module": "PeaqRbac", "call_function": "disable_permission", "call_args": { "permission_id": "392a18cb-9de1-4f41-82fd-7ed1b1e8" } } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) permission_id = "6e100511-7cc1-4365-b18e-d7b0f3ce" response = sdk.rbac.disable_permission(permission_id=permission_id) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully disabled permission 6e100511-7cc1-4365-b18e-d7b0f3ce.", "receipt": { "extrinsic_hash": "0x8f3759de5fb02d8a35d00d0d0199dd77b2ebb39fbbb12b71246ac01766ac772e", "block_hash": "0x0f961991f807a99d6cd58f4dd55e2a6682cf78ed07904a73f4f205c2988009d4", "finalized": false, "events": [...], "is_success": true, "total_fee_amount": 2138997212130607 } } ``` ## assign\_permission\_to\_role(permission\_id, role\_id) Assigns a permission to a role, granting the role the capabilities defined by the permission. | Parameter | Type | EVM | Substrate | Description | | ------------------ | ----- | -------- | --------- | ----------------------------------------------------------- | | **permission\_id** | `str` | Required | Required | ID of the permission to assign (32 characters). | | **role\_id** | `str` | Required | Required | ID of the role to assign the permission to (32 characters). | ### Assign Permission to Role Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) permission_id = "307a081a-45e5-4ca3-b2b5-566ceb31" role_id = "0bb60413-ebdd-44fb-ab2c-e7e0714f" response = sdk.rbac.assign_permission_to_role( permission_id=permission_id, role_id=role_id ) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC assign permission to role call for permission 307a081a-45e5-4ca3-b2b5-566ceb31 and role 0bb60413-ebdd-44fb-ab2c-e7e0714f. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0x404147f133303761303831612d343565352d3463613..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) permission_id = "290506fd-b316-4a10-bd33-e8e2e047" role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" response = sdk.rbac.assign_permission_to_role( permission_id=permission_id, role_id=role_id ) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully assigned permission 290506fd-b316-4a10-bd33-e8e2e047 to role afd2ae0e-b1db-42b9-bca1-93e9328b.", "receipt": { "transactionHash": "0x7ef5672c39ef7b46b513ee63612fdc0557bc089dbeba8766ff1d61e1ce74c573", "transactionIndex": 0, "blockHash": "0xf56a4cfbbc5fdab452cc32601c64e743495a0eb6736b1f5b8d5e9c178a0bb933", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5848033, "cumulativeGasUsed": 42101, "gasUsed": 42101, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0xa02facfb294d21b16df2ffafbdd4e2052e6340881efccdbc403226b8cd2faabb" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646...", "blockHash": "0xf56a4cfbbc5fdab452cc32601c64e743495a0eb6736b1f5b8d5e9c178a0bb933", "blockNumber": 5848033, "transactionHash": "0x7ef5672c39ef7b46b513ee63612fdc0557bc089dbeba8766ff1d61e1ce74c573", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x000000000000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) permission_id = "392a18cb-9de1-4f41-82fd-7ed1b1e8" role_id = "bc3a3e43-6f2e-4e55-83d3-72d5148a" response = sdk.rbac.assign_permission_to_role( permission_id=permission_id, role_id=role_id ) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC assign permission to role call for permission 392a18cb-9de1-4f41-82fd-7ed1b1e8 and role bc3a3e43-6f2e-4e55-83d3-72d5148a. You must sign and send externally.", "call": { "value": { "call_module": "PeaqRbac", "call_function": "assign_permission_to_role", "call_args": { "permission_id": "392a18cb-9de1-4f41-82fd-7ed1b1e8", "role_id": "bc3a3e43-6f2e-4e55-83d3-72d5148a" } } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) permission_id = "6e100511-7cc1-4365-b18e-d7b0f3ce" role_id = "06856fab-c5ff-40cd-8e83-634264c6" response = sdk.rbac.assign_permission_to_role( permission_id=permission_id, role_id=role_id ) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully assigned permission 6e100511-7cc1-4365-b18e-d7b0f3ce to role 06856fab-c5ff-40cd-8e83-634264c6.", "receipt": { "extrinsic_hash": "0x6ed4828323df51efa9a7fd0f3ba2f1bbb30801bea1da497da96afcbddea0b2f5", "block_hash": "0x8a2d5c8336d8c2bd2787d4432aa7784f8716218234e83409278d32cb6ee4e4ef", "finalized": false, "extrinsic": { "address": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "nonce": 1902, "call": { "module": "PeaqRbac", "function": "assign_permission_to_role", "args": { "permission_id": "0x36653130303531312d376363312d343336352d623138652d6437623066336365", "role_id": "0x30363835366661622d633566662d343063642d386538332d3633343236346336" } } }, "events": [...], "success": true, "total_fee": 2412372923063492 } } ``` ## fetch\_role\_permissions(owner, role\_id) Fetches all permissions assigned to a role. | Parameter | Type | EVM | Substrate | Description | | ------------ | ----- | -------- | --------- | -------------------------------------------------------- | | **owner** | `str` | Required | Required | Address representing the owner. | | **role\_id** | `str` | Required | Required | ID of the role to fetch permissions for (32 characters). | ### Fetch Role Permissions Code Examples ```python EVM Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" response = sdk.rbac.fetch_role_permissions( owner=EVM_ADDRESS, role_id=role_id ) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "permission": "290506fd-b316-4a10-bd33-e8e2e047", "role": "afd2ae0e-b1db-42b9-bca1-93e9328b" } ] ``` ```python Substrate Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) role_id = "06856fab-c5ff-40cd-8e83-634264c6" response = sdk.rbac.fetch_role_permissions( owner=SUBSTRATE_ADDRESS, role_id=role_id ) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "permission": "6e100511-7cc1-4365-b18e-d7b0f3ce", "role": "06856fab-c5ff-40cd-8e83-634264c6" } ] ``` ## unassign\_permission\_to\_role(permission\_id, role\_id) Removes a permission from a role, revoking the role's access to the permission's capabilities. | Parameter | Type | EVM | Substrate | Description | | ------------------ | ----- | -------- | --------- | --------------------------------------------------------------- | | **permission\_id** | `str` | Required | Required | ID of the permission to unassign (32 characters). | | **role\_id** | `str` | Required | Required | ID of the role to unassign the permission from (32 characters). | ### Unassign Permission from Role Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) permission_id = "307a081a-45e5-4ca3-b2b5-566ceb31" role_id = "0bb60413-ebdd-44fb-ab2c-e7e0714f" response = sdk.rbac.unassign_permission_to_role( permission_id=permission_id, role_id=role_id ) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC unassign permission from role call for permission 307a081a-45e5-4ca3-b2b5-566ceb31 and role 0bb60413-ebdd-44fb-ab2c-e7e0714f. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0xba427c9e33303761303831612d343..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) permission_id = "290506fd-b316-4a10-bd33-e8e2e047" role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" response = sdk.rbac.unassign_permission_to_role( permission_id=permission_id, role_id=role_id ) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully unassigned permission 290506fd-b316-4a10-bd33-e8e2e047 from role afd2ae0e-b1db-42b9-bca1-93e9328b.", "receipt": { "transactionHash": "0xcfc2d4b8cf66199316189377dad8649e806359d8c68efc5edb90c03d32f5edf9", "transactionIndex": 0, "blockHash": "0x0cf3a17ddd238f091767129373d937da66362ed36fbca2b38f1de73139140bd4", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5848093, "cumulativeGasUsed": 39880, "gasUsed": 39880, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x0a5a672f8e6387c8c1f9aa25df37fd046981403d96ac60f5b4e27578486db14c" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a3...", "blockHash": "0x0cf3a17ddd238f091767129373d937da66362ed36fbca2b38f1de73139140bd4", "blockNumber": 5848093, "transactionHash": "0xcfc2d4b8cf66199316189377dad8649e806359d8c68efc5edb90c03d32f5edf9", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) permission_id = "392a18cb-9de1-4f41-82fd-7ed1b1e8" role_id = "bc3a3e43-6f2e-4e55-83d3-72d5148a" response = sdk.rbac.unassign_permission_to_role( permission_id=permission_id, role_id=role_id ) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC unassign permission from role call for permission 392a18cb-9de1-4f41-82fd-7ed1b1e8 and role bc3a3e43-6f2e-4e55-83d3-72d5148a. You must sign and send externally.", "call": { "value": { "call_module": "PeaqRbac", "call_function": "unassign_permission_to_role", "call_args": { "permission_id": "392a18cb-9de1-4f41-82fd-7ed1b1e8", "role_id": "bc3a3e43-6f2e-4e55-83d3-72d5148a" } } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) permission_id = "6e100511-7cc1-4365-b18e-d7b0f3ce" role_id = "06856fab-c5ff-40cd-8e83-634264c6" response = sdk.rbac.unassign_permission_to_role( permission_id=permission_id, role_id=role_id ) ``` ```python Substrate Signed Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully unassigned permission 6e100511-7cc1-4365-b18e-d7b0f3ce from role 06856fab-c5ff-40cd-8e83-634264c6.", "receipt": { "extrinsic_hash": "0xa03cf6d5e0e00f7205b4e445f1b1a2b03bbe4bd0ff3ee98755c654efb4b2896c", "block_hash": "0x2645b98668cdce99b4548724c2572ee04d7d1221634ffe50025db287902d8f3f", "finalized": false, "extrinsic_idx": 2, "extrinsic": { "extrinsic_hash": "0xa03cf6d5e0e00f7205b4e445f1b1a2b03bbe4bd0ff3ee98755c654efb4b2896c", "extrinsic_length": 170, "address": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "signature": { "Sr25519": "0xfad2c64fe6eca2560b491fd59b69ea2f58406109be303228abc8ae8bbecb9477f0b478178aa8df1626d5f3d4f976d1d11e614d6b3b38f12cc2650ca7f1881885" }, "era": "00", "nonce": 1903, "tip": 0, "mode": { "mode": "Disabled" }, "call": { "call_index": "0x670f", "call_function": "unassign_permission_to_role", "call_module": "PeaqRbac", "call_args": [ { "name": "permission_id", "type": "EntityId", "value": "0x36653130303531312d376363312d343336352d623138652d6437623066336365" }, { "name": "role_id", "type": "EntityId", "value": "0x30363835366661622d633566662d343063642d386538332d3633343236346336" } ], "call_hash": "0xbc4f82b157963f903724fca83e7a662752fec88b912c6582970b6efd530f9e9f" } }, "events": [...], "success": true, "total_fee": 2334479631311760 } } ``` ## fetch\_user\_permissions(owner, user\_id) Fetches all permissions assigned to a user (via roles). | Parameter | Type | EVM | Substrate | Description | | ------------ | ----- | -------- | --------- | -------------------------------------------------------- | | **owner** | `str` | Required | Required | Address representing the owner. | | **user\_id** | `str` | Required | Required | ID of the user to fetch permissions for (32 characters). | In order to fetch a user permission the following flow must take place: 1. Assign permission to role 2. Assign role to user 3. Fetch user permissions The following code shows this flow. ### Fetch User Permissions Code Examples ```python EVM Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) permission_id = "290506fd-b316-4a10-bd33-e8e2e047" role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" sdk.rbac.assign_permission_to_role(permission_id=permission_id, role_id=role_id) user_id = "9e8c7866-8435-4b76-8683-709a03c9" sdk.rbac.assign_role_to_user(user_id=user_id, role_id=role_id) response = sdk.rbac.fetch_user_permissions( owner=EVM_ADDRESS, user_id=user_id ) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "id": "763207f6-32ef-4236-969d-d1b81ac4", "name": "peaq-permission-2", "enabled": true }, { "id": "290506fd-b316-4a10-bd33-e8e2e047", "name": "updated-permission-1", "enabled": true } ] ``` ```python Substrate Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) permission_id = "6e100511-7cc1-4365-b18e-d7b0f3ce" role_id = "06856fab-c5ff-40cd-8e83-634264c6" sdk.rbac.assign_permission_to_role(permission_id=permission_id, role_id=role_id) user_id = "9e8c7866-8435-4b76-8683-709a03c9" sdk.rbac.assign_role_to_user(user_id=user_id, role_id=role_id) response = sdk.rbac.fetch_user_permissions( owner=SUBSTRATE_ADDRESS, user_id=user_id ) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "id": "6e100511-7cc1-4365-b18e-d7b0f3ce", "name": "updated-permission-1", "enabled": true }, { "id": "fc49ee05-f0ad-47ce-b336-62efdfec", "name": "peaq-permission-2", "enabled": true } ] ``` ## fetch\_group\_permissions(owner, group\_id) Fetches all permissions assigned to a group (via roles). | Parameter | Type | EVM | Substrate | Description | | ------------- | ----- | -------- | --------- | --------------------------------------------------------- | | **owner** | `str` | Required | Required | Address representing the owner. | | **group\_id** | `str` | Required | Required | ID of the group to fetch permissions for (32 characters). | In order to fetch a group permission the following flow must take place: 1. Assign permission to role 2. Assign role to group 3. Fetch group permissions The following code shows this flow. ### Fetch Group Permissions Code Examples ```python EVM Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) permission_id = "290506fd-b316-4a10-bd33-e8e2e047" role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" sdk.rbac.assign_permission_to_role(permission_id=permission_id, role_id=role_id) group_id = "38c9ccb3-2346-4b25-ab28-51e1e2e3" sdk.rbac.assign_role_to_group(group_id=group_id, role_id=role_id) response = sdk.rbac.fetch_group_permissions( owner=EVM_ADDRESS, group_id=group_id ) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "id": "290506fd-b316-4a10-bd33-e8e2e047", "name": "updated-permission-1", "enabled": true } ] ``` ```python Substrate Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) permission_id = "6e100511-7cc1-4365-b18e-d7b0f3ce" role_id = "06856fab-c5ff-40cd-8e83-634264c6" sdk.rbac.assign_permission_to_role(permission_id=permission_id, role_id=role_id) group_id = "1801710e-b83b-4f1e-ba91-f7d53dd2" sdk.rbac.assign_role_to_group(group_id=group_id, role_id=role_id) response = sdk.rbac.fetch_group_permissions( owner=SUBSTRATE_ADDRESS, group_id=group_id ) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "id": "6e100511-7cc1-4365-b18e-d7b0f3ce", "name": "updated-permission-1", "enabled": true } ] ``` # Role Source: https://docs.peaq.xyz/peaqchain/sdk-reference/python/rbac-operations/role Python SDK provides support for creating and managing roles in the peaq network's Role-Based Access Control (RBAC) system. ## create\_role(role\_name, role\_id) Creates a new role in the peaq network's Role-Based Access Control (RBAC) system. The role will be owned by the address derived from the seed (if provided) or must be signed externally. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------- | | **role\_name** | `string` | Required | Required | Name of the role to be created. | | **role\_id** | `string` | Optional | Optional | Unique identifier for the role (32 characters). If not provided, one will be auto-generated. | ### Create Role Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) role_name = "test_role_1" response = sdk.rbac.create_role(role_name=role_name) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC create role call for the role name of test_role_1 and role id of 0bb60413-ebdd-44fb-ab2c-e7e0714f. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0xbca838d630626236303431332d656264642d343466622d616232..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) role_name = "test_role_1" response = sdk.rbac.create_role(role_name=role_name) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully added the RBAC role under the role name of test_role_1 with the role id of afd2ae0e-b1db-42b9-bca1-93e9328b.", "receipt": { "transactionHash": "0x1c60eee57692882cbd3dfae35305fb07eae6d567eb4a788bcdeda67d18ff63fb", "transactionIndex": 0, "blockHash": "0xd5cd4c0c29dd7f007dc5fe60b533faaabc4c74d1d8945aaee17a0a3e88911e47", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5846791, "cumulativeGasUsed": 44939, "gasUsed": 44939, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x298ec29f9de7335421cd619739f36aa75b4b6d617ac5c411d41ebbab529593b5" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a...", "blockHash": "0xd5cd4c0c29dd7f007dc5fe60b533faaabc4c74d1d8945aaee17a0a3e88911e47", "blockNumber": 5846791, "transactionHash": "0x1c60eee57692882cbd3dfae35305fb07eae6d567eb4a788bcdeda67d18ff63fb", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) role_name = "test_role_1" response = sdk.rbac.create_role(role_name=role_name) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC create role call for the role name of test_role_1 and role id of bc3a3e43-6f2e-4e55-83d3-72d5148a. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "add_role", "call_args": { "role_id": "bc3a3e43-6f2e-4e55-83d3-72d5148a", "name": "test_role_1" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) role_name = "test_role_1" response = sdk.rbac.create_role(role_name=role_name) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully added the RBAC role under the role name of test_role_1 with the role id of 06856fab-c5ff-40cd-8e83-634264c6.", "receipt": { "extrinsic_hash": "0x491b16e0bb66505a93b84bdaf7b9114dbde62b4e6e56ffb90f6a005e4aea2393", "block_hash": "0x16ef5978e10c9b9b7a7aad676cf6db8531f44175ed72af43bae1384b4e5b9b0d", "address": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "call": { "module": "PeaqRbac", "function": "add_role", "args": { "role_id": "06856fab-c5ff-40cd-8e83-634264c6", "name": "test_role_1" } }, "events": [...], "total_fee_amount": 2510932068336512, "status": "success" } } ``` ## fetch\_role(owner, role\_id) Fetches a specific role by its ID and owner address from the peaq network's RBAC system. | Parameter | Type | EVM | Substrate | Description | | ------------ | -------- | -------- | --------- | ------------------------------------------------------- | | **owner** | `string` | Required | Required | Address representing the owner of the role. | | **role\_id** | `string` | Required | Required | Unique identifier of the role to fetch (32 characters). | ### Fetch Role Code Examples ```python EVM Fetch theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" response = sdk.rbac.fetch_role(owner=EVM_ADDRESS, role_id=role_id) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "id": "afd2ae0e-b1db-42b9-bca1-93e9328b", "name": "test_role_1", "enabled": true } ``` ```python Substrate Fetch theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) role_id = "06856fab-c5ff-40cd-8e83-634264c6" response = sdk.rbac.fetch_role(owner=SUBSTRATE_ADDRESS, role_id=role_id) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "id": "06856fab-c5ff-40cd-8e83-634264c6", "name": "test_role_1", "enabled": true } ``` ## fetch\_roles(owner) Fetches all roles associated with the specified owner address from the peaq network's RBAC system. | Parameter | Type | EVM | Substrate | Description | | --------- | -------- | -------- | --------- | ----------------------------------------------------------- | | **owner** | `string` | Required | Required | Address that represents the owner of all the fetched roles. | ### Fetch Roles Code Examples ```python EVM Fetch theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) response = sdk.rbac.fetch_roles(owner=EVM_ADDRESS) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "id": "b68a5589-1284-49e9-8276-0359a429", "name": "test_role_1", "enabled": true }, { "id": "e09fe342-f8ee-46d1-82e2-a60a5b6e", "name": "admin_role", "enabled": true } ] ``` ```python Substrate Fetch theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) response = sdk.rbac.fetch_roles(owner=SUBSTRATE_ADDRESS) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "id": "bc3f20d5-c519-4048-8db1-4bbf48dc", "name": "test_role_1", "enabled": true }, { "id": "bb3d5a05-3a4a-452b-a96d-372832ff", "name": "admin_role", "enabled": true } ] ``` ## update\_role(role\_id, role\_name) Updates the name of an existing role in the peaq network's RBAC system. | Parameter | Type | EVM | Substrate | Description | | -------------- | ----- | -------- | --------- | -------------------------------------------------------- | | **role\_id** | `str` | Required | Required | Unique identifier of the role to update (32 characters). | | **role\_name** | `str` | Required | Required | New name to assign to the role. | ### Update Role Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) role_id = "0bb60413-ebdd-44fb-ab2c-e7e0714f" new_role_name = "updated-peaq-role" response = sdk.rbac.update_role( role_id=role_id, role_name=new_role_name ) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC update role call for role 0bb60413-ebdd-44fb-ab2c-e7e0714f with name updated-peaq-role. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0x7cc0a218306262363034313..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" new_role_name = "updated-peaq-role" response = sdk.rbac.update_role( role_id=role_id, role_name=new_role_name ) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully updated role afd2ae0e-b1db-42b9-bca1-93e9328b with name updated-peaq-role.", "receipt": { "transactionHash": "0x2e37e5f390d23b0b2d1e3b7360c6a85e6120ece97d5f5c8f4bc0f66b749d9f5d", "transactionIndex": 0, "blockHash": "0x5cd3a0cee6a138d3c37bba32719f622b68681ca778a539afe4d9c2e36dcbf15a", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5846913, "cumulativeGasUsed": 34419, "gasUsed": 34419, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0xc43fa962e2791d84b2f4ad1079b7020844acab2410952d61f00c544d837bc47f" ], "data": "0x0000000000000000000000009eeab1ac...", "blockHash": "0x5cd3a0cee6a138d3c37bba32719f622b68681ca778a539afe4d9c2e36dcbf15a", "blockNumber": 5846913, "transactionHash": "0x2e37e5f390d23b0b2d1e3b7360c6a85e6120ece97d5f5c8f4bc0f66b749d9f5d", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x0000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) role_id = "bc3a3e43-6f2e-4e55-83d3-72d5148a" new_role_name = "updated-peaq-role" response = sdk.rbac.update_role( role_id=role_id, role_name=new_role_name ) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC update role call for role bc3a3e43-6f2e-4e55-83d3-72d5148a with name updated-peaq-role. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "update_role", "call_args": { "role_id": "bc3a3e43-6f2e-4e55-83d3-72d5148a", "name": "updated-peaq-role" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) role_id = "06856fab-c5ff-40cd-8e83-634264c6" new_role_name = "updated-peaq-role" response = sdk.rbac.update_role( role_id=role_id, role_name=new_role_name ) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully updated role 06856fab-c5ff-40cd-8e83-634264c6 with name updated-peaq-role.", "receipt": { "extrinsic_hash": "0xb92e6221fda595175739bc9a5882edfd0dd23193e24636a2037a5d69018b3c3e", "block_hash": "0xa22f5ac2f68f587f82d3b0b6b982708e85dbcb9fce874959effe02bfc2d49d33", "extrinsic_index": 3, "address": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "call": { "call_index": "0x6703", "call_function": "update_role", "call_module": "PeaqRbac", "call_args": [ { "name": "role_id", "type": "EntityId", "value": "0x30363835366661622d633566662d343063642d386538332d3633343236346336" }, { "name": "name", "type": "BoundedVec", "value": "updated-peaq-role" } ], "call_hash": "0x05f9c3dc3912c2cd4315e549a3f83016ba29d05361bd73df541606064492c7e6" }, "events": [...], "total_fee": 2139430732072111 } } ``` ## disable\_role(role\_id) Disables a role in the peaq network's RBAC system, making it inactive while preserving its data. | Parameter | Type | EVM | Substrate | Description | | ------------ | ----- | -------- | --------- | --------------------------------------------------------- | | **role\_id** | `str` | Required | Required | Unique identifier of the role to disable (32 characters). | ### Disable Role Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) role_id = "0bb60413-ebdd-44fb-ab2c-e7e0714f" response = sdk.rbac.disable_role(role_id=role_id) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC disable role call for role 0bb60413-ebdd-44fb-ab2c-e7e0714f. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0x7c4a13b130626236303431332d656264642..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" response = sdk.rbac.disable_role(role_id=role_id) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully disabled role afd2ae0e-b1db-42b9-bca1-93e9328b.", "receipt": { "transactionHash": "0x5de14837069c6b4992b0d7da99de6c7a0759e402cf4be2768bfa28a126be9e0e", "transactionIndex": 0, "blockHash": "0x5f1460c07e1a1dd4951fe258461706f5402b15c6b42fc68c2ba3e2f8d2f3c034", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5848370, "cumulativeGasUsed": 33783, "gasUsed": 33783, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x47808f9766351846ab3f47ddfd0cfc00fd1a4831674f2c872826932b0184d0be" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c61666432616530652d62316...", "blockHash": "0x5f1460c07e1a1dd4951fe258461706f5402b15c6b42fc68c2ba3e2f8d2f3c034", "blockNumber": 5848370, "transactionHash": "0x5de14837069c6b4992b0d7da99de6c7a0759e402cf4be2768bfa28a126be9e0e", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x00000000000000000000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) role_id = "bc3a3e43-6f2e-4e55-83d3-72d5148a" response = sdk.rbac.disable_role(role_id=role_id) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC disable role call for role bc3a3e43-6f2e-4e55-83d3-72d5148a. You must sign and send externally.", "call": { "value": { "call_module": "PeaqRbac", "call_function": "disable_role", "call_args": { "role_id": "bc3a3e43-6f2e-4e55-83d3-72d5148a" } } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) role_id = "06856fab-c5ff-40cd-8e83-634264c6" response = sdk.rbac.disable_role(role_id=role_id) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully disabled role 06856fab-c5ff-40cd-8e83-634264c6.", "receipt": { "extrinsic_hash": "0x2f1dbdc1c1cdd054a0f62b091795098b510894b328605bd37508cdeb7deab84e", "block_hash": "0x347ea0edb1da99e6323cc0e2ec1424af9291d4a9a4d08b88eafadb31bf380b78", "finalized": false, "events": [...], "total_fee_amount": 2138608587458143 } } ``` ## assign\_role\_to\_user(role\_id, user\_id) Assigns a specific role to a user in the peaq network's RBAC system. | Parameter | Type | EVM | Substrate | Description | | ------------ | ----- | -------- | --------- | -------------------------------------------------------------------- | | **role\_id** | `str` | Required | Required | Unique identifier of the role to assign (32 characters). | | **user\_id** | `str` | Required | Required | Unique identifier of the user to assign the role to (32 characters). | ### Assign Role to User Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) role_id = "0bb60413-ebdd-44fb-ab2c-e7e0714f" user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.assign_role_to_user(role_id=role_id, user_id=user_id) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC assign role to user call for role 0bb60413-ebdd-44fb-ab2c-e7e0714f and user 9e8c7866-8435-4b76-8683-709a03c9. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0xae8cdd7930626236303..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.assign_role_to_user(role_id=role_id, user_id=user_id) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully assigned role afd2ae0e-b1db-42b9-bca1-93e9328b to user 9e8c7866-8435-4b76-8683-709a03c9.", "receipt": { "transactionHash": "0xeaabe5ac83c9e7f3701e21434c1c75c4740e5f1beb9f2315536732888acea8c9", "transactionIndex": 1, "blockHash": "0xcc52548f9cdc19bbe866b98d6a2d759713a9323fb66892377dbcd3a9025fe8ec", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5846990, "cumulativeGasUsed": 297661, "gasUsed": 40890, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x484758882cb51a9d0452f6ef468ec5c3baaf0363de4265321ccc1bb959e7a445" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646...", "blockHash": "0xcc52548f9cdc19bbe866b98d6a2d759713a9323fb66892377dbcd3a9025fe8ec", "blockNumber": 5846990, "transactionHash": "0xeaabe5ac83c9e7f3701e21434c1c75c4740e5f1beb9f2315536732888acea8c9", "transactionIndex": 1, "logIndex": 4, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x00000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) role_id = "bc3a3e43-6f2e-4e55-83d3-72d5148a" user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.assign_role_to_user(role_id=role_id, user_id=user_id) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC assign role to user call for role bc3a3e43-6f2e-4e55-83d3-72d5148a and user 9e8c7866-8435-4b76-8683-709a03c9. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "assign_role_to_user", "call_args": { "role_id": "bc3a3e43-6f2e-4e55-83d3-72d5148a", "user_id": "9e8c7866-8435-4b76-8683-709a03c9" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) role_id = "06856fab-c5ff-40cd-8e83-634264c6" user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.assign_role_to_user(role_id=role_id, user_id=user_id) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully assigned role 06856fab-c5ff-40cd-8e83-634264c6 to user 9e8c7866-8435-4b76-8683-709a03c9.", "receipt": { "extrinsic_hash": "0x8e7f2aeb8e3691d788f003df50f7515002fb5408d4ff0bbb2d60b69dfa1adba6", "block_hash": "0xd92005c22c82221ef4c98047724cab91c537ecb08ddc7e0a21626a47f6753922", "finalized": false, "extrinsic_index": 2, "call": { "call_module": "PeaqRbac", "call_function": "assign_role_to_user", "call_args": { "role_id": "06856fab-c5ff-40cd-8e83-634264c6", "user_id": "9e8c7866-8435-4b76-8683-709a03c9" } }, "events": [...], "total_fee": "2369909013455249" } } ``` ## fetch\_user\_roles(owner, user\_id) Fetches all roles assigned to a specific user in the peaq network's RBAC system. | Parameter | Type | EVM | Substrate | Description | | ------------ | ----- | -------- | --------- | ----------------------------------------------------------------- | | **owner** | `str` | Required | Required | Address representing the owner of the roles. | | **user\_id** | `str` | Required | Required | Unique identifier of the user to fetch roles for (32 characters). | ### Fetch User Roles Code Examples ```python EVM Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.fetch_user_roles(owner=EVM_ADDRESS, user_id=user_id) ``` ```python EVM Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "role": "65503c15-6cd7-4320-b326-3f5c7d2b", "user": "9e8c7866-8435-4b76-8683-709a03c9" }, { "role": "afd2ae0e-b1db-42b9-bca1-93e9328b", "user": "9e8c7866-8435-4b76-8683-709a03c9" } ] ``` ```python Substrate Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.fetch_user_roles(owner=SUBSTRATE_ADDRESS, user_id=user_id) ``` ```python Substrate Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} [ { "role": "06856fab-c5ff-40cd-8e83-634264c6", "user": "9e8c7866-8435-4b76-8683-709a03c9" }, { "role": "7128759c-f869-4818-8b1d-98ce4b12", "user": "9e8c7866-8435-4b76-8683-709a03c9" } ] ``` ## unassign\_role\_to\_user(role\_id, user\_id) Removes a role assignment from a user in the peaq network's RBAC system. | Parameter | Type | EVM | Substrate | Description | | ------------ | ----- | -------- | --------- | ---------------------------------------------------------------------- | | **role\_id** | `str` | Required | Required | Unique identifier of the role to unassign (32 characters). | | **user\_id** | `str` | Required | Required | Unique identifier of the user to remove the role from (32 characters). | ### Unassign Role from User Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) role_id = "0bb60413-ebdd-44fb-ab2c-e7e0714f" user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.unassign_role_to_user( role_id=role_id, user_id=user_id ) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC unassign role from user call for role 0bb60413-ebdd-44fb-ab2c-e7e0714f and user 9e8c7866-8435-4b76-8683-709a03c9. You must sign and send externally.", "tx": { "to": "0x0000000000000000000000000000000000000802", "data": "0x7663575030626236303431332d65626..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) role_id = "afd2ae0e-b1db-42b9-bca1-93e9328b" user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.unassign_role_to_user( role_id=role_id, user_id=user_id ) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully unassigned role afd2ae0e-b1db-42b9-bca1-93e9328b from user 9e8c7866-8435-4b76-8683-709a03c9.", "receipt": { "transactionHash": "0x8497af05650616bd105cf4eb88247b469f2c62c9f393dd7caaba93017c6136a1", "transactionIndex": 0, "blockHash": "0x0de592d66c9292cf8d4b5fcabcb618cacb33371f002acaeb4c0b1fb340f40eeb", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000802", "blockNumber": 5847073, "cumulativeGasUsed": 39869, "gasUsed": 39869, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000802", "topics": [ "0x0ff1a305be9c0b5a533bc46d137628af9c53f1941825d0bfe94c23c1d046dc6b" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c616664326165...", "blockHash": "0x0de592d66c9292cf8d4b5fcabcb618cacb33371f002acaeb4c0b1fb340f40eeb", "blockNumber": 5847073, "transactionHash": "0x8497af05650616bd105cf4eb88247b469f2c62c9f393dd7caaba93017c6136a1", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x00000000000000000000000000000100000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) role_id = "bc3a3e43-6f2e-4e55-83d3-72d5148a" user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.unassign_role_to_user( role_id=role_id, user_id=user_id ) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed RBAC unassign role from user call for role bc3a3e43-6f2e-4e55-83d3-72d5148a and user 9e8c7866-8435-4b76-8683-709a03c9. You must sign and send externally.", "call": { "call_module": "PeaqRbac", "call_function": "unassign_role_to_user", "call_args": { "role_id": "bc3a3e43-6f2e-4e55-83d3-72d5148a", "user_id": "9e8c7866-8435-4b76-8683-709a03c9" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) role_id = "06856fab-c5ff-40cd-8e83-634264c6" user_id = "9e8c7866-8435-4b76-8683-709a03c9" response = sdk.rbac.unassign_role_to_user( role_id=role_id, user_id=user_id ) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully unassigned role 06856fab-c5ff-40cd-8e83-634264c6 from user 9e8c7866-8435-4b76-8683-709a03c9.", "receipt": { "extrinsic_hash": "0xd1a7972b2425b07044e16265894246540b7ec90a5a5f398a71279dd5feae23a6", "block_hash": "0xae7f7e1dfe1288aa40f2cfa328d7e6e600bee95a51cb448aea9caeeeefd26396", "finalized": false, "events": [...], "is_success": true, "total_fee_amount": 2334112051296649 } } ``` # Storage Operations Source: https://docs.peaq.xyz/peaqchain/sdk-reference/python/storage-operations The peaq network allows you to store on-chain data using a simple **`key: value`** structure. ## add\_item(item\_type, item) Adds a new key-value pair to the on-chain storage. If the item is not already a string, it will be automatically JSON-serialized before storage. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | ----------------------------------------------------------------------------- | | **item\_type** | `string` | Required | Required | Key used to categorize or identify the item. Max 64 bytes. | | **item** | `object` | Required | Required | Value to store. Automatically JSON-serialized if not a string. Max 256 bytes. | ### Add Item Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) item_type='my_key' item='my_value' response = sdk.storage.add_item(item_type=item_type, item=item) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed add_item tx object for peaq storage with item type my_key and item my_value. You must sign and send it externally.", "tx": { "to": "0x0000000000000000000000000000000000000801", "data": "0x257c3c0300000000000000000..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) item_type='my_key' item='my_value' response = sdk.storage.add_item(item_type=item_type, item=item) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully added the storage item type my_key with item my_value for the address 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C.", "receipt": { "transactionHash": "0x48e40a9e450c24f5d7abd055e368c1c8a80748737f4d7e8e1c29e38a160f0cea", "transactionIndex": 0, "blockHash": "0xa696c8c3620aa5cad713faa9745fb0b105d37c555e1365881bdf3ffabcd502d9", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000801", "blockNumber": 5846526, "cumulativeGasUsed": 43014, "gasUsed": 43014, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000801", "topics": [ "0x6fbdb10fd11f8487a821a58c35e587dcc19748004073777f2a8d87bc5c25535e" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f...", "blockHash": "0xa696c8c3620aa5cad713faa9745fb0b105d37c555e1365881bdf3ffabcd502d9", "blockNumber": 5846526, "transactionHash": "0x48e40a9e450c24f5d7abd055e368c1c8a80748737f4d7e8e1c29e38a160f0cea", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x00000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) item_type='my_key' item='my_value' response = sdk.storage.add_item(item_type=item_type, item=item) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed add_item call object for peaq storage with item type my_key and item my_value. You must sign and send it externally.", "call": { "call_module": "PeaqStorage", "call_function": "add_item", "call_args": { "item_type": "my_key", "item": "my_value" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) item_type='my_key' item='my_value' response = sdk.storage.add_item(item_type=item_type, item=item) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully added the storage item type my_key with item my_value for the address 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg.", "receipt": { "extrinsic_hash": "0xc6f0a03731031b93b8da6fe45548f5373e4f36edbe0fad95934077e5eb1d504c", "block_hash": "0xf25b847bc9e8c2f4b36ef91c9ab8798a3aa2195c48fa1597f7a9156391620ac7", "finalized": false, "call": { "module": "PeaqStorage", "function": "add_item", "args": { "item_type": "my_key", "item": "my_value" } }, "events": [...] } } ``` ## get\_item(item\_type, address) Retrieves a stored item by its key for the specified address. Returns the decoded value as a string. **Cross-chain storage querying:** * **Substrate**: Uses the existing Substrate API connection directly to query storage * **EVM**: Automatically uses the SDK's `base_url` to create a temporary Substrate connection. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | ---------- | ---------- | ------------------------------------------------ | | **item\_type** | `string` | Required | Required | Key used to search for the stored item. | | **address** | `string` | Required\* | Required\* | Address of the wallet that owns the stored item. | \* The address is required when no seed is set. ### Get Item Code Examples ```python EVM No Key Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) item_type='my_key' response = sdk.storage.get_item(item_type=item_type, address=EVM_ADDRESS) ``` ```python EVM No Key Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} {'my_key': 'my_value'} ``` ```python EVM Key Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) item_type='my_key' response = sdk.storage.get_item(item_type=item_type) ``` ```python EVM Key Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} {'my_key': 'my_value'} ``` ```python Substrate No Key Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) item_type='my_key' response = sdk.storage.get_item(item_type=item_type, address=SUBSTRATE_ADDRESS) ``` ```python Substrate No Key Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} {'my_key': 'my_value'} ``` ```python Substrate Key Read theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) item_type='my_key' response = sdk.storage.get_item(item_type=item_type) ``` ```python Substrate Key Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} {'my_key': 'my_value'} ``` ## update\_item(item\_type, item) Updates an existing item in on-chain storage by replacing its value with a new one. The item will be automatically JSON-serialized if it's not already a string. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | ------------------------------------------------------------------ | | **item\_type** | `string` | Required | Required | Key of the existing item to update. | | **item** | `object` | Required | Required | New value to store. Automatically JSON-serialized if not a string. | ### Update Item Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) item_type='my_key' item='my_new_value' response = sdk.storage.update_item(item_type=item_type, item=item) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed update_item tx object for peaq storage with item type my_key and item my_new_value. You must sign and send it externally.", "tx": { "to": "0x0000000000000000000000000000000000000801", "data": "0x1cd4bf09000000000..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) item_type='my_key' item='my_new_value' response = sdk.storage.update_item(item_type=item_type, item=item) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully updated the storage item type my_key with item my_new_value for the address 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C.", "receipt": { "transactionHash": "0xf477fa1b5255fd623b5a25757d42cd82ede5b287e20fe170d407d85f9a0ea412", "transactionIndex": 0, "blockHash": "0x33bd10eead5f94b2dd330625c93b17038b8a35c32dda5ef9f730bfd01bd39504", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000801", "blockNumber": 5846617, "cumulativeGasUsed": 32060, "gasUsed": 32060, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000801", "topics": [ "0x73cf4bb2cd2563692444e276f8a1db29257d4f8caabbd3443cf369e461df95ba" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b...", "blockHash": "0x33bd10eead5f94b2dd330625c93b17038b8a35c32dda5ef9f730bfd01bd39504", "blockNumber": 5846617, "transactionHash": "0xf477fa1b5255fd623b5a25757d42cd82ede5b287e20fe170d407d85f9a0ea412", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x000000000000000000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) item_type='my_key' item='my_new_value' response = sdk.storage.update_item(item_type=item_type, item=item) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed update_item call object for peaq storage with item type my_key and item my_new_value. You must sign and send it externally.", "call": { "call_module": "PeaqStorage", "call_function": "update_item", "call_args": { "item_type": "my_key", "item": "my_new_value" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) item_type='my_key' item='my_new_value' response = sdk.storage.update_item(item_type=item_type, item=item) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully updated the storage item type my_key with item my_new_value for the address 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg.", "receipt": { "extrinsic_hash": "0xd6a2930a6de697cc0c5d31b41fd53bd1e7e80aa95ac4f2feb59539a7ef02310c", "block_hash": "0x61394cf43727841ed98c5487a2c46101e7b432bd830dc76f7c40b5bd1dcc20c0", "finalized": false, "extrinsic_index": 2, "call": { "module": "PeaqStorage", "function": "update_item", "args": { "item_type": "my_key", "item": "my_new_value" } }, "events": [...], "is_success": true, "total_fee": 1954699326844373 } } ``` ## remove\_item(item\_type) Removes an existing key-value pair from on-chain storage. | Parameter | Type | EVM | Substrate | Description | | -------------- | -------- | -------- | --------- | -------------------------------------------- | | **item\_type** | `string` | Required | Required | The key representing the pair to be removed. | ### Remove Item Code Examples ```python EVM Unsigned Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM ) item_type='my_key' response = sdk.storage.remove_item(item_type=item_type) ``` ```python EVM Unsigned Tx Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed remove_item tx object for peaq storage with item type my_key. You must sign and send it externally.", "tx": { "to": "0x0000000000000000000000000000000000000801", "data": "0x2c2decc1000000000000000000000000000000000..." } } ``` ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) item_type='my_key' response = sdk.storage.remove_item(item_type=item_type) ``` ```python EVM Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully removed the storage item type my_key for the address 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C.", "receipt": { "transactionHash": "0xe5c923db4b45567c6673bfc33e87ff24676bac44656e869c06aea507e49dace0", "transactionIndex": 0, "blockHash": "0x2abc1e8dad4ad39689a4a338a2d8a60ea71e541ddcec39872f27cedc8f221816", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000801", "blockNumber": 5846657, "cumulativeGasUsed": 42572, "gasUsed": 42572, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000801", "topics": [ "0x9a7468a61900ddc322ec242b1778095d326b907aca7e792ae8a9b862903a0fa8" ], "data": "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c00000000000000000000000000000000000...", "blockHash": "0x2abc1e8dad4ad39689a4a338a2d8a60ea71e541ddcec39872f27cedc8f221816", "blockNumber": 5846657, "transactionHash": "0xe5c923db4b45567c6673bfc33e87ff24676bac44656e869c06aea507e49dace0", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x0000000000000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate Unsigned Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE ) item_type='my_key' response = sdk.storage.remove_item(item_type=item_type) ``` ```python Substrate Unsigned Call Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Constructed remove_item call object for peaq storage with item type my_key. You must sign and send it externally.", "call": { "call_module": "PeaqStorage", "call_function": "remove_item", "call_args": { "item_type": "my_key" } } } ``` ```python Substrate Write Call theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) item_type='my_key' response = sdk.storage.remove_item(item_type=item_type) ``` ```python Substrate Write Response theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Successfully removed the storage item type my_key for the address 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg.", "receipt": { "extrinsic_hash": "0x03dff995ad423247b1096c63fe0fdcc16940f2b6eba2b602ac43ae5153026680", "block_hash": "0x6140c316ca255d036cfb607bfb2707da6ae4e45bcb0bfd2e58745859194235c4", "finalized": false, "extrinsic_index": 2, "address": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "call": { "module": "PeaqStorage", "function": "remove_item", "args": { "item_type": "my_key" } }, "events": [...], "success": true, "total_fee": "2342748778674540" } } ``` # Transfer Operations Source: https://docs.peaq.xyz/peaqchain/sdk-reference/python/transfer The peaq network provides methods to transfer tokens across supported chains (peaq and agung). This includes native token transfers, ERC-20 token transfers, and ERC-721 (NFT) transfers. ## native(to, amount) Transfers the native token from the signer to a target address. | Parameter | Type | EVM | Substrate | Description | | ---------- | -------------------------------- | -------- | --------- | ------------------------------------------------ | | **to** | `str` | Required | Required | The recipient address (either SS58 or EVM H160). | | **amount** | `int \| float \| str \| Decimal` | Required | Required | Human-readable token amount (e.g., 1.5). | ### Native Token Transfer Code Examples ```python EVM to EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) amount = 1.5 response = sdk.transfer.native(to=EVM_ADDRESS, amount=amount) ``` ```python EVM to EVM receipt theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Sent 1.5 native-token from 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C to 0x3e3FF16083Bf0a444B8fF86C7156eB3368e3cefB.", "receipt": { "transactionHash": "0xeb349f98c31e414b76a674ed7b435005e9e717bc6459156d736c66999bec5728", "transactionIndex": 0, "blockHash": "0xa28e6d76a5b2aea0c24c9f4e39c548c1ddcaeebcffdf6273a4e07b3db36730c0", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x3e3FF16083Bf0a444B8fF86C7156eB3368e3cefB", "blockNumber": 5848490, "cumulativeGasUsed": 21000, "gasUsed": 21000, "contractAddress": null, "logs": [], "logsBloom": "0x000000000000000000000000000000000000000000000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python EVM to Substrate theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) amount = 1.5 response = sdk.transfer.native(to=SUBSTRATE_ADDRESS, amount=amount) ``` ```python EVM to Substrate receipt theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Sent 1.5 native-token from 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C to 5FEw7aWmqcnWDaMcwjKyGtJMjQfqYGxXmDWKVfcpnEPmUM7q.", "receipt": { "transactionHash": "0x006077a3dbc93d2ebaa3ed20e4764ba41de122edd2eaa338f4e593750dcd6d6f", "transactionIndex": 0, "blockHash": "0xb64e5c258440264ff9f354700a56e96102fddedfcabd937f20081ad40843965e", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x0000000000000000000000000000000000000809", "blockNumber": 5848515, "cumulativeGasUsed": 30423, "gasUsed": 30423, "contractAddress": null, "logs": [ { "address": "0x0000000000000000000000000000000000000809", "topics": [ "0xa1f949b36d49ecc12f7df90e9e1c2dfc2091941dbc70b29ad1b995253a47853b", "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c", "0x8c81c81b633fae0cc5713dd9d7ac2ae9fe5d63112b396554c0d8e19b199daf1d" ], "data": "0x00000000000000000000000000000000000000000000000014d1120d7b160000", "blockHash": "0xb64e5c258440264ff9f354700a56e96102fddedfcabd937f20081ad40843965e", "blockNumber": 5848515, "transactionHash": "0x006077a3dbc93d2ebaa3ed20e4764ba41de122edd2eaa338f4e593750dcd6d6f", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x000000000000000000000000080000000080000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` ```python Substrate to EVM theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") EVM_ADDRESS = os.getenv("EVM_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) amount = 1.5 response = sdk.transfer.native(to=EVM_ADDRESS, amount=amount) ``` ```python Substrate to EVM receipt theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Sent 1.5 native-token from 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg to 0x3e3FF16083Bf0a444B8fF86C7156eB3368e3cefB.", "receipt": { "extrinsic_hash": "0x18097b6e184090630d0a136c477a37b80460b8cf9e79074289e2e00b20549e07", "block_hash": "0x8616cb83c69ff2795b4cea5eab7cd3ed5dcd3c634f6dbdc3f373ecff9a96e8fc", "from": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "to": "5Gt6N8WZRfWDuULHrQyVd1qbcHiuVjMTq5MyKhDss7s2mCUF", "amount": 1500000000000000000, "fee": 1972642001704138, "events": [ ... { "event": "Transfer", "from": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "to": "5Gt6N8WZRfWDuULHrQyVd1qbcHiuVjMTq5MyKhDss7s2mCUF", "amount": 1500000000000000000 }, ... ], "success": true } } ``` ```python Substrate to Substrate theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() WSS_PEAQ_URL = os.getenv("WSS_PEAQ_URL") SUBSTRATE_SEED = os.getenv("SUBSTRATE_SEED") SUBSTRATE_ADDRESS = os.getenv("SUBSTRATE_ADDRESS") sdk = Sdk.create_instance( base_url=WSS_PEAQ_URL, chain_type=ChainType.SUBSTRATE, seed=SUBSTRATE_SEED ) amount = 1.5 response = sdk.transfer.native(to=SUBSTRATE_ADDRESS, amount=amount) ``` ```python Substrate to Substrate receipt theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Sent 1.5 native-token from 5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg to 5FEw7aWmqcnWDaMcwjKyGtJMjQfqYGxXmDWKVfcpnEPmUM7q.", "receipt": { "extrinsic_hash": "0xbe07335455ba0dfb33812499a42e8b9c16df65932281ed162696559c5de73494", "block_hash": "0x4a015f85f4a858f89977c981b0b165af0cfa110534a6f89a041843ab6946733d", "from": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "to": "5FEw7aWmqcnWDaMcwjKyGtJMjQfqYGxXmDWKVfcpnEPmUM7q", "amount": 1500000000000000000, "fee": 1972642001704138, "events": [ ... { "event": "Transfer", "from": "5Df42mkztLtkksgQuLy4YV6hmhzdjYvDknoxHv1QBkaY12Pg", "to": "5FEw7aWmqcnWDaMcwjKyGtJMjQfqYGxXmDWKVfcpnEPmUM7q", "amount": 1500000000000000000 }, ... ], "success": true } } ``` ## erc20(erc\_20\_address, recipient\_address, amount, token\_decimals) Transfers ERC-20 tokens from the signer to a recipient address. | Parameter | Type | EVM | Substrate | Description | | ---------------------- | -------------------------------- | -------- | --------- | -------------------------------------------------- | | **erc\_20\_address** | `str` | Required | N/A | The address of the ERC-20 contract. | | **recipient\_address** | `str` | Required | N/A | The recipient's address. | | **amount** | `int \| float \| str \| Decimal` | Required | N/A | Human-readable token amount. | | **token\_decimals** | `int \| float \| str \| Decimal` | Optional | N/A | Number of decimals for the token (defaults to 18). | ### ERC-20 Transfer Code Examples ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) ERC_20_ADDRESS = os.getenv("ERC_20_ADDRESS") EVM_ADDRESS = os.getenv("EVM_ADDRESS") amount = 1.5 token_decimals = 18 response = sdk.transfer.erc20( erc_20_address=ERC_20_ADDRESS, recipient_address=EVM_ADDRESS, amount=amount, token_decimals=token_decimals ) ``` ```python EVM Write Tx Receipt theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Transferred 1.5 of the erc-20 at address 0xFF78784737e8e124cb38603eD3DA78f071f50c2a to the new owner of 0x3e3FF16083Bf0a444B8fF86C7156eB3368e3cefB from the owner 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C.", "receipt": { "transactionHash": "0xc1b8bd9a02a59bcd21aa71200c0e4c7c38117636262cdeae462bd029e24538fc", "transactionIndex": 0, "blockHash": "0xef507bcb8f5fb76fb28d24573597d86618d0d5e4965f3d352bd48fd21c2178ce", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0xFF78784737e8e124cb38603eD3DA78f071f50c2a", "blockNumber": 5848651, "cumulativeGasUsed": 34492, "gasUsed": 34492, "status": 1, "effectiveGasPrice": 102000000000, "type": 2, "logs": [ { "address": "0xFF78784737e8e124cb38603eD3DA78f071f50c2a", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c", "0x0000000000000000000000003e3ff16083bf0a444b8ff86c7156eb3368e3cefb" ], "data": "0x00000000000000000000000000000000000000000000000014d1120d7b160000", "blockHash": "0xef507bcb8f5fb76fb28d24573597d86618d0d5e4965f3d352bd48fd21c2178ce", "blockNumber": 5848651, "transactionHash": "0xc1b8bd9a02a59bcd21aa71200c0e4c7c38117636262cdeae462bd029e24538fc", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ] } } ``` ## erc721(erc\_721\_address, recipient\_address, token\_id) Transfers an ERC-721 token (NFT) from the signer to a recipient address using the `safeTransferFrom` method. | Parameter | Type | EVM | Substrate | Description | | ---------------------- | ----- | -------- | --------- | ------------------------------------ | | **erc\_721\_address** | `str` | Required | N/A | The address of the ERC-721 contract. | | **recipient\_address** | `str` | Required | N/A | The recipient's address. | | **token\_id** | `int` | Required | N/A | The ID of the token to transfer. | ### ERC-721 Transfer Code Examples ```python EVM Write Tx theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os from dotenv import load_dotenv from peaq_sdk import Sdk from peaq_sdk.types import ChainType load_dotenv() HTTPS_PEAQ_URL = os.getenv("HTTPS_PEAQ_URL") EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY") sdk = Sdk.create_instance( base_url=HTTPS_PEAQ_URL, chain_type=ChainType.EVM, seed=EVM_PRIVATE_KEY ) ERC_721_ADDRESS = os.getenv("ERC_721_ADDRESS") EVM_ADDRESS = os.getenv("EVM_ADDRESS") TOKEN_ID = 1 response = sdk.transfer.erc721( erc_721_address=ERC_721_ADDRESS, recipient_address=EVM_ADDRESS, token_id=TOKEN_ID ) ``` ```python EVM Write Tx Receipt theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "message": "Transferred NFT at address 0x3319176b32a4c1e68c02ef454c148ee9b5363c33 to the new owner of 0x3e3FF16083Bf0a444B8fF86C7156eB3368e3cefB from the owner 0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C.", "receipt": { "transactionHash": "0xd483ec3f28326c27d824b2d42fc434ee16c2f25a900d87c4a6a3dd383e87547d", "transactionIndex": 0, "blockHash": "0xb71f47ee73a11a8653397bb7e3ea9e40d8bbdc5a2382db72aae854027f5e62c3", "from": "0x9Eeab1aCcb1A701aEfAB00F3b8a275a39646641C", "to": "0x3319176b32A4c1e68C02Ef454C148eE9b5363C33", "blockNumber": 5848729, "cumulativeGasUsed": 89772, "gasUsed": 89772, "contractAddress": null, "logs": [ { "address": "0x3319176b32A4c1e68C02Ef454C148eE9b5363C33", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000009eeab1accb1a701aefab00f3b8a275a39646641c", "0x0000000000000000000000003e3ff16083bf0a444b8ff86c7156eb3368e3cefb", "0x000000000000000000000000000000000000000000000000000000000000c29d" ], "data": "0x", "blockHash": "0xb71f47ee73a11a8653397bb7e3ea9e40d8bbdc5a2382db72aae854027f5e62c3", "blockNumber": 5848729, "transactionHash": "0xd483ec3f28326c27d824b2d42fc434ee16c2f25a900d87c4a6a3dd383e87547d", "transactionIndex": 0, "logIndex": 0, "transactionLogIndex": "0x0", "removed": false } ], "logsBloom": "0x0000000000000000000000000000000000800000...", "status": 1, "effectiveGasPrice": 102000000000, "type": 2 } } ``` # Permissions Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/access/permissions Define and assign permissions to roles. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot sdk = PeaqRobot() # Create a permission p1 = sdk.access.create_permission("ROBOT_CONTROL", "Full Robot Control Access") print("create_permission:", p1) # Assign permission to a role arp = sdk.access.assign_permission_to_role("ROBOT_CONTROL", "ROBOT_OPERATOR") print("assign_permission_to_role:", arp) # Read permission (may be unavailable depending on chain) print(sdk.access.read_permission("ROBOT_CONTROL")) ``` Expected output (abridged): ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} create_permission: 0x7795a3cff2c33e60... assign_permission_to_role: 0x9b060b42f3d206e9... {'exists': False, 'permission_name': 'ROBOT_CONTROL', 'permission_id': '0x524f424f545f434f4e54524f4c30303030303030303030303030303030303030'} ``` # Roles Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/access/roles Create and manage roles for machines. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot sdk = PeaqRobot() # Create a role r1 = sdk.access.create_role("ROBOT_OPERATOR", "Advanced Robot Operator") print("create_role:", r1) # Grant the role to a user identifier gr = sdk.access.grant_role("ROBOT_OPERATOR", "USER_001") print("grant_role:", gr) # Read back role details rr = sdk.access.read_role("ROBOT_OPERATOR") print(rr) ``` Expected output (abridged): ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} create_role: 0x394fcf222b439369... grant_role: 0x7f6c67dba35ae5fc... {'role_name': 'ROBOT_OPERATOR', 'role_id': '0x524f424f545f4f50455241544f5230303030303030303030303030303030', 'data': {'id': 'ROBOT_OPERATOR000000000000000000', 'name': 'Advanced Robot Operator', 'enabled': True}, 'exists': True, 'read_status': 'success'} ``` # Create Instance Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/create-instance Create a `PeaqRobot` instance to access wallet, identity, and storage modules. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot sdk = PeaqRobot() # 2) Use keystore (recommended) or explicit mnemonic # Keystore path can be set via env PEAQ_ROBOT_KEYSTORE # 3) Choose network (testnet by default). To override: sdk = PeaqRobot(network="agung", mnemonic="your 12/24 words ...") # or "peaq" / custom wss URL ``` Quick check: ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} print(sdk.address) print(sdk.balance) # float ``` Properties: * `sdk.address`: SS58 address of the active keypair * `sdk.balance`: free balance in AGUNG units (float) * `sdk.id`: identity module * `sdk.store`: storage module # Options test Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/examples/options-test title: Options & Callbacks FAST vs FINAL confirmation using the PyPI package. Save as `options_test.py` and run with a funded account. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import asyncio, time from peaq_robot import PeaqRobot from peaq_robot.types import TxOptions, ConfirmationMode sdk = PeaqRobot() # FAST (string tx-hash) tx1 = sdk.store.add_data(f"OPT_FAST_{int(time.time())}", {"t": int(time.time())}) print("FAST:", tx1) # FINAL (awaitable finalize) def on_status(s): try: print(s.model_dump()) except Exception: print({"status": str(s.status), "hash": s.hash}) res = sdk.store.add_data( f"OPT_FINAL_{int(time.time())}", {"t": int(time.time())}, tx_options=TxOptions(mode=ConfirmationMode.FINAL), on_status=on_status, ) final_receipt = asyncio.run(res.finalize) print("FINALIZED:", str(final_receipt)[:180]) ``` Install first: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install -U peaq-robotics-sdk ``` PyPI: [peaq-robotics-sdk](https://pypi.org/project/peaq-robotics-sdk/) Expected output (abridged): ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} FAST: 0x3b7f...e1 {'status': , 'hash': '0x3b7f...e1', ...} {'status': , 'hash': '0x3b7f...e1', ...} {'status': , 'hash': '0x3b7f...e1', ...} FINALIZED: {'extrinsic_hash': '0x3b7f...e1', 'success': True, 'events': [...]} ``` # Wallet demo Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/examples/wallet-demo title: Wallet Demo End‑to‑end sample using the PyPI package. Save as `wallet_demo.py` and run with a funded account. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os, time, asyncio from peaq_robot import PeaqRobot from peaq_robot.types import TxOptions, ConfirmationMode sdk = PeaqRobot() print("Address:", sdk.address) print("Balance:", sdk.balance) # Optional: fund via an env mnemonic (demo only) mnemonic = os.getenv("PEAQ_ROBOT_FUND_MNEMONIC", "") if mnemonic: from substrateinterface.keypair import Keypair kp = Keypair.create_from_mnemonic(mnemonic) _ = sdk.wallet.send_transaction( module='Balances', function='transfer_allow_death', params={'dest': sdk.address, 'value': int(1 * 10**18)}, keypair=kp, ) # Create DID (idempotent) and store a small JSON payload try: _ = sdk.id.create_identity() except Exception: pass tx = sdk.store.add_data("DEMO_DATA_001", {"robot": "demo", "status": "active"}) print("Storage tx:", tx) res = sdk.store.read_data("DEMO_DATA_001") print("Read:", res.get("data")) ``` Install first: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install -U peaq-robotics-sdk ``` PyPI: [peaq-robotics-sdk](https://pypi.org/project/peaq-robotics-sdk/) Expected output (abridged): ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} Address: 5F...abc Balance: ... Storage tx: 0x4a8b...9f Read: {'robot': 'demo', 'status': 'active'} ``` # Introduction Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/home The peaq Robotics SDK provides a clean Python interface to onboard robots to peaq. It wraps peaq runtime native Machine Functions and exposes simple modules: * [Identity (`id`)](/peaqchain/sdk-reference/robotics-sdk/python/identity/create-and-read): create and read identity documents * [Access (`access`)](/peaqchain/sdk-reference/robotics-sdk/python/access/roles): roles, permissions, and grants * [Storage (`store`)](/peaqchain/sdk-reference/robotics-sdk/python/storage/add-and-read): write and read compact JSON data Coming soon: * Verification * Time * Payment > Note: The current SDK version runs on any Unitree model with Python support, including industrial-grade robots, as well as on hobbyist-oriented robots by TurtleBot and HiWonder. Quickstart: ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot sdk = PeaqRobot() print(sdk.address) print(sdk.balance) # Create DID and store data sdk.id.create_identity() sdk.store.add_data("DEMO", {"ok": True}) ``` What you’ll learn here: * Installation and environment setup * Creating an instance * Reading/writing identity and storage data * Transaction confirmation modes and callbacks * End‑to‑end runnable examples # Create and Read Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/identity/create-and-read This concept has been absorbed into peaqOS. See [peaqID](/peaqos/concepts/peaqid) for the current identity model. Create an identity document and read it back from chain. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot sdk = PeaqRobot() # Minimal (defaults id/controller to your address) try: tx = sdk.id.create_identity() print(tx) except Exception: # Identity may already exist pass # Optional: create with a custom name (idempotent) try: _ = sdk.id.create_identity(name=f"did:peaq:{sdk.address}") except Exception: pass doc = sdk.id.read_identity() print(doc["decoded_data"]) # parsed document if available ``` Response example: ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "exists": true, "read_status": "success", "did_account": "5EX69aaLFLyDxNcbxVd3Y4FVxjyHTCXtLDx9scg3uQSXF3r8", "name": "identity:5EX69aaLFLyDxNcbxVd3Y4FVxjyHTCXtLDx9scg3uQSXF3r8", "value": "0x0a3f...", "decoded_data": { "id": "did:peaq:5EX69aaLFLyDxNcbxVd3Y4FVxjyHTCXtLDx9scg3uQSXF3r8", "controller": "did:peaq:5EX69aaLFLyDxNcbxVd3Y4FVxjyHTCXtLDx9scg3uQSXF3r8", "verificationMethods": [ { "id": "did:peaq:5EX69aaLFLyDxNcbxVd3Y4FVxjyHTCXtLDx9scg3uQSXF3r8#keys-1", "type": "Sr25519VerificationKey2020", "controller": "did:peaq:5EX69aaLFLyDxNcbxVd3Y4FVxjyHTCXtLDx9scg3uQSXF3r8", "publicKeyMultibase": "6c97e334e9199cd21944c6882f6222a1b8f5be2ff3b860021f4a074ad4d8d802" } ], "authentications": [ "did:peaq:5EX69aaLFLyDxNcbxVd3Y4FVxjyHTCXtLDx9scg3uQSXF3r8#keys-1" ], "services": [], "signature": { "type": "Sr25519VerificationKey2020", "issuer": "5EX69aaLFLyDxNcbxVd3Y4FVxjyHTCXtLDx9scg3uQSXF3r8", "hash": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" } } } ``` Fields: * `exists`, `read_status` * `name`, `value` (encoded string or raw) * `decoded_data` with `verificationMethods`, `authentications`, `services`, `signature` # Customize Identity Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/identity/customize-document This concept has been absorbed into peaqOS. See [peaqID](/peaqos/concepts/peaqid) for the current identity model. You can fully customize the identity document. All fields are optional; sensible defaults are applied if omitted. Accepted options and types: * `id`: string (default: generated from your address) * `controller`: string (default: same as id) * `verificationMethods`: array of objects `{ id: string, type: string, controller: string, publicKeyMultibase: string }` * `authentications`: array of strings (verification method references) * `services`: array of objects `{ id: string, type: string, serviceEndpoint?: string, data?: string }` * `signature`: object `{ type: string, issuer: string, hash: string }` * `name`: string (stored on-chain; default: `did:peaq:
`) * `tx_options`, `on_status`: transaction options/callback Example (custom fields): ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot from peaq_robot.types import TxOptions, ConfirmationMode sdk = PeaqRobot() res = sdk.id.create_identity( name=f"identity:{sdk.address}", id=f"identity:{sdk.address}", controller=f"identity:{sdk.address}", verificationMethods=[{ "id": f"identity:{sdk.address}#keys-1", "type": "Sr25519VerificationKey2020", "controller": f"identity:{sdk.address}", "publicKeyMultibase": sdk.id.keypair.public_key.hex(), }], authentications=[f"identity:{sdk.address}#keys-1"], services=[ {"id": "#telemetry", "type": "RobotService", "data": "v1"} ], signature={ "type": "Sr25519VerificationKey2020", "issuer": sdk.address, "hash": "demo", }, tx_options=TxOptions(mode=ConfirmationMode.FAST), ) print(res) # Read back the final on-chain document (decoded if available) doc = sdk.id.read_identity() print(doc["decoded_data"]) # includes id, controller, verificationMethods, authentications, services, signature ``` Notes: * If you only want the minimal document, you can omit all fields; defaults are applied. * Services support either serviceEndpoint or compact data for small payloads. * For FINAL confirmation and status logs, pass tx\_options and on\_status. # Installation Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/installation Install the published package from PyPI. Python 3.8–3.12 are supported. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install -U peaq-robotics-sdk ``` PyPI page: [peaq-robotics-sdk](https://pypi.org/project/peaq-robotics-sdk/) ## Verify ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot sdk = PeaqRobot() print(sdk.address) ``` Expected output: ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} 5F...abc ``` # Network Selection Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/network-selection Choose the network to connect to using a simple string or a full WebSocket URL. Recommended values: * `"peaq"` (mainnet) * `"agung"` (testnet, default) * Custom: any `wss://...` endpoint Tip: See [Connecting to peaq](http://localhost:3000/build/getting-started/connecting-to-peaq) for WSS URLs. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot # Defaults to Agung testnet sdk = PeaqRobot() # Explicit selections (mainnet first) sdk = PeaqRobot(network="peaq") sdk = PeaqRobot(network="agung") sdk = PeaqRobot(network="wss://your-endpoint") ``` # Add and Read Data Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/storage/add-and-read Write compact JSON data and read it back. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot sdk = PeaqRobot() # Write tx = sdk.store.add_data("TELEMETRY_001", {"battery": 87.3}) print(tx) # Read res = sdk.store.read_data("TELEMETRY_001") print(res["data"]) # decoded JSON if possible ``` Response example (read): ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "exists": true, "read_status": "success", "address": "5F...abc", "data_type": "TELEMETRY_001", "raw": "0x7b2262617474657279223a38372e337d", "data": { "battery": 87.3 } } ``` Limits: * `data_type` ≤ 64 chars * serialized `data` ≤ 256 chars # Telemetry and Configuration Helpers Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/storage/telemetry-and-config Helpers for common data types. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Telemetry compaction keeps payloads within chain limits sdk.store.add_telemetry("G1", { "timestamp": 1710001111, "battery_level": 87.3, "status": "ok", }) # Device configuration sdk.store.add_configuration("G1", {"mode": "autonomous", "interval": 5}) ``` # Confirmation Modes Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/transactions/confirmation-modes Choose how writes return: * FAST (default): returns a string tx-hash immediately after submission * FINAL: waits for finalization and returns a structured result ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot from peaq_robot.types import TxOptions, ConfirmationMode import asyncio sdk = PeaqRobot() # FAST tx = sdk.store.add_data("FAST_DEMO", {"ok": True}) print(tx) # FINAL res = sdk.store.add_data( "FINAL_DEMO", {"ok": True}, tx_options=TxOptions(mode=ConfirmationMode.FINAL) ) final_receipt = asyncio.run(res.finalize) print(final_receipt) ``` Outputs: * FAST returns a string tx-hash like `0x4a8b...9f`. * FINAL status progression (abridged): ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} {'status': 'TransactionStatus.BROADCAST', 'hash': '0x4a8b...9f'} {'status': 'TransactionStatus.IN_BLOCK', 'hash': '0x4a8b...9f'} {'status': 'TransactionStatus.FINALIZED', 'hash': '0x4a8b...9f'} ``` Final receipt (shape): ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "extrinsic_hash": "0x4a8b...9f", "success": true, "events": [ { "event": "ExtrinsicSuccess", "phase": "ApplyExtrinsic" }, { "event": "System.ExtrinsicSuccess", "phase": "Finalization" } ] } ``` # Status Callbacks Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/transactions/status-callbacks Receive status updates during submission/finalization. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot from peaq_robot.types import TxOptions, ConfirmationMode, TransactionStatusCallback import asyncio def on_status(s: TransactionStatusCallback): print(s.model_dump()) sdk = PeaqRobot() res = sdk.store.add_data( "FINAL_CB", {"ok": True}, tx_options=TxOptions(mode=ConfirmationMode.FINAL), on_status=on_status, ) final_receipt = asyncio.run(res.finalize) ``` Possible `status`: `BROADCAST`, `IN_BLOCK`, `FINALIZED`. Sample callback prints (abridged): ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} {'status': 'TransactionStatus.BROADCAST', 'hash': '0x3b7f...e1', 'total_confirmations': 0} {'status': 'TransactionStatus.IN_BLOCK', 'hash': '0x3b7f...e1', 'total_confirmations': 1} {'status': 'TransactionStatus.FINALIZED', 'hash': '0x3b7f...e1', 'total_confirmations': 10} ``` # Wallet Basics Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/python/wallet-and-keystore Create an SDK instance and access your address and balance. Secrets can be provided explicitly if needed. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_robot import PeaqRobot # Default instance sdk = PeaqRobot() print(sdk.address) # Optional: provide secrets explicitly PeaqRobot(mnemonic="...") PeaqRobot(private_key="0x...") ``` Tip: The examples repo uses a local keystore via `examples/.env`: ```ini theme={"theme":{"light":"github-light-default","dark":"github-dark"}} PEAQ_ROBOT_KEYSTORE=.data/wallet.json PEAQ_ROBOT_FUND_MNEMONIC="" ``` # Access Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/access Govern robot permissions using ROS 2 services that wrap the peaq RBAC pallets. Role-based access control (RBAC) is exposed in ROS 2 through `core_node`. The services map one-to-one with the Robotics Python SDK but run under lifecycle management so you can gate access policies alongside mission logic. ## Capabilities * Create roles that encapsulate operational capabilities (`operator`, `maintenance`) * Define permissions (e.g., `store_telemetry`, `emit_intent`) * Assign permissions to roles and grant roles to robot or user DIDs * Query existing policies for audit trails or dashboards ## Service Reference | Service | Type | Purpose | | ------------------------------------------ | ------------------------------------------------- | ----------------------------------- | | `/peaq_core_node/access/create_role` | `peaq_ros2_interfaces/srv/AccessCreateRole` | Add a new role identifier | | `/peaq_core_node/access/create_permission` | `peaq_ros2_interfaces/srv/AccessCreatePermission` | Register a permission definition | | `/peaq_core_node/access/assign_permission` | `peaq_ros2_interfaces/srv/AccessAssignPermToRole` | Attach a permission to a role | | `/peaq_core_node/access/grant_role` | `peaq_ros2_interfaces/srv/AccessGrantRole` | Grant a role to a robot or user DID | All services accept simple JSON payloads and return transaction hashes so you can follow progress via `peaq/tx_status`. ## Example Workflow ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # 1. Create role ros2 service call /peaq_core_node/access/create_role \ peaq_ros2_interfaces/srv/AccessCreateRole \ '{role_id: "operator"}' # 2. Create permission ros2 service call /peaq_core_node/access/create_permission \ peaq_ros2_interfaces/srv/AccessCreatePermission \ '{permission_id: "telemetry:write"}' # 3. Assign permission to role ros2 service call /peaq_core_node/access/assign_permission \ peaq_ros2_interfaces/srv/AccessAssignPermToRole \ '{role_id: "operator", permission_id: "telemetry:write"}' # 4. Grant role to robot DID ros2 service call /peaq_core_node/access/grant_role \ peaq_ros2_interfaces/srv/AccessGrantRole \ '{user_did: "did:peaq:5G...", role_id: "operator"}' ``` Responses include transaction hashes for each step. Combine them with `ros2 topic echo /peaq/tx_status` to confirm finalization. ## Automation Pattern * Trigger RBAC provisioning from CI whenever a new robot identity is created. * Use a ROS 2 Node (Python/C++) that batches service calls and verifies outcomes before declaring a robot operational. * Persist granted roles using your fleet management system for quick audits. ## Best Practices * Prefix permissions with domain context (`telemetry:write`, `mission:cancel`) to avoid collisions. * Use different roles for humans vs. robots even if they share capabilities—this keeps grants revocable per actor type. * Log RBAC service responses for compliance; they already include block hashes and timestamps. Next, wire RBAC with secure data flows by configuring [Verifiable Storage](/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/verifiable-storage). # Identity Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/identity Manage robot DIDs through lifecycle ROS 2 services with full transaction visibility. This concept has been absorbed into peaqOS. See [peaqID](/peaqos/concepts/peaqid) for the current identity model. peaq ROS 2 brings the Robotics Python SDK’s identity APIs into the ROS 2 ecosystem. `core_node` exposes fully lifecycle-managed services so every robot can create and query decentralized identities (DIDs) without leaving ROS tooling. | Service | Type | Purpose | | --------------------------------- | ----------------------------------------- | ------------------------------------------------- | | `/peaq_core_node/identity/create` | `peaq_ros2_interfaces/srv/IdentityCreate` | Create a DID with optional metadata | | `/peaq_core_node/identity/read` | `peaq_ros2_interfaces/srv/IdentityRead` | Return the DID document linked to the node wallet | ## When to Use It * Provision new robots on the peaq network directly from ROS launch flows * Attach metadata to robots for fleet discovery and access control * Audit DID state via ROS services before orchestrating missions ## Launch Prerequisites ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 launch peaq_ros2_core core.launch.py \ network:=agung \ config_yaml:=/work/peaq_ros2_examples/config/peaq_robot.yaml ros2 lifecycle set /peaq_core_node configure ros2 lifecycle set /peaq_core_node activate ``` The core node must be active to serve identity calls. Ensure the wallet in `peaq_robot.yaml` holds sufficient testnet funds. ## Create a DID ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaq_core_node/identity/create \ peaq_ros2_interfaces/srv/IdentityCreate \ '{metadata_json: "{\"fleet\": \"warehouse_drivers\"}"}' ``` Service response (abridged): ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} transaction_hash: 0xabc123... status: PENDING message: "Identity creation submitted" ``` Monitor progress via `peaq/tx_status`: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 topic echo /peaq/tx_status ``` You will see phases such as `PENDING → IN_BLOCK → FINALIZED`. The node publishes the DID to `/tmp/core_node.log` once finalized. ## Read the DID Document ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaq_core_node/identity/read \ peaq_ros2_interfaces/srv/IdentityRead ``` Example output: ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} document_json: '{"id":"did:peaq:5G...","metadata":{"fleet":"warehouse_drivers"}}' exists: true network: agung ``` Use this service to confirm metadata before handing off to mission control nodes. ## Integrate in Launch Files Embed identity creation in automated provisioning flows: ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # launch/identity_provision.launch.py from launch import LaunchDescription from launch_ros.actions import LifecycleNode def generate_launch_description(): return LaunchDescription([ LifecycleNode( package='peaq_ros2_core', executable='core_node', name='peaq_core_node', parameters=[{'config_yaml': '/work/configs/peaq_robot.yaml'}] ) ]) ``` After activation, issue a `ros2 service call` or include a lightweight automation node that wraps the service call to guarantee every robot registers itself when it boots. ## Operational Tips * Store metadata (e.g., `robot_model`, `location`) to simplify downstream access control rules. * Use `FINAL` confirmation mode for production identity provisioning by setting `PEAQ_ROBOT_CONFIRMATION_MODE=FINAL`. * Automate faucet funding in CI pipelines to avoid manual intervention when spinning up new test robots. Continue to [Access Control](/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/access) for managing roles once identities exist. # Node Info Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/node-info Inspect network, wallet, and config details exposed by `/info`. Use the info service to confirm how the core node is configured before running sensitive operations. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaq_core_node/info \ peaq_ros2_interfaces/srv/GetNodeInfo {} ``` Example response (abridged): ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "network": "agung", "wallet_address": "5F...xyz", "confirmation_mode": "FAST", "events_enabled": true, "version": "1.3.0", "build_timestamp": "2025-10-30T09:55:12Z" } ``` The service returns: * Current network + RPC endpoint * Wallet address used for signing transactions * Default confirmation mode * Event streaming status * Package version and build metadata ### Operational Uses * Validate you are pointing at the correct network before funding wallets * Capture node metadata in deployment runbooks or monitoring dashboards * Compare versions across fleet deployments to ensure consistency Pair this with [Event Streams](/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/event-streams) to monitor transaction outcomes in real time. # Service Index Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/overview All ROS 2 services exposed by the peaq core workspace with request/response context. `peaq_ros2_core` exposes the full robotics SDK as ROS 2 services. Use this index as a quick reference before diving into the detailed guides that follow. ## Service Catalog | Namespace | Service | Type | Purpose | | ------------ | ------------------- | ------------------------------------------------- | -------------------------------------------------------------------- | | `~/identity` | `create` | `peaq_ros2_interfaces/srv/IdentityCreate` | Provision a DID for the robot wallet with optional metadata | | `~/identity` | `read` | `peaq_ros2_interfaces/srv/IdentityRead` | Fetch the current DID document | | `~/storage` | `add` | `peaq_ros2_interfaces/srv/StoreAddData` | Submit compact JSON data or file pointers to the peaq storage pallet | | `~/storage` | `read` | `peaq_ros2_interfaces/srv/StoreReadData` | Retrieve stored data and IPFS CIDs | | `~/access` | `create_role` | `peaq_ros2_interfaces/srv/AccessCreateRole` | Define a new RBAC role | | `~/access` | `create_permission` | `peaq_ros2_interfaces/srv/AccessCreatePermission` | Register a permission identifier | | `~/access` | `assign_permission` | `peaq_ros2_interfaces/srv/AccessAssignPermToRole` | Link a permission to a role | | `~/access` | `grant_role` | `peaq_ros2_interfaces/srv/AccessGrantRole` | Grant a role to a robot or user DID | | `~/` | `info` | `peaq_ros2_interfaces/srv/GetNodeInfo` | Return node metadata, network, wallet address, and config summary | All services are lifecycle-aware: start the node, configure, and activate it before sending requests. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 launch peaq_ros2_core core.launch.py ros2 lifecycle set /peaq_core_node configure ros2 lifecycle set /peaq_core_node activate ``` Next explore the focused guides: * [Identity](/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/identity) * [Access Control](/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/access) * [Storage Services](/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/storage) * [Node Info](/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/node-info) # Storage Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/storage Use `/storage/add` and `/storage/read` to move data between ROS 2 and the peaq storage pallet. Write and read compact JSON data (or file references) via lifecycle ROS 2 services. For high-throughput telemetry with IPFS, see [Verifiable Storage](/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/verifiable-storage). ## Write data (`/storage/add`) ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaq_core_node/storage/add \ peaq_ros2_interfaces/srv/StoreAddData \ '{key: "DEMO", value_json: "{\"ok\": true}", confirmation_mode: "FAST"}' ``` Expected output (abridged): ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} status: submitted message: "Storage transaction initiated" tx_hash: 0xabc123... ``` ## Read data (`/storage/read`) ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaq_core_node/storage/read \ peaq_ros2_interfaces/srv/StoreReadData \ '{key: "DEMO"}' ``` Example response: ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} exists: true value_json: '{"ok": true}' cid: Qm... ``` Subsections * Add: submit JSON data with `/storage/add` * Read: retrieve JSON data with `/storage/read` * Telemetry & Configuration: see [Verifiable Storage](/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/verifiable-storage) for IPFS setup and retry policies Tips * Pair service calls with `ros2 topic echo /peaq/tx_status` to monitor confirmation phases (`PENDING`, `IN_BLOCK`, `FINALIZED`). * Use `confirmation_mode: FINAL` in production for stronger guarantees. * For files, publish via the `peaq/storage/ingest` topic handled by the storage bridge. # Example Launches Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/examples/overview Validate end-to-end ROS 2 + peaq pipelines with production-ready launch files. `peaq_ros2_examples` packages launch files that orchestrate the core services, storage bridge, and helper scripts. Use them to validate your setup or as templates for project-specific launches. ## `demo_core.launch.py` Launches the core node and events node together: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 launch peaq_ros2_examples demo_core.launch.py \ network:=agung \ confirmation_mode:=FAST \ log_level:=INFO ``` * Starts `core_node` + `events_node` * Applies configuration from `peaq_ros2_examples/config/peaq_robot.yaml` * Suitable for identity, access, and storage service testing * Emits rich telemetry on `peaq/tx_status` and `peaq/events` so monitors light up immediately ## `storage_bridge.launch.py` Included with `peaq_ros2_core`, this launch is often combined with `demo_core` to exercise storage flows: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 launch peaq_ros2_core storage_bridge.launch.py ``` * Subscribes to `peaq/storage/ingest` * Publishes `peaq/storage/status` * Honours retry policies defined in the shared configuration file * Demonstrates how RBAC-protected storage flows finalize on-chain with streaming feedback ## `e2e_test.launch.py` Runs a full end-to-end scenario used by the testing guide: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 launch peaq_ros2_examples e2e_test.launch.py \ network:=agung \ confirmation_mode:=FINAL ``` * Spins up core services, storage bridge, and test utilities * Validates DID creation, storage writes, and event streaming automatically Each launch exposes the same arguments (`network`, `confirmation_mode`, `log_level`, `config_yaml`). Extend them with additional nodes or parameters as needed for your deployment. Continue with the [example scripts](/peaqchain/sdk-reference/robotics-sdk/ros2/examples/scripts) to trigger specific blockchain operations. # Example Scripts Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/examples/scripts Run guided scripts that exercise ROS 2 services, storage bridge, and event streaming end to end. The examples package ships Python scripts that call into ROS 2 services and topics. They are great starting points for automated tests or operator tooling. ## `create_identity.py` ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 run peaq_ros2_examples create_identity ``` * Prompts for optional metadata and creates a DID via `/identity/create` * Streams transaction updates until finalized * Prints the resulting DID document when available ## `send_store_add_data.py` ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 run peaq_ros2_examples send_store_add_data ``` * Requests a key, payload, and confirmation mode interactively * Publishes telemetry through the storage bridge and waits for completion * Reads the stored data back to verify the flow Embed the client class (`StorageClient`) in your own scripts to reuse retry handling and logging. ## `emit_intent.py` Even without humanoid adapters, this script is useful for broadcasting structured events and verifying event streaming: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 run peaq_ros2_examples emit_intent ``` * Provides menus for locomotion, posture, and gesture intents * Publishes events onto `peaq/events` so you can observe payloads and transaction status updates ## `user_quickstart.py` ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 run peaq_ros2_examples user_quickstart ``` * Chains identity creation, storage operations, and access control into a single guided flow * Ideal for validating that wallet funding and configuration are correct before deploying robots ## Utilities * `inspect_sdk.py`: prints the active configuration and SDK status * `print_wallet_addr.py`: displays the wallet address associated with the current keystore * `retry_failed_storage.py`: replays failed storage submissions recorded by the storage bridge All scripts expect the workspace to be sourced and the relevant nodes running. Combine them with the [example launches](/peaqchain/sdk-reference/robotics-sdk/ros2/examples/overview) to exercise complete robotics pipelines without writing additional code and to showcase ROS 2 support as a first-class citizen of the peaq robotics platform. # Configuration Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/getting-started/configuration Provide network, wallet, and storage settings for ROS 2 nodes through environment variables or YAML. The packages read configuration from either environment variables or a YAML file referenced in launch parameters. Use whichever is easier for your deployment and keep sensitive data (wallet passwords, managed IPFS JWTs) outside of version control. ## Environment Variables ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Network and confirmation mode export PEAQ_ROBOT_NETWORK=agung export PEAQ_ROBOT_CONFIRMATION_MODE=FAST # Wallet keystore export PEAQ_ROBOT_KEYSTORE=$HOME/.peaq_robot/wallet.json export PEAQ_ROBOT_KEY_PASSWORD="super-secret" # Event forwarding and logging export PEAQ_ROBOT_EVENTS_ENABLED=true export PEAQ_ROBOT_LOG_LEVEL=INFO export PEAQ_ROBOT_LOG_FORMAT=human ``` All nodes (`core_node`, `storage_bridge_node`, `events_node`) consume these variables through the shared configuration module. When unset, defaults come from the YAML configuration or each node’s internal defaults. ## YAML Configuration Create a copy of the example file and fill in required fields: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} cp peaq_ros2_examples/config/peaq_robot.example.yaml \ peaq_ros2_examples/config/peaq_robot.yaml ``` Example snippet: ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} network: agung # or "peaq" for mainnet wallet: path: /work/peaq_wallet.json auto_generate: true default_confirmation_mode: FAST events: enabled: true storage_bridge: storage: mode: pinata pinata: jwt: "" gateway_url: "https://YOUR-GATEWAY.mypinata.cloud/ipfs" ``` Launch files pass this YAML through `config.yaml_path` parameters so that every node reads a single source of truth. Set `auto_generate: true` during testing to create a wallet automatically; switch to a managed keystore in production. ### Secret Management * Keep `PEAQ_ROBOT_KEY_PASSWORD` in an `.env` file or secret manager and source it before launching nodes. * Do not commit the populated `peaq_robot.yaml` to version control—copy it per environment instead. * Rotate managed gateway credentials regularly and store them in the same secure channel as other secrets. Proceed to [launch the core services](/peaqchain/sdk-reference/robotics-sdk/ros2/getting-started/launch-core-services) once configuration is in place. # Installation Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/getting-started/installation Set up the peaq ROS 2 workspace with Docker or a native Humble environment. The ROS 2 packages live in the [`peaq-robotics-ros2`](https://github.com/peaqnetwork/peaq-robotics-ros2) repository. Clone the workspace and choose either a Docker-based workflow or a native ROS 2 Humble setup. ## Docker Workflow (Recommended) ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} git clone https://github.com/peaqnetwork/peaq-robotics-ros2.git cd peaq-robotics-ros2 # Build the development image docker build -t peaq-ros2:latest . # Start a container with workspace mounted docker run -it --rm \ --name peaq-ros2-dev \ -v "$(pwd)":/work \ -w /work \ -p 5001:5001 -p 8080:8080 \ peaq-ros2:latest # Inside the container source /opt/ros/humble/setup.bash colcon build source install/setup.bash ``` The Docker image installs ROS 2 Humble, Python dependencies, and build tooling. Exposed ports (`5001`, `8080`) let IPFS nodes or optional managed gateways run locally when using the storage bridge. ## Native Humble Setup ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} git clone https://github.com/peaqnetwork/peaq-robotics-ros2.git cd peaq-robotics-ros2 # Install Python requirements pip install -r requirements.txt # Ensure ROS 2 Humble is installed and sourced source /opt/ros/humble/setup.bash # (Optional) install additional system dependencies sudo rosdep init 2>/dev/null || true rosdep update rosdep install --from-paths . --ignore-src -r -y || true # Build the workspace colcon build source install/setup.bash ``` ### IPFS (with Optional Managed Pinning) The storage bridge relies on an IPFS node. Follow the repository guide to install Kubo, run `ipfs daemon`, and optionally supply managed gateway credentials (e.g., Pinata, web3.storage) in the configuration file when required. Continue with [configuration](/peaqchain/sdk-reference/robotics-sdk/ros2/getting-started/configuration) once the workspace builds successfully. # Launch Core Services Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/getting-started/launch-core-services Run lifecycle-managed ROS 2 nodes for peaq identity, access, and storage operations. After configuring the workspace, start the lifecycle nodes that provide blockchain connectivity. Source both the ROS 2 distro and the workspace overlay before launching. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} source /opt/ros/humble/setup.bash source install/setup.bash # Launch core services with default parameters ros2 launch peaq_ros2_core core.launch.py ``` The launch file spins up `core_node` and `events_node` with lifecycle management enabled. Key launch arguments: | Parameter | Default | Description | | ------------------- | ------------------------------------------- | --------------------------------------------------- | | `network` | `agung` | peaq network to target (`peaq` for mainnet) | | `confirmation_mode` | `FAST` | Transaction confirmation strategy (`FAST`, `FINAL`) | | `log_level` | `INFO` | Logging level across nodes | | `config_yaml` | `peaq_ros2_examples/config/peaq_robot.yaml` | Path to configuration file | Override them inline: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 launch peaq_ros2_core core.launch.py \ network:=peaq \ confirmation_mode:=FINAL \ log_level:=DEBUG \ config_yaml:=/work/configs/robot.yaml ``` ### Lifecycle Management The nodes start in the *unconfigured* state. Use standard lifecycle commands to configure and activate them: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 lifecycle set /peaq_core_node configure ros2 lifecycle set /peaq_core_node activate ros2 lifecycle get /peaq_core_node ``` Repeat the lifecycle commands for `/peaq_events_node` if you need to pause or resume event streaming independently. Nodes publish status logs to `/tmp/core_node.log` and `/tmp/events_node.log` by default. ### Storage Bridge Launch the storage bridge when you want IPFS-backed storage flows (managed pinning optional): ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 launch peaq_ros2_core storage_bridge.launch.py \ config_yaml:=/work/peaq_ros2_examples/config/peaq_robot.yaml ``` The bridge consumes the same configuration file, handles retry logic, and exposes ROS 2 interfaces documented in [Verifiable Storage](/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/verifiable-storage). For a full demo, continue with the [example launches](/peaqchain/sdk-reference/robotics-sdk/ros2/examples/overview). # Introduction Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/home Enterprise-grade ROS 2 integration for the peaq robotics stack. The peaq Robotics SDK for ROS 2 brings blockchain-native identity, access, and storage flows to ROS applications. It exposes simple modules you can use across robot fleets: * [Identity (`id`)](/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/identity): create and read identity documents * [Access (`access`)](/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/access): roles, permissions, and grants * [Storage (`store`)](/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/storage): write and read compact JSON data * [Tether WDK (`tether`)](/peaqchain/sdk-reference/robotics-sdk/ros2/tether/overview): create EVM addresses, read USDT balances, and transfer USDT on peaq EVM Coming soon: * Verification * Time > Note: The current ROS 2 integration targets Humble (or later) and works on the same robot classes as the Robotics Python SDK—from Unitree models to TurtleBot/HiWonder—whenever ROS 2 is available on the device. Quickstart: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Clone and build git clone https://github.com/peaqnetwork/peaq-robotics-ros2.git cd peaq-robotics-ros2 pip install -r requirements.txt source /opt/ros/humble/setup.bash colcon build && source install/setup.bash # Launch core services ros2 launch peaq_ros2_core core.launch.py # Configure and activate lifecycle ros2 lifecycle set /peaq_core_node configure ros2 lifecycle set /peaq_core_node activate # Create an identity ros2 service call /peaq_core_node/identity/create \ peaq_ros2_interfaces/srv/IdentityCreate \ '{metadata_json: "{\"type\": \"robot\"}"}' ``` What you’ll learn here: * Installation and environment setup * Configuration and launching core services * Reading/writing identity and storage data * Access control (roles, permissions, grants) * Event streams and verifiable storage pipelines * End‑to‑end runnable examples # Event Streams Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/event-streams Subscribe to blockchain events and transaction lifecycles from ROS 2. The ROS 2 event pipeline keeps operators and autonomy stacks informed about blockchain activity in real time. `events_node` converts SDK callbacks into ROS topics so you can react to confirmations, failures, or custom pallet events without polling. ## Topics | Topic | Type | Description | | ---------------- | ----------------------------------- | -------------------------------------------------------------------------- | | `peaq/tx_status` | `peaq_ros2_interfaces/msg/TxStatus` | Transaction lifecycle updates (`PENDING`, `IN_BLOCK`, `FINALIZED`, errors) | | `peaq/events` | `peaq_ros2_interfaces/msg/Event` | Raw blockchain events emitted by pallets (identity, storage, access, etc.) | Both topics are QoS-configured for reliability in distributed robot fleets. ## Launch Events Node Events node starts automatically with `core.launch.py`. To run it on its own: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 run peaq_ros2_core events_node --ros-args \ -p config_yaml:=/work/peaq_ros2_examples/config/peaq_robot.yaml ros2 lifecycle set /peaq_events_node configure ros2 lifecycle set /peaq_events_node activate ``` ## Monitor Transactions ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 topic echo /peaq/tx_status ``` Example output: ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} phase: IN_BLOCK hash: 0x9b060b42... details: confirmation_mode: FAST endpoint: identity.create submitted_at: "2025-10-30T10:05:31Z" ``` Use the metadata to drive dashboards, emit alerts, or trigger retries in application logic. ## Subscribe to Events ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 topic echo /peaq/events --qos-reliability reliable ``` Events include pallet name, method, and payload JSON. Example for a DID creation: ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pallet: identity method: IdentityCreated payload: '{"owner":"did:peaq:5G...","metadata":{"fleet":"warehouse_drivers"}}' ``` ## Integrate with Autonomy Stacks * Feed `peaq/tx_status` into behavior trees to wait for blockchain confirmation before executing moves. * Use `peaq/events` to trigger ROS actions (e.g., start storage ingestion once a DID is acknowledged). * Attach event subscribers to logging pipelines for compliance and analytics. ## Troubleshooting * If events stop flowing, check lifecycle state: `ros2 lifecycle get /peaq_events_node`. * Review `/tmp/events_node.log` for connection or authentication issues. * Ensure `PEAQ_ROBOT_EVENTS_ENABLED=true` is set when relying on environment variables. With event streaming in place you have an end-to-end feedback loop across identity, access, and storage operations. Pair this with the [Messaging Index](/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/topics) and [Verifiable Storage](/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/verifiable-storage) to cover every messaging pathway. # Messaging Index Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/topics ROS 2 topics exposed by the peaq robotics workspace and how to use them. The ROS 2 workspace exposes several pub/sub channels in addition to the service APIs. Use this index to stitch blockchain events and storage flows into your autonomy stack. ## Command & Telemetry Topics | Topic | Type | Direction | Purpose | | --------------------- | ---------------------------------------- | --------- | ----------------------------------------------------- | | `peaq/storage/ingest` | `peaq_ros2_interfaces/msg/StorageIngest` | Publish | Send telemetry or file CIDs to the storage bridge | | `peaq/storage/status` | `peaq_ros2_interfaces/msg/StorageResult` | Subscribe | Track success/failure for each storage ingest | | `peaq/tx_status` | `peaq_ros2_interfaces/msg/TxStatus` | Subscribe | Transaction lifecycle updates for all peaq operations | | `peaq/events` | `peaq_ros2_interfaces/msg/Event` | Subscribe | Raw events emitted by peaq pallets | ### StorageIngest Schema ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} string key bool is_file string file_path string content string content_type string metadata_json ``` * Use `is_file=true` with `file_path` when pointing to local files that the bridge should upload to IPFS * Provide structured payloads via `content` (JSON) plus an optional `metadata_json` envelope for robot state, firmware versions, or mission identifiers ### StorageResult Schema ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} string key string cid string ipfs_url string tx_hash string status string error ``` * `status` transitions through `PENDING`, `RETRYING`, `SUCCESS`, or `FAILED` * Failed entries are also written to `/tmp/storage_bridge_failures.jsonl` so you can replay them later See [Verifiable Storage](/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/verifiable-storage) to add DID-backed authentication on top of these topics. # Verifiable Storage Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/verifiable-storage Cryptographically prove that every ROS 2 data point came from the right robot. **Verifiable Storage** is more than a storage utility—it is the trust anchor that guarantees on-chain data really originated from your robot. By combining ROS 2 lifecycle management, DID-backed authentication, and IPFS persistence (with optional managed pinning), the bridge turns raw telemetry into signed, auditable evidence. ## Why It Matters * **Authenticity first** – every ingest request is checked against the robot’s DID and RBAC grants before it ever touches the blockchain. * **Tamper proof** – payloads are hashed, pinned to IPFS, and referenced on-chain so downstream consumers can verify integrity independently (managed pinning services remain optional). * **Production ready** – lifecycle nodes, retries, and detailed status topics keep operators informed without leaving ROS tools. > **Big picture:** ROS 2 fleets can now publish verifiable data streams that regulators, partners, or marketplaces can trust instantly. ## Architecture | Stage | What Happens | Verification Hooks | | ----------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | 1. Ingest | `StorageIngest` message arrives on `peaq/storage/ingest` | Bridge loads the robot wallet, derives its DID, and checks it exists on-chain when `require_did` is enabled | | 2. Encode | Payload hashed, optional files pinned to IPFS (local or managed) | Hash and DID are logged together; RBAC rules (from the Access module) can gate who triggers ingest | | 3. Submit | Transaction sent via peaq storage pallet | Transaction is signed by the robot’s keystore and tracked through `peaq/tx_status` | | 4. Finalize | On-chain record references IPFS CID | Any consumer can fetch the CID, recompute the hash, and confirm the signing DID | ## Launch the Bridge ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 launch peaq_ros2_core storage_bridge.launch.py \ config_yaml:=/work/peaq_ros2_examples/config/peaq_robot.yaml \ log_level:=INFO ros2 lifecycle set /peaq_storage_bridge configure ros2 lifecycle set /peaq_storage_bridge activate ``` ### Verification-Centric Configuration ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} storage_bridge: robot: require_did: true # verify robot DID is registered on peaq storage: mode: both # local_ipfs | pinata | both pinata: # placeholder name used by the default config jwt: "" # works with any compatible managed gateway gateway_url: "https://your-gateway.example.com/ipfs" local_ipfs: api_url: "http://127.0.0.1:5001" gateway_url: "http://127.0.0.1:8080/ipfs" retry: max_attempts: 3 delay_seconds: 5.0 signature: algorithm: sr25519 # bridge signs payloads with the robot wallet ``` Secrets for managed gateways (JWT/API keys) and wallet passwords must stay out of source control—load them via environment variables before launch. ## IPFS Setup ### Local IPFS (Kubo) ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Install Kubo wget https://dist.ipfs.tech/kubo/v0.38.1/kubo_v0.38.1_linux-amd64.tar.gz tar -xzf kubo_v0.38.1_linux-amd64.tar.gz sudo bash kubo/install.sh # Initialize and start ipfs init ipfs daemon ``` Update the config YAML to point at your node: ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} storage_bridge: storage: mode: local_ipfs local_ipfs: api_url: http://127.0.0.1:5001 gateway_url: http://127.0.0.1:8080/ipfs ``` Optional quality-of-life settings: * `local_ipfs.save_dir`: cache directory for downloaded blobs * `local_ipfs.pin_results=true`: keep data pinned locally for quick replays ### Managed Gateway (Optional) Use a third-party IPFS pinning/gateway provider (e.g., Pinata, web3.storage, NFT.storage) only if you need off-device persistence or public access. Example environment variables: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export PEAQ_ROBOT_IPFS_JWT="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." export PEAQ_ROBOT_IPFS_GATEWAY="https://your-gateway.example.com/ipfs" ``` Reference them in the YAML (the `pinata` block name is historical—you can still point it at any managed gateway): ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} storage_bridge: storage: mode: pinata pinata: jwt: "${PEAQ_ROBOT_IPFS_JWT}" gateway_url: "${PEAQ_ROBOT_IPFS_GATEWAY}" pin: true mode: upload ``` Using both local IPFS and a managed gateway provides redundancy—set `mode: both` to mirror uploads. ### Access Control & Wallets * Ensure the wallet referenced in `wallet.path` has enough balance on the target network (fund via faucet for Agung). * Combine with RBAC by allow-listing roles under `storage_bridge.robot.allowlist_roles` when you want to restrict which robots can publish telemetry. ## Publishing Verifiable Data ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 topic pub --once /peaq/storage/ingest \ peaq_ros2_interfaces/msg/StorageIngest \ '{key: "robot:telemetry", content: "{\"battery\": 0.87}", is_file: false}' ros2 topic echo /peaq/storage/status ``` The storage status stream surfaces the CID, IPFS URL, transaction hash, and success state for each submission. Combine it with `peaq/tx_status` to see the confirmation phases tied back to the robot DID in logs. ## Reading & Verifying Downstream ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaq_core_node/storage/read \ peaq_ros2_interfaces/srv/StoreReadData \ '{key: "robot:telemetry"}' ``` The response includes the payload, IPFS CID, and the originating DID. Consumers can recompute the hash against the IPFS artifact and ensure it matches the on-chain record. ### Manual Verification Checklist 1. Fetch the CID from the service response. 2. Retrieve the payload: `ipfs cat ` or `curl /`. 3. Recompute the hash and compare with the value logged in storage bridge outputs. 4. Confirm DID ownership using `/peaq_core_node/identity/read`. ## Automated Attestation * Attach mission metadata via the `metadata_json` field in `StorageIngest` so every record includes firmware versions or profile IDs. * Pair with the [Access Control](/peaqchain/sdk-reference/robotics-sdk/ros2/core-services/access) guides to revoke publishing rights instantly. * Leverage `/tmp/storage_bridge_failures.jsonl` and the replay scripts to prove that no data was dropped—even during outages. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} python3 scripts/check_storage_failures.py --details python3 scripts/retry_failed_storage.py --key robot:telemetry ``` ## Observability & Audit * Switch to JSON logs (`PEAQ_ROBOT_LOG_FORMAT=json`) for ingestion into SIEM or compliance tooling. * Track wallet-derived DID and CID pairs in your log pipeline to detect impersonation attempts. * Use `ros2 lifecycle get /peaq_storage_bridge` in your health probes; the node exports ready/active states so Kubernetes or fleet managers can react quickly. ### Dashboard Pointers * `peaq/storage/status`: success vs failure counts * `/tmp/storage_bridge_failures.jsonl`: monitor size/age to detect backlogs * `peaq/tx_status`: confirmation latency per network With verifiable telemetry in place, your ROS 2 fleet can supply zero-trust data to marketplaces, regulators, or partners. Continue with [Event Streams](/peaqchain/sdk-reference/robotics-sdk/ros2/messaging/event-streams) to surface confirmations to autonomy stacks. # Service Index Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/tether/overview All address-only Tether WDK services exposed by `peaq_tether_node` with request/response context. `peaq_ros2_tether` adds an optional ROS 2 node (`peaq_tether_node`) that integrates **Tether WDK** (EVM wallet module) so robots can: * Create an **EVM wallet address** * Query **USDT (ERC-20)** balance on peaq EVM * Transfer **USDT** on peaq EVM This integration is **address-only**: * ROS APIs use **only EVM addresses** (`address`, `from_address`, `to_address`) * Wallet secrets (mnemonic) are stored locally on the robot/machine in **one shared registry file** * No private keys/mnemonics are ever sent over ROS services ## Service Catalog | Service | Type | Purpose | | --------------------------------- | ----------------------------------------------- | -------------------------------------------------------------- | | `/peaq_tether_node/wallet/create` | `peaq_ros2_interfaces/srv/TetherCreateWallet` | Create a new EVM wallet address (mnemonic stored locally) | | `/peaq_tether_node/usdt/balance` | `peaq_ros2_interfaces/srv/TetherGetUsdtBalance` | Read USDT balance for an address | | `/peaq_tether_node/usdt/transfer` | `peaq_ros2_interfaces/srv/TetherTransferUsdt` | Transfer USDT from one local wallet address to another address | Use this index as a quick reference, then dive into the focused guides: * [Wallet](/peaqchain/sdk-reference/robotics-sdk/ros2/tether/wallet) * [USDT Balance](/peaqchain/sdk-reference/robotics-sdk/ros2/tether/usdt-balance) * [USDT Transfer](/peaqchain/sdk-reference/robotics-sdk/ros2/tether/usdt-transfer) * [Wallet Registry](/peaqchain/sdk-reference/robotics-sdk/ros2/tether/wallet-registry) ## Launch Prerequisites Install JS dependencies once (Node.js required where the node runs): ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} cd peaq_ros2_tether/js npm ci ``` Start the node via `ros2 run`. Notes: * If you run multiple ROS 2 environments on the same host, isolate them with `ROS_DOMAIN_ID` to prevent service collisions. * When running from a **source checkout**, the node prefers a CLI path where Node can resolve `node_modules/`. The simplest approach is to run from the workspace root. You can also override the CLI path explicitly with `PEAQ_TETHER_CLI_PATH`. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-94}" # Point this to your clone path export REPO_DIR="" cd "$REPO_DIR" export PEAQ_CFG="$REPO_DIR/peaq_ros2_examples/config/peaq_robot.yaml" # IMPORTANT: set this explicitly. If REPO_DIR is empty, the default becomes "/peaq_ros2_tether/..." export PEAQ_TETHER_CLI_PATH="$REPO_DIR/peaq_ros2_tether/js/peaq_tether_cli.mjs" # Option A (recommended): tmux detach (keeps the node running after SSH disconnects) tmux new -s tether # inside tmux: ros2 run peaq_ros2_tether tether_node --ros-args -p config.yaml_path:="$PEAQ_CFG" # detach without stopping: Ctrl-b then d # # After detaching, the node keeps running and ROS services continue to work from any terminal # (as long as you source your ROS environment and use the same ROS_DOMAIN_ID). # Option B: background + disown # ros2 run peaq_ros2_tether tether_node --ros-args -p config.yaml_path:="$PEAQ_CFG" \ # > /tmp/tether_node.log 2>&1 & disown ``` Confirm the node is running and services are registered: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-94}" # In a new terminal you must source ROS so `ros2` is available: # source /opt/ros//setup.bash # source /install/setup.bash ros2 node list | grep peaq_tether_node ros2 service list | grep peaq_tether_node ``` Stop the node: * **tmux option**: * Re-attach: `tmux attach -t tether` * Stop inside tmux: `Ctrl-c` * (Or kill the session): `tmux kill-session -t tether` * **background/disown option**: * Try: * `pkill -f "ros2 run peaq_ros2_tether tether_node" || true` * `pkill -f "peaq_ros2_tether/tether_node" || true` * If the node was started as another user (or with `sudo`), you may need `sudo pkill ...`. * Verify it’s gone: * `pgrep -af "peaq_ros2_tether.*tether_node" || echo "tether node stopped"` If `ros2 node list` still shows stale entries right after stopping, restart the ROS CLI daemon and try again: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 daemon stop || true ros2 daemon start || true ``` # USDT Balance Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/tether/usdt-balance Read USDT (ERC-20) balances on peaq EVM using address-only ROS 2 services. Use this service to query USDT balances for any EVM address on peaq EVM. The call is read-only and does not require the address to exist in the local registry. ## Service Reference | Service | Type | Purpose | | -------------------------------- | ----------------------------------------------- | ---------------------------------- | | `/peaq_tether_node/usdt/balance` | `peaq_ros2_interfaces/srv/TetherGetUsdtBalance` | Return USDT balance for an address | ## Launch Prerequisites Start `peaq_tether_node` once, then call services as needed. * To start/stop the node (tmux + background options), follow the steps in [Service Index](/peaqchain/sdk-reference/robotics-sdk/ros2/tether/overview). * In any new terminal where you run `ros2` commands, make sure you’ve sourced your ROS/workspace environment and set the same `ROS_DOMAIN_ID` as the node: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # source /opt/ros//setup.bash # source /install/setup.bash export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-94}" ``` ## Check Balance ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-94}" ros2 service call /peaq_tether_node/usdt/balance \ peaq_ros2_interfaces/srv/TetherGetUsdtBalance \ "{address: '0x...'}" ``` Response fields: * `balance_raw`: base-10 string (uint256) * `balance_formatted`: formatted using configured decimals (default 6 for peaq USDT) ## Tips * If you hit provider rate limits, retry after a short delay or use a higher-throughput RPC endpoint. * Keep the USDT contract address in config in quotes (YAML) to avoid type parsing issues. # USDT Transfer Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/tether/usdt-transfer Dry-run and broadcast USDT transfers on peaq EVM using address-only ROS 2 services. Use this service to transfer USDT from a locally-managed wallet address to any recipient address. The service is **address-only**: * `from_address` identifies which local wallet to use for signing (mnemonic loaded from the local registry) * `to_address` is the destination address ## Service Reference | Service | Type | Purpose | | --------------------------------- | --------------------------------------------- | ---------------------------------------------------------------- | | `/peaq_tether_node/usdt/transfer` | `peaq_ros2_interfaces/srv/TetherTransferUsdt` | Transfer USDT from a local wallet address to a recipient address | ## Launch Prerequisites Start `peaq_tether_node` once, then call services as needed. * To start/stop the node (tmux + background options), follow the steps in [Service Index](/peaqchain/sdk-reference/robotics-sdk/ros2/tether/overview). * In any new terminal where you run `ros2` commands, make sure you’ve sourced your ROS/workspace environment and set the same `ROS_DOMAIN_ID` as the node: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # source /opt/ros//setup.bash # source /install/setup.bash export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-94}" ``` ## Dry-run (Quote Only) Use `dry_run: true` to estimate and validate the transfer without broadcasting: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-94}" ros2 service call /peaq_tether_node/usdt/transfer \ peaq_ros2_interfaces/srv/TetherTransferUsdt \ "{from_address: '0xFROM', to_address: '0xTO', amount: '0.05', dry_run: true}" ``` ## Real Transfer (Broadcast) ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-94}" ros2 service call /peaq_tether_node/usdt/transfer \ peaq_ros2_interfaces/srv/TetherTransferUsdt \ "{from_address: '0xFROM', to_address: '0xTO', amount: '0.05', dry_run: false}" ``` ## Common Failure Modes * `INSUFFICIENT_FUNDS`: the `from_address` lacks native token for gas (even if USDT amount is 0). * `Unknown address`: the `from_address` is not present in the local registry (no mnemonic available for signing). * Provider rate limits: retry after a short delay, or use a higher-throughput RPC provider. # Wallet Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/tether/wallet Create EVM wallet addresses from ROS 2 without ever passing secrets over services. Use the wallet service to create a new EVM address that the robot can use for USDT operations on peaq EVM. The mnemonic is stored locally in the shared registry file and is never sent over ROS by default. ## Service Reference | Service | Type | Purpose | | --------------------------------- | --------------------------------------------- | --------------------------------------------------------- | | `/peaq_tether_node/wallet/create` | `peaq_ros2_interfaces/srv/TetherCreateWallet` | Create a new EVM wallet address (mnemonic stored locally) | ## Launch Prerequisites Start `peaq_tether_node` once, then call services as needed. * To start/stop the node (tmux + background options), follow the steps in [Service Index](/peaqchain/sdk-reference/robotics-sdk/ros2/tether/overview). * In any new terminal where you run `ros2` commands, make sure you’ve sourced your ROS/workspace environment and set the same `ROS_DOMAIN_ID` as the node: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # source /opt/ros//setup.bash # source /install/setup.bash export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-94}" ``` ## Create a Wallet Address ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-94}" ros2 service call /peaq_tether_node/wallet/create \ peaq_ros2_interfaces/srv/TetherCreateWallet \ "{label: 'robot_001', export_mnemonic: false}" ``` Response fields: * `address`: checksummed EVM address (0x...) * `mnemonic`: empty unless **both** of the following are true: * You call the service with `export_mnemonic: true` * You explicitly allow unsafe export in config (`tether.wallet_registry.unsafe_export_mnemonic: true`) ## Unsafe Mnemonic Export (Development Only) Mnemonic export is disabled by default. To enable it temporarily for local testing: 1. Set the config flag in `peaq_robot.yaml`: ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} tether: wallet_registry: unsafe_export_mnemonic: true ``` 2. Request export in the service call: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-94}" # Restart the node after enabling unsafe export in config (stop + start using your chosen method). # Example stop command (background mode): # pkill -f "peaq_ros2_tether.*tether_node" 2>/dev/null || true ros2 service call /peaq_tether_node/wallet/create \ peaq_ros2_interfaces/srv/TetherCreateWallet \ "{label: 'robot_001', export_mnemonic: true}" ``` > Warning: Never enable mnemonic export in production. Treat exported mnemonics like root credentials. ## Operational Tips * Treat the returned `address` as the only identifier used by the tether node APIs. * Do not enable mnemonic export in production. * Back up the registry file securely if the address controls real funds. # Wallet Registry Source: https://docs.peaq.xyz/peaqchain/sdk-reference/robotics-sdk/ros2/tether/wallet-registry Understand how `peaq_tether_node` stores wallet secrets locally and why ROS remains address-only. `peaq_tether_node` stores wallet secrets locally in a single shared registry file on the robot/machine. This design keeps ROS APIs **address-only** while allowing the node to sign EVM transactions when you call transfer services. ## Why a Registry Exists * ROS services must never receive mnemonics or private keys. * Transfers must still be signed somewhere; the node signs locally by loading the mnemonic from disk. * A single file registry fits the “single robot / single host” assumption and simplifies operations. ## Configuration In `peaq_robot.yaml`: ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} tether: wallet_registry: path: "~/.peaq_robot/tether_wallets.json" unsafe_export_mnemonic: false ``` ### Enabling mnemonic export (unsafe) Mnemonic export is **disabled by default**. If you explicitly enable: ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} tether: wallet_registry: unsafe_export_mnemonic: true ``` then `wallet/create` will return a mnemonic **only when** the request sets `export_mnemonic: true`. ## Security Invariants * The EVM `address` is the only identifier used by services. * Mnemonics are stored locally and are never emitted over ROS by default. * Keep the registry file restricted (e.g., mode `0600`) and treat it like a keystore. ## Operational Tips * Back up the registry if addresses control real funds (secure storage only). * If you rotate machines, migrate the registry file with care. * If multiple ROS 2 environments run on one host, isolate with `ROS_DOMAIN_ID` to prevent service collisions. * To keep `peaq_tether_node` running without tying up your terminal, start it in `tmux` (detach with `Ctrl-b` then `d`) or run it in the background and `disown` it. # Cancel Contract Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/cnft/cancelContract ## `cnft.cancelContract(CancelContract)` Cancel a Contract NFT draft. This sends a transaction from the contract controller and removes the draft from storage. ### CancelContract Type Parameters | Parameter | Type | Required | Description | | ---------------------- | -------- | -------- | ---------------------------------------------------------------------------------------------- | | **contractController** | `Signer` | Required | Contract controller signer authorized to cancel the contract. Must be connected to a provider. | | **contractNft** | `string` | Required | Contract NFT contract address. | | **contractId** | `string` | Required | Contract ID to cancel. | ### Returns | Field | Type | Description | | --------------- | -------------------- | ---------------------------------------------------- | | **status** | `cancelled` | Status of the operation. | | **contractNft** | `string` | Contract NFT contract address. | | **contractId** | `string` | Contract ID that was cancelled (from emitted event). | | **cancelledBy** | `string` | Controller address that submitted the transaction. | | **receipt** | `TransactionReceipt` | Transaction receipt for the cancellation call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet, keccak256, toUtf8Bytes } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Contract controller const alice = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 2. Get contract draft const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const contractId - "1234567890" // 3. Cancel the contract const result = await rwa_sdk.cnft.cancelContract({ contractController: alice, contractNft: contractNft, contractId: contractId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet, keccak256, toUtf8Bytes } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Contract controller and counterparties const alice = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 2. Get contract draft const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const contractId = "1234567890" // 3. Cancel the contract const result = await rwa_sdk.cnft.cancelContract({ contractController: alice, contractNft: contractNft, contractId: contractId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'cancelled', contractNft: '0x35D67095A5a6f00CBE288cF744b3efC48de3699a', contractId: '1234567890', cancelledBy: '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', receipt: ContractTransactionReceipt { ... } } ``` Note: Once cancelled, `getDraft` for the same `contractId` will revert with `Not found`. # Create Contract Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/cnft/createContract ## `cnft.createContract(CreateContract)` Create a Contract NFT draft by initializing the contract with counterparties and content, and paying the setup fee (ERC20). This sends a transaction from the contract controller. ### CreateContract Type Parameters | Parameter | Type | Required | Description | | ---------------------- | ---------- | -------- | ----------------------------------------------------------------------------------------------- | | **contractController** | `Signer` | Required | Signer that controls the contract and submits the transaction. Must be connected to a provider. | | **erc20** | `string` | Required | ERC20 token address used to pay the setup fee. | | **tokenDecimals** | `number` | Required | ERC20 token decimals for human-readable fee amounts. | | **counterparties** | `string[]` | Required | Array of counterparty EOA addresses that must sign the contract. | | **contractNft** | `string` | Required | Contract NFT contract address. | | **contractHash** | `string` | Required | Content hash (e.g., `keccak256` of the contract content). | | **url** | `string` | Required | URL pointing to the contract content/metadata. | ### Returns | Field | Type | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | **status** | `created` | Status of the operation. | | **contractNft** | `string` | Contract NFT contract address. | | **contractId** | `string` | Contract ID for the draft. | | **contractController** | `string` | Controller EOA address. | | **counterparties** | `string[]` | Counterparty addresses included in the draft. | | **content** | `{ hash: string; url: string }` | Content hash and URL. | | **fee** | `{ token: string; tokenDecimals: number; setupAmount: bigint; balanceBefore: bigint; balanceAfter: bigint; humanTokenDelta: string }` | Fee and ERC20 balance details for the setup. | | **receipt** | `TransactionReceipt` | Transaction receipt for the creation call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet, keccak256, toUtf8Bytes } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Contract controller (submits tx and pays ERC20 fee) const alice = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 2. Counterparties const bob = process.env.BOB_PUBLIC_ADDRESS!; const charlie = process.env.CHARLIE_PUBLIC_ADDRESS!; // 3. Prepare content const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const url = "https://example.com"; const content = `This is a test contract ${Date.now()}`; const contractHash = keccak256(toUtf8Bytes(content)); // 4. Create contract draft const result = await rwa_sdk.cnft.createContract({ contractController: alice, erc20: rwa_sdk.getAddresses().erc20.peaq, tokenDecimals: 18, counterparties: [bob, charlie], contractNft: contractNft, contractHash: contractHash, url: url }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet, keccak256, toUtf8Bytes } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Contract controller (submits tx and pays ERC20 fee) const alice = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 2. Counterparties const bob = process.env.BOB_PUBLIC_ADDRESS; const charlie = process.env.CHARLIE_PUBLIC_ADDRESS; // 3. Prepare content const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const url = "https://example.com"; const content = `This is a test contract ${Date.now()}`; const contractHash = keccak256(toUtf8Bytes(content)); // 4. Create contract draft const result = await rwa_sdk.cnft.createContract({ contractController: alice, erc20: rwa_sdk.getAddresses().erc20.peaq, tokenDecimals: 18, counterparties: [bob, charlie], contractNft: contractNft, contractHash: contractHash, url: url }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'created', contractNft: '0x35D67095A5a6f00CBE288cF744b3efC48de3699a', contractId: '1234567890', contractController: '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', counterparties: [ '0xbA9274C766A5961C40bB4a3e0e107699EE9Dab9C', '0x68af027F5AaE3b1B6ff770b87aB7ac360b54ad40' ], content: { hash: '0x...', url: 'https://example.com' }, fee: { token: '0x...', tokenDecimals: 18, setupAmount: 1000000000000000000n, balanceBefore: 1000000000000000000n, balanceAfter: 0n, humanTokenDelta: '1.0' }, receipt: ContractTransactionReceipt { ... } } ``` Note: the contract ID is deterministic based on initiator, counterparties, content hash, and URL. # Get Contract Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/cnft/getContract ## `cnft.getContract(GetContract)` Fetch a Contract NFT by contract ID. This is a read-only call. ### GetContract Type Parameters | Parameter | Type | Required | Description | | --------------- | -------- | -------- | ------------------------------ | | **contractNft** | `string` | Required | Contract NFT contract address. | | **contractId** | `string` | Required | Contract ID to fetch. | ### Returns | Field | Type | Description | | ------------ | ---------- | ---------------------------------------- | | **contract** | `Contract` | Contract tuple returned by the contract. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet, keccak256, toUtf8Bytes } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Get contract draft const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const contractId = "1234567890" // 2. Fetch contract (after all counterparties sign) const result = await rwa_sdk.cnft.getContract({ contractNft: contractNft, contractId: contractId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet, keccak256, toUtf8Bytes } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Get contract draft const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const contractId = "1234567890" // 2. Fetch contract (after all counterparties sign) const result = await rwa_sdk.cnft.getContract({ contractNft: contractNft, contractId: contractId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { contract: [ '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', [ '0xbA9274C766A5961C40bB4a3e0e107699EE9Dab9C', '0x68af027F5AaE3b1B6ff770b87aB7ac360b54ad40' ], 1234567890n, 'https://example.com' ] } ``` # Get Draft Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/cnft/getDraft ## `cnft.getDraft(GetDraft)` Fetch a Contract NFT draft by contract ID. This is a read-only call. ### GetDraft Type Parameters | Parameter | Type | Required | Description | | --------------- | -------- | -------- | ----------------------------------- | | **contractNft** | `string` | Required | Contract NFT contract address. | | **contractId** | `string` | Required | Contract ID to fetch the draft for. | ### Returns | Field | Type | Description | | --------- | --------------- | ------------------------------------- | | **draft** | `ContractDraft` | Draft tuple returned by the contract. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet, keccak256, toUtf8Bytes } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Get contract draft const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const contractId = "1234567890" // 2. Fetch draft const result = await rwa_sdk.cnft.getDraft({ contractNft: contractNft, contractId: contractId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet, keccak256, toUtf8Bytes } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Get contract draft const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const contractId = "1234567890" // 2. Fetch draft const result = await rwa_sdk.cnft.getDraft({ contractNft: contractNft, contractId: contractId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { draft: [ [ '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', [ '0xbA9274C766A5961C40bB4a3e0e107699EE9Dab9C', '0x68af027F5AaE3b1B6ff770b87aB7ac360b54ad40' ], 1234567890n, 'https://example.com' ], [ '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2' ] ] } ``` # Is Blocked Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/cnft/isBlocked ## `cnft.isBlocked(IsBlocked)` Check whether a Contract NFT contract is blocked. This is a read-only call. ### IsBlocked Type Parameters | Parameter | Type | Required | Description | | --------------- | -------- | -------- | ------------------------------ | | **contractNft** | `string` | Required | Contract NFT contract address. | ### Returns | Field | Type | Description | | ----------- | --------- | ------------------------------------- | | **blocked** | `boolean` | `true` if blocked, `false` otherwise. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Check blocked state const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const result = await rwa_sdk.cnft.isBlocked({ contractNft }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Check blocked state const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const result = await rwa_sdk.cnft.isBlocked({ contractNft }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { blocked: true } ``` # Is Contract ID Available Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/cnft/isContractIdAvailable ## `cnft.isContractIdAvailable(IsContractIdAvailable)` Check whether a Contract ID is available for a Contract NFT. This is a read-only call. ### IsContractIdAvailable Type Parameters | Parameter | Type | Required | Description | | --------------- | -------- | -------- | ------------------------------ | | **contractNft** | `string` | Required | Contract NFT contract address. | | **contractId** | `string` | Required | Contract ID to check. | ### Returns | Field | Type | Description | | ------------- | --------- | --------------------------------------------- | | **available** | `boolean` | `true` if available, `false` if already used. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Check availability const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const result = await rwa_sdk.cnft.isContractIdAvailable({ contractNft, contractId: "1" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Check availability const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const result = await rwa_sdk.cnft.isContractIdAvailable({ contractNft, contractId: "1" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { available: true } ``` # Set Blocked Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/cnft/setBlocked ## `cnft.setBlocked(SetBlocked)` Set a Contract NFT contract to blocked or unblocked. This sends a transaction from the Contract NFT owner. ### SetBlocked Type Parameters | Parameter | Type | Required | Description | | --------------------- | --------- | -------- | ------------------------------------------------------------------------------------------- | | **contractNftSigner** | `Signer` | Required | Contract NFT owner signer authorized to set blocked state. Must be connected to a provider. | | **contractNft** | `string` | Required | Contract NFT contract address. | | **blocked** | `boolean` | Required | `true` to block, `false` to unblock. | ### Returns | Field | Type | Description | | --------------- | -------------------- | -------------------------------------------- | | **status** | `set` | Status of the operation. | | **contractNft** | `string` | Contract NFT contract address. | | **blocked** | `boolean` | The resulting block state. | | **setBy** | `string` | Address that submitted the transaction. | | **receipt** | `TransactionReceipt` | Transaction receipt of the update operation. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Contract NFT owner signer const admin = new Wallet(process.env.ADMIN_PRIVATE_KEY!, provider); // 2. Contract NFT address const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; // 3. Block the contract const result = await rwa_sdk.cnft.setBlocked({ contractNftSigner: admin, contractNft: contractNft, blocked: true }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Contract NFT owner signer const admin = new Wallet(process.env.ADMIN_PRIVATE_KEY, provider); // 2. Contract NFT address const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; // 3. Block the contract const result = await rwa_sdk.cnft.setBlocked({ contractNftSigner: admin, contractNft: contractNft, blocked: true }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'set', contractNft: '0x35D67095A5a6f00CBE288cF744b3efC48de3699a', blocked: true, setBy: '0x8BCfa2e9FC4aCa66fCF36Bcf47646E5Fb8d74BA0', receipt: ContractTransactionReceipt { ... } } ``` # Sign Contract Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/cnft/signContract ## `cnft.signContract(SignContract)` Sign a Contract NFT as a counterparty. This sends a transaction from the counterparty signer. If the final signature is collected, the contract becomes completed. ### SignContract Type Parameters | Parameter | Type | Required | Description | | ---------------------- | -------- | -------- | ------------------------------------------------------------------------------------- | | **counterpartySigner** | `Signer` | Required | Counterparty signer authorized to sign the contract. Must be connected to a provider. | | **contractNft** | `string` | Required | Contract NFT contract address. | | **contractId** | `string` | Required | Contract ID to sign. | ### Returns | Field | Type | Description | | ---------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------- | | **status** | `completed` or `signed` or `mined_unknown` | Status of the signing operation. | | **contractId** | `string` | Contract ID that was signed (from emitted event when available). | | **counterpartySigner** | `string` | Counterparty address that signed (from emitted event when available). | | **receipt** | `TransactionReceipt` | Transaction receipt for the signing call. | | **progress** | `{ collected: number; total: number }` | Optional signature progress when `status` is `signed`. Total includes the initiator (+1). | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet, keccak256, toUtf8Bytes } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Contract controller and counterparties const alice = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); const bob = new Wallet(process.env.BOB_PRIVATE_KEY!, provider); const charlie = new Wallet(process.env.CHARLIE_PRIVATE_KEY!, provider); // 2. Get information from contract creation const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const contractId = '1234567890' // 3. Counterparty signs const result = await rwa_sdk.cnft.signContract({ counterpartySigner: bob, contractNft: contractNft, contractId: contractId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet, keccak256, toUtf8Bytes } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Contract counterparty const bob = new Wallet(process.env.BOB_PRIVATE_KEY, provider); // 2. Get information from contract creation const contractNft = "0x35D67095A5a6f00CBE288cF744b3efC48de3699a"; const contractId = '1234567890' // 3. Counterparty signs const result = await rwa_sdk.cnft.signContract({ counterpartySigner: bob, contractNft: contractNft, contractId: contractId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs Signed (more signatures required): ``` Result { status: 'signed', contractId: '1234567890', counterpartySigner: '0x35D67095A5a6f00CBE288cF744b3efC48de3699a', receipt: ContractTransactionReceipt { ... }, progress: { collected: 2, total: 3 } } ``` Completed (final signature collected): ``` Result { status: 'completed', contractId: '1234567890', counterpartySigner: '0x68af027F5AaE3b1B6ff770b87aB7ac360b54ad40', receipt: ContractTransactionReceipt { ... } } ``` Note: If emitted events are not detected, the SDK returns `status: 'mined_unknown'` with `contractId` and `counterpartySigner` set to `'unknown'`. # Add Claim to Identity Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/identity/addClaimToIdentity ## `onchainid.addClaimToIdentity(AddClaimToIdentity)` Add a signed claim to an ONCHAINID identity (calls the identity contract's `addClaim`). ### AddClaimToIdentity Type Parameters | Parameter | Type | Required | Description | | ---------------------- | -------- | -------- | ------------------------------------------------------------------------ | | **subjectIdentity** | `string` | Required | ONCHAINID identity contract address that will receive the claim. | | **identityController** | `Signer` | Required | Signer/wallet that controls the identity, connected to a provider. | | **claim** | `IClaim` | Required | Encoded claim payload: `{ identity, issuer, topic, scheme, data, uri }`. | | **claimSignature** | `string` | Required | `0x`-prefixed hex signature over the claim by the claim issuer. | ### Returns | Field | Type | Description | | ----------- | -------------------- | -------------------------------------------------------------------------------- | | **status** | `added` or `updated` | `'added'` when the claim is new, `'updated'` when it replaces an existing claim. | | **claimId** | `string` | Claim ID emitted by the identity contract. | | **receipt** | `TransactionReceipt` | Transaction receipt of the `addClaim` call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit, type GetIdentity, type IssueKycClaim, type AddClaimToIdentity } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from 'ethers'; async function main() { // 0. Create RWA instance and provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Claim Issuer admin wallet const claimIssuer = new Wallet(process.env.CLAIM_ISSUER_PRIVATE_KEY!, provider); // 2. Get User to KYC const getIdentity: GetIdentity = { subject: process.env.ALICE_PUBLIC_ADDRESS! }; const alice = await rwa_sdk.onchainid.getIdentity(getIdentity); // 3. Create claim + signature const issueKycClaim: IssueKycClaim = { claimIssuerSigner: claimIssuer, claimIssuerContract: process.env.CLAIM_ISSUER_CONTRACT_ADDRESS!, subjectIdentity: alice.identity, name: 'Alice', lastName: 'Doe', dateOfBirth: '1990-01-01', placeOfBirth: 'New York', uri: 'https://example.com/kyc' } const { claim, signature } = await rwa_sdk.onchainid.issueKycClaim(issueKycClaim); // 4. Identity owner signs and submits addClaim const aliceSigner = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); const addClaimToIdentity: AddClaimToIdentity = { subjectIdentity: alice.identity, identityController: aliceSigner, claim: claim, claimSignature: signature, } const { receipt, status, claimId } = await rwa_sdk.onchainid.addClaimToIdentity(addClaimToIdentity); console.log('Add claim result:', { status, claimId, receipt }); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from 'ethers'; async function main() { // 0. Create RWA instance and provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Claim Issuer admin wallet const claimIssuer = new Wallet(process.env.CLAIM_ISSUER_PRIVATE_KEY, provider); // 2. Get User to KYC const alice = await rwa_sdk.onchainid.getIdentity({ subject: process.env.ALICE_PUBLIC_ADDRESS }); // 3. Create claim + signature const { claim, signature } = await rwa_sdk.onchainid.issueKycClaim({ claimIssuerSigner: claimIssuer, claimIssuerContract: process.env.CLAIM_ISSUER_CONTRACT_ADDRESS, subjectIdentity: alice.identity, name: 'Alice', lastName: 'Doe', dateOfBirth: '1990-01-01', placeOfBirth: 'New York', uri: 'https://example.com/kyc' }); // 4. Identity owner signs and submits addClaim const aliceSigner = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); const { receipt, status, claimId } = await rwa_sdk.onchainid.addClaimToIdentity({ subjectIdentity: alice.identity, identityController: aliceSigner, claim: claim, claimSignature: signature, }); console.log('Add claim result:', { status, claimId, receipt }); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` { status: 'added', claimId: '0x29753f23d65eadcfc30f6988fa876cef5069d80f61802576d029c1272a2c9c4e', receipt: TransactionReceipt { ... hash: '0xabccaf471ad0afa2f059747baeb7f79be3d41ecdaae1beed0bd3d903348b302a', status: 1 } } ``` Notes: * Ensure `identityController` controls the ONCHAINID at `subjectIdentity`. * `claimSignature` must match the exact `claim` payload and issuer. # Create Identity Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/identity/createIdentity ## `onchainid.createIdentity(CreateIdentity)` Create (or fetch if already exists) an ONCHAINID identity for a given EOA (Externally Owned Account where the user controls the keys). If an identity is already associated with a `subject`, it returns that identity address with `status: 'exists'`. ### CreateIdentity Type Parameters | Parameter | Type | Required | Description | | ------------------ | -------- | -------- | ------------------------------------------------------------------- | | **idFactoryAdmin** | `Signer` | Required | ID Factory Signer authorized to create identities. | | **subject** | `string` | Required | EOA of the identity the deployed ONCHAINID will be associated with. | | **deploymentSalt** | `string` | Required | Arbitrary string used for deterministic deployment. | ### Returns | Field | Type | Description | | ------------ | --------------------- | ---------------------------------------------------------------------------- | | **status** | `created` or `exists` | `'created'` when a new identity was deployed, `'exists'` if already present. | | **identity** | `string` | ONCHAINID identity contract address that is bound to their EOA. | | **receipt** | `TransactionReceipt` | Transaction receipt when created. Only present when `status` is `'created'`. | ### Usage #### TypeScript ```Typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit, type CreateIdentity } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Get Admin wallet const admin = new Wallet(process.env.ADMIN_PRIVATE_KEY!, provider); // 2. Get Alice public address const alice = process.env.ALICE_PUBLIC_ADDRESS! // 3. Create ONCHAINID Identity params const createIdentity: CreateIdentity = { idFactoryAdmin: admin, subject: alice, deploymentSalt: "identity-" + Date.now().toString() } const result = await rwa_sdk.onchainid.createIdentity(createIdentity); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider: provider }); // 1. Get Admin wallet const admin = new Wallet(process.env.ADMIN_PRIVATE_KEY, provider); // 2. Get Alice public address const alice = process.env.ALICE_PUBLIC_ADDRESS // 3. Create ONCHAINID Identity const result = await rwa_sdk.onchainid.createIdentity({ idFactoryAdmin: admin, subject: alice, deploymentSalt: "identity-" + Date.now().toString() }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs Created: ``` Result { status: 'created', identity: '0x1e747251c5F1A4cDC4CD667536db2949A93aB110', receipt: ContractTransactionReceipt { ... } } ``` Already exists: ``` Result { status: 'exists', identity: '0x1e747251c5F1A4cDC4CD667536db2949A93aB110' } ``` # Get Claim Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/identity/getClaim ## `onchainid.getClaim(GetClaim)` Fetch a claim from an ONCHAINID identity by `claimId`. This is a read-only call to the identity contract. ### GetClaim Type Parameters | Parameter | Type | Required | Description | | ------------------- | -------- | -------- | ------------------------------------------------------------------------- | | **subjectIdentity** | `string` | Required | The ONCHAINID identity contract address to read from. | | **claimId** | `string` | Required | The claim identifier, computed as `keccak256(abi.encode(issuer, topic))`. | Note: The `claimId` is derived from the issuer contract address and the claim topic to uniquely identify a claim on an identity. In Solidity this is `keccak256(abi.encode(_issuer, _topic))`. In ethers you can compute it via `keccak256(new AbiCoder().encode(["address","uint256"], [issuer, topic]))`. ### Returns | Field | Type | Description | | --------- | ------- | ------------------------------------------------------- | | **claim** | `Claim` | The full claim payload read from the identity contract. | ### Usage #### TypeScript ```Typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, ClaimTopics, type SDKInit, type GetIdentity, type GetClaim } from '@peaq-network/rwa'; import { JsonRpcProvider, AbiCoder, keccak256 } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Resolve the identity for an EOA (or use a known identity address) const alice = process.env.ALICE_PUBLIC_ADDRESS!; const identityRes = await rwa_sdk.onchainid.getIdentity({ subject: alice } as GetIdentity); if (identityRes.status !== 'found') throw new Error('Identity not found'); const identity = identityRes.identity; // 2. Compute claimId = keccak256(abi.encode(issuer, topic)) const issuerContract = process.env.CLAIM_ISSUER_CONTRACT_ADDRESS!; const topic = ClaimTopics.CT_KYC_APPROVED; const abiCoder = new AbiCoder(); const claimId = keccak256(abiCoder.encode(["address", "uint256"], [issuerContract, topic])); // 3. Fetch claim const result = await rwa_sdk.onchainid.getClaim({ subjectIdentity: identity, claimId } as GetClaim); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, ClaimTopics } from "@peaq-network/rwa"; import { JsonRpcProvider, AbiCoder, keccak256 } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider: provider }); // 1. Resolve the identity for an EOA (or use a known identity address) const alice = process.env.ALICE_PUBLIC_ADDRESS; const identityRes = await rwa_sdk.onchainid.getIdentity({ subject: alice }); if (identityRes.status !== 'found') throw new Error('Identity not found'); const identity = identityRes.identity; // 2. Compute claimId = keccak256(abi.encode(issuer, topic)) const issuerContract = process.env.CLAIM_ISSUER_CONTRACT_ADDRESS; const topic = ClaimTopics.CT_KYC_APPROVED; const abiCoder = new AbiCoder(); const claimId = keccak256(abiCoder.encode(["address", "uint256"], [issuerContract, topic])); // 3. Fetch claim const result = await rwa_sdk.onchainid.getClaim({ subjectIdentity: identity, claimId: claimId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { claim: { topic: ClaimTopics.CT_KYC_APPROVED, scheme: 1, issuer: '0x187EB39e1aF4B9a79936635dBa52984af98464a9', signature: '0x4bab329329ea682e4ac675d9ebab2b8ec597124b98b5085a4e8f83104ce064d4728771567752a75db8ad75552579f0f805913f1f756947f25a8e85d67ce3055e1b', data: '0x252ec8044814d556905cc1587f4a375a2acfe3f84a17d7d104accd32ee25b3b6', uri: 'https://example.com/kyc' } } ``` # Get Identity Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/identity/getIdentity ## `onchainid.getIdentity(GetIdentity)` Fetch the ONCHAINID identity contract address associated with a given EOA (Externally Owned Account). This is a read-only query against the ID Factory; no transaction is sent. ### GetIdentity Type Parameters | Parameter | Type | Required | Description | | ----------- | -------- | -------- | -------------------------------------------------- | | **subject** | `string` | Required | EOA to check for an associated ONCHAINID identity. | ### Returns | Field | Type | Description | | ------------ | ---------------------- | ------------------------------------------------------------------------------- | | **status** | `found` or `not_found` | `'found'` when an identity is associated with the EOA, otherwise `'not_found'`. | | **identity** | `string` | The identity address when found. Only present when `status` is `'found'`. | ### Usage #### TypeScript ```Typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit, type GetIdentity } from '@peaq-network/rwa'; import { JsonRpcProvider } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Get Alice public address const alice = process.env.ALICE_PUBLIC_ADDRESS!; // 2. Query ONCHAINID Identity const params: GetIdentity = { subject: alice }; const result = await rwa_sdk.onchainid.getIdentity(params); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider: provider }); // 1. Get Alice public address const alice = process.env.ALICE_PUBLIC_ADDRESS // 2. Query ONCHAINID Identity const result = await rwa_sdk.onchainid.getIdentity({ subject: alice }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs Found: ``` Result { status: 'found', identity: '0xF16b0871271C2135b4Ffc374676e74a16aaDC2c9' } ``` Not found: ``` Result { status: 'not_found' } ``` # Issue KYC Claim Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/identity/issueKycClaim ## `onchainid.issueKycClaim(IssueKycClaim)` Generate and sign a KYC claim for an ONCHAINID identity. This does not broadcast a transaction; it returns the encoded claim payload and the issuer's signature that can be submitted or verified off-chain/on-chain by downstream contracts. ### IssueKycClaim Type Parameters | Parameter | Type | Required | Description | | ----------------------- | -------- | -------- | ------------------------------------------------------------------------------------ | | **claimIssuerSigner** | `Signer` | Required | Claim Issuer signer authorized to issue KYC claims. Must be connected to a provider. | | **claimIssuerContract** | `string` | Required | EVM address of the ClaimIssuer contract. | | **subjectIdentity** | `string` | Required | ONCHAINID identity contract address of the subject being KYCed. | | **name** | `string` | Required | First name of the identity owner. | | **lastName** | `string` | Required | Last name of the identity owner. | | **dateOfBirth** | `string` | Required | Date of birth in ISO format `YYYY-MM-DD`. | | **placeOfBirth** | `string` | Required | Place of birth. | | **uri** | `string` | Optional | Optional URI pointing to KYC evidence/metadata. | ### Returns | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------ | | **claim** | `IClaim` | Encoded claim payload: `{ identity, issuer, topic, scheme, data, uri }`. | | **signature** | `string` | Signature over the claim by `claimIssuer`. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit, type GetIdentity, type IssueKycClaim } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Get Alice EOA const aliceEoa = process.env.ALICE_PUBLIC_ADDRESS // 2. Get Alice identity const getIdentity: GetIdentity = { subject: aliceEoa! }; const alice = await rwa_sdk.onchainid.getIdentity(getIdentity); console.log("Alice Identity", alice); // 3. Get Claim Issuer Admin const claimIssuer = new Wallet(process.env.CLAIM_ISSUER_PRIVATE_KEY!, provider); // 4. Get Issuer Contract const issuerContract = process.env.CLAIM_ISSUER_CONTRACT_ADDRESS; // 5. Issue signed KYC claim const issueKycClaim: IssueKycClaim = { claimIssuerSigner: claimIssuer, claimIssuerContract: issuerContract!, subjectIdentity: alice.identity, name: "Alice", lastName: "Doe", dateOfBirth: "1990-01-01", placeOfBirth: "New York", uri: "https://example.com/kyc" } const { claim, signature } = await rwa_sdk.onchainid.issueKycClaim(issueKycClaim); console.log("Result", { claim, signature }); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider: provider }); // 1. Get Alice EOA const aliceEoa = process.env.ALICE_PUBLIC_ADDRESS // 2. Get Alice identity const alice = await rwa_sdk.onchainid.getIdentity({ subject: aliceEoa }); console.log("Alice Identity", alice); // 3. Get Claim Issuer Admin const claimIssuer = new Wallet(process.env.CLAIM_ISSUER_PRIVATE_KEY, provider); // 4. Get Issuer Contract const issuerContract = process.env.CLAIM_ISSUER_CONTRACT_ADDRESS; // 5. Issue signed KYC claim const result = await rwa_sdk.onchainid.issueKycClaim({ claimIssuerSigner: claimIssuer, claimIssuerContract: issuerContract, subjectIdentity: alice.identity, name: "Alice", lastName: "Doe", dateOfBirth: "1990-01-01", placeOfBirth: "New York", uri: "https://example.com/kyc" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Alice Identity { status: 'found', identity: '0x1d0FDE95e971c5c78B6f9c745a8e2791Fe0c962C' } Result { claim: { identity: '0x1d0FDE95e971c5c78B6f9c745a8e2791Fe0c962C', issuer: '0x842d57632954943304441258E94f3f089235022c', topic: ClaimTopics.CT_KYC_APPROVED, scheme: 1, data: '0x252ec8044814d556905cc1587f4a375a2acfe3f84a17d7d104accd32ee25b3b6', uri: 'https://example.com/kyc' }, signature: '0xffd77807790d0e764bcdd2bc7661c6a3e1e016758100f4afc5d6a5f3552455791ddcfd2bd5b705c27eaa66c45ca1c0bf7b53f5a3a533b6023b8bf3f2132a363a1b' } ``` Note: `topic` and `scheme` are set according to the RWA KYC specification. Ensure your `claimIssuer` and `issuerContract` are recognized by your registry/verification flow for successful validation. # Issue Role Claim Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/identity/issueRoleClaim ## `onchainid.issueRoleClaim(IssueRoleClaim)` Generate and sign a Role claim (Machine Regulator or Machine Issuer) for an ONCHAINID identity. This does not broadcast a transaction; it returns the encoded claim payload and the issuer's signature that can be submitted or verified off-chain/on-chain by downstream contracts. ### IssueRoleClaim Type Parameters | Parameter | Type | Required | Description | | ----------------------- | -------- | -------- | ------------------------------------------------------------------------------------- | | **claimIssuerSigner** | `Signer` | Required | Claim Issuer signer authorized to issue Role claims. Must be connected to a provider. | | **claimIssuerContract** | `string` | Required | EVM address of the ClaimIssuer contract. | | **subjectIdentity** | `string` | Required | ONCHAINID identity contract address of the subject. | | **roleTopic** | `number` | Required | Role topic identifier. | | **roleDescription** | `string` | Required | Human-readable role description. | ### Returns | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------ | | **claim** | `IClaim` | Encoded claim payload: `{ identity, issuer, topic, scheme, data, uri }`. | | **signature** | `string` | Signature over the claim by `claimIssuerSigner`. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, ClaimTopics, type SDKInit, type GetIdentity, type IssueRoleClaim } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Get Alice EOA const aliceEoa = process.env.ALICE_PUBLIC_ADDRESS // 2. Get Alice identity const getIdentity: GetIdentity = { subject: aliceEoa! }; const alice = await rwa_sdk.onchainid.getIdentity(getIdentity); console.log("Alice Identity", alice); // 3. Get Claim Issuer Admin const claimIssuer = new Wallet(process.env.CLAIM_ISSUER_PRIVATE_KEY!, provider); // 4. Get Issuer Contract const issuerContract = process.env.CLAIM_ISSUER_CONTRACT_ADDRESS; // 5. Issue signed Role claim const issueRoleClaim: IssueRoleClaim = { claimIssuerSigner: claimIssuer, claimIssuerContract: issuerContract!, subjectIdentity: alice.identity, roleTopic: ClaimTopics.CT_MNFT_ISSUER, roleDescription: "Machine Issuer" } const { claim, signature } = await rwa_sdk.onchainid.issueRoleClaim(issueRoleClaim); console.log("Result", { claim, signature }); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, ClaimTopics } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider: provider }); // 1. Get Alice EOA const aliceEoa = process.env.ALICE_PUBLIC_ADDRESS // 2. Get Alice identity const alice = await rwa_sdk.onchainid.getIdentity({ subject: aliceEoa }); console.log("Alice Identity", alice); // 3. Get Claim Issuer Admin const claimIssuer = new Wallet(process.env.CLAIM_ISSUER_PRIVATE_KEY, provider); // 4. Get Issuer Contract const issuerContract = process.env.CLAIM_ISSUER_CONTRACT_ADDRESS; // 5. Issue signed Role claim const result = await rwa_sdk.onchainid.issueRoleClaim({ claimIssuerSigner: claimIssuer, claimIssuerContract: issuerContract, subjectIdentity: alice.identity, roleTopic: ClaimTopics.CT_MNFT_ISSUER, roleDescription: "Machine Issuer" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Alice Identity { status: 'found', identity: '0x1d0FDE95e971c5c78B6f9c745a8e2791Fe0c962C' } Result { claim: { identity: '0x1d0FDE95e971c5c78B6f9c745a8e2791Fe0c962C', issuer: '0x842d57632954943304441258E94f3f089235022c', topic: 7, scheme: 1, data: '0x252ec8044814d556905cc1587f4a375a2acfe3f84a17d7d104accd32ee25b3b6', uri: 'https://issuer-regulator-provider.com/user/verification' }, signature: '0xffd77807790d0e764bcdd2bc7661c6a3e1e016758100f4afc5d6a5f3552455791ddcfd2bd5b705c27eaa66c45ca1c0bf7b53f5a3a533b6023b8bf3f2132a363a1b' } ``` Note: `roleTopic` and `roleDescription` are encoded into the claim data according to the RWA Role specification. Ensure your `claimIssuerSigner` and `claimIssuerContract` are recognized by your registry/verification flow for successful validation. # Remove Claim from Identity Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/identity/removeClaimFromIdentity ## `onchainid.removeClaimFromIdentity(RemoveClaimFromIdentity)` Remove a claim from an ONCHAINID identity by `claimId`. This sends a transaction to the identity contract and requires the identity owner’s signer. ### RemoveClaimFromIdentity Type Parameters | Parameter | Type | Required | Description | | ---------------------- | -------- | -------- | ------------------------------------------------------------------------- | | **subjectIdentity** | `string` | Required | The ONCHAINID identity contract address to modify. | | **identityController** | `Signer` | Required | The signer authorized to remove claims from the identity (controller). | | **claimId** | `string` | Required | The claim identifier, computed as `keccak256(abi.encode(issuer, topic))`. | Note: The `claimId` uniquely identifies a claim for an identity. It is derived from the issuer contract address and the claim topic: in Solidity `keccak256(abi.encode(_issuer, _topic))`. In ethers this can be reproduced with `keccak256(new AbiCoder().encode(["address","uint256"], [issuer, topic]))`. ### Returns | Field | Type | Description | | ----------- | -------------------- | ------------------------------------------------- | | **status** | `removed` | Status of the removal. | | **claimId** | `string` | Claim ID removed from the identity. | | **receipt** | `TransactionReceipt` | The transaction receipt confirming claim removal. | ### Usage #### TypeScript ```Typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, ClaimTopics, type SDKInit, type GetIdentity, type RemoveClaimFromIdentity } from '@peaq-network/rwa'; import { JsonRpcProvider, AbiCoder, keccak256, Wallet } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Resolve the identity for an EOA (or use a known identity address) const alice = process.env.ALICE_PUBLIC_ADDRESS!; const identityRes = await rwa_sdk.onchainid.getIdentity({ subject: alice } as GetIdentity); if (identityRes.status !== 'found') throw new Error('Identity not found'); const identity = identityRes.identity; // 2. Identity owner signer const identityOwner = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 3. Compute claimId = keccak256(abi.encode(issuer, topic)) const issuerContract = process.env.CLAIM_ISSUER_CONTRACT_ADDRESS!; const topic = ClaimTopics.CT_KYC_APPROVED; const abiCoder = new AbiCoder(); const claimId = keccak256(abiCoder.encode(["address", "uint256"], [issuerContract, topic])); // 4. Remove claim from identity const result = await rwa_sdk.onchainid.removeClaimFromIdentity({ subjectIdentity: identity, identityController: identityOwner, claimId } as RemoveClaimFromIdentity); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, ClaimTopics } from "@peaq-network/rwa"; import { JsonRpcProvider, AbiCoder, keccak256, Wallet } from "ethers"; async function main() { // 0. Create rwa_sdk instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider: provider }); // 1. Resolve the identity for an EOA (or use a known identity address) const alice = process.env.ALICE_PUBLIC_ADDRESS; const identityRes = await rwa_sdk.onchainid.getIdentity({ subject: alice }); if (identityRes.status !== 'found') throw new Error('Identity not found'); const identity = identityRes.identity; // 2. Identity owner signer const identityOwner = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 3. Compute claimId = keccak256(abi.encode(issuer, topic)) const issuerContract = process.env.CLAIM_ISSUER_CONTRACT_ADDRESS; const topic = ClaimTopics.CT_KYC_APPROVED; const abiCoder = new AbiCoder(); const claimId = keccak256(abiCoder.encode(["address", "uint256"], [issuerContract, topic])); // 4. Remove claim from identity const result = await rwa_sdk.onchainid.removeClaimFromIdentity({ subjectIdentity: identity, identityController: identityOwner, claimId: claimId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` { status: 'removed', claimId: '0x8b8a9d2d3a4d1b0f9d1e3f8c1a6d58e4c1b6d95a2a4a9f2e6d0d7d2b8b1c2d3a', receipt: TransactionReceipt { ... hash: '0xbfb964e21d0a8f227e62752a1a5b7cca95aff0cd992430a6213d06e6ea548b9c', status: 1 } } ``` # Installation & Initialization Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/initialize Install the RWA SDK from npm and set up your project. ## Set up TS ENV ### 1. New Project ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm init -y npm pkg set type=module ``` ### 2. Install packages ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install @peaq-network/rwa npm i -D typescript tsx @types/node npm i dotenv ``` ### 3. Add a TypeScript config ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} touch tsconfig.json ``` Add the following into the file: ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "skipLibCheck": true, "resolveJsonModule": true, "verbatimModuleSyntax": true, "sourceMap": true, "noEmit": true }, "include": ["src/**/*.ts"], "exclude": ["node_modules", "dist"] } ``` ### 4. Update scripts in `package.json`. The `src/index.ts` is the executable file. ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} "scripts": { "dev": "tsx watch src/index.ts", "start": "tsx src/index.ts", "typecheck": "tsc -p tsconfig.json --noEmit" }, ``` ### 5. Create source file ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} mkdir src touch src/index.ts ``` ## Set up JS ENV ### 1. Initialize node env ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm init -y npm pkg set type=module ``` ### 2. Install packages ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install @peaq-network/rwa npm i dotenv ``` ### 3. Create source file ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} mkdir src touch src/index.js ``` ## Execute For both TS/JS environments your next steps are ### Create .env file Next we will create the `.env` file where we store `VARIABLES_LIKE_THIS`. Make sure you add the corresponding variables named in the sdk code into this same file. We also add a gitignore to make sure you don't accidently post secrets. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} touch .env touch .gitignore ``` Add to `.env`: If you would like to be able to follow the full example walkthrough, please define your `.env` as: ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} HTTPS_BASE_URL="" // PEAQ OWNER Admin ADMIN_PUBLIC_ADDRESS="" ADMIN_PRIVATE_KEY="" // Claim Issuer CLAIM_ISSUER_PUBLIC_ADDRESS="" CLAIM_ISSUER_PRIVATE_KEY="" CLAIM_ISSUER_CONTRACT_ADDRESS="" CLAIM_ISSUER_IDENTITY_ADDRESS="" // Machine Regulator MACHINE_REGULATOR_PUBLIC_ADDRESS="" MACHINE_REGULATOR_PRIVATE_KEY="" // Machine Issuer MACHINE_ISSUER_PUBLIC_ADDRESS="" MACHINE_ISSUER_PRIVATE_KEY="" // Alice ALICE_PUBLIC_ADDRESS="" ALICE_PRIVATE_KEY="" // Bob BOB_PUBLIC_ADDRESS="" BOB_PRIVATE_KEY="" // Charlie CHARLIE_PUBLIC_ADDRESS="" CHARLIE_PRIVATE_KEY="" ``` Add to `.gitignore`: ``` .env ``` ### TS Execution Copy one of the examples with the proper authority into `src/index.ts` for execution and run with: ``` npm start ``` ### JS Execution Copy one of the examples with the proper authority into `src/index.js` for execution and run with: ``` node src/index.js ``` ## `new RWA(opts)` Initialize the peaq RWA SDK for a specific chain. The instance wires module addresses for that chain and exposes module entry points. A Provider is required at initialization and is used for read operations; write operations require an explicit Signer. ### Parameters | Parameter | Type | Required | Description | | ------------ | ---------- | -------- | ------------------------------------------------------------------------------- | | **chainId** | `Chain` | Required | The Implementation Target network. `Chain.AGUNG` (9990) or `Chain.PEAQ` (3338). | | **provider** | `Provider` | Required | An ethers Provider used for read-only calls across modules. | ### Usage #### TypeScript ```Typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider } from 'ethers'; const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL!); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); console.log(rwa_sdk); ``` #### ESM JavaScript Default setup in JS guide with `"type": "module",` set via cmd: `npm pkg set type=module` ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider } from "ethers"; const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider: provider }); console.log(rwa_sdk); ``` #### Common JavaScript Optional setup to use **cjs**. Remove `"type": "module",` from your `package.json`. ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const { RWA, Chain } = require('@peaq-network/rwa'); const { JsonRpcProvider } = require('ethers'); const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider: provider }); console.log(rwa_sdk); ``` # Ensure Machine NFT Allowance Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/mnft/ensureMachineNftAllowance ## `mnft.ensureMachineNftAllowance(EnsureMachineNftAllowance)` Ensure the ERC20 allowance for the Machine NFT registration account is sufficient to cover the registration fee for N machines of a given value. If the allowance is insufficient, it submits an approval transaction. ### EnsureMachineNftAllowance Type Parameters | Parameter | Type | Required | Description | | --------------------- | -------- | -------- | -------------------------------------------------------------------- | | **machineController** | `Signer` | Required | Signer that will pay the ERC20 fee. Must be connected to a provider. | | **machineNft** | `string` | Required | Machine NFT contract address. | | **machineValueHuman** | `string` | Required | Machine value in human-readable units (e.g., `"10"`). | | **erc20** | `string` | Required | ERC20 token address used to pay the fee. | | **tokenDecimals** | `number` | Required | ERC20 token decimals. | | **machineCount** | `number` | Required | Number of machines to register. | ### Returns | Field | Type | Description | | --------------------- | ---------------------------------- | -------------------------------------------- | | **status** | `approved` or `already_sufficient` | Whether an approval was sent. | | **machineNft** | `string` | Machine NFT contract address. | | **feeToken** | `string` | ERC20 token address used for fees. | | **feePerMachine** | `bigint` | Fee per machine in token units. | | **requiredAllowance** | `bigint` | Total allowance required for `machineCount`. | | **currentAllowance** | `bigint` | Current allowance after the check/approval. | | **receipt** | `TransactionReceipt` | Only present when `status` is `'approved'`. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Machine controller (pays ERC20 fee via allowance) const alice = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 2. Ensure allowance const result = await rwa_sdk.mnft.ensureMachineNftAllowance({ machineController: alice, machineNft: "0x5Ca3db1f292f913DDdA8C5385E5391438665463c", machineValueHuman: "10", erc20: rwa_sdk.getAddresses().erc20.peaq, tokenDecimals: 18, machineCount: 2 }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Machine controller (pays ERC20 fee via allowance) const alice = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 2. Ensure allowance const result = await rwa_sdk.mnft.ensureMachineNftAllowance({ machineController: alice, machineNft: "0x5Ca3db1f292f913DDdA8C5385E5391438665463c", machineValueHuman: "10", erc20: rwa_sdk.getAddresses().erc20.peaq, tokenDecimals: 18, machineCount: 2 }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'approved', machineNft: '0x5Ca3db1f292f913DDdA8C5385E5391438665463c', feeToken: '0x...', feePerMachine: 10000000000000000000n, requiredAllowance: 20000000000000000000n, currentAllowance: 20000000000000000000n, receipt: ContractTransactionReceipt { ... } } ``` # Get Machine DID Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/mnft/getMachineDid ## `mnft.getMachineDid(GetMachineDid)` Read and deserialize a DID document from a Machine NFT. This is a read-only call. ### GetMachineDid Type Parameters | Parameter | Type | Required | Description | | -------------- | -------- | -------- | ----------------------------- | | **machineNft** | `string` | Required | Machine NFT contract address. | | **tokenId** | `string` | Required | Machine NFT token ID. | ### Returns | Field | Type | Description | | --------------- | -------- | ------------------------------------------ | | **didDocument** | `Object` | Deserialized DID document for the machine. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Get a known Machine NFT and tokenId const machineNft = "0x5Ca3db1f292f913DDdA8C5385E5391438665463c"; const tokenId = "tokenId"; // 2. Read DID document const result = await rwa_sdk.mnft.getMachineDid({ machineNft, tokenId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Get a known Machine NFT and tokenId const machineNft = "0x5Ca3db1f292f913DDdA8C5385E5391438665463c"; const tokenId = "tokenId"; // 2. Read DID document const result = await rwa_sdk.mnft.getMachineDid({ machineNft, tokenId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { didDocument: { id: 'did:peaq:...', controller: '0x...', verification_methods: [ ... ], services: [ ... ], authentications: [ ... ], verifiable_credential: { id: '...', type: 'MachineNft', issuer: '0x...', issuance_date: '...', credential_subject: { ... } } } } ``` # Register Machine NFT Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/mnft/registerMachineNft ## `mnft.registerMachine(RegisterMachine)` Register one or more Machine NFTs to a designated controller. This sends transactions from the Machine Issuer and charges the ERC20 fee from the controller. **The Machine NFT must be funded prior to execution.** ### RegisterMachine Type Parameters | Parameter | Type | Required | Description | | ------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------ | | **machineIssuer** | `Signer` | Required | Machine Issuer signer that submits the mint transactions. Must be connected to a provider. | | **machineNft** | `string` | Required | Machine NFT contract address. | | **machineValueHuman** | `string` | Required | Machine value in human-readable units (e.g., `"10"`). | | **machineControllerAddr** | `string` | Required | EOA address that will control/own the machines. | | **erc20** | `string` | Required | ERC20 token address used to pay the fee. | | **tokenDecimals** | `number` | Required | ERC20 token decimals. | | **salt** | `number` | Required | Salt used to derive the DID document. | | **count** | `number` | Required | Number of machines to register. | ### Returns | Field | Type | Description | | --------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------- | | **status** | `issued` | Status of the operation. | | **machineNft** | `string` | Machine NFT contract address. | | **machineIssuer** | `string` | Machine Issuer address that submitted the transaction(s). | | **machineController** | `string` | Machine controller/owner address. | | **machineValue** | `{ human: string; units: bigint; tokenDecimals: number; feeToken: string }` | Machine value details. | | **count** | `number` | Number of machines issued. | | **machines** | `{ machineId: string; did?: string; receipt?: TransactionReceipt }[]` | Per-machine results and receipts. | | **feesPaid** | `bigint` | ERC20 fees paid by the controller. | | **startingBalance** | `bigint` | Controller ERC20 balance before issuance. | | **endingBalance** | `bigint` | Controller ERC20 balance after issuance. | | **humanTokenDelta** | `string` | Human-readable fee delta. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Machine Issuer wallet const machineIssuer = new Wallet(process.env.MACHINE_ISSUER_PRIVATE_KEY!, provider); // 2. Machine controller (receives NFT and pays ERC20 fee via allowance) const alice = process.env.ALICE_PUBLIC_ADDRESS!; // 3. Register MachineNFT(s) for Alice const result = await rwa_sdk.mnft.registerMachine({ machineIssuer: machineIssuer, machineNft: "0x5Ca3db1f292f913DDdA8C5385E5391438665463c", machineValueHuman: "10", machineControllerAddr: alice, erc20: rwa_sdk.getAddresses().erc20.peaq, tokenDecimals: 18, salt: Math.floor(Math.random() * 10000), count: 2 }); console.log('Result', result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Machine Issuer wallet const machineIssuer = new Wallet(process.env.MACHINE_ISSUER_PRIVATE_KEY, provider); // 2. Machine controller (receives NFT and pays ERC20 fee via allowance) const alice = process.env.ALICE_PUBLIC_ADDRESS; // 3. Register MachineNFT(s) for Alice const result = await rwa_sdk.mnft.registerMachine({ machineIssuer: machineIssuer, machineNft: "0x5Ca3db1f292f913DDdA8C5385E5391438665463c", machineValueHuman: "10", machineControllerAddr: alice, erc20: rwa_sdk.getAddresses().erc20.peaq, tokenDecimals: 18, salt: Math.floor(Math.random() * 10000), count: 2 }); console.log('Result', result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'issued', machineNft: '0x5Ca3db1f292f913DDdA8C5385E5391438665463c', machineIssuer: '0x8BCfa2e9FC4aCa66fCF36Bcf47646E5Fb8d74BA0', machineController: '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', machineValue: { human: '10', units: 10000000000000000000n, tokenDecimals: 18, feeToken: '0x...' }, count: 2, machines: [ { machineId: '1', did: 'did:peaq:...', receipt: ContractTransactionReceipt { ... } }, { machineId: '2', did: 'did:peaq:...', receipt: ContractTransactionReceipt { ... } } ], feesPaid: 20000000000000000000n, startingBalance: 100000000000000000000n, endingBalance: 80000000000000000000n, humanTokenDelta: '20.0' } ``` # Add Machine Issuer Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/rwanft/addMachineIssuer ## `rwanft.addMachineIssuer(AddMachineIssuer)` Add a Machine Issuer to the PeaqRwaNft contract. This sends a transaction signed by a Machine Regulator. ### AddMachineIssuer Type Parameters | Parameter | Type | Required | Description | | -------------------------- | -------- | -------- | --------------------------------------------------------------------------------------------- | | **machineRegulatorSigner** | `Signer` | Required | Machine Regulator signer authorized to add a Machine Issuer. Must be connected to a provider. | | **newMachineIssuer** | `string` | Required | EOA address of the Machine Issuer to add. | ### Returns | Field | Type | Description | | ----------------- | -------------------- | ---------------------------------------------------------------- | | **status** | `added` | Status of the operation. | | **peaqRwaNft** | `string` | PeaqRwaNft contract address. | | **machineIssuer** | `string` | Machine Issuer address that was added. | | **machineNft** | `string` | Machine NFT contract address associated with the Machine Issuer. | | **addedBy** | `string` | Machine Regulator address that submitted the transaction. | | **receipt** | `TransactionReceipt` | Transaction receipt of the add operation. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Machine Regulator signer const machineRegulator = new Wallet(process.env.ADMIN_PRIVATE_KEY!, provider); // 2. Machine Issuer address to add const machineIssuer = process.env.MACHINE_ISSUER_PUBLIC_ADDRESS!; // 3. Add Machine Issuer const result = await rwa_sdk.rwanft.addMachineIssuer({ machineRegulatorSigner: machineRegulator, newMachineIssuer: machineIssuer }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Machine Regulator signer const machineRegulator = new Wallet(process.env.MACHINE_REGULATOR_PRIVATE_KEY, provider); // 2. Machine Issuer address to add const machineIssuer = process.env.MACHINE_ISSUER_PUBLIC_ADDRESS; // 3. Add Machine Issuer const result = await rwa_sdk.rwanft.addMachineIssuer({ machineRegulatorSigner: machineRegulator, newMachineIssuer: machineIssuer }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'added', peaqRwaNft: '0x9a7e2c5B4F9bE3C1dA7b5a6F8e2D3c4B5A6F7E8D', machineIssuer: '0x3c1D2e3F4a5B6c7D8e9F0a1B2c3D4e5F6a7B8c9D', machineNft: '0x1A2b3C4d5E6f7A8b9C0d1E2f3A4b5C6D7e8F9A0b', addedBy: '0x8F7e6D5c4B3a2D1c0B9a8E7f6D5c4B3A2D1C0b9A', receipt: ContractTransactionReceipt { ... } } ``` Note: the Machine Issuer address typically must already have the appropriate Role claim before being added. # Find Contract NFT Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/rwanft/findContractNft ## `rwanft.findContractNft(FindContractNft)` Find a ContractNft address with available storage for a given contract ID. This is a read-only call. ### FindContractNft Type Parameters | Parameter | Type | Required | Description | | -------------- | -------- | -------- | ---------------------------------- | | **contractId** | `string` | Required | Contract ID (stringified integer). | ### Returns | Field | Type | Description | | --------------- | -------- | ------------------------------------------------------------------------ | | **contractNft** | `string` | ContractNft address with available storage for the provided contract ID. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Find contract NFT by contract ID const contractId = "1234567890"; const result = await rwa_sdk.rwanft.findContractNft({ contractId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Find contract NFT by contract ID const contractId = "1234567890"; const result = await rwa_sdk.rwanft.findContractNft({ contractId }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { contractNft: '0x35D67095A5a6f00CBE288cF744b3efC48de3699a' } ``` Note: if the contract ID is already in use, the call will revert with a message similar to `Not available, please contact owner`. # Get Machine Issuers Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/rwanft/getMachineIssuers ## `rwanft.getMachineIssuers()` Fetch the list of Machine Issuer addresses registered in the PeaqRwaNft contract. This is a read-only call. ### Parameters None. ### Returns | Field | Type | Description | | ------------------ | ---------- | ---------------------------------- | | **machineIssuers** | `string[]` | Array of Machine Issuer addresses. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Get Machine Issuers const result = await rwa_sdk.rwanft.getMachineIssuers(); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider: provider }); // 1. Get Machine Issuers const result = await rwa_sdk.rwanft.getMachineIssuers(); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { machineIssuers: [ '0x8BCfa2e9FC4aCa66fCF36Bcf47646E5Fb8d74BA0', '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2' ] } ``` # Get Machine Regulators Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/rwanft/getMachineRegulators ## `rwanft.getMachineRegulators()` Fetch the list of Machine Regulator addresses registered in the PeaqRwaNft contract. This is a read-only call. ### Parameters None. ### Returns | Field | Type | Description | | --------------------- | ---------- | ------------------------------------- | | **machineRegulators** | `string[]` | Array of Machine Regulator addresses. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Get Machine Regulators const result = await rwa_sdk.rwanft.getMachineRegulators(); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider: provider }); // 1. Get Machine Regulators const result = await rwa_sdk.rwanft.getMachineRegulators(); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { machineRegulators: [ '0x8BCfa2e9FC4aCa66fCF36Bcf47646E5Fb8d74BA0' ] } ``` # Remove Machine Issuer Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/rwanft/removeMachineIssuer ## `rwanft.removeMachineIssuer(RemoveMachineIssuer)` Remove a Machine Issuer from the PeaqRwaNft contract. This sends a transaction signed by a Machine Regulator. ### RemoveMachineIssuer Type Parameters | Parameter | Type | Required | Description | | -------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------ | | **machineRegulatorSigner** | `Signer` | Required | Machine Regulator signer authorized to remove a Machine Issuer. Must be connected to a provider. | | **machineIssuer** | `string` | Required | EOA address of the Machine Issuer to remove. | ### Returns | Field | Type | Description | | ----------------- | -------------------- | --------------------------------------------------------- | | **status** | `removed` | Status of the operation. | | **peaqRwaNft** | `string` | PeaqRwaNft contract address. | | **machineIssuer** | `string` | Machine Issuer address that was removed. | | **removedBy** | `string` | Machine Regulator address that submitted the transaction. | | **receipt** | `TransactionReceipt` | Transaction receipt of the remove operation. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Machine Regulator signer const machineRegulator = new Wallet(process.env.MACHINE_REGULATOR_PRIVATE_KEY!, provider); // 2. Machine Issuer address to remove const machineIssuer = process.env.MACHINE_ISSUER_PUBLIC_ADDRESS!; // 3. Remove Machine Issuer const result = await rwa_sdk.rwanft.removeMachineIssuer({ machineRegulatorSigner: machineRegulator, machineIssuer: machineIssuer }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Machine Regulator signer const machineRegulator = new Wallet(process.env.MACHINE_REGULATOR_PRIVATE_KEY, provider); // 2. Machine Issuer address to remove const machineIssuer = process.env.MACHINE_ISSUER_PUBLIC_ADDRESS; // 3. Remove Machine Issuer const result = await rwa_sdk.rwanft.removeMachineIssuer({ machineRegulatorSigner: machineRegulator, machineIssuer: machineIssuer }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'removed', peaqRwaNft: '0x9a7e2c5B4F9bE3C1dA7b5a6F8e2D3c4B5A6F7E8D', machineIssuer: '0x3c1D2e3F4a5B6c7D8e9F0a1B2c3D4e5F6a7B8c9D', removedBy: '0x8F7e6D5c4B3a2D1c0B9a8E7f6D5c4B3A2D1C0b9A', receipt: ContractTransactionReceipt { ... } } ``` # Set Machine NFT Block State Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/rwanft/setMachineNftBlockState ## `rwanft.setMachineNftBlockState(SetMachineNftBlockState)` Set the block state of a Machine Issuer address or a Machine NFT contract address in the PeaqRwaNft contract. This sends a transaction signed by a Machine Regulator. ### SetMachineNftBlockState Type Parameters | Parameter | Type | Required | Description | | -------------------------- | --------- | -------- | ----------------------------------------------------------------------------------------------- | | **machineRegulatorSigner** | `Signer` | Required | Machine Regulator signer authorized to update the block state. Must be connected to a provider. | | **issuerOrContractNft** | `string` | Required | EOA address of a Machine Issuer or Machine NFT contract address. | | **blocked** | `boolean` | Required | `true` to block, `false` to unblock. | ### Returns | Field | Type | Description | | -------------- | -------------------- | --------------------------------------------------------- | | **status** | `updated` | Status of the operation. | | **peaqRwaNft** | `string` | PeaqRwaNft contract address. | | **target** | `string` | The address whose block state was updated. | | **blocked** | `boolean` | The resulting block state. | | **updatedBy** | `string` | Machine Regulator address that submitted the transaction. | | **receipt** | `TransactionReceipt` | Transaction receipt of the update operation. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Machine Regulator signer const machineRegulator = new Wallet(process.env.MACHINE_REGULATOR_PRIVATE_KEY!, provider); // 2. Target Machine Issuer or Machine NFT contract const target = process.env.MACHINE_ISSUER_PUBLIC_ADDRESS!; // 3. Block target const result = await rwa_sdk.rwanft.setMachineNftBlockState({ machineRegulatorSigner: machineRegulator, issuerOrContractNft: target, blocked: true }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from '@peaq-network/rwa'; import { JsonRpcProvider, Wallet } from 'ethers'; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Machine Regulator signer const machineRegulator = new Wallet(process.env.MACHINE_REGULATOR_PRIVATE_KEY, provider); // 2. Target Machine Issuer or Machine NFT contract const target = process.env.MACHINE_ISSUER_PUBLIC_ADDRESS; // 3. Block target const result = await rwa_sdk.rwanft.setMachineNftBlockState({ machineRegulatorSigner: machineRegulator, issuerOrContractNft: target, blocked: true }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'updated', peaqRwaNft: '0x9a7e2c5B4F9bE3C1dA7b5a6F8e2D3c4B5A6F7E8D', target: '0x3c1D2e3F4a5B6c7D8e9F0a1B2c3D4e5F6a7B8c9D', blocked: true, updatedBy: '0x8F7e6D5c4B3a2D1c0B9a8E7f6D5c4B3A2D1C0b9A', receipt: ContractTransactionReceipt { ... } } ``` # Approve Vault as Operator Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/approveVaultAsOperator ## `vault.nftApproval(NftApproval)` Approve a Vault as operator for specific Machine/Contract NFT token IDs by calling `approve` per token. This sends transactions from the NFT owner/controller. ### NftApproval Type Parameters | Parameter | Type | Required | Description | | --------------------- | ---------- | -------- | ------------------------------------------------------------------------------- | | **machineController** | `Signer` | Required | Owner/controller signer that grants approvals. Must be connected to a provider. | | **nft** | `string` | Required | Machine NFT or Contract NFT contract address. | | **vault** | `string` | Required | Vault address to approve as operator. | | **tokenIds** | `string[]` | Required | Token IDs to approve. | ### Returns | Field | Type | Description | | ------------------------- | ---------------------- | ----------------------------------------- | | **status** | `approved` | Status of the operation. | | **nft** | `string` | NFT contract address. | | **vault** | `string` | Vault address approved for the token IDs. | | **newlyApprovedTokenIds** | `string[]` | Token IDs approved in this call. | | **receipts** | `TransactionReceipt[]` | Transaction receipts (one per token ID). | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Machine Controller const controller = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 2. Approve the vault for specific MNFT token IDs const result = await rwa_sdk.vault.nftApproval({ machineController: controller, nft: "0x5Ca3db1f292f913DDdA8C5385E5391438665463c", vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", tokenIds: [ "1262843802665614120367007478296348432923457422026", "880598419457374294774049460835571533031091411284" ] }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Machine Controller const controller = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 2. Approve the vault for specific MNFT token IDs const result = await rwa_sdk.vault.nftApproval({ machineController: controller, nft: "0x5Ca3db1f292f913DDdA8C5385E5391438665463c", vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", tokenIds: [ "1262843802665614120367007478296348432923457422026", "880598419457374294774049460835571533031091411284" ] }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'approved', nft: '0x5Ca3db1f292f913DDdA8C5385E5391438665463c', vault: '0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a', newlyApprovedTokenIds: [ '1262843802665614120367007478296348432923457422026', '880598419457374294774049460835571533031091411284' ], receipts: [ ContractTransactionReceipt { ... }, ContractTransactionReceipt { ... } ] } ``` Notes: * This approves specific token IDs (not `setApprovalForAll`). * For Contract NFTs, pass the Contract NFT address and its token IDs. # Claim Yield Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/claimYield ## `vault.claimYield(ClaimYield)` Claim yield from a vault’s reward distributor. Yield is sent to the claimer. ### ClaimYield Type Parameters | Parameter | Type | Required | Description | | ----------------- | -------- | -------- | ---------------------------------------------------------- | | **claimerSigner** | `Signer` | Required | Signer that claims yield. Must be connected to a provider. | | **vault** | `string` | Required | Vault address. | ### Returns | Field | Type | Description | | --------------------- | -------------------- | ------------------------------------------------------------ | | **status** | `claimed` | Status of the operation. | | **vault** | `string` | Vault address. | | **rewardDistributor** | `string` | Reward distributor contract address. | | **claimer** | `string` | Address that submitted the transaction (recipient of yield). | | **receipt** | `TransactionReceipt` | Transaction receipt for the claim call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Claimer signer const bob = new Wallet(process.env.BOB_PRIVATE_KEY!, provider); // 2. Claim yield const result = await rwa_sdk.vault.claimYield({ claimerSigner: bob, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Claimer signer const bob = new Wallet(process.env.BOB_PRIVATE_KEY, provider); // 2. Claim yield const result = await rwa_sdk.vault.claimYield({ claimerSigner: bob, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'claimed', vault: '0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a', rewardDistributor: '0x...', claimer: '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', receipt: ContractTransactionReceipt { ... } } ``` # Claim Yield To Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/claimYieldTo ## `vault.claimYieldTo(ClaimYieldTo)` Claim yield from a vault’s reward distributor and send it to a specified address. ### ClaimYieldTo Type Parameters | Parameter | Type | Required | Description | | ----------------- | -------- | -------- | --------------------------------------------------------------- | | **claimerSigner** | `Signer` | Required | Signer that submits the claim. Must be connected to a provider. | | **vault** | `string` | Required | Vault address. | | **to** | `string` | Required | Recipient address for the claimed yield. | ### Returns | Field | Type | Description | | --------------------- | -------------------- | --------------------------------------- | | **status** | `claimed` | Status of the operation. | | **vault** | `string` | Vault address. | | **rewardDistributor** | `string` | Reward distributor contract address. | | **claimer** | `string` | Address that submitted the transaction. | | **recipient** | `string` | Address that received the yield. | | **receipt** | `TransactionReceipt` | Transaction receipt for the claim call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Claimer signer const alice = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 2. Recipient of yield const charlie = process.env.CHARLIE_PUBLIC_ADDRESS! // 3. Claim yield to Bob const result = await rwa_sdk.vault.claimYieldTo({ claimerSigner: alice, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", to: charlie }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Claimer signer const alice = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 2. Recipient of yield const charlie = process.env.CHARLIE_PUBLIC_ADDRESS // 3. Claim yield to Bob const result = await rwa_sdk.vault.claimYieldTo({ claimerSigner: alice, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", to: charlie }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'claimed', vault: '0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a', rewardDistributor: '0x...', claimer: '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', recipient: '0xbA9274C766A5961C40bB4a3e0e107699EE9Dab9C', receipt: ContractTransactionReceipt { ... } } ``` # Create Vault and Token Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/createVaultAndToken ## `vault.createVault(CreateVault)` Create a new Vault and its associated security token using a Vault Factory. This sends a transaction from the vault deployer. ### CreateVault Type Parameters | Parameter | Type | Required | Description | | ----------------------- | ---------- | -------- | ----------------------------------------------------------------------------- | | **vaultDeployer** | `Signer` | Required | Deployer signer authorized to create vaults. Must be connected to a provider. | | **vaultController** | `string` | Required | EOA address that will control the vault. | | **vaultFactory** | `string` | Required | Vault Factory contract address. | | **infoDesk** | `string` | Required | InfoDesk contract address. | | **trustedClaimIssuers** | `string[]` | Required | Addresses of trusted Claim Issuer contracts. | | **tokenName** | `string` | Required | Security token name. | | **tokenSymbol** | `string` | Required | Security token symbol. | | **payoutToken** | `string` | Required | ERC20 token address used for payouts. | ### Returns | Field | Type | Description | | --------------- | -------------------- | ------------------------------------------ | | **status** | `created` | Status of the operation. | | **vault** | `string` | Deployed Vault address. | | **token** | `string` | Deployed security token address. | | **distributor** | `string` | Deployed reward distributor address. | | **receipt** | `TransactionReceipt` | Transaction receipt for the creation call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Vault deployer signer const admin = new Wallet(process.env.ADMIN_PRIVATE_KEY!, provider); // 2. Vault controller const alice = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 3. Create Vault const result = await rwa_sdk.vault.createVault({ vaultDeployer: admin, vaultController: alice.address, vaultFactory: "0x7809591A43449Ab57452c56Cf7d95b04Ef9886b6", infoDesk: "0x9f2bF4e338cCC48D1b7021494377907ea4a593F2", trustedClaimIssuers: [process.env.CLAIM_ISSUER_CONTRACT_ADDRESS!], tokenName: "Test Token ABC", tokenSymbol: "ABC", payoutToken: rwa_sdk.getAddresses().erc20.peaq }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Vault deployer signer const admin = new Wallet(process.env.ADMIN_PRIVATE_KEY, provider); // 2. Vault controller const alice = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 3. Create Vault const result = await rwa_sdk.vault.createVault({ vaultDeployer: admin, vaultController: alice.address, vaultFactory: "0x5C5Db5CcF63ed6C11063385070C8FD2C990BFd53", infoDesk: "0x3F2c72Ba389632079DA68Ee13E8b955d69D1B5c1", trustedClaimIssuers: [process.env.CLAIM_ISSUER_CONTRACT_ADDRESS], tokenName: "Test Token ABC", tokenSymbol: "ABC", payoutToken: rwa_sdk.getAddresses().erc20.peaq }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'created', vault: '0x4b76a8F7cdB68a9353c83e18077E6bbC760243B3', token: '0x811247945f5fcBD9068F71298a69e71B2A4Ba66f', distributor: '0x4210D83E736789e361DC96CC07756cb573e23CEd', receipt: ContractTransactionReceipt { ... } } ``` Notes: * The Vault deployer must be authorized in the Vault Factory. * `trustedClaimIssuers` should include Claim Issuer contracts required by compliance. # Deposit Yield Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/depositYield ## `vault.depositYield(DepositYield)` Deposit yield into a vault’s reward distributor. This approves the ERC20 amount and then deposits it. ### DepositYield Type Parameters | Parameter | Type | Required | Description | | ----------------------- | -------- | -------- | ------------------------------------------------------------ | | **depositorSigner** | `Signer` | Required | Signer that deposits yield. Must be connected to a provider. | | **vault** | `string` | Required | Vault address. | | **erc20** | `string` | Required | ERC20 token address used to deposit yield. | | **decimals** | `number` | Required | ERC20 token decimals. | | **humanReadableAmount** | `string` | Required | Amount to deposit in human-readable units (e.g., `"1"`). | ### Returns | Field | Type | Description | | --------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------- | | **status** | `deposited` | Status of the operation. | | **vault** | `string` | Vault address. | | **rewardDistributor** | `string` | Reward distributor contract address. | | **depositor** | `string` | Address that submitted the transaction. | | **token** | `{ address: string; decimals: number }` | ERC20 token details. | | **amount** | `{ human: string; units: bigint }` | Amount details. | | **approval** | `{ status: 'approved'; spender: string; allowanceBefore: bigint; allowanceAfter: bigint }` | Approval details for the reward distributor. | | **receipt** | `TransactionReceipt` | Transaction receipt for the deposit call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Depositor signer const alice = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 2. Deposit yield const result = await rwa_sdk.vault.depositYield({ depositorSigner: alice, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", erc20: rwa_sdk.getAddresses().erc20.peaq, decimals: 18, humanReadableAmount: "1" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Depositor signer const alice = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 2. Deposit yield const result = await rwa_sdk.vault.depositYield({ depositorSigner: alice, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", erc20: rwa_sdk.getAddresses().erc20.peaq, decimals: 18, humanReadableAmount: "1" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'deposited', vault: '0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a', rewardDistributor: '0x...', depositor: '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', token: { address: '0x...', decimals: 18 }, amount: { human: '1', units: 1000000000000000000n }, approval: { status: 'approved', spender: '0x...', allowanceBefore: 0n, allowanceAfter: 1000000000000000000n }, receipt: ContractTransactionReceipt { ... } } ``` # Ensure Transfer Fee Allowance Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/ensureTransferFeeAllowance ## `vault.ensureTransferFeeAllowance(EnsureTransferFeeAllowance)` Ensure an ERC20 allowance is set to pay the vault transfer fee for a given token transfer. If the allowance is insufficient, it submits an approval transaction. ### EnsureTransferFeeAllowance Type Parameters | Parameter | Type | Required | Description | | ----------------------- | -------- | -------- | ------------------------------------------------------------------- | | **allowanceSigner** | `Signer` | Required | Signer that pays the transfer fee. Must be connected to a provider. | | **vault** | `string` | Required | Vault address that computes the transfer fee. | | **token** | `string` | Required | Security token address being transferred. | | **erc20** | `string` | Required | ERC20 token address used to pay the fee. | | **transferAmountHuman** | `string` | Required | Transfer amount in human-readable units (e.g., `"2"`). | ### Returns | Field | Type | Description | | -------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | | **status** | `already_sufficient` or `approved` | Whether an approval was sent. | | **vault** | `string` | Vault address. | | **feeToken** | `string` | ERC20 token address used for fees. | | **transfer** | `{ token: string; amountHuman: string; amountUnits: bigint; tokenDecimals: number }` | Transfer amount details. | | **fee** | `{ requiredAllowance: bigint; allowanceBefore: bigint; allowanceAfter: bigint }` | Fee allowance details. | | **approvedBy** | `string` | Address that submitted the approval (or current allowance owner). | | **receipt** | `TransactionReceipt` | Only present when `status` is `'approved'`. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Allowance signer (pays transfer fee) const alice = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 2. Ensure transfer fee allowance const result = await rwa_sdk.vault.ensureTransferFeeAllowance({ allowanceSigner: alice, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", token: "0x2a6aA5ef4e236aeE4247EAA7B926cd843a95bFc8", erc20: rwa_sdk.getAddresses().erc20.peaq, transferAmountHuman: "2" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Allowance signer (pays transfer fee) const alice = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 2. Ensure transfer fee allowance const result = await rwa_sdk.vault.ensureTransferFeeAllowance({ allowanceSigner: alice, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", token: "0x2a6aA5ef4e236aeE4247EAA7B926cd843a95bFc8", erc20: rwa_sdk.getAddresses().erc20.peaq, transferAmountHuman: "2" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'approved', vault: '0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a', feeToken: '0x...', transfer: { token: '0x2a6aA5ef4e236aeE4247EAA7B926cd843a95bFc8', amountHuman: '2', amountUnits: 2000000000000000000n, tokenDecimals: 18 }, fee: { requiredAllowance: 1000000000000000000n, allowanceBefore: 0n, allowanceAfter: 1000000000000000000n }, approvedBy: '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', receipt: ContractTransactionReceipt { ... } } ``` # Mint Security Tokens Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/mintSecurityTokens ## `vault.depositAndMint(DepositAndMint)` Deposit an array of RWA NFTs into the vault and mint the corresponding amount of security tokens. ### DepositAndMint Type Parameters | Parameter | Type | Required | Description | | ------------------- | ---------- | -------- | ---------------------------------------------------------------------------- | | **vaultController** | `Signer` | Required | Vault controller signer that owns the NFTs. Must be connected to a provider. | | **vault** | `string` | Required | Vault address. | | **rwaNfts** | `string[]` | Required | Addresses of the RWA NFT contracts (MNFT/CNFT). | | **tokenIds** | `string[]` | Required | Token IDs of the NFTs to deposit. | | **amount** | `number` | Required | Amount of security tokens to mint. | ### Returns | Field | Type | Description | | -------------- | ---------------------- | -------------------------------------------------- | | **status** | `deposited_and_minted` | Status of the operation. | | **vault** | `string` | Vault address. | | **controller** | `string` | Vault controller address. | | **rwaNfts** | `string[]` | NFT contract addresses provided. | | **tokenIds** | `string[]` | Token IDs deposited. | | **amount** | `number` | Amount of security tokens minted. | | **receipt** | `TransactionReceipt` | Transaction receipt for the deposit and mint call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Vault controller const vaultController = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 2. Deposit NFTs and mint tokens const result = await rwa_sdk.vault.depositAndMint({ vaultController: vaultController, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", rwaNfts: [ "0x5Ca3db1f292f913DDdA8C5385E5391438665463c", "0x5Ca3db1f292f913DDdA8C5385E5391438665463c", "0x35D67095A5a6f00CBE288cF744b3efC48de3699a" ], tokenIds: [ "1262843802665614120367007478296348432923457422026", "880598419457374294774049460835571533031091411284", "110399289532161649501907442204937966168773206671183427730650359857010370852178" ], amount: 10000 }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Vault controller const vaultController = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 2. Deposit NFTs and mint tokens const result = await rwa_sdk.vault.depositAndMint({ vaultController: vaultController, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", rwaNfts: [ "0x5Ca3db1f292f913DDdA8C5385E5391438665463c", "0x5Ca3db1f292f913DDdA8C5385E5391438665463c", "0x35D67095A5a6f00CBE288cF744b3efC48de3699a" ], tokenIds: [ "1262843802665614120367007478296348432923457422026", "880598419457374294774049460835571533031091411284", "110399289532161649501907442204937966168773206671183427730650359857010370852178" ], amount: 10000 }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'deposited_and_minted', vault: '0x4b76a8F7cdB68a9353c83e18077E6bbC760243B3', controller: '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', rwaNfts: [ '0x5Ca3db1f292f913DDdA8C5385E5391438665463c', '0x5Ca3db1f292f913DDdA8C5385E5391438665463c', '0x35D67095A5a6f00CBE288cF744b3efC48de3699a' ], tokenIds: [ '1262843802665614120367007478296348432923457422026', '880598419457374294774049460835571533031091411284', '110399289532161649501907442204937966168773206671183427730650359857010370852178' ], amount: 10000, receipt: ContractTransactionReceipt { ... } } ``` Notes: * Ensure the vault has been approved as operator for the provided NFTs by the `vaultController`. * Ensure `rwaNfts` and `tokenIds` arrays align by index. # Pause Token Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/pauseToken ## `vault.pauseToken(PauseToken)` Pause the security token for a vault via the Vault Factory. This sends a transaction from the vault deployer. ### PauseToken Type Parameters | Parameter | Type | Required | Description | | ----------------- | -------- | -------- | --------------------------------------------------------------------- | | **vaultDeployer** | `Signer` | Required | Deployer signer authorized to pause. Must be connected to a provider. | | **vaultFactory** | `string` | Required | Vault Factory contract address. | | **vault** | `string` | Required | Vault address whose token will be paused. | ### Returns | Field | Type | Description | | ---------------- | -------------------- | --------------------------------------- | | **status** | `paused` | Status of the operation. | | **vault** | `string` | Vault address whose token was paused. | | **vaultFactory** | `string` | Vault Factory contract address. | | **pausedBy** | `string` | Address that submitted the transaction. | | **receipt** | `TransactionReceipt` | Transaction receipt of the pause call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Vault deployer signer const vaultDeployer = new Wallet(process.env.ADMIN_PRIVATE_KEY!, provider); // 2. Pause token const result = await rwa_sdk.vault.pauseToken({ vaultDeployer: vaultDeployer, vaultFactory: "0x7809591A43449Ab57452c56Cf7d95b04Ef9886b6", vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Vault deployer signer const vaultDeployer = new Wallet(process.env.ADMIN_PRIVATE_KEY, provider); // 2. Pause token const result = await rwa_sdk.vault.pauseToken({ vaultDeployer: vaultDeployer, vaultFactory: "0x7809591A43449Ab57452c56Cf7d95b04Ef9886b6", vault: "0x4b76a8F7cdB68a9353c83e18077E6bbC760243B3" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'paused', vault: '0x4b76a8F7cdB68a9353c83e18077E6bbC760243B3', vaultFactory: '0x5C5Db5CcF63ed6C11063385070C8FD2C990BFd53', pausedBy: '0x8BCfa2e9FC4aCa66fCF36Bcf47646E5Fb8d74BA0', receipt: ContractTransactionReceipt { ... } } ``` Notes: * The vault deployer must be authorized in the Vault Factory. # Register Identity Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/registerIdentity ## `vault.registerIdentity(RegisterIdentity)` Register an ONCHAINID for a user in the vault's Identity Registry. Must be called by a wallet authorized as an agent/operator for the registry. ### RegisterIdentity Type Parameters | Parameter | Type | Required | Description | | ------------------- | -------- | -------- | --------------------------------------------------------------------------- | | **vaultDeployer** | `Signer` | Required | Authorized agent of the Identity Registry. Must be connected to a provider. | | **vault** | `string` | Required | Vault address whose Identity Registry will be updated. | | **subject** | `string` | Required | User's EOA to be associated with the identity. | | **subjectIdentity** | `string` | Required | ONCHAINID contract address for the user. | | **country** | `string` | Required | Country code for the investor (e.g., `'0'`). | ### Returns | Field | Type | Description | | -------------------- | -------------------- | --------------------------------------------- | | **status** | `registered` | Status of the operation. | | **vault** | `string` | Vault address. | | **identityRegistry** | `string` | Identity Registry contract address. | | **subject** | `string` | User's EOA associated with the identity. | | **subjectIdentity** | `string` | ONCHAINID contract address for the user. | | **country** | `string` | Country code. | | **registeredBy** | `string` | Address that submitted the transaction. | | **receipt** | `TransactionReceipt` | Transaction receipt of the registration call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Vault deployer (must be an authorized agent) const vaultDeployer = new Wallet(process.env.ADMIN_PRIVATE_KEY!, provider); // 2. Get EOA and identity const alice = await rwa_sdk.onchainid.getIdentity({ subject: process.env.ALICE_PUBLIC_ADDRESS! }); // 3. Register Alice's identity in the vault's Identity Registry const result = await rwa_sdk.vault.registerIdentity({ vaultDeployer: vaultDeployer, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", subject: process.env.ALICE_PUBLIC_ADDRESS!, subjectIdentity: alice.identity, country: '0' }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Vault deployer (must be an authorized agent) const vaultDeployer = new Wallet(process.env.ADMIN_PRIVATE_KEY, provider); // 2. Get EOA and identity const alice = await rwa_sdk.onchainid.getIdentity({ subject: process.env.ALICE_PUBLIC_ADDRESS }); // 3. Register Alice's identity in the vault's Identity Registry const result = await rwa_sdk.vault.registerIdentity({ vaultDeployer: vaultDeployer, vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a", subject: process.env.ALICE_PUBLIC_ADDRESS, subjectIdentity: alice.identity, country: '0' }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'registered', vault: '0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a', identityRegistry: '0x...', subject: '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', subjectIdentity: '0x...', country: '0', registeredBy: '0x8BCfa2e9FC4aCa66fCF36Bcf47646E5Fb8d74BA0', receipt: ContractTransactionReceipt { ... } } ``` Notes: * Ensure the user does not already have an identity registered for this vault. * `vaultDeployer` must be configured as an agent of the Identity Registry. # Transfer Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/transfer ## `vault.transfer(Transfer)` Transfer tokens between addresses, scaling the human-readable amount using the token's decimals. ### Transfer Type Parameters | Parameter | Type | Required | Description | | ----------------------- | -------- | -------- | ------------------------------------------------------------------- | | **from** | `Signer` | Required | Sender wallet (must hold balance). Must be connected to a provider. | | **to** | `string` | Required | Recipient address. | | **token** | `string` | Required | Token address. | | **transferAmountHuman** | `string` | Required | Human-readable amount; scaled by token decimals. | ### Returns | Field | Type | Description | | ------------- | ---------------------------------------------------- | ------------------------------------- | | **status** | `transferred` | Status of the operation. | | **token** | `string` | Token address. | | **sender** | `string` | Sender address. | | **recipient** | `string` | Recipient address. | | **amount** | `{ human: string; units: bigint; decimals: number }` | Amount details. | | **receipt** | `TransactionReceipt` | Transaction receipt for the transfer. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Sender const alice = new Wallet(process.env.ALICE_PRIVATE_KEY!, provider); // 2. Recipient const bob = process.env.BOB_PUBLIC_ADDRESS!; // 3. Transfer const result = await rwa_sdk.vault.transfer({ from: alice, to: bob, token: "0x2a6aA5ef4e236aeE4247EAA7B926cd843a95bFc8", transferAmountHuman: "1" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Sender const alice = new Wallet(process.env.ALICE_PRIVATE_KEY, provider); // 2. Recipient const bob = process.env.BOB_PUBLIC_ADDRESS; // 3. Transfer const result = await rwa_sdk.vault.transfer({ from: alice, to: bob, token: "0x2a6aA5ef4e236aeE4247EAA7B926cd843a95bFc8", transferAmountHuman: "1" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'transferred', token: '0x811247945f5fcBD9068F71298a69e71B2A4Ba66f', sender: '0x16cd4D21537eD8F33bE08271A9FA6DCC426709b2', recipient: '0xbA9274C766A5961C40bB4a3e0e107699EE9Dab9C', amount: { human: '1', units: 1000000000000000000n, decimals: 18 }, receipt: ContractTransactionReceipt { ... } } ``` Notes: * The method fetches the token's decimals and scales the provided amount accordingly. * Ensure the sender is allowed to transfer (compliance checks may apply). # Unpause Token Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/vault/unpauseToken ## `vault.unpauseToken(UnpauseToken)` Unpause the security token for a vault via the Vault Factory. This sends a transaction from the vault deployer. ### UnpauseToken Type Parameters | Parameter | Type | Required | Description | | ----------------- | -------- | -------- | ----------------------------------------------------------------------- | | **vaultDeployer** | `Signer` | Required | Deployer signer authorized to unpause. Must be connected to a provider. | | **vaultFactory** | `string` | Required | Vault Factory contract address. | | **vault** | `string` | Required | Vault address whose token will be unpaused. | ### Returns | Field | Type | Description | | ---------------- | -------------------- | ---------------------------------------- | | **status** | `unpaused` | Status of the operation. | | **vault** | `string` | Vault address whose token was unpaused. | | **vaultFactory** | `string` | Vault Factory contract address. | | **unpausedBy** | `string` | Address that submitted the transaction. | | **receipt** | `TransactionReceipt` | Transaction receipt of the unpause call. | ### Usage #### TypeScript ```TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain, type SDKInit } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const init: SDKInit = { chainId: Chain.AGUNG, provider: provider }; const rwa_sdk = new RWA(init); // 1. Vault deployer const vaultDeployer = new Wallet(process.env.ADMIN_PRIVATE_KEY!, provider); // 2. Unpause token const result = await rwa_sdk.vault.unpauseToken({ vaultDeployer: vaultDeployer, vaultFactory: "0x7809591A43449Ab57452c56Cf7d95b04Ef9886b6", vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` #### JavaScript ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import 'dotenv/config'; import { RWA, Chain } from "@peaq-network/rwa"; import { JsonRpcProvider, Wallet } from "ethers"; async function main() { // 0. Create RWA instance and get provider const provider = new JsonRpcProvider(process.env.HTTPS_BASE_URL); const rwa_sdk = new RWA({ chainId: Chain.AGUNG, provider }); // 1. Vault deployer const vaultDeployer = new Wallet(process.env.ADMIN_PRIVATE_KEY, provider); // 2. Unpause Security Tokens const result = await rwa_sdk.vault.unpauseToken({ vaultDeployer: vaultDeployer, vaultFactory: "0x7809591A43449Ab57452c56Cf7d95b04Ef9886b6", vault: "0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a" }); console.log("Result", result); } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Example outputs ``` Result { status: 'unpaused', vault: '0x907229D0A25A5Bb16F0ff3D890f38Eb4Ad52Ea1a', vaultFactory: '0x7809591A43449Ab57452c56Cf7d95b04Ef9886b6', unpausedBy: '0x8BCfa2e9FC4aCa66fCF36Bcf47646E5Fb8d74BA0', receipt: ContractTransactionReceipt { ... } } ``` Notes: * The vault deployer must be authorized in the Vault Factory. # Common Flow Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/workflows/common-flow ## What this workflow shows This page describes an **end-to-end RWA flow**. It runs in order: 1. **Onboard participants** – Create ONCHAINID identities for Alice, Bob, and Charlie; then attach KYC claims so they can hold compliant tokens. 2. **Asset side** – Alice receives Machine NFTs (via a Machine Issuer) and completes a multi-party Contract NFT with Bob and Charlie. 3. **Vault and token** – Admin creates a vault with Alice as controller and unpauses the security token; then registers Alice, Bob, and Charlie in the vault’s Identity Registry. 4. **Collateralize and mint** – Alice approves the vault to move her Machine NFTs and Contract NFT, deposits them into the vault, and mints security tokens. 5. **Transfers and yield** – Alice transfers tokens to Bob and Charlie; Alice deposits yield into the vault; Bob claims yield (for himself and to Charlie). Throughout the examples you will see `process.env.*` (e.g. `process.env.HTTPS_BASE_URL`). These come from a **`.env` file** you set during [initialization](/peaqchain/sdk-reference/rwa/initialize). Keep this file secret and never commit it. ## Environment variables Use the same variable names as in the SDK reference so you can copy-paste and compare with the test. Example layout: ```js theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // Network RPC URL HTTPS_BASE_URL="" // PEAQ OWNER Admin ADMIN_PUBLIC_ADDRESS="" ADMIN_PRIVATE_KEY="" // Claim Issuer CLAIM_ISSUER_PUBLIC_ADDRESS="" CLAIM_ISSUER_PRIVATE_KEY="" CLAIM_ISSUER_CONTRACT_ADDRESS="" CLAIM_ISSUER_IDENTITY_ADDRESS="" // Machine Regulator MACHINE_REGULATOR_PUBLIC_ADDRESS="" MACHINE_REGULATOR_PRIVATE_KEY="" // Machine Issuer MACHINE_ISSUER_PUBLIC_ADDRESS="" MACHINE_ISSUER_PRIVATE_KEY="" // Alice ALICE_PUBLIC_ADDRESS="" ALICE_PRIVATE_KEY="" // Bob BOB_PUBLIC_ADDRESS="" BOB_PRIVATE_KEY="" // Charlie CHARLIE_PUBLIC_ADDRESS="" CHARLIE_PRIVATE_KEY="" ``` ## Parties at play ### Admin (Framework owner / Implementation Authority) **Who:** In production this is peaq (or the Implementation Authority). In tests it is the wallet you put in `ADMIN_*`. **What they do:** Own the ID Factory and Vault Factory; create ONCHAINID identities for users; create vaults and set the vault controller; unpause vault tokens; register identities in a vault’s Identity Registry so those EOAs can hold and transfer the security token. ### Claim Issuer **Who:** A trusted entity that issues and attests to claims (e.g. KYC). Onboarding is required; peaq approves and adds them to the Trusted Issuers Registry. **What they do:** Issue KYC (and optionally role) claims for identities. The Claim Issuer **contract** address is used by the vault’s compliance; the Claim Issuer **signer** (`CLAIM_ISSUER_PRIVATE_KEY`) signs claims. You need both `CLAIM_ISSUER_CONTRACT_ADDRESS` and the signer in `.env`. ### Machine Regulator **Who:** Authority that decides which addresses may act as Machine Issuers. **What they do:** Add or remove Machine Issuers via the PeaqRwaNft contract; set block state for issuers or Contract NFT contracts. Uses `MACHINE_REGULATOR_*` in the full flow setup (e.g. adding the Machine Issuer before the test runs). ### Machine Issuer **Who:** Entity allowed by the Machine Regulator to register Machine NFTs for a given machine value. **What they do:** Call `mnft.registerMachine` to mint Machine NFTs to a **machine controller** (e.g. Alice). The controller pays the ERC20 fee (after setting allowance via `mnft.ensureMachineNftAllowance`). Uses `MACHINE_ISSUER_*`. ### Alice (Asset owner / Vault controller) **Who:** In this flow, the main asset owner and vault controller. **What they do:** Get an identity and KYC; receive Machine NFTs from the Machine Issuer; create a Contract NFT as controller and have Bob and Charlie sign; become the vault controller when Admin creates the vault; approve the vault for her MNFT and CNFT token IDs; deposit those NFTs and mint security tokens; transfer tokens to Bob and Charlie; deposit yield into the vault. ### Bob and Charlie (Investors / Counterparties) **Who:** Participants who will hold security tokens and (in this flow) sign the Contract NFT as counterparties. **What they do:** Get identities and KYC; sign the Contract NFT created by Alice; get registered in the vault’s Identity Registry; receive token transfers from Alice; claim yield from the vault (Bob claims for himself and can claim to Charlie via `claimYieldTo`). ## Flow (step-by-step) Each step links to the SDK reference for that operation. Replace placeholders (e.g. Alice vs Bob) where the doc says “use this address/signer”. 1. **Create identities**\ [Create Identity](/peaqchain/sdk-reference/rwa/identity/createIdentity) for Alice, Bob, and Charlie. Use `idFactoryAdmin: admin`, `subject: ALICE_PUBLIC_ADDRESS` (or Bob / Charlie), and a unique `deploymentSalt`. 2. **Add KYC claims**\ [Add claim to identity](/peaqchain/sdk-reference/rwa/identity/addClaimToIdentity) for each of Alice, Bob, and Charlie. Use the Claim Issuer signer and contract; each identity owner signs `addClaimToIdentity`. 3. **Register Machine NFTs for Alice**\ [Ensure allowance](/peaqchain/sdk-reference/rwa/mnft/ensureMachineNftAllowance) then [Register machine](/peaqchain/sdk-reference/rwa/mnft/registerMachineNft). Use `machineIssuer` (Machine Issuer signer), `machineControllerAddr: alice.address`, and the same used in your deployment. Record the `machineIds` as these will be needed later when approving and minting. 4. **Create and complete a Contract NFT**\ [Create contract](/peaqchain/sdk-reference/rwa/cnft/createContract) (Alice as controller, Bob and Charlie as counterparties). Then [Sign contract](/peaqchain/sdk-reference/rwa/cnft/signContract) as Bob and as Charlie until status is `completed`. Save the `contractId` that is generated as it will be needed for signing and approval/minting later. 5. **Create vault and unpause token**\ [Create vault](/peaqchain/sdk-reference/rwa/vault/createVaultAndToken) (Admin as `vaultDeployer`, Alice as `vaultController`). Set the vault factory and info desk as the same contracts in your deployed framework. Make sure to write down the addresses for the vault, token, and distributor. Then [Unpause token](/peaqchain/sdk-reference/rwa/vault/unpauseToken) for that vault. 6. **Register identities for the vault**\ [Register identity](/peaqchain/sdk-reference/rwa/vault/registerIdentity) for Alice, Bob, and Charlie in the vault’s Identity Registry (Admin as `vaultDeployer`). 7. **Approve vault for NFTs**\ [NFT approval](/peaqchain/sdk-reference/rwa/vault/approveVaultAsOperator): approve the vault for the Machine NFT token IDs, then for the Contract NFT contract and its token ID (Alice as `machineController`). 8. **Deposit and mint**\ [Deposit and mint](/peaqchain/sdk-reference/rwa/vault/mintSecurityTokens): Alice deposits the same Machine NFTs and Contract NFT token IDs and mints the chosen amount of security tokens. 9. **Transfer tokens**\ [Ensure transfer fee allowance](/peaqchain/sdk-reference/rwa/vault/ensureTransferFeeAllowance) then [Transfer](/peaqchain/sdk-reference/rwa/vault/transfer) from Alice to Bob and from Alice to Charlie (use the vault’s security token address from step 5). 10. **Yield (optional)**\ [Deposit yield](/peaqchain/sdk-reference/rwa/vault/depositYield) (e.g. Alice deposits). [Claim yield](/peaqchain/sdk-reference/rwa/vault/claimYield) (e.g. Bob claims). [Claim yield to](/peaqchain/sdk-reference/rwa/vault/claimYieldTo) (e.g. Bob claims to Charlie). # Machine Issuer Flow Source: https://docs.peaq.xyz/peaqchain/sdk-reference/rwa/workflows/machine-issuer-flow This workflow shows how to **add a new Machine Issuer** so that an address can call `mnft.registerMachine` for a given Machine NFT contract. ## What “adding a Machine Issuer” means * The **Machine Regulator** (e.g. Framework Owner) approves an EOA as a **Machine Issuer**. * That EOA must have an ONCHAINID identity with a **Machine Issuer role claim** (`CT_MNFT_ISSUER`) issued by a trusted Claim Issuer. * The regulator then registers that EOA in the **PeaqRwaNft** contract via `rwanft.addMachineIssuer`. After that, the address can register machines (mint Machine NFTs) for machine controllers. ## Prerequisites * **Machine Regulator** signer (e.g. `MACHINE_REGULATOR_PRIVATE_KEY`) — will issue the role claim and call `addMachineIssuer`. * **Claim Issuer** contract address (`CLAIM_ISSUER_CONTRACT_ADDRESS`) — the same Claim Issuer used for KYC; its signer can issue role claims (in the test the admin is both regulator and claim issuer). * **Candidate Machine Issuer** — the EOA you want to turn into a Machine Issuer (e.g. Alice). They must already have an **ONCHAINID identity**. Create one first with [Create Identity](/peaqchain/sdk-reference/rwa/identity/createIdentity) if needed. ## Steps ### 1. Ensure the candidate has an identity Resolve the candidate’s ONCHAINID identity (create it first if they don’t have one): * [Get Identity](/peaqchain/sdk-reference/rwa/identity/getIdentity) with `subject: candidateAddress`. * If `status === 'not_found'`, [Create Identity](/peaqchain/sdk-reference/rwa/identity/createIdentity) for that address (ID Factory admin signs), then call Get Identity again. ### 2. Issue the Machine Issuer role claim The **Claim Issuer** (or the same signer acting as claim issuer) issues a role claim for the candidate’s identity with topic `CT_MNFT_ISSUER`: * [Issue Role Claim](/peaqchain/sdk-reference/rwa/identity/issueRoleClaim) with: * `claimIssuerSigner`: Machine Regulator (or your Claim Issuer signer). * `claimIssuerContract`: `CLAIM_ISSUER_CONTRACT_ADDRESS`. * `subjectIdentity`: the candidate’s identity address from step 1. * `roleTopic`: `ClaimTopics.CT_MNFT_ISSUER` (e.g. `7` — use the value from your SDK’s `ClaimTopics` enum). * `roleDescription`: e.g. `'Machine Issuer'`. You get back `{ claim, signature }`. ### 3. Add the claim to the candidate’s identity The **identity owner** (the candidate) must add the claim to their identity: * [Add Claim to Identity](/peaqchain/sdk-reference/rwa/identity/addClaimToIdentity) with: * `identityController`: candidate’s signer (e.g. Alice’s wallet). * `subjectIdentity`: candidate’s identity address. * `claim`: the claim from step 2. * `claimSignature`: the signature from step 2. ### 4. Register the Machine Issuer in PeaqRwaNft The **Machine Regulator** adds the candidate’s EOA to the PeaqRwaNft contract: * [Add Machine Issuer](/peaqchain/sdk-reference/rwa/rwanft/addMachineIssuer) with: * `machineRegulatorSigner`: Machine Regulator wallet. * `newMachineIssuer`: candidate’s EOA address (e.g. `alice.address`). Result includes `status: 'added'`, `machineIssuer`, `machineNft` (the contract created for this issuer), and `receipt`. ### 5. (Optional) Verify * [Get Machine Issuers](/peaqchain/sdk-reference/rwa/rwanft/getMachineIssuers) before and after step 4 to confirm the new address appears in the list. # What is peaq chain Source: https://docs.peaq.xyz/peaqchain/what-is-peaq The L1 that underlies the peaqOS contracts. peaq chain is an L1 blockchain. It is where the peaqOS contracts are deployed. Most teams building on peaqOS do not need to interact with peaq chain directly, since the peaqOS SDK abstracts it. This section covers protocol-layer work: deploying your own smart contracts, running a node, staking, and legacy peaqID, RBAC, and UMT primitives that predate peaqOS. The opinionated Machine Financial Passport flow. What 95% of teams should use. Solidity, node operations, staking, and the legacy peaq SDK primitives. ## When to use peaqOS vs peaq chain | I want to... | Use | | :------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- | | Give a machine a cross-chain identity | [peaqOS Activate](/peaqos/functions/activate) | | Query a machine's credit rating from another chain | [peaqOS MCR API](/peaqos/api-reference/overview) | | Submit revenue events for a fleet | [peaqOS SDK (proxy operator)](/peaqos/guides/proxy-operator-fleet) | | Delegate spending to an AI agent | [peaqOS Scale](/peaqos/functions/scale) | | Deploy a Solidity contract | [peaq chain (Build tab)](/peaqchain/build/basic-operations/smart-contracts/deploy-smart-contract) | | Run a validator | [peaq chain (Node operations)](/peaqchain/build/advanced-operations/node-operations/becoming-a-validator/introduction) | | Write to the DID pallet directly | [peaq SDK (legacy)](/peaqchain/sdk-reference/javascript/did-operations) | | Use on-chain RBAC primitives | [peaq SDK (legacy)](/peaqchain/sdk-reference/javascript/rbac-operations/role) | | Fractionalize a machine as an RWA | [peaqOS Tokenize](/peaqos/functions/tokenize) + [peaq chain RWA SDK](/peaqchain/sdk-reference/rwa/initialize) | **Both SDKs coexist.** `@peaqos/peaq-os-sdk` is the opinionated path through the Machine Financial Passport flow. `@peaq-network/sdk` gives full access to peaq chain primitives (DID pallet operations, RBAC, Storage, UMT, chain-level transactions). Most teams use peaqOS SDK; drop to peaq SDK for primitives peaqOS does not cover. ## What you'll find in this section * [**Build** reference](/peaqchain/build/getting-started/install-peaq-sdk): Solidity deployments, gas, wallets, event listening, indexers, precompiles, node operations, account abstraction, and ERC-8004 at the contract layer. * [**SDK reference**](/peaqchain/sdk-reference/home): legacy `@peaq-network/sdk` (JavaScript + Python), the Robotics SDK (Python + ROS 2), and the RWA SDK for fractional ownership primitives. * [**Tokenomics**](/peaqchain/learn/tokenomics): \$PEAQ supply, inflation schedule, validator rewards, and staking economics. # GET /machine/{did} Source: https://docs.peaq.xyz/peaqos/api-reference/get-machine GET https://mcr.peaq.xyz/machine/{did} Return the full machine profile for a DID, with response fields that vary by data visibility setting. ## Endpoint ```http theme={"theme":{"light":"github-light-default","dark":"github-dark"}} GET /machine/{did} ``` Returns the full machine profile for a DID. The response includes identity, MCR score, bond status, and data visibility-dependent fields. This endpoint shares its response builder with [GET /metadata/](/peaqos/api-reference/get-metadata); both return the same shape. ## Path parameters Machine DID (`did:peaq:0x...`) or raw EVM address (`0x...`) ## Response **200 OK -- base fields (always present)** | Field | Type | Description | | :------------------------- | :------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `schema_version` | string | Always `"1.0"` | | `name` | string | `"Machine #"` if an NFT token exists, otherwise `"Machine (no NFT)"` | | `peaqos.machine_id` | integer | On-chain machine ID | | `peaqos.did` | string | Machine DID | | `peaqos.operator` | string or null | Operator DID from the DID document. `null` if not set. | | `peaqos.mcr` | string | Letter rating: `AAA`, `AA`, `A`, `BBB`, `BB`, `B`, `NR`, or `Provisioned` | | `peaqos.mcr_score` | integer | MCR score 0-100 | | `peaqos.bond_status` | string | `"bonded"` or `"unbonded"` | | `peaqos.negative_flag` | boolean | `true` when the AdminFlags contract has a negative-flag timestamp set for this machine. Stays `true` for as long as the timestamp is recorded on-chain, regardless of whether the 180-day MCR scoring penalty is still in effect. | | `peaqos.event_count` | integer | Total on-chain event count | | `peaqos.data_visibility` | string | `"private"`, `"onchain"`, or `"public"`. Defaults to `"private"` if the DID document has no `data_visibility` attribute or contains an unrecognised value. | | `peaqos.documentation_url` | string or null | Public documentation URL from the DID document | ### Additional fields by data visibility The `data_visibility` value on the machine's DID document determines which additional fields appear in the `peaqos` object. #### `data_visibility == "private"` | Field | Type | Description | | :---------------- | :----- | :-------------------------------------------------------------------------------------------------------- | | `peaqos.data_api` | string | The machine's data API URL from the DID document. Only present when a `data_api` attribute is configured. | #### `data_visibility == "onchain"` | Field | Type | Description | | :------------------ | :---- | :--------------------------------------------------------------------------------------------------------- | | `peaqos.event_data` | array | Up to the first 100 events for this machine (oldest first by on-chain index), parsed from on-chain storage | Each element in `event_data`: | Field | Type | Description | | :---------------- | :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event_type` | integer | `0` = revenue, `1` = activity | | `origin_value` | integer | Non-negative ISO 4217 minor-unit integer (e.g. cents for USD/HKD, whole units for JPY/KRW). Revenue: raw partner-submitted amount in `origin_currency`. Activity: arbitrary non-negative integer. | | `timestamp` | integer | Unix timestamp | | `trust_level` | integer | `0` = self-reported, `1` = on-chain verifiable, `2` = hardware-signed | | `metadata` | object | Parsed JSON metadata from the event. Falls back to `{"raw": "..."}` if the stored metadata is not valid JSON. | | `origin_currency` | string | **Revenue only.** ISO 4217 currency code in upper-case. The API normalises against the 25-fiat supported list: USD, HKD, JPY, CNY, KRW, SGD, TWD, THB, PHP, MYR, IDR, VND, INR, EUR, GBP, CHF, SEK, NOK, DKK, PLN, CAD, AUD, NZD, MXN, BRL. Codes outside this set are echoed back as-is with `amount_status: "unsupported_currency"`. When the partner omits the currency on-chain, the server defaults to `"USD"`. Activity events omit this field. | | `origin_subunit` | integer or null | **Revenue only.** Divisor that converts `origin_value` to a display amount: `100` for two-decimal currencies (USD, HKD, EUR, etc.) and `1` for the zero-decimal codes JPY, KRW, and VND. `null` when `origin_currency` is outside the supported whitelist (`amount_status: "unsupported_currency"`). | | `usd_value` | integer or null | **Revenue only.** FX-normalized value in **USD cents**. Display = `usd_value / 100`. `null` when `amount_status != "ok"` (FX failed); consumers must branch on `amount_status` before displaying. | | `usd_subunit` | integer | **Revenue only.** Always `100`. USD's minor unit divisor; the field exists to make the cents convention explicit on the wire. | | `amount_status` | string | **Revenue only.** `"ok"` (FX succeeded; `usd_value` is meaningful), `"unsupported_currency"` (currency not in MCR FX whitelist), or `"fx_unavailable"` (FX feed degraded or stale). Non-`"ok"` rows score conservatively and surface `mcr_degraded: true` on [`/mcr/{did}`](/peaqos/api-reference/get-mcr). | #### `data_visibility == "public"` The server fetches live data from the machine's `data_api` URL with a 5-second timeout. The URL is validated before the request is made: private IP ranges, loopback addresses, link-local addresses, reserved ranges, multicast addresses, and known cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`) are blocked. Redirects are not followed. Responses must be valid JSON objects no larger than 1 MB. On success: | Field | Type | Description | | :-------------------- | :----- | :------------------------------------------------------- | | `peaqos.partner_data` | object | The JSON object returned by the machine's `data_api` URL | On failure: | Field | Type | Description | | :-------------------------- | :----- | :----------------------------- | | `peaqos.partner_data_error` | string | One of the fixed strings below | Possible `partner_data_error` values: | Value | Cause | | :-------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | | `"data_api not configured"` | DID has no `data_api` attribute | | `"blocked: unsafe URL"` | URL scheme is not http/https, hostname is missing, host is in the blocked list, or DNS resolves to a private/loopback/link-local/reserved/multicast IP | | `"blocked: malformed URL"` | URL could not be parsed | | `"fetch failed"` | Network error, timeout, redirect, or non-2xx HTTP response | | `"response too large"` | Response exceeded 1 MB | | `"invalid JSON response"` | Body was not valid JSON, or the parsed value was not a JSON object | ## Error responses | Status | `detail` | Condition | | :----- | :---------------------------------- | :----------------------------------------------------------- | | 400 | `"Empty DID"` | Path parameter resolves to an empty string | | 400 | `"Invalid Ethereum address format"` | DID or address does not match the expected hex format | | 404 | `"Machine DID not found"` | DID address has no `machineId` attribute in the DID registry | | 404 | `"Machine not registered"` | Machine ID is not present in the IdentityRegistry contract | | 503 | `"Service not initialised"` | Server started without contract addresses | | 503 | `"Chain unavailable"` | Any chain call failed | ## Example ```bash bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl "${PEAQOS_MCR_API_URL}/machine/did:peaq:0xabc1230000000000000000000000000000000001" ``` ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const response = await fetch( `${PEAQOS_MCR_API_URL}/machine/did:peaq:0xabc1230000000000000000000000000000000001` ); const data = await response.json(); console.log(data.peaqos.mcr, data.peaqos.data_visibility); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import requests response = requests.get( f"{PEAQOS_MCR_API_URL}/machine/did:peaq:0xabc1230000000000000000000000000000000001" ) data = response.json() print(data["peaqos"]["mcr"], data["peaqos"]["data_visibility"]) ``` **Response (private visibility)** ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "schema_version": "1.0", "name": "Machine #7", "peaqos": { "machine_id": 1, "did": "did:peaq:0xabc1230000000000000000000000000000000001", "operator": "did:peaq:0xoperator0000000000000000000000000000001", "mcr": "BB", "mcr_score": 45, "bond_status": "bonded", "negative_flag": false, "event_count": 12, "data_visibility": "private", "documentation_url": null, "data_api": "https://machine.example.com/api/data" } } ``` **Response (onchain visibility)** ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "schema_version": "1.0", "name": "Machine #7", "peaqos": { "machine_id": 1, "did": "did:peaq:0xabc1230000000000000000000000000000000001", "operator": null, "mcr": "B", "mcr_score": 30, "bond_status": "bonded", "negative_flag": false, "event_count": 5, "data_visibility": "onchain", "documentation_url": null, "event_data": [ { "event_type": 0, "origin_value": 20000, "timestamp": 1711900000, "trust_level": 2, "metadata": { "job_id": "abc" }, "origin_currency": "HKD", "origin_subunit": 100, "usd_value": 2564, "usd_subunit": 100, "amount_status": "ok" } ] } } ``` **Response (public visibility, fetch failure)** ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "schema_version": "1.0", "name": "Machine (no NFT)", "peaqos": { "machine_id": 3, "did": "did:peaq:0xdef4560000000000000000000000000000000003", "operator": null, "mcr": "NR", "mcr_score": 0, "bond_status": "unbonded", "negative_flag": false, "event_count": 0, "data_visibility": "public", "documentation_url": null, "partner_data_error": "blocked: unsafe URL" } } ``` ## Related endpoints * [GET /mcr/](/peaqos/api-reference/get-mcr) returns a focused MCR score response with revenue trend and summary. * [GET /metadata/](/peaqos/api-reference/get-metadata) returns the same response shape, resolved by NFT token ID instead of DID. * [GET /machines/](/peaqos/api-reference/get-machine-card) returns the peaqOS Machine Card resolved by numeric machine ID. # GET /machines/{machine_id} Source: https://docs.peaq.xyz/peaqos/api-reference/get-machine-card GET https://mcr.peaq.xyz/machines/{machine_id} Return a peaqOS Machine Card for a machine identified by its numeric machine ID. ## Endpoint ```http theme={"theme":{"light":"github-light-default","dark":"github-dark"}} GET /machines/{machine_id} ``` Returns a peaqOS Machine Card for a registered machine. The Machine Card follows the ERC-8004 agent registration file pattern, restyled with peaqOS terminology, and includes identity, services, operator, bond status, and registry references. ## Path parameters On-chain machine ID (not an NFT token ID and not a DID) ## Response **200 OK** | Field | Type | Description | | :-------------------------------- | :------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `type` | string | peaqOS registration type: `"peaqos:registration:v1"` | | `name` | string | `"Machine #"` | | `description` | string | Fixed string: `"peaqOS machine"` | | `did` | string | Machine DID derived from the machine wallet address (`did:peaq:`) | | `active` | boolean | Always `true` for registered machines | | `services` | array | List of service endpoints. Contains one `"web"` entry if a `data_api` attribute is configured on the DID document; otherwise empty. | | `services[].name` | string | `"web"` | | `services[].endpoint` | string | Value of the `data_api` DID attribute | | `data_visibility` | string | `"private"`, `"onchain"`, or `"public"`. Defaults to `"private"` if the DID document has no `data_visibility` attribute. | | `documentation_url` | string | Documentation URL from the DID document. Empty string if not set. | | `operator` | string or null | Operator DID from the DID document | | `bond_status` | string | `"bonded"` or `"unbonded"` | | `event_count` | integer | Total on-chain event count | | `registrations` | array | List of registry references (always one entry) | | `registrations[].type` | string | `"peaqos:registration:v1"` | | `registrations[].machineId` | integer | The machine's on-chain ID | | `registrations[].machineRegistry` | string | CAIP-compatible registry identifier: `eip155::` | The `chainId` in `machineRegistry` is read from the connected RPC node at server startup (default `3338` for peaq mainnet). The `registryAddress` is the IdentityRegistry contract address. ## Error responses | Status | `detail` | Condition | | :----- | :--------------------------- | :----------------------------------------------------------------- | | 404 | `"Machine not found"` | Machine ID does not exist in the IdentityRegistry contract | | 404 | `"Machine wallet not found"` | The machine's wallet address is the zero address (`0x0000...0000`) | | 422 | (FastAPI validation error) | `machine_id` is less than 1 or not an integer | | 503 | `"Service not initialised"` | Server started without contract addresses | | 503 | `"Chain unavailable"` | Any chain or DID call failed | ## Example ```bash bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl "${PEAQOS_MCR_API_URL}/machines/42" ``` ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const response = await fetch(`${PEAQOS_MCR_API_URL}/machines/42`); const data = await response.json(); console.log(data.name, data.bond_status, data.registrations); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import requests response = requests.get(f"{PEAQOS_MCR_API_URL}/machines/42") data = response.json() print(data["name"], data["bond_status"], data["registrations"]) ``` **Response** ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "type": "peaqos:registration:v1", "name": "Machine #42", "description": "peaqOS machine", "did": "did:peaq:0xabc1230000000000000000000000000000000001", "active": true, "services": [ { "name": "web", "endpoint": "https://machine.example.com/api/data" } ], "data_visibility": "private", "documentation_url": "https://example.com/docs", "operator": "did:peaq:0xoperator0000000000000000000000000000001", "bond_status": "bonded", "event_count": 150, "registrations": [ { "type": "peaqos:registration:v1", "machineId": 42, "machineRegistry": "eip155:3338:0xIdentityRegistryAddress0000000000000001" } ] } ``` ## Related endpoints * [GET /machine/](/peaqos/api-reference/get-machine) returns the full machine profile with MCR score and data visibility-dependent fields. * [GET /mcr/](/peaqos/api-reference/get-mcr) returns the detailed MCR score, revenue trend, and revenue summary. * [GET /metadata/](/peaqos/api-reference/get-metadata) returns NFT metadata for a MachineNFT token. # GET /mcr/{did} Source: https://docs.peaq.xyz/peaqos/api-reference/get-mcr GET https://mcr.peaq.xyz/mcr/{did} Compute and return the Machine Credit Rating for a single machine DID. ## Endpoint ```http theme={"theme":{"light":"github-light-default","dark":"github-dark"}} GET /mcr/{did} ``` Returns the Machine Credit Rating (MCR) score, letter rating, bond status, event counts, revenue summary, and trend for a single machine. The server paginates the full event history in batches of 100 to compute the score. Results are cached in memory with a TTL configurable via the `MCR_CACHE_TTL` env var (defaults to 3600 seconds; set `MCR_CACHE_TTL=0` to disable caching). Responses are not cached when `mcr_degraded` is `true`: the server retries upstream FX sources on the next request rather than pinning a conservative score. ## Path parameters Machine DID (`did:peaq:0x...`) or raw EVM address (`0x...`). ## Response **200 OK** | Field | Type | Description | | :-------------------------- | :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `did` | string | The DID as supplied in the request path | | `machine_id` | integer | On-chain machine ID resolved from the DID | | `mcr_score` | integer | MCR score 0-100. `0` when the rating is `Provisioned` (not enough history to score) or `NR` (unbonded). | | `mcr` | string | Letter rating: `AAA`, `AA`, `A`, `BBB`, `BB`, `B`, `NR`, or `Provisioned` | | `mcr_degraded` | boolean | `true` if any event in the score window came from a degraded FX path (stale snapshot or upstream FX outage). Distinguishes a conservative score caused by infra degradation from a machine with no revenue history. Degraded responses bypass the cache so the next request retries fresh FX. | | `bond_status` | string | `"bonded"` or `"unbonded"` | | `negative_flag` | boolean | `true` when the AdminFlags contract has a negative-flag timestamp set for this machine (and the value is in the plausible range 2020-01-01 ≤ ts ≤ now+1 day). The MCR scoring penalty only applies while the flag is within its active window, but this field stays `true` for as long as the timestamp is recorded on-chain. | | `event_count` | integer | Total events stored on-chain for this machine | | `revenue_event_count` | integer | Number of revenue events (event\_type 0) over the machine's full event history. | | `activity_event_count` | integer | Number of activity events (event\_type 1) over the machine's full event history. | | `revenue_trend` | string | `"up"`, `"stable"`, `"down"`, or `"insufficient"` (returned when there isn't enough revenue history to compute a trend). | | `total_revenue` | integer | Sum of qualifying revenue-event values, in **USD cents**. Only events meeting the per-event qualifying threshold are summed; daily aggregation for the MCR score uses a separate qualifying threshold. | | `average_revenue_per_event` | number | Mean value per qualifying revenue event, in **USD cents**, rounded to 2 decimals. Divides only by events that meet the per-event qualifying threshold, so the divisor can be smaller than `revenue_event_count`. `0` if no qualifying events. | | `last_updated` | integer or null | Unix timestamp of the most recent event. `null` if no events exist. | ### Rating values Possible values of `mcr`: `AAA`, `AA`, `A`, `BBB`, `BB`, `B`, `NR`, or `Provisioned`. Unbonded machines return a rating of `NR` with `mcr_score: 0`. Bonded machines that have not yet accumulated enough history return a rating of `Provisioned` with `mcr_score: 0`. ## Error responses | Status | `detail` | Condition | | :----- | :---------------------------------- | :----------------------------------------------------------- | | 400 | `"Empty DID"` | Path parameter resolves to an empty string | | 400 | `"Invalid Ethereum address format"` | DID or address does not match the expected hex format | | 404 | `"Machine DID not found"` | DID address has no `machineId` attribute in the DID registry | | 404 | `"Machine not registered"` | Machine ID is not present in the IdentityRegistry contract | | 503 | `"Service not initialised"` | Server started without contract addresses | | 503 | `"Chain unavailable"` | RPC call failed during any chain read | ## Example ```bash bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl "${PEAQOS_MCR_API_URL}/mcr/did:peaq:0xabc1230000000000000000000000000000000001" ``` ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const response = await fetch( `${PEAQOS_MCR_API_URL}/mcr/did:peaq:0xabc1230000000000000000000000000000000001` ); const data = await response.json(); console.log(data.mcr_score, data.mcr); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import requests response = requests.get( f"{PEAQOS_MCR_API_URL}/mcr/did:peaq:0xabc1230000000000000000000000000000000001" ) data = response.json() print(data["mcr_score"], data["mcr"]) ``` **Response** ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "did": "did:peaq:0xabc1230000000000000000000000000000000001", "machine_id": 1, "mcr_score": 45, "mcr": "BB", "mcr_degraded": false, "bond_status": "bonded", "negative_flag": false, "event_count": 12, "revenue_event_count": 7, "activity_event_count": 5, "revenue_trend": "stable", "total_revenue": 35000, "average_revenue_per_event": 5000.0, "last_updated": 1711900000 } ``` ## Related endpoints * [GET /machine/](/peaqos/api-reference/get-machine) returns the full machine profile including data visibility-dependent fields. * [GET /operator//machines](/peaqos/api-reference/get-operator-machines) returns MCR scores for all machines under an operator. * [GET /metadata/](/peaqos/api-reference/get-metadata) returns the same profile shape as `/machine/{did}`, resolved by NFT token ID. # GET /metadata/{token_id} Source: https://docs.peaq.xyz/peaqos/api-reference/get-metadata GET https://mcr.peaq.xyz/metadata/{token_id} Return NFT metadata for a MachineNFT token, intended as an ERC-721 tokenURI target. ## Endpoint ```http theme={"theme":{"light":"github-light-default","dark":"github-dark"}} GET /metadata/{token_id} ``` Returns NFT metadata for a MachineNFT token. The server looks up the machine ID from the token ID via the MachineNFT contract, then resolves the machine owner address and builds the same response as [GET /machine/](/peaqos/api-reference/get-machine). This endpoint is designed to serve as an ERC-721 `tokenURI` target. ## Path parameters NFT token ID (not the machine ID) ## Response **200 OK** The response shape is identical to [GET /machine/](/peaqos/api-reference/get-machine). All base fields and data visibility-dependent fields apply. See that page for the full field reference. | Field | Type | Description | | :------------------------- | :------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `schema_version` | string | Always `"1.0"` | | `name` | string | `"Machine #"` when the token round-trips, or `"Machine (no NFT)"` if the on-chain NFT lookup returns 0 | | `peaqos.machine_id` | integer | On-chain machine ID resolved from the token | | `peaqos.did` | string | Machine DID derived from the owner address (`did:peaq:`) | | `peaqos.operator` | string or null | Operator DID from the DID document | | `peaqos.mcr` | string | Letter rating: `AAA`, `AA`, `A`, `BBB`, `BB`, `B`, `NR`, or `Provisioned` | | `peaqos.mcr_score` | integer | MCR score 0-100 | | `peaqos.bond_status` | string | `"bonded"` or `"unbonded"` | | `peaqos.negative_flag` | boolean | `true` when the AdminFlags contract has a negative-flag timestamp set for this machine. Stays `true` for as long as the timestamp is recorded on-chain, regardless of whether the 180-day MCR scoring penalty is still in effect. | | `peaqos.event_count` | integer | Total on-chain event count | | `peaqos.data_visibility` | string | `"private"`, `"onchain"`, or `"public"` | | `peaqos.documentation_url` | string or null | Public documentation URL from the DID document | Additional fields (`data_api`, `event_data`, `partner_data`, `partner_data_error`) depend on `data_visibility`. See [GET /machine/](/peaqos/api-reference/get-machine) for details. ## Error responses | Status | `detail` | Condition | | :----- | :-------------------------- | :--------------------------------------------------------------------- | | 404 | `"Token not found"` | Token ID does not map to any machine in the MachineNFT contract | | 404 | `"Machine not registered"` | The token's machine ID is not present in the IdentityRegistry contract | | 422 | (FastAPI validation error) | `token_id` is less than 1 or not an integer | | 503 | `"Service not initialised"` | Server started without contract addresses | | 503 | `"Chain unavailable"` | Any chain or DID call failed | ## Example ```bash bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl "${PEAQOS_MCR_API_URL}/metadata/7" ``` ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const response = await fetch(`${PEAQOS_MCR_API_URL}/metadata/7`); const data = await response.json(); console.log(data.name, data.peaqos.mcr); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import requests response = requests.get(f"{PEAQOS_MCR_API_URL}/metadata/7") data = response.json() print(data["name"], data["peaqos"]["mcr"]) ``` **Response** ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "schema_version": "1.0", "name": "Machine #7", "peaqos": { "machine_id": 42, "did": "did:peaq:0xabc1230000000000000000000000000000000001", "operator": "did:peaq:0xoperator0000000000000000000000000000001", "mcr": "B", "mcr_score": 30, "bond_status": "bonded", "negative_flag": false, "event_count": 5, "data_visibility": "onchain", "documentation_url": null, "event_data": [ { "event_type": 0, "origin_value": 1500, "timestamp": 1711900000, "trust_level": 1, "metadata": {}, "origin_currency": "USD", "origin_subunit": 100, "usd_value": 1500, "usd_subunit": 100, "amount_status": "ok" } ] } } ``` ## Related endpoints * [GET /machine/](/peaqos/api-reference/get-machine) returns the same response shape, resolved by DID instead of token ID. * [GET /mcr/](/peaqos/api-reference/get-mcr) returns a focused MCR response with revenue trend and summary. * [GET /machines/](/peaqos/api-reference/get-machine-card) returns the peaqOS Machine Card resolved by numeric machine ID. # GET /operator/{did}/machines Source: https://docs.peaq.xyz/peaqos/api-reference/get-operator-machines GET https://mcr.peaq.xyz/operator/{did}/machines List all machines registered under an operator DID, with paginated MCR scores. ## Endpoint ```http theme={"theme":{"light":"github-light-default","dark":"github-dark"}} GET /operator/{did}/machines ``` Returns a paginated list of machines registered under an operator DID, with a full MCR score for each machine. The MCR score is computed from the machine's full event history (not bond-only). Machines that are not registered in the IdentityRegistry contract are silently excluded. The server reads the operator's `machines` DID attribute, truncates the list to the first 100 entries as a defensive bound, then paginates over that window. Operators with more than 100 registered machines surface only the first 100 here. Query the IdentityRegistry contract directly to enumerate the full set. ## Path parameters Operator DID (`did:peaq:0x...`) or raw EVM address (`0x...`) ## Query parameters Number of machines to skip before the returned page. Must be `>= 0`. Maximum number of machines to return per page. Range `1-20`. ## Response **200 OK** | Field | Type | Description | | :------------------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `operator_did` | string | The operator DID as supplied in the request path | | `machines` | array | List of machines registered to this operator (for the current page) | | `machines[].did` | string | Machine DID derived from the machine wallet address (`did:peaq:`) | | `machines[].machine_id` | integer | On-chain machine ID | | `machines[].mcr_score` | integer | MCR score 0-100 | | `machines[].mcr` | string | Letter rating: `AAA`, `AA`, `A`, `BBB`, `BB`, `B`, `NR`, or `Provisioned` | | `machines[].negative_flag` | boolean | `true` when the AdminFlags contract has a negative-flag timestamp set for this machine. Stays `true` regardless of whether the 180-day MCR scoring penalty is still active. | | `pagination` | object | Pagination metadata | | `pagination.offset` | integer | The offset used for this page | | `pagination.limit` | integer | The limit used for this page | | `pagination.total` | integer | Number of valid machine IDs in the operator's `machines` DID attribute, after the 100-entry cap and integer validation but before registration filtering. Page slices may be smaller than `limit` because unregistered machines are dropped silently. | An operator with no machines returns `200` with an empty `machines` array and `pagination.total` of `0`. ## Error responses | Status | `detail` | Condition | | :----- | :---------------------------------- | :---------------------------------------------------- | | 400 | `"Empty DID"` | Path parameter resolves to an empty string | | 400 | `"Invalid Ethereum address format"` | DID or address does not match the expected hex format | | 422 | (FastAPI validation error) | `offset` or `limit` fails validation constraints | | 503 | `"Service not initialised"` | Server started without contract addresses | | 503 | `"Chain unavailable"` | Any chain or DID call failed | ## Example ```bash bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl "${PEAQOS_MCR_API_URL}/operator/did:peaq:0xoperator0000000000000000000000000000001/machines?offset=0&limit=10" ``` ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const response = await fetch( `${PEAQOS_MCR_API_URL}/operator/did:peaq:0xoperator0000000000000000000000000000001/machines?offset=0&limit=10` ); const data = await response.json(); console.log(data.machines.length, data.pagination.total); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import requests response = requests.get( f"{PEAQOS_MCR_API_URL}/operator/did:peaq:0xoperator0000000000000000000000000000001/machines", params={"offset": 0, "limit": 10}, ) data = response.json() print(len(data["machines"]), data["pagination"]["total"]) ``` **Response** ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "operator_did": "did:peaq:0xoperator0000000000000000000000000000001", "machines": [ { "did": "did:peaq:0xabc1230000000000000000000000000000000001", "machine_id": 1, "mcr_score": 45, "mcr": "BB", "negative_flag": false }, { "did": "did:peaq:0xdef4560000000000000000000000000000000002", "machine_id": 2, "mcr_score": 0, "mcr": "NR", "negative_flag": false } ], "pagination": { "offset": 0, "limit": 10, "total": 2 } } ``` ## Related endpoints * [GET /mcr/](/peaqos/api-reference/get-mcr) returns detailed MCR data for a single machine, including revenue trend and summary. * [GET /machine/](/peaqos/api-reference/get-machine) returns the full machine profile with data visibility-dependent fields. # GET /health and GET /ready Source: https://docs.peaq.xyz/peaqos/api-reference/health GET https://mcr.peaq.xyz/health Liveness and readiness probes for monitoring the peaqOS MCR API server. ## GET /health ```http theme={"theme":{"light":"github-light-default","dark":"github-dark"}} GET /health ``` A lightweight liveness probe. Returns `200` whenever the process is running. Does not check chain connectivity or contract readiness. ### Response **200 OK** | Field | Type | Description | | :------- | :----- | :---------------------------------------- | | `status` | string | Always `"ok"` when the process is running | ### Error responses None. This endpoint always returns `200` while the server process is alive. ### Example ```bash bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl "${PEAQOS_MCR_API_URL}/health" ``` ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const response = await fetch(`${PEAQOS_MCR_API_URL}/health`); const data = await response.json(); console.log(data.status); // "ok" ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import requests response = requests.get(f"{PEAQOS_MCR_API_URL}/health") data = response.json() print(data["status"]) # "ok" ``` **Response** ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "status": "ok" } ``` *** ## GET /ready ```http theme={"theme":{"light":"github-light-default","dark":"github-dark"}} GET /ready ``` A readiness probe. Verifies that the RPC node is reachable and all six contracts (IdentityRegistry, IdentityStaking, EventRegistry, MachineNFT, DID registry, AdminFlags) are callable. Returns `200` when all checks pass and `503` when any check fails. ### Response **200 OK (all contracts ready)** | Field | Type | Description | | :---------------------------- | :------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `status` | string | `"ready"` | | `rpc_connected` | boolean | `true` when the RPC node is reachable | | `contracts.identity_registry` | boolean | IdentityRegistry contract callable | | `contracts.identity_staking` | boolean | IdentityStaking contract callable | | `contracts.event_registry` | boolean | EventRegistry contract callable | | `contracts.machine_nft` | boolean | MachineNFT contract callable | | `contracts.admin_flags` | boolean | AdminFlags contract callable. **Returns `true` when the contract address is not configured.** AdminFlags is optional, so the readiness probe skips the call and reports ready. | | `contracts.did_registry` | boolean | DID precompile callable | **503 Service Unavailable (one or more checks failed)** The response body has the same shape as the 200 response, but `status` is `"not_ready"` and one or more boolean fields are `false`. | Status | Condition | | :----- | :----------------------------------------------------------------- | | 503 | Any contract unreachable, RPC failure, or services not initialised | Two failure modes produce a 503: * **Services not initialised** (server started without `IDENTITY_REGISTRY_ADDRESS`): every field is `false`, including `rpc_connected` and `contracts.admin_flags`. * **Initialised but a check failed** (RPC down, contract unreachable, etc.): only the failing fields are `false`. When `ADMIN_FLAGS_ADDRESS` is unset, `contracts.admin_flags` is **`true`**: AdminFlags is optional, the probe skips it, and that field never blocks readiness. ### Example ```bash bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl "${PEAQOS_MCR_API_URL}/ready" ``` ```javascript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const response = await fetch(`${PEAQOS_MCR_API_URL}/ready`); const data = await response.json(); if (response.ok) { console.log("Service ready"); } else { console.log("Not ready:", data.contracts); } ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import requests response = requests.get(f"{PEAQOS_MCR_API_URL}/ready") data = response.json() if response.status_code == 200: print("Service ready") else: print("Not ready:", data["contracts"]) ``` **Response (healthy)** ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "status": "ready", "rpc_connected": true, "contracts": { "identity_registry": true, "identity_staking": true, "event_registry": true, "machine_nft": true, "admin_flags": true, "did_registry": true } } ``` **Response (not ready)** ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "status": "not_ready", "rpc_connected": false, "contracts": { "identity_registry": false, "identity_staking": false, "event_registry": false, "machine_nft": false, "admin_flags": false, "did_registry": false } } ``` ## Related endpoints * [API reference overview](/peaqos/api-reference/overview) lists all available endpoints and common error patterns. # API reference overview Source: https://docs.peaq.xyz/peaqos/api-reference/overview Base URL, authentication, rate limits, error envelope, and endpoint index for the peaqOS MCR API. The peaqOS MCR API is a read-only HTTP/JSON service that exposes Machine Credit Ratings, machine profiles, operator fleet data, NFT metadata, and Machine Cards. All data originates from on-chain contracts on peaq chain. The API does not perform writes; write operations happen via the [peaqOS SDK](/peaqos/sdk-reference/sdk-js) and smart contracts. ## Quick start Pick the setup that matches your workflow: an AI-driven flow via the peaqOS skill, or direct HTTP calls. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install peaq-os-cli npx @peaqos/skills add peaqos ``` Auto-detects Claude Code, Cursor, or Windsurf. Then invoke `/peaqos` in Claude Code and ask for an MCR score, machine profile, or operator fleet — the skill picks the right CLI command and the CLI hits this API for you. To target a specific runtime, add `--agent claude-code | cursor | windsurf` — see the [peaqOS AI page](/peaqos/peaqos-ai). ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl https://mcr.peaq.xyz/mcr/did:peaq:0xabc123... ``` Endpoint index below. No API key required: MCR data is public. **Coming soon.** ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/mcr/query \ peaq_ros2_interfaces/srv/PeaqosQueryMcr \ "{did: 'did:peaq:'}" ``` `peaq_ros2_peaqos` exposes the MCR API as ROS 2 services. Full reference on [SDK: ROS 2](/peaqos/sdk-reference/ros2/overview); MCR-specific services are listed under [Services → MCR queries](/peaqos/sdk-reference/ros2/services#mcr-queries). ## Base URL Set the environment variable `PEAQOS_MCR_API_URL` to the root of the MCR API server: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export PEAQOS_MCR_API_URL=https://mcr.peaq.xyz ``` For local development against a self-hosted server, use `http://127.0.0.1:8000`. All endpoint paths in this reference are relative to that base URL. ## Authentication The MCR API is a **public read API**. No API keys, tokens, or signatures are required. MCR scores, machine profiles, and metadata are public on-chain data. ## Rate limits The API enforces a limit of **90 requests per minute per IP address**. Requests that exceed the limit receive a `429 Too Many Requests` response. For production deployments with multiple workers, the rate limiter backend can be swapped to Redis. ## Server configuration When self-hosting the MCR API, these environment variables tune caching, storage, and test-mode behavior. Typical deployments only need the contract addresses and RPC; the rest have sensible defaults. | Variable | Default | Purpose | | :-------------------------- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `IDENTITY_REGISTRY_ADDRESS` | n/a | Primary contract address. When empty, the server boots in test mode: `/ready` returns 503 with `rpc_connected: false` and all `contracts.*` (including `admin_flags`) reported as `false`. | | `ADMIN_FLAGS_ADDRESS` | n/a | Optional `AdminFlags` contract. Influences MCR responses via `negative_flag` and admin trust overrides. When the service is initialised but this address is unset, `contracts.admin_flags` on `/ready` is **`true`**: the probe skips the optional check and the service stays ready. | | `MCR_CACHE_TTL` | `3600` | MCR response cache TTL in seconds. Set to `0` to disable caching entirely. | | `EVENT_STORE_DB_PATH` | `/tmp/peaqos_events.db` | SQLite path for the delta-sync event cache. Must resolve under `/tmp`, `/data`, or `/var/lib/peaqos`. | | `SKIP_PROVISIONED_GATE` | `false` | Dev/demo toggle that short-circuits the "must be bonded" gate on scoring. **Never enable in production.** | ## Error envelope Every error response uses the same JSON shape: ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "detail": "" } ``` The `detail` string describes the cause. Common values: | `detail` value | Meaning | | :---------------------------------- | :----------------------------------------------------------- | | `"Service not initialised"` | Server started without contract addresses (test mode) | | `"Chain unavailable"` | RPC call to the node or a contract failed | | `"Empty DID"` | DID path parameter is empty | | `"Invalid Ethereum address format"` | DID or address does not match `^0x[a-fA-F0-9]{40}$` | | `"Machine DID not found"` | DID address has no `machineId` attribute in the DID registry | | `"Machine not registered"` | Machine ID is not present in the IdentityRegistry contract | | `"Machine not found"` | Machine ID does not exist (machines endpoint) | | `"Machine wallet not found"` | Machine wallet is the zero address | | `"Token not found"` | NFT token ID does not map to any machine in MachineNFT | | `"Internal server error"` | Unhandled exception (logged server-side) | ## Status codes | Code | Meaning | | :-------------------------- | :------------------------------------------------------------------------------------------------------------------------- | | `200 OK` | Request succeeded; JSON body returned | | `400 Bad Request` | DID is empty or the address does not match `^0x[a-fA-F0-9]{40}$` | | `404 Not Found` | The requested DID, machine ID, or token ID does not exist on-chain | | `422 Unprocessable Entity` | Path or query parameter failed validation (FastAPI auto-generated) | | `429 Too Many Requests` | Rate limit exceeded. Responses include slowapi headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`) | | `500 Internal Server Error` | Unhandled server exception | | `503 Service Unavailable` | Service not initialised or chain/RPC call failed | ## DID format Endpoints that accept a DID path parameter support two formats: * Full DID: `did:peaq:0xabc123...` * Raw address: `0xabc123...` The server strips the `did:peaq:` prefix internally before querying the chain. ## Endpoints Machine Credit Rating score, rating, bond status, event counts, and revenue trend for a single machine. Full machine profile with data visibility-dependent fields (private, onchain, or public). Paginated list of machines registered under an operator, with per-machine MCR scores. NFT metadata for a MachineNFT token. Same response shape as /machine/. peaqOS Machine Card for a machine, including services, registrations, and operator info. Liveness and readiness probes for health checks and monitoring. # peaqOS CLI Source: https://docs.peaq.xyz/peaqos/cli Drive activate, qualify, and operator flows from your terminal. The peaqOS CLI wraps the SDK into a terminal surface for the most common machine flows: registering a machine, submitting events, querying credit ratings, and managing proxy-operator fleets. It's the same on-chain path as the [JS](/peaqos/sdk-reference/sdk-js) and [Python](/peaqos/sdk-reference/sdk-python) SDKs, scripted. ## Install ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install peaq-os-cli peaqos --version ``` Python 3.10+ required. Ships to PyPI as `peaq-os-cli` and exposes the `peaqos` command. Wraps the [Python SDK](/peaqos/sdk-reference/sdk-python): same on-chain path, scripted. `peaqos --version` prints both the CLI and the underlying `peaq_os_sdk` versions on a single line. Global flags `-v` / `--verbose` and `-q` / `--quiet` toggle DEBUG and ERROR-only logging respectively (mutually exclusive; logs go to stderr). ## Configure The CLI reads the same environment variables as the SDKs. Full table on [Install](/peaqos/install#environment-variables). ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export PEAQOS_RPC_URL=https://peaq.api.onfinality.io/public export PEAQOS_PRIVATE_KEY=0x... # Defaults to http://127.0.0.1:8000 (self-hosted MCR). # Set to the public peaq MCR for mainnet reads. export PEAQOS_MCR_API_URL=https://mcr.peaq.xyz ``` Other RPC endpoints are available. See [Public RPC endpoints](/peaqos/install#public-rpc-endpoints). Or scaffold a `.env` interactively with `peaqos init`. ## Commands ### `peaqos init` Interactive wizard that scaffolds a `.env` with the required peaqOS variables. Prompts for network, private key source (`paste`, `generate`, or `wallet`), RPC URL, MCR API URL, Gas Station URL, and Event Registry address. The `wallet` path creates a new OWS vault wallet and writes `PEAQOS_OWS_WALLET=` instead of `PEAQOS_PRIVATE_KEY`. Writes `.env` with `0o600` permissions and auto-runs `whoami` to verify. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaqos init peaqos init --force # overwrite existing .env without prompt peaqos init --non-interactive # read all values from env vars ``` ### `peaqos whoami` Read-only command that prints the signing address, network, chain ID, RPC + MCR API URLs, and all peaqOS contract addresses the CLI is configured with. Useful as a sanity check after `init`. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaqos whoami ``` ### `peaqos activate` Register a machine on peaq end-to-end: balance check, Gas Station funding (when 2FA is configured), `register_machine`, mint the Machine NFT, write DID attributes. Idempotent: safe to re-run if a step fails. Mirrors the [Activate](/peaqos/functions/activate) flow. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Self-managed: caller's own key drives every step peaqos activate --doc-url "https://example.com/docs" --data-api "https://example.com/events" # Proxy-managed: caller activates on behalf of a machine EOA that signs its own DID writes peaqos activate --for 0xMachineAddress --machine-key ./machine.key --doc-url "https://example.com/docs" --data-api "https://example.com/events" ``` #### Flags | Flag | Required | Meaning | | :--------------- | :------- | :------------------------------------------------------------------------------ | | `--for` | no | Machine EOA address. Presence switches to proxy mode; requires `--machine-key`. | | `--machine-key` | no | Path to a file containing the machine's `0x`-prefixed hex private key. | | `--doc-url` | yes | Documentation URL written to the machine DID. | | `--data-api` | yes | Raw data API URL written to the machine DID. | | `--visibility` | no | Data visibility: `public` (default), `private`, or `onchain`. | | `--skip-funding` | no | Skip steps 1–3 (balance check, 2FA enrollment, Gas Station funding). | Private keys must be supplied via file path (`--machine-key`). Inline key flags are intentionally unsupported: reading from a file keeps the key out of shell history and `ps` output. **Proxy preconditions.** Proxy mode requires the operator to already be registered on `IdentityRegistry` (i.e. have run `peaqos activate` in self mode first). If not, the command exits `2` before spending any gas. #### Output streams The final summary (`Machine ID`, `Token ID`, `Machine DID`; plus `Machine Address` and `Operator DID` in proxy mode) goes to **stdout** so it can be piped or captured. Progress lines (`[1/6]`, `[2/6]`, …) and per-step info/warning messages go to **stderr**. #### Idempotent rerun Every mutating step does a read-before-write precheck. Re-running `peaqos activate` against state that's already complete submits no transactions and exits `0`. A TOCTOU revert (`AlreadyRegistered` / `AlreadyExists`) is recovered as a skip rather than surfaced as a network error. #### `peaqos.log` Every step appends a JSONL entry to `./peaqos.log` (`0o600`) in the working directory: a resume marker for partial failures and the audit trail. ```jsonl theme={"theme":{"light":"github-light-default","dark":"github-dark"}} {"address":"0xDC5b...","machine_id":42,"mode":"self","status":"confirmed","step":"register","ts":"2026-04-23T15:00:18.482917+00:00"} {"machine_id":42,"recipient":"0xDC5b...","status":"confirmed","step":"mint_nft","token_id":11,"tx_hash":"0xminttx...","ts":"..."} {"machine_did_tx_count":6,"machine_id":42,"mode":"self","status":"confirmed","step":"machine_did","token_id":11,"tx_hash":"0xdidtx...","ts":"..."} ``` Keys are alphabetically sorted on disk (`json.dumps(..., sort_keys=True)`); `ts` is the UTC `datetime.isoformat()` (`+00:00` suffix, microseconds included) and sits where the alphabetic order puts it on each line (last for most rows). `status` is one of `pending` / `skipped` / `failed` / `confirmed`. On rerun, the same rows reappear as `"status":"skipped"` and, for DID writes, with `"recovered_from":"AttributeAlreadyOnChain"`. Fund-step retries with the same `request_id` carry `"resumed_from":"pending"` on the new `pending` row instead. ### `peaqos qualify event` Submit a revenue or activity event to the EventRegistry. Wraps [`submit_event`](/peaqos/sdk-reference/sdk-python#submit_event). See the [Submit events guide](/peaqos/guides/submit-events) for trust levels and cross-chain patterns. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # HK$1.23 (123 cents) revenue event peaqos qualify event \ --machine-id 42 \ --type revenue \ --value 123 \ --ts 2026-04-23T12:00:00Z \ --trust self \ --source-chain peaq ``` `--value` is an ISO 4217 minor-unit integer: cents for USD/HKD, whole units for JPY/KRW/VND. `HK$1.23 → --value 123`, `¥100 → --value 100`. The CLI defaults `--currency` to `USD` for revenue events and to `""` for activity events; pass `--currency` explicitly to override. The MCR API converts the value to USD using FX at the event timestamp. | Flag | Required | Meaning | | :--------------- | :---------- | :--------------------------------------------------------------------------------------------------------------- | | `--machine-id` | yes | Positive integer machine ID. | | `--type` | yes | `revenue` or `activity`. | | `--value` | yes | Non-negative integer in the currency's minor unit. | | `--ts` | yes | Unix seconds or ISO 8601 with timezone (`Z` or `+hh:mm`). Must be on or before block time. | | `--currency` | no | ISO 4217 code, 3-10 uppercase alphanumerics (`^[A-Z0-9]{3,10}$`). Default: `USD` for revenue, `""` for activity. | | `--trust` | no | `self` (default), `onchain`, or `hardware`. | | `--source-chain` | no | `same` (default), `peaq`, or `base`. | | `--source-tx` | conditional | 32-byte hex tx hash. **Required** when `--trust onchain`. | | `--raw-data` | no | Path to a file; bytes are hashed and stored as the event data hash. | | `--metadata` | no | Path to a file; bytes are attached as on-chain metadata. Absent = empty bytes. | Human output on success: ``` Event submitted. Machine ID: 42 Type: revenue Value: 123 Trust: self-reported Tx: 0x3f4a8c1e2d9b7f05a6c3e8d1f4b2a7c9e0d5f3b1a8e2c6d9f7b4a1e3c5d8f2b4 Data Hash: 0xa1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890 ``` ### `peaqos qualify mcr` Fetch a machine's credit rating from the [MCR API](/peaqos/api-reference/get-mcr). Wraps [`query_mcr`](/peaqos/sdk-reference/sdk-python#query_mcr). ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaqos qualify mcr did:peaq:0x9a5F1E244c15e491Ae571c5bF77fDD836ddc37C5 peaqos qualify mcr did:peaq:0x9a5F... --json ``` The DID must match `did:peaq:0x` plus 40 hex characters. `--json` emits the raw SDK response on stdout with no banner or prose: useful for `jq` and scripting. Human output: ``` MCR for did:peaq:0x9a5F1E244c15e491Ae571c5bF77fDD836ddc37C5 Rating: A Score: 82 / 100 Bond Status: bonded Events: Total: 150 Revenue: 120 Activity: 30 30-Day Revenue: up Last Updated: 2026-04-20T14:30:00Z FX Degraded: no ``` `revenue_trend` is one of `up`, `stable`, `down`, or `insufficient` (returned when there isn't enough revenue history to compute a trend). `FX Degraded:` reflects the top-level `mcr_degraded` field — `yes` when one or more scored events used a stale or unavailable FX snapshot, `no` otherwise. `--json` output (raw SDK `MCRResponse`): ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "did": "did:peaq:0x9a5F1E244c15e491Ae571c5bF77fDD836ddc37C5", "machine_id": 42, "mcr_score": 82, "mcr": "A", "mcr_degraded": false, "bond_status": "bonded", "negative_flag": false, "event_count": 150, "revenue_event_count": 120, "activity_event_count": 30, "revenue_trend": "up", "total_revenue": 1542075, "average_revenue_per_event": 12851, "last_updated": 1745152200 } ``` ### `peaqos show machine` Fetch a full machine profile: Machine ID, operator, DID attributes, MCR snapshot, recent events. Wraps [`query_machine`](/peaqos/sdk-reference/sdk-python#query_machine). ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaqos show machine did:peaq:0xabc1230000000000000000000000000000000001 peaqos show machine did:peaq:0xabc... --json ``` ### `peaqos show operator machines` List the machines registered under a proxy operator, with MCR per machine. Wraps [`query_operator_machines`](/peaqos/sdk-reference/sdk-python#query_operator_machines). ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaqos show operator machines did:peaq:0xProxyAddress peaqos show operator machines did:peaq:0xProxyAddress --json ``` ### `peaqos wallet` Manage OWS-format wallets in the local encrypted vault at `~/.ows/wallets/`. Requires the `[ows]` extra: `pip install peaq-os-cli[ows]`. The vault passphrase is read from `OWS_PASSPHRASE` when set, otherwise prompted interactively. Subcommands wrap the SDK static helpers documented on [Wallets (OWS)](/peaqos/wallets). | Subcommand | Purpose | | :--------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `peaqos wallet create [--words 12\|24] [--json]` | Generate a new BIP-39 mnemonic and derive accounts for every supported chain. The mnemonic is not displayed; back up via `wallet export`. | | `peaqos wallet import --mnemonic [--index N] [--json]` | Import from an existing BIP-39 mnemonic (hidden prompt). `--index` selects the derivation index (default `0`). | | `peaqos wallet import --private-key-file [--json]` | Import a raw EVM private key from file. peaq-only; other chains synthesize on read where possible. | | `peaqos wallet list [--json]` | Compact table of all wallets: Name, ID, peaq Address, Key Type, Created. | | `peaqos wallet show [--json]` | Wallet metadata plus the full multi-chain address table. | | `peaqos wallet export ` | Reveal the recovery phrase / private key. Requires interactive confirmation. Secret prints to stdout; warning prints to stderr. | | `peaqos wallet delete ` | Securely delete a wallet (file overwritten with random bytes before unlink). Requires confirmation. | | `peaqos wallet use ` | Set the active wallet. Writes `PEAQOS_OWS_WALLET=` into `.env` in the current directory. Subsequent commands that call `load_client()` will sign with this wallet. | When `PEAQOS_OWS_WALLET` is set, `load_client()` resolves the wallet from the vault using `OWS_PASSPHRASE` and skips `PEAQOS_PRIVATE_KEY` entirely. If both are set, the wallet wins. ## Exit codes Every subcommand funnels SDK and network exceptions through a single error handler that raises with a stable exit code. | Exit code | Meaning | | :-------- | :----------------------------------------------------------------- | | `0` | Success | | `1` | User / validation error (bad flag, invalid DID, cap or rate limit) | | `2` | Network, RPC, or on-chain error (connection failure, HTTP, revert) | | `3` | Configuration error (missing env var, invalid private key file) | ## See also The peaqOS agent skill that drives these CLI flows from any AI agent. The TypeScript / Python API the CLI wraps. The MCR API that `get-mcr`, `get machine`, and `operator machines` hit. # Smart contracts Source: https://docs.peaq.xyz/peaqos/concepts/contracts Architecture, addresses, and responsibilities of every peaqOS contract. peaqOS runs on four on-chain layers. The core registry and staking contracts live on peaq chain as UUPS upgradeable proxies; bridging is LayerZero V2 ONFT; smart accounts follow ERC-4337; and low-level DID, batch, and WPEAQ operations are peaq chain precompiles. ## Architecture ``` Core (peaq chain, UUPS proxies) IdentityRegistry ──owns──► IdentityStaking │ ▲ │ (gates on) │ stakeFor() └─────► EventRegistry │ └─────► MachineNFT Cross-chain (peaq ↔ Base, LayerZero V2) MachineNFTAdapter (peaq) ◄──► MachineNFTBase (Base) ERC-4337 (peaq chain) MachineAccountFactory ──deploys──► MachineSmartAccount (BeaconProxy) Precompiles (peaq chain, fixed addresses) DID (0x...800) · Batch (0x...805) · WPEAQ (0x...809) ``` * Core contracts use UUPS upgradeable proxies (OpenZeppelin 5.x) with ERC-7201 namespaced storage. Future upgrades preserve state. * Identity NFT (minted by IdentityRegistry) and Machine NFT (minted by MachineNFT) are separate ERC-721 spaces with independent `tokenId` sequences linked by `machineId`. See [Machine NFT](/peaqos/concepts/machine-nft#ownership-semantics). * IdentityRegistry implements **ERC-8004** for DID-anchored machine metadata; the JSON Machine Card is served by the MCR API. ## peaq mainnet addresses Set these in your environment. The SDK reads them from `fromEnv()` / `from_env()`. ### Core | Contract | Variable | Address | | :--------------- | :-------------------------- | :------------------------------------------- | | IdentityRegistry | `IDENTITY_REGISTRY_ADDRESS` | `0xb53Af985765031936311273599389b5B68aC9956` | | IdentityStaking | `IDENTITY_STAKING_ADDRESS` | `0x11c05A650704136786253e8685f56879A202b1C7` | | EventRegistry | `EVENT_REGISTRY_ADDRESS` | `0x43c6c12eecAf4fB3F164375A9c44f8a6Efc139b9` | | MachineNFT | `MACHINE_NFT_ADDRESS` | `0x2943F80e9DdB11B9Dd275499C661Df78F5F691F9` | ### Precompiles | Contract | Variable | Address | | :------- | :------------------------- | :------------------------------------------- | | DID | `DID_REGISTRY_ADDRESS` | `0x0000000000000000000000000000000000000800` | | Batch | `BATCH_PRECOMPILE_ADDRESS` | `0x0000000000000000000000000000000000000805` | | WPEAQ | n/a | `0x0000000000000000000000000000000000000809` | ### Optional | Contract | Variable | Address | Needed for | | :-------------------- | :-------------------------------- | :------------------------------------------- | :----------------------------------------------------------- | | MachineAccountFactory | `MACHINE_ACCOUNT_FACTORY_ADDRESS` | `0x4A808d5A90A2c91739E92C70aF19924e0B3D527f` | `deploySmartAccount` / `getSmartAccountAddress` | | MachineNFTAdapter | `MACHINE_NFT_ADAPTER_ADDRESS` | `0x9AD5408702EC204441A88589B99ADfC2514AFAE6` | `bridgeNft` from peaq | | AdminFlags | `ADMIN_FLAGS_ADDRESS` | `0x1c5f33fBEE6BA38ed9bDE247C1Ba89A2116C25f1` | MCR API server (negative-flag reads + admin trust overrides) | ## Base mainnet addresses Needed when bridging **into** peaq from Base. Pass as `baseNftAddress` / `base_nft_address` to `bridgeNft` / `bridge_nft`. | Contract | Address | | :------------------------------ | :------------------------------------------- | | MachineNFTBase (LayerZero ONFT) | `0xee8A521eA434b11F956E2402beC5eBfa753Babfa` | ## Agung testnet addresses Use these for development on agung. The contract surface matches mainnet; only the deployed addresses differ. ### Core | Contract | Variable | Address | | :--------------- | :-------------------------- | :------------------------------------------- | | IdentityRegistry | `IDENTITY_REGISTRY_ADDRESS` | `0x9E9463a65c7B74623b3b6Cdc39F71be7274e5971` | | IdentityStaking | `IDENTITY_STAKING_ADDRESS` | `0x55f336714aDb0749DbFE33b057a1702405564E3d` | | EventRegistry | `EVENT_REGISTRY_ADDRESS` | `0x2DAD8905380993940e340C5cE6d313d5c2780040` | | MachineNFT | `MACHINE_NFT_ADDRESS` | `0xB41C2A4f1c19b6B06beaAce0F5CD8439e77C4b1c` | ### Precompiles Identical to mainnet: same fixed addresses on every peaq runtime. | Contract | Variable | Address | | :------- | :------------------------- | :------------------------------------------- | | DID | `DID_REGISTRY_ADDRESS` | `0x0000000000000000000000000000000000000800` | | Batch | `BATCH_PRECOMPILE_ADDRESS` | `0x0000000000000000000000000000000000000805` | | WPEAQ | n/a | `0x0000000000000000000000000000000000000809` | ### Optional | Contract | Variable | Address | Needed for | | :-------------------- | :-------------------------------- | :------------------------------------------- | :----------------------------------------------------------- | | MachineAccountFactory | `MACHINE_ACCOUNT_FACTORY_ADDRESS` | `0x65a4DfEB799dFf8CF15f13816d648a7805d6b1F9` | `deploySmartAccount` / `getSmartAccountAddress` | | MachineNFTAdapter | `MACHINE_NFT_ADAPTER_ADDRESS` | `0x63fD7e64A38e50D1486Bc569B4CaCeD38528De22` | `bridgeNft` from peaq | | AdminFlags | `ADMIN_FLAGS_ADDRESS` | `0x4181a2Aa34aFb247450FfcBd65be5aBD4Cbee658` | MCR API server (negative-flag reads + admin trust overrides) | **Bridging cannot be exercised on agung.** `MachineNFTAdapter` is deployed, but LayerZero V2 has no DVN routes between agung and Base. `bridgeNft` / `bridge_nft` calls will not relay end-to-end. Exercise the bridge on peaq mainnet ↔ Base mainnet only. ## Core contracts ### IdentityRegistry Central machine identity registry. Mints an ERC-721 Identity NFT to each machine on registration and orchestrates bonding through `IdentityStaking.stakeFor()`. Implements **ERC-8004** so that each Identity NFT's `tokenURI` resolves to the machine's DID-anchored Machine Card. DID attributes themselves live on the peaq DID precompile (W3C DID), written by the SDK via `writeMachineDIDAttributes` and read by the MCR API. Tracks per-machine status (`None` → `Pending` → `Verified` / `Rejected` / `Deactivated`). Supports self-registration and proxy registration. **SDK methods:** `registerMachine`, `registerFor` **Key state:** `minBond` (currently 1 PEAQ), `nextMachineId`, `operatorOf`, `machineStatus` ### IdentityStaking Bond token storage. Tokens are staked at registration time via `stakeFor()` and held permanently; no withdrawal path is exposed. Only authorized callers (IdentityRegistry) can initiate stakes. Supports pause / unpause by owner. **SDK interaction:** Indirect. `registerMachine` / `registerFor` route the bond through here automatically. ### EventRegistry On-chain store for revenue (type `0`) and activity (type `1`) events. Gates event submission on (a) the machine being registered in IdentityRegistry, and (b) the machine being bonded in IdentityStaking. Authorization rule: `msg.sender` must be the machine wallet or the operator. Stores a `keccak256` hash of the raw data; payloads stay off-chain. **SDK methods:** `submitEvent`, `batchSubmitEvents` **Concept:** [Events](/peaqos/concepts/events) ### MachineNFT LayerZero V2 ONFT representing a machine's financial profile. Minted in a separate `mintNft` call after registration: the Machine NFT `tokenId` is independent from the Identity NFT `tokenId`. `tokenURI` resolves to the MCR API's `/metadata/{token_id}` endpoint, which returns the Machine Card. **SDK methods:** `mintNft`, `tokenIdOf` **Concept:** [Machine NFT](/peaqos/concepts/machine-nft) ## Cross-chain contracts ### MachineNFTAdapter (peaq) Wraps `MachineNFT` for LayerZero V2 bridging. Lock/unlock pattern: locks the NFT on peaq while the mirror exists on the destination chain. Needed only when bridging **from** peaq. **SDK methods:** `bridgeNft` / `bridge_nft` when `source` is `"peaq"` ### MachineNFTBase (Base) Standard `ONFT721` on Base. Burn/mint pattern: NFT is burned when bridged back to peaq. Needed when bridging **from** Base. **SDK methods:** `bridgeNft` / `bridge_nft` when `source` is `"base"` (pass the address as `baseNftAddress` / `base_nft_address`) ## ERC-4337 smart accounts ### MachineAccountFactory CREATE2 factory for deploying `MachineSmartAccount` BeaconProxy instances. Standard ERC-4337 flow: smart accounts run through the canonical EntryPoint at `0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789` (same address across all EVM networks). **SDK methods:** `deploySmartAccount` / `deploy_smart_account`, `getSmartAccountAddress` / `get_smart_account_address` ### MachineSmartAccount The shared BeaconProxy implementation behind deployed smart accounts. Handles owner/machine RBAC and broad machine execution authority. Users never interact with the implementation address directly; calls go to the deployed proxy. ## AdminFlags Optional peaq-chain contract read by the MCR API server (not the SDK). Holds admin-set flags that modify MCR responses: per-machine `negative_flag`s and [trust-level](/peaqos/concepts/trust-levels) overrides. MCR consumers see the adjusted score in their API response. When the contract isn't configured, `contracts.admin_flags` on [`GET /ready`](/peaqos/api-reference/health) returns `false` and the service serves unmodified MCR. See [API overview](/peaqos/api-reference/overview#server-configuration). ## Precompiles (peaq chain) | Precompile | Address | Purpose | | :--------- | :------------------------------------------- | :-------------------------------------------------------------------------------------------- | | DID | `0x0000000000000000000000000000000000000800` | Read/write DID attributes. Used for Machine Card metadata and proxy-operator fleet attributes | | Batch | `0x0000000000000000000000000000000000000805` | Atomic multi-call for bonding (registration + stake) and proxy DID writes | | WPEAQ | `0x0000000000000000000000000000000000000809` | Wrapped PEAQ used internally by IdentityStaking as the staking token | ## LayerZero endpoints Used by the bridge adapters. You don't set these directly; the SDK handles them. | Network | LayerZero V2 endpoint | EID | | :----------- | :------------------------------------------- | :------ | | peaq mainnet | `0x6F475642a6e85809B1c36Fa62763669b1b48DD5B` | `30302` | | Base mainnet | `0x1a44076050125825900e736c501f859c50fE728c` | `30184` | The SDK exports the EIDs as `LAYER_ZERO_EIDS` (JS) / `LAYERZERO_EIDS` (Python) for reference. ## Upgrade pattern All core contracts (IdentityRegistry, IdentityStaking, EventRegistry, MachineNFT) use OpenZeppelin **UUPS upgradeable proxies** with ERC-7201 namespaced storage. Implementation slots are kept distinct by namespace, so future upgrades won't collide with existing state. MachineSmartAccount uses a **BeaconProxy** pattern: a single implementation upgrade simultaneously applies to every deployed smart account. ## See also Wire the contract addresses into `.env` for the SDK. Every method that reads or writes these contracts. Off-chain reads derived from the on-chain state. # Events Source: https://docs.peaq.xyz/peaqos/concepts/events Revenue and activity events that feed a machine's credit history. Events are the atomic units of a machine's financial history. The EventRegistry contract stores them onchain; the MCR scoring pipeline reads them to compute credit ratings. ## Two event types | Type | Value | Description | | :------- | :---- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Revenue | `0` | The machine earned money. `value` carries the amount as a minor-unit integer (cents for USD/HKD, whole units for JPY/KRW) in the event's `currency`; the MCR scoring pipeline normalizes to USD cents via FX at the event timestamp. Single-event `submitEvent` resolves an omitted `currency` to `"USD"` for revenue and `""` for activity; batch submit requires an explicit `currency` per event. | | Activity | `1` | The machine performed an action (telemetry, data generation, task completion). `value` may be 0. `currency` must be empty (`""`); non-empty currency on an activity event reverts `InvalidCurrencyShape`. | Both SDKs export these as constants: ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { EVENT_TYPE_REVENUE, // 0 EVENT_TYPE_ACTIVITY, // 1 } from "@peaqos/peaq-os-sdk"; ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.constants import ( EVENT_TYPE_REVENUE, # 0 EVENT_TYPE_ACTIVITY, # 1 ) ``` ## MachineEvent schema Every event stored in the EventRegistry follows this structure: The SDKs expose the stored struct as `MachineEvent` (with `dataHash` / `data_hash` as a 32-byte `keccak256`). The SDK input type `SubmitEventParams` takes `rawData` / `raw_data` bytes instead; the SDK computes the hash for you before the call. | Field | Type | Description | | :-------------- | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `machineId` | `uint256` | Machine identity from IdentityRegistry. Primary key linking event to machine. | | `eventType` | `uint8` | `0` = revenue, `1` = activity. | | `value` | `uint256` | Amount earned (revenue) or metric value (activity). May be 0 for activity events. Revenue events express `value` as an ISO 4217 minor-unit integer (cents for USD/HKD, whole units for JPY/KRW) in the event's `currency`. | | `currency` | `string` | Revenue events: 3-10 char uppercase alphanumeric ISO 4217 code (e.g. `"USD"`, `"HKD"`, `"JPY"`). Activity events: empty string `""`. Validated on-chain. | | `timestamp` | `uint256` | Unix timestamp when the event occurred, not when it was submitted. | | `dataHash` | `bytes32` | `keccak256(raw_data)`. Raw data stays off-chain; hash proves integrity. | | `trustLevel` | `uint8` | `0` = self-reported, `1` = on-chain verifiable, `2` = hardware-signed. | | `sourceChainId` | `uint256` | Chain where the original activity occurred (e.g., 3338 for peaq, 8453 for Base). 0 for off-chain events. | | `sourceTxHash` | `bytes32` | Transaction hash on the source chain. Combined with `sourceChainId`, creates a verifiable cross-chain link. Null for off-chain events. | | `metadata` | `bytes` | Arbitrary bytes stored on-chain alongside the event (4096-byte cap). The MCR API server parses JSON metadata into the `event_data[].metadata` object surfaced on `/machine/{did}` for machines with `data_visibility: onchain`; the SDK only writes raw bytes. | ## Cross-chain revenue accounting `sourceChainId` and `sourceTxHash` together form a cross-chain audit trail. When a machine earns revenue on Base, the proxy submits the event to the EventRegistry on peaq with `sourceChainId = 8453` and the Base transaction hash. Any verifier can look up the transaction on the source chain and confirm the event happened. | Scenario | `sourceChainId` | `sourceTxHash` | | :--------------------- | :-------------- | :-------------------- | | Revenue earned on Base | `8453` | Base transaction hash | | Revenue earned on peaq | `3338` | peaq transaction hash | | Off-chain telemetry | `0` | null (`bytes32(0)`) | ## Data integrity via `dataHash` Raw event data stays with the project (in their database, behind their API). The `dataHash` stored onchain is `keccak256(raw_data)`, computed by the SDK before submission. This keeps gas costs at 32 bytes per event regardless of payload size, while anyone can fetch the raw data from the project's `data_api` (set in the machine's DID) and verify `keccak256(fetched_data) == dataHash`. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { computeDataHash } from "@peaqos/peaq-os-sdk"; const rawData = new Uint8Array([1, 2, 3]); const hash = computeDataHash(rawData); // hash === "0x..." (keccak256) ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.utils import compute_data_hash raw_data = b"revenue data" data_hash = compute_data_hash(raw_data) # data_hash is 32 bytes (keccak256) ``` ## Authorization EventRegistry reads IdentityRegistry to verify submit authority. `msg.sender` must be one of: | Path | Address | Use case | | :------------- | :--------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | NFT owner | Identity NFT holder | The account that holds the Identity NFT submits directly. | | Machine wallet | `machineWalletOf(machineId)` | The machine's registered wallet submits. Typical for self-managed flows where the machine signs its own transactions. | | Operator | `operatorOf(machineId)` | Assigned operator submits on behalf of the machine. Only valid when an operator has been set (non-zero). | All three paths produce identical on-chain records: the `machineId` in the stored event always identifies the machine, regardless of who submitted. ## Validation The JS SDK exports `validateSubmitEventParams`, which checks all nine fields before any chain interaction: ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { validateSubmitEventParams } from "@peaqos/peaq-os-sdk"; validateSubmitEventParams({ machineId: 1, eventType: 0, value: 100, // $1.00 in cents currency: "USD", timestamp: Math.floor(Date.now() / 1000), rawData: new Uint8Array([1, 2, 3]), trustLevel: 1, sourceChainId: 8453, sourceTxHash: "0xabc...def", metadata: new Uint8Array([]), }); ``` Key validation rules: * `eventType` must be 0 or 1 * `value` must be a non-negative integer (subunit, per ISO 4217 minor units) * `trustLevel` must be 0, 1, or 2 * `sourceChainId` must be a supported chain (0, 3338, or 8453) * When `trustLevel` is 1 (on-chain verifiable), `sourceTxHash` is required * `rawData` must be non-empty when provided (null is allowed, empty array is not) * `currency` on revenue events must match `^[A-Z0-9]{3,10}$`; on activity events it must be `""` * `timestamp` must be a positive integer; the EventRegistry contract additionally rejects `timestamp > block.timestamp` (`FutureTimestamp` revert) * `metadata` is capped at 4096 bytes on-chain (`MetadataTooLarge` revert above the limit) ## Cross-links * [JS SDK `validateSubmitEventParams`](/peaqos/sdk-reference/sdk-js#validatesubmiteventparams) documents every validation rule * [JS SDK `computeDataHash`](/peaqos/sdk-reference/sdk-js#computedatahash) documents the hashing utility * [Machine Credit Rating](/peaqos/concepts/machine-credit-rating) shows how events feed into a machine's credit history # Gas Station Source: https://docs.peaq.xyz/peaqos/concepts/gas-station 2FA-gated faucet that funds fresh machine wallets on peaq chain so they can bond and register. Gas Station is a 2FA-gated faucet that funds fresh machine wallets on peaq chain so they can bond and register. ## Why Gas Station exists A newly generated machine keypair has zero balance. Before it can register an identity (which requires bonding 1 PEAQ), it needs native tokens for gas. Gas Station solves this bootstrap problem: the owner enrolls in 2FA once, then funds each machine wallet with a single SDK call. ## 2FA setup flow Call `setupFaucet2FA` with the owner's address. The faucet returns an `otpauthUri` for manual entry and a `qrImageUrl` for scanning. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const setup = await client.setupFaucet2FA( ownerAddress, "https://depinstation.peaq.network" ); console.log(setup); // setup.otpauthUri : paste into authenticator app // setup.qrImageUrl : render immediately, expires in ~2 minutes ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} setup = client.setup_faucet_2fa( owner_address="0xOwner...", faucet_base_url="https://depinstation.peaq.network", ) print(setup) # setup["otpauth_uri"] : paste into authenticator app # setup["qr_image_url"] : render immediately, expires in ~2 minutes ``` Open any TOTP-compatible authenticator (Google Authenticator, Authy, 1Password). Scan the QR code or manually enter the `otpauthUri`. The QR image expires after approximately 2 minutes. Submit the current TOTP code from the authenticator app to activate 2FA for this owner address. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} await client.confirmFaucet2FA( ownerAddress, "https://depinstation.peaq.network", "123456" // TOTP code from authenticator ); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} client.confirm_faucet_2fa( owner_address="0xOwner...", faucet_base_url="https://depinstation.peaq.network", two_factor_code="123456", ) ``` ## Funding a machine wallet After 2FA is active, fund any machine wallet with `fundFromGasStation`. The response is a discriminated union: either `success` (transfer landed) or `skipped` (wallet already funded). ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const result = await client.fundFromGasStation( { ownerAddress: "0xOwner...", targetWalletAddress: machineAddress, chainId: "peaq", twoFactorCode: "654321", }, "https://depinstation.peaq.network" ); if (result.status === "success") { console.log("Funded:", result.txHash, result.fundedAmount); } else { // status === "skipped" console.log("Already funded:", result.currentBalance); } ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} result = client.fund_from_gas_station( owner_address="0xOwner...", target_wallet_address=machine_address, chain_id="peaq", two_factor_code="654321", faucet_base_url="https://depinstation.peaq.network", ) if result["status"] == "success": print("Funded:", result["tx_hash"], result["funded_amount"]) else: # status == "skipped" print("Already funded:", result["current_balance"]) ``` ### Success response JS (camelCase). The SDK retains the idempotency key on the response so callers can persist it for retry recovery: | Field | Type | Description | | :------------- | :------------------- | :----------------------------------------------------------- | | `status` | `"success"` | Transfer completed | | `requestId` | string | Idempotency key (echoed from request, or SDK-generated UUID) | | `txHash` | `0x`-prefixed hex | Transaction hash of the funding transfer | | `fundedAmount` | string (decimal wei) | Amount transferred | Python (snake\_case). The SDK does not echo `request_id` on the response. If you need it for retry recovery, pass it in explicitly and hold the value yourself: | Field | Type | Description | | :-------------- | :------------------- | :--------------------------------------- | | `status` | `"success"` | Transfer completed | | `tx_hash` | `0x`-prefixed hex | Transaction hash of the funding transfer | | `funded_amount` | string (decimal wei) | Amount transferred | ### Skipped response JS (camelCase): | Field | Type | Description | | :--------------- | :------------------- | :------------------------------------ | | `status` | `"skipped"` | Wallet already has sufficient balance | | `requestId` | string | Idempotency key | | `currentBalance` | string (decimal wei) | Target wallet's current balance | | `minGasBalance` | string (decimal wei) | Faucet-configured minimum threshold | Python (snake\_case): | Field | Type | Description | | :---------------- | :------------------- | :------------------------------------ | | `status` | `"skipped"` | Wallet already has sufficient balance | | `current_balance` | string (decimal wei) | Target wallet's current balance | | `min_gas_balance` | string (decimal wei) | Faucet-configured minimum threshold | ## Rate limits and caps Gas Station enforces three independent throttles to prevent abuse, plus a per-chain minimum balance threshold that decides whether a funding request transfers anything at all: | Limit | Scope | Surfaces as | Description | | :------------------ | :--------- | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Rate limit | Per owner | `RATE_LIMITED` | Too many funding requests in too short a window. Back off and retry. | | Daily funding cap | Per owner | `CAP_EXCEEDED_OWNER` | Total funded amount per owner per day. | | Daily funding cap | Per wallet | `CAP_EXCEEDED_WALLET` | Total funded amount per target wallet per day. | | Minimum gas balance | Per chain | `skipped` response | When the target wallet already holds at least this much, the faucet returns `skipped` with `currentBalance`/`current_balance` and `minGasBalance`/`min_gas_balance` instead of transferring. | Exact cap values, rate-limit windows, and the minimum gas balance are configured server-side and are subject to change. Branch on the error codes (or the `skipped` status) rather than hard-coding numbers. ## Error codes Gas Station defines 20 error codes. Not every endpoint can raise every code; the subset depends on what the endpoint actually does. ### `POST /2fa/setup`: `setupFaucet2FA` / `setup_faucet_2fa` | Code | Meaning | | :---------------------- | :--------------------------------------------------------------- | | `INVALID_OWNER_ADDRESS` | Owner address failed server-side format validation (setup-only). | | `INVALID_PAYLOAD` | Request body did not match the spec (e.g. unknown `format`). | | `QR_GENERATION_FAILED` | The faucet failed to render the QR image (setup-only). | | `INTERNAL_ERROR` | Unhandled server-side failure. | ### `POST /2fa/confirm`: `confirmFaucet2FA` / `confirm_faucet_2fa` | Code | Meaning | | :------------------- | :-------------------------------------------------------- | | `INVALID_2FA` | TOTP code did not match the secret. | | `2FA_NOT_CONFIGURED` | Owner never completed setup. Call `setupFaucet2FA` first. | | `2FA_LOCKED` | Owner locked after too many invalid attempts. | | `INTERNAL_ERROR` | Unhandled server-side failure. | ### `POST /faucet/fund`: `fundFromGasStation` / `fund_from_gas_station` | Code | Meaning | | :-------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | `INVALID_2FA` | TOTP code did not match. | | `2FA_NOT_CONFIGURED` | Owner never completed setup. | | `2FA_NOT_ACTIVE` | Setup done but never confirmed. Call `confirmFaucet2FA` first. | | `2FA_LOCKED` | Owner locked after too many invalid attempts. | | `DUPLICATE_REQUEST` | A request with this `requestId` is still in flight. | | `REQUEST_ALREADY_PROCESSED` | This `requestId` has already completed. | | `RATE_LIMITED` | Faucet rate limit exceeded. | | `CAP_EXCEEDED_OWNER` | Owner daily funding cap exceeded. | | `CAP_EXCEEDED_WALLET` | Wallet daily funding cap exceeded. | | `INVALID_PAYLOAD` | Request body validation failed server-side. | | `INVALID_OWNER_ADDRESS` | Malformed `ownerAddress`. | | `INVALID_TARGET_ADDRESS` | Malformed `targetWalletAddress`. | | `INVALID_CHAIN_ID` | Unsupported `chainId`. | | `INVALID_REQUEST_ID` | The supplied `requestId` was not a valid UUID. | | `TRANSFER_FAILED` | On-chain transfer failed. | | `CHAIN_RPC_ERROR` | Faucet's upstream RPC provider failed. | | `QR_NOT_FOUND` | QR token does not exist (rare; surfaces when a stale QR is referenced). | | `QR_EXPIRED` | QR token has expired. | | `INTERNAL_ERROR` | Unhandled server-side failure. The SDK also raises this code locally for non-JSON bodies and unexpected envelope shapes. | For SDK-owned message strings and HTTP status codes per code, see [errors](/peaqos/sdk-reference/errors). ## Cross-links * [JS SDK faucet methods](/peaqos/sdk-reference/sdk-js#gas-station) documents `setupFaucet2FA`, `confirmFaucet2FA`, and `fundFromGasStation` * [Python SDK faucet methods](/peaqos/sdk-reference/sdk-python#gas-station) documents the Python equivalents * [Self-managed onboarding guide](/peaqos/guides/self-managed-onboarding) shows Gas Station in the full registration flow * [Errors reference](/peaqos/sdk-reference/errors) lists all error codes with HTTP status codes # Machine Credit Rating Source: https://docs.peaq.xyz/peaqos/concepts/machine-credit-rating A Moody's-style score that summarizes a machine's creditworthiness from its on-chain history. Machine Credit Rating (MCR) is a letter rating, modeled on traditional credit agencies, that summarizes a machine's financial standing from its on-chain history. Any application can query a machine's MCR through the public [MCR API](/peaqos/api-reference/overview): no authentication, no permissions. It travels with the machine across chains. ## Rating scale MCR ratings use a familiar AAA-to-NR scale. The integer `mcr_score` (0-100, rounded) maps to a letter via fixed thresholds: | Rating | `mcr_score` | Meaning | | :-------------- | :---------- | :------------------------------------------------- | | **AAA** | ≥ 95 | Highest credit quality | | **AA** | ≥ 85 | Very high credit quality | | **A** | ≥ 75 | High credit quality | | **BBB** | ≥ 60 | Investment grade | | **BB** | ≥ 45 | Below investment grade | | **B** | ≥ 30 | Minimum rated | | **NR** | 0 | Not rated. Score below 30, or machine is unbonded | | **Provisioned** | 0 | Newly registered. Not enough history to score yet. | Both `NR` and `Provisioned` return `mcr_score = 0`; the `mcr` letter and `bond_status` field on `/mcr/{did}` distinguish the two. ## Lifecycle: Provisioned graduation A freshly registered, bonded machine is **Provisioned**: on-chain, but not yet rated. It graduates onto the rated scale once it has both: * **Enough events:** a sustained set of qualifying events. Activity-only machines (sensors, drones without revenue) can graduate via activity alone; revenue is not required. * **Enough history:** the first-to-last event span covers a sustained operating window. Until both gates are cleared the machine returns `mcr: "Provisioned"` with `mcr_score: 0`, regardless of how strong the underlying activity is. Ratings are not assigned. They're earned. A machine's standing reflects what it has done on-chain. ## How the score is computed The MCR server fetches every event from the `EventRegistry`, normalizes revenue values to USD cents via FX at the event's timestamp, and combines them into a `0`–`100` integer score that maps to a letter rating. The exact weighting is not published. The score is built from a small set of inputs: **Bond.** Every bonded machine receives a baseline contribution. An unbonded machine returns `(0, "NR")` immediately. Bond is the floor for any score at all. **Revenue and activity.** Two factors derived from the event history. Each rewards consistency (regular cadence), depth (sustained activity over many events), and tenure (longer-running machines). Revenue is daily-aggregated: events are summed per UTC calendar day, and only days that meet a minimum economic threshold count toward revenue. **Trust level.** Per-event trust tiers, with revenue events weighted by economic value. Higher tiers contribute more; `0` self-reported, `1` on-chain verifiable, `2` hardware-signed. See [Trust levels](/peaqos/concepts/trust-levels). A long-running self-reported machine earns a small graduation bonus on its trust contribution after sustained operation, but it remains below the on-chain tier. **Freshness.** Recency decay applied to the trust contribution. A machine that hasn't reported recently scores progressively lower; a sustained dormancy gap clamps freshness at a floor until the machine demonstrates renewed activity over a recovery window. Recovery is earned, not granted by a single recent event. **Negative event flag.** While an active flag is set on the machine, the trust contribution is reduced for a fixed window from the flag timestamp; the penalty expires automatically after the window closes. The final score is clamped to `[0, 100]` and rounded to an integer before mapping to a letter. ## Revenue trend `/mcr/{did}` exposes a `revenue_trend` field with one of four values, derived from short-term vs longer-term moving averages over qualifying revenue events: | Value | Meaning | | :--------------- | :---------------------------------------------------------- | | `"up"` | Recent revenue is materially above the longer-term baseline | | `"stable"` | Recent revenue tracks the longer-term baseline | | `"down"` | Recent revenue is materially below the longer-term baseline | | `"insufficient"` | Not enough qualifying revenue events to compute a trend | ## Multi-currency revenue Revenue events submitted in any ISO 4217 currency get FX-normalized to USD by the MCR server before scoring. Pass `currency` directly to `submitEvent` and supply `value` as the currency's minor-unit integer (cents for USD/HKD, whole units for JPY/KRW); the server fetches the historical rate at the event's `timestamp` and stores both `origin_value` and the computed `usd_value` (USD cents). Two distinct degradation paths show up on the wire: * **Unsupported currency.** The per-event `amount_status` becomes `"unsupported_currency"`. The revenue value is ignored when scoring (treated as \$0). This is a partner-input error, not infra failure, so it does **not** flip `mcr_degraded`. * **FX infrastructure failure.** When the MCR server has to fall back to a stale snapshot (`fx_source = "stale_latest"`) or has no FX at all (`fx_source = "default_usd_fx_outage"`), the event's `amount_status` becomes `"fx_unavailable"` **and** the top-level `mcr_degraded: true` flag flips on `/mcr/{did}`, so consumers can distinguish a degraded score from a quiet machine. See [Submit events: currency and value units](/peaqos/guides/submit-events#currency-and-value-units). ## Cross-links * [Events](/peaqos/concepts/events): the revenue and activity records that feed MCR * [Trust levels](/peaqos/concepts/trust-levels): what each `trust_level` means at submit time * [GET /mcr/\{did}](/peaqos/api-reference/get-mcr): fetch a machine's current rating * [Qualify function](/peaqos/functions/qualify): the function that exposes MCR end-to-end # Machine NFT Source: https://docs.peaq.xyz/peaqos/concepts/machine-nft LayerZero V2 ONFT that anchors a machine's identity and links to its credit history. Machine NFT is a LayerZero V2 ONFT, portable across chains by design. It anchors a machine's identity and links to its credit history. ## What the Machine NFT represents The Machine NFT is a financial digital twin, separate from the Identity NFT that IdentityRegistry mints at registration. It carries revenue history, Machine Credit Rating, documentation links, and bond status in its metadata. The two tokens live in different token spaces and are linked by the on-chain `machineId`: | Token | Contract | Token ID | Purpose | Transferability | | :----------- | :--------------- | :--------------------------------------------- | :----------------------------------------------------------- | :--------------------------------------------------------------------------- | | Identity NFT | IdentityRegistry | Equal to `machineId` | On-chain identity credential, governs protocol authorization | Soulbound: transfers revert with `IdentityNFTSoulbound` | | Machine NFT | MachineNFT | Independent tokenId (not equal to `machineId`) | Financial digital twin, carries MCR and revenue metadata | Transfer moves the financial representation; identity stays with the machine | Selling or bridging the Machine NFT does not affect the machine's DID, event submission rights, or protocol authorization. **Machine ID ≠ Machine NFT token ID.** The `registerMachine` SDK method (`register()` on IdentityRegistry) and `registerFor` SDK method (`registerFor(machineAddress)` on IdentityRegistry) both return a `machineId`. The `mintNft(machineId, recipient)` SDK method (`mint(machineId, recipient)` on MachineNFT) results in a different token ID. MachineNFT has its own auto-incrementing tokenId sequence. Read the Machine NFT token ID back with `tokenIdOf(machineId)`. ## Machine Card The Machine Card is a peaqOS registration document that follows the ERC-8004 registration file pattern and is served by the MCR API at `/machines/{machine_id}`: ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "type": "peaqos:registration:v1", "name": "Machine #42", "description": "peaqOS machine", "did": "did:peaq:0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", "active": true, "services": [ { "name": "web", "endpoint": "https://example.com/machines/42/api" } ], "data_visibility": "private", "documentation_url": "https://example.com/machines/42", "operator": "did:peaq:0xOperatorAddress", "bond_status": "bonded", "event_count": 150, "registrations": [ { "type": "peaqos:registration:v1", "machineId": 42, "machineRegistry": "eip155:3338:0xIdentityRegistryAddress" } ] } ``` For the full MCR score and revenue summary, use [GET /machine/](/peaqos/api-reference/get-machine) or [GET /mcr/](/peaqos/api-reference/get-mcr); both are computed live by the MCR API on each request. ## Ownership semantics Unlike the Identity NFT, the Machine NFT is **not** minted automatically during registration. It's a two-step flow owned by the operator or machine: The proxy or machine calls **IdentityRegistry**'s `register()` (proxy uses `registerFor(machineAddress)`) and receives a `machineId`. IdentityRegistry simultaneously mints the Identity NFT to `machineAddress` with `tokenId == machineId`. SDK helpers: `registerMachine` / `registerFor`. The operator or machine then calls **MachineNFT**'s `mint(machineId, recipient)`. MachineNFT assigns a new, independent `tokenId`. Read the assigned token ID back with `tokenIdOf(machineId)`. SDK helper: `mintNft`. Call `writeMachineDIDAttributes` to store `machineId` and `nftTokenId` on the machine's DID, binding the identity to its financial twin. These attribute writes must be signed by the machine's own key (the DID subject); the proxy can't write them on the machine's behalf. Registration does not write them for you. ## Metadata flow `tokenURI()` returns a URL pointing to the MCR API. The API reads onchain events and DID attributes, computes the current MCR score, and returns the full machine profile JSON. A consumer or marketplace calls `tokenURI(tokenId)` on the **MachineNFT** contract and receives the metadata URL. The consumer calls [`GET /metadata/{token_id}`](/peaqos/api-reference/get-metadata) on the MCR API. The API reads onchain events and DID attributes, computes the current MCR score, and returns the same shape as [`GET /machine/{did}`](/peaqos/api-reference/get-machine): the full machine profile. The `baseURI` is updatable by the protocol admin. If the API domain changes, only the contract's `baseURI` is updated. No token migration needed. The lighter [Machine Card](#machine-card) (ERC-8004 registration document) lives at a separate endpoint: [`GET /machines/{machine_id}`](/peaqos/api-reference/get-machine-card). ## Cross-chain portability **Supported routes: peaq ↔ Base.** Additional peaqOS chains are added as peer contracts deploy. Bridging is mainnet-only: LayerZero V2 has no DVN routes between agung and Base, so `bridgeNft` / `bridge_nft` cannot be exercised against the testnet. The Machine NFT implements the LayerZero V2 ONFT standard. peaq is the home chain: its NFT supply is canonical and uses a **lock/unlock** adapter (`MachineNFTAdapter`); destination chains use the standard **burn/mint** ONFT pattern. Only one live instance exists across the network at any time. Canonical Machine NFT contract. Minting and DID linking happen here. Bridging out locks the NFT in `MachineNFTAdapter`; bridging back unlocks it. Bridged destination via LayerZero V2 ONFT. Bridging in mints; bridging back to peaq burns. More bridge destinations are added as peer contracts deploy.
peaq is the home chain. peaq → Base locks on peaq + mints on Base; Base → peaq burns on Base + unlocks on peaq.
Key properties: * **Home chain**: peaq. Minting happens on peaq after machine onboarding. * **Cross-chain**: LayerZero V2 ONFT architecture bridges peaq ↔ Base via the `MachineNFTAdapter` lock/unlock pattern on peaq paired with burn/mint on the destination. Further peaqOS chains are added as peer contracts deploy. * **Metadata**: `tokenURI()` resolves to the same MCR API URL regardless of which chain holds the NFT. * **Identity independence**: The machine's peaqID, DID attributes, and event submission stay on peaq regardless of where the NFT sits. ## Cross-links * [peaqID](/peaqos/concepts/peaqid) is the DID linked to the Machine NFT via the `nftTokenId` attribute * [GET /metadata/](/peaqos/api-reference/get-metadata) returns the full NFT metadata JSON * [Activate function](/peaqos/functions/activate) handles the minting flow during onboarding # peaqID Source: https://docs.peaq.xyz/peaqos/concepts/peaqid W3C DID for machines, portable across every chain peaqOS supports. peaqID is a W3C DID that identifies a machine across every chain it transacts on. ## DID format Every peaqID follows the format: ``` did:peaq:<0x-address> ``` The address is the machine's EOA (externally owned account) on peaq chain. The DID resolves to a flat key-value attribute store on the peaq DID precompile (`0x0000000000000000000000000000000000000800`). Any consumer can start from the DID and traverse to the machine's identity, financial history, and credit rating. ## Per-machine vs per-proxy peaqOS assigns one DID per machine and one DID per proxy operator. The two serve different roles: | DID type | Registered by | Attributes | Purpose | | :---------- | :------------------------------------------------------------------- | :---------------------------------------------------------------------------------------- | :----------------------------------------------------------------------- | | Machine DID | `registerMachine` (self-managed) or `registerFor` (proxy delegation) | `machineId`, `nftTokenId`, `operator`, `documentation_url`, `data_api`, `data_visibility` | Identifies a single machine, links to its Machine NFT and event history | | Proxy DID | `registerMachine`. The proxy itself is a registered machine. | `machineId`, `machines` | Identifies an operator, lists the machine IDs of all machines it manages | A machine that self-manages has no `operator` attribute. A proxy operator's `machines` attribute is a JSON array of machine IDs (e.g., `[123, 456, 789]`). **DID writes always go to the caller's DID.** The DID precompile keys attributes by `msg.sender`. A proxy that calls `registerFor` mints the machine's identity NFT and pays the bond, but it cannot write attributes to the machine's DID; the machine must sign its own `writeMachineDIDAttributes` call. Skipping this leaves the machine unreachable through the MCR API. ## DID attribute table After registration, the operator or machine must explicitly call `writeMachineDIDAttributes` (or `writeProxyDIDAttributes`) to write these attributes to the DID. Registration itself only mints the identity and creates the on-chain machine ID; the DID attribute writes are a separate transaction. | Attribute | Type | Description | | :------------------ | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `machineId` | `uint256` | On-chain machine ID assigned by IdentityRegistry. Primary key for all queries. | | `nftTokenId` | `uint256` | Token ID of the machine's Machine NFT. This is a separate ERC-721 token space and is not equal to `machineId`. | | `operator` | `did:peaq:0x...` | Proxy operator's DID. Absent if the machine self-manages. | | `documentation_url` | URL string | Link to machine documentation maintained by the project. | | `data_api` | URL string | Raw data API endpoint. The MCR API reads this when `data_visibility` is `public`. | | `data_visibility` | `public` / `private` / `onchain` | Controls how the MCR API exposes raw event data. Unset or empty defaults to `private`. | | `machines` | JSON array string | Proxy operator DID only. List of managed machine IDs. The DID precompile stores the full JSON; the MCR API truncates to the first 100 valid IDs when reading the `machines` attribute. | ### Byte limits Both SDKs enforce these constraints before any DID write reaches the chain: | Constant | Value | Applies to | | :-------------------- | :---- | :-------------- | | `DID_MAX_NAME_BYTES` | 64 | Attribute name | | `DID_MAX_VALUE_BYTES` | 2560 | Attribute value | **Migration.** peaq chain has approximately 3.5 million existing peaqID holders. Existing holders retain their peaqIDs. A migration path to the current DID format is on the [roadmap](/roadmap); the new format is what onboards from peaqOS today. ## Resolving a peaqID ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient } from "@peaqos/peaq-os-sdk"; const client = PeaqosClient.fromEnv(); // Fetch machine profile by DID const response = await fetch( `${client.apiUrl}/machine/did:peaq:0xMachineAddress` ); const machine = await response.json(); console.log(machine.peaqos.did); // "did:peaq:0x..." console.log(machine.peaqos.machine_id); // 123 console.log(machine.peaqos.mcr); // "BBB" ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from dotenv import load_dotenv import requests from peaq_os_sdk import PeaqosClient load_dotenv() # load envs from .env file client = PeaqosClient.from_env() # Fetch machine profile by DID response = requests.get( f"{client.api_url}/machine/did:peaq:0xMachineAddress" ) machine = response.json() print(machine["peaqos"]["did"]) # "did:peaq:0x..." print(machine["peaqos"]["machine_id"]) # 123 print(machine["peaqos"]["mcr"]) # "BBB" ``` ## Data visibility modes The `data_visibility` attribute controls how the MCR API handles raw event data for this machine: | Mode | MCR API behavior | Raw data location | | :-------- | :-------------------------------------------------------------------------------------------------------------- | :------------------------ | | `public` | Fetches from `data_api`, includes in response | Project's API | | `private` | Returns the `data_api` URL only; consumer fetches directly | Project's API | | `onchain` | Parses JSON metadata from EventRegistry events into `event_data[]`, capped at the first 100 events per response | Onchain (higher gas cost) | `private` is the default when `data_visibility` is unset or empty. ## Cross-links * [Activate function](/peaqos/functions/activate) registers machines and writes DID attributes * [GET /machine/](/peaqos/api-reference/get-machine) returns the full machine profile, including DID-sourced metadata * [Machine NFT](/peaqos/concepts/machine-nft) is linked to the peaqID via the `nftTokenId` attribute # Trust levels Source: https://docs.peaq.xyz/peaqos/concepts/trust-levels Three tiers classifying how trustworthy a submitted event is. Trust level classifies how trustworthy the data in a submitted event is. Higher trust levels indicate stronger guarantees about the event's authenticity. ## The three levels The machine or operator attests to the event. No external verification: the submitter's word is the source of truth. The event is backed by an on-chain reference (transaction hash, receipt, or cross-chain proof) that anyone can independently verify. The event is signed by attested hardware (secure enclave, TPM, or equivalent) that binds the event to a specific physical device. Choose the highest level you can honestly attest to. ## Trust weight in the MCR Higher trust levels contribute more to the [MCR](/peaqos/concepts/machine-credit-rating) score. Hardware-signed events carry the strongest weight, on-chain verifiable events sit in the middle, and self-reported events the lowest. Self-reported machines with a sustained track record (a year or more of event history) earn a small graduation bonus on their trust contribution, though they remain below the on-chain tier. A machine flagged with a [negative event](/peaqos/api-reference/get-mcr) incurs a temporary penalty on its trust contribution for a fixed window from the flag timestamp, after which the penalty expires automatically. ## Admin overrides peaq operates two on-chain admin functions (`AdminFlags` contract) that adjust how the MCR treats a machine. Both are owner-only and emit events. | Override | Effect | | :----------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `setTrustOverride(machineId, value)` | Replaces the trust contribution for this machine within a permitted range. Reverts with `InvalidTrustOverride(value)` if the value is out of range. Use `clearTrustOverride` to remove. | | `flagMachine(machineId)` | Sets the negative-event flag at `block.timestamp`. The penalty applies for a fixed window from this timestamp. Use `clearFlag` to lift early. | Submitting an event with `trustLevel > 2` reverts on `EventRegistry.submitEvent` with `InvalidTrustLevel()`. # Activate Source: https://docs.peaq.xyz/peaqos/functions/activate Put your machine on-chain. peaqID, wallet, and Machine NFT. Activate is the entry point to peaqOS. Register a machine on peaq chain and give it a [peaqID](/peaqos/concepts/peaqid), a keypair, and a [Machine NFT](/peaqos/concepts/machine-nft): the primitives every later function builds on. ## What ships | Component | Description | | :------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | peaqID | W3C DID anchoring the machine's on-chain identity. Portable across chains. | | Identity NFT | Minted automatically by IdentityRegistry at registration. `tokenId == machineId`. | | Machine NFT | LayerZero V2 ONFT linking to the machine's financial profile. Minted via a separate `mintNft` call after registration. See [Machine NFT ownership](/peaqos/concepts/machine-nft#ownership-semantics). | | 1 PEAQ bond | Sent as `msg.value` on the `register()` call (the contract's current `minBond`). Required to graduate onto the MCR rated scale later. | | Machine wallet | Per-machine EVM keypair, generated by the SDK. | ## How activation works The SDK exposes each step as a discrete call so you can compose your own flow. For one-shot activation from the terminal, the CLI runs all six steps idempotently. Re-runs skip already-completed steps using `./peaqos.log` as the resume marker: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # self-managed: caller's key drives every step peaqos activate --doc-url "https://example.com/docs" --data-api "https://example.com/events" # proxy-managed: --for and --machine-key must be used together peaqos activate --for 0xMachineAddress --machine-key ./machine.key --doc-url "https://example.com/docs" --data-api "https://example.com/events" ``` See [CLI reference: activate](/peaqos/cli#peaqos-activate) for all flags (including `--skip-funding`, `--doc-url`, `--data-api`, `--visibility`). The SDK generates a new keypair for the machine. This becomes the machine's signing identity on peaq chain. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient } from "@peaqos/peaq-os-sdk"; const client = PeaqosClient.fromEnv(); const keypair = PeaqosClient.generateKeypair(); // { address: "0x...", privateKey: "0x..." } ``` Fund the machine's wallet with PEAQ for gas and the 1 PEAQ bond. Projects with partner access to the peaq Gas Station can request initial gas from the faucet using the SDK (2FA-gated). Otherwise, fund the wallet directly from any PEAQ-holding address. Two patterns depending on your deployment model. Owner equals operator. One machine, one wallet. Use `registerMachine` to register the machine under its own keypair. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const machineClient = new PeaqosClient({ rpcUrl: client.rpcUrl, privateKey: keypair.privateKey, contracts: client.contracts, }); const machineId = await machineClient.registerMachine(); console.log(machineId); // Returns the on-chain machine ID (number) ``` Full guide: [Self-managed onboarding](/peaqos/guides/self-managed-onboarding). One owner runs a fleet. Register machines on their behalf using `registerFor`. Each machine gets its own peaqID; the proxy operator controls delegation and mints the Machine NFT in a follow-up step. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const machineId = await client.registerFor(keypair.address); console.log(machineId); // Create Machine Client after registration to mint NFT and link DID const machineClient = new PeaqosClient({ rpcUrl: client.rpcUrl, privateKey: keypair.privateKey, contracts: client.contracts, }); ``` Full guide: [Proxy operator fleet setup](/peaqos/guides/proxy-operator-fleet). NFT and the DID attributes are written by the machine itself. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const tx = await machineClient.mintNft(machineId, keypair.address); const nftTokenId = await machineClient.tokenIdOf(machineId); await machineClient.writeMachineDIDAttributes({ machineId, nftTokenId, operatorDid: "", documentationUrl: "https://example.com/docs", dataApi: "https://example.com/events", dataVisibility: "public", }); ``` NFT is minted to the machine's address, and the DID attributes are written by the machine itself. Proxy pays bond fee, and machine pays DID fees. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const tx = await client.mintNft(machineId, keypair.address); const nftTokenId = await client.tokenIdOf(machineId); await machineClient.writeMachineDIDAttributes({ machineId, nftTokenId, operatorDid: "", documentationUrl: "https://example.com/docs", dataApi: "https://example.com/events", dataVisibility: "public", }); ``` Full guide: [Proxy operator fleet setup](/peaqos/guides/proxy-operator-fleet). `mintNft` returns a new, independent `tokenId` on the MachineNFT contract. `writeMachineDIDAttributes` binds `machineId` and `nftTokenId` to the machine's DID. See [Machine NFT ownership](/peaqos/concepts/machine-nft#ownership-semantics) for the full rationale. After all three calls the machine has a peaqID, an Identity NFT, a Machine NFT, and a 1 PEAQ bond. Query the machine's profile against the [MCR API](/peaqos/api-reference/get-machine), where `{did}` is `did:peaq:`: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl "${PEAQOS_MCR_API_URL}/machine/did:peaq:0xYourMachineAddress" ``` The machine is `Provisioned` at first: on-chain, but not yet rated. It graduates onto the rated scale once **both** gates clear: a sustained first-to-last event span, **and** either enough qualifying revenue events or enough activity events. The events condition is `OR`, so activity-only machines can graduate without revenue. See [Machine Credit Rating](/peaqos/concepts/machine-credit-rating#lifecycle-provisioned-graduation). ## After activation Once a machine is registered, later functions add new capabilities. | Next step | Function | Status | | :----------------------------------------------------- | :------------------------------------- | :---------- | | Build a credit rating from revenue and activity events | [Qualify](/peaqos/functions/qualify) | Live | | Get attested by the peaq Foundation or an OEM | [Verify](/peaqos/functions/verify) | Coming Soon | | List services in the Service Registry | [Monetize](/peaqos/functions/monetize) | Coming Soon | | Pair an AI agent with delegated spending | [Scale](/peaqos/functions/scale) | Coming Soon | | Fractionalize the machine for investor ownership | [Tokenize](/peaqos/functions/tokenize) | Coming Soon | ## Concepts W3C DID, portable identity, cross-chain resolution. LayerZero V2 ONFT whose `tokenURI` resolves to the machine's Machine Card (ERC-8004 JSON schema). Revenue events (type 0) and activity events (type 1). Moody's-style rating from a machine's history. 2FA-gated faucet for initial machine gas. ## SDK reference * [`registerMachine`](/peaqos/sdk-reference/sdk-js#registermachine): Register a self-managed machine. * [`registerFor`](/peaqos/sdk-reference/sdk-js#registerfor): Register a machine as a proxy operator. * [`setupFaucet2FA`](/peaqos/sdk-reference/sdk-js#setupfaucet2fa): Initialize 2FA for Gas Station access. * [`confirmFaucet2FA`](/peaqos/sdk-reference/sdk-js#confirmfaucet2fa): Confirm 2FA setup with TOTP code. * [`fundFromGasStation`](/peaqos/sdk-reference/sdk-js#fundfromgasstation): Request gas funding for a machine address. * [`generateKeypair`](/peaqos/sdk-reference/sdk-js#generatekeypair): Create a new machine keypair. ## Guides Single machine, owner-operated. Register and manage N machines from one identity. # Monetize Source: https://docs.peaq.xyz/peaqos/functions/monetize List your machine's services. Other machines and agents discover and buy them. **Monetize is coming soon.** Details on this page are preliminary and may change. Track progress on the [roadmap](/roadmap). Monetize gives machines a place to list the services they offer (data access, compute, telemetry, physical work) so other machines, agents, and applications can discover and buy them. # Qualify Source: https://docs.peaq.xyz/peaqos/functions/qualify Credit rate your machine from its revenue and activity history. Qualify turns a machine's on-chain history into a [Machine Credit Rating (MCR)](/peaqos/concepts/machine-credit-rating): a Moody's-style letter rating that any protocol, agent, or frontend can query from any chain. ## What ships | Component | Description | | :------------------- | :---------------------------------------------------------------------------------------------------- | | EventRegistry | On-chain store for revenue (type `0`) and activity (type `1`) events, with a cross-chain audit trail. | | MCR scoring pipeline | Computes AAA-to-NR ratings from a bonded machine's event history. | | MCR API | Public read API. Any chain, any caller, no auth. | | SDK helpers | `submitEvent`, `validateSubmitEventParams`, `computeDataHash`, `queryMcr` on both JS and Python. | ## How it works Each time a machine earns revenue or performs a trackable activity, the operator submits an event to the EventRegistry. The SDK validates the payload, computes a `keccak256` data hash, and writes the minimal on-chain record. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient, EVENT_TYPE_REVENUE, TRUST_SELF_REPORTED, SUPPORTED_CHAIN_IDS } from "@peaqos/peaq-os-sdk"; const client = PeaqosClient.fromEnv(); const { txHash, dataHash } = await client.submitEvent({ machineId, eventType: EVENT_TYPE_REVENUE, value: 500, // $5.00 in cents currency: "USD", timestamp: Math.floor(Date.now() / 1000) - 10, // Must be after block time rawData: new TextEncoder().encode(JSON.stringify({ session: "abc" })), trustLevel: TRUST_SELF_REPORTED, sourceChainId: SUPPORTED_CHAIN_IDS.peaq, sourceTxHash: null, metadata: new Uint8Array([]), }); ``` Full walkthrough: [Submit events](/peaqos/guides/submit-events). A freshly registered machine is **Provisioned** until it has enough history to score. Events feed the scoring pipeline, which blends revenue trend, activity cadence, bond status, and trust level. Any consumer (an agent, protocol, frontend, or another chain) can fetch the current rating from the public API. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl ${PEAQOS_MCR_API_URL}/mcr/did:peaq:0xabc... ``` Response includes the rating (AAA / AA / A / BBB / BB / B / NR / Provisioned), score, event counts, revenue trend, and bond status. Full walkthrough: [Query MCR](/peaqos/guides/query-mcr). ## Cross-chain revenue Revenue earned on another chain (e.g., Base) is recorded on peaq with `sourceChainId` and `sourceTxHash` pointing to the origin transaction. Consumers can verify each event against its source chain. See [Events](/peaqos/concepts/events#cross-chain-revenue-accounting) and [Trust levels](/peaqos/concepts/trust-levels). ## Concepts AAA-to-NR scale, lifecycle from Provisioned to rated. Revenue and activity records that feed MCR. Self-reported, on-chain verifiable, hardware-signed. ## SDK reference * [`submitEvent`](/peaqos/sdk-reference/sdk-js#submitevent): Write a single event to EventRegistry. * [`validateSubmitEventParams`](/peaqos/sdk-reference/sdk-js#validatesubmiteventparams): Validate event params client-side. * [`computeDataHash`](/peaqos/sdk-reference/sdk-js#computedatahash): keccak256 of raw event data. * [`queryMcr`](/peaqos/sdk-reference/sdk-js#querymcr): Fetch a machine's rating from the MCR API. ## API reference Rating, score, trend, bond status for a single machine. Paginated operator fleet with per-machine MCR. ## Guides Event types, validation, data hashing, cross-chain pattern. Fetch ratings from curl, JS, or Python. # Scale Source: https://docs.peaq.xyz/peaqos/functions/scale Pair an AI agent that transacts and consumes services on your machine's behalf. **Scale is coming soon.** Details on this page are preliminary and may change. Track progress on the [roadmap](/roadmap). Scale pairs AI agents with machines, giving an agent delegated authority to transact and consume services on a machine's behalf, and extends fleet operations across chains. It builds on the smart account each machine receives at [Activate](/peaqos/functions/activate) and the service listings exposed by [Monetize](/peaqos/functions/monetize), letting an agent buy, sell, and coordinate across a fleet without a human in the loop. # Tokenize Source: https://docs.peaq.xyz/peaqos/functions/tokenize Fractionalize your machine into an investable asset via ERC-3643. **Tokenize is coming soon.** Details on this page are preliminary and may change. Track progress on the [roadmap](/roadmap). Tokenize lets machine owners fractionalize a machine into a tradeable, regulatory-compliant security token, turning machine revenue into an investable asset for third parties. The underlying asset is the [Machine NFT](/peaqos/concepts/machine-nft) minted at [Activate](/peaqos/functions/activate). Tokenize wraps that NFT in an ERC-3643 share class so revenue and activity tracked by [Qualify](/peaqos/functions/qualify) flow back to token holders. # Verify Source: https://docs.peaq.xyz/peaqos/functions/verify Prove your machine is real via hardware attestation and trusted third parties. **Verify is coming soon.** Details on this page are preliminary and may change. Track progress on the [roadmap](/roadmap). Verify is the attestation layer for peaqOS. It lets manufacturers, labs, and the peaq Foundation co-sign a machine's identity, producing a portable signal that the machine exists and is who it claims to be. At launch, [trust levels](/peaqos/concepts/trust-levels) are self-attested when an event is submitted. `Hardware-signed = 2` is a valid value but there's no third-party attestation infrastructure behind it yet. Verify is what closes that gap. # Proxy operator fleet Source: https://docs.peaq.xyz/peaqos/guides/proxy-operator-fleet Register and manage multiple machines from one operator wallet using registerFor. Covers per-machine keys, batch registration, and fleet queries. 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](/peaqos/api-reference/get-operator-machines) 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](/peaqos/guides/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](/peaqos/guides/self-managed-onboarding). The proxy's `machineId` is required to publish the fleet under its DID. * Environment variables configured per the [install guide](/peaqos/install) * 2FA set up and confirmed on the operator wallet (see [self-managed onboarding](/peaqos/guides/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 The proxy operator's private key signs all transactions on behalf of the fleet. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient } from "@peaqos/peaq-os-sdk"; const proxy = PeaqosClient.fromEnv(); console.log("Proxy operator:", proxy.address); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from dotenv import load_dotenv from peaq_os_sdk import PeaqosClient load_dotenv() # load envs from .env file proxy = PeaqosClient.from_env() print("Proxy operator:", proxy.address) ``` Each machine needs its own address. Generate one per device. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const machine = PeaqosClient.generateKeypair(); console.log("Machine address:", machine.address); // Store machine.privateKey securely if the device needs to sign later. ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} machine_address, machine_key = PeaqosClient.generate_keypair() print("Machine address:", machine_address) # Store machine_key securely if the device needs to sign later. ``` `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. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const machineId = await proxy.registerFor(machine.address); console.log("Registered machine", machine.address, "with machine ID", machineId); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} machine_id = proxy.register_for(machine_address) print(f"Registered machine {machine_address} with machine ID {machine_id}") ``` Registration only mints the Identity NFT. The proxy mints the Machine NFT to the machine's address. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} await proxy.mintNft(machineId, machine.address); const nftTokenId = await proxy.tokenIdOf(machineId); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} proxy.mint_nft(machine_id, machine_address) nft_token_id = proxy.token_id_of(machine_id) ``` The machine signs its own DID writes, so its wallet needs a small amount of PEAQ for gas. Use the [Gas Station](/peaqos/concepts/gas-station) from the proxy (2FA-gated), or top it up directly from any PEAQ-holding address. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} 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, ); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} FAUCET_URL = "https://depinstation.peaq.network" proxy.fund_from_gas_station( owner_address=proxy.address, target_wallet_address=machine_address, chain_id="peaq", two_factor_code="654321", # Fresh TOTP code from the proxy operator's authenticator faucet_base_url=FAUCET_URL, ) ``` `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. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} 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", }); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Construct a per-machine client directly: same RPC and contracts # as the proxy, but signed with the machine's own key. No env mutation. machine_client = PeaqosClient( rpc_url=proxy.rpc_url, private_key=machine_key, identity_registry=proxy.contracts.identity_registry, identity_staking=proxy.contracts.identity_staking, event_registry=proxy.contracts.event_registry, machine_nft=proxy.contracts.machine_nft, did_registry=proxy.contracts.did_registry, batch_precompile=proxy.contracts.batch_precompile, ) machine_client.write_machine_did_attributes( machine_id=machine_id, nft_token_id=nft_token_id, operator_did=f"did:peaq:{proxy.address}", documentation_url="https://example.com/docs", data_api="https://example.com/events", data_visibility="public", ) ``` `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`](/peaqos/api-reference/get-operator-machines) endpoint reads from. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // 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 }); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # proxy_machine_id is the proxy operator's own machineId from its # self-registration (register_machine). Persist it once and reuse. proxy_machine_id = 7 # replace with the proxy's actual machine_id proxy.write_proxy_did_attributes( proxy_machine_id=proxy_machine_id, machine_ids=[machine_id], ) ``` See [Machine NFT ownership](/peaqos/concepts/machine-nft#ownership-semantics) 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. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} 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. ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import PeaqosClient proxy = PeaqosClient.from_env() proxy_machine_id = 7 # the proxy's own machineId from its self-registration FLEET_SIZE = 10 FAUCET_URL = "https://depinstation.peaq.network" fleet = [] for i in range(FLEET_SIZE): machine_address, machine_key = PeaqosClient.generate_keypair() try: machine_id = proxy.register_for(machine_address) proxy.mint_nft(machine_id, machine_address) nft_token_id = proxy.token_id_of(machine_id) proxy.fund_from_gas_station( owner_address=proxy.address, target_wallet_address=machine_address, chain_id="peaq", two_factor_code="654321", # Fresh TOTP code per call faucet_base_url=FAUCET_URL, ) # Build a per-machine client directly: same RPC and contracts # as the proxy, but signed with the machine's own key. machine_client = PeaqosClient( rpc_url=proxy.rpc_url, private_key=machine_key, identity_registry=proxy.contracts.identity_registry, identity_staking=proxy.contracts.identity_staking, event_registry=proxy.contracts.event_registry, machine_nft=proxy.contracts.machine_nft, did_registry=proxy.contracts.did_registry, batch_precompile=proxy.contracts.batch_precompile, ) machine_client.write_machine_did_attributes( machine_id=machine_id, nft_token_id=nft_token_id, operator_did=f"did:peaq:{proxy.address}", documentation_url="https://example.com/docs", data_api="https://example.com/events", data_visibility="public", ) fleet.append({ "address": machine_address, "private_key": machine_key, "machine_id": machine_id, }) print(f"[{i + 1}/{FLEET_SIZE}] Activated {machine_address} (machine_id {machine_id})") except Exception as err: print(f"[{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. proxy.write_proxy_did_attributes( proxy_machine_id=proxy_machine_id, machine_ids=[m["machine_id"] for m in fleet], ) print(f"Fleet: {len(fleet)} 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](/peaqos/cli#peaqos-activate). ## Per-machine key vs shared key Two approaches for managing machine keys in a proxy fleet: | Approach | How it works | Trade-offs | | :--------------- | :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | Per-machine key | Generate 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 key | All 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](/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. ```bash curl theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl "${PEAQOS_MCR_API_URL}/operator/did:peaq:0xProxyAddress/machines?offset=0&limit=20" ``` Response shape: ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "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](/peaqos/api-reference/get-operator-machines) for full details. ## Error handling | Error | Cause | Resolution | | :----------------------------------------------------------------------------- | :----------------------------------------------------- | :--------------------------------------------------------------------------------- | | `ValidationError: machineAddress must be a valid 0x-prefixed Ethereum address` | Malformed address string | Check the address format (0x prefix, 40 hex characters) | | `RuntimeError: AlreadyRegistered` / `RpcError: already registered` | The machine address is already on the IdentityRegistry | Skip this address, or query the MCR API to find the existing machine ID | | `RuntimeError: InvalidMachineAddress` / `RpcError: zero address` | Attempted to register the zero address | Pass a valid machine address | | Insufficient balance | Proxy wallet does not hold enough PEAQ for bond + gas | Top up the proxy wallet. For a batch of N machines, ensure at least N \* 1.1 PEAQ. | # Query Machine Credit Rating Source: https://docs.peaq.xyz/peaqos/guides/query-mcr Fetch a machine's MCR from the peaqOS MCR API. Covers curl, JavaScript, and Python. The MCR API returns a machine's credit rating along with event counts, revenue trend, and bond status. It's a public read API. No authentication required. ## Endpoint ``` GET {PEAQOS_MCR_API_URL}/mcr/{did} ``` `{did}` accepts either a full DID (`did:peaq:0xabc...`) or a raw EVM address (`0xabc...`). Set `PEAQOS_MCR_API_URL` to the root of the MCR API server. The public peaq-hosted MCR is at `https://mcr.peaq.xyz`. Self-hosted deployments default to `http://127.0.0.1:8000`. ## Fetch the MCR ```bash curl theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl -s "${PEAQOS_MCR_API_URL}/mcr/did:peaq:0xabc1230000000000000000000000000000000001" ``` ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const did = "did:peaq:0xabc1230000000000000000000000000000000001"; const response = await fetch( `${PEAQOS_MCR_API_URL}/mcr/${encodeURIComponent(did)}` ); if (!response.ok) { throw new Error(`MCR API returned ${response.status}`); } const mcr = await response.json(); console.log("Rating:", mcr.mcr); console.log("Score:", mcr.mcr_score); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os import requests did = "did:peaq:0xabc1230000000000000000000000000000000001" response = requests.get( f"{os.environ['PEAQOS_MCR_API_URL']}/mcr/{did}" ) response.raise_for_status() mcr = response.json() print("Rating:", mcr["mcr"]) print("Score:", mcr["mcr_score"]) ``` ## Response shape ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "did": "did:peaq:0xabc1230000000000000000000000000000000001", "machine_id": 1, "mcr_score": 45, "mcr": "BB", "mcr_degraded": false, "bond_status": "bonded", "negative_flag": false, "event_count": 12, "revenue_event_count": 7, "activity_event_count": 5, "revenue_trend": "stable", "total_revenue": 35000, "average_revenue_per_event": 5000.0, "last_updated": 1711900000 } ``` `total_revenue` and `average_revenue_per_event` are USD cents. Divide by 100 for display ($350 and $50 in this example). A newly registered machine that hasn't accumulated enough history returns `mcr: "Provisioned"` with `mcr_score: 0`. Unbonded machines return `mcr: "NR"` with `mcr_score: 0`. See [GET /mcr/\{did}](/peaqos/api-reference/get-mcr) for the full field reference. ## Query an operator's fleet Use the operator endpoint to list all machines registered under a proxy operator, paginated with MCR scores per machine. ```bash curl theme={"theme":{"light":"github-light-default","dark":"github-dark"}} curl -s "${PEAQOS_MCR_API_URL}/operator/did:peaq:0xProxyAddress/machines?offset=0&limit=20" ``` ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const operatorDid = "did:peaq:0xProxyAddress"; const response = await fetch( `${PEAQOS_MCR_API_URL}/operator/${encodeURIComponent(operatorDid)}/machines?offset=0&limit=20` ); const { machines } = await response.json(); for (const m of machines) { console.log(`Machine ${m.machine_id}: ${m.mcr}`); } ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import os import requests operator_did = "did:peaq:0xProxyAddress" response = requests.get( f"{os.environ['PEAQOS_MCR_API_URL']}/operator/{operator_did}/machines", params={"offset": 0, "limit": 20}, ) response.raise_for_status() for m in response.json()["machines"]: print(f"Machine {m['machine_id']}: {m['mcr']}") ``` See [GET /operator/\{did}/machines](/peaqos/api-reference/get-operator-machines) for the full response shape and pagination details. ## Caching The server applies a 1-hour TTL on MCR responses by default, configurable via the `MCR_CACHE_TTL` env var (`0` to disable). Repeat requests within the window return cached values. The `last_updated` field tells you when the underlying events were most recently added. ## Error handling The MCR API returns standard HTTP status codes: `404` when the DID is unregistered, `503` when the chain is unavailable, `400` for malformed inputs. Bodies are JSON with a `detail` field carrying the upstream message. If you call through the SDK (`client.queryMcr` / `client.query_mcr`), HTTP failures surface as `RuntimeError` (JS) or `ApiError` (Python). The `code` attribute carries `NOT_FOUND`, `SERVICE_UNAVAILABLE`, `SERVER_ERROR`, `TIMEOUT`, etc. See [SDK errors reference](/peaqos/sdk-reference/errors) for the full code map. ## Next steps * [GET /mcr/\{did}](/peaqos/api-reference/get-mcr): full API reference for this endpoint * [GET /machine/\{did}](/peaqos/api-reference/get-machine): full machine profile * [Machine Credit Rating concept](/peaqos/concepts/machine-credit-rating): rating scale and lifecycle # ROS 2 machine runtime Source: https://docs.peaq.xyz/peaqos/guides/ros2-machine-runtime Run peaqOS machine onboarding, MCR, events, smart accounts, and Machine NFT bridge flows from ROS 2. This guide is for teams that use ROS 2 as the robot control plane and want peaqOS as the machine identity, credit, and asset layer. The ROS 2 node wraps the same peaqOS capabilities documented in the [JavaScript SDK](/peaqos/sdk-reference/sdk-js) and [Python SDK](/peaqos/sdk-reference/sdk-python), but changes the security boundary: callers pass EVM addresses over ROS, while signing keys stay in a local registry file. ## Prerequisites * ROS 2 Jazzy on a native host, or the repository Docker image with ROS 2 Humble * A peaq EVM RPC endpoint * `https://mcr.peaq.xyz` for MCR reads * `https://depinstation.peaq.network` for Gas Station flows * A funded peaq EVM signer for registration, minting, events, smart accounts, and bridge transactions * Base ETH on the signer only if you need Base to peaq bridge operations ## Configure Create a local config: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} cp peaq_ros2_examples/config/peaq_robot.example.yaml \ peaq_ros2_examples/config/peaq_robot.yaml ``` Set the peaqOS section: ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaq_os: enabled: true rpc_url: "https://quicknode1.peaq.xyz" api_url: "https://mcr.peaq.xyz" faucet: base_url: "https://depinstation.peaq.network" qr_format: "svg" wallet_registry: path: "~/.peaq_robot/peaqos_wallets.json" ``` Do not commit `peaq_robot.yaml` after adding real addresses, local registry paths, or operational credentials. ## Build and start ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} source /opt/ros/jazzy/setup.bash # In the Docker image, use: source /opt/ros/humble/setup.bash python3 -m pip install -r requirements.txt colcon build --packages-select peaq_ros2_interfaces peaq_ros2_peaqos peaq_ros2_examples source install/setup.bash ros2 run peaq_ros2_peaqos peaqos_node --ros-args \ -p config.yaml_path:=peaq_ros2_examples/config/peaq_robot.yaml ``` Open another terminal, source the same ROS environment, then call services. ## 1. Create a local machine wallet ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/wallet/create \ peaq_ros2_interfaces/srv/PeaqosCreateWallet \ "{label: 'robot-001'}" ``` Save the returned address. The private key is stored in the local registry and is not returned. You can inspect or remove local wallet metadata without exposing keys: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/wallet/list \ peaq_ros2_interfaces/srv/PeaqosListWallets \ "{}" ros2 service call /peaqos_node/wallet/get \ peaq_ros2_interfaces/srv/PeaqosGetWallet \ "{address: ''}" ``` The returned wallet JSON includes public peaq EVM account metadata: `address`, `account_id`, `chain_id`, `network`, `label`, and `created_at`. ## 2. Fund the machine wallet Registration signs from the machine wallet and requires enough native peaq for gas plus the IdentityRegistry registration bond. Fund the returned `` before calling `register`. You can use the Gas Station flow with owner 2FA: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/faucet/setup_2fa \ peaq_ros2_interfaces/srv/PeaqosSetupFaucet2FA \ "{owner_address: '', qr_format: 'svg'}" ros2 service call /peaqos_node/faucet/confirm_2fa \ peaq_ros2_interfaces/srv/PeaqosConfirmFaucet2FA \ "{owner_address: '', two_factor_code: '123456'}" ros2 service call /peaqos_node/wallet/fund \ peaq_ros2_interfaces/srv/PeaqosFundWallet \ "{owner_address: '', target_address: '', chain_id: '3338', two_factor_code: '123456', request_id: ''}" ``` Or transfer native peaq directly to `` from an already funded wallet. Wait for the balance to be available before registration. ## 3. Register the machine For a self-managed machine, the node looks up `` in the local registry and signs the registration transaction with that wallet. For a self-managed machine: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/machine/register \ peaq_ros2_interfaces/srv/PeaqosRegisterMachine \ "{address: ''}" ``` For a proxy operator: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/machine/register_for \ peaq_ros2_interfaces/srv/PeaqosRegisterFor \ "{proxy_address: '', machine_address: ''}" ``` Registration returns `machine_id`. This is the IdentityRegistry machine ID. It is not necessarily the Machine NFT token ID. ## 4. Mint the Machine NFT and write DID attributes ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/nft/mint \ peaq_ros2_interfaces/srv/PeaqosMintNft \ "{signer_address: '', machine_id: 1, recipient: ''}" ``` Read the Machine NFT token ID: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/nft/token_id_of \ peaq_ros2_interfaces/srv/PeaqosTokenIdOf \ "{signer_address: '', machine_id: 1}" ``` Link the machine's DID to its Machine NFT and metadata endpoints: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/did/write_machine_attributes \ peaq_ros2_interfaces/srv/PeaqosWriteMachineDidAttributes \ "{signer_address: '', machine_id: 1, nft_token_id: 1, operator_did: 'did:peaq:', documentation_url: 'https://docs.example/robot-001', data_api: 'https://api.example/robot-001', data_visibility: 'onchain'}" ``` Verify a single attribute: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/did/read_attribute \ peaq_ros2_interfaces/srv/PeaqosReadDidAttribute \ "{signer_address: '', did_address: '', name: 'machineId'}" ``` ## 5. Query MCR and machine profile ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/mcr/query \ peaq_ros2_interfaces/srv/PeaqosQueryMcr \ "{did: 'did:peaq:'}" ``` ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/mcr/machine \ peaq_ros2_interfaces/srv/PeaqosQueryMachine \ "{did: 'did:peaq:'}" ``` Newly registered machines can return `Provisioned` until enough event history exists. ## 6. Validate and submit events Validate before broadcasting: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/events/validate \ peaq_ros2_interfaces/srv/PeaqosValidateEvent \ "{machine_id: 1, event_type: 1, value: 1, timestamp: 1770000000, raw_data_hex: '0x73656e736f723a6f6b', trust_level: 0, source_chain_id: 3338, source_tx_hash: '', metadata_hex: '0x7b7d'}" ``` Submit one event: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/events/submit \ peaq_ros2_interfaces/srv/PeaqosSubmitEvent \ "{signer_address: '', machine_id: 1, event_type: 1, value: 1, timestamp: 1770000000, raw_data_hex: '0x73656e736f723a6f6b', trust_level: 0, source_chain_id: 3338, source_tx_hash: '', metadata_hex: '0x7b7d'}" ``` Batch submission uses `events_json`, a JSON array using the same event field names. ## 7. Predict or deploy a machine smart account ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/smart_account/address \ peaq_ros2_interfaces/srv/PeaqosGetSmartAccountAddress \ "{signer_address: '', owner: '', machine: '', daily_limit: '', salt: '0'}" ``` Deploy with the same parameters: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/smart_account/deploy \ peaq_ros2_interfaces/srv/PeaqosDeploySmartAccount \ "{signer_address: '', owner: '', machine: '', daily_limit: '', salt: '0'}" ``` ## 8. Bridge Machine NFT from peaq to Base ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/bridge/nft \ peaq_ros2_interfaces/srv/PeaqosBridgeNft \ "{signer_address: '', token_id: 1, source: 'peaq', destination: 'base', recipient: '', base_rpc_url: '', base_nft_address: '', options_hex: ''}" ``` Wait for the NFT to appear on Base: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/bridge/wait_arrival \ peaq_ros2_interfaces/srv/PeaqosWaitForBridgeArrival \ "{dst_rpc_url: 'https://mainnet.base.org', dst_nft_address: '0xee8A521eA434b11F956E2402beC5eBfa753Babfa', token_id: 1, timeout: 900}" ``` ## Production checklist * Keep `peaq_os.wallet_registry.path` local to the robot or machine. * Keep the wallet registry file permissioned as `0600`. * Quote all EVM addresses in ROS YAML service payloads. * Record pre and post balances for production bridge or payment tests. * Confirm contract addresses before release if peaqOS publishes a new deployment. * Fund Base ETH before attempting Base to peaq bridge operations. ## References * [ROS 2 service catalog](/peaqos/sdk-reference/ros2/services) * [ROS 2 configuration](/peaqos/sdk-reference/ros2/configuration) * [Machine NFT concept](/peaqos/concepts/machine-nft) * [Events concept](/peaqos/concepts/events) * [Python SDK reference](/peaqos/sdk-reference/sdk-python) * [JavaScript SDK reference](/peaqos/sdk-reference/sdk-js) # Self-managed onboarding Source: https://docs.peaq.xyz/peaqos/guides/self-managed-onboarding Register a single machine where the owner is the operator. Generate keys, set up 2FA, fund via Gas Station, and call registerMachine. Owner equals operator. One keypair, one machine, one identity. This guide covers the full flow from environment setup through on-chain registration. ## Prerequisites * Node.js ≥ 22 or Python 3.10+ * A funded EVM wallet (the owner address) with at least 1.1 PEAQ (1 PEAQ bond + gas) * Environment variables configured per the [install guide](/peaqos/install) ## Environment setup Set these environment variables before running any SDK call. Both SDKs read the same variable names. ```bash .env theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # peaq mainnet RPC. See /peaqos/install#public-rpc-endpoints for alternatives PEAQOS_RPC_URL=https://peaq.api.onfinality.io/public PEAQOS_PRIVATE_KEY=0x<64-hex-chars> # Mainnet contracts. fromEnv() requires every variable below to be set. # For agung testnet addresses see /peaqos/install#agung-testnet-contracts. IDENTITY_REGISTRY_ADDRESS=0xb53Af985765031936311273599389b5B68aC9956 IDENTITY_STAKING_ADDRESS=0x11c05A650704136786253e8685f56879A202b1C7 EVENT_REGISTRY_ADDRESS=0x43c6c12eecAf4fB3F164375A9c44f8a6Efc139b9 MACHINE_NFT_ADDRESS=0x2943F80e9DdB11B9Dd275499C661Df78F5F691F9 DID_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000800 BATCH_PRECOMPILE_ADDRESS=0x0000000000000000000000000000000000000805 ``` JS examples load the file via `import "dotenv/config"`. Python's `from_env()` reads from the shell, so export the file first with `set -a && source .env && set +a`. ## Full flow `fromEnv` reads all required variables and returns a configured client. Throws `ValidationError` if any variable is missing. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient } from "@peaqos/peaq-os-sdk"; const client = PeaqosClient.fromEnv(); console.log("Signer:", client.address); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from dotenv import load_dotenv from peaq_os_sdk import PeaqosClient load_dotenv() # load envs from .env file client = PeaqosClient.from_env() print("Signer:", client.address) ``` If the machine does not already have a wallet, generate one. This is a local operation with no chain interaction. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const keypair = PeaqosClient.generateKeypair(); console.log("Address:", keypair.address); console.log("Key:", keypair.privateKey); // Store the private key securely. It cannot be recovered. ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} address, private_key = PeaqosClient.generate_keypair() print("Address:", address) print("Key:", private_key) # Store the private key securely. It cannot be recovered. ``` If the machine already has a funded wallet, skip to step 5 and construct the client with that key directly. Steps 3-5 will fund the generated keypair. The Gas Station requires 2FA before it funds any address. Call `setupFaucet2FA` to start enrollment. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const FAUCET_URL = "https://depinstation.peaq.network"; const enrollment = await client.setupFaucet2FA( keypair.address, FAUCET_URL, ); console.log("OTPAuth URI:", enrollment.otpauthUri); console.log("QR image:", enrollment.qrImageUrl); // Scan the QR or paste the URI into your authenticator app. // The QR URL expires after roughly 2 minutes. ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} FAUCET_URL = "https://depinstation.peaq.network" enrollment = client.setup_faucet_2fa( owner_address=keypair.address, faucet_base_url=FAUCET_URL, ) print("OTPAuth URI:", enrollment["otpauth_uri"]) print("QR image:", enrollment["qr_image_url"]) # Scan the QR or paste the URI into your authenticator app. # The QR URL expires after roughly 2 minutes. ``` After adding the secret to your authenticator app, submit the current TOTP code. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} await client.confirmFaucet2FA( keypair.address, FAUCET_URL, "123456", // Replace with your actual TOTP code ); // No return value. Success means 2FA is active. ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} client.confirm_faucet_2fa( owner_address=keypair.address, faucet_base_url=FAUCET_URL, two_factor_code="123456", # Replace with your actual TOTP code ) # No return value. Success means 2FA is active. ``` Request initial gas from the Gas Station. The faucet either funds the wallet or skips if the balance is already sufficient. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const result = await client.fundFromGasStation( { ownerAddress: keypair.address, targetWalletAddress: keypair.address, chainId: "peaq", twoFactorCode: "654321", // Fresh TOTP code }, FAUCET_URL, ); if (result.status === "success") { console.log("Funded:", result.txHash, result.fundedAmount); } else { console.log("Skipped, current balance:", result.currentBalance); } ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} result = client.fund_from_gas_station( owner_address=address, target_wallet_address=address, chain_id="peaq", two_factor_code="654321", # Fresh TOTP code faucet_base_url=FAUCET_URL, ) if result["status"] == "success": print("Funded:", result["tx_hash"], result["funded_amount"]) else: print("Skipped, current balance:", result["current_balance"]) ``` Call `registerMachine` to register the machine on the IdentityRegistry. The call sends 1 PEAQ as a bond with the transaction. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} // Build a new client using the machine's own key const machineClient = new PeaqosClient({ rpcUrl: client.rpcUrl, privateKey: keypair.privateKey, contracts: client.contracts, }); const machineId = await machineClient.registerMachine(); console.log("Registered, machine ID:", machineId); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Build a new client using the machine's own key machine_client = PeaqosClient( rpc_url=client.rpc_url, private_key=private_key, identity_registry=client.contracts.identity_registry, identity_staking=client.contracts.identity_staking, event_registry=client.contracts.event_registry, machine_nft=client.contracts.machine_nft, did_registry=client.contracts.did_registry, batch_precompile=client.contracts.batch_precompile, ) machine_id = machine_client.register_machine() print("Registered, machine ID:", machine_id) ``` `registerMachine` mints the Identity NFT (`tokenId == machineId`) and stakes the bond. It does not mint the Machine NFT or write DID attributes; those are two separate calls in the next step. Call `mintNft` to create the machine's financial NFT, then `writeMachineDIDAttributes` to bind `machineId` and the new `nftTokenId` to the machine's DID. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const tx = await machineClient.mintNft(machineId, keypair.address); const nftTokenId = await machineClient.tokenIdOf(machineId); await machineClient.writeMachineDIDAttributes({ machineId, nftTokenId, operatorDid: "", documentationUrl: "https://example.com/docs", dataApi: "https://example.com/events", dataVisibility: "public", }); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} machine_client.mint_nft(machine_id, address) nft_token_id = machine_client.token_id_of(machine_id) machine_client.write_machine_did_attributes( machine_id=machine_id, nft_token_id=nft_token_id, operator_did="", documentation_url="https://example.com/docs", data_api="https://example.com/events", data_visibility="public", ) ``` The Machine NFT gets an independent `tokenId` separate from `machineId`. See [Machine NFT ownership](/peaqos/concepts/machine-nft#ownership-semantics) for the full rationale. After all three calls, the machine has a peaqID, an Identity NFT, a Machine NFT, and a 1 PEAQ bond. It starts as `Provisioned`: on-chain, but not yet rated. It will graduate onto the rated MCR scale once it accumulates enough revenue and activity events. ## Error handling | Error | Cause | Resolution | | :---------------------------------------------------------------------------------------------- | :--------------------------------------- | :----------------------------------------------------------------- | | `ValidationError: PEAQOS_PRIVATE_KEY is required` | Missing environment variable | Check your `.env` file and shell export | | `AlreadyRegistered` (both SDKs) | The address is already registered | Query the MCR API with the address to find the existing machine ID | | `RuntimeError: Invalid 2FA code` / `ApiError: Invalid 2FA code` | TOTP code expired or mistyped | Wait for a fresh code from your authenticator and retry | | `RuntimeError: 2FA locked` / `ApiError: 2FA locked` | Too many invalid 2FA attempts | Wait before retrying | | `RuntimeError: Faucet rate limit exceeded` / `ApiError: Faucet rate limit exceeded` | Too many funding requests | Wait and retry after a short interval | | `RuntimeError: Owner daily funding cap exceeded` / `ApiError: Owner daily funding cap exceeded` | Daily funding cap reached for this owner | Fund manually or wait until the cap resets | | Insufficient balance for gas + bond | Wallet does not hold 1 PEAQ + gas | Top up the machine wallet and retry | ## What the machine receives After completing the full flow (register + mint NFT + write DID attributes): * **peaqID** (W3C DID) anchoring the machine's identity * **Identity NFT** minted automatically at registration (`tokenId == machineId`) * **Machine NFT** (LayerZero V2 ONFT) minted via `mintNft`, with an independent `tokenId` * **1 PEAQ bond** deposited into the IdentityStaking contract * **Gas Station** funding (if requested) # Submit events Source: https://docs.peaq.xyz/peaqos/guides/submit-events Submit revenue and activity events to the EventRegistry. Covers validation, data hashing, event types, cross-chain patterns, and operational limits. Revenue events (type 0) and activity events (type 1) feed the [Machine Credit Rating](/peaqos/concepts/machine-credit-rating). Every event is validated client-side, hashed, and submitted on-chain to the EventRegistry contract. ## Event types | Type | Value | Purpose | Example | | :------- | :---- | :-------------------------------------------------- | :------------------------------------------------- | | Revenue | `0` | Records economic value generated by the machine | A claw machine collects \$5.00 from a play session | | Activity | `1` | Records operational activity with no direct revenue | A weather sensor reports a telemetry ping | ## Trust levels Each event carries a trust level describing how the data was attested. See [Trust levels](/peaqos/concepts/trust-levels) for the concept overview. | Level | Value | Meaning | Needs source tx hash? | | :------------------ | :---- | :-------------------------------------------------- | :-------------------- | | Self-reported | `0` | Machine self-reports. No external verification. | No | | On-chain verifiable | `1` | Event references a verifiable on-chain transaction. | Yes | | Hardware-signed | `2` | Event signed by tamper-resistant hardware. | No | ## Currency and value units `currency` is a first-class parameter on `submitEvent` / `submit_event`. Revenue events take a 3-10 char uppercase alphanumeric code (`USD`, `HKD`, `JPY`, …); activity events must pass `""`. The SDK applies a smart default when omitted on single-event submits (revenue → `"USD"`, activity → `""`); `batchSubmitEvents` / `batch_submit_events` requires it explicitly. `value` is an **ISO 4217 minor-unit integer**: | Currency | Subunit divisor | Example | | :--------------------------------------------------- | :-------------- | :----------------------- | | `USD`, `HKD`, `EUR` (and other 2-decimal currencies) | `100` | `$1.23 → value: 123` | | `JPY`, `KRW`, `VND` (no subunits) | `1` | `¥100 → value: 100` | | `BHD`, `KWD`, `OMR` (3-decimal) | `1000` | `BD 1.234 → value: 1234` | The MCR pipeline converts `value` to USD cents using the FX rate at `timestamp`. The converted amount surfaces on [`GET /machine/{did}`](/peaqos/api-reference/get-machine) as `usd_value` (USD cents integer) on revenue events when `data_visibility` is `onchain`. `amount_status` distinguishes `"ok"`, `"unsupported_currency"` (currency not in the FX whitelist), and `"fx_unavailable"` (degraded FX feed). Non-`"ok"` rows score conservatively and surface `mcr_degraded: true` on [`GET /mcr/{did}`](/peaqos/api-reference/get-mcr). Activity events ignore the FX path entirely. They don't accumulate revenue. ## Validation Call `validateSubmitEventParams` before submitting. It throws `ValidationError` on the first invalid field. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { validateSubmitEventParams, computeDataHash, EVENT_TYPE_REVENUE, TRUST_SELF_REPORTED, SUPPORTED_CHAIN_IDS, } from "@peaqos/peaq-os-sdk"; const params = { machineId: 1, eventType: EVENT_TYPE_REVENUE, // 0 value: 500, // $5.00 in cents currency: "USD", timestamp: Math.floor(Date.now() / 1000) - 10, rawData: new TextEncoder().encode(JSON.stringify({ session: "abc123" })), trustLevel: TRUST_SELF_REPORTED, // 0 sourceChainId: SUPPORTED_CHAIN_IDS.peaq, // 3338 sourceTxHash: null, metadata: new Uint8Array([]), }; // Throws ValidationError if any field is invalid validateSubmitEventParams(params); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import json import time from peaq_os_sdk import EVENT_TYPE_REVENUE, TRUST_SELF_REPORTED, SUPPORTED_CHAINS from peaq_os_sdk.types.events import SubmitEventParams from peaq_os_sdk.validation import validate_submit_event_params params = SubmitEventParams( machine_id=1, event_type=EVENT_TYPE_REVENUE, # 0 value=500, # $5.00 in cents currency="USD", timestamp=int(time.time()), raw_data=json.dumps({"session": "abc123"}).encode(), trust_level=TRUST_SELF_REPORTED, # 0 source_chain_id=SUPPORTED_CHAINS["peaq"], # 3338 source_tx_hash=None, metadata=b"", ) # Raises ValidationError if any field is invalid validate_submit_event_params(params) ``` ### Validation rules | Field | Constraint | Error if violated | | :------------------------------------- | :--------------------------------------------------- | :------------------------------------------------------------------------------------ | | `machineId` / `machine_id` | Positive integer | `machineId must be a positive integer` | | `eventType` / `event_type` | `0` or `1` | `eventType must be 0 or 1` | | `value` | Non-negative integer (ISO 4217 minor units) | `value must be non-negative` | | `currency` | Revenue: `^[A-Z0-9]{3,10}$`. Activity: must be `""`. | `currency must match ^[A-Z0-9]{3,10}$` / `currency must be empty for activity events` | | `trustLevel` / `trust_level` | `0`, `1`, or `2` | `trustLevel must be 0, 1, or 2` | | `sourceChainId` / `source_chain_id` | `0`, `3338`, or `8453` | `sourceChainId must be a supported chain ID` | | `rawData` / `raw_data` | Non-empty when provided | `rawData must not be empty when provided` | | `sourceTxHash` / `source_tx_hash` | 0x-prefixed 32-byte hex (66 chars) when provided | `sourceTxHash must be a 0x-prefixed 32-byte hex string` | | `timestamp` | Positive integer | `timestamp must be a positive integer` | | `sourceTxHash` when `trustLevel === 1` | Required | `sourceTxHash is required when trustLevel is 1` | The contract additionally rejects `metadata` larger than 4096 bytes with a `MetadataTooLarge` revert. The SDK validators don't enforce this client-side, so oversized payloads surface as a transaction failure (`RuntimeError`/`RpcError` with `code: "MetadataTooLarge"`) rather than `ValidationError`. ## Computing the data hash The EventRegistry stores a keccak256 hash of the raw data, not the data itself. Compute it with `computeDataHash` (JS) or `compute_data_hash` (Python). ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { computeDataHash } from "@peaqos/peaq-os-sdk"; const rawData = new TextEncoder().encode( JSON.stringify({ session: "abc123", amount: 500 }) ); const hash = computeDataHash(rawData); // hash: "0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658" ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.utils import compute_data_hash raw_data = b'{"session": "abc123", "amount": 500}' data_hash = compute_data_hash(raw_data) # data_hash is 32 bytes (keccak256) ``` The hash is passed as the `dataHash` field in the on-chain `MachineEvent` struct. Consumers who need to verify the original data compare its keccak256 against the stored hash. ## Submitting a revenue event (type 0) ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient, validateSubmitEventParams, computeDataHash, EVENT_TYPE_REVENUE, TRUST_SELF_REPORTED, SUPPORTED_CHAIN_IDS, } from "@peaqos/peaq-os-sdk"; const client = PeaqosClient.fromEnv(); const rawData = new TextEncoder().encode( JSON.stringify({ session: "abc123", amount: 500 }) ); const params = { machineId: 1, eventType: EVENT_TYPE_REVENUE, value: 500, // $5.00 in cents currency: "USD", timestamp: Math.floor(Date.now() / 1000) - 10, rawData, trustLevel: TRUST_SELF_REPORTED, sourceChainId: SUPPORTED_CHAIN_IDS.peaq, sourceTxHash: null, metadata: new Uint8Array([]), }; validateSubmitEventParams(params); const { txHash, dataHash } = await client.submitEvent(params); console.log("Submitted revenue event:", { txHash, dataHash }); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from dotenv import load_dotenv import json import time from peaq_os_sdk import ( PeaqosClient, EVENT_TYPE_REVENUE, TRUST_SELF_REPORTED, SUPPORTED_CHAINS, ) from peaq_os_sdk.types.events import SubmitEventParams from peaq_os_sdk.validation import validate_submit_event_params load_dotenv() # load envs from .env file client = PeaqosClient.from_env() raw_data = json.dumps({"session": "abc123", "amount": 500}).encode() params = SubmitEventParams( machine_id=1, event_type=EVENT_TYPE_REVENUE, value=500, # $5.00 in cents currency="USD", timestamp=int(time.time()) - 10, raw_data=raw_data, trust_level=TRUST_SELF_REPORTED, source_chain_id=SUPPORTED_CHAINS["peaq"], source_tx_hash=None, metadata=b"", ) validate_submit_event_params(params) tx_hash, data_hash = client.submit_event( machine_id=params.machine_id, event_type=params.event_type, value=params.value, currency=params.currency, timestamp=params.timestamp, raw_data=params.raw_data, trust_level=params.trust_level, source_chain_id=params.source_chain_id, source_tx_hash=params.source_tx_hash, metadata=params.metadata, ) print("Submitted revenue event:", tx_hash, data_hash.hex()) ``` ## Submitting an activity event (type 1) Activity events record operational telemetry with no direct revenue value. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient, validateSubmitEventParams, computeDataHash, EVENT_TYPE_ACTIVITY, TRUST_SELF_REPORTED, SUPPORTED_CHAIN_IDS, } from "@peaqos/peaq-os-sdk"; const client = PeaqosClient.fromEnv(); const rawData = new TextEncoder().encode( JSON.stringify({ type: "heartbeat", uptimeSeconds: 86400 }) ); const params = { machineId: 1, eventType: EVENT_TYPE_ACTIVITY, // 1 value: 0, // No revenue currency: "", // activity events must be empty timestamp: Math.floor(Date.now() / 1000) - 10, rawData, trustLevel: TRUST_SELF_REPORTED, sourceChainId: SUPPORTED_CHAIN_IDS.peaq, sourceTxHash: null, metadata: new Uint8Array([]), }; validateSubmitEventParams(params); const { txHash, dataHash } = await client.submitEvent(params); console.log("Submitted activity event:", { txHash, dataHash }); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from dotenv import load_dotenv import json import time from peaq_os_sdk import ( PeaqosClient, EVENT_TYPE_ACTIVITY, TRUST_SELF_REPORTED, SUPPORTED_CHAINS, ) from peaq_os_sdk.types.events import SubmitEventParams from peaq_os_sdk.validation import validate_submit_event_params load_dotenv() # load envs from .env file client = PeaqosClient.from_env() raw_data = json.dumps({"type": "heartbeat", "uptime_seconds": 86400}).encode() params = SubmitEventParams( machine_id=1, event_type=EVENT_TYPE_ACTIVITY, # 1 value=0, # No revenue currency="", # activity events must be empty timestamp=int(time.time()) - 10, raw_data=raw_data, trust_level=TRUST_SELF_REPORTED, source_chain_id=SUPPORTED_CHAINS["peaq"], source_tx_hash=None, metadata=b"", ) validate_submit_event_params(params) tx_hash, data_hash = client.submit_event( machine_id=params.machine_id, event_type=params.event_type, value=params.value, currency=params.currency, timestamp=params.timestamp, raw_data=params.raw_data, trust_level=params.trust_level, source_chain_id=params.source_chain_id, source_tx_hash=params.source_tx_hash, metadata=params.metadata, ) print("Submitted activity event:", tx_hash, data_hash.hex()) ``` ## Cross-chain revenue pattern When a machine earns revenue on another chain (e.g., Base), reference the source transaction for on-chain verifiable trust (level 1). ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { validateSubmitEventParams, EVENT_TYPE_REVENUE, TRUST_ON_CHAIN_VERIFIABLE, SUPPORTED_CHAIN_IDS, } from "@peaqos/peaq-os-sdk"; const params = { machineId: 1, eventType: EVENT_TYPE_REVENUE, value: 1200, // $12.00 in cents currency: "USD", timestamp: Math.floor(Date.now() / 1000) - 10, rawData: new TextEncoder().encode(JSON.stringify({ invoice: "INV-0042" })), trustLevel: TRUST_ON_CHAIN_VERIFIABLE, // 1 sourceChainId: SUPPORTED_CHAIN_IDS.base, // 8453 sourceTxHash: "0xa1b2c3d4e5f6789000000000000000000000000000000000000000000000a1b2", metadata: new Uint8Array([]), }; validateSubmitEventParams(params); // sourceTxHash is required when trustLevel is 1. // The MCR system can verify this transaction on Base. ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import json import time from peaq_os_sdk import ( EVENT_TYPE_REVENUE, TRUST_ON_CHAIN_VERIFIABLE, SUPPORTED_CHAINS, ) from peaq_os_sdk.types.events import SubmitEventParams from peaq_os_sdk.validation import validate_submit_event_params params = SubmitEventParams( machine_id=1, event_type=EVENT_TYPE_REVENUE, value=1200, # $12.00 in cents currency="USD", timestamp=int(time.time()), raw_data=json.dumps({"invoice": "INV-0042"}).encode(), trust_level=TRUST_ON_CHAIN_VERIFIABLE, # 1 source_chain_id=SUPPORTED_CHAINS["base"], # 8453 source_tx_hash="0xa1b2c3d4e5f6789000000000000000000000000000000000000000000000a1b2", metadata=b"", ) validate_submit_event_params(params) # source_tx_hash is required when trust_level is 1. # The MCR system can verify this transaction on Base. ``` ### Supported chain IDs | Chain | ID | | :---------------------------- | :----- | | peaq (same-chain, or use `0`) | `3338` | | Base | `8453` | ## Operational limits The SDK enforces per-transaction value caps and rate limits before submitting. ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { checkOperationalLimits } from "@peaqos/peaq-os-sdk"; checkOperationalLimits( { machineId: 1, value: 500 }, { maxValuePerTx: 10000, rateLimitMaxEvents: 60, rateLimitWindowSeconds: 3600, }, tracker, // EventTracker from previous submissions, or null ); // Throws ValueCapExceeded if value > maxValuePerTx // Throws RateLimitExceeded if count >= rateLimitMaxEvents within window ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.types.client import OperationalLimits from peaq_os_sdk.validation import check_operational_limits check_operational_limits( params, # SubmitEventParams (or any object with .machine_id and .value) OperationalLimits( max_value_per_tx=10000, rate_limit_max_events=60, rate_limit_window_seconds=3600, ), tracker, # EventTracker from previous submissions, or None ) # Raises ValueCapExceeded if value > max_value_per_tx # Raises RateLimitExceeded if count >= rate_limit_max_events within window ``` | Limit | Error type | Description | | :--------------------------------------------- | :------------------ | :----------------------------------------------------- | | `maxValuePerTx` / `max_value_per_tx` | `ValueCapExceeded` | Single event value exceeds the per-transaction cap | | `rateLimitMaxEvents` / `rate_limit_max_events` | `RateLimitExceeded` | Too many events submitted within the rate-limit window | Set limits to `0` to disable (the default). ## Error handling `submitEvent` / `submit_event` raise four distinct error types. Validation and limit errors are local; `RuntimeError` (JS) / `RpcError` (Python) wraps every chain or RPC failure. JS collapses chain and HTTP errors into a single `RuntimeError`; Python keeps them separate (`RpcError` for chain, `ApiError` for HTTP). ```typescript JS/TS theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { ValidationError, ValueCapExceeded, RateLimitExceeded, RuntimeError, } from "@peaqos/peaq-os-sdk"; try { const { txHash, dataHash } = await client.submitEvent(params); } catch (err) { if (err instanceof ValidationError) { // Bad params: check err.field, err.constraint } else if (err instanceof ValueCapExceeded) { // Per-tx value cap tripped } else if (err instanceof RateLimitExceeded) { // Local rate limit tripped } else if (err instanceof RuntimeError) { // Chain/RPC failure: err.code carries the contract revert name // (e.g. "MetadataTooLarge", "MachineNotFound", "NotAuthorizedSubmitter") // or "TX_REVERTED" for unrecognized reverts } else { throw err; } } ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import ( ValidationError, ValueCapExceeded, RateLimitExceeded, RpcError, ) try: tx_hash, data_hash = client.submit_event( machine_id=params.machine_id, event_type=params.event_type, value=params.value, currency=params.currency, timestamp=params.timestamp, raw_data=params.raw_data, trust_level=params.trust_level, source_chain_id=params.source_chain_id, source_tx_hash=params.source_tx_hash, metadata=params.metadata, ) except ValidationError as err: # Bad params: inspect err.field, err.constraint raise except ValueCapExceeded: # Per-tx value cap tripped raise except RateLimitExceeded: # Local rate limit tripped raise except RpcError as err: # Chain/RPC failure: err.code carries the contract revert name # (e.g. "MetadataTooLarge", "MachineNotFound") or "TX_REVERTED" raise ``` See [SDK errors reference](/peaqos/sdk-reference/errors) for the full code map and the cross-language equivalence between `RuntimeError` (JS) and `RpcError`/`ApiError` (Python). ## Next steps * [Events concept](/peaqos/concepts/events) for deeper coverage of event types and trust levels * [SDK reference](/peaqos/sdk-reference/sdk-js) for full method signatures # Install Source: https://docs.peaq.xyz/peaqos/install Install peaqOS via npm or pip. Pick the SDK that matches your stack. Both paths converge on the same onchain state. ## Requirements | Requirement | Value | | :-------------- | :-------------------------------------------------------------------- | | Node.js | ≥ 22 | | TypeScript | ≥ 5 (for the JS SDK) | | Python | ≥ 3.10 (for the Python SDK) | | Peer dependency | `viem >= 2.47.10` (JS only; Python pulls `web3 >= 6.0` automatically) | | RPC access | peaq mainnet or agung testnet | | Gas | Handled by Gas Station on fresh wallets; needs 2FA for request | ## Install paths ### Two pieces ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # 1. Install the CLI (the skill drives it under the hood) pip install peaq-os-cli # 2. Add the peaqos skill to your agent (auto-detects Claude Code, Cursor, or Windsurf) npx @peaqos/skills add peaqos ``` Then invoke `/peaqos` in any Claude Code session. To target a specific runtime explicitly, add `--agent claude-code | cursor | windsurf`. See the [peaqOS AI page](/peaqos/peaqos-ai) for details and the manual upload path for ChatGPT / custom GPTs. ### Works with The `@peaqos/skills` installer ships first-class adapters for Claude Code, Cursor, and Windsurf — auto-detected, or selected with `--agent`. Hosted assistants without a local CLI (ChatGPT, Claude Projects, custom GPTs) can load `AGENT-PROMPT.md` as a system prompt manually. The skill calls the JavaScript and Python SDKs through the CLI. Tabs below apply once you start editing code yourself. ### One command ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install peaq-os-cli # Optional: include OWS wallet commands (peaqos wallet create / import / list / …) pip install 'peaq-os-cli[ows]' peaqos init && peaqos activate --doc-url "https://example.com/docs" --data-api "https://example.com/events" ``` Drives the same flows as the SDKs from your terminal: `peaqos init`, `peaqos whoami`, `peaqos activate`, `peaqos qualify event`, `peaqos qualify mcr`, `peaqos show machine`, `peaqos show operator machines`, `peaqos wallet`. See [peaqOS CLI](/peaqos/cli) for the full command reference. CLI and SDK wallet helpers live on [Wallets (OWS)](/peaqos/wallets). ### Package ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install @peaqos/peaq-os-sdk viem dotenv ``` OWS wallet helpers (`createWallet`, `importWallet`, `PeaqosClient.fromWallet`, …) work out of the box; `@open-wallet-standard/core` is bundled as a regular dependency of `@peaqos/peaq-os-sdk`. See [Wallets (OWS)](/peaqos/wallets) for the full surface. `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`. ### Language + runtime * Node.js ≥ 22 * TypeScript ≥ 5 * Package exports both ESM and CJS builds; no bundler workarounds required. ### Imports ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient } from "@peaqos/peaq-os-sdk"; ``` Full method reference on [SDK JS](/peaqos/sdk-reference/sdk-js). Error class hierarchy on [errors](/peaqos/sdk-reference/errors). ### Package ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} python3 -m venv .peaq-os source .peaq-os/bin/activate pip install "peaq-os-sdk>=0.0.2" python-dotenv # Optional: include the OWS wallet helpers (createWallet, importWallet, …) pip install "peaq-os-sdk[ows]>=0.0.2" ``` `python-dotenv` is optional but recommended: `PeaqosClient.from_env()` reads from dotenv, so `load_dotenv()` at the top of your entry file is the simplest way to load .env. ### Language + runtime * Python ≥ 3.10 * `web3.py` pulled in automatically. * Virtualenv strongly recommended. The commands above set one up. ### Imports ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import PeaqosClient ``` Full method reference on [SDK Python](/peaqos/sdk-reference/sdk-python). Error class hierarchy on [errors](/peaqos/sdk-reference/errors). ### Workspace ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} git clone https://github.com/peaqnetwork/peaq-robotics-ros2.git cd peaq-robotics-ros2 ``` ### Build ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} source /opt/ros/jazzy/setup.bash # Docker image: source /opt/ros/humble/setup.bash # Native host only: python3 -m pip install -r requirements.txt colcon build --packages-select peaq_ros2_interfaces peaq_ros2_peaqos peaq_ros2_examples source install/setup.bash ``` ### Run ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} cp peaq_ros2_examples/config/peaq_robot.example.yaml peaq_ros2_examples/config/peaq_robot.yaml # Edit peaq_ros2_examples/config/peaq_robot.yaml and set peaq_os.enabled: true. ros2 run peaq_ros2_peaqos peaqos_node --ros-args \ -p config.yaml_path:=peaq_ros2_examples/config/peaq_robot.yaml ``` Full method mapping on [ROS 2 SDK reference](/peaqos/sdk-reference/ros2/overview). End-to-end commands on [ROS 2 machine runtime](/peaqos/guides/ros2-machine-runtime). ## Environment variables | Variable | Required | Default | Purpose | | :-------------------------------- | :-------------- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `PEAQOS_RPC_URL` | Yes | n/a | peaq chain RPC endpoint | | `PEAQOS_PRIVATE_KEY` | Yes | n/a | Owner wallet private key (hex, `0x...`) | | `PEAQOS_NETWORK` | CLI only | n/a | `mainnet` or `testnet`. Selects address + URL defaults for the CLI's `peaqos init`. SDK clients ignore it. | | `PEAQOS_GAS_STATION_URL` | CLI only | n/a | Faucet base URL. Set to `https://depinstation.peaq.network` for the public peaq-hosted Gas Station. Required for the `peaqos activate` funding step unless `--skip-funding` is set or the wallet already meets the gas threshold. | | `OWS_PASSPHRASE` | No | n/a | OWS vault passphrase used by the SDK and CLI wallet helpers (`createWallet`, `importWallet`, `exportWallet`, …). The SDK raises `PeaqosError` if neither this nor the inline `passphrase` arg is set (no interactive prompt). The CLI reads it when `PEAQOS_OWS_WALLET` is set, prompting interactively if unset. See [Wallets (OWS)](/peaqos/wallets). | | `IDENTITY_REGISTRY_ADDRESS` | Yes | n/a | Identity Registry contract. See [peaq mainnet contracts](#peaq-mainnet-contracts) for the canonical address. | | `IDENTITY_STAKING_ADDRESS` | Yes | n/a | Identity Staking contract | | `EVENT_REGISTRY_ADDRESS` | Yes | n/a | Event Registry contract | | `MACHINE_NFT_ADDRESS` | Yes | n/a | Machine NFT (ONFT) contract | | `DID_REGISTRY_ADDRESS` | Yes | n/a | DID Registry precompile address (`0x...0800` on every peaq runtime) | | `BATCH_PRECOMPILE_ADDRESS` | Yes | n/a | Batch precompile for multi-call bonding (`0x...0805` on every peaq runtime) | | `MACHINE_ACCOUNT_FACTORY_ADDRESS` | No | n/a | `MachineAccountFactory`. Required only for smart-account deploy / predict. | | `MACHINE_NFT_ADAPTER_ADDRESS` | No | n/a | `MachineNFTAdapter` (LayerZero ONFT adapter). Required only for `bridge_nft` / `bridgeNft` when `source="peaq"`. | | `PEAQOS_OWS_WALLET` | CLI only | n/a | Active OWS vault wallet name. When set, the CLI loads the wallet via `OWS_PASSPHRASE` and skips `PEAQOS_PRIVATE_KEY`. Set by `peaqos wallet use` or `peaqos init` (`wallet` path). | | `PEAQOS_MCR_API_URL` | Set for mainnet | `http://127.0.0.1:8000` | MCR API base URL. The default is for self-hosted dev. Set to `https://mcr.peaq.xyz` to read from the public peaq-hosted MCR. | Full reference with defaults on [SDK JS environment](/peaqos/sdk-reference/sdk-js) and [SDK Python environment](/peaqos/sdk-reference/sdk-python). ## Public RPC endpoints Use any of the endpoints below for `PEAQOS_RPC_URL`. QuickNode is the primary set; OnFinality and PublicNode are fallbacks. All of them accept EVM JSON-RPC calls. For private dedicated endpoints, see the [QuickNode](https://www.quicknode.com/guides/quicknode-products/how-to-use-the-quicknode-dashboard#create-a-quicknode-endpoint) and [OnFinality](https://documentation.onfinality.io/support/the-enhanced-api-service) guides. Full list on [Connecting to peaq](/peaqchain/build/getting-started/connecting-to-peaq). ```bash peaq mainnet theme={"theme":{"light":"github-light-default","dark":"github-dark"}} https://quicknode1.peaq.xyz https://quicknode2.peaq.xyz https://quicknode3.peaq.xyz # Secondary / fallback https://peaq.api.onfinality.io/public https://peaq-rpc.publicnode.com ``` ```bash agung testnet theme={"theme":{"light":"github-light-default","dark":"github-dark"}} https://peaq-agung.api.onfinality.io/public https://wss-async-agung.peaq.xyz ``` ## peaq mainnet contracts Core contracts are UUPS upgradeable proxies; treat the addresses as the current proxy pointers. For the architecture diagram and what each contract is responsible for, see [Smart contracts](/peaqos/concepts/contracts). ### Required | Variable | Address | | :-------------------------- | :------------------------------------------------------------------- | | `IDENTITY_REGISTRY_ADDRESS` | `0xb53Af985765031936311273599389b5B68aC9956` | | `IDENTITY_STAKING_ADDRESS` | `0x11c05A650704136786253e8685f56879A202b1C7` | | `EVENT_REGISTRY_ADDRESS` | `0x43c6c12eecAf4fB3F164375A9c44f8a6Efc139b9` | | `MACHINE_NFT_ADDRESS` | `0x2943F80e9DdB11B9Dd275499C661Df78F5F691F9` | | `DID_REGISTRY_ADDRESS` | `0x0000000000000000000000000000000000000800` (peaq DID precompile) | | `BATCH_PRECOMPILE_ADDRESS` | `0x0000000000000000000000000000000000000805` (peaq batch precompile) | ### Optional Set only if you use the corresponding SDK method. | Variable | Address | Needed for | | :-------------------------------- | :------------------------------------------- | :------------------------------------------------------------------ | | `MACHINE_ACCOUNT_FACTORY_ADDRESS` | `0x4A808d5A90A2c91739E92C70aF19924e0B3D527f` | `deploySmartAccount` / `getSmartAccountAddress` (ERC-4337) | | `MACHINE_NFT_ADAPTER_ADDRESS` | `0x9AD5408702EC204441A88589B99ADfC2514AFAE6` | `bridgeNft` / `bridge_nft` when `source="peaq"` (LayerZero V2 ONFT) | ## Base mainnet contracts Needed when bridging **into** peaq from Base. Pass `baseNftAddress` / `base_nft_address` to the bridge method. | Contract | Address | | :-------------------------------- | :------------------------------------------- | | `MachineNFTBase` (LayerZero ONFT) | `0xee8A521eA434b11F956E2402beC5eBfa753Babfa` | ## Agung testnet contracts Use these to point the SDK or CLI at agung. Precompile addresses are identical to mainnet (same fixed slots on every peaq runtime). ### Required | Variable | Address | | :-------------------------- | :------------------------------------------------------------------- | | `IDENTITY_REGISTRY_ADDRESS` | `0x9E9463a65c7B74623b3b6Cdc39F71be7274e5971` | | `IDENTITY_STAKING_ADDRESS` | `0x55f336714aDb0749DbFE33b057a1702405564E3d` | | `EVENT_REGISTRY_ADDRESS` | `0x2DAD8905380993940e340C5cE6d313d5c2780040` | | `MACHINE_NFT_ADDRESS` | `0xB41C2A4f1c19b6B06beaAce0F5CD8439e77C4b1c` | | `DID_REGISTRY_ADDRESS` | `0x0000000000000000000000000000000000000800` (peaq DID precompile) | | `BATCH_PRECOMPILE_ADDRESS` | `0x0000000000000000000000000000000000000805` (peaq batch precompile) | ### Optional | Variable | Address | Needed for | | :-------------------------------- | :------------------------------------------- | :------------------------------------------------------------------ | | `MACHINE_ACCOUNT_FACTORY_ADDRESS` | `0x65a4DfEB799dFf8CF15f13816d648a7805d6b1F9` | `deploySmartAccount` / `getSmartAccountAddress` (ERC-4337) | | `MACHINE_NFT_ADAPTER_ADDRESS` | `0x63fD7e64A38e50D1486Bc569B4CaCeD38528De22` | `bridgeNft` / `bridge_nft` when `source="peaq"` (LayerZero V2 ONFT) | | `ADMIN_FLAGS_ADDRESS` | `0x4181a2Aa34aFb247450FfcBd65be5aBD4Cbee658` | MCR API server (negative-flag + trust-override reads) | **Bridging cannot be exercised on agung.** `MachineNFTAdapter` is deployed, but LayerZero V2 has no DVN routes between agung and Base. `bridgeNft` / `bridge_nft` calls will not relay end-to-end. Test the bridge on peaq mainnet ↔ Base mainnet only. ## Troubleshooting The OTP from your authenticator app was wrong or expired. Generate a fresh code and retry. See full [error code reference](/peaqos/sdk-reference/errors). The faucet throttles owners after repeated bad OTPs. Wait out the lockout window and retry. See [error code reference](/peaqos/sdk-reference/errors). The faucet enforces a per-IP and per-wallet cap. Retry after the cooldown. See [error code reference](/peaqos/sdk-reference/errors). The same owner address has hit the daily funding cap. Wait 24 hours or contact support. See [error code reference](/peaqos/sdk-reference/errors). Set `PEAQOS_RPC_URL` in your environment to a valid peaq chain RPC endpoint (e.g., `https://peaq.api.onfinality.io/public`). Without it, `PeaqosClient.fromEnv()` raises a `ValidationError`. See [Public RPC endpoints](#public-rpc-endpoints) for the full list. Full error taxonomy on [errors](/peaqos/sdk-reference/errors). # peaqOS overview Source: https://docs.peaq.xyz/peaqos/overview The machine economy runs on peaqOS. peaqOS is the operating system for the machine economy. It gives robots and machines an on-chain identity, a credit rating, and the infrastructure to earn, transact, and become investable across chains. ## peaqOS is omnichain peaqOS contracts hold the canonical record of identity and credit on peaq chain. Any chain can query them; any chain can consume them. Your machine gets a peaqID and Machine NFT on peaq chain. Revenue and activity events feed into a Machine Credit Rating. Any chain can query identity and credit from the MCR API. Machine NFTs bridge between peaq and Base via LayerZero V2 ONFT; additional peaqOS chains are added as peer contracts deploy. ## Functions Put your machine on-chain: peaqID, Machine NFT, and bond. Self-managed and proxy operator onboarding patterns both supported. Read the [Activate function](/peaqos/functions/activate). Machine Credit Rating built from a machine's revenue and activity history. The MCR API exposes ratings to any chain. Read the [Qualify function](/peaqos/functions/qualify). Pair an AI agent that transacts and consumes services on your machine's behalf. Fleet orchestration and omnichain expansion. Read the [Scale function](/peaqos/functions/scale). Prove a machine is real via hardware attestation and trusted third parties. Read the [Verify function](/peaqos/functions/verify). List your machine's services. Other machines and agents discover and buy them. Read the [Monetize function](/peaqos/functions/monetize). Fractionalize your machine into an investable asset via ERC-3643. Read the [Tokenize function](/peaqos/functions/tokenize). ## Two onboarding patterns Owner equals operator. One machine, one wallet, `registerMachine`. One owner runs a fleet. Register many machines via `registerFor`. ## Keep going TypeScript, Python, and ROS 2 signatures, parameters, returns, errors. Run peaqOS from robot-native ROS 2 services with local key custody. Query MCR and machine profiles from any chain. Onboarding, fleets, event submission. # peaqOS AI Source: https://docs.peaq.xyz/peaqos/peaqos-ai Use the peaqOS agent skill to onboard machines, query MCR, and run fleet workflows from any AI coding agent. peaqOS ships one curated agent skill — `peaqos` — that turns your AI agent into a peaqOS onboarding co-pilot. It's a [skill.md](https://agentskills.io/specification)-spec skill with first-class adapters for Claude Code, Cursor, and Windsurf, plus a manual upload path for any other runtime that reads the standard. Under the hood it drives the [`peaqos` Python CLI](/peaqos/cli) — anything you can do at the terminal, the skill can do for you with the right questions asked first. ## What the skill does Trigger it in your agent (`/peaqos` in Claude Code, or just describe what you want elsewhere) and it picks the right mode based on what you ask: | Mode | When it kicks in | | :------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Demo** | First-time tour on agung testnet — full onboarding in \~15 minutes with explanations at every step. | | **Real onboarding** | Asks five questions about your machine and deployment, recommends self-managed or proxy-managed architecture, then runs the CLI to register, mint the Machine NFT, and submit the first event. | | **Fleet management** | Pulls MCR scores for an operator's fleet, surfaces machines with low or no rating, submits heartbeat events. | | **Troubleshooting** | Diagnoses common failures — funding, activation, MCR lag, key mismatches — and walks you through the fix. | The skill adapts its tone to your background: concise for developers, plain English with narrated steps for non-technical operators. ## Install Two pieces. The CLI does the actual work; the skill is the orchestration layer your agent loads. ### 1. Install the CLI ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} python3 -m venv .peaqos-env source .peaqos-env/bin/activate pip install peaq-os-cli peaqos --version ``` Python 3.10 or newer is required. ### 2. Add the skill to your agent ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Auto-detect (Claude Code, Cursor, or Windsurf) npx @peaqos/skills add peaqos # Or target a specific runtime npx @peaqos/skills add peaqos --agent claude-code npx @peaqos/skills add peaqos --agent cursor npx @peaqos/skills add peaqos --agent windsurf ``` The installer ships adapters for Claude Code, Cursor, and Windsurf, auto-detecting the agent it finds on disk (or prompting if multiple are installed). Invoke `/peaqos` (Claude Code) or just describe what you want (e.g. *"onboard my machine to peaqOS"*) in Cursor or Windsurf. For ChatGPT, Claude Projects, custom GPTs, or anywhere you can't run a local CLI, clone the repo and upload `AGENT-PROMPT.md` as the system prompt with `knowledge/` and `GUIDE.md` as knowledge sources: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} git clone https://github.com/peaqnetwork/peaq-os-skills ``` ## LLM context files Separate from the curated skill, Mintlify auto-generates machine-readable bundles of these docs. Point any agent at one of: | File | URL | Use when | | :-------------- | :------------------------------------------------------------------------- | :----------------------------------------------------------------------- | | `llms.txt` | [https://docs.peaq.xyz/llms.txt](https://docs.peaq.xyz/llms.txt) | Model has a modest context window or you want a compact index with links | | `llms-full.txt` | [https://docs.peaq.xyz/llms-full.txt](https://docs.peaq.xyz/llms-full.txt) | Model has a large context window and you want the full docs inline | | `skill.md` | [https://docs.peaq.xyz/skill.md](https://docs.peaq.xyz/skill.md) | Agent needs a doc-derived capabilities spec rather than prose docs | These are doc context, not a substitute for the curated `peaqos` skill — that one knows the onboarding flow, decision tree, and recovery paths; the auto-generated bundle just knows what's on the page. ## Editor setup (docs context) Wire the peaq docs into your editor of choice. Independent of the `peaqos` skill — useful any time you want the agent to ground answers in current docs. Open **Cursor Settings → Features → Docs**, click **Add new doc**, and paste: ``` https://docs.peaq.xyz/llms-full.txt ``` Reference peaq in chat with `@docs` → peaq. Windsurf has no persistent docs store. Paste into Cascade (`Cmd+L`) per chat: ``` @docs:https://docs.peaq.xyz/llms-full.txt ``` Add the Mintlify-hosted MCP server to `.mcp.json`: ```json theme={"theme":{"light":"github-light-default","dark":"github-dark"}} { "mcpServers": { "peaq-docs": { "url": "https://docs.peaq.xyz/mcp" } } } ``` Or one-shot install with the Mintlify CLI: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npx @mintlify/mcp add docs.peaq.xyz ``` Same MCP URL: `https://docs.peaq.xyz/mcp`. For custom GPTs or Claude Projects, upload `llms-full.txt` as a knowledge source. ## See also Chain-level prompting tips, Cursor Projects, peaq SDK prompt patterns. The ground truth the skill calls into via the CLI. # Errors Source: https://docs.peaq.xyz/peaqos/sdk-reference/errors Error class hierarchy, 20 faucet codes, and on-chain revert names for the peaqOS SDK. Every error raised by the SDK extends a common base. Faucet-specific codes are surfaced on the error instance (`error.code`) so callers can branch without string matching. ## Class hierarchy ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} PeaqosError ├── ValidationError ├── RuntimeError ├── ValueCapExceeded └── RateLimitExceeded ``` All four concrete classes extend `PeaqosError` directly. `catch (err instanceof PeaqosError)` covers every SDK error; catching `RuntimeError` alone will miss `ValueCapExceeded` and `RateLimitExceeded`. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { PeaqosError, // base ValidationError, // bad input RuntimeError, // chain or HTTP failure; carries optional `code` ValueCapExceeded, // operational cap hit (extends PeaqosError) RateLimitExceeded, // operational cap hit (extends PeaqosError) } from "@peaqos/peaq-os-sdk"; ``` ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} PeaqosError ├── ValidationError ├── RpcError ├── ApiError ├── ValueCapExceeded └── RateLimitExceeded ``` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import ( PeaqosError, ValidationError, RpcError, # chain / web3 errors ApiError, # HTTP API errors (faucet, MCR API) ValueCapExceeded, RateLimitExceeded, ) ``` Python separates transport-layer failures into `RpcError` (chain) and `ApiError` (faucet + MCR API). JavaScript collapses both into `RuntimeError` with a `code` field. *** ## Faucet error codes All 20 codes the Gas Station can return from `POST /faucet/fund`, `POST /2fa/setup`, and `POST /2fa/confirm`. Each endpoint returns a subset. For example, `INVALID_OWNER_ADDRESS` and `QR_GENERATION_FAILED` only come from `/2fa/setup`. Codes surface as `RuntimeError.code` (JS) or `ApiError.code` (Python). | Code | Description | Retry | | :------------------- | :--------------------------------------------------- | :--------------------------------------------- | | `INVALID_2FA` | OTP rejected by the faucet | Yes: submit a fresh 6-digit code | | `2FA_NOT_CONFIGURED` | Owner has not completed `setup_faucet_2fa` | No: re-run setup | | `2FA_NOT_ACTIVE` | 2FA enrolled but not confirmed | No: call `confirm_faucet_2fa` with a valid OTP | | `2FA_LOCKED` | Too many invalid attempts; owner temporarily blocked | After lockout window | | Code | Description | Retry | | :-------------------------- | :---------------------------------- | :-------------------------------------------------------- | | `DUPLICATE_REQUEST` | Same `request_id` already in flight | No: use a different `request_id` or wait for prior result | | `REQUEST_ALREADY_PROCESSED` | Same `request_id` already resolved | No: read the prior result | | Code | Description | Retry | | :-------------------- | :---------------------------------- | :------------------------------------- | | `RATE_LIMITED` | Per-IP or per-wallet throttle hit | After cooldown | | `CAP_EXCEEDED_OWNER` | Daily per-owner funding cap reached | Next day | | `CAP_EXCEEDED_WALLET` | Daily per-target-wallet cap reached | Next day, or target a different wallet | | Code | Description | Retry | | :----------------------- | :-------------------------------------- | :------------------ | | `INVALID_PAYLOAD` | Request body malformed | No: fix the payload | | `INVALID_OWNER_ADDRESS` | `owner_address` not a recognized format | No | | `INVALID_TARGET_ADDRESS` | `target_wallet_address` not recognized | No | | `INVALID_CHAIN_ID` | `chain_id` not configured on the faucet | No | | `INVALID_REQUEST_ID` | `request_id` not a UUID | No | | Code | Description | Retry | | :---------------- | :------------------------------------ | :---- | | `TRANSFER_FAILED` | On-chain transfer reverted or stalled | Yes | | `CHAIN_RPC_ERROR` | Faucet's RPC call failed | Yes | | `INTERNAL_ERROR` | Faucet internal failure | Yes | | Code | Description | Retry | | :--------------------- | :--------------------------------------- | :--------------- | | `QR_NOT_FOUND` | QR image expired or never created | No: re-run setup | | `QR_EXPIRED` | QR retrieval attempted after \~2 min TTL | No: re-run setup | | `QR_GENERATION_FAILED` | Faucet could not generate the image | Yes | Full flow reference: [Gas Station concept](/peaqos/concepts/gas-station), [setupFaucet2FA](/peaqos/sdk-reference/sdk-js#setupfaucet2fa), [fund\_from\_gas\_station](/peaqos/sdk-reference/sdk-python#fund_from_gas_station). *** ## On-chain revert names Revert names the SDK translates to a friendly message and surfaces as `RuntimeError.code` (JS) or `RpcError.code` (Python). The contracts define more custom errors than this table; anything not listed surfaces as `code: "TX_REVERTED"` (Python) or `code: ""` with a generic `Transaction reverted: …` message (JS, when viem decodes the selector). | Revert | Raised by | Cause | | :---------------------------- | :----------------------------------------------------- | :------------------------------------------------------------------ | | `AlreadyRegistered` | `registerMachine` / `registerFor` / `register_machine` | Address already has a machine ID | | `InvalidMachineAddress` | `registerFor` | `machineAddress` is the zero address | | `InvalidAddress` | Staking / NFT flows | Caller passed the zero address where a real address is required | | `AmountZero` | Staking / bond flow | Bond amount is zero | | `AlreadyStaked` | Staking flow | Machine is already bonded/staked | | `MachineNotBonded` | `mintNft` / `mint_nft` | Caller's machine is not bonded on the IdentityRegistry | | `AlreadyMinted` | `mintNft` / `mint_nft` | An NFT is already minted for the machine | | `NotMachineOwner` | `mintNft` / `mint_nft` | Caller is not the owner of the machine | | `RecipientMustBeMachineOwner` | `mintNft` / `mint_nft` | Operator-mint guard: recipient must be the registered machine owner | | `MachineNotFound` | `submitEvent` / `tokenIdOf` | machineId has no Identity record on the IdentityRegistry | | `MachineDeactivated` | `submitEvent` | The machine has been deactivated by the IdentityRegistry | | `NotAuthorizedSubmitter` | `submitEvent` | Caller is not authorized to submit events for this machine | | `InvalidEventType` | `submitEvent` | `eventType` is not `0` (revenue) or `1` (activity) | | `InvalidTrustLevel` | `submitEvent` | `trustLevel` is not `0`, `1`, or `2` | ## MCR API error codes Returned by `queryMcr` / `query_mcr`, `queryMachine` / `query_machine`, and `queryOperatorMachines` / `query_operator_machines`. Surfaced as `RuntimeError.code` (JS) or `ApiError.code` (Python). | Code | Cause | Retry | | :-------------------- | :--------------------------------------------------------------------- | :---------------------------------------- | | `NOT_FOUND` | The DID, machine, or token ID was not found by the MCR API (HTTP 404) | No: check the input | | `BAD_RESPONSE` | The MCR returned an unexpected payload shape | No: file an issue if it persists | | `HTTP_ERROR` | The MCR returned an unhandled non-2xx status | Maybe: surface the status code and decide | | `SERVER_ERROR` | The MCR returned 5xx | Yes, with backoff | | `SERVICE_UNAVAILABLE` | The MCR returned 503 (`Service not initialised` / `Chain unavailable`) | Yes, with backoff | | `TIMEOUT` | The HTTP request exceeded `timeoutMs` | Yes | | `NETWORK_ERROR` | Transport-level failure (DNS, TCP, TLS) | Yes | | `ABORTED` | The caller aborted the request via `AbortSignal` (JS only) | Caller's choice | ## OWS signing error codes Raised when transaction signing routes through an OWS vault wallet (`PeaqosClient.fromWallet` / `from_wallet` with `owsSigning=true`). The SDK normalises the upstream OWS error code into a typed SDK exception — only `INVALID_INPUT` becomes `ValidationError`; the other four become `PeaqosError` (or `RuntimeError` in JS) with the original OWS error preserved as `.cause`. | Code | Cause | Surfaces as | | :-------------------- | :--------------------------------------------- | :--------------------------------------- | | `WALLET_NOT_FOUND` | Vault wallet name / UUID does not exist | `PeaqosError` (Py) / `RuntimeError` (JS) | | `INVALID_PASSPHRASE` | Wrong vault passphrase | `PeaqosError` (Py) / `RuntimeError` (JS) | | `INVALID_INPUT` | Malformed transaction or sign-hash payload | `ValidationError(field="transaction")` | | `POLICY_DENIED` | Signing blocked by an OWS policy rule | `PeaqosError` (Py) / `RuntimeError` (JS) | | `CHAIN_NOT_SUPPORTED` | Transaction `chainId` is not configured in OWS | `PeaqosError` (Py) / `RuntimeError` (JS) | Constants exported from both SDKs as `OWS_ERROR_WALLET_NOT_FOUND`, `OWS_ERROR_INVALID_PASSPHRASE`, `OWS_ERROR_INVALID_INPUT`, `OWS_ERROR_POLICY_DENIED`, `OWS_ERROR_CHAIN_NOT_SUPPORTED`. JS additionally exports the `OwsSigningErrorCode` union type. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { OWS_ERROR_INVALID_PASSPHRASE, OWS_ERROR_CHAIN_NOT_SUPPORTED } from "@peaqos/peaq-os-sdk"; try { await client.bridgeNft({ tokenId, destination: "base" }); } catch (err) { if (err.cause?.code === OWS_ERROR_INVALID_PASSPHRASE) { /* prompt re-auth */ } if (err.cause?.code === OWS_ERROR_CHAIN_NOT_SUPPORTED) { /* OWS chain config bug */ } throw err; } ``` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import ( OWS_ERROR_INVALID_PASSPHRASE, OWS_ERROR_CHAIN_NOT_SUPPORTED, PeaqosError, ) try: client.bridge_nft(token_id=..., destination="base") except PeaqosError as err: code = getattr(err.__cause__, "code", None) if code == OWS_ERROR_INVALID_PASSPHRASE: ... # prompt re-auth if code == OWS_ERROR_CHAIN_NOT_SUPPORTED: ... # OWS chain config bug raise ``` ## SDK transaction sentinels Raised by the SDK's transaction helper around any contract call. Surfaced as `RuntimeError.code` (JS) or `RpcError.code` (Python). | Code | Cause | | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `WALLET_NOT_CONFIGURED` | Method requires a signer but none was configured. (JS only; the Python `PeaqosClient` constructor requires a `private_key` and surfaces this as `ValidationError`.) | | `TX_REVERTED` | The transaction reverted on-chain. The revert reason is in the message; revert names from the table above are decoded into `code` when the SDK recognizes them. | | `RECEIPT_AWAIT_FAILED` | Transaction was submitted but the SDK could not retrieve a receipt. The tx may still have landed; re-query by hash before retrying. | | `REGISTERED_EVENT_MISSING` | Receipt has no `Registered` event log. Indicates a contract/SDK ABI mismatch. | | `REGISTERED_EVENT_MALFORMED` | `Registered` event log was decoded but had unexpected fields. Same root cause as above. | | `INVALID_FEE_RESULT` | `quoteSend` on the LayerZero ONFT adapter returned a malformed `MessagingFee`. Raised inside `bridge_nft` (Python) before submission. | ## Faucet / HTTP envelope sentinels Raised by the SDK when a faucet or MCR response is reachable but unparseable. Surfaced as `ApiError.code` (Python). The JS SDK collapses these into the generic `RuntimeError` envelope path. | Code | Cause | | :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INVALID_RESPONSE` | Faucet returned a non-JSON body. | | `UNEXPECTED_RESPONSE` | Faucet returned JSON but the `status` / `code` / `data` envelope did not match the expected shape for the endpoint. | | `NETWORK_ERROR` | Transport-level failure during a faucet call (DNS, connection refused, TLS, timeout via `requests.RequestException`). Same sentinel as the MCR API table above. | ## Client-side error codes Raised by the SDK itself (not by a chain revert). Surfaced as `RuntimeError.code` (JS) or `ValidationError`/`RpcError` attributes (Python). | Code | Raised by | Cause | | :------------------------ | :------------------------------------------ | :------------------------------------------------------------------------------------- | | `MIN_BOND_INVALID_RESULT` | `registerMachine` / `registerFor` (JS only) | `IdentityRegistry.minBond()` returned a non-`bigint` value before submitting the bond. | *** ## Error handling patterns ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { PeaqosError, ValidationError, RuntimeError, RateLimitExceeded, } from "@peaqos/peaq-os-sdk"; try { await client.fundFromGasStation(params, faucetUrl); } catch (err) { if (err instanceof ValidationError) { // fix caller input } else if (err instanceof RateLimitExceeded) { // back off } else if (err instanceof RuntimeError) { switch (err.code) { case "INVALID_2FA": // prompt for a fresh TOTP break; case "CAP_EXCEEDED_OWNER": case "CAP_EXCEEDED_WALLET": // surface cap messaging break; default: throw err; } } else { throw err; } } ``` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import PeaqosError, ValidationError, ApiError, RpcError try: client.fund_from_gas_station( owner_address=owner, target_wallet_address=target, chain_id="peaq", two_factor_code=totp, faucet_base_url=faucet_url, ) except ValidationError: # fix caller input raise except ApiError as err: if err.code == "INVALID_2FA": # prompt for a fresh TOTP pass elif err.code in ("CAP_EXCEEDED_OWNER", "CAP_EXCEEDED_WALLET"): # surface cap messaging pass else: raise except RpcError: # retry with backoff raise ``` *** ## Related * [SDK JS](/peaqos/sdk-reference/sdk-js) * [SDK Python](/peaqos/sdk-reference/sdk-python) * [Gas Station concept](/peaqos/concepts/gas-station) * [Install page troubleshooting](/peaqos/install) # Bridge Source: https://docs.peaq.xyz/peaqos/sdk-reference/ros2/bridge Bridge Machine NFTs between peaq and Base from ROS 2 via LayerZero V2. `peaq_ros2_peaqos` exposes the LayerZero V2 ONFT bridge as two services: | Service | Type | Purpose | | :--------------------------------- | :---------------------------------------------------- | :------------------------------------------------------- | | `/peaqos_node/bridge/nft` | `peaq_ros2_interfaces/srv/PeaqosBridgeNft` | Send a Machine NFT between peaq and Base | | `/peaqos_node/bridge/wait_arrival` | `peaq_ros2_interfaces/srv/PeaqosWaitForBridgeArrival` | Poll the destination chain until the bridged NFT arrives | Bridging is **mainnet only**. LayerZero V2 has no DVN routes between agung and Base, so bridge calls on testnet won't relay end-to-end. Test the round trip on peaq mainnet ↔ Base mainnet. ## Required configuration Both directions need the LayerZero ONFT contracts wired into `peaq_robot.yaml`: ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaq_os: contracts: machine_nft_adapter: "0x9AD5408702EC204441A88589B99ADfC2514AFAE6" # peaq mainnet # Pass the Base counterpart per call (see base_nft_address below) or # leave it blank to fall back to the SDK default. ``` Equivalent env override: `MACHINE_NFT_ADAPTER_ADDRESS`. Base side reference contract: `0xee8A521eA434b11F956E2402beC5eBfa753Babfa` (`MachineNFTBase`). ## peaq → Base The signer must own the Machine NFT on peaq and hold enough peaq for gas plus the LayerZero fee. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/bridge/nft \ peaq_ros2_interfaces/srv/PeaqosBridgeNft \ "{signer_address: '', token_id: 1, source: 'peaq', destination: 'base', recipient: '', base_rpc_url: '', base_nft_address: '', options_hex: ''}" ``` Then poll the destination: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/bridge/wait_arrival \ peaq_ros2_interfaces/srv/PeaqosWaitForBridgeArrival \ "{dst_rpc_url: 'https://mainnet.base.org', dst_nft_address: '0xee8A521eA434b11F956E2402beC5eBfa753Babfa', token_id: 1, timeout: 900}" ``` `timeout` is in seconds. The service returns once `ownerOf(token_id)` matches the recipient on the destination chain or the deadline is hit. ## Base → peaq Same service shape, swapped `source` / `destination`. The signer must hold the Machine NFT on Base **and** enough Base ETH to cover gas plus the LayerZero fee — the bridge can't bootstrap funding for you. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/bridge/nft \ peaq_ros2_interfaces/srv/PeaqosBridgeNft \ "{signer_address: '', token_id: 1, source: 'base', destination: 'peaq', recipient: '', base_rpc_url: 'https://mainnet.base.org', base_nft_address: '0xee8A521eA434b11F956E2402beC5eBfa753Babfa', options_hex: ''}" ``` Wait for arrival on peaq: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/bridge/wait_arrival \ peaq_ros2_interfaces/srv/PeaqosWaitForBridgeArrival \ "{dst_rpc_url: 'https://quicknode1.peaq.xyz', dst_nft_address: '0x2943F80e9DdB11B9Dd275499C661Df78F5F691F9', token_id: 1, timeout: 900}" ``` ## Field reference `PeaqosBridgeNft`: | Field | Description | | :----------------- | :------------------------------------------------------------------------------------------ | | `signer_address` | Local wallet address that owns the NFT on the source chain | | `token_id` | Machine NFT token id | | `source` | `peaq` or `base` | | `destination` | `peaq` or `base` | | `recipient` | Address that receives the NFT on the destination chain | | `base_rpc_url` | Base JSON-RPC URL. Required when `source: 'base'`; unused when `source: 'peaq'` | | `base_nft_address` | Base `MachineNFTBase` address. Required when `source: 'base'`; unused when `source: 'peaq'` | | `options_hex` | Raw LayerZero `extraOptions` payload hex. Leave blank for contract defaults | `PeaqosWaitForBridgeArrival`: | Field | Description | | :---------------- | :------------------------------------------------ | | `dst_rpc_url` | RPC for the destination chain | | `dst_nft_address` | NFT contract on the destination chain | | `token_id` | Machine NFT token id | | `timeout` | Seconds to poll before returning `arrived: false` | ## Operational notes * Record pre/post balances on both chains for any production bridge test. * The reverse direction (Base → peaq) is the failure mode most people hit: a fresh Base wallet has no ETH, so the bridge tx reverts on `insufficient funds`. Fund Base first. * For a deeper protocol view (LayerZero peers, ONFT contracts, supported chains) see the [Activate function](/peaqos/functions/activate) and the [SDK JS](/peaqos/sdk-reference/sdk-js) / [SDK Python](/peaqos/sdk-reference/sdk-python) bridge methods, which this node mirrors 1:1. # ROS 2 configuration Source: https://docs.peaq.xyz/peaqos/sdk-reference/ros2/configuration Configure peaqOS ROS 2 with a unified YAML file, local wallet registry, MCR API, faucet, and contract addresses. `peaqos_node` uses the same unified `peaq_robot.yaml` pattern as the rest of the robotics workspace. Keep populated config files out of version control. ## Minimal YAML ```yaml peaq_robot.yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaq_os: enabled: true rpc_url: "https://quicknode1.peaq.xyz" api_url: "https://mcr.peaq.xyz" faucet: base_url: "https://depinstation.peaq.network" qr_format: "svg" wallet_registry: path: "~/.peaq_robot/peaqos_wallets.json" ``` The ROS 2 node signs internally after looking up the local private key by EVM address. Do not pass private keys, mnemonics, or faucet TOTP secrets through ROS topics. ## Mainnet contracts The example config ships current peaq mainnet proxy defaults. Env override: `IDENTITY_REGISTRY_ADDRESS` Mainnet address: `0xb53Af985765031936311273599389b5B68aC9956` Env override: `IDENTITY_STAKING_ADDRESS` Mainnet address: `0x11c05A650704136786253e8685f56879A202b1C7` Env override: `EVENT_REGISTRY_ADDRESS` Mainnet address: `0x43c6c12eecAf4fB3F164375A9c44f8a6Efc139b9` Env override: `MACHINE_NFT_ADDRESS` Mainnet address: `0x2943F80e9DdB11B9Dd275499C661Df78F5F691F9` Env override: `DID_REGISTRY_ADDRESS` Mainnet address: `0x0000000000000000000000000000000000000800` Env override: `BATCH_PRECOMPILE_ADDRESS` Mainnet address: `0x0000000000000000000000000000000000000805` Env override: `MACHINE_ACCOUNT_FACTORY_ADDRESS` Mainnet address: `0x4A808d5A90A2c91739E92C70aF19924e0B3D527f` Env override: `MACHINE_NFT_ADAPTER_ADDRESS` Mainnet address: `0x9AD5408702EC204441A88589B99ADfC2514AFAE6` ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaq_os: contracts: identity_registry: "0xb53Af985765031936311273599389b5B68aC9956" identity_staking: "0x11c05A650704136786253e8685f56879A202b1C7" event_registry: "0x43c6c12eecAf4fB3F164375A9c44f8a6Efc139b9" machine_nft: "0x2943F80e9DdB11B9Dd275499C661Df78F5F691F9" did_registry: "0x0000000000000000000000000000000000000800" batch_precompile: "0x0000000000000000000000000000000000000805" machine_account_factory: "0x4A808d5A90A2c91739E92C70aF19924e0B3D527f" machine_nft_adapter: "0x9AD5408702EC204441A88589B99ADfC2514AFAE6" ``` ## Optional defaults Use defaults when multiple service calls use the same owner, proxy, or machine wallet. ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaq_os: defaults: owner_address: "0xOwner..." machine_address: "0xMachine..." proxy_address: "0xProxy..." ``` When a matching request field is empty, the node can use these defaults. Production automation should still pass explicit addresses where possible for auditability. ## Operational limits All zeros disable SDK-side event caps. ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaq_os: operational_limits: max_value_per_tx: 0 rate_limit_max_events: 0 rate_limit_window_seconds: 0 ``` ## Base bridge settings peaq to Base bridge calls use the peaq-side `machine_nft_adapter`. Base to peaq bridge calls additionally require a Base RPC URL and Base Machine NFT address in the service request. Known Base Machine NFT address: ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} 0xee8A521eA434b11F956E2402beC5eBfa753Babfa ``` Base to peaq bridge calls require Base ETH on the signer wallet for gas. ## Launch ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 run peaq_ros2_peaqos peaqos_node --ros-args \ -p config.yaml_path:=peaq_ros2_examples/config/peaq_robot.yaml ``` Next, use the [service catalog](/peaqos/sdk-reference/ros2/services) or run the [ROS 2 machine runtime guide](/peaqos/guides/ros2-machine-runtime). # Installation Source: https://docs.peaq.xyz/peaqos/sdk-reference/ros2/installation Build the peaqOS ROS 2 packages and configure the unified peaq_robot.yaml. The peaqOS ROS 2 packages live in [`peaqnetwork/peaq-robotics-ros2`](https://github.com/peaqnetwork/peaq-robotics-ros2). ## Prerequisites | Requirement | Value | | :------------ | :------------------------------------------ | | ROS 2 | Humble (Docker) or Jazzy (native Ubuntu) | | Python | ≥ 3.10 | | `peaq-os-sdk` | ≥ 0.0.2 (PyPI, pulled in by the workspace) | | RPC | peaq mainnet or agung testnet EVM JSON-RPC | | Optional | Base mainnet RPC for the peaq ↔ Base bridge | ## Workspace build The Docker image ships with ROS 2 Humble, IPFS (Kubo), and Python deps preinstalled. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} git clone https://github.com/peaqnetwork/peaq-robotics-ros2.git cd peaq-robotics-ros2 docker build -t peaq-ros2:latest . docker run -it --rm \ --name peaq-ros2-dev \ -v "$(pwd)":/work \ -w /work \ -p 5001:5001 -p 8080:8080 \ peaq-ros2:latest # Inside the container: source /opt/ros/humble/setup.bash colcon build --packages-select \ peaq_ros2_interfaces peaq_ros2_peaqos peaq_ros2_examples source install/setup.bash ``` ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} git clone https://github.com/peaqnetwork/peaq-robotics-ros2.git cd peaq-robotics-ros2 pip install -r requirements.txt source /opt/ros/jazzy/setup.bash colcon build --packages-select \ peaq_ros2_interfaces peaq_ros2_peaqos peaq_ros2_examples source install/setup.bash ``` Add `peaq_ros2_tether`, `peaq_ros2_core`, or `peaq_ros2_openclaw` to the `--packages-select` list when you need the matching node alongside peaqOS. ## Unified config The node reads from a single `peaq_robot.yaml`. Start from the example and fill in placeholders: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} cp peaq_ros2_examples/config/peaq_robot.example.yaml \ peaq_ros2_examples/config/peaq_robot.yaml ``` ### `peaq_os` block ```yaml theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaq_os: enabled: true # peaq EVM JSON-RPC used by peaq-os-sdk rpc_url: "https://quicknode1.peaq.xyz" # Hosted MCR API. Use http://127.0.0.1:8000 to read from a local MCR server. api_url: "https://mcr.peaq.xyz" faucet: base_url: "https://depinstation.peaq.network" qr_format: "svg" wallet_registry: path: "~/.peaq_robot/peaqos_wallets.json" defaults: owner_address: "" machine_address: "" proxy_address: "" contracts: # Current peaq mainnet proxy addresses. identity_registry: "0xb53Af985765031936311273599389b5B68aC9956" identity_staking: "0x11c05A650704136786253e8685f56879A202b1C7" event_registry: "0x43c6c12eecAf4fB3F164375A9c44f8a6Efc139b9" machine_nft: "0x2943F80e9DdB11B9Dd275499C661Df78F5F691F9" did_registry: "0x0000000000000000000000000000000000000800" batch_precompile: "0x0000000000000000000000000000000000000805" # Required only for smart-account and peaq → Base bridge calls. machine_account_factory: "0x4A808d5A90A2c91739E92C70aF19924e0B3D527f" machine_nft_adapter: "0x9AD5408702EC204441A88589B99ADfC2514AFAE6" operational_limits: # All zeros disable SDK-side event limits. max_value_per_tx: 0 rate_limit_max_events: 0 rate_limit_window_seconds: 0 ``` For agung testnet addresses see [Install → Agung testnet contracts](/peaqos/install#agung-testnet-contracts). ### Environment overrides The same overrides recognized by the [Python SDK](/peaqos/sdk-reference/sdk-python) and [JS SDK](/peaqos/sdk-reference/sdk-js) work here. Useful when you pin contract addresses externally: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} export EVENT_REGISTRY_ADDRESS=0x... export MACHINE_ACCOUNT_FACTORY_ADDRESS=0x... export MACHINE_NFT_ADAPTER_ADDRESS=0x... export BATCH_PRECOMPILE_ADDRESS=0x... ``` ## Run the node ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 run peaq_ros2_peaqos peaqos_node --ros-args \ -p config.yaml_path:=peaq_ros2_examples/config/peaq_robot.yaml ``` Confirm services are registered: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 node list | grep peaqos_node ros2 service list | grep /peaqos_node/ ``` Run the node in the background and tail logs: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} nohup ros2 run peaq_ros2_peaqos peaqos_node --ros-args \ -p config.yaml_path:=/work/peaq_ros2_examples/config/peaq_robot.yaml \ > /tmp/peaqos_node.log 2>&1 & tail -f /tmp/peaqos_node.log ``` If multiple ROS 2 environments share a host, isolate them with `ROS_DOMAIN_ID` to prevent service collisions. ## Production checklist * Use a local `peaq_robot.yaml`; do not commit machine private keys, faucet codes, or RPC tokens. * Keep `peaq_os.wallet_registry.path` on encrypted robot storage when possible. Permissions `0600`. * Use a reliable peaq EVM RPC endpoint and monitor rate limits. * Record pre/post balances for any production bridge or event-spend test. * Fund Base ETH on the signer before attempting Base → peaq bridge. * Pin peaqOS contract addresses to the values in [Install](/peaqos/install#peaq-mainnet-contracts) unless deployment docs change. ## Troubleshooting The workspace expects `peaq-os-sdk>=0.0.2`. Install it inside the same Python env that runs `ros2`: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install 'peaq-os-sdk>=0.0.2' ``` Verify with `python3 -c "import peaq_os_sdk; print(peaq_os_sdk.__version__)"`. Quote EVM addresses and large integers in `ros2 service call` payloads: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/wallet/get \ peaq_ros2_interfaces/srv/PeaqosGetWallet \ "{address: '0xAbC...'}" ``` `peaq_os.contracts.machine_nft_adapter` is required only when bridging from peaq. Set it in `peaq_robot.yaml` or export `MACHINE_NFT_ADAPTER_ADDRESS`. Agung has no LayerZero DVN routes — test bridging on peaq mainnet ↔ Base mainnet only. Continue to the full [service reference](/peaqos/sdk-reference/ros2/services). # SDK: ROS 2 Source: https://docs.peaq.xyz/peaqos/sdk-reference/ros2/overview ROS 2 services that turn a robot into an on-chain machine actor. `peaq_ros2_peaqos` maps the peaqOS Python SDK capability surface into typed ROS 2 services. It is for robot teams that already run ROS 2 and want peaqOS primitives without passing private keys through ROS messages. ROS 2 stays the robot control plane; peaqOS becomes the machine economy and identity plane. ## What it unlocks Create local EVM machine wallets, register machines, and fund them through the Gas Station. Mint Machine NFTs and write the standard DID attributes the MCR API understands. Validate single events locally, submit one event, or batch-submit atomically through the peaq batch precompile. Query MCR, machine profiles, and operator fleets from the hosted MCR API. Preview and deploy deterministic ERC-4337 smart accounts through `MachineAccountFactory`. Bridge Machine NFTs between peaq and Base via LayerZero V2. ## Security model ```text theme={"theme":{"light":"github-light-default","dark":"github-dark"}} Robot process | | ROS 2 service call (address only, no keys) v peaqos_node | | local wallet lookup by EVM address v peaq-os-sdk (PyPI) ---> peaq EVM RPC, MCR API, Gas Station API, Base RPC | v peaqOS contracts and services ``` * Wallet private keys live in one local registry file on the robot or machine (default `~/.peaq_robot/peaqos_wallets.json`, perms `0600`). * ROS callers pass EVM **addresses**, not private keys. * The node never accepts or returns private keys over ROS. * Wallet `list` / `get` / `delete` expose only public metadata: `address`, `account_id`, `chain_id`, `network`, `label`, `created_at`. * Faucet 2FA codes are request-only and should not be logged. ## Service map | Area | Service | Purpose | | :------------ | :------------------------------------------ | :----------------------------------------------- | | Wallet | `/peaqos_node/wallet/create` | Create a locally stored EVM wallet | | Wallet | `/peaqos_node/wallet/list` | List local wallet public metadata | | Wallet | `/peaqos_node/wallet/get` | Get one wallet's public metadata | | Wallet | `/peaqos_node/wallet/delete` | Delete one local wallet | | Faucet | `/peaqos_node/faucet/setup_2fa` | Start faucet 2FA enrollment | | Faucet | `/peaqos_node/faucet/confirm_2fa` | Confirm faucet 2FA | | Faucet | `/peaqos_node/wallet/fund` | Request Gas Station funding | | Onboarding | `/peaqos_node/machine/register` | Register the local machine wallet | | Onboarding | `/peaqos_node/agent/register` | Same handler, agent-flavored alias | | Onboarding | `/peaqos_node/machine/register_for` | Register a machine through a proxy operator | | Onboarding | `/peaqos_node/agent/register_for` | Same handler, agent-flavored alias | | NFT | `/peaqos_node/nft/mint` | Mint a Machine NFT | | NFT | `/peaqos_node/nft/token_id_of` | Read a machine's NFT token ID | | DID | `/peaqos_node/did/read_attribute` | Read one DID precompile attribute | | DID | `/peaqos_node/did/write_machine_attributes` | Write the standard machine DID attributes | | DID | `/peaqos_node/did/write_proxy_attributes` | Write proxy/operator DID attributes | | Events | `/peaqos_node/events/validate` | Validate event payload and compute data hash | | Events | `/peaqos_node/events/submit` | Submit one event through `EventRegistry` | | Events | `/peaqos_node/events/batch_submit` | Atomic batch submit through the batch precompile | | MCR | `/peaqos_node/mcr/query` | Query Machine Credit Rating | | MCR | `/peaqos_node/mcr/machine` | Query machine profile | | MCR | `/peaqos_node/mcr/operator_machines` | Query an operator's fleet | | Smart account | `/peaqos_node/smart_account/address` | Predict deterministic smart-account address | | Smart account | `/peaqos_node/smart_account/deploy` | Deploy machine smart account | | Bridge | `/peaqos_node/bridge/nft` | Bridge Machine NFT between peaq and Base | | Bridge | `/peaqos_node/bridge/wait_arrival` | Poll destination chain until bridged NFT arrives | Full service-by-service reference with example payloads is on [Services](/peaqos/sdk-reference/ros2/services). Bridge has its own page on [Bridge](/peaqos/sdk-reference/ros2/bridge). ## Quickstart Build and source the workspace, copy the example config, then start the node against your unified config: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} git clone https://github.com/peaqnetwork/peaq-robotics-ros2.git cd peaq-robotics-ros2 source /opt/ros/jazzy/setup.bash # Use /opt/ros/humble/setup.bash inside the Docker image. # Native host only: python3 -m pip install -r requirements.txt colcon build --packages-select peaq_ros2_interfaces peaq_ros2_peaqos peaq_ros2_examples source install/setup.bash cp peaq_ros2_examples/config/peaq_robot.example.yaml peaq_ros2_examples/config/peaq_robot.yaml # Edit peaq_ros2_examples/config/peaq_robot.yaml and set peaq_os.enabled: true. ros2 run peaq_ros2_peaqos peaqos_node --ros-args \ -p config.yaml_path:=peaq_ros2_examples/config/peaq_robot.yaml ``` The node reads one unified YAML file. See [ROS 2 configuration](/peaqos/sdk-reference/ros2/configuration) for production settings and contract addresses. Create a wallet, register, mint, and submit an event: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/wallet/create \ peaq_ros2_interfaces/srv/PeaqosCreateWallet \ "{label: 'robot-001'}" ros2 service call /peaqos_node/machine/register \ peaq_ros2_interfaces/srv/PeaqosRegisterMachine \ "{address: ''}" ros2 service call /peaqos_node/nft/mint \ peaq_ros2_interfaces/srv/PeaqosMintNft \ "{signer_address: '', machine_id: 1, recipient: ''}" ros2 service call /peaqos_node/events/submit \ peaq_ros2_interfaces/srv/PeaqosSubmitEvent \ "{signer_address: '', machine_id: 1, event_type: 1, value: 1, timestamp: 1770000000, raw_data_hex: '0x', trust_level: 1, source_chain_id: 3338, source_tx_hash: '', metadata_hex: '0x7b7d'}" ``` End-to-end commands are in [ROS 2 machine runtime](/peaqos/guides/ros2-machine-runtime). ## ROS distro support | Distro | Status | | :----------- | :------------------------------- | | ROS 2 Humble | Supported (Docker image default) | | ROS 2 Jazzy | Supported (native Ubuntu hosts) | Native install on non-Linux hosts is best done via Docker or WSL. ## What's tested on mainnet This release path has been exercised on a ROS 2 Jazzy Ubuntu server against peaq mainnet: * Wallet create / list / get / delete public-metadata lifecycle * Machine NFT token lookup * DID `readAttribute` * MCR query, machine query, and operator machines query * Event validation, single submission, and batch submission via the batch precompile * Deterministic smart-account address calculation * Smart-account deployment through `MachineAccountFactory` * peaq to Base Machine NFT bridge through `MachineNFTAdapter` * Base arrival polling for the bridged Machine NFT Reverse Base to peaq bridge requires Base ETH on the signer wallet for gas. ## Keep going YAML fields, contract addresses, wallet registry, API URLs, and environment overrides. Canonical machine endpoints plus documented agent aliases, with request fields and examples. Run the machine onboarding, event, MCR, smart-account, and bridge flow from ROS 2. The PyPI SDK surface wrapped by the ROS 2 node. The JavaScript SDK surface used for peaqOS application integrations. # ROS 2 service catalog Source: https://docs.peaq.xyz/peaqos/sdk-reference/ros2/services Canonical peaqOS ROS 2 machine endpoints plus documented agent aliases, grouped by SDK capability. This page lists the canonical machine endpoints and documented agent aliases exposed under `/peaqos_node`. Requests use ROS 2 service types from `peaq_ros2_interfaces`. All examples assume the node is running with a populated `peaq_robot.yaml`; see [ROS 2 configuration](/peaqos/sdk-reference/ros2/configuration). Quote EVM addresses and large integers in `ros2 service call` YAML payloads. Unquoted addresses can parse as numbers and break the request. ## Wallets Local EVM wallet registry. Service calls return public metadata only: `address`, `account_id`, `chain_id`, `network`, `label`, and `created_at`. Private keys never cross ROS. | Service | Type | | :--------------------------- | :-------------------------------------------- | | `/peaqos_node/wallet/create` | `peaq_ros2_interfaces/srv/PeaqosCreateWallet` | | `/peaqos_node/wallet/list` | `peaq_ros2_interfaces/srv/PeaqosListWallets` | | `/peaqos_node/wallet/get` | `peaq_ros2_interfaces/srv/PeaqosGetWallet` | | `/peaqos_node/wallet/delete` | `peaq_ros2_interfaces/srv/PeaqosDeleteWallet` | ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Create ros2 service call /peaqos_node/wallet/create \ peaq_ros2_interfaces/srv/PeaqosCreateWallet \ "{label: 'robot-001'}" # List ros2 service call /peaqos_node/wallet/list \ peaq_ros2_interfaces/srv/PeaqosListWallets "{}" # Get ros2 service call /peaqos_node/wallet/get \ peaq_ros2_interfaces/srv/PeaqosGetWallet \ "{address: ''}" # Delete ros2 service call /peaqos_node/wallet/delete \ peaq_ros2_interfaces/srv/PeaqosDeleteWallet \ "{address: ''}" ``` SDK import, export, and `from_wallet` flows are intentionally **not** exposed over ROS because they require private keys, mnemonics, or passphrases. Use the [Python SDK](/peaqos/sdk-reference/sdk-python) or [CLI](/peaqos/cli) for those. ## Gas Station Gas Station onboarding for fresh machine wallets. 2FA codes are request-only and should not be logged. | Service | Type | | :-------------------------------- | :------------------------------------------------ | | `/peaqos_node/faucet/setup_2fa` | `peaq_ros2_interfaces/srv/PeaqosSetupFaucet2FA` | | `/peaqos_node/faucet/confirm_2fa` | `peaq_ros2_interfaces/srv/PeaqosConfirmFaucet2FA` | | `/peaqos_node/wallet/fund` | `peaq_ros2_interfaces/srv/PeaqosFundWallet` | ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # 1. Start 2FA enrollment for the owner ros2 service call /peaqos_node/faucet/setup_2fa \ peaq_ros2_interfaces/srv/PeaqosSetupFaucet2FA \ "{owner_address: '', qr_format: 'svg'}" # 2. Confirm with the OTP from the authenticator ros2 service call /peaqos_node/faucet/confirm_2fa \ peaq_ros2_interfaces/srv/PeaqosConfirmFaucet2FA \ "{owner_address: '', two_factor_code: '123456'}" # 3. Fund the target wallet through the Gas Station ros2 service call /peaqos_node/wallet/fund \ peaq_ros2_interfaces/srv/PeaqosFundWallet \ "{owner_address: '', target_address: '', chain_id: '3338', two_factor_code: '123456', request_id: ''}" ``` See the [error reference](/peaqos/sdk-reference/errors) for `INVALID_2FA`, `2FA_LOCKED`, `RATE_LIMITED`, and `CAP_EXCEEDED_OWNER`. ## Registration | Service | Type | | :---------------------------------- | :----------------------------------------------- | | `/peaqos_node/machine/register` | `peaq_ros2_interfaces/srv/PeaqosRegisterMachine` | | `/peaqos_node/agent/register` | `peaq_ros2_interfaces/srv/PeaqosRegisterAgent` | | `/peaqos_node/machine/register_for` | `peaq_ros2_interfaces/srv/PeaqosRegisterFor` | | `/peaqos_node/agent/register_for` | `peaq_ros2_interfaces/srv/PeaqosRegisterFor` | `/machine/register` and `/agent/register` are interchangeable surfaces over the same registration call. Pick whichever language matches your fleet model. `/machine/register_for` and `/agent/register_for` are likewise the same handler under two paths. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Self-managed: machine signs for itself ros2 service call /peaqos_node/machine/register \ peaq_ros2_interfaces/srv/PeaqosRegisterMachine \ "{address: ''}" # Equivalent agent-flavored alias ros2 service call /peaqos_node/agent/register \ peaq_ros2_interfaces/srv/PeaqosRegisterAgent \ "{address: ''}" # Proxy operator: operator signs on behalf of a fleet machine ros2 service call /peaqos_node/machine/register_for \ peaq_ros2_interfaces/srv/PeaqosRegisterFor \ "{proxy_address: '', machine_address: ''}" ``` For background on the two patterns, see [Self-managed onboarding](/peaqos/guides/self-managed-onboarding) and [Proxy operator fleet](/peaqos/guides/proxy-operator-fleet). ## Machine NFT | Service | Type | | :----------------------------- | :----------------------------------------- | | `/peaqos_node/nft/mint` | `peaq_ros2_interfaces/srv/PeaqosMintNft` | | `/peaqos_node/nft/token_id_of` | `peaq_ros2_interfaces/srv/PeaqosTokenIdOf` | ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/nft/mint \ peaq_ros2_interfaces/srv/PeaqosMintNft \ "{signer_address: '', machine_id: 1, recipient: ''}" ros2 service call /peaqos_node/nft/token_id_of \ peaq_ros2_interfaces/srv/PeaqosTokenIdOf \ "{signer_address: '', machine_id: 1}" ``` ## DID attributes | Service | Type | | :------------------------------------------ | :--------------------------------------------------------- | | `/peaqos_node/did/read_attribute` | `peaq_ros2_interfaces/srv/PeaqosReadDidAttribute` | | `/peaqos_node/did/write_machine_attributes` | `peaq_ros2_interfaces/srv/PeaqosWriteMachineDidAttributes` | | `/peaqos_node/did/write_proxy_attributes` | `peaq_ros2_interfaces/srv/PeaqosWriteProxyDidAttributes` | `/did/write_machine_attributes` is the standard machine DID write path the MCR API consumes. It writes `machineId`, `nftTokenId`, `operatorDid`, `documentationUrl`, `dataApi`, and `dataVisibility` in one call. `/did/write_proxy_attributes` is the proxy/operator equivalent; it atomically writes the two standard proxy DID attributes (`proxy_agent_id` and the list of `machine_ids` the operator manages). ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Read one attribute by name from the DID precompile ros2 service call /peaqos_node/did/read_attribute \ peaq_ros2_interfaces/srv/PeaqosReadDidAttribute \ "{signer_address: '', did_address: '', name: 'machineId'}" # Write the standard machine attributes in one shot ros2 service call /peaqos_node/did/write_machine_attributes \ peaq_ros2_interfaces/srv/PeaqosWriteMachineDidAttributes \ "{signer_address: '', machine_id: 1, nft_token_id: 1, operator_did: 'did:peaq:', documentation_url: 'https://docs.example/robot-001', data_api: 'https://api.example/robot-001', data_visibility: 'onchain'}" # Write proxy/operator DID attributes ros2 service call /peaqos_node/did/write_proxy_attributes \ peaq_ros2_interfaces/srv/PeaqosWriteProxyDidAttributes \ "{signer_address: '', proxy_agent_id: 1, machine_ids: [1, 2, 3]}" ``` ## Events | Service | Type | | :--------------------------------- | :------------------------------------------------- | | `/peaqos_node/events/validate` | `peaq_ros2_interfaces/srv/PeaqosValidateEvent` | | `/peaqos_node/events/submit` | `peaq_ros2_interfaces/srv/PeaqosSubmitEvent` | | `/peaqos_node/events/batch_submit` | `peaq_ros2_interfaces/srv/PeaqosBatchSubmitEvents` | `/events/validate` runs locally: it computes the data hash and checks the payload before you spend gas. `/events/batch_submit` goes through the peaq batch precompile (`0x...0805`) so a multi-event submission is atomic on chain. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Validate locally ros2 service call /peaqos_node/events/validate \ peaq_ros2_interfaces/srv/PeaqosValidateEvent \ "{machine_id: 1, event_type: 1, value: 1, timestamp: 1770000000, raw_data_hex: '0x', trust_level: 1, source_chain_id: 3338, source_tx_hash: '', metadata_hex: '0x7b7d'}" # Submit one event ros2 service call /peaqos_node/events/submit \ peaq_ros2_interfaces/srv/PeaqosSubmitEvent \ "{signer_address: '', machine_id: 1, event_type: 1, value: 1, timestamp: 1770000000, raw_data_hex: '0x', trust_level: 1, source_chain_id: 3338, source_tx_hash: '', metadata_hex: '0x7b7d'}" # Atomic batch ros2 service call /peaqos_node/events/batch_submit \ peaq_ros2_interfaces/srv/PeaqosBatchSubmitEvents \ "{signer_address: '', events_json: '[{\"machine_id\":1,\"event_type\":1,\"value\":1,\"timestamp\":1770000000,\"raw_data_hex\":\"0x\",\"trust_level\":1,\"source_chain_id\":3338,\"source_tx_hash\":\"\",\"metadata_hex\":\"0x7b7d\"}]'}" ``` For event field rules, see [Events](/peaqos/concepts/events) and [SDK JS event validation](/peaqos/sdk-reference/sdk-js#validatesubmiteventparams). ## MCR queries Hosted MCR API queries. Returned bodies are SDK JSON passthrough; operator-fleet results may include per-machine `negative_flag` and top-level `pagination`. | Service | Type | | :----------------------------------- | :----------------------------------------------------- | | `/peaqos_node/mcr/query` | `peaq_ros2_interfaces/srv/PeaqosQueryMcr` | | `/peaqos_node/mcr/machine` | `peaq_ros2_interfaces/srv/PeaqosQueryMachine` | | `/peaqos_node/mcr/operator_machines` | `peaq_ros2_interfaces/srv/PeaqosQueryOperatorMachines` | ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/mcr/query \ peaq_ros2_interfaces/srv/PeaqosQueryMcr \ "{did: 'did:peaq:'}" ros2 service call /peaqos_node/mcr/machine \ peaq_ros2_interfaces/srv/PeaqosQueryMachine \ "{did: 'did:peaq:'}" ros2 service call /peaqos_node/mcr/operator_machines \ peaq_ros2_interfaces/srv/PeaqosQueryOperatorMachines \ "{did: 'did:peaq:'}" ``` The full HTTP API is documented under [API Reference](/peaqos/api-reference/overview). ## Smart accounts ERC-4337 machine accounts are deployed through `MachineAccountFactory`. Address prediction is deterministic (CREATE2 salt) and cheap; call `address` first, deploy second. | Service | Type | | :----------------------------------- | :------------------------------------------------------ | | `/peaqos_node/smart_account/address` | `peaq_ros2_interfaces/srv/PeaqosGetSmartAccountAddress` | | `/peaqos_node/smart_account/deploy` | `peaq_ros2_interfaces/srv/PeaqosDeploySmartAccount` | ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # Predict address (no tx) ros2 service call /peaqos_node/smart_account/address \ peaq_ros2_interfaces/srv/PeaqosGetSmartAccountAddress \ "{signer_address: '', owner: '', machine: '', daily_limit: '', salt: '0'}" # Deploy ros2 service call /peaqos_node/smart_account/deploy \ peaq_ros2_interfaces/srv/PeaqosDeploySmartAccount \ "{signer_address: '', owner: '', machine: '', daily_limit: '', salt: '0'}" ``` `daily_limit` is kept on the service for backward compatibility. The current `MachineAccountFactory` ignores it, so an empty string is fine. Both calls require `peaq_os.contracts.machine_account_factory` (or `MACHINE_ACCOUNT_FACTORY_ADDRESS`) to be set. ## Bridge | Service | Type | | :--------------------------------- | :---------------------------------------------------- | | `/peaqos_node/bridge/nft` | `peaq_ros2_interfaces/srv/PeaqosBridgeNft` | | `/peaqos_node/bridge/wait_arrival` | `peaq_ros2_interfaces/srv/PeaqosWaitForBridgeArrival` | ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} ros2 service call /peaqos_node/bridge/nft \ peaq_ros2_interfaces/srv/PeaqosBridgeNft \ "{signer_address: '', token_id: 1, source: 'peaq', destination: 'base', recipient: '', base_rpc_url: '', base_nft_address: '', options_hex: ''}" ros2 service call /peaqos_node/bridge/wait_arrival \ peaq_ros2_interfaces/srv/PeaqosWaitForBridgeArrival \ "{dst_rpc_url: 'https://mainnet.base.org', dst_nft_address: '0xee8A521eA434b11F956E2402beC5eBfa753Babfa', token_id: 1, timeout: 900}" ``` Bridging has its own page: see [Bridge](/peaqos/sdk-reference/ros2/bridge) for the full peaq to Base flow, gas requirements, and arrival polling. Reverse Base to peaq bridging requires Base ETH on the signer wallet. ## Tips * Source ROS and the workspace before every session: `source /opt/ros//setup.bash && source install/setup.bash`. * Stream logs in another terminal: `tail -f /tmp/peaqos_node.log`. * For batched onboarding flows, prefer `/events/batch_submit` over multiple `/events/submit` calls: single tx, lower fees. # SDK: JavaScript Source: https://docs.peaq.xyz/peaqos/sdk-reference/sdk-js TypeScript client for peaqOS. Class, static factories, methods, types, constants. `@peaqos/peaq-os-sdk` is the opinionated TypeScript entry point for the Machine Financial Passport flow. Each capability is exposed both as a `PeaqosClient` instance method and as a standalone function. ## Install ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install @peaqos/peaq-os-sdk viem dotenv ``` * **Node.js:** ≥ 22 * **TypeScript:** ≥ 5 * **Peer dependency:** `viem` (>= 2.47.10) * **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 | Variable | Required | Default | Purpose | | :-------------------------------- | :--------- | :---------------------- | :---------------------------------------------------------------------------------------------------- | | `PEAQOS_RPC_URL` | Yes | n/a | peaq chain RPC endpoint | | `PEAQOS_PRIVATE_KEY` | Yes | n/a | Owner private key (`0x` + 64 hex) | | `IDENTITY_REGISTRY_ADDRESS` | Yes | n/a | Identity Registry contract | | `IDENTITY_STAKING_ADDRESS` | Yes | n/a | Identity Staking contract | | `EVENT_REGISTRY_ADDRESS` | Yes | n/a | Event Registry contract | | `MACHINE_NFT_ADDRESS` | Yes | n/a | Machine NFT contract (LayerZero ONFT) | | `DID_REGISTRY_ADDRESS` | Yes | n/a | DID Registry precompile | | `BATCH_PRECOMPILE_ADDRESS` | Yes | n/a | Batch precompile for multi-call bonding | | `MACHINE_ACCOUNT_FACTORY_ADDRESS` | No | n/a | `MachineAccountFactory`. Required only for `deploySmartAccount` / `getSmartAccountAddress`. | | `MACHINE_NFT_ADAPTER_ADDRESS` | No | n/a | `MachineNFTAdapter` (LayerZero ONFT adapter). Required only for `bridgeNft` when `source === "peaq"`. | | `PEAQOS_MCR_API_URL` | No | `http://127.0.0.1:8000` | MCR API base URL | | `OWS_PASSPHRASE` | Wallet ops | — | OWS vault passphrase for create/import/export wallet helpers when no explicit passphrase is passed | ### peaq mainnet contracts Use these addresses for the `PEAQOS_*_ADDRESS` variables when pointing at peaq mainnet. All contracts are UUPS upgradeable proxies; treat the addresses as the current proxy pointers. | Variable | Address | | :-------------------------------- | :------------------------------------------------------------------- | | `IDENTITY_REGISTRY_ADDRESS` | `0xb53Af985765031936311273599389b5B68aC9956` | | `IDENTITY_STAKING_ADDRESS` | `0x11c05A650704136786253e8685f56879A202b1C7` | | `EVENT_REGISTRY_ADDRESS` | `0x43c6c12eecAf4fB3F164375A9c44f8a6Efc139b9` | | `MACHINE_NFT_ADDRESS` | `0x2943F80e9DdB11B9Dd275499C661Df78F5F691F9` | | `DID_REGISTRY_ADDRESS` | `0x0000000000000000000000000000000000000800` (peaq DID precompile) | | `BATCH_PRECOMPILE_ADDRESS` | `0x0000000000000000000000000000000000000805` (peaq batch precompile) | | `MACHINE_ACCOUNT_FACTORY_ADDRESS` | `0x4A808d5A90A2c91739E92C70aF19924e0B3D527f` | | `MACHINE_NFT_ADAPTER_ADDRESS` | `0x9AD5408702EC204441A88589B99ADfC2514AFAE6` | For agung testnet addresses see [Install → Agung testnet contracts](/peaqos/install#agung-testnet-contracts). Bridging is mainnet-only: LayerZero has no DVN routes to agung. ## Client ### `PeaqosClient` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} class PeaqosClient { constructor(config: Readonly); readonly rpcUrl: string; readonly contracts: Readonly; readonly apiUrl: string; readonly operationalLimits: Readonly; readonly publicClient: PublicClient; readonly walletClient: WalletClient; get address(): Address; static fromEnv(): PeaqosClient; static fromWallet( nameOrId: string, passphrase: string | undefined, owsSigning: boolean | undefined, config: Readonly>, options?: WalletOptions, ): Promise; static generateKeypair(): Readonly<{ address: Address; privateKey: `0x${string}` }>; // Static wallet wrappers (thin pass-throughs to the module-level helpers) static createWallet(name: string, passphrase?: string, words?: 12 | 24, options?: WalletOptions): Promise; static importWallet(name: string, privateKey: string, passphrase?: string, chain?: ImportChain, options?: WalletOptions): Promise; static importWalletMnemonic(name: string, mnemonic: string, passphrase?: string, index?: number, options?: WalletOptions): Promise; static listWallets(options?: WalletOptions): Promise; static getWallet(nameOrId: string, options?: WalletOptions): Promise; static exportWallet(nameOrId: string, passphrase?: string, options?: WalletOptions): Promise; static deleteWallet(nameOrId: string, options?: WalletOptions): Promise; toJSON(): Record; } ``` RPC endpoint (non-empty). `0x` + 64 hex characters. All six contract addresses. MCR API URL. Defaults to `DEFAULT_API_URL`. Per-tx and rate-limit caps. All-zero disables limits. Returns a `PeaqosClient` instance. `toJSON()` and `util.inspect` output redact the private key (`"[REDACTED]"`). Other RPC endpoints are available. See [Public RPC endpoints](/peaqos/install#public-rpc-endpoints). ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { PeaqosClient } from "@peaqos/peaq-os-sdk"; const client = new PeaqosClient({ rpcUrl: "https://peaq.api.onfinality.io/public", privateKey: "0xabc...def", contracts: { identityRegistry: "0x...", identityStaking: "0x...", eventRegistry: "0x...", machineNft: "0x...", didRegistry: "0x...", batchPrecompile: "0x...", }, }); console.log(client.address); // checksummed 0x address ``` **Errors:** `ValidationError`: missing/invalid `rpcUrl`, `privateKey`, or any contract address. ### `fromEnv` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} static fromEnv(): PeaqosClient; ``` Returns a fully configured `PeaqosClient`. All required env vars must be set (see [Environment variables](#environment-variables)). ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient } from "@peaqos/peaq-os-sdk"; const client = PeaqosClient.fromEnv(); ``` **Errors:** `ValidationError`: any required env var missing or empty. ### `fromWallet` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} static fromWallet( nameOrId: string, passphrase: string | undefined, owsSigning: boolean | undefined, config: Readonly>, options?: WalletOptions, ): Promise; ``` Builds a `PeaqosClient` from an OWS vault wallet. 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. Wallet name or UUID in the OWS vault. Vault passphrase. Pass `undefined` to fall back to the `OWS_PASSPHRASE` env var. Route signing through OWS. Defaults to `true` when `undefined`. Client config without `privateKey` (the wallet provides the signer). Optional vault configuration (e.g. custom `vaultPath`). ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { PeaqosClient } from "@peaqos/peaq-os-sdk"; const client = await PeaqosClient.fromWallet("my-machine", "s3cret", true, { rpcUrl: "https://peaq.api.onfinality.io/public", contracts: { identityRegistry: "0x...", identityStaking: "0x...", eventRegistry: "0x...", machineNft: "0x...", didRegistry: "0x...", batchPrecompile: "0x...", }, }); ``` **Errors:** `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. 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](/peaqos/wallets) 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](/peaqos/wallets#sdk-methods). ### `generateKeypair` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} static generateKeypair(): Readonly<{ address: Address; privateKey: `0x${string}`; }>; ``` Returns a frozen object with a fresh `secp256k1` `privateKey` and its derived `address`. No chain interaction. The private key never touches disk. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const kp = PeaqosClient.generateKeypair(); console.log(kp.address); // 0x... console.log(kp.privateKey); // 0x... ``` ### OWS wallet lifecycle OWS wallet helpers are available as static `PeaqosClient` methods and standalone functions. They derive multi-chain accounts, keep wallet material in an encrypted OWS vault, and return public `WalletInfo` metadata. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} static createWallet(name: string, passphrase?: string, words?: 12 | 24, options?: WalletOptions): Promise; static importWallet(name: string, privateKey: string, passphrase?: string, chain?: ImportChain, options?: WalletOptions): Promise; static importWalletMnemonic(name: string, mnemonic: string, passphrase?: string, index?: number, options?: WalletOptions): Promise; static listWallets(options?: WalletOptions): Promise; static getWallet(nameOrId: string, options?: WalletOptions): Promise; static exportWallet(nameOrId: string, passphrase?: string, options?: WalletOptions): Promise; static deleteWallet(nameOrId: string, options?: WalletOptions): Promise; static fromWallet(nameOrId: string, passphrase: string | undefined, owsSigning: boolean | undefined, config: Omit, options?: WalletOptions): Promise; ``` OWS wallet helpers are bundled with `@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. Frozen object with `id`, `name`, `createdAt`, `keyType`, `peaqAddress`, and `accounts`. Each account has `accountId`, `address`, `chainId`, `network`, and `derivationPath`. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { PeaqosClient, IMPORT_CHAIN_EVM, extractPeaqAddress, } from "@peaqos/peaq-os-sdk"; const wallet = await PeaqosClient.createWallet("robot-001"); console.log(wallet.peaqAddress); const imported = await PeaqosClient.importWallet( "legacy-machine", "0xabc...def", undefined, IMPORT_CHAIN_EVM, ); const all = await PeaqosClient.listWallets(); const same = await PeaqosClient.getWallet(imported.id); console.log(all.length, extractPeaqAddress(same.accounts)); const client = await PeaqosClient.fromWallet(imported.id, undefined, true, { rpcUrl: "https://quicknode1.peaq.xyz", contracts: { identityRegistry: "0x...", identityStaking: "0x...", eventRegistry: "0x...", machineNft: "0x...", didRegistry: "0x0000000000000000000000000000000000000800", batchPrecompile: "0x0000000000000000000000000000000000000805", }, }); console.log(client.address); ``` `exportWallet` returns mnemonic or private-key material, and `fromWallet` consumes a vault passphrase. Keep these in local administrative tooling; do not expose them through robot control channels. ### 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`. | Accessor | Type | Description | | :------------------ | :---------------------------- | :-------------------------------------------------- | | `address` | `Address` | Checksummed owner address derived from `privateKey` | | `rpcUrl` | `string` | Configured RPC endpoint | | `contracts` | `Readonly` | Frozen contract address map | | `apiUrl` | `string` | MCR API base URL | | `operationalLimits` | `Readonly` | Per-tx + rate-limit caps | | `publicClient` | `PublicClient` (viem) | Read-only chain client | | `walletClient` | `WalletClient` (viem) | Signer-bound chain client | *** ## 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. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async registerMachine(): Promise; ``` The newly allocated machine ID, decoded from the `Registered` event in the transaction receipt. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const client = PeaqosClient.fromEnv(); const machineId = await client.registerMachine(); console.log(machineId); ``` * `RuntimeError`: chain revert (`AlreadyRegistered`, `IncorrectBondAmount`, or any other custom error), receipt missing the `Registered` log, or decoded `machineId` out of safe-integer range. See [errors](/peaqos/sdk-reference/errors). ### `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`. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async registerFor(machineAddress: `0x${string}`): Promise; ``` Machine EOA. The client's signing address becomes the operator and pays the bond. The newly allocated machine ID for the proxied machine. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const client = PeaqosClient.fromEnv(); const { address: machineAddress } = PeaqosClient.generateKeypair(); const machineId = await client.registerFor(machineAddress); ``` * `ValidationError`: `machineAddress` is not a valid `0x` address. * `RuntimeError`: chain revert (`AlreadyRegistered`, `InvalidMachineAddress` for zero address, `IncorrectBondAmount`, or any other custom error), receipt missing the `Registered` log, or decoded `machineId` out of safe-integer range. The zero address is allowed through to the chain so the user-facing message comes from the `InvalidMachineAddress` revert mapping (contains `"zero address"`). *** ## Gas Station Call [`setupFaucet2FA`](#setupfaucet2fa) to enroll the owner. Call [`confirmFaucet2FA`](#confirmfaucet2fa) with a TOTP from the authenticator. Call [`fundFromGasStation`](#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). ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async setupFaucet2FA( ownerAddress: string, faucetBaseUrl: string, format?: FaucetQrFormat, ): Promise; ``` Owner to enroll (SS58 or hex). Gas Station base URL. QR format. Defaults to `"svg"`. The enrolled owner address. OTP auth URI for authenticator apps. QR code image URL. Expires after \~2 minutes. Render immediately, never persist. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const setup = await client.setupFaucet2FA( client.address, "https://depinstation.peaq.network", ); console.log(setup.otpauthUri); console.log(setup.qrImageUrl); ``` **Errors:** `ValidationError` on empty args. `RuntimeError` for `INVALID_OWNER_ADDRESS`, `INVALID_PAYLOAD`, `QR_GENERATION_FAILED`, unexpected envelope, or HTTP failure. See [errors](/peaqos/sdk-reference/errors). ### `confirmFaucet2FA` Confirms 2FA enrollment with a fresh TOTP code. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async confirmFaucet2FA( ownerAddress: string, faucetBaseUrl: string, twoFactorCode: string, ): Promise; ``` Owner address being confirmed. Gas Station base URL. Fresh 6-digit TOTP. Returns `void` on successful activation. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} await client.confirmFaucet2FA( client.address, "https://depinstation.peaq.network", "123456", ); ``` **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`. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async fundFromGasStation( params: FundFromGasStationParams, faucetBaseUrl: string, ): Promise; ``` 2FA-enrolled owner (SS58 or hex). Machine EOA to fund. Faucet-configured chain identifier (e.g., `"peaq"`). Current TOTP. UUID idempotency key. Auto-generated if omitted. Transaction hash. Decimal wei. Never a JS number. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const result = await client.fundFromGasStation( { ownerAddress: client.address, targetWalletAddress: machineAddress, chainId: "peaq", twoFactorCode: "123456", }, "https://depinstation.peaq.network", ); if (result.status === "success") { console.log("tx:", result.txHash, "amount:", result.fundedAmount); } else { console.log("already funded; balance:", result.currentBalance); } ``` * `ValidationError`: missing/empty required fields, `requestId` not a UUID, or `faucetBaseUrl` empty. * `RuntimeError`: any documented faucet code: `INVALID_2FA`, `2FA_NOT_CONFIGURED`, `2FA_NOT_ACTIVE`, `2FA_LOCKED`, `DUPLICATE_REQUEST`, `REQUEST_ALREADY_PROCESSED`, `RATE_LIMITED`, `CAP_EXCEEDED_OWNER`, `CAP_EXCEEDED_WALLET`, `TRANSFER_FAILED`. See [errors](/peaqos/sdk-reference/errors) for the full code → cause → retry table. *** ## 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 atomic `batchAll` transaction via the peaq Batch precompile. ### `mintNft` Mints a Machine NFT on the MachineNFT contract for a registered, bonded machine. Returns the transaction hash. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async mintNft( machineId: number, recipient: `0x${string}`, ): Promise; ``` Registered machine ID. Must be a positive integer. Address that will own the minted NFT. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const txHash = await client.mintNft(machineId, client.address); ``` * `ValidationError`: `machineId` not positive, `recipient` not a valid `0x` address, or `client.contracts.machineNft` not a valid address. * `RuntimeError`: chain revert (`MachineNotBonded`, `AlreadyMinted`, `NotMachineOwner`, `MachineNotFound`, `InvalidAddress`) or transaction failure. See [errors](/peaqos/sdk-reference/errors). ### `tokenIdOf` Reads the NFT token ID assigned to a registered machine via a view call. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async tokenIdOf(machineId: number): Promise; ``` Registered machine ID. Must be a positive integer. The NFT token ID, or `0` if no NFT has been minted for this machine. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const tokenId = await client.tokenIdOf(machineId); if (tokenId === 0) { console.log("No NFT minted yet"); } ``` * `ValidationError`: `machineId` not a positive integer, or `client.contracts.machineNft` not a valid address. * `RuntimeError`: contract returns a token ID beyond `Number.MAX_SAFE_INTEGER`. ### `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. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async writeMachineDIDAttributes( params: WriteMachineDIDParams, ): Promise; ``` Registered machine ID. NFT token ID assigned to the machine. Operator DID reference. May be an empty string. ASCII, ≤ 2560 bytes. Non-empty ASCII URL, ≤ 2560 bytes. Non-empty ASCII URL for the machine's data API, ≤ 2560 bytes. Visibility setting. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const txHash = await client.writeMachineDIDAttributes({ machineId: 42, nftTokenId: 1, operatorDid: "", documentationUrl: "https://docs.example.com", dataApi: "https://api.example.com", dataVisibility: "public", }); ``` ### `writeProxyDIDAttributes` Atomically writes the two canonical Proxy DID attributes (`machineId`, `machines`) to the caller's DID. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async writeProxyDIDAttributes( params: WriteProxyDIDParams, ): Promise; ``` The proxy operator's registered machine ID. Non-empty list of positive machine IDs managed by this proxy. The JSON-encoded array must be ≤ 2560 bytes. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const txHash = await client.writeProxyDIDAttributes({ proxyMachineId: 10, machineIds: [42, 43, 44], }); ``` ### `readAttribute` Reads a single DID attribute directly from the peaq DID precompile. Most consumers should prefer the [`/machine/{did}` API](/peaqos/api-reference/get-machine), which composes the full attribute set; this helper is the on-chain escape hatch. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function readAttribute( client: PeaqosClient, did: Address, name: string, ): Promise; ``` The DID account address whose attribute is being read (typically a machine address, but any DID-bearing EOA works). Attribute key, e.g. `"machineId"`, `"data_visibility"`, `"machines"`. `{ name: string; value: string; validity: number; created: bigint }`. `validity` is `0` when the attribute has no expiry. Throws `RuntimeError` if the attribute does not exist on the precompile. ### `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. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} function encodeAddAttribute( didAccount: Address, name: string, value: string, validFor: number, ): Hex; ``` Address of the DID account being written. On-chain this must equal `msg.sender` of the resulting precompile call. Attribute name. ASCII only, ≤ 64 bytes. Attribute value. ASCII only, ≤ 2560 bytes. Validity period in blocks. `0` means no expiry. Must be a non-negative integer in the `uint32` range. ABI-encoded call data. Throws `ValidationError` if any constraint is violated. *** ## Smart accounts ERC-4337 smart accounts deployed via the `MachineAccountFactory`. 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. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async deploySmartAccount( params: DeploySmartAccountParams, ): Promise
; ``` EOA that will own the smart account. Machine EOA the account is scoped to. Non-negative CREATE2 salt. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const address = await client.deploySmartAccount({ owner: client.address, machine: machineAddress, salt: 0, }); ``` ### `getSmartAccountAddress` Read-only equivalent: computes the CREATE2 address for the given `(owner, machine, salt)` without deploying. Returns the same address `deploySmartAccount` would produce. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async getSmartAccountAddress( params: DeploySmartAccountParams, ): Promise
; ``` Same parameters as `deploySmartAccount`. No transaction, no gas. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const predicted = await client.getSmartAccountAddress({ owner: client.address, machine: machineAddress, salt: 0, }); ``` *** ## Bridge Supported routes: peaq ↔ Base. Additional peaqOS chains are added as peer contracts deploy. See [Machine NFT cross-chain portability](/peaqos/concepts/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. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async bridgeNft(params: BridgeNftParams): Promise; ``` Positive NFT id to bridge. Origin chain. Target chain (must differ from `source`). Destination-chain recipient. Raw LayerZero v2 `extraOptions` bytes. Defaults to `"0x"` (the contract's enforced options). Base RPC URL. Required only when `source === "base"`. `MachineNFTBase` address on Base. Required only when `source === "base"`. The source-chain transaction hash. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const txHash = await client.bridgeNft({ tokenId: 42, source: "peaq", destination: "base", recipient: "0xabc...", }); ``` ### `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. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} static async waitForBridgeArrival( params: WaitForBridgeArrivalParams, ): Promise; ``` Destination-chain RPC endpoint. `MachineNFT` contract address on the destination. The NFT id expected to arrive. Wait budget in seconds. Defaults to 300 (5 min). Optional abort signal. When aborted, the poll stops immediately with a `RuntimeError` code `ABORTED`. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const arrived = await PeaqosClient.waitForBridgeArrival({ dstRpcUrl: "https://mainnet.base.org", dstNftAddress: "0x...", tokenId: 42, timeout: 120, }); ``` *** ## Events (Qualify) ### `submitEvent` Submits a single event to `EventRegistry`. Validates and normalizes the payload, then calls the contract. Returns the transaction hash and the computed `dataHash`. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async submitEvent( params: SubmitEventParams, ): Promise<{ txHash: Hex; dataHash: Hex }>; ``` Param shape matches [`validateSubmitEventParams`](#validatesubmiteventparams) below. `value` is an **ISO 4217 minor-unit integer** (cents for USD/HKD, whole units for JPY/KRW/VND, thousandths for BHD). `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. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} const { txHash, dataHash } = await client.submitEvent({ machineId: 1024, eventType: EVENT_TYPE_REVENUE, value: 12500, // $125.00 in cents currency: "USD", timestamp: Math.floor(Date.now() / 1000), rawData: new Uint8Array([1, 2, 3]), trustLevel: TRUST_ON_CHAIN_VERIFIABLE, sourceChainId: SUPPORTED_CHAIN_IDS.peaq, sourceTxHash: null, metadata: new Uint8Array([]), }); ``` * `ValidationError`: any `params` field fails validation. * `ValueCapExceeded` / `RateLimitExceeded`: client-side operational limits hit. * `RuntimeError`: chain revert or receipt failure. ### `batchSubmitEvents` Submits multiple events atomically through the peaq Batch precompile. All events land in the same transaction: all succeed or all revert. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async batchSubmitEvents( events: ReadonlyArray, ): Promise; ``` Non-empty list of event payloads. Each element is validated individually before submission. One transaction hash per input event. All hashes are identical (same batch tx). * `ValidationError`: list is empty, or any event fails validation. * `ValueCapExceeded` / `RateLimitExceeded`: operational limits hit for any event in the batch. * `RuntimeError`: batch revert or transport failure. ### `validateSubmitEventParams` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} function validateSubmitEventParams(params: SubmitEventParams): void; ``` Machine ID returned by `registerMachine` / `registerFor`. `0` revenue, `1` activity. Non-negative ISO 4217 minor-unit integer. Cents for USD/HKD, whole units for JPY/KRW/VND, thousandths for BHD. Activity events: any non-negative integer or `0`. 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. Unix seconds. Off-chain payload hashed into `dataHash`. `0` self-reported, `1` on-chain verifiable, `2` hardware-signed. Originating chain. Use `SUPPORTED_CHAIN_IDS.peaq` for local. Cross-chain tx hash when applicable. Arbitrary bytes stored on-chain alongside the event. Use empty bytes when no metadata is needed. Returns `void`. Throws `ValidationError` on any invariant violation. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { validateSubmitEventParams, EVENT_TYPE_REVENUE, TRUST_ON_CHAIN_VERIFIABLE, SUPPORTED_CHAIN_IDS, } from "@peaqos/peaq-os-sdk"; validateSubmitEventParams({ machineId: 1024, eventType: EVENT_TYPE_REVENUE, value: 1250, // $12.50 in cents currency: "USD", timestamp: Math.floor(Date.now() / 1000), rawData: new Uint8Array([1, 2, 3]), trustLevel: TRUST_ON_CHAIN_VERIFIABLE, sourceChainId: SUPPORTED_CHAIN_IDS.base, sourceTxHash: "0xabc...", metadata: new Uint8Array([]), }); ``` ### `computeDataHash` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} function computeDataHash(rawData: Uint8Array): Hex; ``` Off-chain payload bytes to hash. Returns a `keccak256` hash as `0x` + 64 hex characters. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { computeDataHash } from "@peaqos/peaq-os-sdk"; const dataHash = computeDataHash(new TextEncoder().encode("revenue: $12.50")); console.log(dataHash); ``` ### `checkOperationalLimits` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} function checkOperationalLimits( params: { machineId: number; value: number }, limits: OperationalLimits, tracker: EventTracker | null, ): void; ``` Machine ID and event value. Configured `maxValuePerTx`, `rateLimitMaxEvents`, `rateLimitWindowSeconds`. Current rate-tracking state for the machine. Pass `null` if you are not tracking window state. `EventTracker` is `{ machineId: number; count: number; windowStart: number }`. Returns `void`. Throws `ValueCapExceeded` or `RateLimitExceeded` on limit violation. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { checkOperationalLimits } from "@peaqos/peaq-os-sdk"; checkOperationalLimits( { machineId: 1, value: 100 }, client.operationalLimits, { machineId: 1, count: 50, windowStart: Date.now() / 1000 - 1800 }, ); ``` *** ## Queries Read-only helpers backed by the off-chain MCR API server (`client.apiUrl`). 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}`](/peaqos/api-reference/get-mcr). ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function queryMcr( client: PeaqosClient, did: string, options?: GetJsonOptions, ): Promise; ``` Machine DID. Must start with `did:peaq:0x`. Request budget in ms. Defaults to 30 000. Caller abort signal. Either signal aborting wins. Frozen object with camelCase fields: `did`, `machineId`, `mcrScore` (number, 0–100), `mcr` (`"AAA" | "AA" | "A" | "BBB" | "BB" | "B" | "NR" | "Provisioned"`), `mcrDegraded` (boolean: `true` when ≥1 scored event used a stale or unavailable FX source), `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"`), `totalRevenue` (whole USD as float, **not cents**), `averageRevenuePerEvent` (whole USD as float), `lastUpdated` (unix seconds or `null`). The MCR API returns `mcr_score: null` while a machine is still `Provisioned` or has rating `NR`. The JS SDK coerces this to `0` so `mcrScore` is always a number. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { queryMcr } from "@peaqos/peaq-os-sdk"; const score = await queryMcr(client, "did:peaq:0xMachineAddress", { timeoutMs: 5_000, }); console.log(`${score.mcrScore} → ${score.mcr}`); ``` * `ValidationError`: `did` does not start with `did:peaq:0x`. * `RuntimeError`: HTTP 404 (`NOT_FOUND`), 503 (`SERVICE_UNAVAILABLE`), other 5xx (`SERVER_ERROR`), other non-2xx (`HTTP_ERROR`), timeout (`TIMEOUT`), caller abort (`ABORTED`), transport failure (`NETWORK_ERROR`), or malformed body (`BAD_RESPONSE`). ### `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}`](/peaqos/api-reference/get-machine). ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function queryMachine( client: PeaqosClient, did: string, options?: GetJsonOptions, ): Promise; ``` Machine DID. Must start with `did:peaq:0x`. Optional `timeoutMs` (default 30 000) and caller `signal`. 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`. Visibility-dependent extras: `data_api`, `event_data`, `partner_data`, `partner_data_error`. The SDK throws `BAD_RESPONSE` if the server returns anything that fails the schema guard. See [`GET /machine/{did}`](/peaqos/api-reference/get-machine) for full field semantics. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { queryMachine } from "@peaqos/peaq-os-sdk"; const profile = await queryMachine(client, "did:peaq:0xMachineAddress"); console.log(profile.name, profile.peaqos.mcr, profile.peaqos.mcr_score); ``` * `ValidationError`: `did` does not start with `did:peaq:0x`. * `RuntimeError`: same HTTP / transport codes as `queryMcr`; `BAD_RESPONSE` if any required field on `MachineProfileResponse` or its `peaqos` sub-object is missing or wrong-typed. ### `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`](/peaqos/api-reference/get-operator-machines). ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} async function queryOperatorMachines( client: PeaqosClient, did: string, options?: GetJsonOptions, ): Promise; ``` Operator DID. Must start with `did:peaq:0x`. Optional `timeoutMs` (default 30 000) and caller `signal`. Frozen object with `operatorDid`, a frozen `machines` array, and a `pagination` object. Each machine entry exposes `did`, `machineId`, `mcrScore` (number, 0–100; `null` coerced to `0`), `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). ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { queryOperatorMachines } from "@peaqos/peaq-os-sdk"; const fleet = await queryOperatorMachines(client, "did:peaq:0xProxyAddress"); for (const m of fleet.machines) { console.log(`${m.did} → ${m.mcrScore} (${m.mcr}), negative=${m.negativeFlag}`); } console.log(`showing ${fleet.machines.length} of ${fleet.pagination.total}`); ``` * `ValidationError`: `did` does not start with `did:peaq:0x`. * `RuntimeError`: same HTTP / transport codes as `queryMcr`; `BAD_RESPONSE` if the body or any `machines` entry is malformed. *** ## Error classes See [errors](/peaqos/sdk-reference/errors) for the full hierarchy and the 20-code faucet table. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { PeaqosError, RuntimeError, ValidationError, ValueCapExceeded, RateLimitExceeded, } from "@peaqos/peaq-os-sdk"; ``` *** ## Type exports ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import type { // Client PeaqosClientConfig, ContractAddresses, OperationalLimits, // Events SubmitEventParams, MachineEvent, EventType, TrustLevel, EventTracker, // Identity DataVisibility, BondStatus, // API responses MCRResponse, MachineProfileResponse, OperatorMachinesResponse, MCRRating, RevenueTrend, // DID WriteMachineDIDParams, WriteProxyDIDParams, DIDWriteAttribute, DIDAttributeResult, // Wallet WalletInfo, AccountInfo, KeyType, ImportChain, WalletOptions, // Query options GetJsonOptions, // Methods DeploySmartAccountParams, BridgeNftParams, WaitForBridgeArrivalParams, // Faucet FaucetErrorCode, FaucetFundResponse, FaucetFundSkippedResponse, FaucetFundSuccessResponse, FaucetQrFormat, FaucetSetupResponse, FundFromGasStationParams, // OWS signing OwsSigningErrorCode, // Error constructor options RuntimeErrorOptions, ValidationErrorArgs, } from "@peaqos/peaq-os-sdk"; // Enums import { MachineStatus } from "@peaqos/peaq-os-sdk"; ``` *** ## Constants ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { EVENT_TYPE_REVENUE, // 0 EVENT_TYPE_ACTIVITY, // 1 TRUST_SELF_REPORTED, // 0 TRUST_ON_CHAIN_VERIFIABLE, // 1 TRUST_HARDWARE_SIGNED, // 2 } from "@peaqos/peaq-os-sdk"; ``` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { DID_ATTR_MACHINE_ID, DID_ATTR_NFT_TOKEN_ID, DID_ATTR_OPERATOR, DID_ATTR_DOCUMENTATION_URL, DID_ATTR_DATA_API, DID_ATTR_DATA_VISIBILITY, DID_ATTR_MACHINES, DID_MAX_NAME_BYTES, // 64 DID_MAX_VALUE_BYTES, // 2560 } from "@peaqos/peaq-os-sdk"; ``` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { SUPPORTED_CHAIN_IDS, // { peaq: 3338, ethereum: 1, base: 8453, polygon: 137, arbitrum: 42161, optimism: 10 } LAYER_ZERO_EIDS, // { peaq: 30302, base: 30184 } DEFAULT_API_URL, // "http://127.0.0.1:8000" } from "@peaqos/peaq-os-sdk"; ``` ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { IMPORT_CHAIN_EVM, // "evm" (default) IMPORT_CHAIN_SOLANA, // "solana" IMPORT_CHAIN_BITCOIN, // "bitcoin" IMPORT_CHAIN_COSMOS, // "cosmos" IMPORT_CHAIN_TRON, // "tron" IMPORT_CHAIN_TON, // "ton" IMPORT_CHAIN_SUI, // "sui" IMPORT_CHAIN_XRPL, // "xrpl" IMPORT_CHAIN_SPARK, // "spark" IMPORT_CHAIN_FILECOIN, // "filecoin" KEY_TYPE_MNEMONIC, // "mnemonic" KEY_TYPE_PRIVATE_KEY, // "private_key" OWS_PASSPHRASE_ENV, // "OWS_PASSPHRASE" DEFAULT_MCR_HTTP_TIMEOUT_MS, // 30_000 } from "@peaqos/peaq-os-sdk"; ``` `IMPORT_CHAIN_*` is the union behind the `ImportChain` type alias used by `importWallet`. `KEY_TYPE_*` matches the `KeyType` field on `WalletInfo`. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { OWS_ERROR_WALLET_NOT_FOUND, // "WALLET_NOT_FOUND" OWS_ERROR_INVALID_PASSPHRASE, // "INVALID_PASSPHRASE" OWS_ERROR_INVALID_INPUT, // "INVALID_INPUT" OWS_ERROR_POLICY_DENIED, // "POLICY_DENIED" OWS_ERROR_CHAIN_NOT_SUPPORTED, // "CHAIN_NOT_SUPPORTED" } from "@peaqos/peaq-os-sdk"; ``` Surface area for the OWS-native signing path used by `PeaqosClient.fromWallet(..., owsSigning: true)`. The matching `OwsSigningErrorCode` type is the union of all five string-literal codes. # SDK: Python Source: https://docs.peaq.xyz/peaqos/sdk-reference/sdk-python Python client for peaqOS. Class, static factories, methods, types, constants. Mirrors the JavaScript SDK in snake_case. `peaq-os-sdk` on PyPI is the Python equivalent of [`@peaqos/peaq-os-sdk`](/peaqos/sdk-reference/sdk-js). Every method and type mirrors the JS SDK; the key differences are listed below. ## Differences from the JavaScript SDK | Aspect | JavaScript | Python | | :-------------- | :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | | Constructor | `new PeaqosClient({ config })` | `PeaqosClient(**kwargs)` (keyword args) | | Keypairs | Frozen `{ address, privateKey }` object | Tuple `(address, private_key)` | | Faucet methods | Global `fetch` | `requests.Session` as first arg | | Response keys | `camelCase` | `snake_case` | | Error hierarchy | `RuntimeError` with `.code` | Separate `RpcError` + `ApiError` | | Extra accessors | n/a | `w3`, `web3`, `session`, `account`, `identity_registry`, `identity_staking`, `event_registry`, `machine_nft`, `did_precompile`, `batch_precompile` | ## Install ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install "peaq-os-sdk>=0.0.2" python-dotenv # Optional, only for OWS wallet lifecycle helpers: pip install "peaq-os-sdk[ows]>=0.0.2" ``` * **Python:** ≥ 3.10 * **Dependencies:** `web3>=6.0`, `eth-account`, `requests` * **Optional wallet dependency:** `open-wallet-standard` through the `[ows]` extra * Virtualenv recommended. `python-dotenv` is optional but recommended: `PeaqosClient.from_env()` reads from dotenv, so `load_dotenv()` at the top of your entry file is the simplest way to load .env. ## 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](/peaqos/sdk-reference/sdk-js#environment-variables) and [peaq mainnet contracts](/peaqos/sdk-reference/sdk-js#peaq-mainnet-contracts). For agung testnet addresses see [Install → Agung testnet contracts](/peaqos/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`. *** ## Client ### `PeaqosClient` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} class PeaqosClient: def __init__( self, *, rpc_url: str, private_key: str, identity_registry: str, identity_staking: str, event_registry: str, machine_nft: str, did_registry: str, batch_precompile: str, machine_account_factory: str | None = None, machine_nft_adapter: str | None = None, api_url: str = DEFAULT_API_URL, operational_limits: OperationalLimits | None = None, ) -> None: ... ``` RPC endpoint. `0x` + 64 hex. Identity Registry contract address. Identity Staking contract address. Event Registry contract address. Machine NFT contract address (ONFT). DID Registry precompile address. Batch precompile address. `MachineAccountFactory` contract address. Required only for `deploy_smart_account` and `get_smart_account_address`. `MachineNFTAdapter` (LayerZero ONFT adapter) contract address on peaq. Required only for `bridge_nft` when `source="peaq"`. MCR API base URL. Defaults to `DEFAULT_API_URL`. Per-tx and rate-limit caps. Returns a `PeaqosClient` instance. `__repr__` redacts the private key. Other RPC endpoints are available. See [Public RPC endpoints](/peaqos/install#public-rpc-endpoints). ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import PeaqosClient client = PeaqosClient( rpc_url="https://peaq.api.onfinality.io/public", private_key="0xabc...def", identity_registry="0x...", identity_staking="0x...", event_registry="0x...", machine_nft="0x...", did_registry="0x...", batch_precompile="0x...", ) print(client.address) ``` **Errors:** `ValidationError`: missing/invalid constructor args. ### `from_env` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} @classmethod def from_env(cls) -> "PeaqosClient": ... ``` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from dotenv import load_dotenv from peaq_os_sdk import PeaqosClient load_dotenv() # load envs from .env file client = PeaqosClient.from_env() ``` **Errors:** `ValidationError`: any required env var missing or empty. ### `from_wallet` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} @classmethod def from_wallet( cls, name_or_id: str, passphrase: str | None = None, ows_signing: bool = True, vault_path: str | None = None, **config_kwargs: Any, ) -> "PeaqosClient": ... ``` 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. | Param | Default | Meaning | | :------------ | :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name_or_id` | required | Wallet name or UUID in the OWS vault. | | `passphrase` | `OWS_PASSPHRASE` env | Vault passphrase. Falls back to the env var when `None`; raises `PeaqosError` when both are missing. | | `ows_signing` | `True` | When `True`, transactions sign through OWS (key never held by the SDK process). When `False`, the key is exported and decrypted at construction so the client is identical to one built with `private_key=...`. | | `vault_path` | `~/.ows/` | Optional custom vault directory. | 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. The passphrase is verified eagerly: a wrong or missing one raises `PeaqosError` at construction. 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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import PeaqosClient # OWS-native (default): private key never decrypted in this process. client = PeaqosClient.from_wallet( "my-machine", passphrase="s3cret", rpc_url="https://peaq-rpc.example.com", identity_registry="0x...", identity_staking="0x...", event_registry="0x...", machine_nft="0x...", did_registry="0x0000000000000000000000000000000000000800", batch_precompile="0x0000000000000000000000000000000000000805", ) # Eager-decrypt: behaves identically to PeaqosClient(private_key=...). eager = PeaqosClient.from_wallet( "my-machine", passphrase="s3cret", ows_signing=False, rpc_url="https://peaq-rpc.example.com", identity_registry="0x...", # ... ) ``` **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](/peaqos/sdk-reference/errors#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](/peaqos/wallets) integration: mnemonic-backed encrypted vault, multi-chain accounts (peaq, Base, Ethereum, Solana, Bitcoin, etc.). Available under `peaq_os_sdk.wallet` and as `@staticmethod`s 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](/peaqos/wallets#sdk-methods). Wallet returns are typed as `WalletInfo` (frozen dataclass) with an `accounts: list[AccountInfo]` field. `AccountInfo` carries `account_id` (CAIP-10), `address`, `chain_id` (CAIP-2), `network` (human-readable name like `"peaq"`, `"base"`, `"ethereum"`, `"solana"`), and `derivation_path`. The SDK synthesizes accounts for every supported EVM chain from the first EVM source returned by OWS: peaq, ethereum, base, polygon, arbitrum, and optimism all surface in `accounts` even when OWS only returns one EVM key. Both classes are re-exported from the package root. ### `generate_keypair` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} @staticmethod def generate_keypair() -> tuple[Address, str]: ... ``` Returns tuple `(address, private_key)`. Both are `0x`-prefixed hex strings; `Address` is a `NewType` over `str` (checksummed). ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} address, private_key = PeaqosClient.generate_keypair() print(address) ``` ### 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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} @staticmethod def create_wallet(name: str, passphrase: str | None = None, words: int = 12, vault_path: str | None = None) -> WalletInfo: ... @staticmethod def import_wallet(name: str, private_key: str, passphrase: str | None = None, chain: ImportChain = IMPORT_CHAIN_EVM, vault_path: str | None = None) -> WalletInfo: ... @staticmethod def import_wallet_mnemonic(name: str, mnemonic: str, passphrase: str | None = None, index: int = 0, vault_path: str | None = None) -> WalletInfo: ... @staticmethod def list_wallets(vault_path: str | None = None) -> list[WalletInfo]: ... @staticmethod def get_wallet(name_or_id: str, vault_path: str | None = None) -> WalletInfo: ... @staticmethod def export_wallet(name_or_id: str, passphrase: str | None = None, vault_path: str | None = None) -> str: ... @staticmethod def delete_wallet(name_or_id: str, vault_path: str | None = None) -> None: ... ``` Install `peaq-os-sdk[ows]` before using these helpers. `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. 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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import PeaqosClient, IMPORT_CHAIN_EVM from peaq_os_sdk.wallet.utils import extract_peaq_address wallet = PeaqosClient.create_wallet("robot-001") print(wallet.peaq_address) imported = PeaqosClient.import_wallet( "legacy-machine", "0xabc...def", chain=IMPORT_CHAIN_EVM, ) all_wallets = PeaqosClient.list_wallets() same = PeaqosClient.get_wallet(imported.id) print(len(all_wallets), extract_peaq_address(same.accounts)) ``` `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) | Accessor | Type | Description | | :------------------ | :----------------------------- | :--------------------------------------------------------------------------------------- | | `address` | `str` | Checksummed owner address | | `w3` / `web3` | `Web3` | Web3 instance | | `session` | `requests.Session` | HTTP session for API calls | | `account` | `LocalAccount` \| `OWSAccount` | Bound signing account; `OWSAccount` when constructed via `from_wallet(ows_signing=True)` | | `identity_registry` | `Contract` | Bound IdentityRegistry contract | | `identity_staking` | `Contract` | Bound IdentityStaking contract | | `event_registry` | `Contract` | Bound EventRegistry contract | | `machine_nft` | `Contract` | Bound MachineNFT contract | | `did_precompile` | `Contract` | Bound peaq DID precompile | | `batch_precompile` | `Contract` | Bound peaq Batch precompile | *** ## Registration ### `register_machine` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def register_machine(self) -> int: ... ``` 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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} machine_id = client.register_machine() print("registered as", machine_id) ``` **Errors:** `RpcError`: chain revert (`AlreadyRegistered`), insufficient balance for gas + 1 PEAQ bond, or any other on-chain failure. See [errors](/peaqos/sdk-reference/errors). ### `register_for` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def register_for(self, machine_address: str) -> int: ... ``` 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`. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} machine_address, machine_key = PeaqosClient.generate_keypair() machine_id = client.register_for(machine_address) print(f"registered machine {machine_address} with machine ID {machine_id}") ``` **Errors:** `ValidationError` on invalid `machine_address`. `RpcError` on chain revert (`AlreadyRegistered`, `InvalidMachineAddress`). *** ## Gas Station ### `setup_faucet_2fa` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def setup_faucet_2fa( self, owner_address: str, faucet_base_url: str, qr_format: Literal["svg", "png"] = "svg", ) -> FaucetSetupResponse: ... ``` Owner to enroll. Gas Station base URL. QR format. Defaults to `"svg"`. Keys: `owner_address: str`, `otpauth_uri: str` (OTP auth URI for authenticator apps), `qr_image_url: str` (expires after \~2 minutes). ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} setup = client.setup_faucet_2fa( owner_address=client.address, faucet_base_url="https://depinstation.peaq.network", ) print(setup["otpauth_uri"]) ``` **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](/peaqos/sdk-reference/errors). ### `confirm_faucet_2fa` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def confirm_faucet_2fa( self, owner_address: str, faucet_base_url: str, two_factor_code: str, ) -> None: ... ``` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} client.confirm_faucet_2fa( owner_address=client.address, faucet_base_url="https://depinstation.peaq.network", two_factor_code="123456", ) ``` **Errors:** `ValidationError`, `ApiError` (`INVALID_2FA`, `2FA_NOT_CONFIGURED`, `2FA_LOCKED`). ### `fund_from_gas_station` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def fund_from_gas_station( self, owner_address: str, target_wallet_address: str, chain_id: str, two_factor_code: str, faucet_base_url: str, request_id: str | None = None, ) -> FaucetFundResponse: ... ``` 2FA-enrolled owner address. Machine EOA to fund. Chain identifier configured on the faucet, e.g. `"peaq"`. Current TOTP. Gas Station base URL. UUID idempotency key. Auto-generated if omitted. 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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} result = client.fund_from_gas_station( owner_address=client.address, target_wallet_address=machine_address, chain_id="peaq", two_factor_code="123456", faucet_base_url="https://depinstation.peaq.network", ) if result["status"] == "success": print("tx:", result["tx_hash"], "amount:", result["funded_amount"]) else: print("skipped; balance:", result["current_balance"]) ``` **Errors:** `ValidationError`, `ApiError` (any of the 20 faucet codes). See [errors](/peaqos/sdk-reference/errors). *** ## NFT & DID 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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def mint_nft(self, machine_id: int, recipient: str) -> str: ... ``` Registered machine ID. Must be a positive integer. `0x`-prefixed 20-byte hex address that will receive the NFT. Transaction hash as a `0x`-prefixed hex string. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} tx_hash = client.mint_nft(machine_id=42, recipient=client.address) ``` **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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def token_id_of(self, machine_id: int) -> int: ... ``` Registered machine ID. Must be a positive integer. The NFT token ID, as a positive integer. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} token_id = client.token_id_of(machine_id=42) ``` **Errors:** `ValidationError` if `machine_id` is not positive. `RpcError` when the machine has no NFT minted (contract reverts). ### `write_machine_did_attributes` Atomically writes the six canonical Machine DID attributes to the caller's DID via a single `batchAll` transaction. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def write_machine_did_attributes( self, machine_id: int, nft_token_id: int, operator_did: str, documentation_url: str, data_api: str, data_visibility: str, ) -> str: ... ``` Registered machine ID. NFT token ID assigned to the machine. Operator DID reference. May be an empty string. ASCII, ≤ 2560 bytes. Non-empty ASCII URL, ≤ 2560 bytes. Non-empty ASCII URL for the machine's data API, ≤ 2560 bytes. Visibility setting. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} tx_hash = client.write_machine_did_attributes( machine_id=42, nft_token_id=1, operator_did="", documentation_url="https://docs.example.com", data_api="https://api.example.com", data_visibility="public", ) ``` ### `write_proxy_did_attributes` Atomically writes the two canonical Proxy DID attributes (`machineId`, `machines`) to the caller's DID. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def write_proxy_did_attributes( self, proxy_machine_id: int, machine_ids: list[int], ) -> str: ... ``` The proxy operator's registered machine ID. Non-empty list of positive machine IDs. The JSON-encoded array must be ≤ 2560 bytes. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} tx_hash = client.write_proxy_did_attributes( proxy_machine_id=10, machine_ids=[42, 43, 44], ) ``` ### `read_attribute` Reads a single DID attribute directly from the peaq DID precompile. Most consumers should prefer the [`/machine/{did}` API](/peaqos/api-reference/get-machine), which composes the full attribute set; this helper is the on-chain escape hatch. Not exported from the package root: import from `peaq_os_sdk.did.did_precompile`. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.did.did_precompile import read_attribute def read_attribute( client: PeaqosClient, did_address: str, name: str, ) -> DIDAttributeResult: ... ``` The machine address whose DID is being read. Attribute key, e.g. `"machineId"`, `"data_visibility"`, `"machines"`. Keys: `name: str`, `value: str`, `validity: int` (seconds; `0` = no expiry), `created: int` (block timestamp). Raises `RpcError` if the attribute does not exist on the precompile. *** ## 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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def deploy_smart_account( self, owner: str, machine: str, salt: int, ) -> Address: ... ``` EOA that will own the smart account. Machine EOA the account is scoped to. Non-negative CREATE2 salt. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} address = client.deploy_smart_account( owner=client.address, machine=machine_address, salt=0, ) ``` **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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def get_smart_account_address( self, owner: str, machine: str, salt: int, ) -> Address: ... ``` Same parameters as `deploy_smart_account`. No transaction, no gas. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} predicted = client.get_smart_account_address( owner=client.address, machine=machine_address, salt=0, ) ``` *** ## Bridge Supported routes: peaq ↔ Base. Additional peaqOS chains are added as peer contracts deploy. See [Machine NFT cross-chain portability](/peaqos/concepts/machine-nft#cross-chain-portability). LayerZero v2 Machine NFT bridging between peaq and Base. Requires `machine_nft_adapter` (or `MACHINE_NFT_ADAPTER_ADDRESS`) when sending from peaq. The SDK's `source` / `destination` string union expands as peer contracts deploy on new chains. ### `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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def bridge_nft( self, token_id: int, source: str, destination: str, recipient: str, *, base_rpc_url: str | None = None, base_nft_address: str | None = None, options: bytes = b"", ) -> Hex32: ... ``` Positive NFT id to bridge. Origin chain. Target chain. Must differ from `source`. Destination-chain recipient address. Base RPC URL. Required only when `source == "base"`. `MachineNFTBase` address on Base. Required only when `source == "base"`. Raw LayerZero v2 `extraOptions` bytes. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} tx = client.bridge_nft( token_id=42, source="peaq", destination="base", recipient="0xabc...", ) ``` ### `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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} @staticmethod def wait_for_bridge_arrival( dst_rpc_url: str, dst_nft_address: str, token_id: int, timeout: int = 300, ) -> bool: ... ``` Destination-chain RPC endpoint. `MachineNFT` contract address on the destination. The NFT id expected to arrive. Wait budget in seconds. Defaults to 300 (5 min). ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} arrived = PeaqosClient.wait_for_bridge_arrival( dst_rpc_url="https://mainnet.base.org", dst_nft_address="0x...", token_id=42, ) ``` *** ## Events (Qualify) ### `submit_event` Submits a single event to `EventRegistry`. Returns `(tx_hash, data_hash)`. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def submit_event( self, *, machine_id: int, event_type: int, value: int, timestamp: int, raw_data: bytes | None, trust_level: int, source_chain_id: int, source_tx_hash: Hex32 | None, metadata: bytes, currency: str | None = ..., # omit for smart default: revenue → "USD", activity → "" ) -> tuple[str, bytes]: ... ``` Param shape mirrors [`validate_submit_event_params`](#validate_submit_event_params) below. `value` is an **ISO 4217 minor-unit integer**: cents for USD/HKD, whole units for JPY/KRW/VND, thousandths for BHD. The MCR pipeline FX-normalizes to USD cents using the rate at `timestamp`. `currency` is a 3-10 char uppercase alphanumeric code on revenue events and `""` on activity events; omitting the kwarg applies the smart default. `batch_submit_events` is strict: `currency` must be supplied per event. `tx_hash` is a `0x`-prefixed hash string; `data_hash` is exactly 32 bytes. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import time tx_hash, data_hash = client.submit_event( machine_id=1024, event_type=EVENT_TYPE_REVENUE, value=12500, # $125.00 in cents currency="USD", timestamp=int(time.time()), raw_data=b"\x01\x02\x03", trust_level=TRUST_ON_CHAIN_VERIFIABLE, source_chain_id=SUPPORTED_CHAINS["peaq"], source_tx_hash=None, metadata=b"", ) ``` **Errors:** `ValidationError`, `ValueCapExceeded`, `RateLimitExceeded`, `RpcError`. ### `batch_submit_events` Submits multiple events atomically through the peaq Batch precompile. All succeed or all revert. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def batch_submit_events( self, events: list[dict[str, object] | SubmitEventParams], ) -> list[str]: ... ``` Non-empty list of event payloads. Items may be `SubmitEventParams` instances or dicts with matching keys. One transaction hash per input event. All hashes are identical (same batch tx). **Errors:** `ValidationError` on empty list or invalid event. `ValueCapExceeded` / `RateLimitExceeded` when operational limits hit. `RpcError` on revert or transport failure. ### `validate_submit_event_params` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def validate_submit_event_params(params: SubmitEventParams) -> None: ... ``` `SubmitEventParams` is a frozen dataclass (imported from `peaq_os_sdk.types.events`): ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} @dataclass(frozen=True, slots=True) class SubmitEventParams: machine_id: int event_type: int # 0 revenue, 1 activity value: int # ISO 4217 subunit integer timestamp: int raw_data: bytes | None trust_level: int # 0, 1, 2 source_chain_id: int source_tx_hash: Hex32 | None metadata: bytes currency: str = ... # revenue: ^[A-Z0-9]{3,10}$; activity: "" ``` The validator uses attribute access, so callers must construct a `SubmitEventParams` instance. Dict literals will raise `AttributeError`. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import time from peaq_os_sdk import ( EVENT_TYPE_REVENUE, TRUST_ON_CHAIN_VERIFIABLE, SUPPORTED_CHAINS, ) from peaq_os_sdk.types.events import SubmitEventParams from peaq_os_sdk.validation import validate_submit_event_params params = SubmitEventParams( machine_id=1024, event_type=EVENT_TYPE_REVENUE, value=1250, # $12.50 in cents currency="USD", timestamp=int(time.time()), raw_data=b"revenue: $12.50", trust_level=TRUST_ON_CHAIN_VERIFIABLE, source_chain_id=SUPPORTED_CHAINS["base"], source_tx_hash="0xabc...", metadata=b"", ) validate_submit_event_params(params) ``` **Errors:** `ValidationError`: any field out of range. ### `compute_data_hash` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def compute_data_hash(raw_data: bytes) -> bytes: ... ``` Returns the 32-byte `keccak256` digest of `raw_data`. Pair with `submit_event`'s `data_hash` output (also bytes) to compare on-chain payloads. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.utils import compute_data_hash data_hash = compute_data_hash(b"revenue: $12.50") ``` ### `check_operational_limits` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def check_operational_limits( params: SubmitEventParams, limits: OperationalLimits, tracker: EventTracker | None, ) -> None: ... ``` Event with `machine_id` and `value`. Configured `max_value_per_tx`, `rate_limit_max_events`, `rate_limit_window_seconds`. Current rate-tracking state for the machine, or `None` when tracking is disabled. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.validation import check_operational_limits, EventTracker tracker = EventTracker(machine_id=1, count=5, window_start=1700000000.0) check_operational_limits(params, client.operational_limits, tracker) ``` **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 (`client.api_url`). 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 currently expose an override. ### `query_mcr` Fetches the Machine Credit Rating for a machine DID. See [`GET /mcr/{did}`](/peaqos/api-reference/get-mcr). ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def query_mcr(client: PeaqosClient, did: str) -> MCRResponse: ... ``` Machine DID. Must start with `did:peaq:0x`. 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` (`"up" | "stable" | "down" | "insufficient"`), `total_revenue: float` (USD), `average_revenue_per_event: float` (USD), `last_updated: int | None`, `mcr_degraded: bool` (`true` when ≥1 scored event used a stale or unavailable FX source). `mcr_score` is always an `int`. The SDK coerces a `null` score from the API to `0`. `Provisioned` machines (bonded but not yet scored) surface as `0`, matching the JavaScript SDK. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.query import query_mcr mcr = query_mcr(client, "did:peaq:0xMachineAddress") print(mcr["mcr_score"], mcr["mcr"]) ``` **Errors:** `ValidationError` if `did` does not start with `did:peaq:0x`. `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`. ### `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}`](/peaqos/api-reference/get-machine). ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def query_machine(client: PeaqosClient, did: str) -> MachineProfileResponse: ... ``` Machine DID. Must start with `did:peaq:0x`. 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`. Visibility-dependent extras (`data_api: str`, `event_data: list[EventEntry]`, `partner_data: dict[str, Any]`, `partner_data_error: str`) are present only when the API includes them. Use `"data_api" in profile["peaqos"]` (etc.) to test for presence rather than truthiness. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.query import query_machine profile = query_machine(client, "did:peaq:0xMachineAddress") print(profile["schema_version"], profile["name"]) print(profile["peaqos"]["mcr"], profile["peaqos"]["mcr_score"]) ``` **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`](/peaqos/api-reference/get-operator-machines). ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} def query_operator_machines( client: PeaqosClient, did: str, ) -> OperatorMachinesResponse: ... ``` Operator DID. Must start with `did:peaq:0x`. Keys: `operator_did: str`, `machines: list[OperatorMachine]`, and `pagination: Pagination`. Each `OperatorMachine` has `did: str`, `machine_id: int`, `mcr_score: int` (0–100), `mcr: str`, and `negative_flag: bool`. `Pagination` carries `offset: int`, `limit: int`, and `total: int`. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.query import query_operator_machines fleet = query_operator_machines(client, "did:peaq:0xProxyAddress") for m in fleet["machines"]: print(m["machine_id"], m["mcr_score"], m["mcr"]) ``` **Errors:** `ValidationError` on invalid DID. `ApiError` with the same codes as `query_mcr`; `BAD_RESPONSE` if the body or any `machines` entry is malformed. *** ## Error classes ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import ( PeaqosError, ValidationError, RpcError, ApiError, ValueCapExceeded, RateLimitExceeded, ) ``` See [errors](/peaqos/sdk-reference/errors) for the full hierarchy and the 20-code faucet table. *** ## Constants ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import ( MACHINE_STATUS_NONE, # 0 MACHINE_STATUS_PENDING, # 1 MACHINE_STATUS_VERIFIED, # 2 MACHINE_STATUS_REJECTED, # 3 MACHINE_STATUS_DEACTIVATED, # 4 ) ``` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import ( MCR_RATING_AAA, MCR_RATING_AA, MCR_RATING_A, MCR_RATING_BBB, MCR_RATING_BB, MCR_RATING_B, MCR_RATING_NR, ) # Bonded-but-unscored machines are returned by the MCR API with the literal # rating string "Provisioned" and mcr_score coerced to 0 by the SDK. # No SDK constant yet for the "Provisioned" rating string. ``` 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. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import ( DATA_VISIBILITY_PUBLIC, # "public" DATA_VISIBILITY_PRIVATE, # "private" DATA_VISIBILITY_ONCHAIN, # "onchain" ) ``` Pass to `data_visibility=` on `write_machine_did_attributes`. Validated server-side; using one of these constants avoids typos. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import ( IMPORT_CHAIN_EVM, # "evm" (default) IMPORT_CHAIN_SOLANA, # "solana" IMPORT_CHAIN_BITCOIN, # "bitcoin" IMPORT_CHAIN_COSMOS, # "cosmos" IMPORT_CHAIN_TRON, # "tron" IMPORT_CHAIN_TON, # "ton" IMPORT_CHAIN_SUI, # "sui" IMPORT_CHAIN_XRPL, # "xrpl" IMPORT_CHAIN_SPARK, # "spark" IMPORT_CHAIN_FILECOIN, # "filecoin" OWS_PASSPHRASE_ENV, # "OWS_PASSPHRASE" ) ``` Used by `import_wallet(..., chain=IMPORT_CHAIN_SOLANA)`. The JS SDK exports identical constants. See [JS Constants](/peaqos/sdk-reference/sdk-js#constants). # Wallets (OWS) Source: https://docs.peaq.xyz/peaqos/wallets Encrypted, mnemonic-backed wallets for peaqOS via the Open Wallet Standard. Optional, opt-in, fully local. peaqOS supports the [Open Wallet Standard v1.3](https://docs.openwallet.sh/) for wallet generation, encrypted storage, signing, and display. The current raw-key flow uses hex private keys in `.env` files, protected only by file permissions. That works, but it has limits: * No encryption at rest * No mnemonic backup or recovery * No multi-chain visibility. One secp256k1 key derives addresses on every EVM chain, Cosmos, Solana, Bitcoin, Tron, and more, but nothing surfaces them * No standard import / export to wallets like MetaMask or hardware devices * No audit log of key creation, signing, or export events OWS solves all of these with an encrypted local vault, BIP-39 derivation, CAIP-2 chain identifiers, and an append-only audit log: fully local, no cloud, no remote services. OWS is **optional**. Use the SDK helpers below to manage wallets. The raw-key path keeps working unchanged. ## Multi-chain accounts from one mnemonic A mnemonic-derived wallet generates accounts across every OWS-supported chain family from a single 12- or 24-word phrase. | Family | Curve | Coin Type | Default Path | Address Format | | :---------------------------------------- | :-------- | :-------- | :-------------------------- | :--------------------------- | | EVM (peaq, Base, Ethereum, Polygon, etc.) | secp256k1 | 60 | `m/44'/60'/0'/0/{index}` | EIP-55 checksummed hex | | Solana | ed25519 | 501 | `m/44'/501'/{index}'/0'` | Base58 public key | | Bitcoin | secp256k1 | 0 | `m/84'/0'/0'/0/{index}` | Bech32 native segwit | | Cosmos | secp256k1 | 118 | `m/44'/118'/0'/0/{index}` | Bech32 | | Tron | secp256k1 | 195 | `m/44'/195'/0'/0/{index}` | Base58Check | | TON | ed25519 | 607 | `m/44'/607'/{index}'` | Base64url wallet v5r1 | | Sui | ed25519 | 784 | `m/44'/784'/{index}'/0'/0'` | 0x + BLAKE2b-256 hex | | XRPL | secp256k1 | 144 | `m/44'/144'/0'/0/{index}` | Base58Check | | Spark | secp256k1 | 8797555 | `m/84'/0'/0'/0/{index}` | `spark:` + compressed pubkey | | Filecoin | secp256k1 | 461 | `m/44'/461'/0'/0/{index}` | `f1` + base32 | The SDK guarantees an account entry for every protocol-recognized EVM chain (`SUPPORTED_CHAIN_IDS`). If OWS doesn't return one, it's synthesized from the wallet's primary EVM address, which is identical across all EVM chains: | Network | CAIP-2 | Chain ID | | :------- | :------------- | :------- | | peaq | `eip155:3338` | 3338 | | ethereum | `eip155:1` | 1 | | base | `eip155:8453` | 8453 | | polygon | `eip155:137` | 137 | | arbitrum | `eip155:42161` | 42161 | | optimism | `eip155:10` | 10 | Each account is exposed as a [CAIP-10](https://chainagnostic.org/CAIPs/caip-10) account ID (`eip155:3338:0x...`), so a single peaqOS wallet can sign on peaq, Base (for `bridgeNft`), and any other supported chain without ever exporting the key. ## Vault layout The vault lives at `~/.ows/` (override with `OWS_VAULT_PATH`). Structure is set by OWS; peaqOS uses it as-is. ``` ~/.ows/ ├── config.json # OWS settings (700) ├── wallets/ # (700) │ └── {uuid}.json # one per wallet (600) ├── keys/ # (700, future use) ├── policies/ # (755, future use) └── logs/ └── audit.jsonl # append-only operation log (600) ``` Each wallet file holds: * `id` (UUID v4) and `name` * `key_type`: `"mnemonic"` or `"private_key"` * `accounts[]` with one entry per supported chain (CAIP-10, address, derivation path) * `crypto` with `aes-256-gcm` ciphertext, `scrypt` KDF params, IV, salt, and auth tag * `created_at` (ISO 8601) ## SDK methods Wallet lifecycle is exposed through the SDKs. ### JavaScript / TypeScript Available as both module-level imports and static methods on `PeaqosClient`. Pick whichever matches your style. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { PeaqosClient, createWallet, importWallet, importWalletMnemonic, listWallets, getWallet, exportWallet, deleteWallet, } from "@peaqos/peaq-os-sdk"; // Create const wallet = await createWallet("my-machine", passphrase, 12); // or: await PeaqosClient.createWallet("my-machine", passphrase, 12); // Import await importWallet("my-machine", "0x...64hex", passphrase); // default chain: "evm". Pass a different OWS chain (e.g. "solana", "bitcoin", "cosmos") // when the private key is for that chain's curve and address format: // await importWallet("my-machine", "0x...64hex", passphrase, "solana"); await importWalletMnemonic("my-machine", "twelve words ...", passphrase); // Inspect const all = await listWallets(); const one = await getWallet("my-machine"); // Export / delete const phrase = await exportWallet("my-machine", passphrase); await deleteWallet("my-machine"); ``` Every function takes an optional final `WalletOptions` arg with `vaultPath` to point at a vault directory other than `~/.ows/`. Passphrase falls back to `OWS_PASSPHRASE` when omitted; if both are missing, the call throws `PeaqosError`. `importWallet` validates the hex shape eagerly and throws `ValidationError` for malformed keys; `importWalletMnemonic` accepts an optional `index` (default `0`) before the `WalletOptions` arg to derive a non-default account. `WalletInfo` carries `id`, `name`, `createdAt`, `keyType`, `peaqAddress` (convenience), and `accounts: readonly AccountInfo[]` with every chain. `AccountInfo` has `accountId` (CAIP-10), `address`, `chainId` (CAIP-2), `network` (human-readable name like `"peaq"`, `"base"`, `"solana"`), and `derivationPath`. Both shapes are deep-frozen. #### Building a client straight from a vault wallet `PeaqosClient.fromWallet` skips the manual private-key plumbing. It loads a wallet from the vault and returns a configured client whose `account` is the wallet's peaq address. ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { PeaqosClient } from "@peaqos/peaq-os-sdk"; // OWS-native signing (default): key is decrypted per-sign and wiped immediately. // The passphrase is verified on the FIRST signTransaction call, not at construction. const client = await PeaqosClient.fromWallet("my-machine", "s3cret", true, { rpcUrl: "https://peaq-rpc.example.com", contracts: { /* ... */ }, }); // Raw-key mode: decrypts the key once at construction (eager passphrase check) // and signs through viem locally. Use this when you need viem features OWS // doesn't expose (e.g. signMessage, signTypedData). const eager = await PeaqosClient.fromWallet("my-machine", "s3cret", false, { rpcUrl: "https://peaq-rpc.example.com", contracts: { /* ... */ }, }); ``` Signature: `fromWallet(nameOrId, passphrase, owsSigning, config, options?)`. `owsSigning` defaults to `true`. In OWS-native mode the SDK never holds the private key: `signMessage` and `signTypedData` throw because OWS only exposes `signTransaction`. Switch to raw-key mode if you need either. OWS signing surfaces typed errors via `OwsSigningErrorCode` (`WALLET_NOT_FOUND`, `INVALID_PASSPHRASE`, `INVALID_INPUT`, `POLICY_DENIED`, `CHAIN_NOT_SUPPORTED`). `INVALID_INPUT` becomes a `ValidationError`; the rest become `PeaqosError` with the original error preserved as `.cause`. ### Python Available under `peaq_os_sdk.wallet` and as `@staticmethod`s on `PeaqosClient`. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import PeaqosClient from peaq_os_sdk.wallet import ( create_wallet, import_wallet, import_wallet_mnemonic, list_wallets, get_wallet, export_wallet, delete_wallet, ) # Create info = create_wallet("my-machine", passphrase=passphrase, words=12) # or: PeaqosClient.create_wallet("my-machine", passphrase=passphrase, words=12) # Import import_wallet("my-machine", "0x...64hex", passphrase=passphrase) # default chain="evm". Pass a different OWS chain (e.g. "solana", "bitcoin", "cosmos") # when the private key is for that chain's curve and address format: # import_wallet("my-machine", "0x...64hex", passphrase=passphrase, chain="solana") import_wallet_mnemonic("my-machine", "twelve words ...", passphrase=passphrase) # Inspect all_wallets = list_wallets() one = get_wallet("my-machine") # Export / delete phrase = export_wallet("my-machine", passphrase=passphrase) delete_wallet("my-machine") ``` Each function accepts a final `vault_path` keyword for a custom vault directory. Passphrase falls back to the `OWS_PASSPHRASE` env var when `None`; missing both raises `PeaqosError`. On the Python side, `WalletInfo` carries `id`, `name`, `created_at`, `key_type`, `peaq_address` (convenience), and `accounts: list[AccountInfo]` with every chain. `AccountInfo` has `account_id` (CAIP-10), `address`, `chain_id` (CAIP-2), `network` (human-readable name), and `derivation_path`. #### Building a client straight from a vault wallet `PeaqosClient.from_wallet` mirrors the JS factory above. It loads the wallet, verifies the passphrase, and returns a fully configured client. ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk import PeaqosClient client = PeaqosClient.from_wallet( "my-machine", passphrase="s3cret", rpc_url="https://peaq-rpc.example.com", identity_registry="0x...", identity_staking="0x...", event_registry="0x...", machine_nft="0x...", did_registry="0x0000000000000000000000000000000000000800", batch_precompile="0x0000000000000000000000000000000000000805", ) # Eager-decrypt: identical to PeaqosClient(private_key=...). eager = PeaqosClient.from_wallet( "my-machine", passphrase="s3cret", ows_signing=False, rpc_url="https://peaq-rpc.example.com", # ... ) ``` Signature: `from_wallet(name_or_id, passphrase=None, ows_signing=True, vault_path=None, **config_kwargs)`. `passphrase` falls back to `OWS_PASSPHRASE`. In OWS-native mode the SDK never holds the private key — `OWSAccount` decrypts it inside the OWS Rust FFI for each `sign_transaction` call and wipes it immediately. `ows_signing=False` exports and decrypts the key at construction time, behaving identically to a raw-key client. Each transaction's `chainId` drives both the CAIP-2 OWS arg and the EIP-155 `v`, so the same client transparently signs both peaq (`eip155:3338`) and Base (`eip155:8453`) bridge transactions. OWS signing surfaces typed errors via 5 OWS error codes (`WALLET_NOT_FOUND`, `INVALID_PASSPHRASE`, `INVALID_INPUT`, `POLICY_DENIED`, `CHAIN_NOT_SUPPORTED`). `INVALID_INPUT` raises `ValidationError`; the rest raise `PeaqosError`. See [SDK errors: OWS signing error codes](/peaqos/sdk-reference/errors#ows-signing-error-codes). ### Pulling the peaq address out of a wallet `peaq_address` / `peaqAddress` is already populated by every wallet method, so most callers never need anything else. If you're working with a raw `accounts` list (e.g. building a wallet response by hand in tests, or reading a vault file directly), use the helper: ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import { extractPeaqAddress } from "@peaqos/peaq-os-sdk"; const address = extractPeaqAddress(wallet.accounts); ``` ```python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from peaq_os_sdk.wallet import extract_peaq_address address = extract_peaq_address(wallet.accounts) ``` It returns the `eip155:3338` account if present, otherwise the first `eip155:*` account (EVM addresses match across EVM chains), and raises `PeaqosError` when no EVM account exists. ## From the CLI Every SDK wallet helper is also exposed through `peaqos wallet`. Install the optional extra to get them: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install 'peaq-os-cli[ows]' ``` Then: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} peaqos wallet create my-wallet # mnemonic-backed, 12 words peaqos wallet import my-wallet --mnemonic # hidden prompt for the phrase peaqos wallet list peaqos wallet show my-wallet # full multi-chain address table peaqos wallet use my-wallet # writes PEAQOS_OWS_WALLET=my-wallet to .env peaqos wallet export my-wallet # reveals phrase / key after confirm peaqos wallet delete my-wallet # secure overwrite + unlink after confirm ``` `peaqos init` also offers `wallet` as a third `Private key source` choice and writes `PEAQOS_OWS_WALLET` instead of `PEAQOS_PRIVATE_KEY`. Once a wallet is active, `peaqos activate`, `peaqos qualify event`, and the rest of the command surface sign through it. See [peaqOS CLI: wallet](/peaqos/cli#peaqos-wallet) for the full command reference. ## Security model * **Vault encryption.** AES-256-GCM with scrypt KDF (`n=65536, r=8, p=1`). * **Passphrase handling.** Sourced from the explicit argument or the `OWS_PASSPHRASE` env var; if neither is set, the SDK raises `PeaqosError` rather than prompting. Never stored on disk. In CI, pass via secret manager or env injection. * **Mnemonic exposure.** Never returned by `createWallet` / `create_wallet`. The `WalletInfo` response only carries addresses and metadata. To recover the seed phrase you must call `exportWallet` / `export_wallet` with the vault passphrase. * **Single-mnemonic blast radius.** One phrase controls accounts on every supported chain. Treat exported phrases accordingly: anyone with the phrase has access to every chain account. * **Audit log.** Every wallet operation (create, import, export, delete) appends to `~/.ows/logs/audit.jsonl`. ## See also The full peaqOS CLI command reference. The `[ows]` extra and the raw-key flow side-by-side. Full upstream Open Wallet Standard v1.3 spec. # Quickstart Source: https://docs.peaq.xyz/quickstart Register your first machine in five minutes. Install the SDK, generate a keypair, register a machine. ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install peaq-os-cli npx @peaqos/skills add peaqos ``` Auto-detects Claude Code, Cursor, or Windsurf. Invoke `/peaqos` in Claude Code and it drives the whole flow below. To target a specific runtime, add `--agent claude-code | cursor | windsurf` — see the [peaqOS AI page](/peaqos/peaqos-ai). ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} pip install peaq-os-cli peaqos init && peaqos activate --doc-url "https://example.com/docs" --data-api "https://example.com/events" ``` Wraps the SDK into terminal commands. Full reference on the [peaqOS CLI page](/peaqos/cli). ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} npm install @peaqos/peaq-os-sdk viem dotenv ``` ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} python3 -m venv .peaq-os source .peaq-os/bin/activate pip install peaq-os-sdk python-dotenv ``` **Coming soon.** ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} git clone https://github.com/peaqnetwork/peaq-robotics-ros2.git cd peaq-robotics-ros2 source /opt/ros/jazzy/setup.bash colcon build --packages-select \ peaq_ros2_interfaces peaq_ros2_peaqos peaq_ros2_examples source install/setup.bash ``` Wraps the SDKs as ROS 2 services. Full reference on [SDK: ROS 2](/peaqos/sdk-reference/ros2/overview). Full install reference on the [install page](/peaqos/install). Create a `.env` file: ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}} # peaq mainnet RPC. See /peaqos/install#public-rpc-endpoints for alternatives. PEAQOS_RPC_URL=https://peaq.api.onfinality.io/public PEAQOS_PRIVATE_KEY=0x... # Mainnet contracts auto-populated by fromEnv(); shown here for reference. Override only for custom deploys. # For agung testnet addresses see /peaqos/install#agung-testnet-contracts. IDENTITY_REGISTRY_ADDRESS=0xb53Af985765031936311273599389b5B68aC9956 IDENTITY_STAKING_ADDRESS=0x11c05A650704136786253e8685f56879A202b1C7 EVENT_REGISTRY_ADDRESS=0x43c6AF2E14dc1327dc3cc6c7117D1CD72fffEcbA MACHINE_NFT_ADDRESS=0x2943F80e9DdB11B9Dd275499C661Df78F5F691F9 DID_REGISTRY_ADDRESS=0x0000000000000000000000000000000000000800 BATCH_PRECOMPILE_ADDRESS=0x0000000000000000000000000000000000000805 # Optional. Defaults to http://127.0.0.1:8000 PEAQOS_MCR_API_URL=https://mcr.peaq.xyz ``` Mainnet contract addresses (identity, staking, event, NFT, DID, batch) populate automatically with `PeaqosClient.fromEnv()`, so you don't need to set them manually. See the [environment variables table](/peaqos/install#environment-variables) for the full list and the [public RPC endpoints](/peaqos/install#public-rpc-endpoints) section for QuickNode primary plus OnFinality/PublicNode fallbacks. ```typescript JavaScript theme={"theme":{"light":"github-light-default","dark":"github-dark"}} import "dotenv/config"; import { PeaqosClient } from "@peaqos/peaq-os-sdk"; const client = PeaqosClient.fromEnv(); // Register the client's own wallet as a new machine. // The call sends the minimum bond (1 PEAQ) with the transaction. const machineId = await client.registerMachine(); console.log("Registered machine ID:", machineId); ``` ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark"}} from dotenv import load_dotenv from peaq_os_sdk import PeaqosClient load_dotenv() # load envs from .env file client = PeaqosClient.from_env() # Register the client's own wallet as a new machine. # The call sends the minimum bond (1 PEAQ) with the transaction. machine_id = client.register_machine() print("Registered machine ID:", machine_id) ``` Proxy operators register machines on behalf of another wallet. See the [self-managed](/peaqos/guides/self-managed-onboarding) and [proxy operator](/peaqos/guides/proxy-operator-fleet) guides for the fleet flow with `registerFor`. * `PeaqosClient.fromEnv()` bound the signing key from `PEAQOS_PRIVATE_KEY` and loaded contract addresses from the environment. * `registerMachine` sent 1 PEAQ as `msg.value`, registered the identity in IdentityRegistry, and returned the newly assigned machine ID. * The machine ID is the handle for every downstream call: minting the Machine NFT (`mintNft`), writing DID attributes, submitting events, and MCR queries. ## Next Owner = operator. One machine, one wallet. One owner, many machines, `registerFor`. # Roadmap Source: https://docs.peaq.xyz/roadmap What's live, what's next, and where peaqOS is headed. peaqOS ships in stages. Each function adds a new capability to the Machine Economy. **Live.** Put your machine on-chain: peaqID, Machine NFT, and bond. Machines get a self-sovereign identity that travels across chains. **Live.** Machine Credit Rating: a score built from revenue and activity history. Third parties can query any machine's creditworthiness via API. **Coming Soon.** Pair an AI agent that transacts and consumes services on your machine's behalf. Fleet management and omnichain expansion. **Coming Soon.** Prove your machine is real via hardware attestation and trusted third parties. **Coming Soon.** List your machine's services. Other machines and agents discover and buy them. **Coming Soon.** Fractionalize your machine into an investable asset via ERC-3643. This roadmap shows the planned sequence. Functions ship when they're ready, so timelines may shift. ## Beyond the six functions Mnemonic-backed encrypted vault and multi-chain account derivation. SDK wallet lifecycle (`createWallet`, `importWallet`, etc.) and OWS-native signing through `PeaqosClient.fromWallet` are live in both SDKs. CLI wallet commands are next. Optional and additive: the raw-key flow keeps working. See [Wallets (OWS)](/peaqos/wallets).