RaylsEnygmaHandler

RaylsEnygmaHandler is the abstract base contract for Enygma tokens on a Privacy Node (Rayls Sovereign chain). On each chain, an Enygma token is an ERC-20 token. Its extra functions do two things:

  • send tokens to other chains as shielded transfers through the Private Network Hub (PNH, "the Hub"), the Hyperledger Besu chain at the centre of the Rayls Private Network;
  • use the token as the payment leg of private Delivery-versus-Payment (DvP) swaps.

Inheritance: RaylsApp, ERC20, Initializable, ReentrancyGuard, RaylsAccessManaged, IRaylsTokenStandard

Source: src/rayls-protocol-sdk/tokens/RaylsEnygmaHandler.sol in rayls-sovereign-contracts

📘

How Enygma tokens are deployed

You don't deploy this contract directly. The Privacy Node's contract factory, RNContractFactoryV1, deploys ProductionEnygmaToken, the standard concrete Enygma token, with deployEnygmaAsUser(name, symbol, decimals). The caller becomes the token's owner. The token is then authorised on the Privacy Node and activated on the Hub. When the token first reaches another chain, that chain's relayer deploys the same standard contract there, whatever bytecode the issuer used. This page documents the interface every Enygma token exposes.

For a task-oriented guide, see Send Enygma transactions.


Initialisation

initialize — factory deployments

function initialize(bytes calldata userArgs, RaylsTrustedInit calldata trusted) public initializer

The factory calls this once, when it deploys the token.

  • userArgs is abi.encode(string name, string symbol, uint8 decimals).
  • trusted carries the endpoint, owner, deployer and resource ID set by the factory.

The call emits RaylsEnygmaErc20TokenCreated.

Constructor — direct deployments

constructor(
    string memory _name,
    string memory _symbol,
    address _endpoint,
    address _owner,
    uint8 _decimals,
    bool _isCustom
)
ParameterDescription
_nameToken name
_symbolToken symbol
_endpointRayls endpoint on the Privacy Node
_ownerAddress granted the owner role (TOKEN_OWNER for this token): mint, burn, setSwapValidityTime
_decimalsToken decimals, at most 77. More reverts with RaylsEnygmaHandler__InvalidDecimals. ProductionEnygmaToken uses 18.
_isCustomStored by the contract. RaylsEnygmaHandler itself doesn't read it. ProductionEnygmaToken passes false.

The constructor also disables initialize on the deployed instance.


ERC-20 functions

transfer, approve and transferFrom are the standard ERC-20 functions, with one extra check: the token must be authorised on the local Privacy Node. Otherwise they revert with RaylsApp__PrivacyNodeNotActive. These transfers stay on the chain and don't use Enygma.

name, symbol and decimals return the values set at deployment. GetERCStandard() returns SharedObjects.ErcStandard.Enygma.


Cross-chain transfers

A cross-chain transfer burns the total from the sender on this chain and asks the relayer to deliver each leg (recipient, value, destination chain) through the Hub. Every transfer function returns a reference ID (bytes32) and emits it.

crossTransfer — one or more recipients

function crossTransfer(
    address[] memory _to,
    uint256[] memory _value,
    uint256[] memory _toChainId,
    SharedObjects.EnygmaProgramData[][] memory _userProgramData
) public virtual returns (bytes32)

This sends _value[i] to _to[i] on chain _toChainId[i]. _userProgramData[i] holds the program steps to run on the destination chain for leg i; pass an empty array for none.

crossTransferFrom — from another holder

function crossTransferFrom(
    address _from,
    address[] memory _to,
    uint256[] memory _value,
    uint256[] memory _toChainId,
    SharedObjects.EnygmaProgramData[][] memory _userProgramData
) public virtual returns (bytes32)

This works like crossTransfer, but burns from _from. The caller's allowance from _from is reduced by every leg's value, so it must cover the total.

linearCrossTransfer — one recipient

function linearCrossTransfer(
    address _to,
    uint256 _value,
    uint256 _toChainId,
    SharedObjects.EnygmaProgramData[] memory _userProgramData
) public virtual returns (bytes32)

This is a shortcut for a single leg. _userProgramData is that leg's program steps.

linearCrossTransferFrom — one recipient, from another holder

function linearCrossTransferFrom(
    address _from,
    address _to,
    uint256 _value,
    uint256 _toChainId,
    SharedObjects.EnygmaProgramData[] memory _userProgramData
) public virtual returns (bytes32)

This spends _value of the caller's allowance from _from.

Transfer rules

All four functions revert unless:

RuleError
_from is not the zero addressRaylsEnygmaHandler__WrongAddress
_to, _value, _toChainId and _userProgramData have the same lengthRaylsEnygmaHandler__ArrayLengthMismatch
There is at least one legRaylsEnygmaHandler__EmptyArray
Every leg has a non-zero recipient, value and chain IDRaylsEnygmaHandler__ZeroValueArg
No leg targets this chainRaylsEnygmaHandler__WrongFunctionForSameChainId
The legs use at most 5 different destination chainsRaylsEnygmaHandler__TooManyUniqueChainIds
The token has a resource ID, so it has been activated on the HubRaylsApp__ResourceNotApproved
The token isn't frozen on this Privacy NodeRaylsApp__PrivacyNodeFrozen
The token is active on the HubRaylsApp__HubNotActive
This chain and every destination chain are active participants, and the token isn't frozen for any of themReverts in the Privacy Node's EnygmaPNEvents contract
The sender holds the totalERC20InsufficientBalance

The reference ID is derived from this chain's ID, the sender, the destination chains and recipients, the block number and a per-token nonce. All legs of one call share it.

Program steps

Each leg can carry program steps that run on the destination chain, in the same transaction as the settlement mint:

struct EnygmaProgramData {
    bytes32 resourceId;      // target by resource ID...
    address contractAddress; // ...or by address: set exactly one of the two
    bytes4 selector;         // function selector
    bytes args;              // ABI-encoded arguments, without the selector
}
  • Mint first. For each leg, the token puts a settlement mint step first: crossMintStandard(to, value, referenceId) on this token's resource ID. Your steps follow, in order.
  • Execution. On the destination chain, the Privacy Node's ProgrammabilityExecutorV1 runs each leg's steps in one transaction. For each step it resolves the target and checks the target's code hash and the selector against the template registry. It then calls target.call(selector ‖ args ‖ origin), where origin is the 20-byte address of the sender on the source chain. The settlement mint doesn't get the origin.
  • Approved templates only. A step whose code hash and selector aren't approved in the template registry reverts with ProgramData__UnapprovedTemplate. The registry lives on the Hub and is replicated to every Privacy Node. Anyone can propose a template; the Private Network operator approves or revokes it.
  • Limits. A leg can carry at most 256 steps, including the settlement mint. The settlement mint steps in a leg must add up exactly to the leg's value, or the leg reverts with ProgramData__MintTotalMismatch.
  • Failure. If any step reverts, the whole leg reverts, including the mint. The relayer then returns the value to the sender on the source chain.

Example: pay bob 10 tokens on chain B and record a reference in a contract there. The contract's code and setMessage must be an approved template.

import {RaylsEnygmaHandler} from './tokens/RaylsEnygmaHandler.sol';
import {SharedObjects} from './libraries/SharedObjects.sol';

interface IInvoiceBook {
    function setMessage(string calldata message) external;
}

contract PayAndRecord {
    function pay(RaylsEnygmaHandler token, address bob, uint256 chainIdB, address invoiceBookOnB)
        external
        returns (bytes32 referenceId)
    {
        SharedObjects.EnygmaProgramData[] memory steps = new SharedObjects.EnygmaProgramData[](1);
        steps[0] = SharedObjects.EnygmaProgramData({
            resourceId: bytes32(0),
            contractAddress: invoiceBookOnB,
            selector: IInvoiceBook.setMessage.selector,
            args: abi.encode('invoice-42')
        });
        referenceId = token.linearCrossTransfer(bob, 10, chainIdB, steps);
    }
}

Here the contract PayAndRecord is the sender, so it must hold the tokens. Paths are relative to src/rayls-protocol-sdk.

Transfer status

// Status as an enum
function referenceIdStatus(bytes32 _referenceID) public view virtual returns (ReferenceIdStatus)

// Status as its numeric value
function referenceIdStatusUint(bytes32 _referenceID) public view virtual returns (uint256)

Each chain's token records the status of the reference IDs it has seen. Read it on the chain you are interested in.

ValueStatusSet byChain
0NOSTATUS—Not seen on this chain
1SENTcrossTransfer and the other transfer functionsSource
2RECEIVEDcrossMintStandard: the settlement mint of a leg, or of a refundReceiving chain
3DEPOSITEDdepositToDvpDepositor's chain
4WITHDRAW_ASKEDcallWithdrawFromDvpWithdrawer's chain
5WITHDRAW_RECEIVEDreceiveWithdrawFromDvpWithdrawer's chain
6REVERTEDcrossRevertMint (refund on this chain after the Hub didn't accept a transfer batch or a DvP deposit) or crossTransferRevertBatch (leg sent back from its destination)Source or destination

A leg that fails on its destination returns to the sender as a transfer under the same reference ID. So the source chain then shows RECEIVED for that reference ID, not REVERTED.


DvP (Delivery-versus-Payment)

The Enygma token is the payment leg of DvP swaps against ERC-721 and ERC-1155 tokens. A swap has four stages:

  1. Deposit. Each party moves its asset into DvP.
  2. Swap request. Each party calls its token's swap function with the same _sharedId.
  3. Settlement. On the Hub, the first request processed by the relayers initiates the swap and the second completes it. Either side can go first.
  4. Withdrawal. Each party withdraws what it received on its own chain.

See Private DvP with Enygma for the full flow.

All DvP functions require the token to be active on the Hub; otherwise they revert with one of the RaylsApp__ errors in Transfer rules.

depositToDvp

function depositToDvp(uint256 amount) public virtual returns (bytes32)

This burns amount from the caller on this chain and asks the relayer to move it into a DvP coin on the Hub. The reference ID's status becomes DEPOSITED, and the function emits transactionReferenceId. A zero amount is allowed, for example when a non-fungible token (NFT) is given with no payment. If the Hub step fails, the relayer re-mints the amount to the caller, and the status becomes REVERTED.

swapWithDvpForERC721

function swapWithDvpForERC721(
    uint256 _nftId,
    bytes32 _nftResourceId,
    uint256 _enygmaAmount,
    uint256 _destChainId,
    bytes32 _sharedId,
    uint64 _validityTime
) public virtual

The Enygma holder (the buyer) calls this to pay _enygmaAmount for NFT _nftId of the token _nftResourceId, held on chain _destChainId. No tokens move in this call: the relayer pays from the caller's DvP deposits. The NFT holder makes the matching request with swapWithDvpForEnygma on its RaylsErc721DvpHandler token, using the same _sharedId. If the two requests' terms don't match, the relayer doesn't complete the swap. Instead it posts a SwapError status, through the Privacy Node's communicator contract, on the chain of the party whose request arrived second.

swapWithDvpForERC1155

function swapWithDvpForERC1155(
    uint256 _nftId,
    uint256 _nftAmountOrOne,
    bytes32 _nftResourceId,
    uint256 _enygmaAmount,
    uint256 _destChainId,
    bytes32 _sharedId,
    uint64 _validityTime
) public virtual

This is the same for ERC-1155 tokens. _nftAmountOrOne is the amount of token ID _nftId. The counterparty calls swapWithDvpForEnygma on its RaylsErc1155DvpHandler token.

Validity time

_validityTime is how long, in seconds, the swap stays open on the Hub before it can expire.

ValueEffect
0Default validity of 2 days
More than 5 hours and less than 14 daysUsed as given
Anything elseReverts with RaylsEnygmaHandler__SwapValidityOutOfRange

The bounds are exclusive: exactly 5 hours or exactly 14 days reverts.

callWithdrawFromDvp

function callWithdrawFromDvp(uint256 amount) public virtual returns (bytes32)

This asks the relayer to withdraw amount of this token from the caller's DvP coins back to this chain. The seller calls it to collect payment, and the buyer to recover change. The function reverts with RaylsEnygmaHandler__ZeroAmount if amount is 0. It doesn't check DvP holdings on-chain: the relayer must find the caller's coins for the amount, combining several coins if needed. The status becomes WITHDRAW_ASKED. When the Hub step succeeds, the relayer calls receiveWithdrawFromDvp to mint the amount to the caller, and the status becomes WITHDRAW_RECEIVED.

Cancel a swap

function cancelERC721Swap(bytes32 _sharedId, uint256 _toChainId, uint256 _nftId, bytes32 _nftResourceId, uint256 _enygmaAmount) public virtual
function cancelERC1155Swap(bytes32 _sharedId, uint256 _toChainId, uint256 _nftId, uint256 _nftAmountOrOne, bytes32 _nftResourceId, uint256 _enygmaAmount) public virtual

These ask the relayer to cancel a pending swap. On the Hub, cancellation returns the initiator's locked coin. Either party can cancel, from its own token.

On this token, the caller must hold a non-zero balance on this chain, or the call reverts with Caller has no token balance. Tokens deposited in DvP don't count towards this balance.


Mint and burn

// Owner only
function mint(address _to, uint256 _value) public virtual restricted

// Owner only
function burn(address from, uint256 value) public virtual restricted
  • Checks. Both functions revert with RaylsEnygmaHandler__ZeroAmount on a zero value, and with RaylsApp__PrivacyNodeNotActive if the token isn't authorised on the Privacy Node.
  • burn needs no approval. It burns from any holder's balance, without the holder's approval.
  • Hub update. Once the token is activated on the Hub, each mint or burn is also applied to this chain's balance on the Hub by the relayer. The amount is public on the Hub. If the Hub update fails, the relayer reverses the local mint or burn with supplyUpdateRevert.

Swap validity setting

// Owner only
function setSwapValidityTime(uint64 _validityTime) public virtual restricted
function swapValidityTime() public view returns (uint64)

This stores a validity time, with the same bounds as _validityTime. In the current contract, the swap functions don't read the stored value: a zero _validityTime always uses the fixed 2-day default.


Events

EventEmitted by
crossTransferReferenceId(bytes32 _referenceId)crossTransfer, crossTransferFrom, linearCrossTransfer, linearCrossTransferFrom
transactionReferenceId(bytes32 _referenceId)The four transfer functions, depositToDvp, callWithdrawFromDvp
RaylsEnygmaErc20TokenCreated(address indexed tokenAddress)initialize
Transfer, Approval (ERC-20)Every balance or allowance change, including burns and mints

The requests that relayers act on are emitted by the Privacy Node's EnygmaPNEvents contract, not by the token. Examples are EnygmaSendTransferPNH, EnygmaMint, EnygmaBurn, EnygmaDepositToDvp, EnygmaWithdrawFromDvp, EnygmaSwapWithDvpForERC721, EnygmaSwapWithDvpForERC1155 and DvpSwapCancelled. These events carry addresses and amounts in clear, on the Privacy Node only.


Relayer functions

The relayer and the Privacy Node's programmability executor call these functions. Users don't call them directly.

FunctionPurpose
crossMintStandard(address _to, uint256 _value, bytes32 _referenceId)Settlement mint of a leg. Runs once per reference ID: repeat calls for a reference ID that is already RECEIVED or REVERTED do nothing.
crossRevertMint(address _to, uint256 _value, string _reason, bytes32 _referenceId)Refund on the source chain when the Hub didn't accept a batch. Does nothing if the reference ID is already REVERTED or RECEIVED.
crossTransferRevertBatch(address _from, address _to, uint256 _value, uint256 _toChainId, bytes32 _referenceId)Sends a failed leg back from its destination to the sender.
supplyUpdateRevert(uint256 _amount, address _recipient, bool _isMint)Reverses a mint or burn that the Hub didn't record.
receiveWithdrawFromDvp(address _to, uint256 _value, bytes32 _referenceId)Mints a DvP withdrawal.
dvpSwapCompleted(uint256, bytes32 _sharedId)Records that a swap is ready for withdrawal.
notifySenderWithPNCommunicator(...), notifySenderAndReceiverWithPNCommunicator(...)Post DvP status updates.
crossMint(address _to, uint256 _value), crossBurn(address _from, uint256 _value)Mint or burn this token as a program step. The sender on the source chain must hold TOKEN_OWNER for this token on this chain, or the step reverts with RaylsEnygmaHandler__NotTokenOwnerScoped.
crossTransferCheck()Empty hook the relayer calls when it deploys the token on a new chain.

Access control summary

RoleFunctions
Owner (TOKEN_OWNER for this token)mint, burn, setSwapValidityTime
RELAYER (relayer and programmability executor)crossMintStandard, crossRevertMint, crossTransferRevertBatch, supplyUpdateRevert, receiveWithdrawFromDvp, dvpSwapCompleted, notifySenderWithPNCommunicator, notifySenderAndReceiverWithPNCommunicator, crossMint, crossBurn
MESSAGE_EXECUTORcrossTransferCheck
Privacy Node token registrysetResourceId
Any addresstransfer, approve, transferFrom, crossTransfer, crossTransferFrom, linearCrossTransfer, linearCrossTransferFrom, depositToDvp, callWithdrawFromDvp, swapWithDvpForERC721, swapWithDvpForERC1155, cancelERC721Swap, cancelERC1155Swap

Roles are assigned in the Privacy Node's access manager when the token is deployed. If the factory's caller is not the configured owner, the caller also receives TOKEN_OWNER for the token.


Errors

ErrorWhen
RaylsEnygmaHandler__ZeroValueArg(address receiver, uint256 value, uint256 destChainId)A leg has a zero recipient, value or chain ID
RaylsEnygmaHandler__WrongFunctionForSameChainId(uint256 chainId)A leg targets this chain
RaylsEnygmaHandler__WrongAddress(address from)_from is the zero address
RaylsEnygmaHandler__ArrayLengthMismatch()Leg arrays differ in length
RaylsEnygmaHandler__EmptyArray()No legs
RaylsEnygmaHandler__TooManyUniqueChainIds(uint256 count)More than 5 destination chains
RaylsEnygmaHandler__ZeroAmount()Zero value in mint, burn, callWithdrawFromDvp, crossMint, crossBurn or crossMintStandard
RaylsEnygmaHandler__ZeroAddress(address addr)Zero recipient in crossMintStandard
RaylsEnygmaHandler__InvalidDecimals(uint8 decimals)Decimals above 77
RaylsEnygmaHandler__SwapValidityOutOfRange(uint64 provided, uint64 min, uint64 max)Validity time outside the bounds
RaylsEnygmaHandler__NotTokenOwnerScoped(address originSender)crossMint or crossBurn from a sender without TOKEN_OWNER
RaylsApp__ResourceNotApproved()Token not yet activated on the Hub
RaylsApp__PrivacyNodeFrozen(address tokenAddress)Token frozen on this Privacy Node
RaylsApp__HubNotActive(address tokenAddress, uint8 privacyNodeStatus, uint8 hubStatus)Token not active on the Hub, for example frozen there
RaylsApp__PrivacyNodeNotActive(address tokenAddress, uint8 privacyNodeStatus)Token not authorised on this Privacy Node

The contract's application binary interface (ABI) also declares RaylsEnygmaHandler__AuthorityNotSet, RaylsEnygmaHandler__NotRelayer, RaylsEnygmaHandler__CallableTargetNotContract and RaylsEnygmaHandler__CallableExecutionFailed. The current contract never raises them.

Related pages


Did this page help you?