Monitoring cross-chain transactions

The Private Network operator monitors a Rayls Private Network from the Private Network Hub. Everything that moves between Rayls Sovereign chains (Privacy Nodes in the code) passes through the Hub, encrypted. The operator's governance services read it, decrypt it and keep a searchable record. Activity that stays inside one Rayls Sovereign chain never reaches the Hub, so the operator can't see it.

The monitoring services

The operator runs three services from rayls-sovereign-pnh-governance, which share a PostgreSQL database, and a web interface on top:

ServicePortWhat it does
Listener8081Reads the Hub block by block, decrypts the events it needs, and writes transactions, tokens, participants, freezes and block headers to the database. It passes events through NATS JetStream between reading and processing.
Flagger8082Keeps each chain's net position in each token, and flags transfers that would take it below zero and chains that stop submitting block headers. See Flagging transactions.
API8080A read-only REST API over the database. Its Swagger UI is at /swagger/index.html.
Private Network Auditor Explorer80 in its containerA web interface for searching decrypted transactions through the API

In the local stack started with ./rayls init --full, the API is at http://localhost:9100 and the Auditor Explorer at http://localhost:8181.

The services find the Hub contracts through the Hub's DeploymentProxyRegistryV1 (PNH_DEPLOYMENT_PROXY_REGISTRY). They decrypt with the operator's view secret key (PNH_RAYLS_VIEW_SECRET_KEY).

What the listener decrypts

Every participant's view private key is encrypted to the operator when the participant registers (see Adding new participants). The listener uses the operator's key to recover each participant's view key, then the shared secrets between participants. With those it decrypts:

  • the batches of messages each chain sends through the Hub: arbitrary messages, including their payloads;
  • Enygma transfers: sender, receiver, amount;
  • the terms of delivery-versus-payment (DvP) swaps.

It reloads the keys whenever a participant registers or updates its keys on the Hub. For what this means for Enygma users, see Who sees what.

What gets recorded

Each cross-chain transaction is recorded with a protocol and a message type:

ProtocolWhat it is
CUSTOMAn arbitrary message from a contract on one chain to a contract on another
ENYGMAAn Enygma transfer between chains
DVP_DEPOSIT, DVP_WITHDRAW, DVP_SWAPMoving an asset into or out of DvP, and a DvP swap

The message type is, for example, custom, enygma, dvp_erc721 or dvp_erc1155. Enygma mints and burns, which are public on the Hub, are recorded with the message type enygma.

The listener also records:

  • token registrations, status changes, and mints and burns that issuers report to the Hub;
  • token freezes and unfreezes;
  • participant registrations and changes of status or role;
  • the block headers each chain's private relayer submits to the Hub (Proofs contract).

Querying the governance API

Audit endpoints

Method and pathReturns
GET /audit/transactionsA page of transactions, newest first. Filters: messageId, sourceChainId, destinationChainId, fromAddress, toAddress, resourceId, messageType, initiatedAfter, initiatedBefore; paging: page, limit.
GET /audit/transactions/{messageId}One transaction, by message ID
GET /audit/transactions/batch/{batchId}The transactions in a batch of messages, paged
GET /audit/transactions/enygma/batch/{batchId}The decrypted transfers in an Enygma transaction, paged
GET /audit/transactions/dvp/swap/{sharedId}Both sides of a DvP swap
GET /audit/transactions/dvp/{transactionId}One transaction by its ID in the governance database. Despite the path, this works for any transaction.
GET /audit/participantsAll participants. Filters: name, chainId, status, role, createdAfter, createdBefore.
GET /audit/participants/{chainId}One participant, with its status and any flag
GET /audit/tokensA page of tokens, with supply per participant and the chains each is frozen for. Filters: name, symbol, issuerId, status, ercStandard, decimals, createdAfter, createdBefore.
GET /audit/tokens/{resourceId}One token
GET /audit/header-proofs?chainId=…&startBlock=…&endBlock=…The block headers a chain submitted to the Hub in a block range. Paging: page, pageSize (up to 1,000).
GET /flaggedThe transactions the flagger has flagged

For example, every Enygma transfer that chain 200002 sent after 1 October:

curl "http://<governance-api>/audit/transactions?sourceChainId=200002&messageType=enygma&initiatedAfter=2026-10-01"
{
  "data": [
    {
      "type": "ENYGMA",
      "id": "0x5b1e…",
      "idType": "batch_id",
      "createdAt": "2026-10-03 14:22:05 +0000 UTC",
      "sourceChainId": "200002",
      "sourceAddress": "0x…"
    }
  ],
  "total": 1,
  "limit": 10,
  "page": 1
}

The list gives each entry's id and idType. Use them to fetch the details: message_id with /audit/transactions/{messageId}, batch_id with the batch endpoints, shared_id with /audit/transactions/dvp/swap/{sharedId}, and transaction_id with /audit/transactions/dvp/{transactionId}.

Balance and status endpoints

These need a login. POST /private-network/login with {"username": "…", "password": "…"} sets an Authorization cookie valid for 30 days; POST /private-network/signup creates the account.

Method and pathReturns
GET /resources/{chainId}/The chain's net position in every token, as the flagger computes it
GET /resources/{chainId}/{resourceId}The chain's net position in one token
GET /resource_info_all_chains/{resourceId}Every chain's net position in one token
POST /resource_info_list_chainsThe same for chosen chains, with {"resource_id": "…", "chains": ["…"]}
GET /participant_info/{chainId}One participant
GET /token_status/{resourceId}One token's registry record

The net positions come from the Hub traffic only: the issuer's mints and burns, and the decrypted transfers between chains. They are not read from the Rayls Sovereign chains.

🚧

Restrict access to the API

The API doesn't authenticate the /audit endpoints or /flagged, and anyone who can reach it can create an account with /private-network/signup. Its data includes decrypted transfers. Expose it, and the Auditor Explorer, only on a network the operator controls.

What to watch

QuestionWhere to look
Has a chain stopped reporting?GET /audit/participants shows isFlagged, flagReason and flaggedAt for chains whose block headers are late. GET /audit/header-proofs shows the headers themselves.
Has a transfer gone wrong?GET /flagged. See Flagging transactions.
Did a transfer fail and get reversed?The transaction's details include revertDataTransaction, with the hashes and statuses of the reverting transactions.
Who is frozen?GET /audit/participants?status=frozen, and frozenChainIds on each token
Which tokens are waiting for approval?GET /audit/tokens?status=new. See Approving new tokens.

The services don't send alerts. Poll the API, or watch the flagger's logs, which record each flag as a warning.


Did this page help you?