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.
UPDATE 2022
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:
mapping(address => bool)
and functionsallow(address)
anddeny(address)
(both only callable by the owner)?