Skip to content

Instantly share code, notes, and snippets.

@Dobrokhvalov
Last active November 23, 2018 01:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Dobrokhvalov/372f03cd5e2f093b6b47445cf3a41ed0 to your computer and use it in GitHub Desktop.
Save Dobrokhvalov/372f03cd5e2f093b6b47445cf3a41ed0 to your computer and use it in GitHub Desktop.
import './SafeMath.sol';
import './Stoppable.sol';
import './MyCoolNFT.sol';
/**
* @title NFT Linkdrop Contract
*
*/
contract NFTLinkdropContract is Stoppable {
using SafeMath for uint256;
address public NFT_ADDRESS; // token to distribute
address public LINKDROPPER; // linkdropper's address, which has NFTs to distribute
address public LINKDROP_VERIFICATION_ADDRESS; // special address, used on claim to verify
// that links signed by the linkdropper
event LogWithdraw(
address indexed transitAddress,
uint256 indexed tokenId,
address receiver,
uint timestamp,
uint256 wrappedETH
);
event LogBuy(
uint256 indexed tokenId,
bytes32 secretHash,
uint wrappedETH,
uint commissionFee
);
event LogCancel(
address indexed sender,
uint256 indexed tokenId
);
event LogWithdrawCommission(uint commissionAmount);
event LogChangeFixedCommissionFee(
uint oldCommissionFee,
uint newCommissionFee
);
// Mappings of transit address => receiver address if link is used.
mapping (address => address) usedLinks;
struct Gift {
address sender;
bytes32 secretHash;
uint wrappedETH;
}
// mapping tokenId => secretHash
mapping (uint => Gift) gifts;
uint public accruedCommission;
uint public commissionFee;
/**
* @dev Contructor that sets linkdrop params
* @param _linkdropVerificationAddress special address, used on claim to
* verify that links signed by the linkdropper
*/
constructor(
address _linkdropVerificationAddress,
uint _commissionFee,
string _name,
string _symbol
) public {
LINKDROPPER = msg.sender;
NFT_ADDRESS = new MintableNFT(_name, _symbol);
LINKDROP_VERIFICATION_ADDRESS = _linkdropVerificationAddress;
commissionFee = _commissionFee;
accruedCommission = 0;
}
/**
* @dev Verify that address is signed with needed private key.
* @param _transitAddress transit address assigned to transfer
* @param _addressSigned address Signed address.
* @param _tokenId tokenId attached to link.
* @param _v ECDSA signature parameter v.
* @param _r ECDSA signature parameters r.
* @param _s ECDSA signature parameters s.
* @return True if signature is correct.
*/
function verifyLinkPrivateKey(
address _transitAddress,
address _addressSigned,
uint256 _tokenId,
uint8 _v,
bytes32 _r,
bytes32 _s)
public pure returns(bool success) {
bytes32 prefixedHash = keccak256("\x19Ethereum Signed Message:\n32", _addressSigned, _tokenId);
address retAddr = ecrecover(prefixedHash, _v, _r, _s);
return retAddr == _transitAddress;
}
/**
* @dev Change relayer's fixed commission fee.
* Only owner can change commision fee.
*
* @param _newCommissionFee uint New relayer's fixed commission
* @return True if success.
*/
function changeFixedCommissionFee(uint _newCommissionFee)
public
whenNotPaused
whenNotStopped
onlyOwner
returns(bool success) {
uint oldCommissionFee = commissionFee;
commissionFee = _newCommissionFee;
emit LogChangeFixedCommissionFee(oldCommissionFee, commissionFee);
return true;
}
function buyGiftLink(uint _tokenId, bytes32 _secretHash)
payable public
whenNotPaused
whenNotStopped
onlyOwner returns (bool) {
require(msg.value > commissionFee);
uint wrappedETH = msg.value.sub(commissionFee); //amount = msg.value - comission
// saving transfer details
gifts[_tokenId] = Gift(
msg.sender,
_secretHash,
wrappedETH
);
// accrue verifier's commission
accruedCommission = accruedCommission.add(commissionFee);
// send nft
MintableNFT nft = MintableNFT(NFT_ADDRESS);
nft.mint(address(this), _tokenId);
// log buy event
emit LogBuy(_tokenId, _secretHash, wrappedETH, commissionFee);
return true;
}
/**
* @dev Cancel transfer and get sent ether back. Only transfer sender can
* cancel transfer.
* @return True if success.
*/
function cancelGift(uint _tokenId) public returns (bool success) {
Gift memory gift = gifts[_tokenId];
// only sender can cancel transfer;
require(msg.sender == gift.sender);
delete gifts[_tokenId];
// transfer ether to recipient's address
msg.sender.transfer(gift.wrappedETH);
// log cancel event
emit LogCancel(msg.sender, _tokenId);
return true;
}
function getGift(uint _tokenId)
public view returns (bytes32 secretHash, uint wrappedETH) {
return (
gifts[_tokenId].secretHash,
gifts[_tokenId].wrappedETH
);
}
/**
* @dev Transfer accrued commission to verifier's address.
* @return True if success.
*/
function withdrawCommission()
public
whenNotPaused
returns(bool success)
{
uint commissionToTransfer = accruedCommission;
accruedCommission = 0;
owner.transfer(commissionToTransfer); // owner is verifier
emit LogWithdrawCommission(commissionToTransfer);
return true;
}
/**
* @dev Verify that address is signed with needed private key.
* @param _transitAddress transit address assigned to transfer
* @param _addressSigned address Signed address.
* @param _v ECDSA signature parameter v.
* @param _r ECDSA signature parameters r.
* @param _s ECDSA signature parameters s.
* @return True if signature is correct.
*/
function verifyReceiverAddress(
address _transitAddress,
address _addressSigned,
uint8 _v,
bytes32 _r,
bytes32 _s)
public pure returns(bool success) {
bytes32 prefixedHash = keccak256("\x19Ethereum Signed Message:\n32", _addressSigned);
address retAddr = ecrecover(prefixedHash, _v, _r, _s);
return retAddr == _transitAddress;
}
/**
* @dev Verify that claim params are correct and the link key wasn't used before.
* @param _recipient address to receive tokens.
* @param _tokenId NFT's id
* @param _transitAddress transit address provided by the airdropper
* @param _keyV ECDSA signature parameter v. Signed by the airdrop transit key.
* @param _keyR ECDSA signature parameters r. Signed by the airdrop transit key.
* @param _keyS ECDSA signature parameters s. Signed by the airdrop transit key.
* @param _recipientV ECDSA signature parameter v. Signed by the link key.
* @param _recipientR ECDSA signature parameters r. Signed by the link key.
* @param _recipientS ECDSA signature parameters s. Signed by the link key.
* @return True if claim params are correct.
*/
function checkWithdrawal(
address _recipient,
uint256 _tokenId,
address _transitAddress,
uint8 _keyV,
bytes32 _keyR,
bytes32 _keyS,
uint8 _recipientV,
bytes32 _recipientR,
bytes32 _recipientS)
public view returns(bool success) {
// verify that link wasn't used before
require(isLinkClaimed(_transitAddress) == false);
// verifying that key is legit and signed by LINKDROP_VERIFICATION_ADDRESS's key
require(verifyLinkPrivateKey(LINKDROP_VERIFICATION_ADDRESS, _transitAddress, _tokenId, _keyV, _keyR, _keyS));
// verifying that recepients address signed correctly
require(verifyReceiverAddress(_transitAddress, _recipient, _recipientV, _recipientR, _recipientS));
return true;
}
/**
* @dev Withdraw an nft to receiver address if withdraw params are correct.
* @param _recipient address to receive the nft.
* @param _tokenId NFT's id
* @param _transitAddress transit address provided by the airdropper
* @param _keyV ECDSA signature parameter v. Signed by the airdrop transit key.
* @param _keyR ECDSA signature parameters r. Signed by the airdrop transit key.
* @param _keyS ECDSA signature parameters s. Signed by the airdrop transit key.
* @param _recipientV ECDSA signature parameter v. Signed by the link key.
* @param _recipientR ECDSA signature parameters r. Signed by the link key.
* @param _recipientS ECDSA signature parameters s. Signed by the link key.
* @return True if NFT was successfully sent to receiver.
*/
function withdraw(
address _recipient,
uint256 _tokenId,
address _transitAddress,
uint8 _keyV,
bytes32 _keyR,
bytes32 _keyS,
uint8 _recipientV,
bytes32 _recipientR,
bytes32 _recipientS
)
public
whenNotPaused
whenNotStopped
returns (bool success) {
Gift memory gift = gifts[_tokenId];
// verify link
require(checkWithdrawal(_recipient,
_tokenId,
_transitAddress,
_keyV,
_keyR,
_keyS,
_recipientV,
_recipientR,
_recipientS));
// mark link as used
usedLinks[_transitAddress] = _recipient;
// delete not needed info
delete gifts[_tokenId];
// send nft
ERC721 nft = ERC721(NFT_ADDRESS);
nft.transferFrom(address(this), _recipient, _tokenId);
// transfer ether to recipient's address
_recipient.transfer(gift.wrappedETH);
// log withdrawal
emit LogWithdraw(
_transitAddress,
_tokenId,
_recipient,
now,
gift.wrappedETH
);
return true;
}
/**
* @dev Get boolean if link is already claimed.
* @param _transitAddress transit address provided to receiver by the airdropper
* @return True if the transit address was already used.
*/
function isLinkClaimed(address _transitAddress)
public view returns (bool claimed) {
return linkClaimedTo(_transitAddress) != 0x0;
}
/**
* @dev Get receiver for claimed link
* @param _transitAddress transit address provided to receiver by the airdropper
* @return True if the transit address was already used.
*/
function linkClaimedTo(address _transitAddress)
public view returns (address receiver) {
return usedLinks[_transitAddress];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment