Rayls Docs

Installing the Rayls SDK

Prerequisites

The SDK must be installed in a blockchain that supports Rayls, that is, an EVM-compatible blockchain with a Rayls endpoint smart contract setup.

If you have any questions, please contact us here

Installation

via pnpm

pnpm add @rayls/contracts

via npm

npm install @rayls/contracts --save

Description

SDK stands for Software Development Kit. On Rayls, this means all of the interfaces, types, utils, contracts and much more you need to interact with the endpoint -- and therefore teleport your message to other blockchains.

The most important contract on the SDK is RaylsApp, and that's because it is the one responsible to concentrate all the "Rayls" methods, that is, the methods that interact with the endpoint, which in turn interact with the commit chain and so on, such as sending a payload cross-chain to an address or to a resourceId

Other than that, the SDK offers some pre-made contracts inheriting from RaylsApp in order to demonstrate what can be done with it. One example is the RaylsErc20Handler.sol, which inherits not only RaylsApp but also Erc20, implementing all needed functionalities to be a "Rayls Erc20", e.g. capable of transacting cross-chain. Here is a snapshot of the contract:

abstract contract RaylsErc20Handler is RaylsApp, ERC20, Initializable, Ownable {
    string tokenName;
    string tokenSymbol;
    mapping(address => uint256) lockedAmount;

	constructor(string memory _name, string memory _symbol, address _endpoint, address _owner) 
		ERC20(_name, _symbol) 
		RaylsApp(_endpoint) 
		Ownable(_owner)
    {
        tokenName = _name;
        tokenSymbol = _symbol;
        _disableInitializers();
    }
  
  // More methods down here ...

Notice that RaylsErc20Handler.sol is just a base point, an implementation suggestion, but you are free to implement it in any other way -- or any other contract. As long as you inherit RaylsApp.sol, your contract will have access to the necessary methods to communicate with the Rayls infrastructure.

Examples

We'll go over some scenarios using the SDK: setting up a token, token minting and a token teleport using the RaylsErc20Handler.sol and sending arbitrary messages using RaylsApp.

Setting up a Token

As seen on the RaylsErc20Handler.sol constructor, it requires:

  • A _name for the token
  • A _symbol for the token
  • The blockchain's _endpoint address
  • The token _owner's address

By deploying the contract with such information, you have an ERC20 token with all needed capabilities, from standard minting and burning to Rayls teleporting and reverting.

Of course, you can even extend the RaylsErc20Handler and add your desired methods.

contract Erc20TokenExample is RaylsErc20Handler {
    constructor(
        string memory _name,
        string memory _symbol,
        address _endpoint
    )
        RaylsErc20Handler(
            _name,
            _symbol,
            _endpoint,
            msg.sender
        )
    {
        _mint(msg.sender, 2000);
    }

Token Minting

Once you have your token setup, you can:

  • Mint using mint from RaylsErc20Handler.sol
/**
     * @dev Mint new tokens and submit an update to the CommitChain.
     * @param to The address to which the new tokens will be minted.
     * @param value The amount of tokens to mint.
*/
function mint(address to, uint256 value) public virtual onlyOwner {
  _mint(to, value);
  uint8 updateType = 1;
  _submitTokenUpdate(value, updateType);
}

As can be seen in the method, you have to inform the address to where the tokens will be minted and the value of the token to mint. Then, the native Erc20 _mint method is used and the token is minted. Another procedure is done, which is updating the commit chain's TokenRegistry with your new balance.

This is the formal and correct way to mint a token because it keeps the commit chain updated about it, and thus allows for the correct procedure of teleporting tokens cross-chain.

However, if your intent is to just test token minting internally on the PL, you can use the other method for minting.

  • Mint using any other mint method implemented by you

As can be seen on the Erc20TokenExample.sol above, on the Setting up a Token section, you can, for example, create a token that mints some amount of the token as soon as it is created to the address of your choice, on the constructor.

You can also, of course, implement a public mint method that redirects its parameters to the native Erc20 _mint method and so on. Feel free to choose whatever fits best your needs (figuring out Rayls features, testing etc).

/**
     * @dev Mint new tokens and submit an update to the CommitChain.
     * @param to The address to which the new tokens will be minted.
     * @param value The amount of tokens to mint.
*/
function mint(address to, uint256 value) public {
  _mint(to, value);
}

Just keep in mind: official cross-chain transactions will only be recognized and allowed by the commit chain if the TokenRegistry is properly informed and updated of them.

Token Teleport

Teleporting a token cross-chain is basically sending a payload cross-chain (which is a functionality present in RaylsApp.sol) requesting the receiver to mint the token amount while the sender burns the token amount.

As with the minting, it can also be done by your implementation, but we'll have a look here at some of the RaylsErc20Handler.sol's implementation:

function teleport(address to, uint256 value, uint256 chainId) public virtual returns (bool) {
  _burn(msg.sender, value);

  BridgedTransferMetadata memory transferMetadata = BridgedTransferMetadata({
    assetType: RaylsBridgeableERC.ERC20,
    id: 0,
    from: msg.sender,
    to: to,
    amount: value
  });

  sendTeleport(
    chainId,
    abi.encodeWithSignature("receiveTeleport(address,uint256)", to, value),
    bytes(""),
    bytes(""),
    bytes(""),
    transferMetadata
  );
  return true;
}

function sendTeleport(
  uint256 chainId,
  bytes memory _payload,
  bytes memory _lockDataPayload,
  bytes memory _revertDataPayloadSender,
  bytes memory _revertDataPayloadReceiver,
  BridgedTransferMetadata memory transferMetadata
) internal {
  require(resourceId != bytes32(0), "Token not registered.");

  _raylsSendToResourceId(
    chainId,
    resourceId,
    _payload,
    _lockDataPayload,
    _revertDataPayloadSender,
    _revertDataPayloadReceiver,
    transferMetadata
  );
}

function receiveTeleport(address to, uint256 value) public virtual receiveMethod {
  _mint(to, value);
}

As can be seen, you inform the address and the chainId you are sending to, and the value you are sending, and it will make and send a payload cross-chain, activating the receiveTeleport method in the receiver, which will mint the respective value in the to address.

Arbitrary Messages

Arbitrary messages are any bytes payload you wish to send cross-chain. With the RaylsApp.sol methods, it gets really simple to send one.

The simplest method is _raylsSend, where all it requires is:

  • The _dstChainId (destination chainId) number
  • The _destination address
  • The _payload to send, in bytes
{
    
    function _raylsSend(
        uint256 _dstChainId,
        address _destination,
        bytes memory _payload
    ) internal virtual {
        endpoint.send(_dstChainId, _destination, _payload);
    }
    
}

Of course, you can always extend the RaylsApp.sol and add your desired methods.

contract ArbitraryMessage is RaylsApp {
    string public msg;
    BridgedTransferMetadata private emptyMetadata;

  constructor(address _endpoint) RaylsApp(_endpoint) {}

    function sendMessage(bytes32 _resourceId, string memory message, uint256 destChainId) public {
        _raylsSendToResourceId(
            destChainId,
            _resourceId, 
            abi.encodeWithSelector(this.receiveMsg.selector, message),
            "",
            "",
            "",
            emptyMetadata
        );
    }

    function receiveMsg(string memory _msg) public {
        msg = _msg;
    }
}

As can be seen in this example, both the sender and the destination will deploy the contract. Then, the sender can call the sendMessage method to the destination _resourceId and destChainId with a message. The payload is then encoded to activate the receiveMsg method in the receiver with the parameter message. Thus, the message sent by the sender is gonna be stored on the msg variable of the receiver. Finally, we have successfully sent an arbitrary string message from the sender to the receiver.


What’s Next