├── contracts ├── dao │ ├── Fracton.sol │ └── FractonVesting.sol ├── library │ ├── FractonMiniNFTHelper.sol │ └── FractonFFTHelper.sol ├── interface │ ├── IFractonTokenFactory.sol │ ├── IFractonFFT.sol │ ├── IFractonMiniNFT.sol │ ├── IFractonSwap.sol │ └── IFractonVesting.sol ├── member │ └── FractonMemberNFT.sol └── fundraising │ ├── FractonTokenFactory.sol │ ├── FractonMiniNFT.sol │ ├── FractonFFT.sol │ └── FractonSwap.sol └── README.md /contracts/dao/Fracton.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; 5 | import '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol'; 6 | 7 | contract Fracton is ERC20, ERC20Burnable { 8 | constructor() ERC20('Fracton', 'FT') { 9 | _mint(msg.sender, 1E26); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/library/FractonMiniNFTHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import '../fundraising/FractonMiniNFT.sol'; 5 | 6 | library FractonMiniNFTHelper { 7 | function getBytecode(string memory uri) public pure returns (bytes memory) { 8 | bytes memory bytecode = type(FractonMiniNFT).creationCode; 9 | return abi.encodePacked(bytecode, abi.encode(uri)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/library/FractonFFTHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import '../fundraising/FractonFFT.sol'; 5 | 6 | library FractonFFTHelper { 7 | function getBytecode(string memory name, string memory symbol) 8 | public 9 | pure 10 | returns (bytes memory) 11 | { 12 | bytes memory bytecode = type(FractonFFT).creationCode; 13 | return abi.encodePacked(bytecode, abi.encode(name, symbol)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/interface/IFractonTokenFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | interface IFractonTokenFactory { 5 | function getowner() external view returns (address); 6 | 7 | function getDAOAddress() external view returns (address); 8 | 9 | function getVaultAddress() external view returns (address); 10 | 11 | function getSwapAddress() external view returns (address); 12 | 13 | function getPoolFundingVaultAddress() external view returns (address); 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FractonV1 2 | 3 | Fracton Protocol is an NFT liquidity infrastructure, with 2 steps of fractionalization (ERC721-ERC1155-ERC20), which provides permissionless liquidity and oracle for all kinds of NFTs. Based on a deeply reformed ERC1155 middle layer standard, Fracton is building a non-status smart contract system, to increase the protocol’s efficiency, lower gas fees, and maximize asset security. 4 | 5 | Dedicated to abstracting the financial layer apart from the utility of NFTs, Fracton can be easily integrated into both CEX and Dapps, and also offer solutions for both inter-protocol liquidity providers and fractionalized NFT market makers. 6 | 7 | 8 | Website: https://www.fracton.cool 9 | 10 | Twitter: https://twitter.com/FractonProtocol 11 | 12 | Discord: https://discord.gg/qBVrFcEAGV 13 | 14 | Mirror: https://medium.com/@FractonProtocol 15 | 16 | Medium: https://medium.com/@FractonProtocol 17 | -------------------------------------------------------------------------------- /contracts/interface/IFractonFFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; 5 | 6 | interface IFractonFFT is IERC20 { 7 | event SetPercent(uint256 vaultPercent, uint256 pfVaultPercent); 8 | 9 | function swapmint(uint256 amount, address to) external returns (bool); 10 | 11 | function transfer(address to, uint256 value) external returns (bool); 12 | 13 | function multiTransfer(address[] memory receivers, uint256[] memory amounts) 14 | external; 15 | 16 | function transferFrom( 17 | address from, 18 | address to, 19 | uint256 value 20 | ) external returns (bool); 21 | 22 | function burnFrom(address from, uint256 value) external returns (bool); 23 | 24 | function isExcludedFromFee(address account) external view returns (bool); 25 | 26 | function updateFee(uint256 vaultPercent_, uint256 pfVaultPercent_) 27 | external 28 | returns (bool); 29 | } 30 | -------------------------------------------------------------------------------- /contracts/interface/IFractonMiniNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | interface IFractonMiniNFT { 5 | event StartNewRound(uint256 blockNumber, uint256 sellingPrice); 6 | 7 | event CloseRound(uint256 blockNumber); 8 | 9 | event ClaimBlindBox(address owner, uint256 tokenID, uint256 amount); 10 | 11 | event WithdrawEther(address caller, uint256 amount); 12 | 13 | event UpdateRoundSucceed(uint256 round, uint256 blockNumber); 14 | 15 | event UpdateBlindBoxPrice(uint256 price); 16 | 17 | function startNewRound(uint256 sellingPrice) external returns (bool); 18 | 19 | function closeRound() external returns (bool); 20 | 21 | function mintBlindBox(uint256 amount) external payable returns (uint256); 22 | 23 | function claimBlindBox(uint256 tokenID) external returns (uint256); 24 | 25 | function withdrawEther() external returns (bool); 26 | 27 | function updateRoundSucceed(uint256 round) external returns (bool); 28 | 29 | function updateBlindBoxPrice(uint256 BBoxPrice) external returns (bool); 30 | 31 | function totalSupply(uint256 id) external view returns (uint256); 32 | 33 | function burn(uint256 amount) external; 34 | 35 | function swapmint(uint256 amount, address to) external returns (bool); 36 | } 37 | -------------------------------------------------------------------------------- /contracts/member/FractonMemberNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; 5 | import '@openzeppelin/contracts/access/Ownable.sol'; 6 | import '@openzeppelin/contracts/utils/Counters.sol'; 7 | 8 | contract FractonMemberNFT is ERC721, Ownable { 9 | using Counters for Counters.Counter; 10 | string public defaulturi = 11 | 'https://ipfs.io/ipfs/bafkreicf36h66xrhkso2wytsp5wuzkwons6jnnw47yvz6b5nogxi6guymm'; 12 | string private _baseuri = ''; 13 | 14 | Counters.Counter private _tokenIdCounter; 15 | 16 | constructor() ERC721('Non-Fungible Crew', 'NFCrew') {} 17 | 18 | function safeMint(address to) public onlyOwner { 19 | uint256 tokenId = _tokenIdCounter.current(); 20 | _tokenIdCounter.increment(); 21 | _safeMint(to, tokenId); 22 | } 23 | 24 | function batchSafeMint(address to, uint256 amount) external onlyOwner { 25 | uint256 tokenId; 26 | for (uint256 i = 0; i < amount; i++) { 27 | tokenId = _tokenIdCounter.current(); 28 | _tokenIdCounter.increment(); 29 | _safeMint(to, tokenId); 30 | } 31 | } 32 | 33 | function _baseURI() internal view override returns (string memory) { 34 | return _baseuri; 35 | } 36 | 37 | function setBaseURI(string memory uri) external onlyOwner { 38 | _baseuri = uri; 39 | } 40 | 41 | function tokenURI(uint256 tokenId) 42 | public 43 | view 44 | override 45 | returns (string memory) 46 | { 47 | require( 48 | _exists(tokenId), 49 | 'ERC721URIStorage: URI query for nonexistent token' 50 | ); 51 | 52 | string memory base = _baseURI(); 53 | // If there is no base URI, return the default token URI. 54 | if (bytes(base).length == 0) { 55 | return defaulturi; 56 | } 57 | return string(abi.encodePacked(base, Strings.toString(tokenId))); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /contracts/interface/IFractonSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | interface IFractonSwap { 5 | event UpdatePoolRelation( 6 | address editor, 7 | address miniNFT, 8 | address FFT, 9 | address NFT 10 | ); 11 | 12 | event PoolClaim(address owner, address miniNFTcontract, uint256 tokenID); 13 | 14 | event SwapMiniNFTtoFFT( 15 | address owner, 16 | address miniNFTcontract, 17 | uint256 tokenID, 18 | uint256 miniNFTAmount 19 | ); 20 | 21 | event SwapFFTtoMiniNFT( 22 | address owner, 23 | address miniNFTcontract, 24 | uint256 miniNFTAmount 25 | ); 26 | 27 | event SendChainlinkVRF( 28 | uint256 requestId, 29 | address sender, 30 | address NFTContract 31 | ); 32 | 33 | event SwapMiniNFTtoNFT(address owner, address NFTContract, uint256 NFTID); 34 | 35 | event UpdateFactory(address factory); 36 | 37 | event UpdateTax(uint256 fftTax, uint256 nftTax); 38 | 39 | struct ChainLinkRequest { 40 | address sender; 41 | address nft; 42 | } 43 | 44 | function updatePoolRelation( 45 | address miniNFT, 46 | address FFT, 47 | address NFT 48 | ) external returns (bool); 49 | 50 | function poolClaim(address miniNFTcontract, uint256 tokenID) 51 | external 52 | returns (bool); 53 | 54 | function swapMiniNFTtoFFT( 55 | address miniNFTcontract, 56 | uint256 tokenID, 57 | uint256 amount 58 | ) external returns (bool); 59 | 60 | function swapFFTtoMiniNFT(address miniNFTcontract, uint256 miniNFTamount) 61 | external 62 | returns (bool); 63 | 64 | function swapMiniNFTtoNFT(address NFTContract) external returns (bool); 65 | 66 | function swapNFTtoMiniNFT( 67 | address NFTContract, 68 | address fromOwner, 69 | uint256 tokenId 70 | ) external returns (bool); 71 | 72 | function updateCallbackGasLimit(uint32 gasLimit_) external returns (bool); 73 | 74 | function updateVrfSubscriptionId(uint64 subscriptionId_) 75 | external 76 | returns (bool); 77 | } 78 | -------------------------------------------------------------------------------- /contracts/fundraising/FractonTokenFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import '@openzeppelin/contracts/utils/Create2.sol'; 5 | import '../library/FractonMiniNFTHelper.sol'; 6 | import '../library/FractonFFTHelper.sol'; 7 | import '../interface/IFractonTokenFactory.sol'; 8 | import '../interface/IFractonSwap.sol'; 9 | 10 | contract FractonTokenFactory is IFractonTokenFactory { 11 | address private _owner; 12 | address private _FractonGovernor; 13 | address public FractonSwap; 14 | address private _FractonVault; 15 | address private _FractonPFVault; //poolfundingvault 16 | address public pendingVault; 17 | address public pendingPFVault; 18 | 19 | mapping(address => address) public projectToMiniNFT; 20 | mapping(address => address) public projectToFFT; 21 | 22 | constructor( 23 | address daoAddress, 24 | address swapAddress, 25 | address vaultAddress, 26 | address PFvaultAddress 27 | ) { 28 | _owner = msg.sender; 29 | _FractonGovernor = daoAddress; 30 | _FractonVault = vaultAddress; 31 | _FractonPFVault = PFvaultAddress; 32 | FractonSwap = swapAddress; 33 | 34 | pendingVault = _FractonVault; 35 | pendingPFVault = _FractonPFVault; 36 | } 37 | 38 | modifier onlyFactoryOwner() { 39 | require(msg.sender == _owner, 'Fracton: invalid caller'); 40 | _; 41 | } 42 | 43 | modifier onlyDao() { 44 | require(msg.sender == _FractonGovernor, 'Fracton: caller is not dao'); 45 | _; 46 | } 47 | 48 | function createCollectionPair( 49 | address projectAddress, 50 | bytes32 salt, 51 | string memory miniNFTBaseUri, 52 | string memory name, 53 | string memory symbol 54 | ) external onlyFactoryOwner returns (address, address) { 55 | require( 56 | projectToMiniNFT[projectAddress] == address(0) && 57 | projectToFFT[projectAddress] == address(0), 58 | 'Already exist.' 59 | ); 60 | 61 | address newMiniNFTContract = Create2.deploy( 62 | 0, 63 | salt, 64 | FractonMiniNFTHelper.getBytecode(miniNFTBaseUri) 65 | ); 66 | 67 | require(newMiniNFTContract != address(0), 'Fracton: deploy MiniNFT Failed'); 68 | 69 | address newFFTContract = Create2.deploy( 70 | 0, 71 | salt, 72 | FractonFFTHelper.getBytecode(name, symbol) 73 | ); 74 | 75 | require(newFFTContract != address(0), 'Fracton: deploy FFT Failed'); 76 | 77 | projectToMiniNFT[projectAddress] = newMiniNFTContract; 78 | projectToFFT[projectAddress] = newFFTContract; 79 | 80 | require( 81 | IFractonSwap(FractonSwap).updatePoolRelation( 82 | newMiniNFTContract, 83 | newFFTContract, 84 | projectAddress 85 | ) 86 | ); 87 | 88 | return (newMiniNFTContract, newFFTContract); 89 | } 90 | 91 | function updateDao(address daoAddress) external onlyDao returns (bool) { 92 | _FractonGovernor = daoAddress; 93 | return true; 94 | } 95 | 96 | function signDaoReq() external onlyFactoryOwner returns (bool) { 97 | _FractonVault = pendingVault; 98 | _FractonPFVault = pendingPFVault; 99 | 100 | return true; 101 | } 102 | 103 | function updateVault(address pendingVault_) external onlyDao returns (bool) { 104 | pendingVault = pendingVault_; 105 | return true; 106 | } 107 | 108 | function updatePFVault(address pendingPFVault_) 109 | external 110 | onlyDao 111 | returns (bool) 112 | { 113 | pendingPFVault = pendingPFVault_; 114 | return true; 115 | } 116 | 117 | function getowner() external view override returns (address) { 118 | return _owner; 119 | } 120 | 121 | function getDAOAddress() external view override returns (address) { 122 | return _FractonGovernor; 123 | } 124 | 125 | function getSwapAddress() external view override returns (address) { 126 | return FractonSwap; 127 | } 128 | 129 | function getVaultAddress() external view override returns (address) { 130 | return _FractonVault; 131 | } 132 | 133 | function getPoolFundingVaultAddress() 134 | external 135 | view 136 | override 137 | returns (address) 138 | { 139 | return _FractonPFVault; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /contracts/interface/IFractonVesting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | interface IFractonVesting { 5 | event VestingScheduleCreated( 6 | address indexed vestingLocation, 7 | uint32 cliffDuration, 8 | uint32 duration, 9 | uint32 interval, 10 | bool isRevocable 11 | ); 12 | 13 | event VestingTokensGranted( 14 | address indexed beneficiary, 15 | uint256 vestingAmount, 16 | uint32 startDay, 17 | address vestingLocation 18 | ); 19 | 20 | event VestingTokensClaimed(address indexed beneficiary, uint256 amount); 21 | 22 | event GrantRevoked(address indexed grantHolder); 23 | 24 | struct vestingSchedule { 25 | bool isRevocable; /* true if the vesting option is revocable (a gift), false if irrevocable (purchased) */ 26 | uint32 cliffDuration; /* Duration of the cliff, with respect to the grant start day, in days. */ 27 | uint32 duration; /* Duration of the vesting schedule, with respect to the grant start day, in days. */ 28 | uint32 interval; /* Duration in days of the vesting interval. */ 29 | } 30 | 31 | struct tokenGrant { 32 | bool isActive; /* true if this vesting entry is active and in-effect entry. */ 33 | bool wasRevoked; /* true if this vesting schedule was revoked. */ 34 | address vestingLocation; /* Address of wallet that is holding the vesting schedule. */ 35 | uint32 startDay; /* Start day of the grant, in days since the UNIX epoch (start of day). */ 36 | uint256 amount; /* Total number of tokens that vest. */ 37 | uint256 claimedAmount; /* Out of vested amount, the amount that has been already transferred to beneficiary */ 38 | } 39 | 40 | function withdrawTokens(address beneficiary, uint256 amount) external; 41 | 42 | // ========================================================================= 43 | // === Methods for claiming tokens. 44 | // ========================================================================= 45 | 46 | function claimVestingTokens(address beneficiary) external; 47 | 48 | function claimVestingTokensForAll() external; 49 | 50 | // ========================================================================= 51 | // === Methods for administratively creating a vesting schedule for an account. 52 | // ========================================================================= 53 | 54 | function setVestingSchedule( 55 | address vestingLocation, 56 | uint32 cliffDuration, 57 | uint32 duration, 58 | uint32 interval, 59 | bool isRevocable 60 | ) external; 61 | 62 | // ========================================================================= 63 | // === Token grants (general-purpose) 64 | // === Methods to be used for administratively creating one-off token grants with vesting schedules. 65 | // ========================================================================= 66 | 67 | function addGrant( 68 | address beneficiary, 69 | uint256 vestingAmount, 70 | uint32 startDay, 71 | uint32 duration, 72 | uint32 cliffDuration, 73 | uint32 interval, 74 | bool isRevocable 75 | ) external; 76 | 77 | function addGrantWithScheduleAt( 78 | address beneficiary, 79 | uint256 vestingAmount, 80 | uint32 startDay, 81 | address vestingLocation 82 | ) external; 83 | 84 | function addGrantFromToday( 85 | address beneficiary, 86 | uint256 vestingAmount, 87 | uint32 duration, 88 | uint32 cliffDuration, 89 | uint32 interval, 90 | bool isRevocable 91 | ) external; 92 | 93 | // ========================================================================= 94 | // === Check vesting. 95 | // ========================================================================= 96 | 97 | function today() external view returns (uint32 dayNumber); 98 | 99 | function getGrantInfo(address grantHolder, uint32 onDayOrToday) 100 | external 101 | view 102 | returns ( 103 | uint256 amountVested, 104 | uint256 amountNotVested, 105 | uint256 amountOfGrant, 106 | uint256 amountAvailable, 107 | uint256 amountClaimed, 108 | uint32 vestStartDay, 109 | bool isActive, 110 | bool wasRevoked 111 | ); 112 | 113 | function getScheduleAtInfo(address vestingLocation) 114 | external 115 | view 116 | returns ( 117 | bool isRevocable, 118 | uint32 vestDuration, 119 | uint32 cliffDuration, 120 | uint32 vestIntervalDays 121 | ); 122 | 123 | function getScheduleInfo(address grantHolder) 124 | external 125 | view 126 | returns ( 127 | bool isRevocable, 128 | uint32 vestDuration, 129 | uint32 cliffDuration, 130 | uint32 vestIntervalDays 131 | ); 132 | 133 | // ========================================================================= 134 | // === Grant revocation 135 | // ========================================================================= 136 | 137 | function revokeGrant(address grantHolder) external; 138 | } 139 | -------------------------------------------------------------------------------- /contracts/fundraising/FractonMiniNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import '@openzeppelin/contracts/token/ERC1155/ERC1155.sol'; 5 | import '@openzeppelin/contracts/token/ERC721/IERC721.sol'; 6 | import '@openzeppelin/contracts/utils/Counters.sol'; 7 | import '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol'; 8 | import '../interface/IFractonMiniNFT.sol'; 9 | import '../interface/IFractonTokenFactory.sol'; 10 | 11 | contract FractonMiniNFT is ERC1155URIStorage, IFractonMiniNFT { 12 | using Counters for Counters.Counter; 13 | 14 | Counters.Counter private _tokenIds; //counter of round. 0 reserves for people's NFT 15 | 16 | bool public saleIsActive; //is open for blindbox mint 17 | 18 | address private _factory; //factory deployer address 19 | 20 | uint256 public blindBoxPrice = 1E17; //blindbox price 21 | 22 | uint256 public constant ROUND_CAP = 1000; 23 | 24 | mapping(uint256 => bool) public roundSucceed; //is round succeed for users' claiming into people's NFT 25 | 26 | mapping(uint256 => uint256) private _totalAmount; 27 | 28 | modifier onlyDAO() { 29 | address dao = IFractonTokenFactory(_factory).getDAOAddress(); 30 | require(msg.sender == dao, 'Fracton: caller is not Fracton DAO'); 31 | _; 32 | } 33 | 34 | modifier onlyOwner() { 35 | address owner = IFractonTokenFactory(_factory).getowner(); 36 | require(msg.sender == owner, 'Fracton: caller is not the owner'); 37 | _; 38 | } 39 | 40 | /** 41 | * @dev Total amount of tokens in with a given id. 42 | */ 43 | constructor(string memory uri_) ERC1155(uri_) { 44 | _factory = msg.sender; 45 | } 46 | 47 | receive() external payable {} 48 | 49 | function startNewRound(uint256 sellingPrice) 50 | external 51 | override 52 | onlyDAO 53 | returns (bool) 54 | { 55 | require(!saleIsActive, 'Fracton: active sale exists'); 56 | _tokenIds.increment(); 57 | saleIsActive = true; 58 | blindBoxPrice = sellingPrice; 59 | 60 | emit StartNewRound(block.number, sellingPrice); 61 | return true; 62 | } 63 | 64 | function closeRound() external override onlyDAO returns (bool) { 65 | saleIsActive = false; 66 | 67 | emit CloseRound(block.number); 68 | return true; 69 | } 70 | 71 | function mintBlindBox(uint256 amount) 72 | external 73 | payable 74 | override 75 | returns (uint256) 76 | { 77 | uint256 round = _tokenIds.current(); 78 | require(saleIsActive, 'Fracton: sale not active'); 79 | require( 80 | blindBoxPrice * amount <= msg.value, 81 | 'Fracton: Ether value not enough' 82 | ); 83 | _totalAmount[round] += amount; 84 | if (totalSupply(round) >= ROUND_CAP) { 85 | saleIsActive = false; 86 | } 87 | _mint(msg.sender, round, amount, ''); 88 | return amount; 89 | } 90 | 91 | function claimBlindBox(uint256 tokenID) external override returns (uint256) { 92 | require(roundSucceed[tokenID], 'Fracton: round is not succeed'); 93 | 94 | uint256 amount = balanceOf(msg.sender, tokenID); 95 | require(amount > 0, 'Fracton: no blindbox to claim'); 96 | 97 | _totalAmount[tokenID] -= amount; 98 | _totalAmount[0] += amount; 99 | _burn(msg.sender, tokenID, amount); 100 | _mint(msg.sender, 0, amount, ''); 101 | 102 | emit ClaimBlindBox(msg.sender, tokenID, amount); 103 | return amount; 104 | } 105 | 106 | // Withdraw fundrasing ethers for purchasing NFT 107 | function withdrawEther() external override onlyDAO returns (bool) { 108 | uint256 amount = address(this).balance; 109 | address dao = msg.sender; 110 | payable(dao).transfer(amount); 111 | 112 | emit WithdrawEther(msg.sender, amount); 113 | return true; 114 | } 115 | 116 | function updateDefaultURI(string memory defaultURI) external onlyOwner { 117 | _setURI(defaultURI); 118 | } 119 | 120 | function updateTokenURI(uint256 tokenId, string memory tokenURI) 121 | external 122 | onlyOwner 123 | { 124 | _setURI(tokenId, tokenURI); 125 | } 126 | 127 | function updateRoundSucceed(uint256 round) 128 | external 129 | override 130 | onlyDAO 131 | returns (bool) 132 | { 133 | require(_totalAmount[round] >= ROUND_CAP, 'Fracton: Not achieve yet'); 134 | roundSucceed[round] = true; 135 | 136 | emit UpdateRoundSucceed(round, block.number); 137 | return roundSucceed[round]; 138 | } 139 | 140 | function updateBlindBoxPrice(uint256 newPrice) 141 | external 142 | override 143 | onlyDAO 144 | returns (bool) 145 | { 146 | blindBoxPrice = newPrice; 147 | 148 | emit UpdateBlindBoxPrice(newPrice); 149 | return true; 150 | } 151 | 152 | function totalSupply(uint256 id) public view override returns (uint256) { 153 | return _totalAmount[id]; 154 | } 155 | 156 | function burn(uint256 amount) external { 157 | _totalAmount[0] -= amount; 158 | _burn(msg.sender, 0, amount); 159 | } 160 | 161 | function currentRound() public view returns (uint256) { 162 | return _tokenIds.current(); 163 | } 164 | 165 | function swapmint(uint256 amount, address to) 166 | external 167 | virtual 168 | override 169 | returns (bool) 170 | { 171 | require( 172 | msg.sender == IFractonTokenFactory(_factory).getSwapAddress(), 173 | 'Fracton: caller is not swap' 174 | ); 175 | _totalAmount[0] += amount; 176 | _mint(to, 0, amount, ''); 177 | return true; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /contracts/fundraising/FractonFFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; 5 | import '@openzeppelin/contracts/utils/math/SafeMath.sol'; 6 | import '../interface/IFractonFFT.sol'; 7 | import '../interface/IFractonTokenFactory.sol'; 8 | 9 | contract FractonFFT is ERC20, IFractonFFT { 10 | using SafeMath for uint256; 11 | 12 | mapping(address => bool) private _isExcludedFromFee; 13 | 14 | address public _factory; 15 | 16 | uint256 public vaultPercent = 20; 17 | uint256 public pfVaultPercent = 0; 18 | 19 | modifier onlyOwner() { 20 | address owner = IFractonTokenFactory(_factory).getowner(); 21 | require(msg.sender == owner, 'Fracton: caller is not the owner'); 22 | _; 23 | } 24 | 25 | modifier onlyDAO() { 26 | address dao = IFractonTokenFactory(_factory).getDAOAddress(); 27 | require(msg.sender == dao, 'Fracton: caller is not Fracton DAO'); 28 | _; 29 | } 30 | 31 | modifier onlySwap() { 32 | address dao = IFractonTokenFactory(_factory).getSwapAddress(); 33 | require(msg.sender == dao, 'Fracton: caller is not swap'); 34 | _; 35 | } 36 | 37 | constructor(string memory name_, string memory symbol_) 38 | ERC20(name_, symbol_) 39 | { 40 | _factory = msg.sender; 41 | 42 | _isExcludedFromFee[_factory] = true; 43 | _isExcludedFromFee[IFractonTokenFactory(_factory).getSwapAddress()] = true; 44 | _isExcludedFromFee[ 45 | IFractonTokenFactory(_factory).getPoolFundingVaultAddress() 46 | ] = true; 47 | _isExcludedFromFee[IFractonTokenFactory(_factory).getVaultAddress()] = true; 48 | _isExcludedFromFee[address(this)] = true; 49 | } 50 | 51 | function swapmint(uint256 amount, address to) 52 | external 53 | virtual 54 | override 55 | onlySwap 56 | returns (bool) 57 | { 58 | _mint(to, amount); 59 | return true; 60 | } 61 | 62 | /* math that SafeMath doesn't include */ 63 | function ceil(uint256 a, uint256 m) internal pure returns (uint256) { 64 | uint256 c = a.add(m); 65 | uint256 d = c.sub(1); 66 | return d.div(m).mul(m); 67 | } 68 | 69 | function cut(uint256 value, uint256 percent) public pure returns (uint256) { 70 | if (percent == 0) { 71 | return 0; 72 | } else { 73 | uint256 roundValue = ceil(value, percent); 74 | uint256 cutValue = roundValue.mul(percent).div(10000); 75 | return cutValue; 76 | } 77 | } 78 | 79 | function transfer(address to, uint256 value) 80 | public 81 | virtual 82 | override(ERC20, IFractonFFT) 83 | returns (bool) 84 | { 85 | address from = _msgSender(); 86 | require(value <= balanceOf(from), 'from balance insufficient'); 87 | 88 | if (isExcludedFromFee(from) || isExcludedFromFee(to)) { 89 | _transfer(from, to, value); 90 | } else { 91 | uint256 vaultFee = cut(value, vaultPercent); 92 | uint256 pfVaultFee = cut(value, pfVaultPercent); 93 | uint256 tokensToTransfer = value.sub(vaultFee).sub(pfVaultFee); 94 | 95 | _transfer( 96 | from, 97 | IFractonTokenFactory(_factory).getVaultAddress(), 98 | vaultFee 99 | ); 100 | _transfer( 101 | from, 102 | IFractonTokenFactory(_factory).getPoolFundingVaultAddress(), 103 | pfVaultFee 104 | ); 105 | _transfer(from, to, tokensToTransfer); 106 | } 107 | return true; 108 | } 109 | 110 | function multiTransfer(address[] memory receivers, uint256[] memory amounts) 111 | public 112 | virtual 113 | override 114 | { 115 | for (uint256 i = 0; i < receivers.length; i++) { 116 | transfer(receivers[i], amounts[i]); 117 | } 118 | } 119 | 120 | function transferFrom( 121 | address from, 122 | address to, 123 | uint256 value 124 | ) public virtual override(ERC20, IFractonFFT) returns (bool) { 125 | address spender = _msgSender(); 126 | require(value <= balanceOf(from), 'from balance insufficient'); 127 | 128 | if (isExcludedFromFee(from) || isExcludedFromFee(to)) { 129 | _spendAllowance(from, spender, value); 130 | _transfer(from, to, value); 131 | } else { 132 | uint256 vaultFee = cut(value, vaultPercent); 133 | uint256 pfVaultFee = cut(value, pfVaultPercent); 134 | uint256 tokensToTransfer = value.sub(vaultFee).sub(pfVaultFee); 135 | _spendAllowance(from, spender, value); 136 | 137 | _transfer( 138 | from, 139 | IFractonTokenFactory(_factory).getVaultAddress(), 140 | vaultFee 141 | ); 142 | _transfer( 143 | from, 144 | IFractonTokenFactory(_factory).getPoolFundingVaultAddress(), 145 | pfVaultFee 146 | ); 147 | _transfer(from, to, tokensToTransfer); 148 | } 149 | 150 | return true; 151 | } 152 | 153 | function burnFrom(address from, uint256 value) 154 | external 155 | virtual 156 | override 157 | returns (bool) 158 | { 159 | address spender = _msgSender(); 160 | if (from != spender) { 161 | _spendAllowance(from, spender, value); 162 | } 163 | _burn(from, value); 164 | return true; 165 | } 166 | 167 | function excludeFromFee(address account) public onlyDAO returns (bool) { 168 | _isExcludedFromFee[account] = true; 169 | return true; 170 | } 171 | 172 | function batchExcludeFromFee(address[] memory accounts) 173 | external 174 | onlyDAO 175 | returns (bool) 176 | { 177 | for (uint256 i = 0; i < accounts.length; i++) { 178 | _isExcludedFromFee[accounts[i]] = true; 179 | } 180 | return true; 181 | } 182 | 183 | function includeInFee(address account) external onlyDAO returns (bool) { 184 | _isExcludedFromFee[account] = false; 185 | return true; 186 | } 187 | 188 | function updateFee(uint256 vaultPercent_, uint256 pfVaultPercent_) 189 | external 190 | override 191 | onlyDAO 192 | returns (bool) 193 | { 194 | vaultPercent = vaultPercent_; 195 | pfVaultPercent = pfVaultPercent_; 196 | 197 | emit SetPercent(vaultPercent_, pfVaultPercent_); 198 | return true; 199 | } 200 | 201 | function isExcludedFromFee(address account) 202 | public 203 | view 204 | virtual 205 | override 206 | returns (bool) 207 | { 208 | return _isExcludedFromFee[account]; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /contracts/fundraising/FractonSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import '@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol'; 5 | import '@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol'; 6 | import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; 7 | import '@openzeppelin/contracts/token/ERC721/IERC721.sol'; 8 | import '@openzeppelin/contracts/token/ERC1155/IERC1155.sol'; 9 | import '@openzeppelin/contracts/access/Ownable.sol'; 10 | import '@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol'; 11 | import '@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol'; 12 | 13 | import '../interface/IFractonMiniNFT.sol'; 14 | import '../interface/IFractonFFT.sol'; 15 | import '../interface/IFractonSwap.sol'; 16 | import '../interface/IFractonTokenFactory.sol'; 17 | 18 | contract FractonSwap is 19 | ERC721Holder, 20 | ERC1155Holder, 21 | Ownable, 22 | IFractonSwap, 23 | VRFConsumerBaseV2 24 | { 25 | uint256 public swapRate = 1E21; 26 | uint256 public fftTax = 3E18; 27 | uint256 public nftTax = 3; 28 | address public tokenFactory; 29 | address public vrfRescuer; 30 | 31 | mapping(uint256 => ChainLinkRequest) public chainLinkRequests; 32 | mapping(address => uint256[]) public NFTIds; 33 | mapping(address => address) public NFTtoMiniNFT; 34 | mapping(address => address) public miniNFTtoFFT; 35 | 36 | // Chinlink VRF 37 | VRFCoordinatorV2Interface COORDINATOR; 38 | bytes32 public keyHash; 39 | uint32 public callbackGasLimit = 1000000; 40 | uint32 public numWords = 1; 41 | uint16 public requestConfirmations = 3; 42 | uint64 public subscriptionId; 43 | uint256[] public s_randomWords; 44 | 45 | constructor( 46 | address vrfCoordinator_, 47 | address vrfRescuer_, 48 | bytes32 keyHash_, 49 | uint64 subscriptionId_ 50 | ) VRFConsumerBaseV2(vrfCoordinator_) { 51 | COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator_); 52 | vrfRescuer = vrfRescuer_; 53 | keyHash = keyHash_; 54 | subscriptionId = subscriptionId_; 55 | } 56 | 57 | modifier onlyDAO() { 58 | address dao = IFractonTokenFactory(tokenFactory).getDAOAddress(); 59 | require(msg.sender == dao, 'Fracton: caller is not Fracton DAO'); 60 | _; 61 | } 62 | 63 | modifier onlyFactoryOrOwner() { 64 | require( 65 | msg.sender == tokenFactory || msg.sender == owner(), 66 | 'Invalid Caller' 67 | ); 68 | _; 69 | } 70 | 71 | function updatePoolRelation( 72 | address miniNFT, 73 | address FFT, 74 | address NFT 75 | ) external virtual override onlyFactoryOrOwner returns (bool) { 76 | miniNFTtoFFT[miniNFT] = FFT; 77 | NFTtoMiniNFT[NFT] = miniNFT; 78 | emit UpdatePoolRelation(msg.sender, miniNFT, FFT, NFT); 79 | return true; 80 | } 81 | 82 | function poolClaim(address miniNFTContract, uint256 tokenID) 83 | external 84 | virtual 85 | override 86 | returns (bool) 87 | { 88 | require( 89 | miniNFTtoFFT[miniNFTContract] != address(0), 90 | 'swap: invalid contract' 91 | ); 92 | require(IFractonMiniNFT(miniNFTContract).claimBlindBox(tokenID) > 0); 93 | 94 | emit PoolClaim(msg.sender, miniNFTContract, tokenID); 95 | return true; 96 | } 97 | 98 | function swapMiniNFTtoFFT( 99 | address miniNFTContract, 100 | uint256 tokenID, 101 | uint256 amount 102 | ) external virtual override returns (bool) { 103 | require( 104 | miniNFTtoFFT[miniNFTContract] != address(0), 105 | 'swap: invalid contract' 106 | ); 107 | 108 | uint256 miniNFTBalance = IERC1155(miniNFTContract).balanceOf( 109 | msg.sender, 110 | tokenID 111 | ); 112 | require(miniNFTBalance >= amount, 'swap: balance insufficient'); 113 | 114 | IERC1155(miniNFTContract).safeTransferFrom( 115 | msg.sender, 116 | address(this), 117 | tokenID, 118 | amount, 119 | '' 120 | ); 121 | 122 | require( 123 | IFractonFFT(miniNFTtoFFT[miniNFTContract]).swapmint( 124 | amount * swapRate, 125 | msg.sender 126 | ) 127 | ); 128 | 129 | emit SwapMiniNFTtoFFT(msg.sender, miniNFTContract, tokenID, amount); 130 | return true; 131 | } 132 | 133 | function swapFFTtoMiniNFT(address miniNFTContract, uint256 miniNFTAmount) 134 | external 135 | virtual 136 | override 137 | returns (bool) 138 | { 139 | require( 140 | miniNFTtoFFT[miniNFTContract] != address(0), 141 | 'swap: invalid contract' 142 | ); 143 | require( 144 | IERC1155(miniNFTContract).balanceOf(address(this), 0) >= miniNFTAmount, 145 | 'swap:insufficient miniNFT in pool' 146 | ); 147 | uint256 FFTamount = miniNFTAmount * swapRate; 148 | uint256 taxfee = miniNFTAmount * fftTax; 149 | 150 | require( 151 | IFractonFFT(miniNFTtoFFT[miniNFTContract]).burnFrom(msg.sender, FFTamount) 152 | ); 153 | 154 | require( 155 | IFractonFFT(miniNFTtoFFT[miniNFTContract]).transferFrom( 156 | msg.sender, 157 | IFractonTokenFactory(tokenFactory).getVaultAddress(), 158 | taxfee 159 | ) 160 | ); 161 | IERC1155(miniNFTContract).safeTransferFrom( 162 | address(this), 163 | msg.sender, 164 | 0, 165 | miniNFTAmount, 166 | '' 167 | ); 168 | 169 | emit SwapFFTtoMiniNFT(msg.sender, miniNFTContract, miniNFTAmount); 170 | return true; 171 | } 172 | 173 | function swapMiniNFTtoNFT(address NFTContract) 174 | external 175 | virtual 176 | override 177 | returns (bool) 178 | { 179 | address miniNFTContract = NFTtoMiniNFT[NFTContract]; 180 | require(miniNFTContract != address(0), 'swap: invalid contract'); 181 | require(NFTIds[NFTContract].length > 0, 'swap: no NFT left'); 182 | 183 | uint256 requestId = COORDINATOR.requestRandomWords( 184 | keyHash, 185 | subscriptionId, 186 | requestConfirmations, 187 | callbackGasLimit, 188 | numWords 189 | ); 190 | 191 | chainLinkRequests[requestId] = ChainLinkRequest(msg.sender, NFTContract); 192 | 193 | emit SendChainlinkVRF(requestId, msg.sender, NFTContract); 194 | return true; 195 | } 196 | 197 | function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) 198 | internal 199 | virtual 200 | override 201 | { 202 | address sender = chainLinkRequests[requestId].sender; 203 | 204 | address NFTContract = chainLinkRequests[requestId].nft; 205 | 206 | address miniNFTContract = NFTtoMiniNFT[NFTContract]; 207 | 208 | uint256 NFTNumber = NFTIds[NFTContract].length; 209 | 210 | require(NFTNumber > 0, 'swap: no NFT left'); 211 | 212 | uint256 NFTIndex = randomWords[0] % NFTNumber; 213 | 214 | uint256 NFTID = NFTIds[NFTContract][NFTIndex]; 215 | 216 | NFTIds[NFTContract][NFTIndex] = NFTIds[NFTContract][NFTNumber - 1]; 217 | NFTIds[NFTContract].pop(); 218 | 219 | IERC1155(miniNFTContract).safeTransferFrom( 220 | sender, 221 | address(this), 222 | 0, 223 | 1000, 224 | '' 225 | ); 226 | 227 | IFractonMiniNFT(miniNFTContract).burn(1000); 228 | 229 | IERC1155(miniNFTContract).safeTransferFrom( 230 | sender, 231 | IFractonTokenFactory(tokenFactory).getVaultAddress(), 232 | 0, 233 | nftTax, 234 | '' 235 | ); 236 | 237 | IERC721(NFTContract).transferFrom(address(this), sender, NFTID); 238 | 239 | emit SwapMiniNFTtoNFT(sender, NFTContract, NFTID); 240 | } 241 | 242 | function swapNFTtoMiniNFT( 243 | address NFTContract, 244 | address fromOwner, 245 | uint256 tokenId 246 | ) external virtual override onlyDAO returns (bool) { 247 | address miniNFTContract = NFTtoMiniNFT[NFTContract]; 248 | 249 | require(miniNFTContract != address(0), 'swap: invalid contract'); 250 | 251 | IERC721(NFTContract).safeTransferFrom(fromOwner, address(this), tokenId); 252 | 253 | require(IFractonMiniNFT(miniNFTContract).swapmint(1000, fromOwner)); 254 | 255 | return true; 256 | } 257 | 258 | function withdrawERC20(address project, uint256 amount) 259 | external 260 | onlyDAO 261 | returns (bool) 262 | { 263 | require( 264 | IERC20(project).transfer(msg.sender, amount), 265 | 'swap: withdraw failed' 266 | ); 267 | return true; 268 | } 269 | 270 | function withdrawERC721(address airdropContract, uint256 tokenId) 271 | external 272 | onlyDAO 273 | returns (bool) 274 | { 275 | require( 276 | NFTtoMiniNFT[airdropContract] == address(0), 277 | 'swap: cannot withdraw ProjectNFT' 278 | ); 279 | 280 | IERC721(airdropContract).safeTransferFrom( 281 | address(this), 282 | msg.sender, 283 | tokenId 284 | ); 285 | 286 | return true; 287 | } 288 | 289 | function withdrawERC1155( 290 | address airdropContract, 291 | uint256 tokenId, 292 | uint256 amount 293 | ) external onlyDAO returns (bool) { 294 | require( 295 | miniNFTtoFFT[airdropContract] == address(0), 296 | 'swap: cannot withdraw ProjectNFT' 297 | ); 298 | 299 | IERC1155(airdropContract).safeTransferFrom( 300 | address(this), 301 | msg.sender, 302 | tokenId, 303 | amount, 304 | '' 305 | ); 306 | 307 | return true; 308 | } 309 | 310 | function updateFactory(address factory_) external onlyOwner returns (bool) { 311 | require(tokenFactory == address(0), 'swap: factory has been set'); 312 | require(factory_ != address(0), 'swap: factory can not be 0 address'); 313 | 314 | tokenFactory = factory_; 315 | 316 | emit UpdateFactory(factory_); 317 | return true; 318 | } 319 | 320 | function updateTax(uint256 fftTax_, uint256 nftTax_) 321 | external 322 | onlyDAO 323 | returns (bool) 324 | { 325 | fftTax = fftTax_; 326 | nftTax = nftTax_; 327 | 328 | emit UpdateTax(fftTax_, nftTax_); 329 | return true; 330 | } 331 | 332 | function updateCallbackGasLimit(uint32 gasLimit_) 333 | external 334 | override 335 | onlyDAO 336 | returns (bool) 337 | { 338 | callbackGasLimit = gasLimit_; 339 | return true; 340 | } 341 | 342 | // only used when Chainlink VRF Service is down 343 | function emergencyUpdateVrf(address vrfCoordinator_) external { 344 | require(msg.sender == vrfRescuer, 'swap: invalid caller'); 345 | require(vrfCoordinator_ != address(0), 'swap: invaild coordiantor address'); 346 | 347 | COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator_); 348 | } 349 | 350 | function updateVrfSubscriptionId(uint64 subscriptionId_) 351 | external 352 | override 353 | onlyDAO 354 | returns (bool) 355 | { 356 | subscriptionId = subscriptionId_; 357 | return true; 358 | } 359 | 360 | function onERC721Received( 361 | address operator, 362 | address from, 363 | uint256 tokenId, 364 | bytes calldata data 365 | ) public virtual override returns (bytes4) { 366 | NFTIds[msg.sender].push(tokenId); 367 | return super.onERC721Received(operator, from, tokenId, data); 368 | } 369 | 370 | function numberOfNFT(address NFTContract) external view returns (uint256) { 371 | return NFTIds[NFTContract].length; 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /contracts/dao/FractonVesting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import '@openzeppelin/contracts/access/Ownable.sol'; 5 | import '@openzeppelin/contracts/utils/math/SafeMath.sol'; 6 | import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; 7 | import '../interface/IFractonVesting.sol'; 8 | 9 | contract FractonVesting is IFractonVesting, Context, Ownable { 10 | using SafeMath for uint256; 11 | 12 | // Date-related constants for sanity-checking dates to reject obvious erroneous inputs 13 | // and conversions from seconds to days and years that are more or less leap year-aware. 14 | uint32 private constant _THOUSAND_YEARS_DAYS = 365243; /* See https://www.timeanddate.com/date/durationresult.html?m1=1&d1=1&y1=2000&m2=1&d2=1&y2=3000 */ 15 | uint32 private constant _TEN_YEARS_DAYS = _THOUSAND_YEARS_DAYS / 100; /* Includes leap years (though it doesn't really matter) */ 16 | uint32 private constant _SECONDS_PER_DAY = 24 * 60 * 60; /* 86400 seconds in a day */ 17 | uint32 private constant _JAN_1_2000_SECONDS = 946684800; /* Saturday, January 1, 2000 0:00:00 (GMT) (see https://www.epochconverter.com/) */ 18 | uint32 private constant _JAN_1_2000_DAYS = 19 | _JAN_1_2000_SECONDS / _SECONDS_PER_DAY; 20 | uint32 private constant _JAN_1_3000_DAYS = 21 | _JAN_1_2000_DAYS + _THOUSAND_YEARS_DAYS; 22 | 23 | mapping(address => vestingSchedule) private _vestingSchedules; 24 | mapping(address => tokenGrant) private _tokenGrants; 25 | address[] private _allBeneficiaries; 26 | address public immutable vestingToken; 27 | 28 | constructor(address vestingToken_) { 29 | vestingToken = vestingToken_; 30 | } 31 | 32 | function withdrawTokens(address beneficiary, uint256 amount) 33 | external 34 | override 35 | onlyOwner 36 | { 37 | require(amount > 0, 'amount must be > 0'); 38 | require(IERC20(vestingToken).transfer(beneficiary, amount)); 39 | } 40 | 41 | // ========================================================================= 42 | // === Methods for claiming tokens. 43 | // ========================================================================= 44 | 45 | function claimVestingTokens(address beneficiary) 46 | public 47 | override 48 | onlyOwnerOrSelf(beneficiary) 49 | { 50 | uint256 amount = _getAvailableAmount(beneficiary, 0); 51 | if (amount > 0) { 52 | _tokenGrants[beneficiary].claimedAmount = _tokenGrants[beneficiary] 53 | .claimedAmount 54 | .add(amount); 55 | _deliverTokens(beneficiary, amount); 56 | emit VestingTokensClaimed(beneficiary, amount); 57 | } 58 | } 59 | 60 | function claimVestingTokensForAll() external override onlyOwner { 61 | for (uint256 i = 0; i < _allBeneficiaries.length; i++) { 62 | claimVestingTokens(_allBeneficiaries[i]); 63 | } 64 | } 65 | 66 | function _deliverTokens(address beneficiary, uint256 amount) internal { 67 | require(amount > 0, 'amount must be > 0'); 68 | require( 69 | amount <= IERC20(vestingToken).balanceOf(address(this)), 70 | 'amount exceeded balance' 71 | ); 72 | require( 73 | _tokenGrants[beneficiary].claimedAmount.add(amount) <= 74 | _tokenGrants[beneficiary].amount, 75 | 'claimed amount must be <= grant amount' 76 | ); 77 | 78 | require(IERC20(vestingToken).transfer(beneficiary, amount)); 79 | } 80 | 81 | // ========================================================================= 82 | // === Methods for administratively creating a vesting schedule for an account. 83 | // ========================================================================= 84 | 85 | /** 86 | * @dev This one-time operation permanently establishes a vesting schedule in the given account. 87 | * 88 | * @param cliffDuration = Duration of the cliff, with respect to the grant start day, in days. 89 | * @param duration = Duration of the vesting schedule, with respect to the grant start day, in days. 90 | * @param interval = Number of days between vesting increases. 91 | * @param isRevocable = True if the grant can be revoked (i.e. was a gift) or false if it cannot 92 | * be revoked (i.e. tokens were purchased). 93 | */ 94 | function setVestingSchedule( 95 | address vestingLocation, 96 | uint32 cliffDuration, 97 | uint32 duration, 98 | uint32 interval, 99 | bool isRevocable 100 | ) public override onlyOwner { 101 | // Check for a valid vesting schedule given (disallow absurd values to reject likely bad input). 102 | require( 103 | duration > 0 && 104 | duration <= _TEN_YEARS_DAYS && 105 | cliffDuration < duration && 106 | interval >= 1, 107 | 'invalid vesting schedule' 108 | ); 109 | 110 | // Make sure the duration values are in harmony with interval (both should be an exact multiple of interval). 111 | require( 112 | duration % interval == 0 && cliffDuration % interval == 0, 113 | 'invalid cliff/duration for interval' 114 | ); 115 | 116 | // Create and populate a vesting schedule. 117 | _vestingSchedules[vestingLocation] = vestingSchedule( 118 | isRevocable, 119 | cliffDuration, 120 | duration, 121 | interval 122 | ); 123 | 124 | // Emit the event. 125 | emit VestingScheduleCreated( 126 | vestingLocation, 127 | cliffDuration, 128 | duration, 129 | interval, 130 | isRevocable 131 | ); 132 | } 133 | 134 | // ========================================================================= 135 | // === Token grants (general-purpose) 136 | // === Methods to be used for administratively creating one-off token grants with vesting schedules. 137 | // ========================================================================= 138 | 139 | /** 140 | * @dev Grants tokens to an account. 141 | * 142 | * @param beneficiary = Address to which tokens will be granted. 143 | * @param vestingAmount = The number of tokens subject to vesting. 144 | * @param startDay = Start day of the grant's vesting schedule, in days since the UNIX epoch 145 | * (start of day). The startDay may be given as a date in the future or in the past, going as far 146 | * back as year 2000. 147 | * @param vestingLocation = Account where the vesting schedule is held (must already exist). 148 | */ 149 | function _addGrant( 150 | address beneficiary, 151 | uint256 vestingAmount, 152 | uint32 startDay, 153 | address vestingLocation 154 | ) internal { 155 | // Make sure no prior grant is in effect. 156 | require(!_tokenGrants[beneficiary].isActive, 'grant already exists'); 157 | 158 | // Check for valid vestingAmount 159 | require( 160 | vestingAmount > 0 && 161 | startDay >= _JAN_1_2000_DAYS && 162 | startDay < _JAN_1_3000_DAYS, 163 | 'invalid vesting params' 164 | ); 165 | 166 | // Create and populate a token grant, referencing vesting schedule. 167 | _tokenGrants[beneficiary] = tokenGrant( 168 | true, // isActive 169 | false, // wasRevoked 170 | vestingLocation, // The wallet address where the vesting schedule is kept. 171 | startDay, 172 | vestingAmount, 173 | 0 // claimedAmount 174 | ); 175 | _allBeneficiaries.push(beneficiary); 176 | 177 | // Emit the event. 178 | emit VestingTokensGranted( 179 | beneficiary, 180 | vestingAmount, 181 | startDay, 182 | vestingLocation 183 | ); 184 | } 185 | 186 | /** 187 | * @dev Grants tokens to an address, including a portion that will vest over time 188 | * according to a set vesting schedule. The overall duration and cliff duration of the grant must 189 | * be an even multiple of the vesting interval. 190 | * 191 | * @param beneficiary = Address to which tokens will be granted. 192 | * @param vestingAmount = The number of tokens subject to vesting. 193 | * @param startDay = Start day of the grant's vesting schedule, in days since the UNIX epoch 194 | * (start of day). The startDay may be given as a date in the future or in the past, going as far 195 | * back as year 2000. 196 | * @param duration = Duration of the vesting schedule, with respect to the grant start day, in days. 197 | * @param cliffDuration = Duration of the cliff, with respect to the grant start day, in days. 198 | * @param interval = Number of days between vesting increases. 199 | * @param isRevocable = True if the grant can be revoked (i.e. was a gift) or false if it cannot 200 | * be revoked (i.e. tokens were purchased). 201 | */ 202 | function addGrant( 203 | address beneficiary, 204 | uint256 vestingAmount, 205 | uint32 startDay, 206 | uint32 duration, 207 | uint32 cliffDuration, 208 | uint32 interval, 209 | bool isRevocable 210 | ) public override onlyOwner { 211 | // Make sure no prior vesting schedule has been set. 212 | require(!_tokenGrants[beneficiary].isActive, 'grant already exists'); 213 | 214 | // The vesting schedule is unique to this wallet and so will be stored here, 215 | setVestingSchedule( 216 | beneficiary, 217 | cliffDuration, 218 | duration, 219 | interval, 220 | isRevocable 221 | ); 222 | 223 | // Issue tokens to the beneficiary, using beneficiary's own vesting schedule. 224 | _addGrant(beneficiary, vestingAmount, startDay, beneficiary); 225 | } 226 | 227 | function addGrantWithScheduleAt( 228 | address beneficiary, 229 | uint256 vestingAmount, 230 | uint32 startDay, 231 | address vestingLocation 232 | ) external override onlyOwner { 233 | // Issue tokens to the beneficiary, using custom vestingLocation. 234 | _addGrant(beneficiary, vestingAmount, startDay, vestingLocation); 235 | } 236 | 237 | function addGrantFromToday( 238 | address beneficiary, 239 | uint256 vestingAmount, 240 | uint32 duration, 241 | uint32 cliffDuration, 242 | uint32 interval, 243 | bool isRevocable 244 | ) external override onlyOwner { 245 | addGrant( 246 | beneficiary, 247 | vestingAmount, 248 | today(), 249 | duration, 250 | cliffDuration, 251 | interval, 252 | isRevocable 253 | ); 254 | } 255 | 256 | // ========================================================================= 257 | // === Check vesting. 258 | // ========================================================================= 259 | function today() public view virtual override returns (uint32 dayNumber) { 260 | return uint32(block.timestamp / _SECONDS_PER_DAY); 261 | } 262 | 263 | function _effectiveDay(uint32 onDayOrToday) 264 | internal 265 | view 266 | returns (uint32 dayNumber) 267 | { 268 | return onDayOrToday == 0 ? today() : onDayOrToday; 269 | } 270 | 271 | /** 272 | * @dev Determines the amount of tokens that have not vested in the given account. 273 | * 274 | * The math is: not vested amount = vesting amount * (end date - on date)/(end date - start date) 275 | * 276 | * @param grantHolder = The account to check. 277 | * @param onDayOrToday = The day to check for, in days since the UNIX epoch. Can pass 278 | * the special value 0 to indicate today. 279 | */ 280 | function _getNotVestedAmount(address grantHolder, uint32 onDayOrToday) 281 | internal 282 | view 283 | returns (uint256 amountNotVested) 284 | { 285 | tokenGrant storage grant = _tokenGrants[grantHolder]; 286 | vestingSchedule storage vesting = _vestingSchedules[grant.vestingLocation]; 287 | uint32 onDay = _effectiveDay(onDayOrToday); 288 | 289 | // If there's no schedule, or before the vesting cliff, then the full amount is not vested. 290 | if (!grant.isActive || onDay < grant.startDay + vesting.cliffDuration) { 291 | // None are vested (all are not vested) 292 | return grant.amount - grant.claimedAmount; 293 | } 294 | // If after end of vesting, then the not vested amount is zero (all are vested). 295 | else if (onDay >= grant.startDay + vesting.duration) { 296 | // All are vested (none are not vested) 297 | return uint256(0); 298 | } 299 | // Otherwise a fractional amount is vested. 300 | else { 301 | // Compute the exact number of days vested. 302 | uint32 daysVested = onDay - grant.startDay; 303 | // Adjust result rounding down to take into consideration the interval. 304 | uint32 effectiveDaysVested = (daysVested / vesting.interval) * 305 | vesting.interval; 306 | 307 | // Compute the fraction vested from schedule using 224.32 fixed point math for date range ratio. 308 | // Note: This is safe in 256-bit math because max value of X billion tokens = X*10^27 wei, and 309 | // typical token amounts can fit into 90 bits. Scaling using a 32 bits value results in only 125 310 | // bits before reducing back to 90 bits by dividing. There is plenty of room left, even for token 311 | // amounts many orders of magnitude greater than mere billions. 312 | uint256 vested = grant.amount.mul(effectiveDaysVested).div( 313 | vesting.duration 314 | ); 315 | uint256 result = grant.amount.sub(vested); 316 | 317 | return result; 318 | } 319 | } 320 | 321 | /** 322 | * @dev Computes the amount of funds in the given account which are available for use as of 323 | * the given day, i.e. the claimable amount. 324 | * 325 | * The math is: available amount = totalGrantAmount - notVestedAmount - claimedAmount. 326 | * 327 | * @param grantHolder = The account to check. 328 | * @param onDay = The day to check for, in days since the UNIX epoch. 329 | */ 330 | function _getAvailableAmount(address grantHolder, uint32 onDay) 331 | internal 332 | view 333 | returns (uint256 amountAvailable) 334 | { 335 | tokenGrant storage grant = _tokenGrants[grantHolder]; 336 | return 337 | _getAvailableAmountImpl(grant, _getNotVestedAmount(grantHolder, onDay)); 338 | } 339 | 340 | function _getAvailableAmountImpl( 341 | tokenGrant storage grant, 342 | uint256 notVastedOnDay 343 | ) internal view returns (uint256 amountAvailable) { 344 | uint256 vested = grant.amount.sub(notVastedOnDay); 345 | if (grant.wasRevoked) { 346 | return 0; 347 | } 348 | 349 | uint256 result = vested.sub(grant.claimedAmount); 350 | require( 351 | result <= grant.amount && 352 | grant.claimedAmount.add(result) <= grant.amount && 353 | result <= vested && 354 | vested <= grant.amount 355 | ); 356 | 357 | return result; 358 | } 359 | 360 | /** 361 | * @dev returns all information about the grant's vesting as of the given day 362 | * for the given account. Only callable by the account holder or a contract owner. 363 | * 364 | * @param grantHolder = The address to do this for. 365 | * @param onDayOrToday = The day to check for, in days since the UNIX epoch. Can pass 366 | * the special value 0 to indicate today. 367 | * return = A tuple with the following values: 368 | * amountVested = the amount that is already vested 369 | * amountNotVested = the amount that is not yet vested (equal to vestingAmount - vestedAmount) 370 | * amountOfGrant = the total amount of tokens subject to vesting. 371 | * amountAvailable = the amount of funds in the given account which are available for use as of the given day 372 | * amountClaimed = out of amountVested, the amount that has been already transferred to beneficiary 373 | * vestStartDay = starting day of the grant (in days since the UNIX epoch). 374 | * isActive = true if the vesting schedule is currently active. 375 | * wasRevoked = true if the vesting schedule was revoked. 376 | */ 377 | function getGrantInfo(address grantHolder, uint32 onDayOrToday) 378 | external 379 | view 380 | override 381 | returns ( 382 | uint256 amountVested, 383 | uint256 amountNotVested, 384 | uint256 amountOfGrant, 385 | uint256 amountAvailable, 386 | uint256 amountClaimed, 387 | uint32 vestStartDay, 388 | bool isActive, 389 | bool wasRevoked 390 | ) 391 | { 392 | tokenGrant storage grant = _tokenGrants[grantHolder]; 393 | uint256 notVestedAmount = _getNotVestedAmount(grantHolder, onDayOrToday); 394 | 395 | return ( 396 | grant.amount.sub(notVestedAmount), 397 | notVestedAmount, 398 | grant.amount, 399 | _getAvailableAmountImpl(grant, notVestedAmount), 400 | grant.claimedAmount, 401 | grant.startDay, 402 | grant.isActive, 403 | grant.wasRevoked 404 | ); 405 | } 406 | 407 | function getScheduleAtInfo(address vestingLocation) 408 | public 409 | view 410 | override 411 | returns ( 412 | bool isRevocable, 413 | uint32 vestDuration, 414 | uint32 cliffDuration, 415 | uint32 vestIntervalDays 416 | ) 417 | { 418 | vestingSchedule storage vesting = _vestingSchedules[vestingLocation]; 419 | 420 | return ( 421 | vesting.isRevocable, 422 | vesting.duration, 423 | vesting.cliffDuration, 424 | vesting.interval 425 | ); 426 | } 427 | 428 | function getScheduleInfo(address grantHolder) 429 | external 430 | view 431 | override 432 | returns ( 433 | bool isRevocable, 434 | uint32 vestDuration, 435 | uint32 cliffDuration, 436 | uint32 vestIntervalDays 437 | ) 438 | { 439 | tokenGrant storage grant = _tokenGrants[grantHolder]; 440 | return getScheduleAtInfo(grant.vestingLocation); 441 | } 442 | 443 | // ========================================================================= 444 | // === Grant revocation 445 | // ========================================================================= 446 | 447 | /** 448 | * @dev If the account has a revocable grant, this forces the grant to end immediately. 449 | * After this function is called, getGrantInfo will return incomplete data 450 | * and there will be no possibility to claim non-claimed tokens 451 | * 452 | * @param grantHolder = Address to which tokens will be granted. 453 | */ 454 | function revokeGrant(address grantHolder) external override onlyOwner { 455 | tokenGrant storage grant = _tokenGrants[grantHolder]; 456 | vestingSchedule storage vesting = _vestingSchedules[grant.vestingLocation]; 457 | 458 | // Make sure a vesting schedule has previously been set. 459 | require(grant.isActive, 'not active grant'); 460 | // Make sure it's revocable. 461 | require(vesting.isRevocable, 'inrevocable'); 462 | 463 | // Kill the grant by updating wasRevoked and isActive. 464 | _tokenGrants[grantHolder].wasRevoked = true; 465 | _tokenGrants[grantHolder].isActive = false; 466 | 467 | // Emits the GrantRevoked event. 468 | emit GrantRevoked(grantHolder); 469 | } 470 | 471 | modifier onlyOwnerOrSelf(address account) { 472 | require( 473 | _msgSender() == owner() || _msgSender() == account, 474 | 'onlyOwnerOrSelf' 475 | ); 476 | _; 477 | } 478 | } 479 | --------------------------------------------------------------------------------