RaylsApp

RaylsApp is the base contract for all cross-chain applications on Rayls Privacy Nodes. It provides the messaging primitives (_raylsSend, _raylsSendToResourceId) and the access control foundation used by every handler contract.

Inheritance: abstract contract \u2014 extend it directly for custom arbitrary-message apps; token handlers inherit it transitively.


Constructor

constructor(
    address _endpoint,
    address _raylsNodeEndpoint,
    address _userGovernance
)
ParameterDescription
_endpointPrivacy Node endpoint \u2014 routes messages to other Privacy Nodes via the Private Network Hub
_raylsNodeEndpointPrivacy Node endpoint for the Rayls Public Chain \u2014 used when bridging to/from the public chain
_userGovernanceRBAC contract that manages user roles and permissions within the Privacy Node

Cross-chain messaging

_raylsSend \u2014 send by address

function _raylsSend(
    uint256 _dstChainId,
    address _destination,
    bytes memory _payload
) internal virtual

Sends a message to a specific contract address on another chain. Use when you know the destination contract address.

  • _dstChainId \u2014 destination Privacy Node chain ID (or Public Chain ID)
  • _destination \u2014 the contract address to call on the destination chain
  • _payload \u2014 ABI-encoded function call, e.g. abi.encodeWithSignature("myFunction(address,uint256)", to, amount)

_raylsSendToResourceId \u2014 send by resource ID

function _raylsSendToResourceId(
    uint256 _dstChainId,
    bytes32 _resourceId,
    bytes memory _payload
) internal virtual

Routes by resourceId instead of address. Use when you don't know the exact contract address on the destination chain (the endpoint resolves it via the resource registry).

Atomic variant \u2014 with lock/revert payloads

function _raylsSendToResourceId(
    uint256 _dstChainId,
    bytes32 _resourceId,
    bytes memory _payload,
    bytes memory _lockData,
    bytes memory _revertDataPayloadSender,
    bytes memory _revertDataPayloadReceiver,
    BridgedTransferMetadata memory transferMetadata
) internal virtual

Used by token handlers for atomic teleports. The extra payloads are executed if the transfer needs to be reverted: _revertDataPayloadSender runs on the source chain, _revertDataPayloadReceiver on the destination.


Access control

Rayls handlers use a role-based access control system (RaylsAccessManaged) instead of a single-owner pattern. Roles are enforced by the shared RaylsAccessManagerV1 on the Privacy Node.

The restricted modifier checks whether the caller has the required role for that function selector:

modifier restricted() {
    _checkCanCall(msg.sender, msg.sig);
    _;
}

If the caller does not have the role, the call reverts with RaylsAccessManaged__Unauthorized.

Standard roles

RoleWho holds itTypical functions
OwnerThe institution that deployed the tokenmint, burn, submitTokenUpdate
MESSAGE_EXECUTORThe Rayls relayerCross-chain receive callbacks (receiveTeleport, receiveResourceId, unlock, etc.)
RELAYERThe Rayls relayer (DVP handlers and Enygma)dvpSwapCompleted, crossMint, crossRevertMint, etc.

Roles are registered automatically when a handler is deployed, via _registerAccessControl(address _owner) inside the constructor. You do not need to configure roles manually for standard handlers.

onlyRegisteredUsers modifier

modifier onlyRegisteredUsers()

Restricts a function to callers who are registered with the Privacy Node's user governance contract (_userGovernance). Used on teleportToPublicChain to ensure only approved users can bridge to the public chain.


Token registration lifecycle

After deploying any token handler, call registerToken on the Privacy Node's TokenRegistryReplica system contract:

// Call on the TokenRegistryReplica system contract (not on your token)
TokenRegistryReplica.registerToken(
    address tokenAddress,
    SharedObjects.ErcStandard ercStandard,
    bool isCustom
)

This sends a cross-chain message to the Private Hub's TokenRegistry. Once the Hub operator approves the registration, the relayer calls receiveResourceId() on your token contract automatically:

// Present in all handlers \u2014 called by the relayer after approval
function receiveResourceId(bytes32 _resourceId) public virtual restricted {
    resourceId = _resourceId;
    _registerResourceId(); // maps resourceId \u2192 address(this) on the endpoint
}

You can verify registration by reading the public resourceId variable \u2014 a non-zero value means the token is registered. Teleport functions will work automatically once the registration message has been received.


Minimal custom cross-chain app

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "@rayls/contracts/RaylsApp.sol";

contract HelloWorldContract is RaylsApp {
    string public message;

    constructor(
        address _endpoint,
        address _raylsNodeEndpoint,
        address _userGovernance
    ) RaylsApp(_endpoint, _raylsNodeEndpoint, _userGovernance) {}

    // Send a greeting to a contract on another Privacy Node
    function sendGreeting(uint256 destChainId, address destContract, string memory _msg) external {
        _raylsSend(
            destChainId,
            destContract,
            abi.encodeWithSignature("receiveGreeting(string)", _msg)
        );
    }

    // Receive a greeting \u2014 restrict to MESSAGE_EXECUTOR in production
    function receiveGreeting(string memory _msg) public {
        message = _msg;
    }
}

Important: In production, receive functions should be marked restricted and their selectors registered under the MESSAGE_EXECUTOR role so only the Rayls relayer can invoke them.