Rayls Docs

RaylsErc721Handler

Definition

In Rayls SDK, apart from what you can build using the base contract RaylsApp, we offer other contracts to complement and ease your implementations. Here, you will see the handler for Erc721, which inherits from RaylsApp — to become integrated to Rayls — and also, of course, from Erc721.

Methods

If you wish to explore the contract on your own, it can be found in the section below. Here we'll highlight only some of the main methods.

Constructor

abstract contract RaylsErc721Handler is RaylsApp, ERC721, Initializable, Ownable {
    string private _uri;
    string public _name;
    string public _symbol;

    address balanceCommitmentsContract;
    mapping(address => mapping(uint256 => bool)) lockedTokens;
    mapping(uint256 => bool) alreadySentDeployInstructions;

    /**
     * @dev Constructor to initialize the RaylsCore with the provided endpoint and owner.
     * @param _endpoint The address of the Rayls endpoint.
     * @param _owner The address of the owner of the RaylsCore.
     */
    constructor(string memory uri, string memory name_, string memory symbol_, address _endpoint, address _owner)
        ERC721(name_, symbol_)
        RaylsApp(_endpoint)
        Ownable(_owner)
    {
        _name = name_;
        _symbol = symbol_;
        _uri = uri;
        _disableInitializers();
    }
  
    // ...
}

As we can see, we have _name, _symbol and uri to represent the ERC721 in the constructor, as well as the _endpoint for the RaylsApp and an address to set as the _owner.

initialize

function initialize(string memory uri, string memory name_, string memory symbol_) public virtual initializer {
    address _owner = _getOwnerAddressOnInitialize();
    address _endpoint = _getEndpointAddressOnInitialize();
    resourceId = _getResourceIdOnInitialize();
    // ERC721 initizalization
    _name = name_;
    _symbol = symbol_;
    _uri = uri;
    // RaylsApp Initialization
    _transferOwnership(_owner);
    endpoint = IRaylsEndpoint(_endpoint);
}

As this contract can be used by proxy contracts, we need to do the initializations in a standard methods, not on the constructor (otherwise the proxy contract state won't be updated properly).

initialize

function initialize(string memory uri, string memory name_, string memory symbol_) public virtual initializer {
    address _owner = _getOwnerAddressOnInitialize();
    address _endpoint = _getEndpointAddressOnInitialize();
    resourceId = _getResourceIdOnInitialize();
    // ERC721 initizalization
    _name = name_;
    _symbol = symbol_;
    _uri = uri;
    // RaylsApp Initialization
    _transferOwnership(_owner);
    endpoint = IRaylsEndpoint(_endpoint);
}

As this contract can be used by proxy contracts, we need to do the initializations in a standard methods, not on the constructor (otherwise the proxy contract state won't be updated properly).

sendTeleport

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

The base of every following teleport method: It basically proxies the request to the RaylsApp method _raylsSendToResourceId, which sends a payload (including possible atomic revert mechanisms) cross-chain.

teleport

function teleport(address to, uint256 id, uint256 chainId) public virtual returns (bool) {
    _burn(id);

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

    sendTeleport(
      chainId,
      abi.encodeWithSignature("receiveTeleport(address,uint256)", to, id),
      bytes(""),
      abi.encodeWithSignature("revertTeleportMint(address,uint256)", msg.sender, id),
      abi.encodeWithSignature("revertTeleportBurn(address,uint256)", msg.sender, id),
      metadata
    );
  
    return true;
}

A method to teleport (send cross-chain) an amount of the token. This is a vanilla teleport, which means there are no revert mechanisms, that is, it assumes the transaction will be a success.

teleportAtomic

function teleportAtomic(address to, uint256 id, uint256 chainId) public virtual returns (bool) {
    _burn(id);

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

    sendTeleport(
      chainId,
      abi.encodeWithSignature("receiveTeleportAtomic(address,uint256)", to, id),
      abi.encodeWithSignature("unlock(address,uint256)", to, id),
      abi.encodeWithSignature("revertTeleportMint(address,uint256)", msg.sender, id),
      abi.encodeWithSignature("revertTeleportBurn(address,uint256)", msg.sender, id),
      metadata
    );
    return true;
}

A method to teleport (send cross-chain) an amount of the token. This is an atomic teleport, which means there are revert mechanisms, that is, if the transaction fails by some reason, it will be reverted.

Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {RaylsApp} from "../RaylsApp.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/IRaylsEndpoint.sol";
import "../RaylsMessage.sol";
import "../libraries/Pedersen/Curve.sol";
import "../libraries/SharedObjects.sol";
import "../libraries/Utils.sol";

abstract contract RaylsErc721Handler is RaylsApp, ERC721, Initializable, Ownable {
    string private _uri;
    string public _name;
    string public _symbol;

    address balanceCommitmentsContract;
    mapping(address => mapping(uint256 => bool)) lockedTokens;
    mapping(uint256 => bool) alreadySentDeployInstructions;

    /**
     * @dev Constructor to initialize the RaylsCore with the provided endpoint and owner.
     * @param _endpoint The address of the Rayls endpoint.
     * @param _owner The address of the owner of the RaylsCore.
     */
    constructor(string memory uri, string memory name_, string memory symbol_, address _endpoint, address _owner)
        ERC721(name_, symbol_)
        RaylsApp(_endpoint)
        Ownable(_owner)
    {
        _name = name_;
        _symbol = symbol_;
        _uri = uri;
        _disableInitializers();
    }

    function initialize(string memory uri, string memory name_, string memory symbol_) public virtual initializer {
        address _owner = _getOwnerAddressOnInitialize();
        address _endpoint = _getEndpointAddressOnInitialize();
        resourceId = _getResourceIdOnInitialize();
        // ERC721 initizalization
        _name = name_;
        _symbol = symbol_;
        _uri = uri;
        // RaylsApp Initialization
        _transferOwnership(_owner);
        endpoint = IRaylsEndpoint(_endpoint);
    }

    function name() public view virtual override returns (string memory) {
        return _name;
    }

    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return _uri;
    }

    function teleport(address to, uint256 id, uint256 chainId) public virtual returns (bool) {
        _burn(id);

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

        sendTeleport(
            chainId,
            abi.encodeWithSignature("receiveTeleport(address,uint256)", to, id),
            bytes(""),
            abi.encodeWithSignature("revertTeleportMint(address,uint256)", msg.sender, id),
            abi.encodeWithSignature("revertTeleportBurn(address,uint256)", msg.sender, id),
            metadata
        );
        return true;
    }

    function teleportAtomic(address to, uint256 id, uint256 chainId) public virtual returns (bool) {
        _burn(id);

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

        sendTeleport(
            chainId,
            abi.encodeWithSignature("receiveTeleportAtomic(address,uint256)", to, id),
            abi.encodeWithSignature("unlock(address,uint256)", to, id),
            abi.encodeWithSignature("revertTeleportMint(address,uint256)", msg.sender, id),
            abi.encodeWithSignature("revertTeleportBurn(address,uint256)", msg.sender, id),
            metadata
        );
        return true;
    }

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

    function receiveTeleportAtomic(address to, uint256 id) public virtual {
        _mint(owner(), id);
        if (to != owner()) {
            _lock(to, id);
        }
    }

    function revertTeleportMint(address to, uint256 id) public virtual receiveMethod {
        _mint(to, id);
    }

    function revertTeleportBurn(uint256 id) public virtual receiveMethod {
        _burn(id);
    }

    /**
     * @notice Unlocks the locked funds and calls transfer
     * @param to Address of a "to" account to unlock the funds to
     * @param id Id of the token to unlock
     */
    function unlock(address to, uint256 id) external returns (bool) {
        bool success = _unlock(to, id);
        require(success, "cannot unlock the assets");
        _safeTransfer(owner(), to, id);
        return true;
    }

    function _lock(address to, uint256 id) internal {
        require(to != address(0));
        lockedTokens[to][id] = true;
    }

    function _unlock(address to, uint256 id) internal returns (bool) {
        require(to != address(0));
        bool isLocked = lockedTokens[to][id];
        require(isLocked == true, "No funds to unlock");
        lockedTokens[to][id] = false;
        return true;
    }

    function receiveResourceId(bytes32 _resourceId) public virtual receiveMethod onlyFromCommitChain {
        resourceId = _resourceId;
        _registerResourceId();
    }

    function submitTokenRegistration() public virtual {
        _raylsSend(
            endpoint.getCommitChainId(),
            endpoint.getCommitChainAddress("TokenRegistry"),
            abi.encodeWithSignature(
                "addToken((string,string,uint256,uint256,bytes,bytes,bool,uint8))",
                SharedObjects.TokenRegistrationData({
                    name: _name,
                    symbol: _symbol,
                    totalSupply: 0,
                    issuerChainId: endpoint.getChainId(),
                    bytecode: address(this).code,
                    initializerParams: _generateInitializerParams(),
                    isFungible: false,
                    ercStandard: SharedObjects.ErcStandard.ERC721
                })
            )
        );
    }

    function _generateInitializerParams() internal view virtual returns (bytes memory) {
        return abi.encodeWithSignature("initialize(string,string,string)", _uri, _name, _symbol);
    }

    function isTokenLocked(address account, uint256 id) public view returns (bool) {
        return lockedTokens[account][id];
    }

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