Repository: FractonProtocol/FractonV1 Branch: main Commit: 5fb56d5466cb Files: 15 Total size: 50.1 KB Directory structure: gitextract_nau5t6v2/ ├── README.md └── contracts/ ├── dao/ │ ├── Fracton.sol │ └── FractonVesting.sol ├── fundraising/ │ ├── FractonFFT.sol │ ├── FractonMiniNFT.sol │ ├── FractonSwap.sol │ └── FractonTokenFactory.sol ├── interface/ │ ├── IFractonFFT.sol │ ├── IFractonMiniNFT.sol │ ├── IFractonSwap.sol │ ├── IFractonTokenFactory.sol │ └── IFractonVesting.sol ├── library/ │ ├── FractonFFTHelper.sol │ └── FractonMiniNFTHelper.sol └── member/ └── FractonMemberNFT.sol ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # FractonV1 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. 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. Website: https://www.fracton.cool Twitter: https://twitter.com/FractonProtocol Discord: https://discord.gg/qBVrFcEAGV Mirror: https://medium.com/@FractonProtocol Medium: https://medium.com/@FractonProtocol ================================================ FILE: contracts/dao/Fracton.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol'; contract Fracton is ERC20, ERC20Burnable { constructor() ERC20('Fracton', 'FT') { _mint(msg.sender, 1E26); } } ================================================ FILE: contracts/dao/FractonVesting.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import '@openzeppelin/contracts/access/Ownable.sol'; import '@openzeppelin/contracts/utils/math/SafeMath.sol'; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import '../interface/IFractonVesting.sol'; contract FractonVesting is IFractonVesting, Context, Ownable { using SafeMath for uint256; // Date-related constants for sanity-checking dates to reject obvious erroneous inputs // and conversions from seconds to days and years that are more or less leap year-aware. 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 */ uint32 private constant _TEN_YEARS_DAYS = _THOUSAND_YEARS_DAYS / 100; /* Includes leap years (though it doesn't really matter) */ uint32 private constant _SECONDS_PER_DAY = 24 * 60 * 60; /* 86400 seconds in a day */ uint32 private constant _JAN_1_2000_SECONDS = 946684800; /* Saturday, January 1, 2000 0:00:00 (GMT) (see https://www.epochconverter.com/) */ uint32 private constant _JAN_1_2000_DAYS = _JAN_1_2000_SECONDS / _SECONDS_PER_DAY; uint32 private constant _JAN_1_3000_DAYS = _JAN_1_2000_DAYS + _THOUSAND_YEARS_DAYS; mapping(address => vestingSchedule) private _vestingSchedules; mapping(address => tokenGrant) private _tokenGrants; address[] private _allBeneficiaries; address public immutable vestingToken; constructor(address vestingToken_) { vestingToken = vestingToken_; } function withdrawTokens(address beneficiary, uint256 amount) external override onlyOwner { require(amount > 0, 'amount must be > 0'); require(IERC20(vestingToken).transfer(beneficiary, amount)); } // ========================================================================= // === Methods for claiming tokens. // ========================================================================= function claimVestingTokens(address beneficiary) public override onlyOwnerOrSelf(beneficiary) { uint256 amount = _getAvailableAmount(beneficiary, 0); if (amount > 0) { _tokenGrants[beneficiary].claimedAmount = _tokenGrants[beneficiary] .claimedAmount .add(amount); _deliverTokens(beneficiary, amount); emit VestingTokensClaimed(beneficiary, amount); } } function claimVestingTokensForAll() external override onlyOwner { for (uint256 i = 0; i < _allBeneficiaries.length; i++) { claimVestingTokens(_allBeneficiaries[i]); } } function _deliverTokens(address beneficiary, uint256 amount) internal { require(amount > 0, 'amount must be > 0'); require( amount <= IERC20(vestingToken).balanceOf(address(this)), 'amount exceeded balance' ); require( _tokenGrants[beneficiary].claimedAmount.add(amount) <= _tokenGrants[beneficiary].amount, 'claimed amount must be <= grant amount' ); require(IERC20(vestingToken).transfer(beneficiary, amount)); } // ========================================================================= // === Methods for administratively creating a vesting schedule for an account. // ========================================================================= /** * @dev This one-time operation permanently establishes a vesting schedule in the given account. * * @param cliffDuration = Duration of the cliff, with respect to the grant start day, in days. * @param duration = Duration of the vesting schedule, with respect to the grant start day, in days. * @param interval = Number of days between vesting increases. * @param isRevocable = True if the grant can be revoked (i.e. was a gift) or false if it cannot * be revoked (i.e. tokens were purchased). */ function setVestingSchedule( address vestingLocation, uint32 cliffDuration, uint32 duration, uint32 interval, bool isRevocable ) public override onlyOwner { // Check for a valid vesting schedule given (disallow absurd values to reject likely bad input). require( duration > 0 && duration <= _TEN_YEARS_DAYS && cliffDuration < duration && interval >= 1, 'invalid vesting schedule' ); // Make sure the duration values are in harmony with interval (both should be an exact multiple of interval). require( duration % interval == 0 && cliffDuration % interval == 0, 'invalid cliff/duration for interval' ); // Create and populate a vesting schedule. _vestingSchedules[vestingLocation] = vestingSchedule( isRevocable, cliffDuration, duration, interval ); // Emit the event. emit VestingScheduleCreated( vestingLocation, cliffDuration, duration, interval, isRevocable ); } // ========================================================================= // === Token grants (general-purpose) // === Methods to be used for administratively creating one-off token grants with vesting schedules. // ========================================================================= /** * @dev Grants tokens to an account. * * @param beneficiary = Address to which tokens will be granted. * @param vestingAmount = The number of tokens subject to vesting. * @param startDay = Start day of the grant's vesting schedule, in days since the UNIX epoch * (start of day). The startDay may be given as a date in the future or in the past, going as far * back as year 2000. * @param vestingLocation = Account where the vesting schedule is held (must already exist). */ function _addGrant( address beneficiary, uint256 vestingAmount, uint32 startDay, address vestingLocation ) internal { // Make sure no prior grant is in effect. require(!_tokenGrants[beneficiary].isActive, 'grant already exists'); // Check for valid vestingAmount require( vestingAmount > 0 && startDay >= _JAN_1_2000_DAYS && startDay < _JAN_1_3000_DAYS, 'invalid vesting params' ); // Create and populate a token grant, referencing vesting schedule. _tokenGrants[beneficiary] = tokenGrant( true, // isActive false, // wasRevoked vestingLocation, // The wallet address where the vesting schedule is kept. startDay, vestingAmount, 0 // claimedAmount ); _allBeneficiaries.push(beneficiary); // Emit the event. emit VestingTokensGranted( beneficiary, vestingAmount, startDay, vestingLocation ); } /** * @dev Grants tokens to an address, including a portion that will vest over time * according to a set vesting schedule. The overall duration and cliff duration of the grant must * be an even multiple of the vesting interval. * * @param beneficiary = Address to which tokens will be granted. * @param vestingAmount = The number of tokens subject to vesting. * @param startDay = Start day of the grant's vesting schedule, in days since the UNIX epoch * (start of day). The startDay may be given as a date in the future or in the past, going as far * back as year 2000. * @param duration = Duration of the vesting schedule, with respect to the grant start day, in days. * @param cliffDuration = Duration of the cliff, with respect to the grant start day, in days. * @param interval = Number of days between vesting increases. * @param isRevocable = True if the grant can be revoked (i.e. was a gift) or false if it cannot * be revoked (i.e. tokens were purchased). */ function addGrant( address beneficiary, uint256 vestingAmount, uint32 startDay, uint32 duration, uint32 cliffDuration, uint32 interval, bool isRevocable ) public override onlyOwner { // Make sure no prior vesting schedule has been set. require(!_tokenGrants[beneficiary].isActive, 'grant already exists'); // The vesting schedule is unique to this wallet and so will be stored here, setVestingSchedule( beneficiary, cliffDuration, duration, interval, isRevocable ); // Issue tokens to the beneficiary, using beneficiary's own vesting schedule. _addGrant(beneficiary, vestingAmount, startDay, beneficiary); } function addGrantWithScheduleAt( address beneficiary, uint256 vestingAmount, uint32 startDay, address vestingLocation ) external override onlyOwner { // Issue tokens to the beneficiary, using custom vestingLocation. _addGrant(beneficiary, vestingAmount, startDay, vestingLocation); } function addGrantFromToday( address beneficiary, uint256 vestingAmount, uint32 duration, uint32 cliffDuration, uint32 interval, bool isRevocable ) external override onlyOwner { addGrant( beneficiary, vestingAmount, today(), duration, cliffDuration, interval, isRevocable ); } // ========================================================================= // === Check vesting. // ========================================================================= function today() public view virtual override returns (uint32 dayNumber) { return uint32(block.timestamp / _SECONDS_PER_DAY); } function _effectiveDay(uint32 onDayOrToday) internal view returns (uint32 dayNumber) { return onDayOrToday == 0 ? today() : onDayOrToday; } /** * @dev Determines the amount of tokens that have not vested in the given account. * * The math is: not vested amount = vesting amount * (end date - on date)/(end date - start date) * * @param grantHolder = The account to check. * @param onDayOrToday = The day to check for, in days since the UNIX epoch. Can pass * the special value 0 to indicate today. */ function _getNotVestedAmount(address grantHolder, uint32 onDayOrToday) internal view returns (uint256 amountNotVested) { tokenGrant storage grant = _tokenGrants[grantHolder]; vestingSchedule storage vesting = _vestingSchedules[grant.vestingLocation]; uint32 onDay = _effectiveDay(onDayOrToday); // If there's no schedule, or before the vesting cliff, then the full amount is not vested. if (!grant.isActive || onDay < grant.startDay + vesting.cliffDuration) { // None are vested (all are not vested) return grant.amount - grant.claimedAmount; } // If after end of vesting, then the not vested amount is zero (all are vested). else if (onDay >= grant.startDay + vesting.duration) { // All are vested (none are not vested) return uint256(0); } // Otherwise a fractional amount is vested. else { // Compute the exact number of days vested. uint32 daysVested = onDay - grant.startDay; // Adjust result rounding down to take into consideration the interval. uint32 effectiveDaysVested = (daysVested / vesting.interval) * vesting.interval; // Compute the fraction vested from schedule using 224.32 fixed point math for date range ratio. // Note: This is safe in 256-bit math because max value of X billion tokens = X*10^27 wei, and // typical token amounts can fit into 90 bits. Scaling using a 32 bits value results in only 125 // bits before reducing back to 90 bits by dividing. There is plenty of room left, even for token // amounts many orders of magnitude greater than mere billions. uint256 vested = grant.amount.mul(effectiveDaysVested).div( vesting.duration ); uint256 result = grant.amount.sub(vested); return result; } } /** * @dev Computes the amount of funds in the given account which are available for use as of * the given day, i.e. the claimable amount. * * The math is: available amount = totalGrantAmount - notVestedAmount - claimedAmount. * * @param grantHolder = The account to check. * @param onDay = The day to check for, in days since the UNIX epoch. */ function _getAvailableAmount(address grantHolder, uint32 onDay) internal view returns (uint256 amountAvailable) { tokenGrant storage grant = _tokenGrants[grantHolder]; return _getAvailableAmountImpl(grant, _getNotVestedAmount(grantHolder, onDay)); } function _getAvailableAmountImpl( tokenGrant storage grant, uint256 notVastedOnDay ) internal view returns (uint256 amountAvailable) { uint256 vested = grant.amount.sub(notVastedOnDay); if (grant.wasRevoked) { return 0; } uint256 result = vested.sub(grant.claimedAmount); require( result <= grant.amount && grant.claimedAmount.add(result) <= grant.amount && result <= vested && vested <= grant.amount ); return result; } /** * @dev returns all information about the grant's vesting as of the given day * for the given account. Only callable by the account holder or a contract owner. * * @param grantHolder = The address to do this for. * @param onDayOrToday = The day to check for, in days since the UNIX epoch. Can pass * the special value 0 to indicate today. * return = A tuple with the following values: * amountVested = the amount that is already vested * amountNotVested = the amount that is not yet vested (equal to vestingAmount - vestedAmount) * amountOfGrant = the total amount of tokens subject to vesting. * amountAvailable = the amount of funds in the given account which are available for use as of the given day * amountClaimed = out of amountVested, the amount that has been already transferred to beneficiary * vestStartDay = starting day of the grant (in days since the UNIX epoch). * isActive = true if the vesting schedule is currently active. * wasRevoked = true if the vesting schedule was revoked. */ function getGrantInfo(address grantHolder, uint32 onDayOrToday) external view override returns ( uint256 amountVested, uint256 amountNotVested, uint256 amountOfGrant, uint256 amountAvailable, uint256 amountClaimed, uint32 vestStartDay, bool isActive, bool wasRevoked ) { tokenGrant storage grant = _tokenGrants[grantHolder]; uint256 notVestedAmount = _getNotVestedAmount(grantHolder, onDayOrToday); return ( grant.amount.sub(notVestedAmount), notVestedAmount, grant.amount, _getAvailableAmountImpl(grant, notVestedAmount), grant.claimedAmount, grant.startDay, grant.isActive, grant.wasRevoked ); } function getScheduleAtInfo(address vestingLocation) public view override returns ( bool isRevocable, uint32 vestDuration, uint32 cliffDuration, uint32 vestIntervalDays ) { vestingSchedule storage vesting = _vestingSchedules[vestingLocation]; return ( vesting.isRevocable, vesting.duration, vesting.cliffDuration, vesting.interval ); } function getScheduleInfo(address grantHolder) external view override returns ( bool isRevocable, uint32 vestDuration, uint32 cliffDuration, uint32 vestIntervalDays ) { tokenGrant storage grant = _tokenGrants[grantHolder]; return getScheduleAtInfo(grant.vestingLocation); } // ========================================================================= // === Grant revocation // ========================================================================= /** * @dev If the account has a revocable grant, this forces the grant to end immediately. * After this function is called, getGrantInfo will return incomplete data * and there will be no possibility to claim non-claimed tokens * * @param grantHolder = Address to which tokens will be granted. */ function revokeGrant(address grantHolder) external override onlyOwner { tokenGrant storage grant = _tokenGrants[grantHolder]; vestingSchedule storage vesting = _vestingSchedules[grant.vestingLocation]; // Make sure a vesting schedule has previously been set. require(grant.isActive, 'not active grant'); // Make sure it's revocable. require(vesting.isRevocable, 'inrevocable'); // Kill the grant by updating wasRevoked and isActive. _tokenGrants[grantHolder].wasRevoked = true; _tokenGrants[grantHolder].isActive = false; // Emits the GrantRevoked event. emit GrantRevoked(grantHolder); } modifier onlyOwnerOrSelf(address account) { require( _msgSender() == owner() || _msgSender() == account, 'onlyOwnerOrSelf' ); _; } } ================================================ FILE: contracts/fundraising/FractonFFT.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import '@openzeppelin/contracts/utils/math/SafeMath.sol'; import '../interface/IFractonFFT.sol'; import '../interface/IFractonTokenFactory.sol'; contract FractonFFT is ERC20, IFractonFFT { using SafeMath for uint256; mapping(address => bool) private _isExcludedFromFee; address public _factory; uint256 public vaultPercent = 20; uint256 public pfVaultPercent = 0; modifier onlyOwner() { address owner = IFractonTokenFactory(_factory).getowner(); require(msg.sender == owner, 'Fracton: caller is not the owner'); _; } modifier onlyDAO() { address dao = IFractonTokenFactory(_factory).getDAOAddress(); require(msg.sender == dao, 'Fracton: caller is not Fracton DAO'); _; } modifier onlySwap() { address dao = IFractonTokenFactory(_factory).getSwapAddress(); require(msg.sender == dao, 'Fracton: caller is not swap'); _; } constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) { _factory = msg.sender; _isExcludedFromFee[_factory] = true; _isExcludedFromFee[IFractonTokenFactory(_factory).getSwapAddress()] = true; _isExcludedFromFee[ IFractonTokenFactory(_factory).getPoolFundingVaultAddress() ] = true; _isExcludedFromFee[IFractonTokenFactory(_factory).getVaultAddress()] = true; _isExcludedFromFee[address(this)] = true; } function swapmint(uint256 amount, address to) external virtual override onlySwap returns (bool) { _mint(to, amount); return true; } /* math that SafeMath doesn't include */ function ceil(uint256 a, uint256 m) internal pure returns (uint256) { uint256 c = a.add(m); uint256 d = c.sub(1); return d.div(m).mul(m); } function cut(uint256 value, uint256 percent) public pure returns (uint256) { if (percent == 0) { return 0; } else { uint256 roundValue = ceil(value, percent); uint256 cutValue = roundValue.mul(percent).div(10000); return cutValue; } } function transfer(address to, uint256 value) public virtual override(ERC20, IFractonFFT) returns (bool) { address from = _msgSender(); require(value <= balanceOf(from), 'from balance insufficient'); if (isExcludedFromFee(from) || isExcludedFromFee(to)) { _transfer(from, to, value); } else { uint256 vaultFee = cut(value, vaultPercent); uint256 pfVaultFee = cut(value, pfVaultPercent); uint256 tokensToTransfer = value.sub(vaultFee).sub(pfVaultFee); _transfer( from, IFractonTokenFactory(_factory).getVaultAddress(), vaultFee ); _transfer( from, IFractonTokenFactory(_factory).getPoolFundingVaultAddress(), pfVaultFee ); _transfer(from, to, tokensToTransfer); } return true; } function multiTransfer(address[] memory receivers, uint256[] memory amounts) public virtual override { for (uint256 i = 0; i < receivers.length; i++) { transfer(receivers[i], amounts[i]); } } function transferFrom( address from, address to, uint256 value ) public virtual override(ERC20, IFractonFFT) returns (bool) { address spender = _msgSender(); require(value <= balanceOf(from), 'from balance insufficient'); if (isExcludedFromFee(from) || isExcludedFromFee(to)) { _spendAllowance(from, spender, value); _transfer(from, to, value); } else { uint256 vaultFee = cut(value, vaultPercent); uint256 pfVaultFee = cut(value, pfVaultPercent); uint256 tokensToTransfer = value.sub(vaultFee).sub(pfVaultFee); _spendAllowance(from, spender, value); _transfer( from, IFractonTokenFactory(_factory).getVaultAddress(), vaultFee ); _transfer( from, IFractonTokenFactory(_factory).getPoolFundingVaultAddress(), pfVaultFee ); _transfer(from, to, tokensToTransfer); } return true; } function burnFrom(address from, uint256 value) external virtual override returns (bool) { address spender = _msgSender(); if (from != spender) { _spendAllowance(from, spender, value); } _burn(from, value); return true; } function excludeFromFee(address account) public onlyDAO returns (bool) { _isExcludedFromFee[account] = true; return true; } function batchExcludeFromFee(address[] memory accounts) external onlyDAO returns (bool) { for (uint256 i = 0; i < accounts.length; i++) { _isExcludedFromFee[accounts[i]] = true; } return true; } function includeInFee(address account) external onlyDAO returns (bool) { _isExcludedFromFee[account] = false; return true; } function updateFee(uint256 vaultPercent_, uint256 pfVaultPercent_) external override onlyDAO returns (bool) { vaultPercent = vaultPercent_; pfVaultPercent = pfVaultPercent_; emit SetPercent(vaultPercent_, pfVaultPercent_); return true; } function isExcludedFromFee(address account) public view virtual override returns (bool) { return _isExcludedFromFee[account]; } } ================================================ FILE: contracts/fundraising/FractonMiniNFT.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import '@openzeppelin/contracts/token/ERC1155/ERC1155.sol'; import '@openzeppelin/contracts/token/ERC721/IERC721.sol'; import '@openzeppelin/contracts/utils/Counters.sol'; import '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol'; import '../interface/IFractonMiniNFT.sol'; import '../interface/IFractonTokenFactory.sol'; contract FractonMiniNFT is ERC1155URIStorage, IFractonMiniNFT { using Counters for Counters.Counter; Counters.Counter private _tokenIds; //counter of round. 0 reserves for people's NFT bool public saleIsActive; //is open for blindbox mint address private _factory; //factory deployer address uint256 public blindBoxPrice = 1E17; //blindbox price uint256 public constant ROUND_CAP = 1000; mapping(uint256 => bool) public roundSucceed; //is round succeed for users' claiming into people's NFT mapping(uint256 => uint256) private _totalAmount; modifier onlyDAO() { address dao = IFractonTokenFactory(_factory).getDAOAddress(); require(msg.sender == dao, 'Fracton: caller is not Fracton DAO'); _; } modifier onlyOwner() { address owner = IFractonTokenFactory(_factory).getowner(); require(msg.sender == owner, 'Fracton: caller is not the owner'); _; } /** * @dev Total amount of tokens in with a given id. */ constructor(string memory uri_) ERC1155(uri_) { _factory = msg.sender; } receive() external payable {} function startNewRound(uint256 sellingPrice) external override onlyDAO returns (bool) { require(!saleIsActive, 'Fracton: active sale exists'); _tokenIds.increment(); saleIsActive = true; blindBoxPrice = sellingPrice; emit StartNewRound(block.number, sellingPrice); return true; } function closeRound() external override onlyDAO returns (bool) { saleIsActive = false; emit CloseRound(block.number); return true; } function mintBlindBox(uint256 amount) external payable override returns (uint256) { uint256 round = _tokenIds.current(); require(saleIsActive, 'Fracton: sale not active'); require( blindBoxPrice * amount <= msg.value, 'Fracton: Ether value not enough' ); _totalAmount[round] += amount; if (totalSupply(round) >= ROUND_CAP) { saleIsActive = false; } _mint(msg.sender, round, amount, ''); return amount; } function claimBlindBox(uint256 tokenID) external override returns (uint256) { require(roundSucceed[tokenID], 'Fracton: round is not succeed'); uint256 amount = balanceOf(msg.sender, tokenID); require(amount > 0, 'Fracton: no blindbox to claim'); _totalAmount[tokenID] -= amount; _totalAmount[0] += amount; _burn(msg.sender, tokenID, amount); _mint(msg.sender, 0, amount, ''); emit ClaimBlindBox(msg.sender, tokenID, amount); return amount; } // Withdraw fundrasing ethers for purchasing NFT function withdrawEther() external override onlyDAO returns (bool) { uint256 amount = address(this).balance; address dao = msg.sender; payable(dao).transfer(amount); emit WithdrawEther(msg.sender, amount); return true; } function updateDefaultURI(string memory defaultURI) external onlyOwner { _setURI(defaultURI); } function updateTokenURI(uint256 tokenId, string memory tokenURI) external onlyOwner { _setURI(tokenId, tokenURI); } function updateRoundSucceed(uint256 round) external override onlyDAO returns (bool) { require(_totalAmount[round] >= ROUND_CAP, 'Fracton: Not achieve yet'); roundSucceed[round] = true; emit UpdateRoundSucceed(round, block.number); return roundSucceed[round]; } function updateBlindBoxPrice(uint256 newPrice) external override onlyDAO returns (bool) { blindBoxPrice = newPrice; emit UpdateBlindBoxPrice(newPrice); return true; } function totalSupply(uint256 id) public view override returns (uint256) { return _totalAmount[id]; } function burn(uint256 amount) external { _totalAmount[0] -= amount; _burn(msg.sender, 0, amount); } function currentRound() public view returns (uint256) { return _tokenIds.current(); } function swapmint(uint256 amount, address to) external virtual override returns (bool) { require( msg.sender == IFractonTokenFactory(_factory).getSwapAddress(), 'Fracton: caller is not swap' ); _totalAmount[0] += amount; _mint(to, 0, amount, ''); return true; } } ================================================ FILE: contracts/fundraising/FractonSwap.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import '@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol'; import '@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol'; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import '@openzeppelin/contracts/token/ERC721/IERC721.sol'; import '@openzeppelin/contracts/token/ERC1155/IERC1155.sol'; import '@openzeppelin/contracts/access/Ownable.sol'; import '@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol'; import '@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol'; import '../interface/IFractonMiniNFT.sol'; import '../interface/IFractonFFT.sol'; import '../interface/IFractonSwap.sol'; import '../interface/IFractonTokenFactory.sol'; contract FractonSwap is ERC721Holder, ERC1155Holder, Ownable, IFractonSwap, VRFConsumerBaseV2 { uint256 public swapRate = 1E21; uint256 public fftTax = 3E18; uint256 public nftTax = 3; address public tokenFactory; address public vrfRescuer; mapping(uint256 => ChainLinkRequest) public chainLinkRequests; mapping(address => uint256[]) public NFTIds; mapping(address => address) public NFTtoMiniNFT; mapping(address => address) public miniNFTtoFFT; // Chinlink VRF VRFCoordinatorV2Interface COORDINATOR; bytes32 public keyHash; uint32 public callbackGasLimit = 1000000; uint32 public numWords = 1; uint16 public requestConfirmations = 3; uint64 public subscriptionId; uint256[] public s_randomWords; constructor( address vrfCoordinator_, address vrfRescuer_, bytes32 keyHash_, uint64 subscriptionId_ ) VRFConsumerBaseV2(vrfCoordinator_) { COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator_); vrfRescuer = vrfRescuer_; keyHash = keyHash_; subscriptionId = subscriptionId_; } modifier onlyDAO() { address dao = IFractonTokenFactory(tokenFactory).getDAOAddress(); require(msg.sender == dao, 'Fracton: caller is not Fracton DAO'); _; } modifier onlyFactoryOrOwner() { require( msg.sender == tokenFactory || msg.sender == owner(), 'Invalid Caller' ); _; } function updatePoolRelation( address miniNFT, address FFT, address NFT ) external virtual override onlyFactoryOrOwner returns (bool) { miniNFTtoFFT[miniNFT] = FFT; NFTtoMiniNFT[NFT] = miniNFT; emit UpdatePoolRelation(msg.sender, miniNFT, FFT, NFT); return true; } function poolClaim(address miniNFTContract, uint256 tokenID) external virtual override returns (bool) { require( miniNFTtoFFT[miniNFTContract] != address(0), 'swap: invalid contract' ); require(IFractonMiniNFT(miniNFTContract).claimBlindBox(tokenID) > 0); emit PoolClaim(msg.sender, miniNFTContract, tokenID); return true; } function swapMiniNFTtoFFT( address miniNFTContract, uint256 tokenID, uint256 amount ) external virtual override returns (bool) { require( miniNFTtoFFT[miniNFTContract] != address(0), 'swap: invalid contract' ); uint256 miniNFTBalance = IERC1155(miniNFTContract).balanceOf( msg.sender, tokenID ); require(miniNFTBalance >= amount, 'swap: balance insufficient'); IERC1155(miniNFTContract).safeTransferFrom( msg.sender, address(this), tokenID, amount, '' ); require( IFractonFFT(miniNFTtoFFT[miniNFTContract]).swapmint( amount * swapRate, msg.sender ) ); emit SwapMiniNFTtoFFT(msg.sender, miniNFTContract, tokenID, amount); return true; } function swapFFTtoMiniNFT(address miniNFTContract, uint256 miniNFTAmount) external virtual override returns (bool) { require( miniNFTtoFFT[miniNFTContract] != address(0), 'swap: invalid contract' ); require( IERC1155(miniNFTContract).balanceOf(address(this), 0) >= miniNFTAmount, 'swap:insufficient miniNFT in pool' ); uint256 FFTamount = miniNFTAmount * swapRate; uint256 taxfee = miniNFTAmount * fftTax; require( IFractonFFT(miniNFTtoFFT[miniNFTContract]).burnFrom(msg.sender, FFTamount) ); require( IFractonFFT(miniNFTtoFFT[miniNFTContract]).transferFrom( msg.sender, IFractonTokenFactory(tokenFactory).getVaultAddress(), taxfee ) ); IERC1155(miniNFTContract).safeTransferFrom( address(this), msg.sender, 0, miniNFTAmount, '' ); emit SwapFFTtoMiniNFT(msg.sender, miniNFTContract, miniNFTAmount); return true; } function swapMiniNFTtoNFT(address NFTContract) external virtual override returns (bool) { address miniNFTContract = NFTtoMiniNFT[NFTContract]; require(miniNFTContract != address(0), 'swap: invalid contract'); require(NFTIds[NFTContract].length > 0, 'swap: no NFT left'); uint256 requestId = COORDINATOR.requestRandomWords( keyHash, subscriptionId, requestConfirmations, callbackGasLimit, numWords ); chainLinkRequests[requestId] = ChainLinkRequest(msg.sender, NFTContract); emit SendChainlinkVRF(requestId, msg.sender, NFTContract); return true; } function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual override { address sender = chainLinkRequests[requestId].sender; address NFTContract = chainLinkRequests[requestId].nft; address miniNFTContract = NFTtoMiniNFT[NFTContract]; uint256 NFTNumber = NFTIds[NFTContract].length; require(NFTNumber > 0, 'swap: no NFT left'); uint256 NFTIndex = randomWords[0] % NFTNumber; uint256 NFTID = NFTIds[NFTContract][NFTIndex]; NFTIds[NFTContract][NFTIndex] = NFTIds[NFTContract][NFTNumber - 1]; NFTIds[NFTContract].pop(); IERC1155(miniNFTContract).safeTransferFrom( sender, address(this), 0, 1000, '' ); IFractonMiniNFT(miniNFTContract).burn(1000); IERC1155(miniNFTContract).safeTransferFrom( sender, IFractonTokenFactory(tokenFactory).getVaultAddress(), 0, nftTax, '' ); IERC721(NFTContract).transferFrom(address(this), sender, NFTID); emit SwapMiniNFTtoNFT(sender, NFTContract, NFTID); } function swapNFTtoMiniNFT( address NFTContract, address fromOwner, uint256 tokenId ) external virtual override onlyDAO returns (bool) { address miniNFTContract = NFTtoMiniNFT[NFTContract]; require(miniNFTContract != address(0), 'swap: invalid contract'); IERC721(NFTContract).safeTransferFrom(fromOwner, address(this), tokenId); require(IFractonMiniNFT(miniNFTContract).swapmint(1000, fromOwner)); return true; } function withdrawERC20(address project, uint256 amount) external onlyDAO returns (bool) { require( IERC20(project).transfer(msg.sender, amount), 'swap: withdraw failed' ); return true; } function withdrawERC721(address airdropContract, uint256 tokenId) external onlyDAO returns (bool) { require( NFTtoMiniNFT[airdropContract] == address(0), 'swap: cannot withdraw ProjectNFT' ); IERC721(airdropContract).safeTransferFrom( address(this), msg.sender, tokenId ); return true; } function withdrawERC1155( address airdropContract, uint256 tokenId, uint256 amount ) external onlyDAO returns (bool) { require( miniNFTtoFFT[airdropContract] == address(0), 'swap: cannot withdraw ProjectNFT' ); IERC1155(airdropContract).safeTransferFrom( address(this), msg.sender, tokenId, amount, '' ); return true; } function updateFactory(address factory_) external onlyOwner returns (bool) { require(tokenFactory == address(0), 'swap: factory has been set'); require(factory_ != address(0), 'swap: factory can not be 0 address'); tokenFactory = factory_; emit UpdateFactory(factory_); return true; } function updateTax(uint256 fftTax_, uint256 nftTax_) external onlyDAO returns (bool) { fftTax = fftTax_; nftTax = nftTax_; emit UpdateTax(fftTax_, nftTax_); return true; } function updateCallbackGasLimit(uint32 gasLimit_) external override onlyDAO returns (bool) { callbackGasLimit = gasLimit_; return true; } // only used when Chainlink VRF Service is down function emergencyUpdateVrf(address vrfCoordinator_) external { require(msg.sender == vrfRescuer, 'swap: invalid caller'); require(vrfCoordinator_ != address(0), 'swap: invaild coordiantor address'); COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator_); } function updateVrfSubscriptionId(uint64 subscriptionId_) external override onlyDAO returns (bool) { subscriptionId = subscriptionId_; return true; } function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) public virtual override returns (bytes4) { NFTIds[msg.sender].push(tokenId); return super.onERC721Received(operator, from, tokenId, data); } function numberOfNFT(address NFTContract) external view returns (uint256) { return NFTIds[NFTContract].length; } } ================================================ FILE: contracts/fundraising/FractonTokenFactory.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import '@openzeppelin/contracts/utils/Create2.sol'; import '../library/FractonMiniNFTHelper.sol'; import '../library/FractonFFTHelper.sol'; import '../interface/IFractonTokenFactory.sol'; import '../interface/IFractonSwap.sol'; contract FractonTokenFactory is IFractonTokenFactory { address private _owner; address private _FractonGovernor; address public FractonSwap; address private _FractonVault; address private _FractonPFVault; //poolfundingvault address public pendingVault; address public pendingPFVault; mapping(address => address) public projectToMiniNFT; mapping(address => address) public projectToFFT; constructor( address daoAddress, address swapAddress, address vaultAddress, address PFvaultAddress ) { _owner = msg.sender; _FractonGovernor = daoAddress; _FractonVault = vaultAddress; _FractonPFVault = PFvaultAddress; FractonSwap = swapAddress; pendingVault = _FractonVault; pendingPFVault = _FractonPFVault; } modifier onlyFactoryOwner() { require(msg.sender == _owner, 'Fracton: invalid caller'); _; } modifier onlyDao() { require(msg.sender == _FractonGovernor, 'Fracton: caller is not dao'); _; } function createCollectionPair( address projectAddress, bytes32 salt, string memory miniNFTBaseUri, string memory name, string memory symbol ) external onlyFactoryOwner returns (address, address) { require( projectToMiniNFT[projectAddress] == address(0) && projectToFFT[projectAddress] == address(0), 'Already exist.' ); address newMiniNFTContract = Create2.deploy( 0, salt, FractonMiniNFTHelper.getBytecode(miniNFTBaseUri) ); require(newMiniNFTContract != address(0), 'Fracton: deploy MiniNFT Failed'); address newFFTContract = Create2.deploy( 0, salt, FractonFFTHelper.getBytecode(name, symbol) ); require(newFFTContract != address(0), 'Fracton: deploy FFT Failed'); projectToMiniNFT[projectAddress] = newMiniNFTContract; projectToFFT[projectAddress] = newFFTContract; require( IFractonSwap(FractonSwap).updatePoolRelation( newMiniNFTContract, newFFTContract, projectAddress ) ); return (newMiniNFTContract, newFFTContract); } function updateDao(address daoAddress) external onlyDao returns (bool) { _FractonGovernor = daoAddress; return true; } function signDaoReq() external onlyFactoryOwner returns (bool) { _FractonVault = pendingVault; _FractonPFVault = pendingPFVault; return true; } function updateVault(address pendingVault_) external onlyDao returns (bool) { pendingVault = pendingVault_; return true; } function updatePFVault(address pendingPFVault_) external onlyDao returns (bool) { pendingPFVault = pendingPFVault_; return true; } function getowner() external view override returns (address) { return _owner; } function getDAOAddress() external view override returns (address) { return _FractonGovernor; } function getSwapAddress() external view override returns (address) { return FractonSwap; } function getVaultAddress() external view override returns (address) { return _FractonVault; } function getPoolFundingVaultAddress() external view override returns (address) { return _FractonPFVault; } } ================================================ FILE: contracts/interface/IFractonFFT.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; interface IFractonFFT is IERC20 { event SetPercent(uint256 vaultPercent, uint256 pfVaultPercent); function swapmint(uint256 amount, address to) external returns (bool); function transfer(address to, uint256 value) external returns (bool); function multiTransfer(address[] memory receivers, uint256[] memory amounts) external; function transferFrom( address from, address to, uint256 value ) external returns (bool); function burnFrom(address from, uint256 value) external returns (bool); function isExcludedFromFee(address account) external view returns (bool); function updateFee(uint256 vaultPercent_, uint256 pfVaultPercent_) external returns (bool); } ================================================ FILE: contracts/interface/IFractonMiniNFT.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; interface IFractonMiniNFT { event StartNewRound(uint256 blockNumber, uint256 sellingPrice); event CloseRound(uint256 blockNumber); event ClaimBlindBox(address owner, uint256 tokenID, uint256 amount); event WithdrawEther(address caller, uint256 amount); event UpdateRoundSucceed(uint256 round, uint256 blockNumber); event UpdateBlindBoxPrice(uint256 price); function startNewRound(uint256 sellingPrice) external returns (bool); function closeRound() external returns (bool); function mintBlindBox(uint256 amount) external payable returns (uint256); function claimBlindBox(uint256 tokenID) external returns (uint256); function withdrawEther() external returns (bool); function updateRoundSucceed(uint256 round) external returns (bool); function updateBlindBoxPrice(uint256 BBoxPrice) external returns (bool); function totalSupply(uint256 id) external view returns (uint256); function burn(uint256 amount) external; function swapmint(uint256 amount, address to) external returns (bool); } ================================================ FILE: contracts/interface/IFractonSwap.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; interface IFractonSwap { event UpdatePoolRelation( address editor, address miniNFT, address FFT, address NFT ); event PoolClaim(address owner, address miniNFTcontract, uint256 tokenID); event SwapMiniNFTtoFFT( address owner, address miniNFTcontract, uint256 tokenID, uint256 miniNFTAmount ); event SwapFFTtoMiniNFT( address owner, address miniNFTcontract, uint256 miniNFTAmount ); event SendChainlinkVRF( uint256 requestId, address sender, address NFTContract ); event SwapMiniNFTtoNFT(address owner, address NFTContract, uint256 NFTID); event UpdateFactory(address factory); event UpdateTax(uint256 fftTax, uint256 nftTax); struct ChainLinkRequest { address sender; address nft; } function updatePoolRelation( address miniNFT, address FFT, address NFT ) external returns (bool); function poolClaim(address miniNFTcontract, uint256 tokenID) external returns (bool); function swapMiniNFTtoFFT( address miniNFTcontract, uint256 tokenID, uint256 amount ) external returns (bool); function swapFFTtoMiniNFT(address miniNFTcontract, uint256 miniNFTamount) external returns (bool); function swapMiniNFTtoNFT(address NFTContract) external returns (bool); function swapNFTtoMiniNFT( address NFTContract, address fromOwner, uint256 tokenId ) external returns (bool); function updateCallbackGasLimit(uint32 gasLimit_) external returns (bool); function updateVrfSubscriptionId(uint64 subscriptionId_) external returns (bool); } ================================================ FILE: contracts/interface/IFractonTokenFactory.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; interface IFractonTokenFactory { function getowner() external view returns (address); function getDAOAddress() external view returns (address); function getVaultAddress() external view returns (address); function getSwapAddress() external view returns (address); function getPoolFundingVaultAddress() external view returns (address); } ================================================ FILE: contracts/interface/IFractonVesting.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; interface IFractonVesting { event VestingScheduleCreated( address indexed vestingLocation, uint32 cliffDuration, uint32 duration, uint32 interval, bool isRevocable ); event VestingTokensGranted( address indexed beneficiary, uint256 vestingAmount, uint32 startDay, address vestingLocation ); event VestingTokensClaimed(address indexed beneficiary, uint256 amount); event GrantRevoked(address indexed grantHolder); struct vestingSchedule { bool isRevocable; /* true if the vesting option is revocable (a gift), false if irrevocable (purchased) */ uint32 cliffDuration; /* Duration of the cliff, with respect to the grant start day, in days. */ uint32 duration; /* Duration of the vesting schedule, with respect to the grant start day, in days. */ uint32 interval; /* Duration in days of the vesting interval. */ } struct tokenGrant { bool isActive; /* true if this vesting entry is active and in-effect entry. */ bool wasRevoked; /* true if this vesting schedule was revoked. */ address vestingLocation; /* Address of wallet that is holding the vesting schedule. */ uint32 startDay; /* Start day of the grant, in days since the UNIX epoch (start of day). */ uint256 amount; /* Total number of tokens that vest. */ uint256 claimedAmount; /* Out of vested amount, the amount that has been already transferred to beneficiary */ } function withdrawTokens(address beneficiary, uint256 amount) external; // ========================================================================= // === Methods for claiming tokens. // ========================================================================= function claimVestingTokens(address beneficiary) external; function claimVestingTokensForAll() external; // ========================================================================= // === Methods for administratively creating a vesting schedule for an account. // ========================================================================= function setVestingSchedule( address vestingLocation, uint32 cliffDuration, uint32 duration, uint32 interval, bool isRevocable ) external; // ========================================================================= // === Token grants (general-purpose) // === Methods to be used for administratively creating one-off token grants with vesting schedules. // ========================================================================= function addGrant( address beneficiary, uint256 vestingAmount, uint32 startDay, uint32 duration, uint32 cliffDuration, uint32 interval, bool isRevocable ) external; function addGrantWithScheduleAt( address beneficiary, uint256 vestingAmount, uint32 startDay, address vestingLocation ) external; function addGrantFromToday( address beneficiary, uint256 vestingAmount, uint32 duration, uint32 cliffDuration, uint32 interval, bool isRevocable ) external; // ========================================================================= // === Check vesting. // ========================================================================= function today() external view returns (uint32 dayNumber); function getGrantInfo(address grantHolder, uint32 onDayOrToday) external view returns ( uint256 amountVested, uint256 amountNotVested, uint256 amountOfGrant, uint256 amountAvailable, uint256 amountClaimed, uint32 vestStartDay, bool isActive, bool wasRevoked ); function getScheduleAtInfo(address vestingLocation) external view returns ( bool isRevocable, uint32 vestDuration, uint32 cliffDuration, uint32 vestIntervalDays ); function getScheduleInfo(address grantHolder) external view returns ( bool isRevocable, uint32 vestDuration, uint32 cliffDuration, uint32 vestIntervalDays ); // ========================================================================= // === Grant revocation // ========================================================================= function revokeGrant(address grantHolder) external; } ================================================ FILE: contracts/library/FractonFFTHelper.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import '../fundraising/FractonFFT.sol'; library FractonFFTHelper { function getBytecode(string memory name, string memory symbol) public pure returns (bytes memory) { bytes memory bytecode = type(FractonFFT).creationCode; return abi.encodePacked(bytecode, abi.encode(name, symbol)); } } ================================================ FILE: contracts/library/FractonMiniNFTHelper.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import '../fundraising/FractonMiniNFT.sol'; library FractonMiniNFTHelper { function getBytecode(string memory uri) public pure returns (bytes memory) { bytes memory bytecode = type(FractonMiniNFT).creationCode; return abi.encodePacked(bytecode, abi.encode(uri)); } } ================================================ FILE: contracts/member/FractonMemberNFT.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; import '@openzeppelin/contracts/access/Ownable.sol'; import '@openzeppelin/contracts/utils/Counters.sol'; contract FractonMemberNFT is ERC721, Ownable { using Counters for Counters.Counter; string public defaulturi = 'https://ipfs.io/ipfs/bafkreicf36h66xrhkso2wytsp5wuzkwons6jnnw47yvz6b5nogxi6guymm'; string private _baseuri = ''; Counters.Counter private _tokenIdCounter; constructor() ERC721('Non-Fungible Crew', 'NFCrew') {} function safeMint(address to) public onlyOwner { uint256 tokenId = _tokenIdCounter.current(); _tokenIdCounter.increment(); _safeMint(to, tokenId); } function batchSafeMint(address to, uint256 amount) external onlyOwner { uint256 tokenId; for (uint256 i = 0; i < amount; i++) { tokenId = _tokenIdCounter.current(); _tokenIdCounter.increment(); _safeMint(to, tokenId); } } function _baseURI() internal view override returns (string memory) { return _baseuri; } function setBaseURI(string memory uri) external onlyOwner { _baseuri = uri; } function tokenURI(uint256 tokenId) public view override returns (string memory) { require( _exists(tokenId), 'ERC721URIStorage: URI query for nonexistent token' ); string memory base = _baseURI(); // If there is no base URI, return the default token URI. if (bytes(base).length == 0) { return defaulturi; } return string(abi.encodePacked(base, Strings.toString(tokenId))); } }