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 deployedYou don't deploy this contract directly. The Privacy Node's contract factory,
RNContractFactoryV1, deploysProductionEnygmaToken, the standard concrete Enygma token, withdeployEnygmaAsUser(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
initialize — factory deploymentsfunction initialize(bytes calldata userArgs, RaylsTrustedInit calldata trusted) public initializerThe factory calls this once, when it deploys the token.
userArgsisabi.encode(string name, string symbol, uint8 decimals).trustedcarries 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
)| Parameter | Description |
|---|---|
_name | Token name |
_symbol | Token symbol |
_endpoint | Rayls endpoint on the Privacy Node |
_owner | Address granted the owner role (TOKEN_OWNER for this token): mint, burn, setSwapValidityTime |
_decimals | Token decimals, at most 77. More reverts with RaylsEnygmaHandler__InvalidDecimals. ProductionEnygmaToken uses 18. |
_isCustom | Stored 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
crossTransfer — one or more recipientsfunction 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
crossTransferFrom — from another holderfunction 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
linearCrossTransfer — one recipientfunction 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
linearCrossTransferFrom — one recipient, from another holderfunction 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:
| Rule | Error |
|---|---|
_from is not the zero address | RaylsEnygmaHandler__WrongAddress |
_to, _value, _toChainId and _userProgramData have the same length | RaylsEnygmaHandler__ArrayLengthMismatch |
| There is at least one leg | RaylsEnygmaHandler__EmptyArray |
| Every leg has a non-zero recipient, value and chain ID | RaylsEnygmaHandler__ZeroValueArg |
| No leg targets this chain | RaylsEnygmaHandler__WrongFunctionForSameChainId |
| The legs use at most 5 different destination chains | RaylsEnygmaHandler__TooManyUniqueChainIds |
| The token has a resource ID, so it has been activated on the Hub | RaylsApp__ResourceNotApproved |
| The token isn't frozen on this Privacy Node | RaylsApp__PrivacyNodeFrozen |
| The token is active on the Hub | RaylsApp__HubNotActive |
| This chain and every destination chain are active participants, and the token isn't frozen for any of them | Reverts in the Privacy Node's EnygmaPNEvents contract |
| The sender holds the total | ERC20InsufficientBalance |
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
ProgrammabilityExecutorV1runs 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 callstarget.call(selector ‖ args ‖ origin), whereoriginis 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.
| Value | Status | Set by | Chain |
|---|---|---|---|
| 0 | NOSTATUS | — | Not seen on this chain |
| 1 | SENT | crossTransfer and the other transfer functions | Source |
| 2 | RECEIVED | crossMintStandard: the settlement mint of a leg, or of a refund | Receiving chain |
| 3 | DEPOSITED | depositToDvp | Depositor's chain |
| 4 | WITHDRAW_ASKED | callWithdrawFromDvp | Withdrawer's chain |
| 5 | WITHDRAW_RECEIVED | receiveWithdrawFromDvp | Withdrawer's chain |
| 6 | REVERTED | crossRevertMint (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:
- Deposit. Each party moves its asset into DvP.
- Swap request. Each party calls its token's swap function with the same
_sharedId. - Settlement. On the Hub, the first request processed by the relayers initiates the swap and the second completes it. Either side can go first.
- 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
depositToDvpfunction 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
swapWithDvpForERC721function swapWithDvpForERC721(
uint256 _nftId,
bytes32 _nftResourceId,
uint256 _enygmaAmount,
uint256 _destChainId,
bytes32 _sharedId,
uint64 _validityTime
) public virtualThe 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
swapWithDvpForERC1155function swapWithDvpForERC1155(
uint256 _nftId,
uint256 _nftAmountOrOne,
bytes32 _nftResourceId,
uint256 _enygmaAmount,
uint256 _destChainId,
bytes32 _sharedId,
uint64 _validityTime
) public virtualThis 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.
| Value | Effect |
|---|---|
0 | Default validity of 2 days |
| More than 5 hours and less than 14 days | Used as given |
| Anything else | Reverts with RaylsEnygmaHandler__SwapValidityOutOfRange |
The bounds are exclusive: exactly 5 hours or exactly 14 days reverts.
callWithdrawFromDvp
callWithdrawFromDvpfunction 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 virtualThese 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__ZeroAmounton a zero value, and withRaylsApp__PrivacyNodeNotActiveif the token isn't authorised on the Privacy Node. burnneeds 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
| Event | Emitted 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.
| Function | Purpose |
|---|---|
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
| Role | Functions |
|---|---|
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_EXECUTOR | crossTransferCheck |
| Privacy Node token registry | setResourceId |
| Any address | transfer, 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
| Error | When |
|---|---|
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
Updated 4 days ago
