I want to build a service where users can sign up (off-chain) to get credentials which give them the right to perform certain on-chain actions.

For example, lets pretend I'm building a service where someone can buy my time by holding my token (and burning it, or spending it or whatever), but I only want people who know me to be able to buy the token. So I have a site where my friends can give me their addresses, and then trading of my token will be restricted to within those addresses.

I want this list of valid owners to be updatable. How can I accomplish this?

For bonus points, lets assume the whitelist changes relatively frequently. Frequently enough that changing the contract becomes expensive. Is there a way around updating the contract itself?

  • How about a state variable of type mapping(address => bool) and functions allow(address) and deny(address) (both only callable by the owner)?
    – user19510
    Jan 10, 2018 at 1:10

2 Answers 2


You can use this smart contract, with the modifier onlyAgent, which is working the same way as onlyOwner (Ownable). Agents must be approved by adding them using the addAgent method.

Your contract must inherit AgentRole



So, you will need to write your own "Oracle". Your offchain application can gather the addresses, and an administrative backend, where only you have access, could be used to call a registerOwners function in the contract, that adds the address to a list of owners (be sure to provide a way of also deactivating them, if needed).

This should be tested, but in order to save gas, you could have your friends call a function that saves their address in a mapping (a kind of pre-registration), and you would just call a function that flips a boolean inside of a struct, saying that the pre-registration has been allowed.


Time passes, experience is added ;)

You can use signed messages. Have a look at this contract: https://etherscan.io/address/0xF2Bce1776A49250A5cad0F81aeb5C21Be72F537a#code

On line 374 (File 1 of 21):

function mintById_SignedMessage(uint256 _tokenId, uint256 _setPrice, uint256 expirationTimestamp, uint256 _nonce, bytes memory _sig) public payable{
        // check validity and execute
        require(expirationTimestamp <= block.timestamp, "Expired");
        bytes32 message = SignedMessages.prefixed(keccak256(abi.encodePacked(msg.sender, _tokenId, _setPrice, expirationTimestamp, _nonce)));
        require(msg.value == _setPrice, "Wrong payment");
        require(SignedMessages.consumePass(message, _sig, _nonce), "Error in signed msg");
        _internalMintById(msg.sender, _tokenId);
        if (msg.value > 0) {
            emit FundsReceived(msg.sender, msg.value, "payment by minting sale");

This function is called by passing so called "minting passes". Head down to file 3 of 21: This is the contract that checks the signature. So, the call parameters get signed with the private key of a valid "issuer" offchain (by you). Then you provide this signed message to the users (e.g. via frontend or backend) and when they call the contract, they can provide that signed message. Your contract can verify it has been issued by the right issuer, as shown here:

function consumePass(bytes32 message, bytes memory sig, uint256 nonce) internal returns(bool){
    // check the nonce first
    if (nonces[nonce]) {
        return false;
    // check the issuer
    if (!issuers[recoverSigner(message, sig)]) {
        return false;
    // consume the nonce if it is safe
    nonces[nonce] = true;
    return true;

Some further links:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.