> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peaq.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

<CodeGroup>
  ```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();
  ```
</CodeGroup>

📦 ***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)

<CodeGroup>
  ```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();
  ```
</CodeGroup>

### 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.

<CodeGroup>
  ```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();
  ```
</CodeGroup>

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**.

<CodeGroup>
  ```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();
  ```
</CodeGroup>

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.

<CodeGroup>
  ```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();
  ```
</CodeGroup>

## 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.
