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

# Submit events

> Submit revenue and activity events to the EventRegistry. Covers validation, data hashing, event types, cross-chain patterns, and operational limits.

export const KeyTerms = ({all = {}, ids = [], title = "Key terms in this guide"}) => <details className="not-prose my-4 rounded-xl border border-zinc-200 px-4 py-3 dark:border-zinc-800">
    <summary className="cursor-pointer font-medium text-zinc-900 dark:text-zinc-100">
      {title}
    </summary>
    <div className="mt-3 space-y-2 text-sm text-zinc-700 dark:text-zinc-300">
      {ids.map(id => {
  const t = all[id] || ({});
  return <div key={id}>
            <strong>{t.term}.</strong> {t.def}
          </div>;
})}
    </div>
  </details>;

export const G = {
  onchain: {
    id: "onchain",
    cat: "chain-infra",
    term: "On-chain vs off-chain",
    def: "On-chain means written to the shared public ledger every machine agrees on: permanent and readable by anyone. Off-chain means kept on a normal private server instead."
  },
  blockchain: {
    id: "blockchain",
    cat: "chain-infra",
    term: "Chain / blockchain",
    def: "A shared, tamper-resistant public database maintained by a whole network of computers with no single owner. Different chains are separate such networks."
  },
  peaqChain: {
    id: "peaqChain",
    cat: "chain-infra",
    term: "peaq chain",
    def: "The machine-focused blockchain peaqOS uses as home base for identity and credit records."
  },
  transaction: {
    id: "transaction",
    cat: "chain-infra",
    term: "Transaction (tx) / tx hash",
    def: "A single signed request that changes the ledger. Its hash is a unique, receipt-like ID you can use to look it up later."
  },
  rpcUrl: {
    id: "rpcUrl",
    cat: "chain-infra",
    term: "RPC URL / endpoint",
    def: "The network address your code calls to read from or write to a chain, like the base URL of the chain's API server."
  },
  mainnet: {
    id: "mainnet",
    cat: "chain-infra",
    term: "Mainnet / testnet (agung)",
    def: "Mainnet is the real, live network where tokens have real value. Testnet is a free practice copy with worthless tokens; peaq's is called agung."
  },
  evm: {
    id: "evm",
    cat: "chain-infra",
    term: "EVM / EVM-compatible",
    def: "The Ethereum Virtual Machine: the standard runtime many chains share, so the same 0x... addresses and tools work across all of them. peaq is EVM-compatible."
  },
  node: {
    id: "node",
    cat: "chain-infra",
    term: "Node (RPC node)",
    def: "A server running the blockchain software that holds a copy of the ledger and answers queries. NOT a ROS 2 node, despite the shared word."
  },
  chainId: {
    id: "chainId",
    cat: "chain-infra",
    term: "Chain ID",
    def: "A number that uniquely labels a chain so software doesn't confuse networks (peaq is 3338, Base is 8453)."
  },
  precompile: {
    id: "precompile",
    cat: "chain-infra",
    term: "Precompile",
    def: "A built-in function baked into the chain at a fixed address that acts like a contract but runs as faster native code. The batch one bundles several actions into one all-or-nothing transaction."
  },
  dataHash: {
    id: "dataHash",
    cat: "chain-infra",
    term: "Data hash (keccak256)",
    def: "A short, fixed-length fingerprint of a file, stored on-chain instead of the file itself, so data can be verified later while the raw data stays off-chain."
  },
  wallet: {
    id: "wallet",
    cat: "wallet-keys",
    term: "Wallet",
    def: "An account on the chain, identified by a public address, that holds a machine's funds and approves its actions. Really just a pair of keys, not a place money is stored."
  },
  keypair: {
    id: "keypair",
    cat: "wallet-keys",
    term: "Keypair",
    def: "The two matched secrets behind a wallet: a public address you can share, and a private key you keep secret that signs actions."
  },
  privateKey: {
    id: "privateKey",
    cat: "wallet-keys",
    term: "Private key",
    def: "The secret string that proves you control a wallet. Anyone who has it has full control, like a master password that can never be reset."
  },
  sign: {
    id: "sign",
    cat: "wallet-keys",
    term: "Sign / signature",
    def: "Using your private key to produce a cryptographic stamp proving you approved a specific action, without ever revealing the key."
  },
  signer: {
    id: "signer",
    cat: "wallet-keys",
    term: "Signer / signing identity",
    def: "The wallet whose private key authorizes an action: the account the network treats as the one taking it. NOT a file or an app."
  },
  address: {
    id: "address",
    cat: "wallet-keys",
    term: "Address (0x...)",
    def: "The public 0x... identifier of a wallet or contract you can freely share so others can send to it or look it up, like an account number."
  },
  eoa: {
    id: "eoa",
    cat: "wallet-keys",
    term: "EOA (externally owned account)",
    def: "A plain wallet controlled directly by a private key, as opposed to one controlled by code. Here, the account that IS the machine."
  },
  ows: {
    id: "ows",
    cat: "wallet-keys",
    term: "OWS / wallet vault",
    def: "An open standard for storing wallet keys in an encrypted local file (a vault) with a backup phrase and an activity log, instead of a bare key in a text file."
  },
  passphrase: {
    id: "passphrase",
    cat: "wallet-keys",
    term: "Passphrase (OWS_PASSPHRASE)",
    def: "The password that unlocks the encrypted wallet vault so its key can be used to sign."
  },
  mnemonic: {
    id: "mnemonic",
    cat: "wallet-keys",
    term: "Mnemonic / seed phrase",
    def: "A list of 12 or 24 ordinary words that encodes a wallet's secret key, used to back it up and recover it. Whoever has the words controls the wallet."
  },
  derivation: {
    id: "derivation",
    cat: "wallet-keys",
    term: "Derivation path",
    def: "The deterministic recipe that turns one backup phrase into many specific keys and addresses, one per network or index."
  },
  challenge: {
    id: "challenge",
    cat: "wallet-keys",
    term: "Challenge (sign-to-prove)",
    def: "A login-style handshake: the server sends a random message, you sign it with your key, and the signature proves you control the account without sending the key."
  },
  eip191: {
    id: "eip191",
    cat: "wallet-keys",
    term: "EIP-191 / personal_sign",
    def: "A standard way to sign a plain message to prove you control an account, without sending any on-chain transaction."
  },
  did: {
    id: "did",
    cat: "identity",
    term: "DID / peaqID",
    def: "A globally unique, self-owned ID for a machine that lives on the chain and isn't issued by any single company. peaqID is peaq's version, written did:peaq:0x..."
  },
  register: {
    id: "register",
    cat: "identity",
    term: "Register a machine",
    def: "Putting a machine on the network for the first time, which gives it an ID, a DID, an ownership token, and a locked deposit. registerMachine is self-managed; registerFor is on someone else's behalf."
  },
  machineId: {
    id: "machineId",
    cat: "identity",
    term: "Machine ID",
    def: "The number the network assigns your machine when it registers, used as its handle in every later call."
  },
  ownerOperator: {
    id: "ownerOperator",
    cat: "identity",
    term: "Owner / operator",
    def: "The owner owns a machine; the operator runs it. They can be the same account (self-managed) or different (proxy-managed)."
  },
  proxyOperator: {
    id: "proxyOperator",
    cat: "identity",
    term: "Proxy operator",
    def: "One account that registers and manages many machines on behalf of their owners, so a fleet operator can handle a whole fleet from one wallet."
  },
  didAttributes: {
    id: "didAttributes",
    cat: "identity",
    term: "DID attributes",
    def: "Public name-value facts (a docs link, a data endpoint) attached to a machine's DID and stored on-chain for anyone to read. Writing them is a separate transaction from registration."
  },
  pairing: {
    id: "pairing",
    cat: "identity",
    term: "Pairing / pairing token",
    def: "The verified link between an AI agent and a machine, set up by signing a challenge. The pairing token is the signed credential the agent sends with each request, like a temporary access badge."
  },
  hardwareAttestation: {
    id: "hardwareAttestation",
    cat: "identity",
    term: "Hardware attestation",
    def: "A tamper-resistant chip on the machine cryptographically vouching that it's genuine hardware, so its identity can't be faked in software. This is the Verify layer."
  },
  gas: {
    id: "gas",
    cat: "tokens-economics",
    term: "Gas",
    def: "The small fee, paid in the chain's token, that every action writing to the ledger costs, like a per-write transaction cost."
  },
  peaqToken: {
    id: "peaqToken",
    cat: "tokens-economics",
    term: "PEAQ (token)",
    def: "The peaq network's own token, used to pay gas fees and to lock up as the deposit when registering a machine."
  },
  gasStation: {
    id: "gasStation",
    cat: "tokens-economics",
    term: "Gas Station / faucet",
    def: "A peaq service that hands a brand-new, empty wallet a tiny starting amount of tokens so it can afford its first network fees. Gated by 2FA."
  },
  bond: {
    id: "bond",
    cat: "tokens-economics",
    term: "Bond",
    def: "A refundable deposit (currently 1 PEAQ) you lock up to register a machine, proving skin in the game, like a security deposit. Bonded means the deposit is in place."
  },
  nft: {
    id: "nft",
    cat: "tokens-economics",
    term: "NFT",
    def: "A unique, one-of-a-kind ownership token recorded on the chain. Unlike a coin, no two are interchangeable."
  },
  mint: {
    id: "mint",
    cat: "tokens-economics",
    term: "Mint / minting",
    def: "Creating a brand-new token on the chain and assigning it to an owner, like stamping a fresh serial-numbered certificate into existence."
  },
  machineNft: {
    id: "machineNft",
    cat: "tokens-economics",
    term: "Machine NFT",
    def: "The unique token representing one specific physical machine and its financial profile. It can be sold or bridged on its own, separate from the machine's identity."
  },
  identityNft: {
    id: "identityNft",
    cat: "tokens-economics",
    term: "Identity NFT",
    def: "A non-transferable (soulbound) token minted automatically when a machine registers, representing its identity. Its token ID equals the machine ID."
  },
  tokenId: {
    id: "tokenId",
    cat: "tokens-economics",
    term: "Token ID",
    def: "The unique number identifying one specific token within a collection, like a serial number."
  },
  mcr: {
    id: "mcr",
    cat: "tokens-economics",
    term: "Machine Credit Rating (MCR)",
    def: "A creditworthiness score for a machine (a Moody's-style grade AAA down to NR, plus a 0-100 number) computed from its recorded earnings and activity. Like a credit score for a robot."
  },
  mcrApi: {
    id: "mcrApi",
    cat: "tokens-economics",
    term: "MCR API",
    def: "The public web service you call to fetch a machine's credit score and profile as JSON, and the one place a machine's monetization is switched on or off with a signed message. No login needed: reads are open, and the write is authorized by the signature itself."
  },
  provisioned: {
    id: "provisioned",
    cat: "tokens-economics",
    term: "Provisioned / NR (Not Rated)",
    def: "Early MCR statuses. Provisioned means registered and bonded but with too little history to score yet. NR means no grade, because the score is too low or the machine isn't bonded."
  },
  event: {
    id: "event",
    cat: "tokens-economics",
    term: "Event (revenue / activity)",
    def: "A recorded data point about a machine's work, submitted to the chain to feed its credit score. Revenue events report money earned; activity events report work with no money. NOT a ROS topic message."
  },
  trustLevel: {
    id: "trustLevel",
    cat: "tokens-economics",
    term: "Trust level",
    def: "A label on each submitted event saying how strongly its truth is backed: the machine's word (0), a checkable on-chain record (1), or tamper-proof hardware proof (2)."
  },
  escrow: {
    id: "escrow",
    cat: "tokens-economics",
    term: "Escrow",
    def: "Holding a buyer's payment in a neutral locked place until the service is delivered, then releasing it, so neither side has to trust the other first."
  },
  paymentRail: {
    id: "paymentRail",
    cat: "tokens-economics",
    term: "Payment rail",
    def: "The specific method or channel a payment moves through, like choosing card vs bank transfer vs a particular token."
  },
  x402: {
    id: "x402",
    cat: "tokens-economics",
    term: "x402",
    def: "A web payment standard where a server answers 'payment required' with exact instructions, and the buyer's wallet signs an authorization instead of sending a separate transaction — built for machines and agents paying per request."
  },
  usdt: {
    id: "usdt",
    cat: "tokens-economics",
    term: "USDT",
    def: "A stablecoin token meant to hold a value of one US dollar, used to pay service providers without price swings."
  },
  fractionalize: {
    id: "fractionalize",
    cat: "tokens-economics",
    term: "Fractionalize (ERC-3643)",
    def: "Splitting ownership of one machine into many small tradable shares so multiple people can each own a piece. ERC-3643 is the regulated-securities token standard used to do it."
  },
  smartContract: {
    id: "smartContract",
    cat: "smart-contracts",
    term: "Smart contract / contract address",
    def: "A program deployed on the chain that runs exactly as written and that anyone can call, identified by its own 0x... address."
  },
  registryContracts: {
    id: "registryContracts",
    cat: "smart-contracts",
    term: "Registry contracts",
    def: "On-chain programs that each keep an official, lookup-able list: IdentityRegistry tracks which machines exist, EventRegistry stores their events, IdentityStaking holds their deposits."
  },
  smartAccount: {
    id: "smartAccount",
    cat: "smart-contracts",
    term: "Smart account (ERC-4337)",
    def: "A programmable wallet controlled by code instead of a single key, so it can enforce rules like spending limits. Each machine gets one at activation."
  },
  submitEvent: {
    id: "submitEvent",
    cat: "smart-contracts",
    term: "submitEvent / batchSubmitEvents",
    def: "The calls that record one or many of a machine's revenue or activity entries onto the chain."
  },
  revert: {
    id: "revert",
    cat: "smart-contracts",
    term: "Revert",
    def: "When an on-chain call is rejected and fully undone because a rule was broken, leaving no changes and usually a named error."
  },
  soulbound: {
    id: "soulbound",
    cat: "smart-contracts",
    term: "Soulbound",
    def: "A token that can never be transferred or sold and stays permanently attached to one owner. The Identity NFT is soulbound."
  },
  bridge: {
    id: "bridge",
    cat: "cross-chain",
    term: "Bridge / bridging",
    def: "Moving a token from one chain to another, so the same Machine NFT can exist on a different chain. peaq and Base are live today; bridging is mainnet-only."
  },
  base: {
    id: "base",
    cat: "cross-chain",
    term: "Base",
    def: "Another blockchain network (built by Coinbase) that peaqOS can move Machine NFTs to and from. Paying fees on Base needs Base ETH."
  },
  omniChain: {
    id: "omniChain",
    cat: "cross-chain",
    term: "Omni-chain / cross-chain",
    def: "Working across many separate chains at once, so a machine's identity and credit created on peaq can be read or used on other chains."
  },
  homeChain: {
    id: "homeChain",
    cat: "cross-chain",
    term: "Home chain",
    def: "The chain where a record's canonical, authoritative copy lives. For peaqOS that is peaq chain; every other chain holds a mirror."
  },
  satelliteChain: {
    id: "satelliteChain",
    cat: "cross-chain",
    term: "Satellite chain",
    def: "A chain carrying a read-only, automatically synced mirror of home-chain records, so apps there can use them without crossing back to the home chain."
  },
  sourceChainId: {
    id: "sourceChainId",
    cat: "cross-chain",
    term: "sourceChainId / sourceTxHash",
    def: "Two fields recording which chain an action happened on and its hash there, so a cross-chain event can be traced back and verified."
  },
  machineAgent: {
    id: "machineAgent",
    cat: "general-web3",
    term: "Machine Agent",
    def: "A third-party AI program (Claude, OpenAI, a custom bot) paired to a machine and given limited permission to find, buy, and pay for services on its behalf."
  },
  delegationPolicy: {
    id: "delegationPolicy",
    cat: "general-web3",
    term: "Delegation policy",
    def: "The rules an owner gives an AI agent that cap how much it can spend per transaction and per day and which services it may use, so it transacts within guardrails."
  },
  machineMarkets: {
    id: "machineMarkets",
    cat: "general-web3",
    term: "Machine Markets / Service Registry",
    def: "peaqOS's marketplace where machines list services they offer (Service Registry) and where agents discover, order, pay for, and run services from others."
  },
  sdk: {
    id: "sdk",
    cat: "general-web3",
    term: "SDK (peaq-os-sdk)",
    def: "peaq's code library (Python and JS) you install to call all this functionality without writing low-level blockchain calls yourself."
  },
  stream: {
    id: "stream",
    cat: "data-stream",
    term: "Stream (Data-as-a-Service)",
    def: "The peaqOS function where a machine sells the data it generates: it signs the data, encrypts what's sensitive, and grants buyers access. Selling data, as opposed to selling capacity (that's Monetize)."
  },
  edgeAgent: {
    id: "edgeAgent",
    cat: "data-stream",
    term: "peaqOS Edge Agent",
    def: "Software that runs on the machine itself (as a ROS 2 node) and signs, encrypts, and ships the data it produces. The on-machine half of Stream."
  },
  dataPackage: {
    id: "dataPackage",
    cat: "data-stream",
    term: "Signed data package",
    def: "A bundle of machine data stamped with the machine's identity (DID, timestamp, sequence number) and a signature, so anyone can prove which machine produced it and that it wasn't altered."
  },
  dataEventMap: {
    id: "dataEventMap",
    cat: "data-stream",
    term: "Data Event Map",
    def: "The policy file a machine owner writes to control what streams out: which topics to read, which fields to keep, drop, or encrypt, and where the signed data goes."
  },
  chunk: {
    id: "chunk",
    cat: "data-stream",
    term: "Chunk",
    def: "A bounded, individually encrypted slice of a data stream (by time window or size). The unit a buyer actually purchases and decrypts."
  },
  chunkChain: {
    id: "chunkChain",
    cat: "data-stream",
    term: "Chunk chain",
    def: "A run of chunks linked in order, each referencing the one before it, so missing, reordered, or edited chunks are detectable. Tamper-evidence for a continuous stream."
  },
  manifest: {
    id: "manifest",
    cat: "data-stream",
    term: "Manifest",
    def: "A signed record describing a chunk or dataset — its hashes, storage location, and encryption details — without the data itself. Buyers verify the manifest before trusting or buying."
  },
  dataset: {
    id: "dataset",
    cat: "data-stream",
    term: "Dataset",
    def: "A group of chunks for one topic and time range, packaged for sale with a single fingerprint (a Merkle root) that covers every chunk in it."
  },
  merkleRoot: {
    id: "merkleRoot",
    cat: "data-stream",
    term: "Merkle root",
    def: "One short hash that stands in for a whole set of items, letting you later prove a specific chunk belongs to a dataset without revealing the rest."
  },
  envelopeEncryption: {
    id: "envelopeEncryption",
    cat: "data-stream",
    term: "Envelope encryption / key wrapping",
    def: "Encrypt the data once with a random key, then lock that key separately for each authorized reader. Granting a buyer access re-locks the key to their public key — the data itself is never re-encrypted."
  },
  accessGrant: {
    id: "accessGrant",
    cat: "data-stream",
    term: "Access grant",
    def: "What a buyer receives after paying: the chunk keys they bought, each locked to their public key. They unlock with their private key and decrypt only those chunks."
  },
  contextProvider: {
    id: "contextProvider",
    cat: "data-stream",
    term: "Context Provider",
    def: "A third party that buys machine data, normalizes it into datasets, and serves or resells it (for example, for AI training). The buyer side of Stream, such as DataHive."
  },
  computeProvider: {
    id: "computeProvider",
    cat: "monetize",
    term: "Compute provider",
    def: "A machine that rents out its processing power to a compute network. Instead of sitting idle, the machine runs other people's workloads and earns for it."
  },
  provisioningManifest: {
    id: "provisioningManifest",
    cat: "monetize",
    term: "Provisioning manifest",
    def: "A published, step-by-step install plan that turns a machine into a compute provider: the checks to run first, the commands to execute, and the probes that prove it worked. The machine pulls it on demand and runs it locally."
  },
  heartbeat: {
    id: "heartbeat",
    cat: "monetize",
    term: "Heartbeat",
    def: "A short signed 'I'm here' message a machine sends out at a regular interval. As long as heartbeats keep arriving, the machine counts as online; if they stop, it's marked offline automatically."
  },
  aggregator: {
    id: "aggregator",
    cat: "monetize",
    term: "Aggregator",
    def: "A network that pools capacity from many machines and sells it on, such as a decentralized compute marketplace. Machines connect to an aggregator, which then dispatches work to them."
  },
  machineWallet: {
    id: "machineWallet",
    cat: "monetize",
    term: "Machine wallet",
    def: "The wallet that belongs to the machine itself, separate from its owner's or operator's wallet. Earnings from the machine's work land here by default."
  },
  walrus: {
    id: "walrus",
    cat: "chain-infra",
    term: "Walrus",
    def: "A decentralized storage network where encrypted data chunks can be parked, referenced by walrus:// links. The data stays off the blockchain; only its reference and fingerprint are tracked on-chain."
  },
  solana: {
    id: "solana",
    cat: "cross-chain",
    term: "Solana",
    def: "A high-throughput blockchain. peaqOS wallets can hold a Solana account and sign Solana transactions, and machine-economy payments can settle there."
  }
};

Revenue events (type 0) and activity events (type 1) feed the [Machine Credit Rating](/peaqos/concepts/machine-credit-rating). Every <Tooltip tip={G.event.def}>event</Tooltip> is validated client-side, hashed, and submitted <Tooltip tip={G.onchain.def}>on-chain</Tooltip> to the EventRegistry contract.

<KeyTerms all={G} ids={["event", "onchain", "trustLevel", "dataHash", "mcr", "transaction", "submitEvent", "base"]} />

## 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 <Tooltip tip={G.trustLevel.def}>trust level</Tooltip> 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 <Tooltip tip={G.mcr.def}>MCR</Tooltip> 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.

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

### 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` <Tooltip tip={G.revert.def}>revert</Tooltip>. The SDK validators don't enforce this client-side, so oversized payloads surface as a <Tooltip tip={G.transaction.def}>transaction</Tooltip> failure (`RuntimeError`/`RpcError` with `code: "MetadataTooLarge"`) rather than `ValidationError`.

## Computing the data hash

The EventRegistry stores a <Tooltip tip={G.dataHash.def}>keccak256 hash</Tooltip> of the raw data, not the data itself. Compute it with `computeDataHash` (JS) or `compute_data_hash` (Python).

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

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)

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

## Submitting an activity event (type 1)

Activity events record operational telemetry with no direct revenue value.

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

## Cross-chain revenue pattern

When a machine earns revenue on another <Tooltip tip={G.blockchain.def}>chain</Tooltip> (e.g., <Tooltip tip={G.base.def}>Base</Tooltip>), reference the source transaction for on-chain verifiable trust (level 1).

<CodeGroup>
  ```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.
  ```
</CodeGroup>

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

<CodeGroup>
  ```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
  ```
</CodeGroup>

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

<CodeGroup>
  ```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
  ```
</CodeGroup>

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
