Controlled transfers

A controlled transfer allows an extension to perform a transfer from one address to another without needing prior approval. This is only possible for extensions granted with the TOKEN_CONTROLLER_ROLE token role.

Warning

  • When an extension (or any address) is granted the TOKEN_CONTROLLER_ROLE token role, the granted address has approval on all token balance holders.
  • Make sure only trusted addresses have the TOKEN_CONTROLLER_ROLE token role.

An extension can request the TOKEN_CONTROLLER_ROLE token role during registration by specifying it as a required role using _requireRole(TOKEN_CONTROLLER_ROLE).

import {TokenExtension, TransferData} from "@consensys-software/UniversalToken-extendable/contracts/extensions/TokenExtension.sol";

contract HoldExtension is TokenExtension {

    constructor() {
        _requireRole(TOKEN_CONTROLLER_ROLE)
    }

    function initialize() external override {
    // ...
    }
}

When your extension has the required token role, you can perform a controlled transfer.

Create a TransferData object specifying the details of the transfer. Do this manually by instantiating the TransferData struct, or by using the _buildTransfer helper function from TokenExtension.

When you have a TransferData object, invoke the _tokenTransfer(TransferData) function inside of TokenExtension.

import {TokenExtension, TransferData} from "@consensys-software/UniversalToken-extendable/contracts/extensions/TokenExtension.sol";

contract HoldExtension is TokenExtension {

    // ...

    constructor() {
        _requireRole(TOKEN_CONTROLLER_ROLE)
    }

    function initialize() external override {
    // ...
    }

    function _executeHold(bytes32 holdId, bytes32 lockPreimage, address recipient) internal isHeld(holdId) {
        HoldExtensionData storage data = holdData();
        require(data.holds[holdId].notary == _msgSender(), "executeHold: caller must be the hold notary");

        TransferData memory transferData = _buildTransfer(data.holds[holdId].sender, recipient, data.holds[holdId].amount);
        _tokenTransfer(transferData);
        // equivalent to:
        // _transfer(holds[holdId].sender, recipient, holds[holdId].amount);
    }
}

Warning

A controlled transfer triggers both a TOKEN_BEFORE_TRANSFER_EVENT and a TOKEN_TRANSFER_EVENT. This means that controlled transfers can still be restricted by extensions and controlled transfers cannot occur inside a TOKEN_BEFORE_TRANSFER_EVENT or TOKEN_TRANSFER_EVENT callback.