diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..88b9fa0
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,67 @@
+name: Tests
+
+on:
+ pull_request:
+ branches:
+ - main
+ - develop
+
+jobs:
+ test-contracts:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [18.x]
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node-version }}
+ submodules: recursive
+
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
+ with:
+ version: nightly
+
+ - name: Install Dependencies
+ run: yarn --ignore-scripts
+
+ - name: Audit
+ run: yarn audit --groups dependencies
+
+ - name: Compile
+ run: yarn compile
+
+ - name: Run Test
+ run: yarn test
+
+ test-ui:
+ runs-on: ubuntu-latest
+
+ defaults:
+ run:
+ working-directory: ui
+
+ strategy:
+ matrix:
+ node-version: [18.x]
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Install Dependencies
+ run: yarn --ignore-scripts
+
+ - name: Audit
+ run: yarn audit --groups dependencies
+
+ - name: Build
+ run: yarn build
\ No newline at end of file
diff --git a/.husky/lint-staged.config.json b/.husky/lint-staged.config.json
new file mode 100644
index 0000000..57227fc
--- /dev/null
+++ b/.husky/lint-staged.config.json
@@ -0,0 +1,3 @@
+{
+ "*.{js,json,sol,ts}": "prettier --write"
+}
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 0000000..3b432df
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx lint-staged --config .husky/lint-staged.config.json
diff --git a/.husky/pre-push b/.husky/pre-push
new file mode 100755
index 0000000..bfc028b
--- /dev/null
+++ b/.husky/pre-push
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+yarn test
diff --git a/.prettierrc b/.prettierrc
index f3136b7..459d666 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -8,7 +8,7 @@
{
"files": "*.sol",
"options": {
- "printWidth": 80,
+ "printWidth": 120,
"tabWidth": 4,
"singleQuote": false,
"bracketSpacing": false
diff --git a/contracts/FleekAccessControl.sol b/contracts/FleekAccessControl.sol
index 9f18772..72a5e18 100644
--- a/contracts/FleekAccessControl.sol
+++ b/contracts/FleekAccessControl.sol
@@ -1,60 +1,45 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8.7;
-
-import "@openzeppelin/contracts/access/AccessControl.sol";
-
-abstract contract FleekAccessControl is AccessControl {
- bytes32 public constant COLLECTION_OWNER_ROLE =
- keccak256("COLLECTION_OWNER_ROLE");
- bytes32 public constant COLLECTION_CONTROLLER_ROLE =
- keccak256("COLLECTION_CONTROLLER_ROLE");
-
- constructor() {
- _setRoleAdmin(COLLECTION_OWNER_ROLE, DEFAULT_ADMIN_ROLE);
- _grantRole(COLLECTION_OWNER_ROLE, msg.sender);
- }
-
- modifier requireCollectionOwner() {
- require(
- hasRole(COLLECTION_OWNER_ROLE, msg.sender),
- "FleekAccessControl: must have collection owner role"
- );
- _;
- }
-
- modifier requireCollectionController() {
- require(
- hasRole(COLLECTION_OWNER_ROLE, msg.sender) ||
- hasRole(COLLECTION_CONTROLLER_ROLE, msg.sender),
- "FleekAccessControl: must have collection controller role"
- );
- _;
- }
-
- modifier requireTokenController(uint256 tokenId) {
- require(
- hasRole(_tokenRole(tokenId, "CONTROLLER"), msg.sender),
- "FleekAccessControl: must have token role"
- );
- _;
- }
-
- function isTokenController(
- uint256 tokenId,
- address account
- ) public view returns (bool) {
- return hasRole(_tokenRole(tokenId, "CONTROLLER"), account);
- }
-
- function _tokenRole(
- uint256 tokenId,
- string memory role
- ) internal pure returns (bytes32) {
- return keccak256(abi.encodePacked("TOKEN_", role, tokenId));
- }
-
- function _clearTokenControllers(uint256 tokenId) internal {
- // TODO: Remove token controllers from AccessControl
- }
-}
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.7;
+
+import "@openzeppelin/contracts/access/AccessControl.sol";
+
+abstract contract FleekAccessControl is AccessControl {
+ bytes32 public constant COLLECTION_OWNER_ROLE = keccak256("COLLECTION_OWNER_ROLE");
+ bytes32 public constant COLLECTION_CONTROLLER_ROLE = keccak256("COLLECTION_CONTROLLER_ROLE");
+
+ constructor() {
+ _setRoleAdmin(COLLECTION_OWNER_ROLE, DEFAULT_ADMIN_ROLE);
+ _grantRole(COLLECTION_OWNER_ROLE, msg.sender);
+ }
+
+ modifier requireCollectionOwner() {
+ require(hasRole(COLLECTION_OWNER_ROLE, msg.sender), "FleekAccessControl: must have collection owner role");
+ _;
+ }
+
+ modifier requireCollectionController() {
+ require(
+ hasRole(COLLECTION_OWNER_ROLE, msg.sender) || hasRole(COLLECTION_CONTROLLER_ROLE, msg.sender),
+ "FleekAccessControl: must have collection controller role"
+ );
+ _;
+ }
+
+ modifier requireTokenController(uint256 tokenId) {
+ require(hasRole(_tokenRole(tokenId, "CONTROLLER"), msg.sender), "FleekAccessControl: must have token role");
+ _;
+ }
+
+ function isTokenController(uint256 tokenId, address account) public view returns (bool) {
+ return hasRole(_tokenRole(tokenId, "CONTROLLER"), account);
+ }
+
+ function _tokenRole(uint256 tokenId, string memory role) internal pure returns (bytes32) {
+ return keccak256(abi.encodePacked("TOKEN_", role, tokenId));
+ }
+
+ function _clearTokenControllers(uint256 tokenId) internal {
+ // TODO: Remove token controllers from AccessControl
+ }
+}
diff --git a/contracts/FleekERC721.sol b/contracts/FleekERC721.sol
index a65e13d..8a48a9f 100644
--- a/contracts/FleekERC721.sol
+++ b/contracts/FleekERC721.sol
@@ -41,16 +41,10 @@ contract FleekERC721 is ERC721, FleekAccessControl {
Counters.Counter private _tokenIds;
mapping(uint256 => App) private _apps;
- constructor(
- string memory _name,
- string memory _symbol
- ) ERC721(_name, _symbol) {}
+ constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}
modifier requireTokenOwner(uint256 tokenId) {
- require(
- msg.sender == ownerOf(tokenId),
- "FleekERC721: must be token owner"
- );
+ require(msg.sender == ownerOf(tokenId), "FleekERC721: must be token owner");
_;
}
@@ -92,7 +86,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
return tokenId;
}
- /**
+ /**
* @dev Returns the token metadata associated with the `tokenId`.
*
* Returns a based64 encoded string value of the URI.
@@ -102,13 +96,12 @@ contract FleekERC721 is ERC721, FleekAccessControl {
* - the tokenId must be minted and valid.
*
*/
- function tokenURI(
- uint256 tokenId
- ) public view virtual override returns (string memory) {
+ function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireMinted(tokenId);
address owner = ownerOf(tokenId);
App storage app = _apps[tokenId];
+ // prettier-ignore
bytes memory dataURI = abi.encodePacked(
'{',
'"name":"', app.name, '",',
@@ -128,7 +121,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
return string(abi.encodePacked(_baseURI(), Base64.encode((dataURI))));
}
- /**
+ /**
* @dev Adds a new address with the`tokenController` privileges to a previously minted `tokenId`.
*
* May emit a {RoleGranted} event.
@@ -139,15 +132,12 @@ contract FleekERC721 is ERC721, FleekAccessControl {
* - the sender must have the `tokenOwner` role.
*
*/
- function addTokenController(
- uint256 tokenId,
- address controller
- ) public requireTokenOwner(tokenId) {
+ function addTokenController(uint256 tokenId, address controller) public requireTokenOwner(tokenId) {
_requireMinted(tokenId);
_grantRole(_tokenRole(tokenId, "CONTROLLER"), controller);
}
- /**
+ /**
* @dev Strips an address from their `tokenController` privileges on a previously minted `tokenId`.
*
* May emit a {RoleRevoked} event.
@@ -158,10 +148,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
* - the sender must have the `tokenOwner` role.
*
*/
- function removeTokenController(
- uint256 tokenId,
- address controller
- ) public requireTokenOwner(tokenId) {
+ function removeTokenController(uint256 tokenId, address controller) public requireTokenOwner(tokenId) {
_requireMinted(tokenId);
_revokeRole(_tokenRole(tokenId, "CONTROLLER"), controller);
}
@@ -169,9 +156,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
/**
* @dev See {IERC165-supportsInterface}.
*/
- function supportsInterface(
- bytes4 interfaceId
- ) public view virtual override(ERC721, AccessControl) returns (bool) {
+ function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, AccessControl) returns (bool) {
return super.supportsInterface(interfaceId);
}
@@ -207,7 +192,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
return "data:application/json;base64,";
}
- /**
+ /**
* @dev Updates the `externalURL` metadata field of a minted `tokenId`.
*
* May emit a {NewTokenExternalURL} event.
@@ -227,7 +212,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
emit NewTokenExternalURL(tokenId, _tokenExternalURL, msg.sender);
}
- /**
+ /**
* @dev Updates the `ENS` metadata field of a minted `tokenId`.
*
* May emit a {NewTokenENS} event.
@@ -238,16 +223,13 @@ contract FleekERC721 is ERC721, FleekAccessControl {
* - the sender must have the `tokenController` role.
*
*/
- function setTokenENS(
- uint256 tokenId,
- string memory _tokenENS
- ) public virtual requireTokenController(tokenId) {
+ function setTokenENS(uint256 tokenId, string memory _tokenENS) public virtual requireTokenController(tokenId) {
_requireMinted(tokenId);
_apps[tokenId].ENS = _tokenENS;
emit NewTokenENS(tokenId, _tokenENS, msg.sender);
}
- /**
+ /**
* @dev Updates the `name` metadata field of a minted `tokenId`.
*
* May emit a {NewTokenName} event.
@@ -258,16 +240,13 @@ contract FleekERC721 is ERC721, FleekAccessControl {
* - the sender must have the `tokenController` role.
*
*/
- function setTokenName(
- uint256 tokenId,
- string memory _tokenName
- ) public virtual requireTokenController(tokenId) {
+ function setTokenName(uint256 tokenId, string memory _tokenName) public virtual requireTokenController(tokenId) {
_requireMinted(tokenId);
_apps[tokenId].name = _tokenName;
emit NewTokenName(tokenId, _tokenName, msg.sender);
}
- /**
+ /**
* @dev Updates the `description` metadata field of a minted `tokenId`.
*
* May emit a {NewTokenDescription} event.
@@ -287,7 +266,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
emit NewTokenDescription(tokenId, _tokenDescription, msg.sender);
}
- /**
+ /**
* @dev Updates the `image` metadata field of a minted `tokenId`.
*
* May emit a {NewTokenImage} event.
@@ -298,16 +277,13 @@ contract FleekERC721 is ERC721, FleekAccessControl {
* - the sender must have the `tokenController` role.
*
*/
- function setTokenImage(
- uint256 tokenId,
- string memory _tokenImage
- ) public virtual requireTokenController(tokenId) {
+ function setTokenImage(uint256 tokenId, string memory _tokenImage) public virtual requireTokenController(tokenId) {
_requireMinted(tokenId);
_apps[tokenId].image = _tokenImage;
emit NewTokenImage(tokenId, _tokenImage, msg.sender);
}
- /**
+ /**
* @dev Adds a new build to a minted `tokenId`'s builds mapping.
*
* May emit a {NewBuild} event.
@@ -324,14 +300,11 @@ contract FleekERC721 is ERC721, FleekAccessControl {
string memory _gitRepository
) public virtual requireTokenController(tokenId) {
_requireMinted(tokenId);
- _apps[tokenId].builds[++_apps[tokenId].currentBuild] = Build(
- _commitHash,
- _gitRepository
- );
+ _apps[tokenId].builds[++_apps[tokenId].currentBuild] = Build(_commitHash, _gitRepository);
emit NewBuild(tokenId, _commitHash, msg.sender);
}
- /**
+ /**
* @dev Burns a previously minted `tokenId`.
*
* May emit a {Transfer} event.
@@ -342,9 +315,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
* - the sender must have the `tokenOwner` role.
*
*/
- function burn(
- uint256 tokenId
- ) public virtual requireTokenOwner(tokenId) {
+ function burn(uint256 tokenId) public virtual requireTokenOwner(tokenId) {
super._burn(tokenId);
if (bytes(_apps[tokenId].externalURL).length != 0) {
diff --git a/deployments/mumbai/FleekERC721.json b/deployments/mumbai/FleekERC721.json
index 7416f7c..d3f75a6 100644
--- a/deployments/mumbai/FleekERC721.json
+++ b/deployments/mumbai/FleekERC721.json
@@ -985,10 +985,7 @@
"status": 1,
"byzantium": true
},
- "args": [
- "FleekSites",
- "FLKSITE"
- ],
+ "args": ["FleekSites", "FLKSITE"],
"numDeployments": 1,
"solcInputHash": "0ee8c4d44ebad02e2364970b68450c98",
"metadata": "{\"compiler\":{\"version\":\"0.8.7+commit.e28d00a7\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_symbol\",\"type\":\"string\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"approved\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"ApprovalForAll\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"token\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"string\",\"name\":\"commit_hash\",\"type\":\"string\"}],\"name\":\"NewBuild\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"token\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"}],\"name\":\"NewTokenDescription\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"token\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"string\",\"name\":\"ENS\",\"type\":\"string\"}],\"name\":\"NewTokenENS\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"token\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"string\",\"name\":\"external_url\",\"type\":\"string\"}],\"name\":\"NewTokenExternalURL\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"token\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"string\",\"name\":\"image\",\"type\":\"string\"}],\"name\":\"NewTokenImage\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"token\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"NewTokenName\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"COLLECTION_CONTROLLER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"COLLECTION_OWNER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"controller\",\"type\":\"address\"}],\"name\":\"addTokenController\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"getApproved\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"isApprovedForAll\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"isTokenController\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"image\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"external_url\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"ENS\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"commit_hash\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"git_repository\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"author\",\"type\":\"string\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"ownerOf\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"controller\",\"type\":\"address\"}],\"name\":\"removeTokenController\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"setApprovalForAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"_commit_hash\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_git_repository\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_author\",\"type\":\"string\"}],\"name\":\"setTokenBuild\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"_tokenDescription\",\"type\":\"string\"}],\"name\":\"setTokenDescription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"_tokenENS\",\"type\":\"string\"}],\"name\":\"setTokenENS\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"_tokenExternalURL\",\"type\":\"string\"}],\"name\":\"setTokenExternalURL\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"_tokenImage\",\"type\":\"string\"}],\"name\":\"setTokenImage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"_tokenName\",\"type\":\"string\"}],\"name\":\"setTokenName\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"tokenURI\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"approve(address,uint256)\":{\"details\":\"See {IERC721-approve}.\"},\"balanceOf(address)\":{\"details\":\"See {IERC721-balanceOf}.\"},\"getApproved(uint256)\":{\"details\":\"See {IERC721-getApproved}.\"},\"getRoleAdmin(bytes32)\":{\"details\":\"Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. To change a role's admin, use {_setRoleAdmin}.\"},\"grantRole(bytes32,address)\":{\"details\":\"Grants `role` to `account`. If `account` had not been already granted `role`, emits a {RoleGranted} event. Requirements: - the caller must have ``role``'s admin role. May emit a {RoleGranted} event.\"},\"hasRole(bytes32,address)\":{\"details\":\"Returns `true` if `account` has been granted `role`.\"},\"isApprovedForAll(address,address)\":{\"details\":\"See {IERC721-isApprovedForAll}.\"},\"name()\":{\"details\":\"See {IERC721Metadata-name}.\"},\"ownerOf(uint256)\":{\"details\":\"See {IERC721-ownerOf}.\"},\"renounceRole(bytes32,address)\":{\"details\":\"Revokes `role` from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked `role`, emits a {RoleRevoked} event. Requirements: - the caller must be `account`. May emit a {RoleRevoked} event.\"},\"revokeRole(bytes32,address)\":{\"details\":\"Revokes `role` from `account`. If `account` had been granted `role`, emits a {RoleRevoked} event. Requirements: - the caller must have ``role``'s admin role. May emit a {RoleRevoked} event.\"},\"safeTransferFrom(address,address,uint256)\":{\"details\":\"See {IERC721-safeTransferFrom}.\"},\"safeTransferFrom(address,address,uint256,bytes)\":{\"details\":\"See {IERC721-safeTransferFrom}.\"},\"setApprovalForAll(address,bool)\":{\"details\":\"See {IERC721-setApprovalForAll}.\"},\"symbol()\":{\"details\":\"See {IERC721Metadata-symbol}.\"},\"tokenURI(uint256)\":{\"details\":\"See {IERC721Metadata-tokenURI}.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC721-transferFrom}.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/FleekERC721.sol\":\"FleekERC721\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/AccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IAccessControl.sol\\\";\\nimport \\\"../utils/Context.sol\\\";\\nimport \\\"../utils/Strings.sol\\\";\\nimport \\\"../utils/introspection/ERC165.sol\\\";\\n\\n/**\\n * @dev Contract module that allows children to implement role-based access\\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\\n * members except through off-chain means by accessing the contract event logs. Some\\n * applications may benefit from on-chain enumerability, for those cases see\\n * {AccessControlEnumerable}.\\n *\\n * Roles are referred to by their `bytes32` identifier. These should be exposed\\n * in the external API and be unique. The best way to achieve this is by\\n * using `public constant` hash digests:\\n *\\n * ```\\n * bytes32 public constant MY_ROLE = keccak256(\\\"MY_ROLE\\\");\\n * ```\\n *\\n * Roles can be used to represent a set of permissions. To restrict access to a\\n * function call, use {hasRole}:\\n *\\n * ```\\n * function foo() public {\\n * require(hasRole(MY_ROLE, msg.sender));\\n * ...\\n * }\\n * ```\\n *\\n * Roles can be granted and revoked dynamically via the {grantRole} and\\n * {revokeRole} functions. Each role has an associated admin role, and only\\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\\n *\\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\\n * that only accounts with this role will be able to grant or revoke other\\n * roles. More complex role relationships can be created by using\\n * {_setRoleAdmin}.\\n *\\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\\n * grant and revoke this role. Extra precautions should be taken to secure\\n * accounts that have been granted it.\\n */\\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\\n struct RoleData {\\n mapping(address => bool) members;\\n bytes32 adminRole;\\n }\\n\\n mapping(bytes32 => RoleData) private _roles;\\n\\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\\n\\n /**\\n * @dev Modifier that checks that an account has a specific role. Reverts\\n * with a standardized message including the required role.\\n *\\n * The format of the revert reason is given by the following regular expression:\\n *\\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\\n *\\n * _Available since v4.1._\\n */\\n modifier onlyRole(bytes32 role) {\\n _checkRole(role);\\n _;\\n }\\n\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\\n }\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) public view virtual override returns (bool) {\\n return _roles[role].members[account];\\n }\\n\\n /**\\n * @dev Revert with a standard message if `_msgSender()` is missing `role`.\\n * Overriding this function changes the behavior of the {onlyRole} modifier.\\n *\\n * Format of the revert message is described in {_checkRole}.\\n *\\n * _Available since v4.6._\\n */\\n function _checkRole(bytes32 role) internal view virtual {\\n _checkRole(role, _msgSender());\\n }\\n\\n /**\\n * @dev Revert with a standard message if `account` is missing `role`.\\n *\\n * The format of the revert reason is given by the following regular expression:\\n *\\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\\n */\\n function _checkRole(bytes32 role, address account) internal view virtual {\\n if (!hasRole(role, account)) {\\n revert(\\n string(\\n abi.encodePacked(\\n \\\"AccessControl: account \\\",\\n Strings.toHexString(account),\\n \\\" is missing role \\\",\\n Strings.toHexString(uint256(role), 32)\\n )\\n )\\n );\\n }\\n }\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {\\n return _roles[role].adminRole;\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n *\\n * May emit a {RoleGranted} event.\\n */\\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\\n _grantRole(role, account);\\n }\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n *\\n * May emit a {RoleRevoked} event.\\n */\\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\\n _revokeRole(role, account);\\n }\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n *\\n * May emit a {RoleRevoked} event.\\n */\\n function renounceRole(bytes32 role, address account) public virtual override {\\n require(account == _msgSender(), \\\"AccessControl: can only renounce roles for self\\\");\\n\\n _revokeRole(role, account);\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event. Note that unlike {grantRole}, this function doesn't perform any\\n * checks on the calling account.\\n *\\n * May emit a {RoleGranted} event.\\n *\\n * [WARNING]\\n * ====\\n * This function should only be called from the constructor when setting\\n * up the initial roles for the system.\\n *\\n * Using this function in any other way is effectively circumventing the admin\\n * system imposed by {AccessControl}.\\n * ====\\n *\\n * NOTE: This function is deprecated in favor of {_grantRole}.\\n */\\n function _setupRole(bytes32 role, address account) internal virtual {\\n _grantRole(role, account);\\n }\\n\\n /**\\n * @dev Sets `adminRole` as ``role``'s admin role.\\n *\\n * Emits a {RoleAdminChanged} event.\\n */\\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\\n bytes32 previousAdminRole = getRoleAdmin(role);\\n _roles[role].adminRole = adminRole;\\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\\n }\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * Internal function without access restriction.\\n *\\n * May emit a {RoleGranted} event.\\n */\\n function _grantRole(bytes32 role, address account) internal virtual {\\n if (!hasRole(role, account)) {\\n _roles[role].members[account] = true;\\n emit RoleGranted(role, account, _msgSender());\\n }\\n }\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * Internal function without access restriction.\\n *\\n * May emit a {RoleRevoked} event.\\n */\\n function _revokeRole(bytes32 role, address account) internal virtual {\\n if (hasRole(role, account)) {\\n _roles[role].members[account] = false;\\n emit RoleRevoked(role, account, _msgSender());\\n }\\n }\\n}\\n\",\"keccak256\":\"0x67e3daf189111d6d5b0464ed09cf9f0605a22c4b965a7fcecd707101faff008a\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev External interface of AccessControl declared to support ERC165 detection.\\n */\\ninterface IAccessControl {\\n /**\\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\\n *\\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\\n * {RoleAdminChanged} not being emitted signaling this.\\n *\\n * _Available since v3.1._\\n */\\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\\n\\n /**\\n * @dev Emitted when `account` is granted `role`.\\n *\\n * `sender` is the account that originated the contract call, an admin role\\n * bearer except when using {AccessControl-_setupRole}.\\n */\\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Emitted when `account` is revoked `role`.\\n *\\n * `sender` is the account that originated the contract call:\\n * - if using `revokeRole`, it is the admin role bearer\\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\\n */\\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) external view returns (bool);\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been granted `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) external;\\n}\\n\",\"keccak256\":\"0x59ce320a585d7e1f163cd70390a0ef2ff9cec832e2aa544293a00692465a7a57\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/ERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/ERC721.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IERC721.sol\\\";\\nimport \\\"./IERC721Receiver.sol\\\";\\nimport \\\"./extensions/IERC721Metadata.sol\\\";\\nimport \\\"../../utils/Address.sol\\\";\\nimport \\\"../../utils/Context.sol\\\";\\nimport \\\"../../utils/Strings.sol\\\";\\nimport \\\"../../utils/introspection/ERC165.sol\\\";\\n\\n/**\\n * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including\\n * the Metadata extension, but not including the Enumerable extension, which is available separately as\\n * {ERC721Enumerable}.\\n */\\ncontract ERC721 is Context, ERC165, IERC721, IERC721Metadata {\\n using Address for address;\\n using Strings for uint256;\\n\\n // Token name\\n string private _name;\\n\\n // Token symbol\\n string private _symbol;\\n\\n // Mapping from token ID to owner address\\n mapping(uint256 => address) private _owners;\\n\\n // Mapping owner address to token count\\n mapping(address => uint256) private _balances;\\n\\n // Mapping from token ID to approved address\\n mapping(uint256 => address) private _tokenApprovals;\\n\\n // Mapping from owner to operator approvals\\n mapping(address => mapping(address => bool)) private _operatorApprovals;\\n\\n /**\\n * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.\\n */\\n constructor(string memory name_, string memory symbol_) {\\n _name = name_;\\n _symbol = symbol_;\\n }\\n\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {\\n return\\n interfaceId == type(IERC721).interfaceId ||\\n interfaceId == type(IERC721Metadata).interfaceId ||\\n super.supportsInterface(interfaceId);\\n }\\n\\n /**\\n * @dev See {IERC721-balanceOf}.\\n */\\n function balanceOf(address owner) public view virtual override returns (uint256) {\\n require(owner != address(0), \\\"ERC721: address zero is not a valid owner\\\");\\n return _balances[owner];\\n }\\n\\n /**\\n * @dev See {IERC721-ownerOf}.\\n */\\n function ownerOf(uint256 tokenId) public view virtual override returns (address) {\\n address owner = _ownerOf(tokenId);\\n require(owner != address(0), \\\"ERC721: invalid token ID\\\");\\n return owner;\\n }\\n\\n /**\\n * @dev See {IERC721Metadata-name}.\\n */\\n function name() public view virtual override returns (string memory) {\\n return _name;\\n }\\n\\n /**\\n * @dev See {IERC721Metadata-symbol}.\\n */\\n function symbol() public view virtual override returns (string memory) {\\n return _symbol;\\n }\\n\\n /**\\n * @dev See {IERC721Metadata-tokenURI}.\\n */\\n function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {\\n _requireMinted(tokenId);\\n\\n string memory baseURI = _baseURI();\\n return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : \\\"\\\";\\n }\\n\\n /**\\n * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each\\n * token will be the concatenation of the `baseURI` and the `tokenId`. Empty\\n * by default, can be overridden in child contracts.\\n */\\n function _baseURI() internal view virtual returns (string memory) {\\n return \\\"\\\";\\n }\\n\\n /**\\n * @dev See {IERC721-approve}.\\n */\\n function approve(address to, uint256 tokenId) public virtual override {\\n address owner = ERC721.ownerOf(tokenId);\\n require(to != owner, \\\"ERC721: approval to current owner\\\");\\n\\n require(\\n _msgSender() == owner || isApprovedForAll(owner, _msgSender()),\\n \\\"ERC721: approve caller is not token owner or approved for all\\\"\\n );\\n\\n _approve(to, tokenId);\\n }\\n\\n /**\\n * @dev See {IERC721-getApproved}.\\n */\\n function getApproved(uint256 tokenId) public view virtual override returns (address) {\\n _requireMinted(tokenId);\\n\\n return _tokenApprovals[tokenId];\\n }\\n\\n /**\\n * @dev See {IERC721-setApprovalForAll}.\\n */\\n function setApprovalForAll(address operator, bool approved) public virtual override {\\n _setApprovalForAll(_msgSender(), operator, approved);\\n }\\n\\n /**\\n * @dev See {IERC721-isApprovedForAll}.\\n */\\n function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {\\n return _operatorApprovals[owner][operator];\\n }\\n\\n /**\\n * @dev See {IERC721-transferFrom}.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) public virtual override {\\n //solhint-disable-next-line max-line-length\\n require(_isApprovedOrOwner(_msgSender(), tokenId), \\\"ERC721: caller is not token owner or approved\\\");\\n\\n _transfer(from, to, tokenId);\\n }\\n\\n /**\\n * @dev See {IERC721-safeTransferFrom}.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) public virtual override {\\n safeTransferFrom(from, to, tokenId, \\\"\\\");\\n }\\n\\n /**\\n * @dev See {IERC721-safeTransferFrom}.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes memory data\\n ) public virtual override {\\n require(_isApprovedOrOwner(_msgSender(), tokenId), \\\"ERC721: caller is not token owner or approved\\\");\\n _safeTransfer(from, to, tokenId, data);\\n }\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\\n *\\n * `data` is additional data, it has no specified format and it is sent in call to `to`.\\n *\\n * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.\\n * implement alternative mechanisms to perform token transfer, such as signature-based.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _safeTransfer(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes memory data\\n ) internal virtual {\\n _transfer(from, to, tokenId);\\n require(_checkOnERC721Received(from, to, tokenId, data), \\\"ERC721: transfer to non ERC721Receiver implementer\\\");\\n }\\n\\n /**\\n * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist\\n */\\n function _ownerOf(uint256 tokenId) internal view virtual returns (address) {\\n return _owners[tokenId];\\n }\\n\\n /**\\n * @dev Returns whether `tokenId` exists.\\n *\\n * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.\\n *\\n * Tokens start existing when they are minted (`_mint`),\\n * and stop existing when they are burned (`_burn`).\\n */\\n function _exists(uint256 tokenId) internal view virtual returns (bool) {\\n return _ownerOf(tokenId) != address(0);\\n }\\n\\n /**\\n * @dev Returns whether `spender` is allowed to manage `tokenId`.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {\\n address owner = ERC721.ownerOf(tokenId);\\n return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);\\n }\\n\\n /**\\n * @dev Safely mints `tokenId` and transfers it to `to`.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must not exist.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _safeMint(address to, uint256 tokenId) internal virtual {\\n _safeMint(to, tokenId, \\\"\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is\\n * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.\\n */\\n function _safeMint(\\n address to,\\n uint256 tokenId,\\n bytes memory data\\n ) internal virtual {\\n _mint(to, tokenId);\\n require(\\n _checkOnERC721Received(address(0), to, tokenId, data),\\n \\\"ERC721: transfer to non ERC721Receiver implementer\\\"\\n );\\n }\\n\\n /**\\n * @dev Mints `tokenId` and transfers it to `to`.\\n *\\n * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible\\n *\\n * Requirements:\\n *\\n * - `tokenId` must not exist.\\n * - `to` cannot be the zero address.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _mint(address to, uint256 tokenId) internal virtual {\\n require(to != address(0), \\\"ERC721: mint to the zero address\\\");\\n require(!_exists(tokenId), \\\"ERC721: token already minted\\\");\\n\\n _beforeTokenTransfer(address(0), to, tokenId, 1);\\n\\n // Check that tokenId was not minted by `_beforeTokenTransfer` hook\\n require(!_exists(tokenId), \\\"ERC721: token already minted\\\");\\n\\n unchecked {\\n // Will not overflow unless all 2**256 token ids are minted to the same owner.\\n // Given that tokens are minted one by one, it is impossible in practice that\\n // this ever happens. Might change if we allow batch minting.\\n // The ERC fails to describe this case.\\n _balances[to] += 1;\\n }\\n\\n _owners[tokenId] = to;\\n\\n emit Transfer(address(0), to, tokenId);\\n\\n _afterTokenTransfer(address(0), to, tokenId, 1);\\n }\\n\\n /**\\n * @dev Destroys `tokenId`.\\n * The approval is cleared when the token is burned.\\n * This is an internal function that does not check if the sender is authorized to operate on the token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _burn(uint256 tokenId) internal virtual {\\n address owner = ERC721.ownerOf(tokenId);\\n\\n _beforeTokenTransfer(owner, address(0), tokenId, 1);\\n\\n // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook\\n owner = ERC721.ownerOf(tokenId);\\n\\n // Clear approvals\\n delete _tokenApprovals[tokenId];\\n\\n unchecked {\\n // Cannot overflow, as that would require more tokens to be burned/transferred\\n // out than the owner initially received through minting and transferring in.\\n _balances[owner] -= 1;\\n }\\n delete _owners[tokenId];\\n\\n emit Transfer(owner, address(0), tokenId);\\n\\n _afterTokenTransfer(owner, address(0), tokenId, 1);\\n }\\n\\n /**\\n * @dev Transfers `tokenId` from `from` to `to`.\\n * As opposed to {transferFrom}, this imposes no restrictions on msg.sender.\\n *\\n * Requirements:\\n *\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must be owned by `from`.\\n *\\n * Emits a {Transfer} event.\\n */\\n function _transfer(\\n address from,\\n address to,\\n uint256 tokenId\\n ) internal virtual {\\n require(ERC721.ownerOf(tokenId) == from, \\\"ERC721: transfer from incorrect owner\\\");\\n require(to != address(0), \\\"ERC721: transfer to the zero address\\\");\\n\\n _beforeTokenTransfer(from, to, tokenId, 1);\\n\\n // Check that tokenId was not transferred by `_beforeTokenTransfer` hook\\n require(ERC721.ownerOf(tokenId) == from, \\\"ERC721: transfer from incorrect owner\\\");\\n\\n // Clear approvals from the previous owner\\n delete _tokenApprovals[tokenId];\\n\\n unchecked {\\n // `_balances[from]` cannot overflow for the same reason as described in `_burn`:\\n // `from`'s balance is the number of token held, which is at least one before the current\\n // transfer.\\n // `_balances[to]` could overflow in the conditions described in `_mint`. That would require\\n // all 2**256 token ids to be minted, which in practice is impossible.\\n _balances[from] -= 1;\\n _balances[to] += 1;\\n }\\n _owners[tokenId] = to;\\n\\n emit Transfer(from, to, tokenId);\\n\\n _afterTokenTransfer(from, to, tokenId, 1);\\n }\\n\\n /**\\n * @dev Approve `to` to operate on `tokenId`\\n *\\n * Emits an {Approval} event.\\n */\\n function _approve(address to, uint256 tokenId) internal virtual {\\n _tokenApprovals[tokenId] = to;\\n emit Approval(ERC721.ownerOf(tokenId), to, tokenId);\\n }\\n\\n /**\\n * @dev Approve `operator` to operate on all of `owner` tokens\\n *\\n * Emits an {ApprovalForAll} event.\\n */\\n function _setApprovalForAll(\\n address owner,\\n address operator,\\n bool approved\\n ) internal virtual {\\n require(owner != operator, \\\"ERC721: approve to caller\\\");\\n _operatorApprovals[owner][operator] = approved;\\n emit ApprovalForAll(owner, operator, approved);\\n }\\n\\n /**\\n * @dev Reverts if the `tokenId` has not been minted yet.\\n */\\n function _requireMinted(uint256 tokenId) internal view virtual {\\n require(_exists(tokenId), \\\"ERC721: invalid token ID\\\");\\n }\\n\\n /**\\n * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.\\n * The call is not executed if the target address is not a contract.\\n *\\n * @param from address representing the previous owner of the given token ID\\n * @param to target address that will receive the tokens\\n * @param tokenId uint256 ID of the token to be transferred\\n * @param data bytes optional data to send along with the call\\n * @return bool whether the call correctly returned the expected magic value\\n */\\n function _checkOnERC721Received(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes memory data\\n ) private returns (bool) {\\n if (to.isContract()) {\\n try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {\\n return retval == IERC721Receiver.onERC721Received.selector;\\n } catch (bytes memory reason) {\\n if (reason.length == 0) {\\n revert(\\\"ERC721: transfer to non ERC721Receiver implementer\\\");\\n } else {\\n /// @solidity memory-safe-assembly\\n assembly {\\n revert(add(32, reason), mload(reason))\\n }\\n }\\n }\\n } else {\\n return true;\\n }\\n }\\n\\n /**\\n * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is\\n * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.\\n *\\n * Calling conditions:\\n *\\n * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.\\n * - When `from` is zero, the tokens will be minted for `to`.\\n * - When `to` is zero, ``from``'s tokens will be burned.\\n * - `from` and `to` are never both zero.\\n * - `batchSize` is non-zero.\\n *\\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\\n */\\n function _beforeTokenTransfer(\\n address from,\\n address to,\\n uint256, /* firstTokenId */\\n uint256 batchSize\\n ) internal virtual {\\n if (batchSize > 1) {\\n if (from != address(0)) {\\n _balances[from] -= batchSize;\\n }\\n if (to != address(0)) {\\n _balances[to] += batchSize;\\n }\\n }\\n }\\n\\n /**\\n * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is\\n * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.\\n *\\n * Calling conditions:\\n *\\n * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.\\n * - When `from` is zero, the tokens were minted for `to`.\\n * - When `to` is zero, ``from``'s tokens were burned.\\n * - `from` and `to` are never both zero.\\n * - `batchSize` is non-zero.\\n *\\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\\n */\\n function _afterTokenTransfer(\\n address from,\\n address to,\\n uint256 firstTokenId,\\n uint256 batchSize\\n ) internal virtual {}\\n}\\n\",\"keccak256\":\"0xd89f3585b211fc9e3408384a4c4efdc3a93b2f877a3821046fa01c219d35be1b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/IERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../../utils/introspection/IERC165.sol\\\";\\n\\n/**\\n * @dev Required interface of an ERC721 compliant contract.\\n */\\ninterface IERC721 is IERC165 {\\n /**\\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\\n */\\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\\n */\\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\\n\\n /**\\n * @dev Returns the number of tokens in ``owner``'s account.\\n */\\n function balanceOf(address owner) external view returns (uint256 balance);\\n\\n /**\\n * @dev Returns the owner of the `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function ownerOf(uint256 tokenId) external view returns (address owner);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes calldata data\\n ) external;\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Transfers `tokenId` token from `from` to `to`.\\n *\\n * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721\\n * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must\\n * understand this adds an external call which potentially creates a reentrancy vulnerability.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\\n * The approval is cleared when the token is transferred.\\n *\\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\\n *\\n * Requirements:\\n *\\n * - The caller must own the token or be an approved operator.\\n * - `tokenId` must exist.\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Approve or remove `operator` as an operator for the caller.\\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\\n *\\n * Requirements:\\n *\\n * - The `operator` cannot be the caller.\\n *\\n * Emits an {ApprovalForAll} event.\\n */\\n function setApprovalForAll(address operator, bool _approved) external;\\n\\n /**\\n * @dev Returns the account approved for `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function getApproved(uint256 tokenId) external view returns (address operator);\\n\\n /**\\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\\n *\\n * See {setApprovalForAll}\\n */\\n function isApprovedForAll(address owner, address operator) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xab28a56179c1db258c9bf5235b382698cb650debecb51b23d12be9e241374b68\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title ERC721 token receiver interface\\n * @dev Interface for any contract that wants to support safeTransfers\\n * from ERC721 asset contracts.\\n */\\ninterface IERC721Receiver {\\n /**\\n * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}\\n * by `operator` from `from`, this function is called.\\n *\\n * It must return its Solidity selector to confirm the token transfer.\\n * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.\\n *\\n * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.\\n */\\n function onERC721Received(\\n address operator,\\n address from,\\n uint256 tokenId,\\n bytes calldata data\\n ) external returns (bytes4);\\n}\\n\",\"keccak256\":\"0xa82b58eca1ee256be466e536706850163d2ec7821945abd6b4778cfb3bee37da\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC721.sol\\\";\\n\\n/**\\n * @title ERC-721 Non-Fungible Token Standard, optional metadata extension\\n * @dev See https://eips.ethereum.org/EIPS/eip-721\\n */\\ninterface IERC721Metadata is IERC721 {\\n /**\\n * @dev Returns the token collection name.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the token collection symbol.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.\\n */\\n function tokenURI(uint256 tokenId) external view returns (string memory);\\n}\\n\",\"keccak256\":\"0x75b829ff2f26c14355d1cba20e16fe7b29ca58eb5fef665ede48bc0f9c6c74b9\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\\n *\\n * _Available since v4.8._\\n */\\n function verifyCallResultFromTarget(\\n address target,\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n if (success) {\\n if (returndata.length == 0) {\\n // only check isContract if the call was successful and the return data is empty\\n // otherwise we already know that it was a contract\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n }\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason or using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n /// @solidity memory-safe-assembly\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n}\\n\",\"keccak256\":\"0xf96f969e24029d43d0df89e59d365f277021dac62b48e1c1e3ebe0acdd7f1ca1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Base64.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Base64.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides a set of functions to operate with Base64 strings.\\n *\\n * _Available since v4.5._\\n */\\nlibrary Base64 {\\n /**\\n * @dev Base64 Encoding/Decoding Table\\n */\\n string internal constant _TABLE = \\\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\\\";\\n\\n /**\\n * @dev Converts a `bytes` to its Bytes64 `string` representation.\\n */\\n function encode(bytes memory data) internal pure returns (string memory) {\\n /**\\n * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence\\n * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol\\n */\\n if (data.length == 0) return \\\"\\\";\\n\\n // Loads the table into memory\\n string memory table = _TABLE;\\n\\n // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter\\n // and split into 4 numbers of 6 bits.\\n // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up\\n // - `data.length + 2` -> Round up\\n // - `/ 3` -> Number of 3-bytes chunks\\n // - `4 *` -> 4 characters for each chunk\\n string memory result = new string(4 * ((data.length + 2) / 3));\\n\\n /// @solidity memory-safe-assembly\\n assembly {\\n // Prepare the lookup table (skip the first \\\"length\\\" byte)\\n let tablePtr := add(table, 1)\\n\\n // Prepare result pointer, jump over length\\n let resultPtr := add(result, 32)\\n\\n // Run over the input, 3 bytes at a time\\n for {\\n let dataPtr := data\\n let endPtr := add(data, mload(data))\\n } lt(dataPtr, endPtr) {\\n\\n } {\\n // Advance 3 bytes\\n dataPtr := add(dataPtr, 3)\\n let input := mload(dataPtr)\\n\\n // To write each character, shift the 3 bytes (18 bits) chunk\\n // 4 times in blocks of 6 bits for each character (18, 12, 6, 0)\\n // and apply logical AND with 0x3F which is the number of\\n // the previous character in the ASCII table prior to the Base64 Table\\n // The result is then added to the table to get the character to write,\\n // and finally write it in the result pointer but with a left shift\\n // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits\\n\\n mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))\\n resultPtr := add(resultPtr, 1) // Advance\\n\\n mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))\\n resultPtr := add(resultPtr, 1) // Advance\\n\\n mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))\\n resultPtr := add(resultPtr, 1) // Advance\\n\\n mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))\\n resultPtr := add(resultPtr, 1) // Advance\\n }\\n\\n // When data `bytes` is not exactly 3 bytes long\\n // it is padded with `=` characters at the end\\n switch mod(mload(data), 3)\\n case 1 {\\n mstore8(sub(resultPtr, 1), 0x3d)\\n mstore8(sub(resultPtr, 2), 0x3d)\\n }\\n case 2 {\\n mstore8(sub(resultPtr, 1), 0x3d)\\n }\\n }\\n\\n return result;\\n }\\n}\\n\",\"keccak256\":\"0x5f3461639fe20794cfb4db4a6d8477388a15b2e70a018043084b7c4bedfa8136\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Counters.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @title Counters\\n * @author Matt Condon (@shrugs)\\n * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number\\n * of elements in a mapping, issuing ERC721 ids, or counting request ids.\\n *\\n * Include with `using Counters for Counters.Counter;`\\n */\\nlibrary Counters {\\n struct Counter {\\n // This variable should never be directly accessed by users of the library: interactions must be restricted to\\n // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add\\n // this feature: see https://github.com/ethereum/solidity/issues/4637\\n uint256 _value; // default: 0\\n }\\n\\n function current(Counter storage counter) internal view returns (uint256) {\\n return counter._value;\\n }\\n\\n function increment(Counter storage counter) internal {\\n unchecked {\\n counter._value += 1;\\n }\\n }\\n\\n function decrement(Counter storage counter) internal {\\n uint256 value = counter._value;\\n require(value > 0, \\\"Counter: decrement overflow\\\");\\n unchecked {\\n counter._value = value - 1;\\n }\\n }\\n\\n function reset(Counter storage counter) internal {\\n counter._value = 0;\\n }\\n}\\n\",\"keccak256\":\"0xf0018c2440fbe238dd3a8732fa8e17a0f9dce84d31451dc8a32f6d62b349c9f1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./math/Math.sol\\\";\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _SYMBOLS = \\\"0123456789abcdef\\\";\\n uint8 private constant _ADDRESS_LENGTH = 20;\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n unchecked {\\n uint256 length = Math.log10(value) + 1;\\n string memory buffer = new string(length);\\n uint256 ptr;\\n /// @solidity memory-safe-assembly\\n assembly {\\n ptr := add(buffer, add(32, length))\\n }\\n while (true) {\\n ptr--;\\n /// @solidity memory-safe-assembly\\n assembly {\\n mstore8(ptr, byte(mod(value, 10), _SYMBOLS))\\n }\\n value /= 10;\\n if (value == 0) break;\\n }\\n return buffer;\\n }\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n unchecked {\\n return toHexString(value, Math.log256(value) + 1);\\n }\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\\n */\\n function toHexString(address addr) internal pure returns (string memory) {\\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\\n }\\n}\\n\",\"keccak256\":\"0xa4d1d62251f8574deb032a35fc948386a9b4de74b812d4f545a1ac120486b48a\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/ERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./IERC165.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC165} interface.\\n *\\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\\n * for the additional interface id that will be supported. For example:\\n *\\n * ```solidity\\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\\n * }\\n * ```\\n *\\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\\n */\\nabstract contract ERC165 is IERC165 {\\n /**\\n * @dev See {IERC165-supportsInterface}.\\n */\\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\\n return interfaceId == type(IERC165).interfaceId;\\n }\\n}\\n\",\"keccak256\":\"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/Math.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Standard math utilities missing in the Solidity language.\\n */\\nlibrary Math {\\n enum Rounding {\\n Down, // Toward negative infinity\\n Up, // Toward infinity\\n Zero // Toward zero\\n }\\n\\n /**\\n * @dev Returns the largest of two numbers.\\n */\\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a > b ? a : b;\\n }\\n\\n /**\\n * @dev Returns the smallest of two numbers.\\n */\\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a < b ? a : b;\\n }\\n\\n /**\\n * @dev Returns the average of two numbers. The result is rounded towards\\n * zero.\\n */\\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\\n // (a + b) / 2 can overflow.\\n return (a & b) + (a ^ b) / 2;\\n }\\n\\n /**\\n * @dev Returns the ceiling of the division of two numbers.\\n *\\n * This differs from standard division with `/` in that it rounds up instead\\n * of rounding down.\\n */\\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\\n // (a + b - 1) / b can overflow on addition, so we distribute.\\n return a == 0 ? 0 : (a - 1) / b + 1;\\n }\\n\\n /**\\n * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0\\n * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)\\n * with further edits by Uniswap Labs also under MIT license.\\n */\\n function mulDiv(\\n uint256 x,\\n uint256 y,\\n uint256 denominator\\n ) internal pure returns (uint256 result) {\\n unchecked {\\n // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use\\n // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\\n // variables such that product = prod1 * 2^256 + prod0.\\n uint256 prod0; // Least significant 256 bits of the product\\n uint256 prod1; // Most significant 256 bits of the product\\n assembly {\\n let mm := mulmod(x, y, not(0))\\n prod0 := mul(x, y)\\n prod1 := sub(sub(mm, prod0), lt(mm, prod0))\\n }\\n\\n // Handle non-overflow cases, 256 by 256 division.\\n if (prod1 == 0) {\\n return prod0 / denominator;\\n }\\n\\n // Make sure the result is less than 2^256. Also prevents denominator == 0.\\n require(denominator > prod1);\\n\\n ///////////////////////////////////////////////\\n // 512 by 256 division.\\n ///////////////////////////////////////////////\\n\\n // Make division exact by subtracting the remainder from [prod1 prod0].\\n uint256 remainder;\\n assembly {\\n // Compute remainder using mulmod.\\n remainder := mulmod(x, y, denominator)\\n\\n // Subtract 256 bit number from 512 bit number.\\n prod1 := sub(prod1, gt(remainder, prod0))\\n prod0 := sub(prod0, remainder)\\n }\\n\\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.\\n // See https://cs.stackexchange.com/q/138556/92363.\\n\\n // Does not overflow because the denominator cannot be zero at this stage in the function.\\n uint256 twos = denominator & (~denominator + 1);\\n assembly {\\n // Divide denominator by twos.\\n denominator := div(denominator, twos)\\n\\n // Divide [prod1 prod0] by twos.\\n prod0 := div(prod0, twos)\\n\\n // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.\\n twos := add(div(sub(0, twos), twos), 1)\\n }\\n\\n // Shift in bits from prod1 into prod0.\\n prod0 |= prod1 * twos;\\n\\n // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such\\n // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for\\n // four bits. That is, denominator * inv = 1 mod 2^4.\\n uint256 inverse = (3 * denominator) ^ 2;\\n\\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works\\n // in modular arithmetic, doubling the correct bits in each step.\\n inverse *= 2 - denominator * inverse; // inverse mod 2^8\\n inverse *= 2 - denominator * inverse; // inverse mod 2^16\\n inverse *= 2 - denominator * inverse; // inverse mod 2^32\\n inverse *= 2 - denominator * inverse; // inverse mod 2^64\\n inverse *= 2 - denominator * inverse; // inverse mod 2^128\\n inverse *= 2 - denominator * inverse; // inverse mod 2^256\\n\\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\\n // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is\\n // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1\\n // is no longer required.\\n result = prod0 * inverse;\\n return result;\\n }\\n }\\n\\n /**\\n * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.\\n */\\n function mulDiv(\\n uint256 x,\\n uint256 y,\\n uint256 denominator,\\n Rounding rounding\\n ) internal pure returns (uint256) {\\n uint256 result = mulDiv(x, y, denominator);\\n if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {\\n result += 1;\\n }\\n return result;\\n }\\n\\n /**\\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.\\n *\\n * Inspired by Henry S. Warren, Jr.'s \\\"Hacker's Delight\\\" (Chapter 11).\\n */\\n function sqrt(uint256 a) internal pure returns (uint256) {\\n if (a == 0) {\\n return 0;\\n }\\n\\n // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.\\n //\\n // We know that the \\\"msb\\\" (most significant bit) of our target number `a` is a power of 2 such that we have\\n // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.\\n //\\n // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`\\n // \\u2192 `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`\\n // \\u2192 `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`\\n //\\n // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.\\n uint256 result = 1 << (log2(a) >> 1);\\n\\n // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,\\n // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at\\n // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision\\n // into the expected uint128 result.\\n unchecked {\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n return min(result, a / result);\\n }\\n }\\n\\n /**\\n * @notice Calculates sqrt(a), following the selected rounding direction.\\n */\\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\\n unchecked {\\n uint256 result = sqrt(a);\\n return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);\\n }\\n }\\n\\n /**\\n * @dev Return the log in base 2, rounded down, of a positive value.\\n * Returns 0 if given 0.\\n */\\n function log2(uint256 value) internal pure returns (uint256) {\\n uint256 result = 0;\\n unchecked {\\n if (value >> 128 > 0) {\\n value >>= 128;\\n result += 128;\\n }\\n if (value >> 64 > 0) {\\n value >>= 64;\\n result += 64;\\n }\\n if (value >> 32 > 0) {\\n value >>= 32;\\n result += 32;\\n }\\n if (value >> 16 > 0) {\\n value >>= 16;\\n result += 16;\\n }\\n if (value >> 8 > 0) {\\n value >>= 8;\\n result += 8;\\n }\\n if (value >> 4 > 0) {\\n value >>= 4;\\n result += 4;\\n }\\n if (value >> 2 > 0) {\\n value >>= 2;\\n result += 2;\\n }\\n if (value >> 1 > 0) {\\n result += 1;\\n }\\n }\\n return result;\\n }\\n\\n /**\\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\\n * Returns 0 if given 0.\\n */\\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\\n unchecked {\\n uint256 result = log2(value);\\n return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);\\n }\\n }\\n\\n /**\\n * @dev Return the log in base 10, rounded down, of a positive value.\\n * Returns 0 if given 0.\\n */\\n function log10(uint256 value) internal pure returns (uint256) {\\n uint256 result = 0;\\n unchecked {\\n if (value >= 10**64) {\\n value /= 10**64;\\n result += 64;\\n }\\n if (value >= 10**32) {\\n value /= 10**32;\\n result += 32;\\n }\\n if (value >= 10**16) {\\n value /= 10**16;\\n result += 16;\\n }\\n if (value >= 10**8) {\\n value /= 10**8;\\n result += 8;\\n }\\n if (value >= 10**4) {\\n value /= 10**4;\\n result += 4;\\n }\\n if (value >= 10**2) {\\n value /= 10**2;\\n result += 2;\\n }\\n if (value >= 10**1) {\\n result += 1;\\n }\\n }\\n return result;\\n }\\n\\n /**\\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\\n * Returns 0 if given 0.\\n */\\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\\n unchecked {\\n uint256 result = log10(value);\\n return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);\\n }\\n }\\n\\n /**\\n * @dev Return the log in base 256, rounded down, of a positive value.\\n * Returns 0 if given 0.\\n *\\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\\n */\\n function log256(uint256 value) internal pure returns (uint256) {\\n uint256 result = 0;\\n unchecked {\\n if (value >> 128 > 0) {\\n value >>= 128;\\n result += 16;\\n }\\n if (value >> 64 > 0) {\\n value >>= 64;\\n result += 8;\\n }\\n if (value >> 32 > 0) {\\n value >>= 32;\\n result += 4;\\n }\\n if (value >> 16 > 0) {\\n value >>= 16;\\n result += 2;\\n }\\n if (value >> 8 > 0) {\\n result += 1;\\n }\\n }\\n return result;\\n }\\n\\n /**\\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\\n * Returns 0 if given 0.\\n */\\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\\n unchecked {\\n uint256 result = log256(value);\\n return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);\\n }\\n }\\n}\\n\",\"keccak256\":\"0xa1e8e83cd0087785df04ac79fb395d9f3684caeaf973d9e2c71caef723a3a5d6\",\"license\":\"MIT\"},\"contracts/FleekAccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\r\\n\\r\\npragma solidity ^0.8.7;\\r\\n\\r\\nimport \\\"@openzeppelin/contracts/access/AccessControl.sol\\\";\\r\\n\\r\\nabstract contract FleekAccessControl is AccessControl {\\r\\n bytes32 public constant COLLECTION_OWNER_ROLE =\\r\\n keccak256(\\\"COLLECTION_OWNER_ROLE\\\");\\r\\n bytes32 public constant COLLECTION_CONTROLLER_ROLE =\\r\\n keccak256(\\\"COLLECTION_CONTROLLER_ROLE\\\");\\r\\n\\r\\n constructor() {\\r\\n _setRoleAdmin(COLLECTION_OWNER_ROLE, DEFAULT_ADMIN_ROLE);\\r\\n _grantRole(COLLECTION_OWNER_ROLE, msg.sender);\\r\\n }\\r\\n\\r\\n modifier requireCollectionOwner() {\\r\\n require(\\r\\n hasRole(COLLECTION_OWNER_ROLE, msg.sender),\\r\\n \\\"FleekAccessControl: must have collection owner role\\\"\\r\\n );\\r\\n _;\\r\\n }\\r\\n\\r\\n modifier requireCollectionController() {\\r\\n require(\\r\\n hasRole(COLLECTION_OWNER_ROLE, msg.sender) ||\\r\\n hasRole(COLLECTION_CONTROLLER_ROLE, msg.sender),\\r\\n \\\"FleekAccessControl: must have collection controller role\\\"\\r\\n );\\r\\n _;\\r\\n }\\r\\n\\r\\n modifier requireTokenController(uint256 tokenId) {\\r\\n require(\\r\\n hasRole(_tokenRole(tokenId, \\\"CONTROLLER\\\"), msg.sender),\\r\\n \\\"FleekAccessControl: must have token role\\\"\\r\\n );\\r\\n _;\\r\\n }\\r\\n\\r\\n function isTokenController(\\r\\n uint256 tokenId,\\r\\n address account\\r\\n ) public view returns (bool) {\\r\\n return hasRole(_tokenRole(tokenId, \\\"CONTROLLER\\\"), account);\\r\\n }\\r\\n\\r\\n function _tokenRole(\\r\\n uint256 tokenId,\\r\\n string memory role\\r\\n ) internal pure returns (bytes32) {\\r\\n return keccak256(abi.encodePacked(\\\"TOKEN_\\\", role, tokenId));\\r\\n }\\r\\n\\r\\n function _clearTokenControllers(uint256 tokenId) internal {\\r\\n // TODO: Remove token controllers from AccessControl\\r\\n }\\r\\n}\\r\\n\",\"keccak256\":\"0x6c8115940e2d11e8fb541873abefd9bbe1601bb6a3753580b2eea9feb2ce1014\",\"license\":\"MIT\"},\"contracts/FleekERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.7;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC721/ERC721.sol\\\";\\nimport \\\"@openzeppelin/contracts/utils/Counters.sol\\\";\\nimport \\\"@openzeppelin/contracts/utils/Base64.sol\\\";\\nimport \\\"./FleekAccessControl.sol\\\";\\n\\ncontract FleekERC721 is ERC721, FleekAccessControl {\\n using Strings for uint256;\\n using Counters for Counters.Counter;\\n\\n event NewBuild(uint256 indexed token, string indexed commit_hash);\\n event NewTokenName(uint256 indexed token, string indexed name);\\n event NewTokenDescription(uint256 indexed token, string indexed description);\\n event NewTokenImage(uint256 indexed token, string indexed image);\\n event NewTokenExternalURL(uint256 indexed token, string indexed external_url);\\n event NewTokenENS(uint256 indexed token, string indexed ENS);\\n\\n struct Build {\\n string commit_hash;\\n string git_repository;\\n string author;\\n }\\n\\n /**\\n * The properties are stored as string to keep consistency with\\n * other token contracts, we might consider changing for bytes32\\n * in the future due to gas optimization\\n */\\n struct App {\\n string name; // Name of the site\\n string description; // Description about the site\\n string image; // Preview Image IPFS Link\\n string external_url; // Site URL\\n string ENS; // ENS ID\\n uint256 current_build; // The current build number (Increments by one with each change, starts at zero)\\n mapping(uint256 => Build) builds; // Mapping to build details for each build number\\n }\\n\\n Counters.Counter private _tokenIds;\\n mapping(uint256 => App) private _apps;\\n\\n constructor(\\n string memory _name,\\n string memory _symbol\\n ) ERC721(_name, _symbol) {}\\n\\n modifier requireTokenOwner(uint256 tokenId) {\\n require(\\n msg.sender == ownerOf(tokenId),\\n \\\"FleekERC721: must be token owner\\\"\\n );\\n _;\\n }\\n\\n function mint(\\n address to,\\n string memory name,\\n string memory description,\\n string memory image,\\n string memory external_url,\\n string memory ENS,\\n string memory commit_hash,\\n string memory git_repository,\\n string memory author\\n ) public payable requireCollectionOwner returns (uint256) {\\n uint256 tokenId = _tokenIds.current();\\n _mint(to, tokenId);\\n _tokenIds.increment();\\n\\n App storage app = _apps[tokenId];\\n app.name = name;\\n app.description = description;\\n app.image = image;\\n app.external_url = external_url;\\n app.ENS = ENS;\\n\\n // The mint interaction is considered to be the first build of the site. Updates from now on all increment the current_build by one and update the mapping.\\n app.current_build = 0;\\n app.builds[0] = Build(commit_hash, git_repository, author);\\n\\n return tokenId;\\n }\\n\\n function tokenURI(\\n uint256 tokenId\\n ) public view virtual override returns (string memory) {\\n _requireMinted(tokenId);\\n address owner = ownerOf(tokenId);\\n App storage app = _apps[tokenId];\\n\\n bytes memory dataURI = abi.encodePacked(\\n '{',\\n '\\\"name\\\":\\\"', app.name, '\\\",',\\n '\\\"description\\\":\\\"', app.description, '\\\",',\\n '\\\"owner\\\":\\\"', Strings.toHexString(uint160(owner), 20), '\\\",',\\n '\\\"external_url\\\":\\\"', app.external_url, '\\\",',\\n '\\\"image\\\":\\\"', app.image, '\\\",',\\n '\\\"attributes\\\": [',\\n '{\\\"trait_type\\\": \\\"ENS\\\", \\\"value\\\":\\\"', app.ENS,'\\\"},',\\n '{\\\"trait_type\\\": \\\"Commit Hash\\\", \\\"value\\\":\\\"', app.builds[app.current_build].commit_hash,'\\\"},',\\n '{\\\"trait_type\\\": \\\"Repository\\\", \\\"value\\\":\\\"', app.builds[app.current_build].git_repository,'\\\"},',\\n '{\\\"trait_type\\\": \\\"Author\\\", \\\"value\\\":\\\"', app.builds[app.current_build].author,'\\\"},',\\n '{\\\"trait_type\\\": \\\"Version\\\", \\\"value\\\":\\\"', Strings.toString(app.current_build),'\\\"}',\\n ']',\\n '}'\\n );\\n\\n return string(abi.encodePacked(_baseURI(), Base64.encode((dataURI))));\\n }\\n\\n function addTokenController(\\n uint256 tokenId,\\n address controller\\n ) public requireTokenOwner(tokenId) {\\n _requireMinted(tokenId);\\n _grantRole(_tokenRole(tokenId, \\\"CONTROLLER\\\"), controller);\\n }\\n\\n function removeTokenController(\\n uint256 tokenId,\\n address controller\\n ) public requireTokenOwner(tokenId) {\\n _requireMinted(tokenId);\\n _revokeRole(_tokenRole(tokenId, \\\"CONTROLLER\\\"), controller);\\n }\\n\\n function supportsInterface(\\n bytes4 interfaceId\\n ) public view virtual override(ERC721, AccessControl) returns (bool) {\\n return super.supportsInterface(interfaceId);\\n }\\n\\n /**\\n * @dev Override of _beforeTokenTransfer of ERC721.\\n * Here it needs to update the token controller roles for mint, burn and transfer.\\n * IMPORTANT: The function for clearing token controllers is not implemented yet.\\n */\\n function _beforeTokenTransfer(\\n address from,\\n address to,\\n uint256 tokenId,\\n uint256 batchSize\\n ) internal virtual override {\\n if (from != address(0) && to != address(0)) {\\n // Transfer\\n _clearTokenControllers(tokenId);\\n _grantRole(_tokenRole(tokenId, \\\"CONTROLLER\\\"), to);\\n } else if (from == address(0)) {\\n // Mint\\n _grantRole(_tokenRole(tokenId, \\\"CONTROLLER\\\"), to);\\n } else if (to == address(0)) {\\n // Burn\\n _clearTokenControllers(tokenId);\\n }\\n super._beforeTokenTransfer(from, to, tokenId, batchSize);\\n }\\n\\n function _baseURI() internal view virtual override returns (string memory) {\\n return \\\"data:application/json;base64,\\\";\\n }\\n\\n function setTokenExternalURL(\\n uint256 tokenId,\\n string memory _tokenExternalURL\\n ) public virtual requireTokenController(tokenId) {\\n _requireMinted(tokenId);\\n _apps[tokenId].external_url = _tokenExternalURL;\\n emit NewTokenExternalURL(tokenId, _tokenExternalURL);\\n }\\n\\n function setTokenENS(\\n uint256 tokenId,\\n string memory _tokenENS\\n ) public virtual requireTokenController(tokenId) {\\n _requireMinted(tokenId);\\n _apps[tokenId].ENS = _tokenENS;\\n emit NewTokenENS(tokenId, _tokenENS);\\n }\\n\\n function setTokenName(\\n uint256 tokenId,\\n string memory _tokenName\\n ) public virtual requireTokenController(tokenId) {\\n _requireMinted(tokenId);\\n _apps[tokenId].name = _tokenName;\\n emit NewTokenName(tokenId, _tokenName);\\n }\\n\\n function setTokenDescription(\\n uint256 tokenId,\\n string memory _tokenDescription\\n ) public virtual requireTokenController(tokenId) {\\n _requireMinted(tokenId);\\n _apps[tokenId].description = _tokenDescription;\\n emit NewTokenDescription(tokenId, _tokenDescription);\\n }\\n\\n function setTokenImage(\\n uint256 tokenId,\\n string memory _tokenImage\\n ) public virtual requireTokenController(tokenId) {\\n _requireMinted(tokenId);\\n _apps[tokenId].image = _tokenImage;\\n emit NewTokenImage(tokenId, _tokenImage);\\n }\\n\\n function setTokenBuild(\\n uint256 tokenId,\\n string memory _commit_hash,\\n string memory _git_repository,\\n string memory _author\\n ) public virtual requireTokenController(tokenId) {\\n _requireMinted(tokenId);\\n _apps[tokenId].builds[++_apps[tokenId].current_build] = Build(\\n _commit_hash,\\n _git_repository,\\n _author\\n );\\n emit NewBuild(tokenId, _commit_hash);\\n }\\n\\n function burn(\\n uint256 tokenId\\n ) public virtual requireTokenOwner(tokenId) {\\n super._burn(tokenId);\\n\\n if (bytes(_apps[tokenId].external_url).length != 0) {\\n delete _apps[tokenId];\\n }\\n }\\n}\\n\",\"keccak256\":\"0x09e1fc900f727d7948ab307e3ed432c91e2e4b5c734343db7fd94577de3c27e3\",\"license\":\"MIT\"}},\"version\":1}",
@@ -1340,4 +1337,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/deployments/mumbai/solcInputs/0ee8c4d44ebad02e2364970b68450c98.json b/deployments/mumbai/solcInputs/0ee8c4d44ebad02e2364970b68450c98.json
index 66e7d77..711ec5a 100644
--- a/deployments/mumbai/solcInputs/0ee8c4d44ebad02e2364970b68450c98.json
+++ b/deployments/mumbai/solcInputs/0ee8c4d44ebad02e2364970b68450c98.json
@@ -68,13 +68,11 @@
"storageLayout",
"evm.gasEstimates"
],
- "": [
- "ast"
- ]
+ "": ["ast"]
}
},
"metadata": {
"useLiteralContent": true
}
}
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index dba181e..47b7c33 100644
--- a/package.json
+++ b/package.json
@@ -3,15 +3,19 @@
"version": "0.0.1",
"description": "",
"main": "index.js",
+ "private": "false",
"scripts": {
"test": "hardhat test && forge test --via-ir",
"test:foundry": "forge test --via-ir",
"test:hardhat": "hardhat test",
- "format": "prettier --write \"./**/*.{js,ts,sol}\"",
+ "format": "prettier --write \"./**/*.{js,json,sol,ts}\"",
"node:hardhat": "hardhat node --tags local",
"deploy:local": "hardhat deploy --tags local",
"deploy:mumbai": "hardhat deploy --tags mumbai --network mumbai",
- "compile": "hardhat compile"
+ "compile": "hardhat compile",
+ "postinstall": "husky install",
+ "prepack": "pinst --disable",
+ "postpack": "pinst --enable"
},
"repository": {
"type": "git",
@@ -39,7 +43,9 @@
"hardhat-contract-sizer": "^2.6.1",
"hardhat-deploy": "^0.11.15",
"hardhat-gas-reporter": "^1.0.9",
- "minimist": "^1.2.7",
+ "husky": "^8.0.2",
+ "lint-staged": "^13.0.4",
+ "pinst": "^3.0.0",
"prettier": "^2.7.1",
"prettier-plugin-solidity": "^1.0.0",
"solidity-coverage": "^0.8.2",
diff --git a/scripts/mint.js b/scripts/mint.js
index 7c302cc..fe43263 100644
--- a/scripts/mint.js
+++ b/scripts/mint.js
@@ -1,23 +1,23 @@
-// npx hardhat run scripts/mint.js --network mumbai
-const { getContract } = require('./util');
-
-// TODO: make this arguments
-const params = [
- '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', // to
- 'Fleek App', // name
- 'Description', // description
- 'https://fleek.network/fleek-network-logo-minimal.png', // image
- 'https://fleek.co/', // external url
- 'fleek.eth', // ens
- '6ea6ad16c46ae85faced7e50555ff7368422f57', // commit hash
- 'https://github.com/org/repo', // repo
- 'fleek', // author
-];
-
-(async () => {
- const contract = getContract('FleekERC721');
-
- const transaction = await contract.mint(...params);
-
- console.log('Response: ', transaction);
-})();
+// npx hardhat run scripts/mint.js --network mumbai
+const { getContract } = require('./util');
+
+// TODO: make this arguments
+const params = [
+ '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', // to
+ 'Fleek App', // name
+ 'Description', // description
+ 'https://fleek.network/fleek-network-logo-minimal.png', // image
+ 'https://fleek.co/', // external url
+ 'fleek.eth', // ens
+ '6ea6ad16c46ae85faced7e50555ff7368422f57', // commit hash
+ 'https://github.com/org/repo', // repo
+ 'fleek', // author
+];
+
+(async () => {
+ const contract = getContract('FleekERC721');
+
+ const transaction = await contract.mint(...params);
+
+ console.log('Response: ', transaction);
+})();
diff --git a/scripts/tokenURI.js b/scripts/tokenURI.js
index 98a89c9..b34077e 100644
--- a/scripts/tokenURI.js
+++ b/scripts/tokenURI.js
@@ -1,17 +1,17 @@
-// npx hardhat run scripts/tokenURI.js --network mumbai
-const { getContract } = require('./util');
-
-// TODO: make this arguments
-const tokenId = 1;
-
-(async () => {
- const contract = await getContract('FleekERC721');
-
- const transaction = await contract.tokenURI(tokenId);
-
- const parsed = JSON.parse(
- Buffer.from(transaction.slice(29), 'base64').toString('utf-8')
- );
-
- console.log('Response: ', parsed);
-})();
+// npx hardhat run scripts/tokenURI.js --network mumbai
+const { getContract } = require('./util');
+
+// TODO: make this arguments
+const tokenId = 1;
+
+(async () => {
+ const contract = await getContract('FleekERC721');
+
+ const transaction = await contract.tokenURI(tokenId);
+
+ const parsed = JSON.parse(
+ Buffer.from(transaction.slice(29), 'base64').toString('utf-8')
+ );
+
+ console.log('Response: ', parsed);
+})();
diff --git a/scripts/upgrade.js b/scripts/upgrade.js
index 2ab2f1e..e0b049c 100644
--- a/scripts/upgrade.js
+++ b/scripts/upgrade.js
@@ -1,18 +1,18 @@
-// npx hardhat run scripts/upgrade.js --network mumbai
-const { getContract } = require('./util');
-
-// TODO: make this arguments
-const params = [
- 1, // tokenId
- '97e7908f70f0862d753c66689ff09e70caa43df2', // commit hash
- 'https://github.com/org/new-repo', // repo
- 'new-author', // author
-];
-
-(async () => {
- const contract = await getContract('FleekERC721');
-
- const transaction = await contract.setTokenBuild(...params);
-
- console.log('Response: ', transaction);
-})();
+// npx hardhat run scripts/upgrade.js --network mumbai
+const { getContract } = require('./util');
+
+// TODO: make this arguments
+const params = [
+ 1, // tokenId
+ '97e7908f70f0862d753c66689ff09e70caa43df2', // commit hash
+ 'https://github.com/org/new-repo', // repo
+ 'new-author', // author
+];
+
+(async () => {
+ const contract = await getContract('FleekERC721');
+
+ const transaction = await contract.setTokenBuild(...params);
+
+ console.log('Response: ', transaction);
+})();
diff --git a/scripts/util.js b/scripts/util.js
index 2f13dda..cda9549 100644
--- a/scripts/util.js
+++ b/scripts/util.js
@@ -1,7 +1,7 @@
-module.exports.getContract = async function (contractName) {
- const {
- address,
- } = require(`../deployments/${hre.network.name}/${contractName}.json`);
-
- return hre.ethers.getContractAt(contractName, address);
-};
+module.exports.getContract = async function (contractName) {
+ const {
+ address,
+ } = require(`../deployments/${hre.network.name}/${contractName}.json`);
+
+ return hre.ethers.getContractAt(contractName, address);
+};
diff --git a/test/FleekERC721.ts b/test/FleekERC721.ts
index 242b0d3..50d1e4b 100644
--- a/test/FleekERC721.ts
+++ b/test/FleekERC721.ts
@@ -1,175 +1,167 @@
-import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
-import { expect } from 'chai';
-import { ethers } from 'hardhat';
-import web3 from 'web3';
-
-describe('FleekERC721', () => {
- const COLLECTION_OWNER_ROLE = web3.utils.keccak256('COLLECTION_OWNER_ROLE');
-
- const MINT_PARAMS = Object.freeze({
- name: 'Fleek Test App',
- description: 'Fleek Test App Description',
- image: 'https://fleek.co/image.png',
- ens: 'fleek.eth',
- externalUrl: 'https://fleek.co',
- commitHash: 'b72e47171746b6a9e29b801af9cb655ecf4d665c',
- gitRepository: 'https://github.com/fleekxyz/contracts',
- author: 'author',
- });
-
- const COLLECTION_PARAMS = Object.freeze({
- name: 'FleekERC721',
- symbol: 'FLEEK',
- });
-
- const defaultFixture = async () => {
- // Contracts are deployed using the first signer/account by default
- const [owner, otherAccount] = await ethers.getSigners();
-
- const Contract = await ethers.getContractFactory('FleekERC721');
- const contract = await Contract.deploy(
- COLLECTION_PARAMS.name,
- COLLECTION_PARAMS.symbol
- );
-
- return { owner, otherAccount, contract };
- };
-
- describe('Deployment', () => {
- it('should assign the name and the symbol of the ERC721 contract', async () => {
- const { contract } = await loadFixture(defaultFixture);
-
- expect(await contract.name()).to.equal(COLLECTION_PARAMS.name);
- expect(await contract.symbol()).to.equal(COLLECTION_PARAMS.symbol);
- });
-
- it('should assign the owner of the contract', async () => {
- const { owner, contract } = await loadFixture(defaultFixture);
-
- expect(
- await contract.hasRole(COLLECTION_OWNER_ROLE, owner.address)
- ).to.equal(true);
- });
-
- it('should support ERC721 interface', async () => {
- const { contract } = await loadFixture(defaultFixture);
-
- expect(await contract.supportsInterface('0x80ac58cd')).to.equal(true);
- });
- });
-
- describe('Minting', () => {
- it('should be able to mint a new token', async () => {
- const { owner, contract } = await loadFixture(defaultFixture);
-
- const response = await contract.mint(
- owner.address,
- MINT_PARAMS.name,
- MINT_PARAMS.description,
- MINT_PARAMS.image,
- MINT_PARAMS.externalUrl,
- MINT_PARAMS.ens,
- MINT_PARAMS.commitHash,
- MINT_PARAMS.gitRepository,
- MINT_PARAMS.author
- );
-
- expect(response.value).to.be.instanceOf(ethers.BigNumber);
- expect(response.value.toNumber()).to.equal(0);
- });
-
- it('should not be able to mint a new token if not the owner', async () => {
- const { otherAccount, contract } = await loadFixture(defaultFixture);
-
- await expect(
- contract
- .connect(otherAccount)
- .mint(
- otherAccount.address,
- MINT_PARAMS.name,
- MINT_PARAMS.description,
- MINT_PARAMS.image,
- MINT_PARAMS.externalUrl,
- MINT_PARAMS.ens,
- MINT_PARAMS.commitHash,
- MINT_PARAMS.gitRepository,
- MINT_PARAMS.author
- )
- ).to.be.revertedWith(
- 'FleekAccessControl: must have collection owner role'
- );
- });
- });
-
- describe('Token', () => {
- let tokenId: number;
- let fixture: Awaited>;
-
- before(async () => {
- fixture = await loadFixture(defaultFixture);
- const { contract } = fixture;
-
- const response = await contract.mint(
- fixture.owner.address,
- MINT_PARAMS.name,
- MINT_PARAMS.description,
- MINT_PARAMS.image,
- MINT_PARAMS.externalUrl,
- MINT_PARAMS.ens,
- MINT_PARAMS.commitHash,
- MINT_PARAMS.gitRepository,
- MINT_PARAMS.author
- );
-
- tokenId = response.value.toNumber();
- });
-
- it('should return the token URI', async () => {
- const { contract } = fixture;
- const tokenURI = await contract.tokenURI(tokenId);
-
- const tokenURIDecoded = Buffer.from(
- tokenURI.replace('data:application/json;base64,', ''),
- 'base64'
- ).toString('ascii');
-
- const parsedURI = JSON.parse(tokenURIDecoded);
-
- expect(parsedURI).to.eql({
- owner: fixture.owner.address.toLowerCase(),
- name: MINT_PARAMS.name,
- description: MINT_PARAMS.description,
- image: MINT_PARAMS.image,
- external_url: MINT_PARAMS.externalUrl,
- attributes: [
- {
- trait_type: 'ENS',
- value: MINT_PARAMS.ens,
- },
- {
- trait_type: 'Commit Hash',
- value: MINT_PARAMS.commitHash,
- },
- {
- trait_type: 'Repository',
- value: MINT_PARAMS.gitRepository,
- },
- {
- trait_type: 'Author',
- value: MINT_PARAMS.author,
- },
- {
- trait_type: 'Version',
- value: '0',
- },
- ],
- });
- });
-
- it('should match the token owner', async () => {
- const { contract, owner } = fixture;
- const tokenOwner = await contract.ownerOf(tokenId);
- expect(tokenOwner).to.equal(owner.address);
- });
- });
-});
+import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { expect } from 'chai';
+import { ethers } from 'hardhat';
+import web3 from 'web3';
+
+describe('FleekERC721', () => {
+ const COLLECTION_OWNER_ROLE = web3.utils.keccak256('COLLECTION_OWNER_ROLE');
+
+ const MINT_PARAMS = Object.freeze({
+ name: 'Fleek Test App',
+ description: 'Fleek Test App Description',
+ image: 'https://fleek.co/image.png',
+ ens: 'fleek.eth',
+ externalUrl: 'https://fleek.co',
+ commitHash: 'b72e47171746b6a9e29b801af9cb655ecf4d665c',
+ gitRepository: 'https://github.com/fleekxyz/contracts',
+ });
+
+ const COLLECTION_PARAMS = Object.freeze({
+ name: 'FleekERC721',
+ symbol: 'FLEEK',
+ });
+
+ const defaultFixture = async () => {
+ // Contracts are deployed using the first signer/account by default
+ const [owner, otherAccount] = await ethers.getSigners();
+
+ const Contract = await ethers.getContractFactory('FleekERC721');
+ const contract = await Contract.deploy(
+ COLLECTION_PARAMS.name,
+ COLLECTION_PARAMS.symbol
+ );
+
+ return { owner, otherAccount, contract };
+ };
+
+ describe('Deployment', () => {
+ it('should assign the name and the symbol of the ERC721 contract', async () => {
+ const { contract } = await loadFixture(defaultFixture);
+
+ expect(await contract.name()).to.equal(COLLECTION_PARAMS.name);
+ expect(await contract.symbol()).to.equal(COLLECTION_PARAMS.symbol);
+ });
+
+ it('should assign the owner of the contract', async () => {
+ const { owner, contract } = await loadFixture(defaultFixture);
+
+ expect(
+ await contract.hasRole(COLLECTION_OWNER_ROLE, owner.address)
+ ).to.equal(true);
+ });
+
+ it('should support ERC721 interface', async () => {
+ const { contract } = await loadFixture(defaultFixture);
+
+ expect(await contract.supportsInterface('0x80ac58cd')).to.equal(true);
+ });
+ });
+
+ describe('Minting', () => {
+ it('should be able to mint a new token', async () => {
+ const { owner, contract } = await loadFixture(defaultFixture);
+
+ const response = await contract.mint(
+ owner.address,
+ MINT_PARAMS.name,
+ MINT_PARAMS.description,
+ MINT_PARAMS.image,
+ MINT_PARAMS.externalUrl,
+ MINT_PARAMS.ens,
+ MINT_PARAMS.commitHash,
+ MINT_PARAMS.gitRepository
+ );
+
+ expect(response.value).to.be.instanceOf(ethers.BigNumber);
+ expect(response.value.toNumber()).to.equal(0);
+ });
+
+ it('should not be able to mint a new token if not the owner', async () => {
+ const { otherAccount, contract } = await loadFixture(defaultFixture);
+
+ await expect(
+ contract
+ .connect(otherAccount)
+ .mint(
+ otherAccount.address,
+ MINT_PARAMS.name,
+ MINT_PARAMS.description,
+ MINT_PARAMS.image,
+ MINT_PARAMS.externalUrl,
+ MINT_PARAMS.ens,
+ MINT_PARAMS.commitHash,
+ MINT_PARAMS.gitRepository
+ )
+ ).to.be.revertedWith(
+ 'FleekAccessControl: must have collection owner role'
+ );
+ });
+ });
+
+ describe('Token', () => {
+ let tokenId: number;
+ let fixture: Awaited>;
+
+ before(async () => {
+ fixture = await loadFixture(defaultFixture);
+ const { contract } = fixture;
+
+ const response = await contract.mint(
+ fixture.owner.address,
+ MINT_PARAMS.name,
+ MINT_PARAMS.description,
+ MINT_PARAMS.image,
+ MINT_PARAMS.externalUrl,
+ MINT_PARAMS.ens,
+ MINT_PARAMS.commitHash,
+ MINT_PARAMS.gitRepository
+ );
+
+ tokenId = response.value.toNumber();
+ });
+
+ it('should return the token URI', async () => {
+ const { contract } = fixture;
+ const tokenURI = await contract.tokenURI(tokenId);
+
+ const tokenURIDecoded = Buffer.from(
+ tokenURI.replace('data:application/json;base64,', ''),
+ 'base64'
+ ).toString('ascii');
+
+ const parsedURI = JSON.parse(tokenURIDecoded);
+
+ expect(parsedURI).to.eql({
+ owner: fixture.owner.address.toLowerCase(),
+ name: MINT_PARAMS.name,
+ description: MINT_PARAMS.description,
+ image: MINT_PARAMS.image,
+ external_url: MINT_PARAMS.externalUrl,
+ attributes: [
+ {
+ trait_type: 'ENS',
+ value: MINT_PARAMS.ens,
+ },
+ {
+ trait_type: 'Commit Hash',
+ value: MINT_PARAMS.commitHash,
+ },
+ {
+ trait_type: 'Repository',
+ value: MINT_PARAMS.gitRepository,
+ },
+ {
+ trait_type: 'Version',
+ value: '0',
+ },
+ ],
+ });
+ });
+
+ it('should match the token owner', async () => {
+ const { contract, owner } = fixture;
+ const tokenOwner = await contract.ownerOf(tokenId);
+ expect(tokenOwner).to.equal(owner.address);
+ });
+ });
+});
diff --git a/test/foundry/apps.t.sol b/test/foundry/apps.t.sol
index d80be72..afe6892 100644
--- a/test/foundry/apps.t.sol
+++ b/test/foundry/apps.t.sol
@@ -1,58 +1,45 @@
pragma solidity ^0.8.7;
import "forge-std/Test.sol";
-import "../../contracts/FleekERC721.sol";
+import "../../contracts/FleekERC721.sol";
contract ContractBTest is Test {
FleekERC721 fleekContract;
uint256 testNumber;
function setUp() public {
- fleekContract = new FleekERC721('Test Contract', 'FLKAPS');
+ fleekContract = new FleekERC721("Test Contract", "FLKAPS");
}
function testName() public {
- assertEq(fleekContract.name(), 'Test Contract'));
+ assertEq(fleekContract.name(), "Test Contract");
}
function testSymbol() public {
- assertEq(fleekContract.symbol(), 'FLKAPS'));
+ assertEq(fleekContract.symbol(), "FLKAPS");
}
- function testMint() public {
- }
+ function testMint() public {}
- function testTokenURI() public {
- }
+ function testTokenURI() public {}
- function testBurn() public {
- }
+ function testBurn() public {}
- function testSetTokenName() public {
- }
+ function testSetTokenName() public {}
- function testSetTokenDescription() public {
- }
+ function testSetTokenDescription() public {}
- function testSetTokenImage() public {
- }
+ function testSetTokenImage() public {}
- function testSetTokenExternalURL() public {
- }
+ function testSetTokenExternalURL() public {}
- function testSetTokenBuild() public {
- }
+ function testSetTokenBuild() public {}
- function testUpgradeTokenBuild() public {
- }
+ function testUpgradeTokenBuild() public {}
- function testSetTokenENS() public {
- }
+ function testSetTokenENS() public {}
- function testAddTokenController() public {
- }
-
- function testRemoveTokenController() public {
- }
+ function testAddTokenController() public {}
+ function testRemoveTokenController() public {}
}
diff --git a/ui/package.json b/ui/package.json
index 19a63a3..9a852b3 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,37 +1,46 @@
-{
- "name": "sites-as-nfts",
- "version": "0.0.1",
- "description": "Minimal UI for sites as NFTs",
- "main": "index.js",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "preview": "vite preview"
- },
- "author": "Fleek",
- "license": "ISC",
- "dependencies": {
- "path": "^0.12.7",
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
- },
- "devDependencies": {
- "@types/jest": "^29.2.3",
- "@types/node": "^18.11.9",
- "@types/react": "^18.0.25",
- "@types/react-dom": "^18.0.9",
- "@typescript-eslint/eslint-plugin": "^5.45.0",
- "@typescript-eslint/parser": "^5.45.0",
- "@vitejs/plugin-react": "^2.2.0",
- "eslint": "^8.28.0",
- "eslint-config-prettier": "^8.5.0",
- "eslint-plugin-jest": "^27.1.6",
- "eslint-plugin-prettier": "^4.2.1",
- "eslint-plugin-react": "^7.31.11",
- "prettier": "^2.8.0",
- "ts-loader": "^9.4.1",
- "typescript": "^4.9.3",
- "vite": "^3.2.4",
- "vite-tsconfig-paths": "^3.6.0"
- }
-}
+{
+ "name": "sites-as-nfts",
+ "version": "0.0.1",
+ "description": "Minimal UI for sites as NFTs",
+ "main": "index.js",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "author": "Fleek",
+ "license": "ISC",
+ "dependencies": {
+ "@chakra-ui/icons": "^2.0.13",
+ "@chakra-ui/react": "^2.4.2",
+ "@emotion/react": "^11.10.5",
+ "@emotion/styled": "^11.10.5",
+ "formik": "^2.2.9",
+ "framer-motion": "^7.6.17",
+ "path": "^0.12.7",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.4.4"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.2.3",
+ "@types/node": "^18.11.9",
+ "@types/react": "^18.0.25",
+ "@types/react-dom": "^18.0.9",
+ "@typescript-eslint/eslint-plugin": "^5.45.0",
+ "@typescript-eslint/parser": "^5.45.0",
+ "@vitejs/plugin-react": "^2.2.0",
+ "eslint": "^8.28.0",
+ "eslint-config-prettier": "^8.5.0",
+ "eslint-plugin-jest": "^27.1.6",
+ "eslint-plugin-prettier": "^4.2.1",
+ "eslint-plugin-react": "^7.31.11",
+ "ethers": "^5.7.2",
+ "prettier": "^2.8.0",
+ "react-query": "^3.39.2",
+ "ts-loader": "^9.4.1",
+ "typescript": "^4.9.3",
+ "vite": "^3.2.4",
+ "vite-tsconfig-paths": "^3.6.0"
+ }
+}
diff --git a/ui/src/app.css b/ui/src/app.css
deleted file mode 100644
index b7cd4d3..0000000
--- a/ui/src/app.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.main {
- text-align: center;
-}
diff --git a/ui/src/app.tsx b/ui/src/app.tsx
index b37bcac..7fe03d2 100644
--- a/ui/src/app.tsx
+++ b/ui/src/app.tsx
@@ -1,10 +1,17 @@
import React from 'react';
-import './App.css';
+import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom';
+import { Home, MintSite, MintedSiteDetail } from './views';
export const App = () => {
return (
-