Merge pull request #182 from fleekxyz/develop
release: developer pre-release v0.0.4
This commit is contained in:
commit
e8468b8e83
|
|
@ -0,0 +1,19 @@
|
|||
node_modules
|
||||
.eslintrc.js
|
||||
|
||||
ui/public
|
||||
ui/dist
|
||||
ui/.graphclient
|
||||
|
||||
contracts/.openzeppelin
|
||||
contracts/artifacts
|
||||
contracts/cache
|
||||
contracts/deployments
|
||||
contracts/forge-cache
|
||||
contracts/lib
|
||||
contracts/out
|
||||
|
||||
subgraph/abis
|
||||
subgraph/build
|
||||
subgraph/generated
|
||||
subgraph/examples/query/.graphclient
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
module.exports = {
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: [
|
||||
'./contracts/tsconfig.json',
|
||||
'./ui/tsconfig.json',
|
||||
'./subgraph/tsconfig.json',
|
||||
'./subgraph/tsconfig.tools.json',
|
||||
],
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'prettier'],
|
||||
root: true,
|
||||
};
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
name: Tests
|
||||
name: Contract
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
paths:
|
||||
- 'contracts/**'
|
||||
|
||||
jobs:
|
||||
test-contracts:
|
||||
|
|
@ -42,30 +44,3 @@ jobs:
|
|||
|
||||
- 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
|
||||
|
|
@ -6,7 +6,6 @@ on:
|
|||
- main
|
||||
|
||||
jobs:
|
||||
|
||||
test-subgraph:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
name: UI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
paths:
|
||||
- 'ui/**'
|
||||
|
||||
jobs:
|
||||
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: Create .graphclient folder
|
||||
run: yarn graphclient build
|
||||
|
||||
- name: Audit
|
||||
run: yarn audit --groups dependencies
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
module.exports = {
|
||||
extends: ['../.eslintrc.js'],
|
||||
rules: {
|
||||
'no-undef': 'off',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -34,6 +34,11 @@
|
|||
"address": "0x550Ee47Fa9E0B81c1b9C394FeE62Fe699a955519",
|
||||
"txHash": "0x7076aaf31e50c5f9ddc4aeb1025c8b41e753ee99cc0d15ac5ac26395f04326e3",
|
||||
"kind": "transparent"
|
||||
},
|
||||
{
|
||||
"address": "0x37150709cFf366DeEaB836d05CAf49F4DA46Bb2E",
|
||||
"txHash": "0x808546aa8bbc4e36c54d955970d8cfe8c4dc925eb5f65ff7b25203dd312bad4c",
|
||||
"kind": "transparent"
|
||||
}
|
||||
],
|
||||
"impls": {
|
||||
|
|
@ -6988,6 +6993,458 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"eb08503319f5be721686b3aa1a477f4936b7bd581264aec1f5567149b8e15ff5": {
|
||||
"address": "0xc1eEFa5035898B7D33572397A263D262b449886D",
|
||||
"txHash": "0x61f5b9f485cf414e467dfd2077cbe785412639c51c7631f34628d5a43f8c371e",
|
||||
"layout": {
|
||||
"solcVersion": "0.8.12",
|
||||
"storage": [
|
||||
{
|
||||
"label": "_initialized",
|
||||
"offset": 0,
|
||||
"slot": "0",
|
||||
"type": "t_uint8",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62",
|
||||
"retypedFrom": "bool"
|
||||
},
|
||||
{
|
||||
"label": "_initializing",
|
||||
"offset": 1,
|
||||
"slot": "0",
|
||||
"type": "t_bool",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67"
|
||||
},
|
||||
{
|
||||
"label": "__gap",
|
||||
"offset": 0,
|
||||
"slot": "1",
|
||||
"type": "t_array(t_uint256)50_storage",
|
||||
"contract": "ContextUpgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36"
|
||||
},
|
||||
{
|
||||
"label": "__gap",
|
||||
"offset": 0,
|
||||
"slot": "51",
|
||||
"type": "t_array(t_uint256)50_storage",
|
||||
"contract": "ERC165Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41"
|
||||
},
|
||||
{
|
||||
"label": "_name",
|
||||
"offset": 0,
|
||||
"slot": "101",
|
||||
"type": "t_string_storage",
|
||||
"contract": "ERC721Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:25"
|
||||
},
|
||||
{
|
||||
"label": "_symbol",
|
||||
"offset": 0,
|
||||
"slot": "102",
|
||||
"type": "t_string_storage",
|
||||
"contract": "ERC721Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:28"
|
||||
},
|
||||
{
|
||||
"label": "_owners",
|
||||
"offset": 0,
|
||||
"slot": "103",
|
||||
"type": "t_mapping(t_uint256,t_address)",
|
||||
"contract": "ERC721Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:31"
|
||||
},
|
||||
{
|
||||
"label": "_balances",
|
||||
"offset": 0,
|
||||
"slot": "104",
|
||||
"type": "t_mapping(t_address,t_uint256)",
|
||||
"contract": "ERC721Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:34"
|
||||
},
|
||||
{
|
||||
"label": "_tokenApprovals",
|
||||
"offset": 0,
|
||||
"slot": "105",
|
||||
"type": "t_mapping(t_uint256,t_address)",
|
||||
"contract": "ERC721Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:37"
|
||||
},
|
||||
{
|
||||
"label": "_operatorApprovals",
|
||||
"offset": 0,
|
||||
"slot": "106",
|
||||
"type": "t_mapping(t_address,t_mapping(t_address,t_bool))",
|
||||
"contract": "ERC721Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:40"
|
||||
},
|
||||
{
|
||||
"label": "__gap",
|
||||
"offset": 0,
|
||||
"slot": "107",
|
||||
"type": "t_array(t_uint256)44_storage",
|
||||
"contract": "ERC721Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:514"
|
||||
},
|
||||
{
|
||||
"label": "_collectionRolesCounter",
|
||||
"offset": 0,
|
||||
"slot": "151",
|
||||
"type": "t_mapping(t_enum(CollectionRoles)3829,t_uint256)",
|
||||
"contract": "FleekAccessControl",
|
||||
"src": "contracts/FleekAccessControl.sol:57"
|
||||
},
|
||||
{
|
||||
"label": "_collectionRoles",
|
||||
"offset": 0,
|
||||
"slot": "152",
|
||||
"type": "t_mapping(t_enum(CollectionRoles)3829,t_mapping(t_address,t_bool))",
|
||||
"contract": "FleekAccessControl",
|
||||
"src": "contracts/FleekAccessControl.sol:62"
|
||||
},
|
||||
{
|
||||
"label": "_tokenRolesVersion",
|
||||
"offset": 0,
|
||||
"slot": "153",
|
||||
"type": "t_mapping(t_uint256,t_uint256)",
|
||||
"contract": "FleekAccessControl",
|
||||
"src": "contracts/FleekAccessControl.sol:69"
|
||||
},
|
||||
{
|
||||
"label": "_tokenRoles",
|
||||
"offset": 0,
|
||||
"slot": "154",
|
||||
"type": "t_mapping(t_uint256,t_mapping(t_uint256,t_mapping(t_enum(TokenRoles)3831,t_mapping(t_address,t_bool))))",
|
||||
"contract": "FleekAccessControl",
|
||||
"src": "contracts/FleekAccessControl.sol:74"
|
||||
},
|
||||
{
|
||||
"label": "__gap",
|
||||
"offset": 0,
|
||||
"slot": "155",
|
||||
"type": "t_array(t_uint256)49_storage",
|
||||
"contract": "FleekAccessControl",
|
||||
"src": "contracts/FleekAccessControl.sol:176"
|
||||
},
|
||||
{
|
||||
"label": "_paused",
|
||||
"offset": 0,
|
||||
"slot": "204",
|
||||
"type": "t_bool",
|
||||
"contract": "FleekPausable",
|
||||
"src": "contracts/FleekPausable.sol:23"
|
||||
},
|
||||
{
|
||||
"label": "_canPause",
|
||||
"offset": 1,
|
||||
"slot": "204",
|
||||
"type": "t_bool",
|
||||
"contract": "FleekPausable",
|
||||
"src": "contracts/FleekPausable.sol:24"
|
||||
},
|
||||
{
|
||||
"label": "__gap",
|
||||
"offset": 0,
|
||||
"slot": "205",
|
||||
"type": "t_array(t_uint256)49_storage",
|
||||
"contract": "FleekPausable",
|
||||
"src": "contracts/FleekPausable.sol:133"
|
||||
},
|
||||
{
|
||||
"label": "_billings",
|
||||
"offset": 0,
|
||||
"slot": "254",
|
||||
"type": "t_mapping(t_enum(Billing)4234,t_uint256)",
|
||||
"contract": "FleekBilling",
|
||||
"src": "contracts/FleekBilling.sol:31"
|
||||
},
|
||||
{
|
||||
"label": "__gap",
|
||||
"offset": 0,
|
||||
"slot": "255",
|
||||
"type": "t_array(t_uint256)49_storage",
|
||||
"contract": "FleekBilling",
|
||||
"src": "contracts/FleekBilling.sol:81"
|
||||
},
|
||||
{
|
||||
"label": "_appIds",
|
||||
"offset": 0,
|
||||
"slot": "304",
|
||||
"type": "t_uint256",
|
||||
"contract": "FleekERC721",
|
||||
"src": "contracts/FleekERC721.sol:119"
|
||||
},
|
||||
{
|
||||
"label": "_apps",
|
||||
"offset": 0,
|
||||
"slot": "305",
|
||||
"type": "t_mapping(t_uint256,t_struct(App)4585_storage)",
|
||||
"contract": "FleekERC721",
|
||||
"src": "contracts/FleekERC721.sol:120"
|
||||
},
|
||||
{
|
||||
"label": "_accessPoints",
|
||||
"offset": 0,
|
||||
"slot": "306",
|
||||
"type": "t_mapping(t_string_memory_ptr,t_struct(AccessPoint)4609_storage)",
|
||||
"contract": "FleekERC721",
|
||||
"src": "contracts/FleekERC721.sol:121"
|
||||
}
|
||||
],
|
||||
"types": {
|
||||
"t_address": {
|
||||
"label": "address",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_array(t_uint256)44_storage": {
|
||||
"label": "uint256[44]",
|
||||
"numberOfBytes": "1408"
|
||||
},
|
||||
"t_array(t_uint256)49_storage": {
|
||||
"label": "uint256[49]",
|
||||
"numberOfBytes": "1568"
|
||||
},
|
||||
"t_array(t_uint256)50_storage": {
|
||||
"label": "uint256[50]",
|
||||
"numberOfBytes": "1600"
|
||||
},
|
||||
"t_bool": {
|
||||
"label": "bool",
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_enum(AccessPointCreationStatus)4595": {
|
||||
"label": "enum FleekERC721.AccessPointCreationStatus",
|
||||
"members": [
|
||||
"DRAFT",
|
||||
"APPROVED",
|
||||
"REJECTED",
|
||||
"REMOVED"
|
||||
],
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_enum(Billing)4234": {
|
||||
"label": "enum FleekBilling.Billing",
|
||||
"members": [
|
||||
"Mint",
|
||||
"AddAccessPoint"
|
||||
],
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_enum(CollectionRoles)3829": {
|
||||
"label": "enum FleekAccessControl.CollectionRoles",
|
||||
"members": [
|
||||
"Owner"
|
||||
],
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_enum(TokenRoles)3831": {
|
||||
"label": "enum FleekAccessControl.TokenRoles",
|
||||
"members": [
|
||||
"Controller"
|
||||
],
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_mapping(t_address,t_bool)": {
|
||||
"label": "mapping(address => bool)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_address,t_mapping(t_address,t_bool))": {
|
||||
"label": "mapping(address => mapping(address => bool))",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_address,t_uint256)": {
|
||||
"label": "mapping(address => uint256)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_enum(Billing)4234,t_uint256)": {
|
||||
"label": "mapping(enum FleekBilling.Billing => uint256)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_enum(CollectionRoles)3829,t_mapping(t_address,t_bool))": {
|
||||
"label": "mapping(enum FleekAccessControl.CollectionRoles => mapping(address => bool))",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_enum(CollectionRoles)3829,t_uint256)": {
|
||||
"label": "mapping(enum FleekAccessControl.CollectionRoles => uint256)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_enum(TokenRoles)3831,t_mapping(t_address,t_bool))": {
|
||||
"label": "mapping(enum FleekAccessControl.TokenRoles => mapping(address => bool))",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_string_memory_ptr,t_struct(AccessPoint)4609_storage)": {
|
||||
"label": "mapping(string => struct FleekERC721.AccessPoint)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint256,t_address)": {
|
||||
"label": "mapping(uint256 => address)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint256,t_mapping(t_enum(TokenRoles)3831,t_mapping(t_address,t_bool)))": {
|
||||
"label": "mapping(uint256 => mapping(enum FleekAccessControl.TokenRoles => mapping(address => bool)))",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint256,t_mapping(t_uint256,t_mapping(t_enum(TokenRoles)3831,t_mapping(t_address,t_bool))))": {
|
||||
"label": "mapping(uint256 => mapping(uint256 => mapping(enum FleekAccessControl.TokenRoles => mapping(address => bool))))",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint256,t_struct(App)4585_storage)": {
|
||||
"label": "mapping(uint256 => struct FleekERC721.App)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint256,t_struct(Build)4590_storage)": {
|
||||
"label": "mapping(uint256 => struct FleekERC721.Build)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint256,t_uint256)": {
|
||||
"label": "mapping(uint256 => uint256)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_string_memory_ptr": {
|
||||
"label": "string",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_string_storage": {
|
||||
"label": "string",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_struct(AccessPoint)4609_storage": {
|
||||
"label": "struct FleekERC721.AccessPoint",
|
||||
"members": [
|
||||
{
|
||||
"label": "tokenId",
|
||||
"type": "t_uint256",
|
||||
"offset": 0,
|
||||
"slot": "0"
|
||||
},
|
||||
{
|
||||
"label": "score",
|
||||
"type": "t_uint256",
|
||||
"offset": 0,
|
||||
"slot": "1"
|
||||
},
|
||||
{
|
||||
"label": "contentVerified",
|
||||
"type": "t_bool",
|
||||
"offset": 0,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "nameVerified",
|
||||
"type": "t_bool",
|
||||
"offset": 1,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "owner",
|
||||
"type": "t_address",
|
||||
"offset": 2,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "status",
|
||||
"type": "t_enum(AccessPointCreationStatus)4595",
|
||||
"offset": 22,
|
||||
"slot": "2"
|
||||
}
|
||||
],
|
||||
"numberOfBytes": "96"
|
||||
},
|
||||
"t_struct(App)4585_storage": {
|
||||
"label": "struct FleekERC721.App",
|
||||
"members": [
|
||||
{
|
||||
"label": "name",
|
||||
"type": "t_string_storage",
|
||||
"offset": 0,
|
||||
"slot": "0"
|
||||
},
|
||||
{
|
||||
"label": "description",
|
||||
"type": "t_string_storage",
|
||||
"offset": 0,
|
||||
"slot": "1"
|
||||
},
|
||||
{
|
||||
"label": "externalURL",
|
||||
"type": "t_string_storage",
|
||||
"offset": 0,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "ENS",
|
||||
"type": "t_string_storage",
|
||||
"offset": 0,
|
||||
"slot": "3"
|
||||
},
|
||||
{
|
||||
"label": "currentBuild",
|
||||
"type": "t_uint256",
|
||||
"offset": 0,
|
||||
"slot": "4"
|
||||
},
|
||||
{
|
||||
"label": "builds",
|
||||
"type": "t_mapping(t_uint256,t_struct(Build)4590_storage)",
|
||||
"offset": 0,
|
||||
"slot": "5"
|
||||
},
|
||||
{
|
||||
"label": "logo",
|
||||
"type": "t_string_storage",
|
||||
"offset": 0,
|
||||
"slot": "6"
|
||||
},
|
||||
{
|
||||
"label": "color",
|
||||
"type": "t_uint24",
|
||||
"offset": 0,
|
||||
"slot": "7"
|
||||
},
|
||||
{
|
||||
"label": "accessPointAutoApproval",
|
||||
"type": "t_bool",
|
||||
"offset": 3,
|
||||
"slot": "7"
|
||||
}
|
||||
],
|
||||
"numberOfBytes": "256"
|
||||
},
|
||||
"t_struct(Build)4590_storage": {
|
||||
"label": "struct FleekERC721.Build",
|
||||
"members": [
|
||||
{
|
||||
"label": "commitHash",
|
||||
"type": "t_string_storage",
|
||||
"offset": 0,
|
||||
"slot": "0"
|
||||
},
|
||||
{
|
||||
"label": "gitRepository",
|
||||
"type": "t_string_storage",
|
||||
"offset": 0,
|
||||
"slot": "1"
|
||||
}
|
||||
],
|
||||
"numberOfBytes": "64"
|
||||
},
|
||||
"t_uint24": {
|
||||
"label": "uint24",
|
||||
"numberOfBytes": "3"
|
||||
},
|
||||
"t_uint256": {
|
||||
"label": "uint256",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_uint8": {
|
||||
"label": "uint8",
|
||||
"numberOfBytes": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"semi": true,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.sol",
|
||||
"options": {
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4,
|
||||
"singleQuote": false,
|
||||
"bracketSpacing": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
## Folder Structure
|
||||
|
||||
Inside contracts you are going to find:
|
||||
|
||||
- [contracts](./contracts): all the developed contracts
|
||||
- [deployments](./deployments): resultant ABI and info for deployments (each network will have a nested folder)
|
||||
- [lib](./lib): external modules used by Foundry
|
||||
|
|
@ -23,7 +24,7 @@ The contracts present in this project are based in [Solidity](https://github.com
|
|||
|
||||
> ⚠️ Before starting developing make sure you Solidity, Node.js and yarn correctly installed in your environment
|
||||
|
||||
Also, make sure you run `yarn` at the root directory to get some required tools. The rest of these steps assume you are in the [contracts folder](./).
|
||||
Also, make sure you run `yarn` at the root directory to get some required tools. The rest of these steps assume you are in the [contracts folder](./).
|
||||
|
||||
Follow the steps:
|
||||
|
||||
|
|
@ -153,6 +154,23 @@ $ yarn deploy:mumbai
|
|||
|
||||
to deploy the contract on the testnet. Please note that your wallet needs to hold enough Mumbai MATIC for the deployment to be successful. To reach more in-depth information about how to deploy contract checkout [this guide](https://wiki.polygon.technology/docs/develop/alchemy).
|
||||
|
||||
### **Deploy arguments**
|
||||
|
||||
For any of the deploy scripts above you are able to input arguments to change the date sent during the deployment. They are:
|
||||
|
||||
| Argument | Description | Example | Default |
|
||||
| -------------------- | ----------------------------------------- | -------------------------- | --------- |
|
||||
| --new-proxy-instance | Force to deploy a new proxy instance | --new-proxy-instance | false |
|
||||
| --name | The collection name | --name "Fleek NFAs" | FleekNFAs |
|
||||
| --symbol | The collection symbol | --symbol "FKNFA" | FLKNFA |
|
||||
| --billing | The billing values in an array of numbers | --billing "[10000, 20000]" | [] |
|
||||
|
||||
Appending all of them together would be like:
|
||||
|
||||
```
|
||||
$ yarn deploy:hardhat --new-proxy-instance --name "Fleek NFAs" --symbol "FKNFA" --billing "[10000, 20000]"
|
||||
```
|
||||
|
||||
<!-- TODO: add this section after the mainnet setup is done and tested
|
||||
**Polygon main-net**
|
||||
|
||||
|
|
@ -188,4 +206,4 @@ The project should provide a way for interacting with the contract as owner with
|
|||
|
||||
> 🛠️ Work in progress...
|
||||
|
||||
-->
|
||||
-->
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ contract FleekAccessControl is Initializable {
|
|||
* @dev All available collection roles.
|
||||
*/
|
||||
enum CollectionRoles {
|
||||
Owner
|
||||
Owner,
|
||||
Verifier
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -78,6 +79,7 @@ contract FleekAccessControl is Initializable {
|
|||
*/
|
||||
function __FleekAccessControl_init() internal onlyInitializing {
|
||||
_grantCollectionRole(CollectionRoles.Owner, msg.sender);
|
||||
_grantCollectionRole(CollectionRoles.Verifier, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,212 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import {FleekStrings} from "./util/FleekStrings.sol";
|
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
|
||||
error AccessPointNotExistent();
|
||||
error AccessPointAlreadyExists();
|
||||
error AccessPointScoreCannotBeLower();
|
||||
error MustBeAccessPointOwner();
|
||||
error InvalidTokenIdForAccessPoint();
|
||||
error AccessPointCreationStatusAlreadySet();
|
||||
|
||||
abstract contract FleekAccessPoints is Initializable {
|
||||
using FleekStrings for FleekAccessPoints.AccessPoint;
|
||||
|
||||
event NewAccessPoint(string apName, uint256 indexed tokenId, address indexed owner);
|
||||
event RemoveAccessPoint(string apName, uint256 indexed tokenId, address indexed owner);
|
||||
|
||||
event ChangeAccessPointScore(string apName, uint256 indexed tokenId, uint256 score, address indexed triggeredBy);
|
||||
|
||||
event ChangeAccessPointNameVerify(
|
||||
string apName,
|
||||
uint256 tokenId,
|
||||
bool indexed verified,
|
||||
address indexed triggeredBy
|
||||
);
|
||||
event ChangeAccessPointContentVerify(
|
||||
string apName,
|
||||
uint256 tokenId,
|
||||
bool indexed verified,
|
||||
address indexed triggeredBy
|
||||
);
|
||||
event ChangeAccessPointCreationStatus(
|
||||
string apName,
|
||||
uint256 tokenId,
|
||||
AccessPointCreationStatus status,
|
||||
address indexed triggeredBy
|
||||
);
|
||||
|
||||
/**
|
||||
* Creation status enums for access points
|
||||
*/
|
||||
enum AccessPointCreationStatus {
|
||||
DRAFT,
|
||||
APPROVED,
|
||||
REJECTED,
|
||||
REMOVED
|
||||
}
|
||||
|
||||
/**
|
||||
* The stored data for each AccessPoint.
|
||||
*/
|
||||
struct AccessPoint {
|
||||
uint256 tokenId;
|
||||
uint256 score;
|
||||
bool contentVerified;
|
||||
bool nameVerified;
|
||||
address owner;
|
||||
AccessPointCreationStatus status;
|
||||
}
|
||||
|
||||
mapping(string => AccessPoint) private _accessPoints;
|
||||
|
||||
mapping(uint256 => bool) private _autoApproval;
|
||||
|
||||
/**
|
||||
* @dev Checks if the AccessPoint exists.
|
||||
*/
|
||||
modifier requireAP(string memory apName) {
|
||||
if (_accessPoints[apName].owner == address(0)) revert AccessPointNotExistent();
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev A view function to gether information about an AccessPoint.
|
||||
* It returns a JSON string representing the AccessPoint information.
|
||||
*/
|
||||
function getAccessPointJSON(string memory apName) public view requireAP(apName) returns (string memory) {
|
||||
AccessPoint storage _ap = _accessPoints[apName];
|
||||
return _ap.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev A view function to check if a AccessPoint is verified.
|
||||
*/
|
||||
function isAccessPointNameVerified(string memory apName) public view requireAP(apName) returns (bool) {
|
||||
return _accessPoints[apName].nameVerified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Increases the score of a AccessPoint registry.
|
||||
*/
|
||||
function increaseAccessPointScore(string memory apName) public requireAP(apName) {
|
||||
_accessPoints[apName].score++;
|
||||
emit ChangeAccessPointScore(apName, _accessPoints[apName].tokenId, _accessPoints[apName].score, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Decreases the score of a AccessPoint registry if is greater than 0.
|
||||
*/
|
||||
function decreaseAccessPointScore(string memory apName) public requireAP(apName) {
|
||||
if (_accessPoints[apName].score == 0) revert AccessPointScoreCannotBeLower();
|
||||
_accessPoints[apName].score--;
|
||||
emit ChangeAccessPointScore(apName, _accessPoints[apName].tokenId, _accessPoints[apName].score, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Add a new AccessPoint register for an app token.
|
||||
* The AP name should be a DNS or ENS url and it should be unique.
|
||||
*/
|
||||
function _addAccessPoint(uint256 tokenId, string memory apName) internal {
|
||||
if (_accessPoints[apName].owner != address(0)) revert AccessPointAlreadyExists();
|
||||
|
||||
emit NewAccessPoint(apName, tokenId, msg.sender);
|
||||
|
||||
if (_autoApproval[tokenId]) {
|
||||
// Auto Approval is on.
|
||||
_accessPoints[apName] = AccessPoint(
|
||||
tokenId,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
msg.sender,
|
||||
AccessPointCreationStatus.APPROVED
|
||||
);
|
||||
|
||||
emit ChangeAccessPointCreationStatus(apName, tokenId, AccessPointCreationStatus.APPROVED, msg.sender);
|
||||
} else {
|
||||
// Auto Approval is off. Should wait for approval.
|
||||
_accessPoints[apName] = AccessPoint(tokenId, 0, false, false, msg.sender, AccessPointCreationStatus.DRAFT);
|
||||
emit ChangeAccessPointCreationStatus(apName, tokenId, AccessPointCreationStatus.DRAFT, msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Remove an AccessPoint registry for an app token.
|
||||
* It will also remove the AP from the app token APs list.
|
||||
*/
|
||||
function _removeAccessPoint(string memory apName) internal requireAP(apName) {
|
||||
if (msg.sender != _accessPoints[apName].owner) revert MustBeAccessPointOwner();
|
||||
_accessPoints[apName].status = AccessPointCreationStatus.REMOVED;
|
||||
uint256 tokenId = _accessPoints[apName].tokenId;
|
||||
emit ChangeAccessPointCreationStatus(apName, tokenId, AccessPointCreationStatus.REMOVED, msg.sender);
|
||||
emit RemoveAccessPoint(apName, tokenId, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates the `accessPointAutoApproval` settings on minted `tokenId`.
|
||||
*/
|
||||
function _setAccessPointAutoApproval(uint256 tokenId, bool _apAutoApproval) internal {
|
||||
_autoApproval[tokenId] = _apAutoApproval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set approval settings for an access point.
|
||||
* It will add the access point to the token's AP list, if `approved` is true.
|
||||
*/
|
||||
function _setApprovalForAccessPoint(uint256 tokenId, string memory apName, bool approved) internal {
|
||||
AccessPoint storage accessPoint = _accessPoints[apName];
|
||||
if (accessPoint.tokenId != tokenId) revert InvalidTokenIdForAccessPoint();
|
||||
if (accessPoint.status != AccessPointCreationStatus.DRAFT) revert AccessPointCreationStatusAlreadySet();
|
||||
|
||||
if (approved) {
|
||||
// Approval
|
||||
accessPoint.status = AccessPointCreationStatus.APPROVED;
|
||||
emit ChangeAccessPointCreationStatus(apName, tokenId, AccessPointCreationStatus.APPROVED, msg.sender);
|
||||
} else {
|
||||
// Not Approved
|
||||
accessPoint.status = AccessPointCreationStatus.REJECTED;
|
||||
emit ChangeAccessPointCreationStatus(apName, tokenId, AccessPointCreationStatus.REJECTED, msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set the content verification of a AccessPoint registry.
|
||||
*/
|
||||
function _setAccessPointContentVerify(string memory apName, bool verified) internal requireAP(apName) {
|
||||
_accessPoints[apName].contentVerified = verified;
|
||||
emit ChangeAccessPointContentVerify(apName, _accessPoints[apName].tokenId, verified, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set the name verification of a AccessPoint registry.
|
||||
*/
|
||||
function _setAccessPointNameVerify(string memory apName, bool verified) internal requireAP(apName) {
|
||||
_accessPoints[apName].nameVerified = verified;
|
||||
emit ChangeAccessPointNameVerify(apName, _accessPoints[apName].tokenId, verified, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get the AccessPoint token id.
|
||||
*/
|
||||
function _getAccessPointTokenId(string memory apName) internal view requireAP(apName) returns (uint256) {
|
||||
return _accessPoints[apName].tokenId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get the Auto Approval setting for token id.
|
||||
*/
|
||||
function _getAccessPointAutoApproval(uint256 tokenId) internal view returns (bool) {
|
||||
return _autoApproval[tokenId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev This empty reserved space is put in place to allow future versions to add new
|
||||
* variables without shifting down storage in the inheritance chain.
|
||||
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
|
||||
*/
|
||||
uint256[49] private __gap;
|
||||
}
|
||||
|
|
@ -7,22 +7,26 @@ import "@openzeppelin/contracts/utils/Base64.sol";
|
|||
import "@openzeppelin/contracts/utils/Strings.sol";
|
||||
import "./FleekAccessControl.sol";
|
||||
import "./FleekBilling.sol";
|
||||
import "./util/FleekStrings.sol";
|
||||
import "./FleekPausable.sol";
|
||||
import "./FleekAccessPoints.sol";
|
||||
import "./util/FleekStrings.sol";
|
||||
import "./IERCX.sol";
|
||||
|
||||
error AccessPointNotExistent();
|
||||
error AccessPointAlreadyExists();
|
||||
error AccessPointScoreCannotBeLower();
|
||||
error MustBeAccessPointOwner();
|
||||
error MustBeTokenOwner(uint256 tokenId);
|
||||
error MustBeTokenVerifier(uint256 tokenId);
|
||||
error ThereIsNoTokenMinted();
|
||||
error InvalidTokenIdForAccessPoint();
|
||||
error AccessPointCreationStatusAlreadySet();
|
||||
|
||||
contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, FleekPausable, FleekBilling {
|
||||
contract FleekERC721 is
|
||||
IERCX,
|
||||
Initializable,
|
||||
ERC721Upgradeable,
|
||||
FleekAccessControl,
|
||||
FleekPausable,
|
||||
FleekBilling,
|
||||
FleekAccessPoints
|
||||
{
|
||||
using Strings for uint256;
|
||||
using FleekStrings for FleekERC721.App;
|
||||
using FleekStrings for FleekERC721.AccessPoint;
|
||||
using FleekStrings for FleekERC721.Token;
|
||||
using FleekStrings for string;
|
||||
using FleekStrings for uint24;
|
||||
|
||||
|
|
@ -38,87 +42,15 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
|||
uint24 color,
|
||||
bool accessPointAutoApproval,
|
||||
address indexed minter,
|
||||
address indexed owner
|
||||
);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, string value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, uint24 value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, string[2] value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, bool value, address indexed triggeredBy);
|
||||
|
||||
event NewAccessPoint(string apName, uint256 indexed tokenId, address indexed owner);
|
||||
event RemoveAccessPoint(string apName, uint256 indexed tokenId, address indexed owner);
|
||||
|
||||
event ChangeAccessPointScore(string apName, uint256 indexed tokenId, uint256 score, address indexed triggeredBy);
|
||||
|
||||
event ChangeAccessPointNameVerify(
|
||||
string apName,
|
||||
uint256 tokenId,
|
||||
bool indexed verified,
|
||||
address indexed triggeredBy
|
||||
);
|
||||
event ChangeAccessPointContentVerify(
|
||||
string apName,
|
||||
uint256 tokenId,
|
||||
bool indexed verified,
|
||||
address indexed triggeredBy
|
||||
);
|
||||
event ChangeAccessPointCreationStatus(
|
||||
string apName,
|
||||
uint256 tokenId,
|
||||
AccessPointCreationStatus status,
|
||||
address indexed triggeredBy
|
||||
address indexed owner,
|
||||
address verifier
|
||||
);
|
||||
|
||||
/**
|
||||
* The properties are stored as string to keep consistency with
|
||||
* other token contracts, we might consider changing for bytes32
|
||||
* in the future due to gas optimization.
|
||||
*/
|
||||
struct App {
|
||||
string name; // Name of the site
|
||||
string description; // Description about the site
|
||||
string externalURL; // Site URL
|
||||
string ENS; // ENS ID
|
||||
uint256 currentBuild; // The current build number (Increments by one with each change, starts at zero)
|
||||
mapping(uint256 => Build) builds; // Mapping to build details for each build number
|
||||
string logo;
|
||||
uint24 color; // Color of the nft
|
||||
bool accessPointAutoApproval; // AP Auto Approval
|
||||
}
|
||||
|
||||
/**
|
||||
* The metadata that is stored for each build.
|
||||
*/
|
||||
struct Build {
|
||||
string commitHash;
|
||||
string gitRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creation status enums for access points
|
||||
*/
|
||||
enum AccessPointCreationStatus {
|
||||
DRAFT,
|
||||
APPROVED,
|
||||
REJECTED,
|
||||
REMOVED
|
||||
}
|
||||
|
||||
/**
|
||||
* The stored data for each AccessPoint.
|
||||
*/
|
||||
struct AccessPoint {
|
||||
uint256 tokenId;
|
||||
uint256 score;
|
||||
bool contentVerified;
|
||||
bool nameVerified;
|
||||
address owner;
|
||||
AccessPointCreationStatus status;
|
||||
}
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, address value, address indexed triggeredBy);
|
||||
|
||||
uint256 private _appIds;
|
||||
mapping(uint256 => App) private _apps;
|
||||
mapping(string => AccessPoint) private _accessPoints;
|
||||
mapping(uint256 => Token) private _apps;
|
||||
mapping(uint256 => address) private _tokenVerifier;
|
||||
|
||||
/**
|
||||
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
|
||||
|
|
@ -135,10 +67,10 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
|||
}
|
||||
|
||||
/**
|
||||
* @dev Checks if the AccessPoint exists.
|
||||
* @dev Checks if caller is the verifier of the token.
|
||||
*/
|
||||
modifier requireAP(string memory apName) {
|
||||
if (_accessPoints[apName].owner == address(0)) revert AccessPointNotExistent();
|
||||
modifier requireTokenVerifier(uint256 tokenId) {
|
||||
if (_tokenVerifier[tokenId] != msg.sender) revert MustBeTokenVerifier(tokenId);
|
||||
_;
|
||||
}
|
||||
|
||||
|
|
@ -164,25 +96,26 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
|||
string memory gitRepository,
|
||||
string memory logo,
|
||||
uint24 color,
|
||||
bool accessPointAutoApproval
|
||||
bool accessPointAutoApproval,
|
||||
address verifier
|
||||
) public payable requirePayment(Billing.Mint) returns (uint256) {
|
||||
uint256 tokenId = _appIds;
|
||||
_mint(to, tokenId);
|
||||
|
||||
_appIds += 1;
|
||||
|
||||
App storage app = _apps[tokenId];
|
||||
Token storage app = _apps[tokenId];
|
||||
app.name = name;
|
||||
app.description = description;
|
||||
app.externalURL = externalURL;
|
||||
app.ENS = ENS;
|
||||
app.logo = logo;
|
||||
app.color = color;
|
||||
app.accessPointAutoApproval = accessPointAutoApproval;
|
||||
|
||||
// The mint interaction is considered to be the first build of the site. Updates from now on all increment the currentBuild by one and update the mapping.
|
||||
app.currentBuild = 0;
|
||||
app.builds[0] = Build(commitHash, gitRepository);
|
||||
|
||||
emit NewMint(
|
||||
tokenId,
|
||||
name,
|
||||
|
|
@ -195,8 +128,13 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
|||
color,
|
||||
accessPointAutoApproval,
|
||||
msg.sender,
|
||||
to
|
||||
to,
|
||||
verifier
|
||||
);
|
||||
|
||||
_tokenVerifier[tokenId] = verifier;
|
||||
_setAccessPointAutoApproval(tokenId, accessPointAutoApproval);
|
||||
|
||||
return tokenId;
|
||||
}
|
||||
|
||||
|
|
@ -210,12 +148,13 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
|||
* - 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(ERC721Upgradeable, IERCX) returns (string memory) {
|
||||
_requireMinted(tokenId);
|
||||
address owner = ownerOf(tokenId);
|
||||
App storage app = _apps[tokenId];
|
||||
bool accessPointAutoApproval = _getAccessPointAutoApproval(tokenId);
|
||||
Token storage app = _apps[tokenId];
|
||||
|
||||
return string(abi.encodePacked(_baseURI(), app.toString(owner).toBase64()));
|
||||
return string(abi.encodePacked(_baseURI(), app.toString(owner, accessPointAutoApproval).toBase64()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -237,7 +176,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
|||
returns (string memory, string memory, string memory, string memory, uint256, string memory, uint24)
|
||||
{
|
||||
_requireMinted(tokenId);
|
||||
App storage app = _apps[tokenId];
|
||||
Token storage app = _apps[tokenId];
|
||||
return (app.name, app.description, app.externalURL, app.ENS, app.currentBuild, app.logo, app.color);
|
||||
}
|
||||
|
||||
|
|
@ -287,26 +226,6 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
|||
return "data:application/json;base64,";
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates the `accessPointAutoApproval` settings on minted `tokenId`.
|
||||
*
|
||||
* May emit a {MetadataUpdate} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the tokenId must be minted and valid.
|
||||
* - the sender must have the `tokenController` role.
|
||||
*
|
||||
*/
|
||||
function setAccessPointAutoApproval(
|
||||
uint256 tokenId,
|
||||
bool _apAutoApproval
|
||||
) public virtual requireTokenOwner(tokenId) {
|
||||
_requireMinted(tokenId);
|
||||
_apps[tokenId].accessPointAutoApproval = _apAutoApproval;
|
||||
emit MetadataUpdate(tokenId, "accessPointAutoApproval", _apAutoApproval, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates the `externalURL` metadata field of a minted `tokenId`.
|
||||
*
|
||||
|
|
@ -443,197 +362,6 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
|||
setTokenColor(tokenId, _tokenColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Add a new AccessPoint register for an app token.
|
||||
* The AP name should be a DNS or ENS url and it should be unique.
|
||||
* Anyone can add an AP but it should requires a payment.
|
||||
*
|
||||
* May emit a {NewAccessPoint} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the tokenId must be minted and valid.
|
||||
* - billing for add acess point may be applied.
|
||||
* - the contract must be not paused.
|
||||
*
|
||||
*/
|
||||
function addAccessPoint(
|
||||
uint256 tokenId,
|
||||
string memory apName
|
||||
) public payable whenNotPaused requirePayment(Billing.AddAccessPoint) {
|
||||
// require(msg.value == 0.1 ether, "You need to pay at least 0.1 ETH"); // TODO: define a minimum price
|
||||
_requireMinted(tokenId);
|
||||
if (_accessPoints[apName].owner != address(0)) revert AccessPointAlreadyExists();
|
||||
|
||||
emit NewAccessPoint(apName, tokenId, msg.sender);
|
||||
|
||||
if (_apps[tokenId].accessPointAutoApproval) {
|
||||
// Auto Approval is on.
|
||||
_accessPoints[apName] = AccessPoint(
|
||||
tokenId,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
msg.sender,
|
||||
AccessPointCreationStatus.APPROVED
|
||||
);
|
||||
|
||||
emit ChangeAccessPointCreationStatus(apName, tokenId, AccessPointCreationStatus.APPROVED, msg.sender);
|
||||
} else {
|
||||
// Auto Approval is off. Should wait for approval.
|
||||
_accessPoints[apName] = AccessPoint(tokenId, 0, false, false, msg.sender, AccessPointCreationStatus.DRAFT);
|
||||
emit ChangeAccessPointCreationStatus(apName, tokenId, AccessPointCreationStatus.DRAFT, msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set approval settings for an access point.
|
||||
* It will add the access point to the token's AP list, if `approved` is true.
|
||||
*
|
||||
* May emit a {ChangeAccessPointApprovalStatus} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the tokenId must exist and be the same as the tokenId that is set for the AP.
|
||||
* - the AP must exist.
|
||||
* - must be called by a token controller.
|
||||
*/
|
||||
function setApprovalForAccessPoint(
|
||||
uint256 tokenId,
|
||||
string memory apName,
|
||||
bool approved
|
||||
) public requireTokenOwner(tokenId) {
|
||||
AccessPoint storage accessPoint = _accessPoints[apName];
|
||||
if (accessPoint.tokenId != tokenId) revert InvalidTokenIdForAccessPoint();
|
||||
if (accessPoint.status != AccessPointCreationStatus.DRAFT) revert AccessPointCreationStatusAlreadySet();
|
||||
|
||||
if (approved) {
|
||||
// Approval
|
||||
accessPoint.status = AccessPointCreationStatus.APPROVED;
|
||||
emit ChangeAccessPointCreationStatus(apName, tokenId, AccessPointCreationStatus.APPROVED, msg.sender);
|
||||
} else {
|
||||
// Not Approved
|
||||
accessPoint.status = AccessPointCreationStatus.REJECTED;
|
||||
emit ChangeAccessPointCreationStatus(apName, tokenId, AccessPointCreationStatus.REJECTED, msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Remove an AccessPoint registry for an app token.
|
||||
* It will also remove the AP from the app token APs list.
|
||||
*
|
||||
* May emit a {RemoveAccessPoint} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the AP must exist.
|
||||
* - must be called by the AP owner.
|
||||
* - the contract must be not paused.
|
||||
*
|
||||
*/
|
||||
function removeAccessPoint(string memory apName) public whenNotPaused requireAP(apName) {
|
||||
if (msg.sender != _accessPoints[apName].owner) revert MustBeAccessPointOwner();
|
||||
_accessPoints[apName].status = AccessPointCreationStatus.REMOVED;
|
||||
uint256 tokenId = _accessPoints[apName].tokenId;
|
||||
emit ChangeAccessPointCreationStatus(apName, tokenId, AccessPointCreationStatus.REMOVED, msg.sender);
|
||||
emit RemoveAccessPoint(apName, tokenId, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev A view function to gether information about an AccessPoint.
|
||||
* It returns a JSON string representing the AccessPoint information.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the AP must exist.
|
||||
*
|
||||
*/
|
||||
function getAccessPointJSON(string memory apName) public view requireAP(apName) returns (string memory) {
|
||||
AccessPoint storage _ap = _accessPoints[apName];
|
||||
return _ap.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev A view function to check if a AccessPoint is verified.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the AP must exist.
|
||||
*
|
||||
*/
|
||||
function isAccessPointNameVerified(string memory apName) public view requireAP(apName) returns (bool) {
|
||||
return _accessPoints[apName].nameVerified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Increases the score of a AccessPoint registry.
|
||||
*
|
||||
* May emit a {ChangeAccessPointScore} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the AP must exist.
|
||||
*
|
||||
*/
|
||||
function increaseAccessPointScore(string memory apName) public requireAP(apName) {
|
||||
_accessPoints[apName].score++;
|
||||
emit ChangeAccessPointScore(apName, _accessPoints[apName].tokenId, _accessPoints[apName].score, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Decreases the score of a AccessPoint registry if is greater than 0.
|
||||
*
|
||||
* May emit a {ChangeAccessPointScore} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the AP must exist.
|
||||
*
|
||||
*/
|
||||
function decreaseAccessPointScore(string memory apName) public requireAP(apName) {
|
||||
if (_accessPoints[apName].score == 0) revert AccessPointScoreCannotBeLower();
|
||||
_accessPoints[apName].score--;
|
||||
emit ChangeAccessPointScore(apName, _accessPoints[apName].tokenId, _accessPoints[apName].score, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set the content verification of a AccessPoint registry.
|
||||
*
|
||||
* May emit a {ChangeAccessPointContentVerify} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the AP must exist.
|
||||
* - the sender must have the token controller role.
|
||||
*
|
||||
*/
|
||||
function setAccessPointContentVerify(
|
||||
string memory apName,
|
||||
bool verified
|
||||
) public requireAP(apName) requireTokenRole(_accessPoints[apName].tokenId, TokenRoles.Controller) {
|
||||
_accessPoints[apName].contentVerified = verified;
|
||||
emit ChangeAccessPointContentVerify(apName, _accessPoints[apName].tokenId, verified, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set the name verification of a AccessPoint registry.
|
||||
*
|
||||
* May emit a {ChangeAccessPointNameVerify} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the AP must exist.
|
||||
* - the sender must have the token controller role.
|
||||
*
|
||||
*/
|
||||
function setAccessPointNameVerify(
|
||||
string memory apName,
|
||||
bool verified
|
||||
) public requireAP(apName) requireTokenRole(_accessPoints[apName].tokenId, TokenRoles.Controller) {
|
||||
_accessPoints[apName].nameVerified = verified;
|
||||
emit ChangeAccessPointNameVerify(apName, _accessPoints[apName].tokenId, verified, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Adds a new build to a minted `tokenId`'s builds mapping.
|
||||
*
|
||||
|
|
@ -675,6 +403,156 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets an address as verifier of a token.
|
||||
* The verifier must have `CollectionRoles.Verifier` role.
|
||||
*
|
||||
* May emit a {MetadataUpdate} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the tokenId must be minted and valid.
|
||||
* - the sender must be the owner of the token.
|
||||
* - the verifier must have `CollectionRoles.Verifier` role.
|
||||
*
|
||||
*/
|
||||
function setTokenVerifier(uint256 tokenId, address verifier) public requireTokenOwner(tokenId) {
|
||||
if (!hasCollectionRole(CollectionRoles.Verifier, verifier))
|
||||
revert MustHaveCollectionRole(uint8(CollectionRoles.Verifier));
|
||||
_requireMinted(tokenId);
|
||||
_tokenVerifier[tokenId] = verifier;
|
||||
emit MetadataUpdate(tokenId, "verifier", verifier, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the verifier of a token.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the tokenId must be minted and valid.
|
||||
*
|
||||
*/
|
||||
function getTokenVerifier(uint256 tokenId) public view returns (address) {
|
||||
_requireMinted(tokenId);
|
||||
return _tokenVerifier[tokenId];
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////
|
||||
ACCESS POINTS
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* @dev Add a new AccessPoint register for an app token.
|
||||
* The AP name should be a DNS or ENS url and it should be unique.
|
||||
* Anyone can add an AP but it should requires a payment.
|
||||
*
|
||||
* May emit a {NewAccessPoint} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the tokenId must be minted and valid.
|
||||
* - billing for add acess point may be applied.
|
||||
* - the contract must be not paused.
|
||||
*
|
||||
*/
|
||||
function addAccessPoint(
|
||||
uint256 tokenId,
|
||||
string memory apName
|
||||
) public payable whenNotPaused requirePayment(Billing.AddAccessPoint) {
|
||||
_requireMinted(tokenId);
|
||||
_addAccessPoint(tokenId, apName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Remove an AccessPoint registry for an app token.
|
||||
* It will also remove the AP from the app token APs list.
|
||||
*
|
||||
* May emit a {RemoveAccessPoint} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the AP must exist.
|
||||
* - must be called by the AP owner.
|
||||
* - the contract must be not paused.
|
||||
*
|
||||
*/
|
||||
function removeAccessPoint(string memory apName) public whenNotPaused {
|
||||
_removeAccessPoint(apName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates the `accessPointAutoApproval` settings on minted `tokenId`.
|
||||
*
|
||||
* May emit a {MetadataUpdate} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the tokenId must be minted and valid.
|
||||
* - the sender must have the `tokenController` role.
|
||||
*
|
||||
*/
|
||||
function setAccessPointAutoApproval(uint256 tokenId, bool _apAutoApproval) public requireTokenOwner(tokenId) {
|
||||
_requireMinted(tokenId);
|
||||
_setAccessPointAutoApproval(tokenId, _apAutoApproval);
|
||||
emit MetadataUpdate(tokenId, "accessPointAutoApproval", _apAutoApproval, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set approval settings for an access point.
|
||||
* It will add the access point to the token's AP list, if `approved` is true.
|
||||
*
|
||||
* May emit a {ChangeAccessPointApprovalStatus} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the tokenId must exist and be the same as the tokenId that is set for the AP.
|
||||
* - the AP must exist.
|
||||
* - must be called by a token controller.
|
||||
*/
|
||||
function setApprovalForAccessPoint(
|
||||
uint256 tokenId,
|
||||
string memory apName,
|
||||
bool approved
|
||||
) public requireTokenOwner(tokenId) {
|
||||
_setApprovalForAccessPoint(tokenId, apName, approved);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set the content verification of a AccessPoint registry.
|
||||
*
|
||||
* May emit a {ChangeAccessPointContentVerify} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the AP must exist.
|
||||
* - the sender must have the token controller role.
|
||||
*
|
||||
*/
|
||||
function setAccessPointContentVerify(
|
||||
string memory apName,
|
||||
bool verified
|
||||
) public requireCollectionRole(CollectionRoles.Verifier) requireTokenVerifier(_getAccessPointTokenId(apName)) {
|
||||
_setAccessPointContentVerify(apName, verified);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set the name verification of a AccessPoint registry.
|
||||
*
|
||||
* May emit a {ChangeAccessPointNameVerify} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the AP must exist.
|
||||
* - the sender must have the token controller role.
|
||||
*
|
||||
*/
|
||||
function setAccessPointNameVerify(
|
||||
string memory apName,
|
||||
bool verified
|
||||
) public requireCollectionRole(CollectionRoles.Verifier) requireTokenVerifier(_getAccessPointTokenId(apName)) {
|
||||
_setAccessPointNameVerify(apName, verified);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////
|
||||
ACCESS CONTROL
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
||||
|
||||
/**
|
||||
* @title ERCX Interface
|
||||
* @author
|
||||
* @notice
|
||||
*
|
||||
* ERCX is a standard for NFTs that represent websites. It is a standard that
|
||||
* allows for the storage of metadata about a website, and allows for the
|
||||
* storage of multiple builds of a website. This allows for the NFT to be
|
||||
* used as a way to store the history of a website.
|
||||
*/
|
||||
interface IERCX {
|
||||
/**
|
||||
* Event emitted when a token's metadata is updated.
|
||||
* @param _tokenId the updated token id.
|
||||
* @param key which metadata key was updated
|
||||
* @param value the new value of the metadata
|
||||
* @param triggeredBy the address that triggered the update
|
||||
*/
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, string value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, uint24 value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, string[2] value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, bool value, address indexed triggeredBy);
|
||||
|
||||
/**
|
||||
* The metadata that is stored for each build.
|
||||
*/
|
||||
struct Build {
|
||||
string commitHash;
|
||||
string gitRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* The properties are stored as string to keep consistency with
|
||||
* other token contracts, we might consider changing for bytes32
|
||||
* in the future due to gas optimization.
|
||||
*/
|
||||
struct Token {
|
||||
string name; // Name of the site
|
||||
string description; // Description about the site
|
||||
string externalURL; // Site URL
|
||||
string ENS; // ENS for the site
|
||||
string logo; // Branding logo
|
||||
uint24 color; // Branding color
|
||||
uint256 currentBuild; // The current build number (Increments by one with each change, starts at zero)
|
||||
mapping(uint256 => Build) builds; // Mapping to build details for each build number
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets a minted token's external URL.
|
||||
*/
|
||||
function setTokenExternalURL(uint256 tokenId, string memory _tokenExternalURL) external;
|
||||
|
||||
/**
|
||||
* @dev Sets a minted token's ENS.
|
||||
*/
|
||||
function setTokenENS(uint256 tokenId, string memory _tokenENS) external;
|
||||
|
||||
/**
|
||||
* @dev Sets a minted token's name.
|
||||
*/
|
||||
function setTokenName(uint256 tokenId, string memory _tokenName) external;
|
||||
|
||||
/**
|
||||
* @dev Sets a minted token's description.
|
||||
*/
|
||||
function setTokenDescription(uint256 tokenId, string memory _tokenDescription) external;
|
||||
|
||||
/**
|
||||
* @dev Sets a minted token's logo.
|
||||
*/
|
||||
function setTokenLogo(uint256 tokenId, string memory _tokenLogo) external;
|
||||
|
||||
/**
|
||||
* @dev Sets a minted token's color.
|
||||
*/
|
||||
function setTokenColor(uint256 tokenId, uint24 _tokenColor) external;
|
||||
|
||||
/**
|
||||
* @dev Sets a minted token's build.
|
||||
*/
|
||||
function setTokenBuild(uint256 tokenId, string memory commitHash, string memory gitRepository) external;
|
||||
|
||||
/**
|
||||
* @dev Returns the token metadata for a given tokenId.
|
||||
* It must return a valid JSON object in string format encoded in Base64.
|
||||
*/
|
||||
function tokenURI(uint256 tokenId) external returns (string memory);
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "../FleekERC721.sol";
|
||||
import "@openzeppelin/contracts/utils/Strings.sol";
|
||||
import "@openzeppelin/contracts/utils/Base64.sol";
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "../FleekERC721.sol";
|
||||
import "@openzeppelin/contracts/utils/Strings.sol";
|
||||
import "@openzeppelin/contracts/utils/Base64.sol";
|
||||
import "./FleekSVG.sol";
|
||||
import "../IERCX.sol";
|
||||
import "../FleekAccessPoints.sol";
|
||||
|
||||
library FleekStrings {
|
||||
using Strings for uint256;
|
||||
|
|
@ -29,10 +30,14 @@ library FleekStrings {
|
|||
}
|
||||
|
||||
/**
|
||||
* @dev Converts FleekERC721.App to a JSON string.
|
||||
* @dev Converts IERCX.Token to a JSON string.
|
||||
* It requires to receive owner address as a parameter.
|
||||
*/
|
||||
function toString(FleekERC721.App storage app, address owner) internal view returns (string memory) {
|
||||
function toString(
|
||||
IERCX.Token storage app,
|
||||
address owner,
|
||||
bool accessPointAutoApproval
|
||||
) internal view returns (string memory) {
|
||||
// prettier-ignore
|
||||
return string(abi.encodePacked(
|
||||
'{',
|
||||
|
|
@ -41,7 +46,7 @@ library FleekStrings {
|
|||
'"owner":"', uint160(owner).toHexString(20), '",',
|
||||
'"external_url":"', app.externalURL, '",',
|
||||
'"image":"', FleekSVG.generateBase64(app.name, app.ENS, app.logo, app.color.toColorString()), '",',
|
||||
'"access_point_auto_approval":',app.accessPointAutoApproval.toString(),',',
|
||||
'"access_point_auto_approval":', accessPointAutoApproval.toString(),',',
|
||||
'"attributes": [',
|
||||
'{"trait_type": "ENS", "value":"', app.ENS,'"},',
|
||||
'{"trait_type": "Commit Hash", "value":"', app.builds[app.currentBuild].commitHash,'"},',
|
||||
|
|
@ -54,9 +59,9 @@ library FleekStrings {
|
|||
}
|
||||
|
||||
/**
|
||||
* @dev Converts FleekERC721.AccessPoint to a JSON string.
|
||||
* @dev Converts FleekAccessPoints.AccessPoint to a JSON string.
|
||||
*/
|
||||
function toString(FleekERC721.AccessPoint storage ap) internal view returns (string memory) {
|
||||
function toString(FleekAccessPoints.AccessPoint storage ap) internal view returns (string memory) {
|
||||
// prettier-ignore
|
||||
return string(abi.encodePacked(
|
||||
"{",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,9 @@
|
|||
{
|
||||
"FleekERC721": [
|
||||
{
|
||||
"address": "0x37150709cFf366DeEaB836d05CAf49F4DA46Bb2E",
|
||||
"timestamp": "3/3/2023, 4:43:25 PM"
|
||||
},
|
||||
{
|
||||
"address": "0x550Ee47Fa9E0B81c1b9C394FeE62Fe699a955519",
|
||||
"timestamp": "2/24/2023, 5:28:44 PM"
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -8,6 +8,8 @@ import 'hardhat-contract-sizer';
|
|||
import '@openzeppelin/hardhat-upgrades';
|
||||
import * as dotenv from 'dotenv';
|
||||
import { HardhatUserConfig } from 'hardhat/types';
|
||||
import { task, types } from 'hardhat/config';
|
||||
import deploy from './scripts/deploy';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
|
@ -65,3 +67,16 @@ const config: HardhatUserConfig = {
|
|||
};
|
||||
|
||||
export default config;
|
||||
|
||||
// npx hardhat deploy --network mumbai --new-proxy-instance --name "FleekNFAs" --symbol "FLKNFA" --billing "[10000, 20000]"
|
||||
task('deploy', 'Deploy the contracts')
|
||||
.addFlag('newProxyInstance', 'Force to deploy a new proxy instance')
|
||||
.addOptionalParam('name', 'The collection name', 'FleekNFAs', types.string)
|
||||
.addOptionalParam('symbol', 'The collection symbol', 'FLKNFA', types.string)
|
||||
.addOptionalParam(
|
||||
'billing',
|
||||
'The billing values in an array of numbers like "[10000, 20000]"',
|
||||
[],
|
||||
types.json
|
||||
)
|
||||
.setAction(deploy);
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
"test:hardhat": "hardhat test",
|
||||
"format": "prettier --write \"./**/*.{js,json,sol,ts}\"",
|
||||
"node:hardhat": "hardhat node",
|
||||
"deploy:hardhat": "hardhat run scripts/deploy.js --network hardhat",
|
||||
"deploy:mumbai": "hardhat run scripts/deploy.js --network mumbai",
|
||||
"deploy:hardhat": "hardhat deploy --network hardhat",
|
||||
"deploy:mumbai": "hardhat deploy --network mumbai",
|
||||
"compile": "hardhat compile",
|
||||
"verify:mumbai": "npx hardhat run ./scripts/verify-polyscan.js --network mumbai"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,22 +4,14 @@ const {
|
|||
} = require('./utils/deploy-store');
|
||||
const { getProxyAddress, proxyStore } = require('./utils/proxy-store');
|
||||
|
||||
// --- Inputs ---
|
||||
const ARGUMENTS = [
|
||||
'FleekNFAs', // Collection name
|
||||
'FLKNFA', // Collection symbol
|
||||
[], // Billing values
|
||||
];
|
||||
|
||||
// --- Script Settings ---
|
||||
const CONTRACT_NAME = 'FleekERC721';
|
||||
const NETWORK = hre.network.name;
|
||||
const DEFAULT_PROXY_SETTINGS = {
|
||||
unsafeAllow: ['external-library-linking'],
|
||||
};
|
||||
const LIBRARIES_TO_DEPLOY = ['FleekSVG'];
|
||||
|
||||
const libraryDeployment = async () => {
|
||||
const libraryDeployment = async (hre) => {
|
||||
console.log('Deploying Libraries...');
|
||||
const libraries = {};
|
||||
|
||||
|
|
@ -33,7 +25,7 @@ const libraryDeployment = async () => {
|
|||
const libContract = await hre.ethers.getContractFactory(lib);
|
||||
const libInstance = await libContract.deploy();
|
||||
await libInstance.deployed();
|
||||
deployStore(NETWORK, lib, libInstance);
|
||||
deployStore(hre.network.name, lib, libInstance);
|
||||
console.log(`Library "${lib}" deployed at ${libInstance.address}`);
|
||||
libraries[lib] = libInstance.address;
|
||||
}
|
||||
|
|
@ -41,55 +33,70 @@ const libraryDeployment = async () => {
|
|||
return libraries;
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
console.log(':: Starting Deployment ::');
|
||||
console.log('Network:', NETWORK);
|
||||
console.log('Contract:', CONTRACT_NAME);
|
||||
module.exports = async (taskArgs, hre) => {
|
||||
const { newProxyInstance, name, symbol, billing } = taskArgs;
|
||||
const network = hre.network.name;
|
||||
|
||||
const libraries = await libraryDeployment();
|
||||
console.log(':: Starting Deployment ::');
|
||||
console.log('Network:', network);
|
||||
console.log('Contract:', CONTRACT_NAME);
|
||||
console.log(':: Arguments ::');
|
||||
console.log(taskArgs);
|
||||
console.log();
|
||||
|
||||
const arguments = [name, symbol, billing];
|
||||
|
||||
const libraries = await libraryDeployment(hre);
|
||||
|
||||
const Contract = await ethers.getContractFactory(CONTRACT_NAME, {
|
||||
libraries,
|
||||
});
|
||||
const proxyAddress = await getProxyAddress(CONTRACT_NAME, NETWORK);
|
||||
const proxyAddress = await getProxyAddress(CONTRACT_NAME, network);
|
||||
|
||||
let deployResult;
|
||||
|
||||
try {
|
||||
if (!proxyAddress) throw new Error('No proxy address found');
|
||||
if (!proxyAddress || newProxyInstance)
|
||||
throw new Error('new-proxy-instance');
|
||||
console.log(`Trying to upgrade proxy contract at: "${proxyAddress}"`);
|
||||
deployResult = await upgrades.upgradeProxy(
|
||||
proxyAddress,
|
||||
Contract,
|
||||
DEFAULT_PROXY_SETTINGS
|
||||
);
|
||||
|
||||
console.log('\x1b[32m');
|
||||
console.log(
|
||||
`Contract ${CONTRACT_NAME} upgraded at "${deployResult.address}" by account "${deployResult.signer.address}"`
|
||||
);
|
||||
await deployStore(NETWORK, CONTRACT_NAME, deployResult);
|
||||
console.log('\x1b[0m');
|
||||
await deployStore(network, CONTRACT_NAME, deployResult);
|
||||
} catch (e) {
|
||||
if (
|
||||
e.message === 'No proxy address found' ||
|
||||
e.message === 'new-proxy-instance' ||
|
||||
e.message.includes("doesn't look like an ERC 1967 proxy")
|
||||
) {
|
||||
console.log(`Failed to upgrade proxy contract: "${e.message?.trim()}"`);
|
||||
console.log('Creating new proxy contract...');
|
||||
deployResult = await upgrades.deployProxy(
|
||||
Contract,
|
||||
ARGUMENTS,
|
||||
arguments,
|
||||
DEFAULT_PROXY_SETTINGS
|
||||
);
|
||||
await deployResult.deployed();
|
||||
await proxyStore(CONTRACT_NAME, deployResult.address, hre.network.name);
|
||||
await proxyStore(CONTRACT_NAME, deployResult.address, network);
|
||||
|
||||
console.log('\x1b[32m');
|
||||
console.log(
|
||||
`Contract ${CONTRACT_NAME} deployed at "${deployResult.address}" by account "${deployResult.signer.address}"`
|
||||
);
|
||||
console.log('\x1b[0m');
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
await deployStore(NETWORK, CONTRACT_NAME, deployResult);
|
||||
await deployStore(network, CONTRACT_NAME, deployResult);
|
||||
} catch (e) {
|
||||
console.error('Could not write deploy files', e);
|
||||
}
|
||||
|
|
@ -97,5 +104,3 @@ const main = async () => {
|
|||
|
||||
return deployResult;
|
||||
};
|
||||
|
||||
main();
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ const DEFAULT_MINTS = {
|
|||
|
||||
const params = DEFAULT_MINTS.fleek;
|
||||
const mintTo = '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049';
|
||||
const verifier = '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049';
|
||||
|
||||
(async () => {
|
||||
const contract = await getContract('FleekERC721');
|
||||
|
|
@ -90,6 +91,8 @@ const mintTo = '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049';
|
|||
params.push(await getSVGBase64(svgPath));
|
||||
console.log('SVG length: ', params[params.length - 1].length);
|
||||
params.push(await getSVGColor(svgPath));
|
||||
params.push(false);
|
||||
params.push(verifier);
|
||||
|
||||
const transaction = await contract.mint(...params);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ contract Test_FleekERC721_AccessControlAssertions is Test {
|
|||
contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC721_AccessControlAssertions {
|
||||
uint256 internal tokenId;
|
||||
address internal collectionOwner = address(100);
|
||||
address internal collectionVerifier = address(101);
|
||||
address internal tokenOwner = address(200);
|
||||
address internal tokenController = address(300);
|
||||
address internal anyAddress = address(400);
|
||||
|
|
@ -28,28 +29,45 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
|
||||
// Set collectionOwner
|
||||
CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner);
|
||||
// Set collectionVerifier
|
||||
CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Verifier, collectionVerifier);
|
||||
// Mint to tokenOwner to set tokenOwner
|
||||
mintDefault(tokenOwner);
|
||||
// Set tokenController to minted token
|
||||
vm.prank(tokenOwner);
|
||||
vm.startPrank(tokenOwner);
|
||||
CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenController);
|
||||
CuT.setTokenVerifier(tokenId, collectionVerifier);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_setUp() public {
|
||||
// Check deployer
|
||||
assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, deployer));
|
||||
assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, deployer));
|
||||
|
||||
// Check collectionOwner
|
||||
assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner));
|
||||
assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, collectionOwner));
|
||||
assertFalse(CuT.ownerOf(tokenId) == collectionOwner);
|
||||
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, collectionOwner));
|
||||
// Check collectionVerifier
|
||||
assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionVerifier));
|
||||
assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, collectionVerifier));
|
||||
assertFalse(CuT.ownerOf(tokenId) == collectionVerifier);
|
||||
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, collectionVerifier));
|
||||
// Check tokenOwner
|
||||
assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, tokenOwner));
|
||||
assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, tokenOwner));
|
||||
assertTrue(CuT.ownerOf(tokenId) == tokenOwner);
|
||||
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenOwner));
|
||||
// Check tokenController
|
||||
assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, tokenController));
|
||||
assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, tokenController));
|
||||
assertFalse(CuT.ownerOf(tokenId) == tokenController);
|
||||
assertTrue(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenController));
|
||||
// Check anyAddress
|
||||
assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, anyAddress));
|
||||
assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, anyAddress));
|
||||
assertFalse(CuT.ownerOf(tokenId) == anyAddress);
|
||||
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, anyAddress));
|
||||
}
|
||||
|
|
@ -65,6 +83,14 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress));
|
||||
vm.stopPrank();
|
||||
|
||||
// CollectionVerifier
|
||||
vm.startPrank(collectionVerifier);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
|
||||
vm.stopPrank();
|
||||
|
||||
// TokenOwner
|
||||
vm.startPrank(tokenOwner);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
|
|
@ -101,6 +127,14 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
|
||||
vm.stopPrank();
|
||||
|
||||
// CollectionVerifier
|
||||
vm.startPrank(collectionVerifier);
|
||||
expectRevertWithMustBeTokenOwner(tokenId);
|
||||
CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
|
||||
expectRevertWithMustBeTokenOwner(tokenId);
|
||||
CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
|
||||
vm.stopPrank();
|
||||
|
||||
// TokenOwner
|
||||
vm.startPrank(tokenOwner);
|
||||
CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
|
||||
|
|
@ -147,6 +181,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenExternalURL(tokenId, externalURL);
|
||||
|
||||
// VerifierRole
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenExternalURL(tokenId, externalURL);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
CuT.setTokenExternalURL(tokenId, externalURL);
|
||||
|
|
@ -169,6 +208,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenENS(tokenId, ens);
|
||||
|
||||
// VerifierRole
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenENS(tokenId, ens);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
CuT.setTokenENS(tokenId, ens);
|
||||
|
|
@ -191,6 +235,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenName(tokenId, name);
|
||||
|
||||
// VerifierRole
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenName(tokenId, name);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
CuT.setTokenName(tokenId, name);
|
||||
|
|
@ -213,6 +262,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenDescription(tokenId, description);
|
||||
|
||||
// VerifierRole
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenDescription(tokenId, description);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
CuT.setTokenDescription(tokenId, description);
|
||||
|
|
@ -235,6 +289,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenLogo(tokenId, logo);
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenLogo(tokenId, logo);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
CuT.setTokenLogo(tokenId, logo);
|
||||
|
|
@ -257,6 +316,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenColor(tokenId, color);
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenColor(tokenId, color);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
CuT.setTokenColor(tokenId, color);
|
||||
|
|
@ -280,6 +344,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenLogoAndColor(tokenId, logo, color);
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenLogoAndColor(tokenId, logo, color);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
CuT.setTokenLogoAndColor(tokenId, logo, color);
|
||||
|
|
@ -303,6 +372,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
|
||||
|
|
@ -323,6 +397,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
expectRevertWithMustBeTokenOwner(tokenId);
|
||||
CuT.burn(tokenId);
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithMustBeTokenOwner(tokenId);
|
||||
CuT.burn(tokenId);
|
||||
|
||||
// TokenController
|
||||
vm.prank(tokenController);
|
||||
expectRevertWithMustBeTokenOwner(tokenId);
|
||||
|
|
@ -338,11 +417,74 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
CuT.burn(tokenId);
|
||||
}
|
||||
|
||||
function test_setAccessPointContentVerify() public {
|
||||
string memory apName = "random.com";
|
||||
CuT.addAccessPoint(tokenId, apName);
|
||||
|
||||
// CollectionOwner
|
||||
vm.prank(collectionOwner);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointContentVerify(apName, true);
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
CuT.setAccessPointContentVerify(apName, true);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointContentVerify(apName, false);
|
||||
|
||||
// TokenController
|
||||
vm.prank(tokenController);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointContentVerify(apName, false);
|
||||
|
||||
// AnyAddress
|
||||
vm.prank(anyAddress);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointContentVerify(apName, false);
|
||||
}
|
||||
|
||||
function test_setAccessPointNameVerify() public {
|
||||
string memory apName = "random.com";
|
||||
CuT.addAccessPoint(tokenId, apName);
|
||||
|
||||
// CollectionOwner
|
||||
vm.prank(collectionOwner);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointNameVerify(apName, true);
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
CuT.setAccessPointNameVerify(apName, true);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointNameVerify(apName, false);
|
||||
|
||||
// TokenController
|
||||
vm.prank(tokenController);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointNameVerify(apName, false);
|
||||
|
||||
// AnyAddress
|
||||
vm.prank(anyAddress);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointNameVerify(apName, false);
|
||||
}
|
||||
|
||||
function test_setBilling() public {
|
||||
// ColletionOwner
|
||||
vm.prank(collectionOwner);
|
||||
CuT.setBilling(FleekBilling.Billing.Mint, 1 ether);
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
CuT.setBilling(FleekBilling.Billing.Mint, 2 ether);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
|
|
@ -365,6 +507,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
vm.prank(collectionOwner);
|
||||
CuT.withdraw();
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
CuT.withdraw();
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
|
|
@ -381,13 +528,6 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
CuT.withdraw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev `receive` and `fallback` are required for test contract receive ETH
|
||||
*/
|
||||
receive() external payable {}
|
||||
|
||||
fallback() external payable {}
|
||||
|
||||
function test_pauseAndUnpause() public {
|
||||
// ColletionOwner
|
||||
vm.startPrank(collectionOwner);
|
||||
|
|
@ -395,6 +535,14 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
CuT.unpause();
|
||||
vm.stopPrank();
|
||||
|
||||
// CollectionVerifier
|
||||
vm.startPrank(collectionVerifier);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
CuT.pause();
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
CuT.unpause();
|
||||
vm.stopPrank();
|
||||
|
||||
// TokenOwner
|
||||
vm.startPrank(tokenOwner);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
|
|
@ -425,6 +573,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
vm.prank(collectionOwner);
|
||||
CuT.setPausable(false);
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
CuT.setPausable(true);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
|
||||
|
|
@ -464,4 +617,11 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
vm.prank(tokenOwner);
|
||||
CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, anyAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev `receive` and `fallback` are required for test contract receive ETH
|
||||
*/
|
||||
receive() external payable {}
|
||||
|
||||
fallback() external payable {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,13 +169,22 @@ contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base, APConstants {
|
|||
CuT.addAccessPoint(tokenId, accessPointName);
|
||||
|
||||
vm.startPrank(randomAddress);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointNameVerify(accessPointName, true);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointContentVerify(accessPointName, true);
|
||||
vm.stopPrank();
|
||||
|
||||
CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
|
||||
CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Verifier, randomAddress);
|
||||
|
||||
vm.startPrank(randomAddress);
|
||||
expectRevertWithMustBeTokenVerifier(tokenId);
|
||||
CuT.setAccessPointNameVerify(accessPointName, true);
|
||||
expectRevertWithMustBeTokenVerifier(tokenId);
|
||||
CuT.setAccessPointContentVerify(accessPointName, true);
|
||||
vm.stopPrank();
|
||||
|
||||
CuT.setTokenVerifier(tokenId, randomAddress);
|
||||
|
||||
vm.startPrank(randomAddress);
|
||||
CuT.setAccessPointNameVerify(accessPointName, true);
|
||||
|
|
|
|||
|
|
@ -171,13 +171,22 @@ contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base, APConstants {
|
|||
CuT.addAccessPoint(tokenId, accessPointName);
|
||||
|
||||
vm.startPrank(randomAddress);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointNameVerify(accessPointName, true);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||
CuT.setAccessPointContentVerify(accessPointName, true);
|
||||
vm.stopPrank();
|
||||
|
||||
CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
|
||||
CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Verifier, randomAddress);
|
||||
|
||||
vm.startPrank(randomAddress);
|
||||
expectRevertWithMustBeTokenVerifier(tokenId);
|
||||
CuT.setAccessPointNameVerify(accessPointName, true);
|
||||
expectRevertWithMustBeTokenVerifier(tokenId);
|
||||
CuT.setAccessPointContentVerify(accessPointName, true);
|
||||
vm.stopPrank();
|
||||
|
||||
CuT.setTokenVerifier(tokenId, randomAddress);
|
||||
|
||||
vm.startPrank(randomAddress);
|
||||
CuT.setAccessPointNameVerify(accessPointName, true);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
|
|||
contract APConstants is Test {
|
||||
using Strings for address;
|
||||
|
||||
function expectRevertWithMustBeTokenVerifier(uint256 tokenId) public {
|
||||
vm.expectRevert(abi.encodeWithSelector(MustBeTokenVerifier.selector, tokenId));
|
||||
}
|
||||
|
||||
function assertAccessPointJSON(
|
||||
string memory accessPointName,
|
||||
string memory _tokenId,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,8 @@ contract Test_FleekERC721_Billing is Test_FleekERC721_Base, Test_FleekERC721_Bil
|
|||
TestConstants.APP_GIT_REPOSITORY,
|
||||
TestConstants.LOGO_0,
|
||||
TestConstants.APP_COLOR,
|
||||
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS
|
||||
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS,
|
||||
deployer
|
||||
);
|
||||
assertEq(CuT.ownerOf(tokenId), deployer);
|
||||
assertEq(address(CuT).balance, mintPrice);
|
||||
|
|
@ -76,7 +77,8 @@ contract Test_FleekERC721_Billing is Test_FleekERC721_Base, Test_FleekERC721_Bil
|
|||
TestConstants.APP_GIT_REPOSITORY,
|
||||
TestConstants.LOGO_0,
|
||||
TestConstants.APP_COLOR,
|
||||
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS
|
||||
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS,
|
||||
deployer
|
||||
);
|
||||
assertEq(address(CuT).balance, 0);
|
||||
}
|
||||
|
|
@ -98,7 +100,8 @@ contract Test_FleekERC721_Billing is Test_FleekERC721_Base, Test_FleekERC721_Bil
|
|||
TestConstants.APP_GIT_REPOSITORY,
|
||||
TestConstants.LOGO_0,
|
||||
TestConstants.APP_COLOR,
|
||||
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS
|
||||
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS,
|
||||
deployer
|
||||
);
|
||||
assertEq(CuT.ownerOf(tokenId), deployer);
|
||||
assertEq(address(CuT).balance, value);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
|
|||
"https://github.com/a-different/repository",
|
||||
TestConstants.LOGO_1,
|
||||
0x654321,
|
||||
false
|
||||
false,
|
||||
deployer
|
||||
);
|
||||
|
||||
assertEq(firstMint, 0);
|
||||
|
|
@ -54,7 +55,8 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
|
|||
"https://github.com/a-different/repository",
|
||||
TestConstants.LOGO_1,
|
||||
0x654321,
|
||||
true
|
||||
true,
|
||||
deployer
|
||||
);
|
||||
|
||||
assertEq(mint, 0);
|
||||
|
|
@ -91,7 +93,8 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
|
|||
gitRepository,
|
||||
logo,
|
||||
color,
|
||||
autoApprovalAp
|
||||
autoApprovalAp,
|
||||
deployer
|
||||
);
|
||||
assertEq(tokenId, 0);
|
||||
assertEq(CuT.ownerOf(tokenId), to);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ abstract contract Test_FleekERC721_Base is Test, Test_FleekERC721_Assertions {
|
|||
TestConstants.APP_GIT_REPOSITORY,
|
||||
TestConstants.LOGO_0,
|
||||
TestConstants.APP_COLOR,
|
||||
false // Auto Approval Is OFF
|
||||
false, // Auto Approval Is OFF
|
||||
deployer
|
||||
);
|
||||
|
||||
return mint;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
|
||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
|
||||
import { expect } from 'chai';
|
||||
import { ethers } from 'hardhat';
|
||||
import { TestConstants, Fixtures, Errors } from '../helpers';
|
||||
const { AccessPointStatus } = TestConstants;
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ describe('FleekERC721.Billing', () => {
|
|||
MintParams.logo,
|
||||
MintParams.color,
|
||||
MintParams.accessPointAutoApprovalSettings,
|
||||
owner.address,
|
||||
{ value }
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -175,4 +175,40 @@ describe('FleekERC721.CollectionRoles', () => {
|
|||
contract.revokeCollectionRole(CollectionRoles.Owner, owner.address)
|
||||
).to.be.revertedWithCustomError(contract, Errors.MustHaveAtLeastOneOwner);
|
||||
});
|
||||
|
||||
it('should not be able to verify access point if not verifier', async () => {
|
||||
const { contract, otherAccount, owner } = fixture;
|
||||
|
||||
await contract.mint(
|
||||
otherAccount.address,
|
||||
TestConstants.MintParams.name,
|
||||
TestConstants.MintParams.description,
|
||||
TestConstants.MintParams.externalUrl,
|
||||
TestConstants.MintParams.ens,
|
||||
TestConstants.MintParams.commitHash,
|
||||
TestConstants.MintParams.gitRepository,
|
||||
TestConstants.MintParams.logo,
|
||||
TestConstants.MintParams.color,
|
||||
TestConstants.MintParams.accessPointAutoApprovalSettings,
|
||||
owner.address
|
||||
);
|
||||
|
||||
await contract.addAccessPoint(0, 'random.com');
|
||||
|
||||
await expect(
|
||||
contract
|
||||
.connect(otherAccount)
|
||||
.setAccessPointContentVerify('random.com', true)
|
||||
)
|
||||
.to.be.revertedWithCustomError(contract, Errors.MustHaveCollectionRole)
|
||||
.withArgs(CollectionRoles.Verifier);
|
||||
|
||||
await expect(
|
||||
contract
|
||||
.connect(otherAccount)
|
||||
.setAccessPointNameVerify('random.com', true)
|
||||
)
|
||||
.to.be.revertedWithCustomError(contract, Errors.MustHaveCollectionRole)
|
||||
.withArgs(CollectionRoles.Verifier);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
|
||||
import { expect } from 'chai';
|
||||
import { TestConstants, Fixtures, Errors } from './helpers';
|
||||
import { ethers } from 'hardhat';
|
||||
|
||||
const { MintParams, Roles } = TestConstants;
|
||||
|
||||
describe('FleekERC721.GetLastTokenId', () => {
|
||||
let fixture: Awaited<ReturnType<typeof Fixtures.default>>;
|
||||
|
|
@ -19,7 +16,8 @@ describe('FleekERC721.GetLastTokenId', () => {
|
|||
TestConstants.MintParams.gitRepository,
|
||||
TestConstants.MintParams.logo,
|
||||
TestConstants.MintParams.color,
|
||||
false
|
||||
false,
|
||||
fixture.owner.address
|
||||
);
|
||||
|
||||
return response;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export const TestConstants = Object.freeze({
|
||||
CollectionRoles: {
|
||||
Owner: 0,
|
||||
Verifier: 1,
|
||||
},
|
||||
TokenRoles: {
|
||||
Controller: 0,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ import { ethers, upgrades } from 'hardhat';
|
|||
import { TestConstants } from './constants';
|
||||
|
||||
export abstract class Fixtures {
|
||||
static async paused() {}
|
||||
|
||||
static async default() {
|
||||
// Contracts are deployed using the first signer/account by default
|
||||
const [owner, otherAccount] = await ethers.getSigners();
|
||||
|
|
@ -45,7 +43,8 @@ export abstract class Fixtures {
|
|||
TestConstants.MintParams.gitRepository,
|
||||
TestConstants.MintParams.logo,
|
||||
TestConstants.MintParams.color,
|
||||
TestConstants.MintParams.accessPointAutoApprovalSettings
|
||||
TestConstants.MintParams.accessPointAutoApprovalSettings,
|
||||
fromDefault.owner.address
|
||||
);
|
||||
|
||||
const tokenId = response.value.toNumber();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
|
||||
import { expect } from 'chai';
|
||||
import { TestConstants, Fixtures, Errors } from './helpers';
|
||||
import { TestConstants, Fixtures } from './helpers';
|
||||
import { ethers } from 'hardhat';
|
||||
|
||||
const { MintParams, CollectionRoles } = TestConstants;
|
||||
const { MintParams } = TestConstants;
|
||||
|
||||
describe('FleekERC721.Minting', () => {
|
||||
it('should be able to mint a new token', async () => {
|
||||
|
|
@ -19,7 +19,8 @@ describe('FleekERC721.Minting', () => {
|
|||
MintParams.gitRepository,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
MintParams.accessPointAutoApprovalSettings
|
||||
MintParams.accessPointAutoApprovalSettings,
|
||||
owner.address
|
||||
);
|
||||
|
||||
expect(response.value).to.be.instanceOf(ethers.BigNumber);
|
||||
|
|
@ -41,7 +42,8 @@ describe('FleekERC721.Minting', () => {
|
|||
MintParams.gitRepository,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
MintParams.accessPointAutoApprovalSettings
|
||||
MintParams.accessPointAutoApprovalSettings,
|
||||
owner.address
|
||||
);
|
||||
|
||||
const tokenId = response.value.toNumber();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ describe('FleekERC721.Pausable', () => {
|
|||
MintParams.gitRepository,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
false
|
||||
false,
|
||||
owner.address
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"include": ["./**/*"]
|
||||
}
|
||||
12
package.json
12
package.json
|
|
@ -4,6 +4,7 @@
|
|||
"description": "",
|
||||
"private": "false",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write \"./**/*.{js,json,sol,ts}\"",
|
||||
"postinstall": "husky install",
|
||||
"prepack": "pinst --disable",
|
||||
|
|
@ -21,9 +22,16 @@
|
|||
"homepage": "https://github.com/fleekxyz/non-fungible-apps#readme",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^2.16.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.1",
|
||||
"@typescript-eslint/parser": "^5.54.1",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-config-prettier": "^8.7.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"husky": "^8.0.2",
|
||||
"pinst": "^3.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-solidity": "^1.0.0"
|
||||
"prettier": "^2.8.4",
|
||||
"prettier-plugin-solidity": "^1.0.0",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ build
|
|||
generated
|
||||
abis
|
||||
examples/query/.graphclient
|
||||
tests/matchstick/.bin
|
||||
|
|
@ -0,0 +1 @@
|
|||
yarn-error.log
|
||||
|
|
@ -31,7 +31,8 @@ type NewMint @entity(immutable: true) {
|
|||
color: Int!
|
||||
accessPointAutoApproval: Boolean!
|
||||
triggeredBy: Bytes! # address
|
||||
tokenOwner: Owner! # address
|
||||
owner: Owner! # address
|
||||
verifier: Bytes!
|
||||
blockNumber: BigInt!
|
||||
blockTimestamp: BigInt!
|
||||
transactionHash: Bytes!
|
||||
|
|
@ -78,29 +79,27 @@ type Token @entity {
|
|||
gitRepository: GitRepository!
|
||||
commitHash: String!
|
||||
accessPoints: [AccessPoint!] @derivedFrom(field: "token")
|
||||
verifier: Verifier! # Address
|
||||
}
|
||||
|
||||
# Owner entity for collection, access points, and tokens
|
||||
type Owner @entity {
|
||||
id: Bytes! # address
|
||||
tokens: [Token!] @derivedFrom(field: "owner")
|
||||
accessPoints: [AccessPoint!] @derivedFrom(field: "owner")
|
||||
collection: Boolean!
|
||||
}
|
||||
|
||||
# Controller entity for tokens
|
||||
type Controller @entity {
|
||||
id: Bytes! # address
|
||||
tokens: [Token!] @derivedFrom(field: "controllers")
|
||||
}
|
||||
type Collection @entity {
|
||||
id: Bytes! #address
|
||||
deployer: Bytes! #address
|
||||
transactionHash: Bytes! #transaction hash
|
||||
owners: [CollectionOwner!]
|
||||
}
|
||||
|
||||
type CollectionOwner @entity {
|
||||
# Verifier entity for tokens
|
||||
type Verifier @entity {
|
||||
id: Bytes! # address
|
||||
accessGrantedBy: Bytes! #address
|
||||
transactionHash: Bytes!
|
||||
tokens: [Token!] @derivedFrom(field: "verifier")
|
||||
}
|
||||
|
||||
type GitRepository @entity {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
import {
|
||||
Address,
|
||||
Bytes,
|
||||
log,
|
||||
store,
|
||||
ethereum,
|
||||
BigInt,
|
||||
} from '@graphprotocol/graph-ts';
|
||||
|
||||
// Event Imports [based on the yaml config]
|
||||
import {
|
||||
TokenRoleChanged as TokenRoleChangedEvent,
|
||||
CollectionRoleChanged as CollectionRoleChangedEvent,
|
||||
} from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import {
|
||||
Owner,
|
||||
Token,
|
||||
} from '../generated/schema';
|
||||
|
||||
enum CollectionRoles {
|
||||
Owner,
|
||||
}
|
||||
|
||||
enum TokenRoles {
|
||||
Controller,
|
||||
}
|
||||
|
||||
export function handleCollectionRoleChanged(
|
||||
event: CollectionRoleChangedEvent
|
||||
): void {
|
||||
let toAddress = event.params.toAddress;
|
||||
let byAddress = event.params.byAddress;
|
||||
let role = event.params.role;
|
||||
let status = event.params.status;
|
||||
|
||||
if (role === CollectionRoles.Owner) {
|
||||
// Owner role
|
||||
if (status) {
|
||||
// granted
|
||||
let owner = Owner.load(toAddress);
|
||||
if (!owner) {
|
||||
owner = new Owner(toAddress);
|
||||
}
|
||||
owner.collection = true;
|
||||
owner.save();
|
||||
} else {
|
||||
// revoked
|
||||
let owner = Owner.load(toAddress);
|
||||
if (!owner) {
|
||||
log.error(
|
||||
'Owner entity not found. Role: {}, byAddress: {}, toAddress: {}',
|
||||
[role.toString(), byAddress.toHexString(), toAddress.toHexString()]
|
||||
);
|
||||
return;
|
||||
}
|
||||
owner.collection = false;
|
||||
owner.save();
|
||||
}
|
||||
} else {
|
||||
log.error('Role not supported. Role: {}, byAddress: {}, toAddress: {}', [
|
||||
role.toString(),
|
||||
byAddress.toHexString(),
|
||||
toAddress.toHexString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleTokenRoleChanged(event: TokenRoleChangedEvent): void {
|
||||
let tokenId = event.params.tokenId;
|
||||
let toAddress = event.params.toAddress;
|
||||
let byAddress = event.params.byAddress;
|
||||
let role = event.params.role;
|
||||
let status = event.params.status;
|
||||
|
||||
// load token
|
||||
let token = Token.load(Bytes.fromByteArray(Bytes.fromBigInt(tokenId)));
|
||||
if (!token) {
|
||||
log.error('Token not found. TokenId: {}', [tokenId.toString()]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (role === TokenRoles.Controller) {
|
||||
// Controller role
|
||||
// get the list of controllers.
|
||||
let token_controllers = token.controllers;
|
||||
if (!token_controllers) {
|
||||
token_controllers = [];
|
||||
}
|
||||
if (status) {
|
||||
// granted
|
||||
token_controllers.push(toAddress);
|
||||
} else {
|
||||
// revoked
|
||||
// remove address from the controllers list
|
||||
const index = token_controllers.indexOf(event.params.toAddress, 0);
|
||||
if (index > -1) {
|
||||
token_controllers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
token.controllers = token_controllers;
|
||||
} else {
|
||||
log.error('Role not supported. Role: {}, byAddress: {}, toAddress: {}', [
|
||||
role.toString(),
|
||||
byAddress.toHexString(),
|
||||
toAddress.toHexString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
import {
|
||||
Address,
|
||||
Bytes,
|
||||
log,
|
||||
store,
|
||||
ethereum,
|
||||
BigInt,
|
||||
} from '@graphprotocol/graph-ts';
|
||||
|
||||
// Event Imports [based on the yaml config]
|
||||
import {
|
||||
ChangeAccessPointCreationStatus as ChangeAccessPointCreationStatusEvent,
|
||||
ChangeAccessPointScore as ChangeAccessPointCreationScoreEvent,
|
||||
NewAccessPoint as NewAccessPointEvent,
|
||||
ChangeAccessPointNameVerify as ChangeAccessPointNameVerifyEvent,
|
||||
ChangeAccessPointContentVerify as ChangeAccessPointContentVerifyEvent,
|
||||
} from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import {
|
||||
AccessPoint,
|
||||
Owner,
|
||||
} from '../generated/schema';
|
||||
|
||||
/**
|
||||
* This handler will create and load entities in the following order:
|
||||
* - AccessPoint [create]
|
||||
* - Owner [load / create]
|
||||
* Note to discuss later: Should a `NewAccessPoint` entity be also created and defined?
|
||||
*/
|
||||
export function handleNewAccessPoint(event: NewAccessPointEvent): void {
|
||||
// Create an AccessPoint entity
|
||||
let accessPointEntity = new AccessPoint(event.params.apName);
|
||||
accessPointEntity.score = BigInt.fromU32(0);
|
||||
accessPointEntity.contentVerified = false;
|
||||
accessPointEntity.nameVerified = false;
|
||||
accessPointEntity.creationStatus = 'DRAFT'; // Since a `ChangeAccessPointCreationStatus` event is emitted instantly after `NewAccessPoint`, the status will be updated in that handler.
|
||||
accessPointEntity.owner = event.params.owner;
|
||||
accessPointEntity.token = Bytes.fromByteArray(
|
||||
Bytes.fromBigInt(event.params.tokenId)
|
||||
);
|
||||
|
||||
// Load / Create an Owner entity
|
||||
let ownerEntity = Owner.load(event.params.owner);
|
||||
|
||||
if (!ownerEntity) {
|
||||
// Create a new owner entity
|
||||
ownerEntity = new Owner(event.params.owner);
|
||||
// Since no CollectionRoleChanged event was emitted before for this address, we can set `collection` to false.
|
||||
ownerEntity.collection = false;
|
||||
}
|
||||
|
||||
// Save entities.
|
||||
accessPointEntity.save();
|
||||
ownerEntity.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler will update the status of an access point entity.
|
||||
*/
|
||||
export function handleChangeAccessPointCreationStatus(
|
||||
event: ChangeAccessPointCreationStatusEvent
|
||||
): void {
|
||||
// Load the AccessPoint entity
|
||||
let accessPointEntity = AccessPoint.load(event.params.apName);
|
||||
let status = event.params.status;
|
||||
|
||||
if (accessPointEntity) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
accessPointEntity.creationStatus = 'DRAFT';
|
||||
break;
|
||||
case 1:
|
||||
accessPointEntity.creationStatus = 'APPROVED';
|
||||
break;
|
||||
case 2:
|
||||
accessPointEntity.creationStatus = 'REJECTED';
|
||||
break;
|
||||
case 3:
|
||||
accessPointEntity.creationStatus = 'REMOVED';
|
||||
break;
|
||||
default:
|
||||
// Unknown status
|
||||
log.error(
|
||||
'Unable to handle ChangeAccessPointCreationStatus. Unknown status. Status: {}, AccessPoint: {}',
|
||||
[status.toString(), event.params.apName]
|
||||
);
|
||||
}
|
||||
|
||||
accessPointEntity.save();
|
||||
} else {
|
||||
// Unknown access point
|
||||
log.error(
|
||||
'Unable to handle ChangeAccessPointCreationStatus. Unknown access point. Status: {}, AccessPoint: {}',
|
||||
[status.toString(), event.params.apName]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler will update the score of an access point entity.
|
||||
*/
|
||||
export function handleChangeAccessPointScore(
|
||||
event: ChangeAccessPointCreationScoreEvent
|
||||
): void {
|
||||
// Load the AccessPoint entity
|
||||
let accessPointEntity = AccessPoint.load(event.params.apName);
|
||||
|
||||
if (accessPointEntity) {
|
||||
accessPointEntity.score = event.params.score;
|
||||
accessPointEntity.save();
|
||||
} else {
|
||||
// Unknown access point
|
||||
log.error(
|
||||
'Unable to handle ChangeAccessPointScore. Unknown access point. Score: {}, AccessPoint: {}',
|
||||
[event.params.score.toString(), event.params.apName]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler will update the nameVerified field of an access point entity.
|
||||
*/
|
||||
export function handleChangeAccessPointNameVerify(
|
||||
event: ChangeAccessPointNameVerifyEvent
|
||||
): void {
|
||||
// Load the AccessPoint entity
|
||||
let accessPointEntity = AccessPoint.load(event.params.apName);
|
||||
|
||||
if (accessPointEntity) {
|
||||
accessPointEntity.nameVerified = event.params.verified;
|
||||
accessPointEntity.save();
|
||||
} else {
|
||||
// Unknown access point
|
||||
log.error(
|
||||
'Unable to handle ChangeAccessPointNameVerify. Unknown access point. Verified: {}, AccessPoint: {}',
|
||||
[event.params.verified.toString(), event.params.apName]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler will update the contentVerified field of an access point entity.
|
||||
*/
|
||||
export function handleChangeAccessPointContentVerify(
|
||||
event: ChangeAccessPointContentVerifyEvent
|
||||
): void {
|
||||
// Load the AccessPoint entity
|
||||
let accessPointEntity = AccessPoint.load(event.params.apName);
|
||||
|
||||
if (accessPointEntity) {
|
||||
accessPointEntity.contentVerified = event.params.verified;
|
||||
accessPointEntity.save();
|
||||
} else {
|
||||
// Unknown access point
|
||||
log.error(
|
||||
'Unable to handle ChangeAccessPointContentVerify. Unknown access point. Verified: {}, AccessPoint: {}',
|
||||
[event.params.verified.toString(), event.params.apName]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Event Imports [based on the yaml config]
|
||||
import {
|
||||
Approval as ApprovalEvent,
|
||||
ApprovalForAll as ApprovalForAllEvent,
|
||||
} from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import {
|
||||
Approval,
|
||||
ApprovalForAll,
|
||||
} from '../generated/schema';
|
||||
|
||||
export function handleApproval(event: ApprovalEvent): void {
|
||||
let entity = new Approval(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
entity.owner = event.params.owner;
|
||||
entity.approved = event.params.approved;
|
||||
entity.tokenId = event.params.tokenId;
|
||||
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
}
|
||||
|
||||
export function handleApprovalForAll(event: ApprovalForAllEvent): void {
|
||||
let entity = new ApprovalForAll(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
entity.owner = event.params.owner;
|
||||
entity.operator = event.params.operator;
|
||||
entity.approved = event.params.approved;
|
||||
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import {
|
||||
log,
|
||||
ethereum,
|
||||
} from '@graphprotocol/graph-ts';
|
||||
|
||||
// Event Imports [based on the yaml config]
|
||||
import {
|
||||
Initialized as InitializedEvent,
|
||||
} from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import {
|
||||
Owner,
|
||||
} from '../generated/schema';
|
||||
export function handleInitialized(event: InitializedEvent): void {
|
||||
// This is the contract creation transaction.
|
||||
log.warning('This is the contract creation transaction.', []);
|
||||
if (event.receipt) {
|
||||
let receipt = event.receipt as ethereum.TransactionReceipt;
|
||||
log.warning('Contract address is: {}', [
|
||||
receipt.contractAddress.toHexString(),
|
||||
]);
|
||||
|
||||
// add owner
|
||||
let owner = new Owner(event.transaction.from);
|
||||
owner.collection = true;
|
||||
owner.save();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,437 +1,7 @@
|
|||
import { Address, Bytes, log, store, ethereum, BigInt } from '@graphprotocol/graph-ts';
|
||||
import {
|
||||
Approval as ApprovalEvent,
|
||||
ApprovalForAll as ApprovalForAllEvent,
|
||||
MetadataUpdate as MetadataUpdateEvent,
|
||||
MetadataUpdate1 as MetadataUpdateEvent1,
|
||||
MetadataUpdate2 as MetadataUpdateEvent2,
|
||||
MetadataUpdate3 as MetadataUpdateEvent3,
|
||||
Transfer as TransferEvent,
|
||||
NewMint as NewMintEvent,
|
||||
ChangeAccessPointCreationStatus as ChangeAccessPointCreationStatusEvent,
|
||||
ChangeAccessPointScore as ChangeAccessPointCreationScoreEvent,
|
||||
NewAccessPoint as NewAccessPointEvent,
|
||||
ChangeAccessPointNameVerify as ChangeAccessPointNameVerifyEvent,
|
||||
ChangeAccessPointContentVerify as ChangeAccessPointContentVerifyEvent,
|
||||
} from '../generated/FleekNFA/FleekNFA';
|
||||
import {
|
||||
AccessPoint,
|
||||
Approval,
|
||||
ApprovalForAll,
|
||||
Collection,
|
||||
CollectionOwner,
|
||||
Controller,
|
||||
GitRepository as GitRepositoryEntity,
|
||||
MetadataUpdate,
|
||||
NewMint,
|
||||
Owner,
|
||||
Token,
|
||||
Transfer,
|
||||
} from '../generated/schema';
|
||||
|
||||
export function handleApproval(event: ApprovalEvent): void {
|
||||
let entity = new Approval(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
entity.owner = event.params.owner;
|
||||
entity.approved = event.params.approved;
|
||||
entity.tokenId = event.params.tokenId;
|
||||
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
}
|
||||
|
||||
export function handleApprovalForAll(event: ApprovalForAllEvent): void {
|
||||
let entity = new ApprovalForAll(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
entity.owner = event.params.owner;
|
||||
entity.operator = event.params.operator;
|
||||
entity.approved = event.params.approved;
|
||||
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
}
|
||||
|
||||
export function handleNewMint(event: NewMintEvent): void {
|
||||
let newMintEntity = new NewMint(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
let name = event.params.name;
|
||||
let description = event.params.description;
|
||||
let externalURL = event.params.externalURL;
|
||||
let ENS = event.params.ENS;
|
||||
let gitRepository = event.params.gitRepository;
|
||||
let commitHash = event.params.commitHash;
|
||||
let logo = event.params.logo;
|
||||
let color = event.params.color;
|
||||
let accessPointAutoApproval = event.params.accessPointAutoApproval;
|
||||
let tokenId = event.params.tokenId;
|
||||
let ownerAddress = event.params.owner;
|
||||
|
||||
newMintEntity.tokenId = tokenId;
|
||||
newMintEntity.name = name;
|
||||
newMintEntity.description = description;
|
||||
newMintEntity.externalURL = externalURL;
|
||||
newMintEntity.ENS = ENS;
|
||||
newMintEntity.commitHash = commitHash;
|
||||
newMintEntity.gitRepository = gitRepository;
|
||||
newMintEntity.logo = logo;
|
||||
newMintEntity.color = color;
|
||||
newMintEntity.accessPointAutoApproval = accessPointAutoApproval;
|
||||
newMintEntity.triggeredBy = event.params.minter;
|
||||
newMintEntity.tokenOwner = ownerAddress;
|
||||
newMintEntity.blockNumber = event.block.number;
|
||||
newMintEntity.blockTimestamp = event.block.timestamp;
|
||||
newMintEntity.transactionHash = event.transaction.hash;
|
||||
newMintEntity.save();
|
||||
log.error('{}', [tokenId.toString()]);
|
||||
|
||||
// Create Token, Owner, and Controller entities
|
||||
|
||||
let owner = Owner.load(ownerAddress);
|
||||
let controller = Controller.load(ownerAddress);
|
||||
let gitRepositoryEntity = GitRepositoryEntity.load(gitRepository);
|
||||
let token = new Token(Bytes.fromByteArray(Bytes.fromBigInt(tokenId)));
|
||||
|
||||
if (!owner) {
|
||||
// Create a new owner entity
|
||||
owner = new Owner(ownerAddress);
|
||||
}
|
||||
|
||||
if (!controller) {
|
||||
// Create a new controller entity
|
||||
controller = new Controller(ownerAddress);
|
||||
}
|
||||
|
||||
if (!gitRepositoryEntity) {
|
||||
// Create a new gitRepository entity
|
||||
gitRepositoryEntity = new GitRepositoryEntity(gitRepository);
|
||||
}
|
||||
|
||||
// Populate Token with data from the event
|
||||
token.tokenId = tokenId;
|
||||
token.name = name;
|
||||
token.description = description;
|
||||
token.externalURL = externalURL;
|
||||
token.ENS = ENS;
|
||||
token.gitRepository = gitRepository;
|
||||
token.commitHash = commitHash;
|
||||
token.logo = logo;
|
||||
token.color = color;
|
||||
token.accessPointAutoApproval = accessPointAutoApproval;
|
||||
token.owner = ownerAddress;
|
||||
token.mintTransaction = event.transaction.hash.concatI32(
|
||||
event.logIndex.toI32()
|
||||
);
|
||||
token.mintedBy = event.params.minter;
|
||||
token.controllers = [ownerAddress];
|
||||
|
||||
// Save entities
|
||||
owner.save();
|
||||
controller.save();
|
||||
gitRepositoryEntity.save();
|
||||
token.save();
|
||||
}
|
||||
|
||||
export function handleMetadataUpdateWithStringValue(
|
||||
event: MetadataUpdateEvent
|
||||
): void {
|
||||
/**
|
||||
* Metadata handled here:
|
||||
* setTokenExternalURL
|
||||
* setTokenENS
|
||||
* setTokenName
|
||||
* setTokenDescription
|
||||
* setTokenLogo
|
||||
* */
|
||||
let entity = new MetadataUpdate(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
entity.tokenId = event.params._tokenId;
|
||||
entity.key = event.params.key;
|
||||
entity.stringValue = event.params.value;
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
|
||||
// UPDATE TOKEN
|
||||
let token = Token.load(
|
||||
Bytes.fromByteArray(Bytes.fromBigInt(event.params._tokenId))
|
||||
);
|
||||
|
||||
if (token) {
|
||||
if (event.params.key == 'externalURL') {
|
||||
token.externalURL = event.params.value;
|
||||
} else if (event.params.key == 'ENS') {
|
||||
token.ENS = event.params.value;
|
||||
} else if (event.params.key == 'name') {
|
||||
token.name = event.params.value;
|
||||
} else if (event.params.key == 'description') {
|
||||
token.description = event.params.value;
|
||||
} else {
|
||||
// logo
|
||||
token.logo = event.params.value;
|
||||
}
|
||||
token.save();
|
||||
}
|
||||
}
|
||||
|
||||
export function handleMetadataUpdateWithDoubleStringValue(
|
||||
event: MetadataUpdateEvent2
|
||||
): void {
|
||||
/**
|
||||
* setTokenBuild
|
||||
*/
|
||||
let entity = new MetadataUpdate(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
entity.key = event.params.key;
|
||||
entity.tokenId = event.params._tokenId;
|
||||
entity.doubleStringValue = event.params.value;
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
|
||||
// UPDATE TOKEN
|
||||
let token = Token.load(
|
||||
Bytes.fromByteArray(Bytes.fromBigInt(event.params._tokenId))
|
||||
);
|
||||
|
||||
if (token) {
|
||||
if (event.params.key == 'build') {
|
||||
let gitRepositoryEntity = GitRepositoryEntity.load(event.params.value[1]);
|
||||
if (!gitRepositoryEntity) {
|
||||
// Create a new gitRepository entity
|
||||
gitRepositoryEntity = new GitRepositoryEntity(event.params.value[1]);
|
||||
}
|
||||
token.commitHash = event.params.value[0];
|
||||
token.gitRepository = event.params.value[1];
|
||||
token.save();
|
||||
gitRepositoryEntity.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function handleMetadataUpdateWithIntValue(
|
||||
event: MetadataUpdateEvent1
|
||||
): void {
|
||||
/**
|
||||
* setTokenColor
|
||||
*/
|
||||
let entity = new MetadataUpdate(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
entity.key = event.params.key;
|
||||
entity.tokenId = event.params._tokenId;
|
||||
entity.uint24Value = event.params.value;
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
|
||||
let token = Token.load(
|
||||
Bytes.fromByteArray(Bytes.fromBigInt(event.params._tokenId))
|
||||
);
|
||||
|
||||
if (token) {
|
||||
if (event.params.key == 'color') {
|
||||
token.color = event.params.value;
|
||||
}
|
||||
token.save();
|
||||
}
|
||||
}
|
||||
|
||||
export function handleMetadataUpdateWithBooleanValue(event: MetadataUpdateEvent3): void {
|
||||
/**
|
||||
* accessPointAutoApproval
|
||||
*/
|
||||
let entity = new MetadataUpdate(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
entity.key = event.params.key;
|
||||
entity.tokenId = event.params._tokenId;
|
||||
entity.booleanValue = event.params.value;
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
|
||||
let token = Token.load(Bytes.fromByteArray(Bytes.fromBigInt(event.params._tokenId)));
|
||||
|
||||
if (token) {
|
||||
if (event.params.key == 'accessPointAutoApproval') {
|
||||
token.accessPointAutoApproval = event.params.value;
|
||||
}
|
||||
token.save();
|
||||
}
|
||||
}
|
||||
|
||||
export function handleTransfer(event: TransferEvent): void {
|
||||
let entity = new Transfer(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
entity.from = event.params.from;
|
||||
entity.to = event.params.to;
|
||||
entity.tokenId = event.params.tokenId;
|
||||
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
|
||||
let token: Token | null;
|
||||
|
||||
let owner_address = event.params.to;
|
||||
let owner = Owner.load(owner_address);
|
||||
|
||||
if (!owner) {
|
||||
// Create a new owner entity
|
||||
owner = new Owner(owner_address);
|
||||
}
|
||||
|
||||
if (parseInt(event.params.from.toHexString()) !== 0) {
|
||||
// Transfer
|
||||
|
||||
// Load the Token by using its TokenId
|
||||
token = Token.load(
|
||||
Bytes.fromByteArray(Bytes.fromBigInt(event.params.tokenId))
|
||||
);
|
||||
|
||||
if (token) {
|
||||
// Entity exists
|
||||
token.owner = owner_address;
|
||||
|
||||
// Save both entities
|
||||
owner.save();
|
||||
token.save();
|
||||
} else {
|
||||
// Entity does not exist
|
||||
log.error('Unknown token was transferred.', []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This handler will create and load entities in the following order:
|
||||
* - AccessPoint [create]
|
||||
* - Owner [load / create]
|
||||
* Note to discuss later: Should a `NewAccessPoint` entity be also created and defined?
|
||||
*/
|
||||
export function handleNewAccessPoint(event: NewAccessPointEvent): void {
|
||||
// Create an AccessPoint entity
|
||||
let accessPointEntity = new AccessPoint(event.params.apName);
|
||||
accessPointEntity.score = BigInt.fromU32(0);
|
||||
accessPointEntity.contentVerified = false;
|
||||
accessPointEntity.nameVerified = false;
|
||||
accessPointEntity.status = 'DRAFT'; // Since a `ChangeAccessPointCreationStatus` event is emitted instantly after `NewAccessPoint`, the status will be updated in that handler.
|
||||
accessPointEntity.owner = event.params.owner;
|
||||
accessPointEntity.token = Bytes.fromByteArray(Bytes.fromBigInt(event.params.tokenId));
|
||||
|
||||
// Load / Create an Owner entity
|
||||
let ownerEntity = Owner.load(event.params.owner);
|
||||
|
||||
if (!ownerEntity) {
|
||||
// Create a new owner entity
|
||||
ownerEntity = new Owner(event.params.owner);
|
||||
}
|
||||
|
||||
// Save entities.
|
||||
accessPointEntity.save();
|
||||
ownerEntity.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler will update the status of an access point entity.
|
||||
*/
|
||||
export function handleChangeAccessPointCreationStatus(event: ChangeAccessPointCreationStatusEvent): void {
|
||||
// Load the AccessPoint entity
|
||||
let accessPointEntity = AccessPoint.load(event.params.apName);
|
||||
let status = event.params.status;
|
||||
|
||||
if (accessPointEntity) {
|
||||
if (status == 0) {
|
||||
accessPointEntity.status = 'DRAFT';
|
||||
} else if (status == 1) {
|
||||
accessPointEntity.status = 'APPROVED';
|
||||
} else if (status == 2) {
|
||||
accessPointEntity.status = 'REJECTED';
|
||||
} else if (status == 3) {
|
||||
accessPointEntity.status = 'REMOVED';
|
||||
} else {
|
||||
// Unknown status
|
||||
log.error('Unable to handle ChangeAccessPointCreationStatus. Unknown status. Status: {}, AccessPoint: {}', [status.toString(), event.params.apName]);
|
||||
}
|
||||
accessPointEntity.save();
|
||||
} else {
|
||||
// Unknown access point
|
||||
log.error('Unable to handle ChangeAccessPointCreationStatus. Unknown access point. Status: {}, AccessPoint: {}', [status.toString(), event.params.apName]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler will update the score of an access point entity.
|
||||
*/
|
||||
export function handleChangeAccessPointScore(event: ChangeAccessPointCreationScoreEvent): void {
|
||||
// Load the AccessPoint entity
|
||||
let accessPointEntity = AccessPoint.load(event.params.apName);
|
||||
|
||||
if (accessPointEntity) {
|
||||
accessPointEntity.score = event.params.score;
|
||||
accessPointEntity.save();
|
||||
} else {
|
||||
// Unknown access point
|
||||
log.error('Unable to handle ChangeAccessPointScore. Unknown access point. Score: {}, AccessPoint: {}', [event.params.score.toString(), event.params.apName]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler will update the nameVerified field of an access point entity.
|
||||
*/
|
||||
export function handleChangeAccessPointNameVerify(event: ChangeAccessPointNameVerifyEvent): void {
|
||||
// Load the AccessPoint entity
|
||||
let accessPointEntity = AccessPoint.load(event.params.apName);
|
||||
|
||||
if (accessPointEntity) {
|
||||
accessPointEntity.nameVerified = event.params.verified;
|
||||
accessPointEntity.save();
|
||||
} else {
|
||||
// Unknown access point
|
||||
log.error('Unable to handle ChangeAccessPointNameVerify. Unknown access point. Verified: {}, AccessPoint: {}', [event.params.verified.toString(), event.params.apName]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler will update the contentVerified field of an access point entity.
|
||||
*/
|
||||
export function handleChangeAccessPointContentVerify(event: ChangeAccessPointContentVerifyEvent): void {
|
||||
// Load the AccessPoint entity
|
||||
let accessPointEntity = AccessPoint.load(event.params.apName);
|
||||
|
||||
if (accessPointEntity) {
|
||||
accessPointEntity.contentVerified = event.params.verified;
|
||||
accessPointEntity.save();
|
||||
} else {
|
||||
// Unknown access point
|
||||
log.error('Unable to handle ChangeAccessPointContentVerify. Unknown access point. Verified: {}, AccessPoint: {}', [event.params.verified.toString(), event.params.apName]);
|
||||
}
|
||||
}
|
||||
export * from './access-control';
|
||||
export * from './access-point';
|
||||
export * from './approval';
|
||||
export * from './contract';
|
||||
export * from './metadata-update';
|
||||
export * from './mint';
|
||||
export * from './transfer';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
import { Bytes } from '@graphprotocol/graph-ts';
|
||||
|
||||
// Event Imports [based on the yaml config]
|
||||
import {
|
||||
MetadataUpdate as MetadataUpdateEvent,
|
||||
MetadataUpdate1 as MetadataUpdateEvent1,
|
||||
MetadataUpdate2 as MetadataUpdateEvent2,
|
||||
MetadataUpdate3 as MetadataUpdateEvent3,
|
||||
MetadataUpdate4 as MetadataUpdateEvent4,
|
||||
} from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import {
|
||||
GitRepository as GitRepositoryEntity,
|
||||
MetadataUpdate,
|
||||
Token,
|
||||
} from '../generated/schema';
|
||||
|
||||
export function handleMetadataUpdateWithStringValue(
|
||||
event: MetadataUpdateEvent1
|
||||
): void {
|
||||
/**
|
||||
* Metadata handled here:
|
||||
* setTokenExternalURL
|
||||
* setTokenENS
|
||||
* setTokenName
|
||||
* setTokenDescription
|
||||
* setTokenLogo
|
||||
* */
|
||||
let entity = new MetadataUpdate(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
entity.tokenId = event.params._tokenId;
|
||||
entity.key = event.params.key;
|
||||
entity.stringValue = event.params.value;
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
|
||||
// UPDATE TOKEN
|
||||
let token = Token.load(
|
||||
Bytes.fromByteArray(Bytes.fromBigInt(event.params._tokenId))
|
||||
);
|
||||
|
||||
if (token) {
|
||||
if (event.params.key == 'externalURL') {
|
||||
token.externalURL = event.params.value;
|
||||
} else if (event.params.key == 'ENS') {
|
||||
token.ENS = event.params.value;
|
||||
} else if (event.params.key == 'name') {
|
||||
token.name = event.params.value;
|
||||
} else if (event.params.key == 'description') {
|
||||
token.description = event.params.value;
|
||||
} else {
|
||||
// logo
|
||||
token.logo = event.params.value;
|
||||
}
|
||||
token.save();
|
||||
}
|
||||
}
|
||||
|
||||
export function handleMetadataUpdateWithDoubleStringValue(
|
||||
event: MetadataUpdateEvent3
|
||||
): void {
|
||||
/**
|
||||
* setTokenBuild
|
||||
*/
|
||||
let entity = new MetadataUpdate(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
entity.key = event.params.key;
|
||||
entity.tokenId = event.params._tokenId;
|
||||
entity.doubleStringValue = event.params.value;
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
|
||||
// UPDATE TOKEN
|
||||
let token = Token.load(
|
||||
Bytes.fromByteArray(Bytes.fromBigInt(event.params._tokenId))
|
||||
);
|
||||
|
||||
if (token) {
|
||||
if (event.params.key == 'build') {
|
||||
let gitRepositoryEntity = GitRepositoryEntity.load(event.params.value[1]);
|
||||
if (!gitRepositoryEntity) {
|
||||
// Create a new gitRepository entity
|
||||
gitRepositoryEntity = new GitRepositoryEntity(event.params.value[1]);
|
||||
}
|
||||
token.commitHash = event.params.value[0];
|
||||
token.gitRepository = event.params.value[1];
|
||||
token.save();
|
||||
gitRepositoryEntity.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function handleMetadataUpdateWithIntValue(
|
||||
event: MetadataUpdateEvent2
|
||||
): void {
|
||||
/**
|
||||
* setTokenColor
|
||||
*/
|
||||
let entity = new MetadataUpdate(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
entity.key = event.params.key;
|
||||
entity.tokenId = event.params._tokenId;
|
||||
entity.uint24Value = event.params.value;
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
|
||||
let token = Token.load(
|
||||
Bytes.fromByteArray(Bytes.fromBigInt(event.params._tokenId))
|
||||
);
|
||||
|
||||
if (token) {
|
||||
if (event.params.key == 'color') {
|
||||
token.color = event.params.value;
|
||||
}
|
||||
token.save();
|
||||
}
|
||||
}
|
||||
|
||||
export function handleMetadataUpdateWithBooleanValue(
|
||||
event: MetadataUpdateEvent4
|
||||
): void {
|
||||
/**
|
||||
* accessPointAutoApproval
|
||||
*/
|
||||
let entity = new MetadataUpdate(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
entity.key = event.params.key;
|
||||
entity.tokenId = event.params._tokenId;
|
||||
entity.booleanValue = event.params.value;
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
|
||||
let token = Token.load(
|
||||
Bytes.fromByteArray(Bytes.fromBigInt(event.params._tokenId))
|
||||
);
|
||||
|
||||
if (token) {
|
||||
if (event.params.key == 'accessPointAutoApproval') {
|
||||
token.accessPointAutoApproval = event.params.value;
|
||||
}
|
||||
token.save();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import {
|
||||
Bytes,
|
||||
log,
|
||||
} from '@graphprotocol/graph-ts';
|
||||
|
||||
// Event Imports [based on the yaml config]
|
||||
import {
|
||||
NewMint as NewMintEvent,
|
||||
} from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import {
|
||||
Owner,
|
||||
GitRepository as GitRepositoryEntity,
|
||||
NewMint,
|
||||
Token,
|
||||
} from '../generated/schema';
|
||||
|
||||
export function handleNewMint(event: NewMintEvent): void {
|
||||
let newMintEntity = new NewMint(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
let name = event.params.name;
|
||||
let description = event.params.description;
|
||||
let externalURL = event.params.externalURL;
|
||||
let ENS = event.params.ENS;
|
||||
let gitRepository = event.params.gitRepository;
|
||||
let commitHash = event.params.commitHash;
|
||||
let logo = event.params.logo;
|
||||
let color = event.params.color;
|
||||
let accessPointAutoApproval = event.params.accessPointAutoApproval;
|
||||
let tokenId = event.params.tokenId;
|
||||
let ownerAddress = event.params.owner;
|
||||
let verifierAddress = event.params.verifier;
|
||||
|
||||
newMintEntity.tokenId = tokenId;
|
||||
newMintEntity.name = name;
|
||||
newMintEntity.description = description;
|
||||
newMintEntity.externalURL = externalURL;
|
||||
newMintEntity.ENS = ENS;
|
||||
newMintEntity.commitHash = commitHash;
|
||||
newMintEntity.gitRepository = gitRepository;
|
||||
newMintEntity.logo = logo;
|
||||
newMintEntity.color = color;
|
||||
newMintEntity.accessPointAutoApproval = accessPointAutoApproval;
|
||||
newMintEntity.triggeredBy = event.params.minter;
|
||||
newMintEntity.owner = ownerAddress;
|
||||
newMintEntity.verifier = verifierAddress;
|
||||
newMintEntity.blockNumber = event.block.number;
|
||||
newMintEntity.blockTimestamp = event.block.timestamp;
|
||||
newMintEntity.transactionHash = event.transaction.hash;
|
||||
newMintEntity.save();
|
||||
log.error('{}', [tokenId.toString()]);
|
||||
|
||||
// Create Token, Owner, and Controller entities
|
||||
|
||||
let owner = Owner.load(ownerAddress);
|
||||
let token = new Token(Bytes.fromByteArray(Bytes.fromBigInt(tokenId)));
|
||||
|
||||
if (!owner) {
|
||||
// Create a new owner entity
|
||||
owner = new Owner(ownerAddress);
|
||||
// Since no CollectionRoleChanged event was emitted before for this address, we can set `collection` to false.
|
||||
owner.collection = false;
|
||||
}
|
||||
|
||||
// Populate Token with data from the event
|
||||
token.tokenId = tokenId;
|
||||
token.name = name;
|
||||
token.description = description;
|
||||
token.externalURL = externalURL;
|
||||
token.ENS = ENS;
|
||||
token.gitRepository = gitRepository;
|
||||
token.commitHash = commitHash;
|
||||
token.logo = logo;
|
||||
token.color = color;
|
||||
token.accessPointAutoApproval = accessPointAutoApproval;
|
||||
token.owner = ownerAddress;
|
||||
token.verifier = verifierAddress;
|
||||
token.mintTransaction = event.transaction.hash.concatI32(
|
||||
event.logIndex.toI32()
|
||||
);
|
||||
token.mintedBy = event.params.minter;
|
||||
token.controllers = [ownerAddress];
|
||||
|
||||
// Save entities
|
||||
owner.save();
|
||||
token.save();
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import {
|
||||
Bytes,
|
||||
log,
|
||||
store
|
||||
} from '@graphprotocol/graph-ts';
|
||||
|
||||
// Event Imports [based on the yaml config]
|
||||
import {
|
||||
Transfer as TransferEvent,
|
||||
} from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import {
|
||||
Owner,
|
||||
Token,
|
||||
Transfer,
|
||||
} from '../generated/schema';
|
||||
|
||||
export function handleTransfer(event: TransferEvent): void {
|
||||
let transfer = new Transfer(
|
||||
event.transaction.hash.concatI32(event.logIndex.toI32())
|
||||
);
|
||||
|
||||
const TokenId = event.params.tokenId;
|
||||
|
||||
transfer.from = event.params.from;
|
||||
transfer.to = event.params.to;
|
||||
transfer.tokenId = TokenId;
|
||||
|
||||
transfer.blockNumber = event.block.number;
|
||||
transfer.blockTimestamp = event.block.timestamp;
|
||||
transfer.transactionHash = event.transaction.hash;
|
||||
|
||||
transfer.save();
|
||||
|
||||
let token: Token | null;
|
||||
|
||||
let owner_address = event.params.to;
|
||||
let owner = Owner.load(owner_address);
|
||||
|
||||
if (!owner) {
|
||||
// Create a new owner entity
|
||||
owner = new Owner(owner_address);
|
||||
}
|
||||
|
||||
if (parseInt(event.params.from.toHexString()) !== 0) {
|
||||
if (parseInt(event.params.to.toHexString()) === 0) {
|
||||
// Burn
|
||||
// Remove the entity from storage
|
||||
// Its controllers and owner will be affected.
|
||||
store.remove('Token', TokenId.toString());
|
||||
} else {
|
||||
// Transfer
|
||||
// Load the Token by using its TokenId
|
||||
token = Token.load(
|
||||
Bytes.fromByteArray(Bytes.fromBigInt(TokenId))
|
||||
);
|
||||
|
||||
if (token) {
|
||||
// Entity exists
|
||||
token.owner = owner_address;
|
||||
|
||||
// Save both entities
|
||||
owner.save();
|
||||
token.save();
|
||||
} else {
|
||||
// Entity does not exist
|
||||
log.error('Unknown token was transferred.', []);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -19,9 +19,10 @@ dataSources:
|
|||
- NewMint
|
||||
- Transfer
|
||||
- Token
|
||||
- Owner
|
||||
- Controller
|
||||
- TokenOwner
|
||||
- TokenController
|
||||
- CollectionOwner
|
||||
- Collection
|
||||
- GithubRepository
|
||||
- AccessPoint
|
||||
- ChangeAccessPointCreationStatus
|
||||
|
|
@ -37,6 +38,7 @@ dataSources:
|
|||
handler: handleApproval
|
||||
- event: ApprovalForAll(indexed address,indexed address,bool)
|
||||
handler: handleApprovalForAll
|
||||
# Token Events
|
||||
- event: MetadataUpdate(indexed uint256,string,string,indexed address)
|
||||
handler: handleMetadataUpdateWithStringValue
|
||||
- event: MetadataUpdate(indexed uint256,string,string[2],indexed address)
|
||||
|
|
@ -45,10 +47,20 @@ dataSources:
|
|||
handler: handleMetadataUpdateWithIntValue
|
||||
- event: MetadataUpdate(indexed uint256,string,bool,indexed address)
|
||||
handler: handleMetadataUpdateWithBooleanValue
|
||||
- event: NewMint(indexed uint256,string,string,string,string,string,string,string,uint24,bool,indexed address,indexed address)
|
||||
- event: NewMint(indexed uint256,string,string,string,string,string,string,string,uint24,bool,indexed address,indexed address,address)
|
||||
handler: handleNewMint
|
||||
- event: Transfer(indexed address,indexed address,indexed uint256)
|
||||
handler: handleTransfer
|
||||
# Access Control Events
|
||||
- event: TokenRoleChanged(indexed uint256,indexed uint8,indexed address,bool,address)
|
||||
handler: handleTokenRoleChanged
|
||||
- event: TokenRolesCleared(indexed uint256,address)
|
||||
handler: handleTokenRolesCleared
|
||||
- event: CollectionRoleChanged(indexed uint8,indexed address,bool,address)
|
||||
handler: handleCollectionRoleChanged
|
||||
- event: Initialized(uint8)
|
||||
handler: handleInitialized
|
||||
# Access Point Events
|
||||
- event: ChangeAccessPointContentVerify(string,uint256,indexed bool,indexed address)
|
||||
handler: handleChangeAccessPointContentVerify
|
||||
- event: ChangeAccessPointNameVerify(string,uint256,indexed bool,indexed address)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"version": "0.5.4",
|
||||
"timestamp": 1677510060006
|
||||
}
|
||||
"timestamp": 1679061942846
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
import {
|
||||
assert,
|
||||
describe,
|
||||
test,
|
||||
clearStore,
|
||||
beforeAll,
|
||||
afterAll,
|
||||
} from 'matchstick-as/assembly/index';
|
||||
import { BigInt, Bytes } from '@graphprotocol/graph-ts';
|
||||
import {
|
||||
createNewAccessPointEvent,
|
||||
createNewChangeAccessPointCreationStatus,
|
||||
handleChangeAccessPointCreationStatusList,
|
||||
handleNewAccessPoints,
|
||||
makeEventId,
|
||||
USER_ONE,
|
||||
USER_TWO,
|
||||
} from '../helpers/utils';
|
||||
import {
|
||||
ChangeAccessPointCreationStatus,
|
||||
NewAccessPoint,
|
||||
} from '../../../generated/FleekNFA/FleekNFA';
|
||||
|
||||
describe('Change Access Point Creation Status tests', () => {
|
||||
beforeAll(() => {
|
||||
// New Access Points
|
||||
let newAccessPoints: NewAccessPoint[] = [];
|
||||
|
||||
// User One has two access points: one for tokenId 0 and one for tokenId 1
|
||||
newAccessPoints.push(
|
||||
createNewAccessPointEvent(0, 'firstAP', BigInt.fromI32(0), USER_ONE)
|
||||
);
|
||||
newAccessPoints.push(
|
||||
createNewAccessPointEvent(1, 'secondAP', BigInt.fromI32(1), USER_ONE)
|
||||
);
|
||||
|
||||
// User Two has one access point for tokenId 0
|
||||
newAccessPoints.push(
|
||||
createNewAccessPointEvent(2, 'thirdAP', BigInt.fromI32(0), USER_TWO)
|
||||
);
|
||||
handleNewAccessPoints(newAccessPoints);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
clearStore();
|
||||
});
|
||||
|
||||
describe('Assertions', () => {
|
||||
test('Check the `creationStatus` field of each access point entity', () => {
|
||||
assert.fieldEquals('AccessPoint', 'firstAP', 'creationStatus', 'DRAFT');
|
||||
assert.fieldEquals('AccessPoint', 'secondAP', 'creationStatus', 'DRAFT');
|
||||
assert.fieldEquals('AccessPoint', 'thirdAP', 'creationStatus', 'DRAFT');
|
||||
});
|
||||
|
||||
test('Check the `creationStatus` field of each access point entity after changing it', () => {
|
||||
// New Access Points
|
||||
let changeAccessPointCreationStatusList: ChangeAccessPointCreationStatus[] =
|
||||
[];
|
||||
|
||||
// User One has two access points: one for tokenId 0 and one for tokenId 1
|
||||
changeAccessPointCreationStatusList.push(
|
||||
createNewChangeAccessPointCreationStatus(
|
||||
0,
|
||||
'firstAP',
|
||||
BigInt.fromI32(0),
|
||||
1,
|
||||
USER_ONE
|
||||
)
|
||||
);
|
||||
changeAccessPointCreationStatusList.push(
|
||||
createNewChangeAccessPointCreationStatus(
|
||||
0,
|
||||
'secondAP',
|
||||
BigInt.fromI32(1),
|
||||
1,
|
||||
USER_ONE
|
||||
)
|
||||
);
|
||||
|
||||
// User Two has one access point for tokenId 0
|
||||
changeAccessPointCreationStatusList.push(
|
||||
createNewChangeAccessPointCreationStatus(
|
||||
0,
|
||||
'thirdAP',
|
||||
BigInt.fromI32(0),
|
||||
1,
|
||||
USER_TWO
|
||||
)
|
||||
);
|
||||
|
||||
handleChangeAccessPointCreationStatusList(
|
||||
changeAccessPointCreationStatusList
|
||||
);
|
||||
|
||||
assert.fieldEquals(
|
||||
'AccessPoint',
|
||||
'firstAP',
|
||||
'creationStatus',
|
||||
'APPROVED'
|
||||
);
|
||||
assert.fieldEquals(
|
||||
'AccessPoint',
|
||||
'secondAP',
|
||||
'creationStatus',
|
||||
'APPROVED'
|
||||
);
|
||||
assert.fieldEquals(
|
||||
'AccessPoint',
|
||||
'thirdAP',
|
||||
'creationStatus',
|
||||
'APPROVED'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import {
|
||||
assert,
|
||||
describe,
|
||||
test,
|
||||
clearStore,
|
||||
beforeAll,
|
||||
afterAll,
|
||||
} from 'matchstick-as/assembly/index';
|
||||
import { BigInt } from '@graphprotocol/graph-ts';
|
||||
import {
|
||||
createNewAccessPointEvent,
|
||||
createNewChangeAccessPointNameVerify,
|
||||
handleChangeAccessPointNameVerifies,
|
||||
handleNewAccessPoints,
|
||||
USER_ONE,
|
||||
USER_TWO,
|
||||
} from '../helpers/utils';
|
||||
import {
|
||||
ChangeAccessPointNameVerify,
|
||||
NewAccessPoint,
|
||||
} from '../../../generated/FleekNFA/FleekNFA';
|
||||
|
||||
describe('Change Access Point Name Verify tests', () => {
|
||||
beforeAll(() => {
|
||||
// New Access Points
|
||||
let newAccessPoints: NewAccessPoint[] = [];
|
||||
|
||||
// User One has two access points: one for tokenId 0 and one for tokenId 1
|
||||
newAccessPoints.push(
|
||||
createNewAccessPointEvent(0, 'firstAP', BigInt.fromI32(0), USER_ONE)
|
||||
);
|
||||
newAccessPoints.push(
|
||||
createNewAccessPointEvent(1, 'secondAP', BigInt.fromI32(1), USER_ONE)
|
||||
);
|
||||
|
||||
// User Two has one access point for tokenId 0
|
||||
newAccessPoints.push(
|
||||
createNewAccessPointEvent(2, 'thirdAP', BigInt.fromI32(0), USER_TWO)
|
||||
);
|
||||
handleNewAccessPoints(newAccessPoints);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
clearStore();
|
||||
});
|
||||
|
||||
describe('Assertions', () => {
|
||||
test('Check the `nameVerified` field of each access point entity', () => {
|
||||
assert.fieldEquals('AccessPoint', 'firstAP', 'nameVerified', 'false');
|
||||
assert.fieldEquals('AccessPoint', 'secondAP', 'nameVerified', 'false');
|
||||
assert.fieldEquals('AccessPoint', 'thirdAP', 'nameVerified', 'false');
|
||||
});
|
||||
|
||||
test('Check the `nameVerified` field of each access point entity after changing it', () => {
|
||||
// New Access Point Name Verified fields
|
||||
let changeAccessPointNameVerifies: ChangeAccessPointNameVerify[] = [];
|
||||
|
||||
changeAccessPointNameVerifies.push(
|
||||
createNewChangeAccessPointNameVerify(
|
||||
0,
|
||||
'firstAP',
|
||||
BigInt.fromI32(0),
|
||||
true,
|
||||
USER_ONE
|
||||
)
|
||||
);
|
||||
changeAccessPointNameVerifies.push(
|
||||
createNewChangeAccessPointNameVerify(
|
||||
0,
|
||||
'secondAP',
|
||||
BigInt.fromI32(1),
|
||||
true,
|
||||
USER_ONE
|
||||
)
|
||||
);
|
||||
|
||||
changeAccessPointNameVerifies.push(
|
||||
createNewChangeAccessPointNameVerify(
|
||||
0,
|
||||
'thirdAP',
|
||||
BigInt.fromI32(0),
|
||||
true,
|
||||
USER_TWO
|
||||
)
|
||||
);
|
||||
|
||||
handleChangeAccessPointNameVerifies(changeAccessPointNameVerifies);
|
||||
|
||||
assert.fieldEquals('AccessPoint', 'firstAP', 'nameVerified', 'true');
|
||||
assert.fieldEquals('AccessPoint', 'secondAP', 'nameVerified', 'true');
|
||||
assert.fieldEquals('AccessPoint', 'thirdAP', 'nameVerified', 'true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -5,12 +5,22 @@ import {
|
|||
ApprovalForAll as ApprovalForAllEvent,
|
||||
Transfer as TransferEvent,
|
||||
NewMint as NewMintEvent,
|
||||
NewAccessPoint,
|
||||
ChangeAccessPointCreationStatus,
|
||||
ChangeAccessPointNameVerify,
|
||||
TokenRoleChanged,
|
||||
CollectionRoleChanged,
|
||||
} from '../../../generated/FleekNFA/FleekNFA';
|
||||
import {
|
||||
handleApproval,
|
||||
handleApprovalForAll,
|
||||
handleChangeAccessPointCreationStatus,
|
||||
handleChangeAccessPointNameVerify,
|
||||
handleNewAccessPoint,
|
||||
handleNewMint,
|
||||
handleTransfer,
|
||||
handleTokenRoleChanged,
|
||||
handleCollectionRoleChanged,
|
||||
} from '../../../src/fleek-nfa';
|
||||
|
||||
export function createApprovalEvent(
|
||||
|
|
@ -154,6 +164,9 @@ export function createNewMintEvent(
|
|||
newMintEvent.parameters.push(
|
||||
new ethereum.EventParam('owner', ethereum.Value.fromAddress(to))
|
||||
);
|
||||
newMintEvent.parameters.push(
|
||||
new ethereum.EventParam('verifier', ethereum.Value.fromAddress(to))
|
||||
);
|
||||
|
||||
newMintEvent.transaction.hash = Bytes.fromI32(event_count);
|
||||
newMintEvent.logIndex = new BigInt(event_count);
|
||||
|
|
@ -161,16 +174,211 @@ export function createNewMintEvent(
|
|||
return newMintEvent;
|
||||
}
|
||||
|
||||
export function createNewAccessPointEvent(
|
||||
event_count: i32,
|
||||
apName: string,
|
||||
tokenId: BigInt,
|
||||
owner: Address
|
||||
): NewAccessPoint {
|
||||
let newAccessPoint = changetype<NewAccessPoint>(newMockEvent());
|
||||
|
||||
newAccessPoint.parameters = new Array();
|
||||
|
||||
newAccessPoint.parameters.push(
|
||||
new ethereum.EventParam(
|
||||
'apName',
|
||||
ethereum.Value.fromString(apName.toString())
|
||||
)
|
||||
);
|
||||
|
||||
newAccessPoint.parameters.push(
|
||||
new ethereum.EventParam(
|
||||
'tokenId',
|
||||
ethereum.Value.fromUnsignedBigInt(tokenId)
|
||||
)
|
||||
);
|
||||
|
||||
newAccessPoint.parameters.push(
|
||||
new ethereum.EventParam('owner', ethereum.Value.fromAddress(owner))
|
||||
);
|
||||
|
||||
newAccessPoint.transaction.hash = Bytes.fromI32(event_count);
|
||||
newAccessPoint.logIndex = new BigInt(event_count);
|
||||
|
||||
return newAccessPoint;
|
||||
}
|
||||
|
||||
export function createNewChangeAccessPointCreationStatus(
|
||||
event_count: i32,
|
||||
apName: string,
|
||||
tokenId: BigInt,
|
||||
status: i32,
|
||||
triggeredBy: Address
|
||||
): ChangeAccessPointCreationStatus {
|
||||
let changeAccessPointCreationStatus =
|
||||
changetype<ChangeAccessPointCreationStatus>(newMockEvent());
|
||||
|
||||
changeAccessPointCreationStatus.parameters = new Array();
|
||||
|
||||
changeAccessPointCreationStatus.parameters.push(
|
||||
new ethereum.EventParam(
|
||||
'apName',
|
||||
ethereum.Value.fromString(apName.toString())
|
||||
)
|
||||
);
|
||||
|
||||
changeAccessPointCreationStatus.parameters.push(
|
||||
new ethereum.EventParam(
|
||||
'tokenId',
|
||||
ethereum.Value.fromUnsignedBigInt(tokenId)
|
||||
)
|
||||
);
|
||||
|
||||
changeAccessPointCreationStatus.parameters.push(
|
||||
new ethereum.EventParam('creationStatus', ethereum.Value.fromI32(status))
|
||||
);
|
||||
|
||||
changeAccessPointCreationStatus.parameters.push(
|
||||
new ethereum.EventParam(
|
||||
'triggeredBy',
|
||||
ethereum.Value.fromAddress(triggeredBy)
|
||||
)
|
||||
);
|
||||
|
||||
changeAccessPointCreationStatus.transaction.hash = Bytes.fromI32(event_count);
|
||||
changeAccessPointCreationStatus.logIndex = new BigInt(event_count);
|
||||
|
||||
return changeAccessPointCreationStatus;
|
||||
}
|
||||
|
||||
export function createNewChangeAccessPointNameVerify(
|
||||
event_count: i32,
|
||||
apName: string,
|
||||
tokenId: BigInt,
|
||||
verified: boolean,
|
||||
triggeredBy: Address
|
||||
): ChangeAccessPointNameVerify {
|
||||
let changeAccessPointNameVerify = changetype<ChangeAccessPointNameVerify>(
|
||||
newMockEvent()
|
||||
);
|
||||
|
||||
changeAccessPointNameVerify.parameters = new Array();
|
||||
|
||||
changeAccessPointNameVerify.parameters.push(
|
||||
new ethereum.EventParam(
|
||||
'apName',
|
||||
ethereum.Value.fromString(apName.toString())
|
||||
)
|
||||
);
|
||||
|
||||
changeAccessPointNameVerify.parameters.push(
|
||||
new ethereum.EventParam(
|
||||
'tokenId',
|
||||
ethereum.Value.fromUnsignedBigInt(tokenId)
|
||||
)
|
||||
);
|
||||
|
||||
changeAccessPointNameVerify.parameters.push(
|
||||
new ethereum.EventParam('verified', ethereum.Value.fromBoolean(verified))
|
||||
);
|
||||
|
||||
changeAccessPointNameVerify.parameters.push(
|
||||
new ethereum.EventParam(
|
||||
'triggeredBy',
|
||||
ethereum.Value.fromAddress(triggeredBy)
|
||||
)
|
||||
);
|
||||
|
||||
changeAccessPointNameVerify.transaction.hash = Bytes.fromI32(event_count);
|
||||
changeAccessPointNameVerify.logIndex = new BigInt(event_count);
|
||||
|
||||
return changeAccessPointNameVerify;
|
||||
}
|
||||
|
||||
export function createNewTokenRoleChanged(
|
||||
event_count: i32,
|
||||
tokenId: BigInt,
|
||||
role: i32,
|
||||
toAddress: Address,
|
||||
status: boolean,
|
||||
byAddress: Address
|
||||
): TokenRoleChanged {
|
||||
let tokenRoleChanged = changetype<TokenRoleChanged>(newMockEvent());
|
||||
|
||||
tokenRoleChanged.parameters = new Array();
|
||||
|
||||
tokenRoleChanged.parameters.push(
|
||||
new ethereum.EventParam(
|
||||
'tokenId',
|
||||
ethereum.Value.fromUnsignedBigInt(tokenId)
|
||||
)
|
||||
);
|
||||
|
||||
tokenRoleChanged.parameters.push(
|
||||
new ethereum.EventParam('role', ethereum.Value.fromI32(role))
|
||||
);
|
||||
|
||||
tokenRoleChanged.parameters.push(
|
||||
new ethereum.EventParam('toAddress', ethereum.Value.fromAddress(toAddress))
|
||||
);
|
||||
|
||||
tokenRoleChanged.parameters.push(
|
||||
new ethereum.EventParam('status', ethereum.Value.fromBoolean(status))
|
||||
);
|
||||
|
||||
tokenRoleChanged.parameters.push(
|
||||
new ethereum.EventParam('byAddress', ethereum.Value.fromAddress(byAddress))
|
||||
);
|
||||
|
||||
tokenRoleChanged.transaction.hash = Bytes.fromI32(event_count);
|
||||
tokenRoleChanged.logIndex = new BigInt(event_count);
|
||||
|
||||
return tokenRoleChanged;
|
||||
}
|
||||
|
||||
export function createNewCollectionRoleChanged(
|
||||
event_count: i32,
|
||||
role: i32,
|
||||
toAddress: Address,
|
||||
status: boolean,
|
||||
byAddress: Address
|
||||
): CollectionRoleChanged {
|
||||
let collectionRoleChanged = changetype<CollectionRoleChanged>(newMockEvent());
|
||||
|
||||
collectionRoleChanged.parameters = new Array();
|
||||
|
||||
collectionRoleChanged.parameters.push(
|
||||
new ethereum.EventParam('role', ethereum.Value.fromI32(role))
|
||||
);
|
||||
|
||||
collectionRoleChanged.parameters.push(
|
||||
new ethereum.EventParam('toAddress', ethereum.Value.fromAddress(toAddress))
|
||||
);
|
||||
|
||||
collectionRoleChanged.parameters.push(
|
||||
new ethereum.EventParam('status', ethereum.Value.fromBoolean(status))
|
||||
);
|
||||
|
||||
collectionRoleChanged.parameters.push(
|
||||
new ethereum.EventParam('byAddress', ethereum.Value.fromAddress(byAddress))
|
||||
);
|
||||
|
||||
collectionRoleChanged.transaction.hash = Bytes.fromI32(event_count);
|
||||
collectionRoleChanged.logIndex = new BigInt(event_count);
|
||||
|
||||
return collectionRoleChanged;
|
||||
}
|
||||
|
||||
export const CONTRACT: Address = Address.fromString(
|
||||
'0x0000000000000000000000000000000000000000'
|
||||
);
|
||||
export const CONTRACT_OWNER: Address = Address.fromString(
|
||||
'0x1000000000000000000000000000000000000001'
|
||||
);
|
||||
export const TOKEN_OWNER_ONE: Address = Address.fromString(
|
||||
export const USER_ONE: Address = Address.fromString(
|
||||
'0x2000000000000000000000000000000000000002'
|
||||
);
|
||||
export const TOKEN_OWNER_TWO: Address = Address.fromString(
|
||||
export const USER_TWO: Address = Address.fromString(
|
||||
'0x3000000000000000000000000000000000000003'
|
||||
);
|
||||
|
||||
|
|
@ -198,6 +406,42 @@ export function handleApprovalForAlls(events: ApprovalForAllEvent[]): void {
|
|||
});
|
||||
}
|
||||
|
||||
export function handleNewAccessPoints(events: NewAccessPoint[]): void {
|
||||
events.forEach((event) => {
|
||||
handleNewAccessPoint(event);
|
||||
});
|
||||
}
|
||||
|
||||
export function handleChangeAccessPointCreationStatusList(
|
||||
events: ChangeAccessPointCreationStatus[]
|
||||
): void {
|
||||
events.forEach((event) => {
|
||||
handleChangeAccessPointCreationStatus(event);
|
||||
});
|
||||
}
|
||||
|
||||
export function handleChangeAccessPointNameVerifies(
|
||||
events: ChangeAccessPointNameVerify[]
|
||||
): void {
|
||||
events.forEach((event) => {
|
||||
handleChangeAccessPointNameVerify(event);
|
||||
});
|
||||
}
|
||||
|
||||
export function handleTokenRoleChangedList(events: TokenRoleChanged[]): void {
|
||||
events.forEach((event) => {
|
||||
handleTokenRoleChanged(event);
|
||||
});
|
||||
}
|
||||
|
||||
export function handleCollectionRoleChangedList(
|
||||
events: CollectionRoleChanged[]
|
||||
): void {
|
||||
events.forEach((event) => {
|
||||
handleCollectionRoleChanged(event);
|
||||
});
|
||||
}
|
||||
|
||||
export function makeEventId(id: i32): string {
|
||||
return Bytes.fromI32(id).toHexString() + '00000000';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import {
|
|||
handleNewMints,
|
||||
handleTransfers,
|
||||
makeEventId,
|
||||
TOKEN_OWNER_ONE,
|
||||
TOKEN_OWNER_TWO,
|
||||
USER_ONE,
|
||||
USER_TWO,
|
||||
} from './helpers/utils';
|
||||
import { NewMint, Transfer } from '../../generated/FleekNFA/FleekNFA';
|
||||
|
||||
|
|
@ -25,44 +25,34 @@ describe('Owner tests', () => {
|
|||
beforeAll(() => {
|
||||
// NEW MINTS
|
||||
let newMints: NewMint[] = [];
|
||||
newMints.push(createNewMintEvent(0, TOKEN_OWNER_ONE, BigInt.fromI32(0)));
|
||||
newMints.push(createNewMintEvent(1, TOKEN_OWNER_TWO, BigInt.fromI32(1)));
|
||||
newMints.push(createNewMintEvent(2, TOKEN_OWNER_ONE, BigInt.fromI32(2)));
|
||||
newMints.push(createNewMintEvent(3, TOKEN_OWNER_ONE, BigInt.fromI32(3)));
|
||||
newMints.push(createNewMintEvent(4, TOKEN_OWNER_TWO, BigInt.fromI32(4)));
|
||||
newMints.push(createNewMintEvent(0, USER_ONE, BigInt.fromI32(0)));
|
||||
newMints.push(createNewMintEvent(1, USER_TWO, BigInt.fromI32(1)));
|
||||
newMints.push(createNewMintEvent(2, USER_ONE, BigInt.fromI32(2)));
|
||||
newMints.push(createNewMintEvent(3, USER_ONE, BigInt.fromI32(3)));
|
||||
newMints.push(createNewMintEvent(4, USER_TWO, BigInt.fromI32(4)));
|
||||
handleNewMints(newMints);
|
||||
// TRANSFERS
|
||||
let transfers: Transfer[] = [];
|
||||
transfers.push(
|
||||
createTransferEvent(0, CONTRACT, TOKEN_OWNER_ONE, BigInt.fromI32(0))
|
||||
createTransferEvent(0, CONTRACT, USER_ONE, BigInt.fromI32(0))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(1, CONTRACT, TOKEN_OWNER_TWO, BigInt.fromI32(1))
|
||||
createTransferEvent(1, CONTRACT, USER_TWO, BigInt.fromI32(1))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(2, CONTRACT, TOKEN_OWNER_ONE, BigInt.fromI32(2))
|
||||
createTransferEvent(2, CONTRACT, USER_ONE, BigInt.fromI32(2))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(3, CONTRACT, TOKEN_OWNER_ONE, BigInt.fromI32(3))
|
||||
createTransferEvent(3, CONTRACT, USER_ONE, BigInt.fromI32(3))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(
|
||||
4,
|
||||
TOKEN_OWNER_TWO,
|
||||
TOKEN_OWNER_ONE,
|
||||
BigInt.fromI32(1)
|
||||
)
|
||||
createTransferEvent(4, USER_TWO, USER_ONE, BigInt.fromI32(1))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(5, CONTRACT, TOKEN_OWNER_TWO, BigInt.fromI32(4))
|
||||
createTransferEvent(5, CONTRACT, USER_TWO, BigInt.fromI32(4))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(
|
||||
6,
|
||||
TOKEN_OWNER_ONE,
|
||||
TOKEN_OWNER_TWO,
|
||||
BigInt.fromI32(0)
|
||||
)
|
||||
createTransferEvent(6, USER_ONE, USER_TWO, BigInt.fromI32(0))
|
||||
);
|
||||
handleTransfers(transfers);
|
||||
//logStore();
|
||||
|
|
@ -129,15 +119,15 @@ describe('Owner tests', () => {
|
|||
test('Check the existence of owners in store', () => {
|
||||
assert.fieldEquals(
|
||||
'Owner',
|
||||
TOKEN_OWNER_ONE.toHexString(),
|
||||
USER_ONE.toHexString(),
|
||||
'id',
|
||||
TOKEN_OWNER_ONE.toHexString()
|
||||
USER_ONE.toHexString()
|
||||
);
|
||||
assert.fieldEquals(
|
||||
'Owner',
|
||||
TOKEN_OWNER_TWO.toHexString(),
|
||||
USER_TWO.toHexString(),
|
||||
'id',
|
||||
TOKEN_OWNER_TWO.toHexString()
|
||||
USER_TWO.toHexString()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import {
|
|||
createTransferEvent,
|
||||
handleTransfers,
|
||||
makeEventId,
|
||||
TOKEN_OWNER_ONE,
|
||||
TOKEN_OWNER_TWO,
|
||||
USER_ONE,
|
||||
USER_TWO,
|
||||
} from './helpers/utils';
|
||||
import { Transfer } from '../../generated/FleekNFA/FleekNFA';
|
||||
|
||||
|
|
@ -24,35 +24,25 @@ describe('Transfer tests', () => {
|
|||
// TRANSFERS
|
||||
let transfers: Transfer[] = [];
|
||||
transfers.push(
|
||||
createTransferEvent(0, CONTRACT, TOKEN_OWNER_ONE, BigInt.fromI32(0))
|
||||
createTransferEvent(0, CONTRACT, USER_ONE, BigInt.fromI32(0))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(1, CONTRACT, TOKEN_OWNER_TWO, BigInt.fromI32(1))
|
||||
createTransferEvent(1, CONTRACT, USER_TWO, BigInt.fromI32(1))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(2, CONTRACT, TOKEN_OWNER_ONE, BigInt.fromI32(2))
|
||||
createTransferEvent(2, CONTRACT, USER_ONE, BigInt.fromI32(2))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(3, CONTRACT, TOKEN_OWNER_ONE, BigInt.fromI32(3))
|
||||
createTransferEvent(3, CONTRACT, USER_ONE, BigInt.fromI32(3))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(
|
||||
4,
|
||||
TOKEN_OWNER_TWO,
|
||||
TOKEN_OWNER_ONE,
|
||||
BigInt.fromI32(1)
|
||||
)
|
||||
createTransferEvent(4, USER_TWO, USER_ONE, BigInt.fromI32(1))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(5, CONTRACT, TOKEN_OWNER_TWO, BigInt.fromI32(4))
|
||||
createTransferEvent(5, CONTRACT, USER_TWO, BigInt.fromI32(4))
|
||||
);
|
||||
transfers.push(
|
||||
createTransferEvent(
|
||||
6,
|
||||
TOKEN_OWNER_ONE,
|
||||
TOKEN_OWNER_TWO,
|
||||
BigInt.fromI32(0)
|
||||
)
|
||||
createTransferEvent(6, USER_ONE, USER_TWO, BigInt.fromI32(0))
|
||||
);
|
||||
handleTransfers(transfers);
|
||||
// logStore();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"include": ["./tests/**/*", "./examples/**/*"]
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
node_modules
|
||||
public
|
||||
|
|
@ -4,19 +4,16 @@ module.exports = {
|
|||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'../.eslintrc.js',
|
||||
'plugin:react/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
overrides: [],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['react', '@typescript-eslint', 'simple-import-sort'],
|
||||
plugins: ['react', 'simple-import-sort'],
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-function-return-type': [
|
||||
'error',
|
||||
|
|
@ -26,8 +23,7 @@ module.exports = {
|
|||
],
|
||||
'@typescript-eslint/no-namespace': 'off',
|
||||
'simple-import-sort/imports': 2,
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'no-console': 'error',
|
||||
'unused-imports/no-unused-imports-ts': 'error',
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
dist
|
||||
dist
|
||||
.graphclient
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# .graphclientrc.yml
|
||||
sources:
|
||||
- name: FleekNFA
|
||||
handler:
|
||||
graphql:
|
||||
endpoint: https://api.thegraph.com/subgraphs/name/emperororokusaki/flk-test-subgraph #replace for nfa subgraph
|
||||
|
||||
documents:
|
||||
- ./graphql/*.graphql
|
||||
|
||||
|
|
@ -23,11 +23,20 @@ To run the UI localy follow the steps:
|
|||
```bash
|
||||
$ yarn
|
||||
```
|
||||
|
||||
This also will generate the `.graphclient` folder. Every time you do a change on the queries files, you'll have to build again that folder, to do it run:
|
||||
|
||||
```bash
|
||||
$ yarn graphclient build
|
||||
```
|
||||
|
||||
3. To use ConnecKit is neccessary get an [Alchemy ID](https://alchemy.com/), so create an App and get the credentials. Then set the following .env file
|
||||
|
||||
```bash
|
||||
VITE_ALCHEMY_API_KEY
|
||||
VITE_ALCHEMY_APP_NAME
|
||||
```
|
||||
|
||||
Also, you'll need to set up your firebase cretendials to make work the github login. Add to the .env file the following variables
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
query lastMintsPaginated($pageSize: Int, $skip: Int) {
|
||||
newMints(
|
||||
first: $pageSize
|
||||
skip: $skip
|
||||
orderDirection: desc
|
||||
orderBy: tokenId
|
||||
) {
|
||||
id
|
||||
tokenId
|
||||
description
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
query totalTokens {
|
||||
tokens {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6,25 +6,32 @@
|
|||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:css": "tailwindcss -o ./tailwind.css --watch && yarn dev",
|
||||
"build": "vite build",
|
||||
"build": "yarn graphclient build && vite build",
|
||||
"postinstall": "graphclient build",
|
||||
"preview": "vite preview",
|
||||
"prod": "yarn build && npx serve dist -s",
|
||||
"storybook": "export SET NODE_OPTIONS=--openssl-legacy-provider && start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook"
|
||||
},
|
||||
"author": "Fleek",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.9",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@graphprotocol/client-apollo": "^1.0.16",
|
||||
"@headlessui/react": "^1.7.8",
|
||||
"@radix-ui/colors": "^0.1.8",
|
||||
"@radix-ui/react-avatar": "^1.0.1",
|
||||
"@radix-ui/react-toast": "^1.1.2",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@reduxjs/toolkit": "^1.9.1",
|
||||
"@stitches/react": "^1.2.8",
|
||||
"abitype": "^0.5.0",
|
||||
"alchemy-sdk": "^2.5.0",
|
||||
"colorthief": "^2.3.2",
|
||||
"connectkit": "^1.1.3",
|
||||
"firebase": "^9.17.1",
|
||||
"formik": "^2.2.9",
|
||||
"graphql": "^16.6.0",
|
||||
"octokit": "^2.0.14",
|
||||
"path": "^0.12.7",
|
||||
"react": "^18.2.0",
|
||||
|
|
@ -35,8 +42,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.12",
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"@graphprotocol/client-cli": "^2.2.19",
|
||||
"@storybook/addon-actions": "^6.5.15",
|
||||
"@storybook/addon-essentials": "^6.5.15",
|
||||
"@storybook/addon-interactions": "^6.5.15",
|
||||
|
|
@ -50,8 +56,6 @@
|
|||
"@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",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"babel-loader": "^8.3.0",
|
||||
|
|
@ -67,12 +71,12 @@
|
|||
"prettier": "^2.8.0",
|
||||
"process": "^0.11.10",
|
||||
"react-query": "^3.39.2",
|
||||
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||
"storybook-dark-mode": "^2.0.5",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"ts-loader": "^9.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^3.2.4",
|
||||
"vite-plugin-node-polyfills": "^0.7.0",
|
||||
"vite-tsconfig-paths": "^3.6.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom';
|
||||
import { HashRouter, Route, Routes, Navigate } from 'react-router-dom';
|
||||
import { themeGlobals } from '@/theme/globals';
|
||||
import { Home, Mint } from './views';
|
||||
import { SVGTestScreen } from './views/svg-test'; // TODO: remove when done
|
||||
import { ComponentsTest, Home, Mint } from './views';
|
||||
import { ConnectKitButton } from 'connectkit';
|
||||
import { MintTest } from './views/mint-test';
|
||||
import { ToastProvider } from './components';
|
||||
import { CreateAP } from './views/access-point';
|
||||
|
||||
export const App = () => {
|
||||
themeGlobals();
|
||||
|
|
@ -13,15 +14,18 @@ export const App = () => {
|
|||
{/* TODO remove after adding NavBar */}
|
||||
<ConnectKitButton />
|
||||
</div>
|
||||
<BrowserRouter>
|
||||
<ToastProvider />
|
||||
<HashRouter>
|
||||
<Routes>
|
||||
<Route path="/home" element={<Home />} />
|
||||
<Route path="/mint" element={<Mint />} />
|
||||
<Route path="/svg" element={<SVGTestScreen />} />
|
||||
<Route path="/create-ap/:id" element={<CreateAP />} />
|
||||
{/** TODO remove for release */}
|
||||
<Route path="/components-test" element={<ComponentsTest />} />
|
||||
<Route path="/mint-test" element={<MintTest />} />
|
||||
<Route path="*" element={<Navigate to="/home" />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</HashRouter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export interface IconButtonProps extends BaseButtonProps {
|
|||
'aria-label': string;
|
||||
}
|
||||
|
||||
export const IconButton = forwardRef<IconButtonProps, 'button'>(
|
||||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
||||
function IconButton(props, ref) {
|
||||
const {
|
||||
icon,
|
||||
|
|
|
|||
|
|
@ -1,39 +1,62 @@
|
|||
import React, { Fragment, useRef, useState } from 'react';
|
||||
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import { Combobox as ComboboxLib, Transition } from '@headlessui/react';
|
||||
import { Icon } from '@/components/core/icon';
|
||||
import { Icon, IconName } from '@/components/core/icon';
|
||||
import { Flex } from '@/components/layout';
|
||||
import { useDebounce } from '@/hooks/use-debounce';
|
||||
import { Separator } from '../separator.styles';
|
||||
import { cleanString } from './combobox.utils';
|
||||
|
||||
type ComboboxInputProps = {
|
||||
/**
|
||||
* If it's true, the list of options will be displayed
|
||||
*/
|
||||
open: boolean;
|
||||
/**
|
||||
* Name of the left icon to display in the input
|
||||
*/
|
||||
leftIcon: IconName;
|
||||
/**
|
||||
* Function to handle the input change
|
||||
*/
|
||||
handleInputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
/**
|
||||
* Function to handle the input click. When the user clicks on the input, the list of options will be displayed
|
||||
*/
|
||||
handleInputClick: () => void;
|
||||
};
|
||||
|
||||
const ComboboxInput = ({
|
||||
open,
|
||||
leftIcon,
|
||||
handleInputChange,
|
||||
handleInputClick,
|
||||
}: ComboboxInputProps) => (
|
||||
<div className="relative w-full cursor-default ">
|
||||
<div className="relative w-full">
|
||||
<Icon
|
||||
name="search"
|
||||
name={leftIcon}
|
||||
size="sm"
|
||||
css={{
|
||||
position: 'absolute',
|
||||
left: '$3',
|
||||
top: '0.9375rem',
|
||||
top: '$3',
|
||||
fontSize: '$xl',
|
||||
color: 'slate8',
|
||||
}}
|
||||
/>
|
||||
<ComboboxLib.Input
|
||||
placeholder="Search"
|
||||
className={`w-full border-solid border border-slate7 h-11 py-3 pl-8 pr-10 text-sm bg-transparent leading-5 text-slate11 outline-none ${
|
||||
open ? 'border-b-0 rounded-t-xl bg-black border-slate6' : 'rounded-xl'
|
||||
className={`w-full border-solid border border-slate7 h-11 py-3 px-10 text-sm leading-5 text-slate11 outline-none ${
|
||||
open
|
||||
? 'border-b-0 rounded-t-xl bg-black border-slate6'
|
||||
: 'rounded-xl bg-transparent'
|
||||
}`}
|
||||
displayValue={(selectedValue: ComboboxItem) => selectedValue.label}
|
||||
onChange={handleInputChange}
|
||||
onClick={handleInputClick}
|
||||
/>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-4">
|
||||
<Icon name="chevron-down" css={{ fontSize: '$xs' }} />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -52,9 +75,13 @@ const ComboboxOption = ({ option }: ComboboxOptionProps) => (
|
|||
>
|
||||
{({ selected, active }) => (
|
||||
<Flex css={{ justifyContent: 'space-between' }}>
|
||||
<Flex css={{ flexDirection: 'row' }}>
|
||||
<Flex css={{ flexDirection: 'row', maxWidth: '95%' }}>
|
||||
{option.icon}
|
||||
<span className={`${active ? 'text-slate12' : 'text-slate11'}`}>
|
||||
<span
|
||||
className={`${active ? 'text-slate12' : 'text-slate11'} ${
|
||||
option.icon ? 'max-w-70' : 'max-w-full'
|
||||
} whitespace-nowrap text-ellipsis overflow-hidden`}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
</Flex>
|
||||
|
|
@ -73,38 +100,97 @@ export const NoResults = ({ css }: { css?: string }) => (
|
|||
);
|
||||
|
||||
export type ComboboxItem = {
|
||||
/**
|
||||
* The key of the item.
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* The label to display of the item.
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Optional icon to display on the left of the item.
|
||||
*/
|
||||
icon?: React.ReactNode;
|
||||
};
|
||||
|
||||
export type ComboboxProps = {
|
||||
/**
|
||||
* List of items to be displayed in the combobox.
|
||||
*/
|
||||
items: ComboboxItem[];
|
||||
/**
|
||||
* The selected value of the combobox.
|
||||
*/
|
||||
selectedValue: ComboboxItem | undefined;
|
||||
/**
|
||||
* If true, the combobox will add the input if it doesn't exist in the list of items.
|
||||
*/
|
||||
withAutocomplete?: boolean;
|
||||
/**
|
||||
* Name of the left icon to display in the input. Defualt is "search".
|
||||
*/
|
||||
leftIcon?: IconName;
|
||||
/**
|
||||
* Callback when the selected value changes.
|
||||
*/
|
||||
onChange(option: ComboboxItem): void;
|
||||
};
|
||||
|
||||
export const Combobox: React.FC<ComboboxProps> = ({
|
||||
items,
|
||||
selectedValue = { value: '', label: '' },
|
||||
withAutocomplete = false,
|
||||
leftIcon = 'search',
|
||||
onChange,
|
||||
}) => {
|
||||
const [query, setQuery] = useState('');
|
||||
const [filteredItems, setFilteredItems] = useState<ComboboxItem[]>([]);
|
||||
const [autocompleteItems, setAutocompleteItems] = useState<ComboboxItem[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// If the selected value doesn't exist in the list of items, we add it
|
||||
if (
|
||||
items.filter((item) => item === selectedValue).length === 0 &&
|
||||
selectedValue.value !== undefined &&
|
||||
autocompleteItems.length === 0 &&
|
||||
withAutocomplete
|
||||
) {
|
||||
setAutocompleteItems([selectedValue]);
|
||||
}
|
||||
}, [selectedValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredItems(items);
|
||||
}, [items]);
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const filteredItems =
|
||||
query === ''
|
||||
? items
|
||||
: items.filter((person) =>
|
||||
person.label
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '')
|
||||
.includes(query.toLowerCase().replace(/\s+/g, ''))
|
||||
);
|
||||
const handleSearch = useDebounce((searchValue: string) => {
|
||||
if (searchValue === '') {
|
||||
setFilteredItems(items);
|
||||
|
||||
if (withAutocomplete) {
|
||||
setAutocompleteItems([]);
|
||||
handleComboboxChange({} as ComboboxItem);
|
||||
}
|
||||
} else {
|
||||
const filteredValues = items.filter((item) =>
|
||||
cleanString(item.label).startsWith(cleanString(searchValue))
|
||||
);
|
||||
|
||||
if (withAutocomplete && filteredValues.length === 0) {
|
||||
// If the search value doesn't exist in the list of items, we add it
|
||||
setAutocompleteItems([{ value: searchValue, label: searchValue }]);
|
||||
}
|
||||
setFilteredItems(filteredValues);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQuery(event.target.value);
|
||||
event.stopPropagation();
|
||||
handleSearch(event.target.value);
|
||||
};
|
||||
|
||||
const handleInputClick = () => {
|
||||
|
|
@ -116,7 +202,11 @@ export const Combobox: React.FC<ComboboxProps> = ({
|
|||
};
|
||||
|
||||
const handleLeaveTransition = () => {
|
||||
setQuery('');
|
||||
setFilteredItems(items);
|
||||
if (selectedValue.value === undefined && withAutocomplete) {
|
||||
setAutocompleteItems([]);
|
||||
handleComboboxChange({} as ComboboxItem);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -131,6 +221,7 @@ export const Combobox: React.FC<ComboboxProps> = ({
|
|||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
open={open}
|
||||
leftIcon={leftIcon}
|
||||
/>
|
||||
<ComboboxLib.Button ref={buttonRef} className="hidden" />
|
||||
|
||||
|
|
@ -144,12 +235,25 @@ export const Combobox: React.FC<ComboboxProps> = ({
|
|||
afterLeave={handleLeaveTransition}
|
||||
>
|
||||
<ComboboxLib.Options className="absolute max-h-60 w-full z-10 overflow-auto rounded-b-xl border-solid border-slate6 border bg-black pt-2 px-3 text-base focus:outline-none sm:text-sm">
|
||||
{filteredItems.length === 0 && query !== '' ? (
|
||||
{[...autocompleteItems, ...filteredItems].length === 0 ||
|
||||
filteredItems === undefined ? (
|
||||
<NoResults />
|
||||
) : (
|
||||
filteredItems.map((option: ComboboxItem) => {
|
||||
return <ComboboxOption key={option.value} option={option} />;
|
||||
})
|
||||
<>
|
||||
{autocompleteItems.length > 0 && <span>Create new</span>}
|
||||
{autocompleteItems.map((autocompleteOption: ComboboxItem) => (
|
||||
<ComboboxOption
|
||||
key={autocompleteOption.value}
|
||||
option={autocompleteOption}
|
||||
/>
|
||||
))}
|
||||
{autocompleteItems.length > 0 && filteredItems.length > 0 && (
|
||||
<Separator css={{ mb: '$2' }} />
|
||||
)}
|
||||
{filteredItems.map((option: ComboboxItem) => (
|
||||
<ComboboxOption key={option.value} option={option} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</ComboboxLib.Options>
|
||||
</Transition>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
export const cleanString = (str: string) =>
|
||||
str.toLowerCase().replace(/\s+/g, '');
|
||||
|
|
@ -52,8 +52,10 @@ type DropdownButtonProps = {
|
|||
|
||||
const DropdownButton = ({ selectedValue, open }: DropdownButtonProps) => (
|
||||
<Listbox.Button
|
||||
className={`relative w-full cursor-default bg-transparent border-solid border border-slate7 py-3 pl-3.5 pr-10 h-11 text-left focus:outline-none sm:text-sm ${
|
||||
open ? 'border-b-0 rounded-t-xl bg-black border-slate6' : 'rounded-xl'
|
||||
className={`relative w-full cursor-default border-solid border border-slate7 py-3 pl-3.5 pr-10 h-11 text-left focus:outline-none sm:text-sm ${
|
||||
open
|
||||
? 'border-b-0 rounded-t-xl bg-black border-slate6'
|
||||
: 'rounded-xl bg-transparent'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import { IconStyles as IS } from '../icon.styles';
|
||||
|
||||
export const ErrorIcon: React.FC<IS.CustomProps> = (props) => (
|
||||
<IS.Custom
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10 0.25C4.62391 0.25 0.25 4.62391 0.25 10C0.25 15.3761 4.62391 19.75 10 19.75C15.3761 19.75 19.75 15.3761 19.75 10C19.75 4.62391 15.3761 0.25 10 0.25ZM10 15.2458C9.81458 15.2458 9.63332 15.1908 9.47915 15.0878C9.32498 14.9848 9.20482 14.8384 9.13386 14.667C9.06291 14.4957 9.04434 14.3072 9.08051 14.1254C9.11669 13.9435 9.20598 13.7765 9.33709 13.6454C9.4682 13.5143 9.63525 13.425 9.8171 13.3888C9.99896 13.3526 10.1875 13.3712 10.3588 13.4421C10.5301 13.5131 10.6765 13.6333 10.7795 13.7874C10.8825 13.9416 10.9375 14.1229 10.9375 14.3083C10.9375 14.5569 10.8387 14.7954 10.6629 14.9712C10.4871 15.147 10.2486 15.2458 10 15.2458ZM11.0181 5.81687L10.7491 11.5356C10.7491 11.7345 10.67 11.9253 10.5294 12.066C10.3887 12.2066 10.198 12.2856 9.99906 12.2856C9.80015 12.2856 9.60938 12.2066 9.46873 12.066C9.32808 11.9253 9.24906 11.7345 9.24906 11.5356L8.98 5.81969V5.81734C8.9741 5.67997 8.99607 5.54282 9.04458 5.41415C9.09308 5.28549 9.16713 5.16797 9.26225 5.06868C9.35737 4.96938 9.47161 4.89037 9.59807 4.83639C9.72454 4.78241 9.86062 4.75458 9.99813 4.75458C10.1356 4.75458 10.2717 4.78241 10.3982 4.83639C10.5246 4.89037 10.6389 4.96938 10.734 5.06868C10.8291 5.16797 10.9032 5.28549 10.9517 5.41415C11.0002 5.54282 11.0221 5.67997 11.0162 5.81734L11.0181 5.81687Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</IS.Custom>
|
||||
);
|
||||
|
|
@ -8,18 +8,26 @@ import { IoCloudUploadSharp } from '@react-icons/all-files/io5/IoCloudUploadShar
|
|||
import { MetamaskIcon, EthereumIcon } from './custom';
|
||||
import { IoCheckmarkCircleSharp } from '@react-icons/all-files/io5/IoCheckmarkCircleSharp';
|
||||
import { AiOutlineTwitter } from '@react-icons/all-files/ai/AiOutlineTwitter';
|
||||
import { ErrorIcon } from './custom/error';
|
||||
import { IoClose } from '@react-icons/all-files/io5/IoClose';
|
||||
import { AiFillCheckCircle } from '@react-icons/all-files/ai/AiFillCheckCircle';
|
||||
import { BiGitBranch } from '@react-icons/all-files/bi/BiGitBranch';
|
||||
|
||||
export const IconLibrary = Object.freeze({
|
||||
back: IoArrowBackCircleSharp,
|
||||
branch: BiGitBranch,
|
||||
check: AiOutlineCheck,
|
||||
'check-circle': IoCheckmarkCircleSharp,
|
||||
'chevron-down': AiOutlineDown,
|
||||
close: IoClose,
|
||||
error: ErrorIcon,
|
||||
ethereum: EthereumIcon,
|
||||
github: IoLogoGithub,
|
||||
info: IoInformationCircleSharp,
|
||||
upload: IoCloudUploadSharp,
|
||||
metamask: MetamaskIcon, //remove if not used
|
||||
search: BiSearch,
|
||||
success: AiFillCheckCircle,
|
||||
twitter: AiOutlineTwitter,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,15 @@ export abstract class FormStyles {
|
|||
},
|
||||
});
|
||||
|
||||
static readonly RequiredLabel = styled('span', {
|
||||
color: '$red11',
|
||||
});
|
||||
|
||||
static readonly MaxLength = styled(FormStyles.Label, {
|
||||
textAlign: 'right',
|
||||
mt: '$1h',
|
||||
});
|
||||
|
||||
static readonly ErrorMessage = styled('span', {
|
||||
color: '$red11',
|
||||
fontSize: '0.625rem',
|
||||
|
|
|
|||
|
|
@ -14,13 +14,24 @@ export abstract class Form {
|
|||
);
|
||||
|
||||
static readonly Label = forwardRef<HTMLLabelElement, Form.LabelProps>(
|
||||
({ children, ...props }, ref) => (
|
||||
({ children, isRequired, ...props }, ref) => (
|
||||
<FormStyles.Label ref={ref} {...props}>
|
||||
{children}
|
||||
{children}{' '}
|
||||
{isRequired && <FormStyles.RequiredLabel>*</FormStyles.RequiredLabel>}
|
||||
</FormStyles.Label>
|
||||
)
|
||||
);
|
||||
|
||||
static readonly MaxLength = forwardRef<HTMLLabelElement, Form.LabelProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<FormStyles.MaxLength ref={ref} {...props}>
|
||||
{children}
|
||||
</FormStyles.MaxLength>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
static readonly Error = forwardRef<HTMLDivElement, Form.ErrorProps>(
|
||||
({ children, ...props }, ref) => (
|
||||
<FormStyles.ErrorMessage ref={ref} {...props}>
|
||||
|
|
@ -55,7 +66,9 @@ export namespace Form {
|
|||
children: React.ReactNode;
|
||||
} & React.ComponentProps<typeof FormStyles.Field>;
|
||||
|
||||
export type LabelProps = React.ComponentProps<typeof FormStyles.Label>;
|
||||
export type LabelProps = { isRequired?: boolean } & React.ComponentProps<
|
||||
typeof FormStyles.Label
|
||||
>;
|
||||
|
||||
export type ErrorProps = React.ComponentProps<typeof FormStyles.ErrorMessage>;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ export * from './layout';
|
|||
export * from './form';
|
||||
export * from './card';
|
||||
export * from './spinner';
|
||||
export * from './toast';
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export * from './toast';
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
import { dripStitches } from '@/theme';
|
||||
import * as ToastLib from '@radix-ui/react-toast';
|
||||
import { Icon, IconButton } from '../core';
|
||||
import { Flex } from '../layout';
|
||||
|
||||
const { styled, keyframes } = dripStitches;
|
||||
|
||||
export abstract class ToastStyles {
|
||||
static readonly Provider = ToastLib.Provider;
|
||||
|
||||
static readonly DismissTimeout = 200;
|
||||
|
||||
static readonly ViewportPadding = '$md';
|
||||
|
||||
static readonly KeyFrames = {
|
||||
hide: keyframes({
|
||||
'0%': { opacity: 1 },
|
||||
'100%': { opacity: 0 },
|
||||
}),
|
||||
show: keyframes({
|
||||
'0%': { opacity: 0 },
|
||||
'100%': { opacity: 1 },
|
||||
}),
|
||||
};
|
||||
|
||||
static readonly Root = styled(ToastLib.Root, {
|
||||
padding: '$4 $5',
|
||||
borderRadius: '$lg',
|
||||
borderWidth: '$default',
|
||||
maxWidth: '$128',
|
||||
|
||||
variants: {
|
||||
variant: {
|
||||
error: {
|
||||
backgroundColor: '$red3',
|
||||
borderColor: '$red6',
|
||||
color: '$red11',
|
||||
},
|
||||
success: {
|
||||
backgroundColor: '$green3',
|
||||
borderColor: '$green6',
|
||||
color: '$green11',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'@media (prefers-reduced-motion: no-preference)': {
|
||||
'&[data-state="open"]': {
|
||||
animation: `${this.KeyFrames.show} 750ms `,
|
||||
},
|
||||
'&[data-state="closed"]': {
|
||||
animation: `${this.KeyFrames.hide} ${this.DismissTimeout}ms ease-in`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
static readonly Body = styled(ToastLib.Description, {
|
||||
fontSize: '$md',
|
||||
fontWeight: '$normal',
|
||||
mr: '$5',
|
||||
});
|
||||
|
||||
static readonly Close = styled(ToastLib.Close, {});
|
||||
|
||||
static readonly CloseButton = styled(IconButton, {
|
||||
variants: {
|
||||
colorScheme: {
|
||||
error: {
|
||||
color: '$red11 !important',
|
||||
},
|
||||
success: {
|
||||
color: '$green11 !important',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
static readonly Viewport = styled(ToastLib.Viewport, {
|
||||
padding: '$14',
|
||||
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
left: '50%',
|
||||
transform: 'translate(-50%)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '$4',
|
||||
zIndex: '$toast',
|
||||
});
|
||||
|
||||
static readonly Layout = styled(Flex, {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
});
|
||||
|
||||
static readonly Icon = styled(Icon, {
|
||||
fontSize: '$5',
|
||||
marginRight: '$2h',
|
||||
});
|
||||
|
||||
static readonly Content = styled(Flex, {});
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import {
|
||||
toastsActions,
|
||||
ToastsState,
|
||||
useAppDispatch,
|
||||
useToastsState,
|
||||
} from '@/store';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Icon } from '../core';
|
||||
import { ToastStyles } from './toast.styles';
|
||||
|
||||
type ToastProps = ToastsState.Toast;
|
||||
|
||||
const Toast: React.FC<ToastProps> = ({
|
||||
id,
|
||||
type,
|
||||
message,
|
||||
onDismiss,
|
||||
duration = 3000,
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
const handleOpenChange = useCallback(
|
||||
(value: boolean) => {
|
||||
setOpen(value);
|
||||
if (!value) {
|
||||
if (onDismiss) onDismiss();
|
||||
setTimeout(() => {
|
||||
dispatch(toastsActions.dismiss(id));
|
||||
}, ToastStyles.DismissTimeout);
|
||||
}
|
||||
},
|
||||
[onDismiss, dispatch, id]
|
||||
);
|
||||
|
||||
return (
|
||||
<ToastStyles.Root
|
||||
open={open}
|
||||
duration={duration}
|
||||
variant={type}
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
<ToastStyles.Layout>
|
||||
<ToastStyles.Content>
|
||||
<ToastStyles.Icon name={type} />
|
||||
<ToastStyles.Body>{message}</ToastStyles.Body>
|
||||
</ToastStyles.Content>
|
||||
<ToastStyles.Close asChild>
|
||||
<ToastStyles.CloseButton
|
||||
aria-label="close"
|
||||
colorScheme={type}
|
||||
variant="link"
|
||||
icon={<Icon name="close" />}
|
||||
onClick={onDismiss}
|
||||
/>
|
||||
</ToastStyles.Close>
|
||||
</ToastStyles.Layout>
|
||||
</ToastStyles.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export const ToastProvider: React.FC = () => {
|
||||
const { toasts } = useToastsState();
|
||||
|
||||
return (
|
||||
<ToastStyles.Provider duration={3000} swipeDirection="down">
|
||||
{toasts.map((toast) => (
|
||||
<Toast key={toast.id} {...toast} />
|
||||
))}
|
||||
<ToastStyles.Viewport />
|
||||
</ToastStyles.Provider>
|
||||
);
|
||||
};
|
||||
|
|
@ -3,6 +3,10 @@ export const env = Object.freeze({
|
|||
id: import.meta.env.VITE_ALCHEMY_API_KEY || '',
|
||||
appName: import.meta.env.VITE_ALCHEMY_APP_NAME || '',
|
||||
},
|
||||
ens: {
|
||||
contractAddress: import.meta.env.VITE_ENS_ADDRESS || '',
|
||||
validationEnsURL: import.meta.env.VITE_ENS_VALIDATION_URL || '',
|
||||
},
|
||||
firebase: {
|
||||
apiKey: import.meta.env.VITE_FIREBASE_API_KEY || '',
|
||||
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN || '',
|
||||
|
|
@ -12,4 +16,7 @@ export const env = Object.freeze({
|
|||
appId: import.meta.env.VITE_FIREBASE_APP_ID || '',
|
||||
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID || '',
|
||||
},
|
||||
twitter: {
|
||||
url: import.meta.env.VITE_TWITTER_URL || '',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import { useRef } from 'react';
|
||||
|
||||
export const useDebounce = <A extends any[], F extends (...args: A) => void>(
|
||||
f: F,
|
||||
t = 500
|
||||
) => {
|
||||
const timeOutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
return (...args: A) => {
|
||||
timeOutRef.current && clearTimeout(timeOutRef.current);
|
||||
timeOutRef.current = setTimeout(() => {
|
||||
f(...args);
|
||||
}, t);
|
||||
};
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,18 @@
|
|||
import { JsonRpcProvider, Networkish } from '@ethersproject/providers';
|
||||
import { ethers } from 'ethers';
|
||||
import * as Contracts from './contracts';
|
||||
import { env } from '@/constants';
|
||||
import { Alchemy, Network } from 'alchemy-sdk';
|
||||
|
||||
const config = {
|
||||
apiKey: env.alchemy.id,
|
||||
network: Network.ETH_MAINNET,
|
||||
};
|
||||
|
||||
const alchemy = new Alchemy(config);
|
||||
|
||||
export const Ethereum: Ethereum.Core = {
|
||||
//TODO remove
|
||||
defaultNetwork: 'https://rpc-mumbai.maticvigil.com', // TODO: make it environment variable
|
||||
|
||||
provider: {
|
||||
|
|
@ -20,6 +30,23 @@ export const Ethereum: Ethereum.Core = {
|
|||
|
||||
return new ethers.Contract(contract.address, contract.abi, provider);
|
||||
},
|
||||
|
||||
async getEnsName(address) {
|
||||
const ensAddresses = await alchemy.nft.getNftsForOwner(address, {
|
||||
contractAddresses: [env.ens.contractAddress],
|
||||
});
|
||||
|
||||
return ensAddresses.ownedNfts.map((nft) => nft.title);
|
||||
},
|
||||
|
||||
//TODO remove if we're not gonna validate ens on the client side
|
||||
async validateEnsName(name) {
|
||||
const provider = new ethers.providers.JsonRpcProvider(
|
||||
env.ens.validationEnsURL
|
||||
);
|
||||
|
||||
return Boolean(await provider.resolveName(name));
|
||||
},
|
||||
};
|
||||
|
||||
export namespace Ethereum {
|
||||
|
|
@ -36,5 +63,9 @@ export namespace Ethereum {
|
|||
contractName: keyof typeof Contracts,
|
||||
providerName?: Providers
|
||||
) => ethers.Contract;
|
||||
|
||||
getEnsName: (address: string) => Promise<string[]>;
|
||||
|
||||
validateEnsName: (name: string) => Promise<boolean>;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ const createWriteContractContext = <
|
|||
TAbi extends EthereumHooks.Abi,
|
||||
TArgumentsMap extends EthereumHooks.WriteContext.ArgumentsMap,
|
||||
TFunctionName extends keyof TArgumentsMap & string,
|
||||
TFunctionArguments extends TArgumentsMap[TFunctionName]
|
||||
TFunctionArguments extends [
|
||||
...TArgumentsMap[TFunctionName],
|
||||
EthereumHooks.WriteContext.SettingsParam
|
||||
]
|
||||
>(
|
||||
address: string,
|
||||
abi: TAbi,
|
||||
|
|
@ -89,7 +92,10 @@ export const EthereumHooks = {
|
|||
*/
|
||||
createFleekERC721WriteContext: <
|
||||
TFunctionName extends keyof ArgumentsMaps.FleekERC721 & string,
|
||||
TFunctionArguments extends ArgumentsMaps.FleekERC721[TFunctionName]
|
||||
TFunctionArguments extends [
|
||||
...ArgumentsMaps.FleekERC721[TFunctionName],
|
||||
EthereumHooks.WriteContext.SettingsParam
|
||||
]
|
||||
>(
|
||||
functionName: TFunctionName
|
||||
) => {
|
||||
|
|
@ -115,7 +121,10 @@ export namespace EthereumHooks {
|
|||
TAbi extends Abi,
|
||||
TArgumentsMap extends ArgumentsMap,
|
||||
TFunctionName extends keyof TArgumentsMap & string,
|
||||
TFunctionArguments extends TArgumentsMap[TFunctionName]
|
||||
TFunctionArguments extends [
|
||||
...TArgumentsMap[TFunctionName],
|
||||
EthereumHooks.WriteContext.SettingsParam
|
||||
]
|
||||
> {
|
||||
functionName: TFunctionName;
|
||||
prepare: ReturnType<
|
||||
|
|
@ -147,6 +156,8 @@ export namespace EthereumHooks {
|
|||
children?: React.ReactNode | React.ReactNode[];
|
||||
config?: ProviderConfig<TFunctionName>;
|
||||
}
|
||||
|
||||
export type SettingsParam = { value?: string };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,38 @@
|
|||
import {
|
||||
ErrorDescription as InterfaceErrorDescription,
|
||||
Result as InterfaceResult,
|
||||
} from '@ethersproject/abi/lib/interface';
|
||||
import { BytesLike } from 'ethers';
|
||||
import { Ethereum } from '../ethereum';
|
||||
|
||||
enum Billing {
|
||||
Mint,
|
||||
AddAccessPoint,
|
||||
}
|
||||
|
||||
enum CollectionRoles {
|
||||
Owner,
|
||||
Verifier,
|
||||
}
|
||||
|
||||
enum TokenRoles {
|
||||
Controller,
|
||||
}
|
||||
|
||||
export const FleekERC721 = {
|
||||
contract: Ethereum.getContract('FleekERC721'),
|
||||
|
||||
Enums: {
|
||||
Billing,
|
||||
CollectionRoles,
|
||||
TokenRoles,
|
||||
},
|
||||
|
||||
async mint(
|
||||
params: FleekERC721.MintParams,
|
||||
provider: Ethereum.Providers
|
||||
): Promise<void> {
|
||||
const contract = Ethereum.getContract('FleekERC721', provider);
|
||||
|
||||
const response = await contract.mint(
|
||||
const response = await this.contract.connect(provider).mint(
|
||||
params.owner,
|
||||
params.name,
|
||||
params.description.replaceAll(/\n/g, '\\n'), //replace break lines with \\n so it doesn't break the json,
|
||||
|
|
@ -22,9 +47,7 @@ export const FleekERC721 = {
|
|||
},
|
||||
|
||||
async tokenMetadata(tokenId: number): Promise<FleekERC721.Metadata> {
|
||||
const contract = Ethereum.getContract('FleekERC721');
|
||||
|
||||
const response = await contract.tokenURI(Number(tokenId));
|
||||
const response = await this.contract.tokenURI(Number(tokenId));
|
||||
|
||||
const parsed = JSON.parse(
|
||||
Buffer.from(response.slice(29), 'base64')
|
||||
|
|
@ -36,8 +59,92 @@ export const FleekERC721 = {
|
|||
},
|
||||
|
||||
async lastTokenId(): Promise<number> {
|
||||
// TODO: fetch last token id
|
||||
return 7;
|
||||
const contract = Ethereum.getContract('FleekERC721');
|
||||
|
||||
return contract.getLastTokenId();
|
||||
},
|
||||
|
||||
async getBilling(key: keyof typeof Billing): Promise<string> {
|
||||
const contract = Ethereum.getContract('FleekERC721');
|
||||
|
||||
return (await contract.getBilling(this.Enums.Billing[key])).toString();
|
||||
},
|
||||
|
||||
parseError(error: BytesLike): FleekERC721.TransactionError {
|
||||
try {
|
||||
if (!error) throw new Error('Empty error');
|
||||
|
||||
const description = this.contract.interface.parseError(error);
|
||||
const result = this.contract.interface.decodeErrorResult(
|
||||
description.signature,
|
||||
error
|
||||
);
|
||||
|
||||
let message: string;
|
||||
|
||||
switch (description.signature) {
|
||||
case 'ContractIsNotPausable()':
|
||||
message = 'This contract is not pausable';
|
||||
break;
|
||||
|
||||
case 'ContractIsNotPaused()':
|
||||
message = 'This contract is not paused';
|
||||
break;
|
||||
|
||||
case 'ContractIsPaused()':
|
||||
message = 'This contract is paused';
|
||||
break;
|
||||
|
||||
case 'MustBeTokenOwner(uint256)':
|
||||
message = `You must be the token #${result.tokenId} owner`;
|
||||
break;
|
||||
|
||||
case 'MustHaveAtLeastOneOwner()':
|
||||
message = 'You must have at least one owner';
|
||||
break;
|
||||
|
||||
case 'MustHaveCollectionRole(uint8)':
|
||||
message = `You must have a collection role "${
|
||||
CollectionRoles[result.role]
|
||||
}" to mint`;
|
||||
break;
|
||||
|
||||
case 'MustHaveTokenRole(uint256,uint8)':
|
||||
message = `You must have a token role "${
|
||||
TokenRoles[result.role]
|
||||
}" on token #${result.tokenId}`;
|
||||
break;
|
||||
|
||||
case 'PausableIsSetTo(bool)':
|
||||
message = `Pausable is set to "${result.isPausable}"`;
|
||||
break;
|
||||
|
||||
case 'RoleAlreadySet()':
|
||||
message = `Role is already set`;
|
||||
break;
|
||||
|
||||
case 'ThereIsNoTokenMinted()':
|
||||
message = `There is no token minted`;
|
||||
break;
|
||||
|
||||
default:
|
||||
message = 'Unknown error';
|
||||
}
|
||||
|
||||
return {
|
||||
message,
|
||||
description,
|
||||
result,
|
||||
isIdentified: true,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
message: 'Unknown error',
|
||||
description: null,
|
||||
result: null,
|
||||
isIdentified: false,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -61,4 +168,11 @@ export namespace FleekERC721 {
|
|||
}
|
||||
];
|
||||
};
|
||||
|
||||
export type TransactionError = {
|
||||
message: string;
|
||||
description: InterfaceErrorDescription | null;
|
||||
result: InterfaceResult | null;
|
||||
isIdentified: boolean;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
ApolloClient,
|
||||
InMemoryCache,
|
||||
ApolloProvider as Provider,
|
||||
} from '@apollo/client';
|
||||
import { GraphApolloLink } from '@graphprotocol/client-apollo';
|
||||
import * as GraphClient from '@/graphclient';
|
||||
|
||||
const client = new ApolloClient({
|
||||
link: new GraphApolloLink(GraphClient),
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
|
||||
type ApolloProviderProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
export const ApolloProvider: React.FC<ApolloProviderProps> = ({ children }) => {
|
||||
return <Provider client={client}>{children}</Provider>;
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { ApolloProvider } from './apollo-provider';
|
||||
import { ConnectkitProvider } from './connectkit-provider';
|
||||
import { ReactQueryProvider } from './react-query-provider';
|
||||
import { ReduxProvider } from './redux-provider';
|
||||
|
|
@ -10,7 +11,9 @@ export const Providers: React.FC<ProviderProps> = ({ children }) => {
|
|||
return (
|
||||
<ReduxProvider>
|
||||
<ReactQueryProvider>
|
||||
<ConnectkitProvider>{children}</ConnectkitProvider>
|
||||
<ApolloProvider>
|
||||
<ConnectkitProvider>{children}</ConnectkitProvider>
|
||||
</ApolloProvider>
|
||||
</ReactQueryProvider>
|
||||
</ReduxProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { Ethereum } from '@/integrations';
|
||||
import { RootState } from '@/store';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { ensActions } from '../ens-slice';
|
||||
|
||||
export const fetchEnsNamesThunk = createAsyncThunk<void, `0x${string}`>(
|
||||
'ens/fetchEnsNames',
|
||||
async (address, { dispatch, getState }) => {
|
||||
const { state } = (getState() as RootState).ens;
|
||||
if (state === 'loading') return;
|
||||
|
||||
try {
|
||||
dispatch(ensActions.setState('loading'));
|
||||
|
||||
//fetch ens names for received addresses
|
||||
const ensList = await Ethereum.getEnsName(address);
|
||||
|
||||
dispatch(ensActions.setEnsNames(ensList));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
dispatch(ensActions.setState('failed'));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './fetch-ens-names';
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { RootState, useAppSelector } from '@/store';
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import * as asyncThunk from './async-thunk';
|
||||
|
||||
export namespace EnsState {
|
||||
export type State = 'idle' | 'loading' | 'success' | 'failed';
|
||||
|
||||
export type EnsNames = string[];
|
||||
}
|
||||
|
||||
export interface EnsState {
|
||||
state: EnsState.State;
|
||||
ensNames: EnsState.EnsNames;
|
||||
}
|
||||
|
||||
const initialState: EnsState = {
|
||||
state: 'idle',
|
||||
ensNames: [],
|
||||
};
|
||||
|
||||
export const ensSlice = createSlice({
|
||||
name: 'ens',
|
||||
initialState,
|
||||
reducers: {
|
||||
setEnsNames: (state, action: PayloadAction<EnsState.EnsNames>) => {
|
||||
state.ensNames = action.payload;
|
||||
state.state = 'success';
|
||||
},
|
||||
setState: (
|
||||
state,
|
||||
action: PayloadAction<Exclude<EnsState.State, 'success'>>
|
||||
) => {
|
||||
state.state = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const ensActions = {
|
||||
...ensSlice.actions,
|
||||
...asyncThunk,
|
||||
};
|
||||
|
||||
const selectEnsState = (state: RootState): EnsState => state.ens;
|
||||
|
||||
export const useEnsStore = (): EnsState => useAppSelector(selectEnsState);
|
||||
|
||||
export default ensSlice.reducer;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './ens-slice';
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { FleekERC721 } from '@/integrations';
|
||||
import { FleekERC721State, fleekERC721Actions, RootState } from '@/store';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
|
||||
type FetchBilling = FleekERC721State.BillingKeys;
|
||||
|
||||
export const fetchBilling = createAsyncThunk<void, FetchBilling>(
|
||||
'fleekERC721/fetchBilling',
|
||||
async (key, { dispatch, getState }) => {
|
||||
const { billingState } = (getState() as RootState).fleekERC721;
|
||||
|
||||
if (billingState[key] === 'loading') return;
|
||||
|
||||
try {
|
||||
dispatch(fleekERC721Actions.setBillingState({ key, value: 'loading' }));
|
||||
|
||||
const value = await FleekERC721.getBilling(key);
|
||||
|
||||
dispatch(fleekERC721Actions.setBilling({ key, value }));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
dispatch(fleekERC721Actions.setBillingState({ key, value: 'failed' }));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './fetch-billing';
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { RootState } from '@/store';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
import * as asyncThunk from './async-thunk';
|
||||
import { FleekERC721 } from '@/integrations';
|
||||
|
||||
export namespace FleekERC721State {
|
||||
export type QueryState = undefined | 'loading' | 'failed' | 'success';
|
||||
|
||||
export type BillingKeys = keyof typeof FleekERC721.Enums.Billing;
|
||||
|
||||
export type Billing = {
|
||||
[key in BillingKeys]?: string;
|
||||
};
|
||||
|
||||
export type BillingState = {
|
||||
[key in BillingKeys]?: QueryState;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FleekERC721State {
|
||||
billing: FleekERC721State.Billing;
|
||||
billingState: FleekERC721State.BillingState;
|
||||
}
|
||||
|
||||
const initialState: FleekERC721State = {
|
||||
billing: {},
|
||||
billingState: {},
|
||||
};
|
||||
|
||||
export const fleekERC721Slice = createSlice({
|
||||
name: 'fleekERC721',
|
||||
initialState,
|
||||
reducers: {
|
||||
setBilling: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
key: FleekERC721State.BillingKeys;
|
||||
value: string;
|
||||
}>
|
||||
) => {
|
||||
state.billing[action.payload.key] = action.payload.value;
|
||||
state.billingState[action.payload.key] = 'success';
|
||||
},
|
||||
setBillingState: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
key: FleekERC721State.BillingKeys;
|
||||
value: Exclude<FleekERC721State.QueryState, 'success' | undefined>;
|
||||
}>
|
||||
) => {
|
||||
state.billingState[action.payload.key] = action.payload.value;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const fleekERC721Actions = {
|
||||
...fleekERC721Slice.actions,
|
||||
...asyncThunk,
|
||||
};
|
||||
|
||||
const selectFleekERC721State = (state: RootState): FleekERC721State =>
|
||||
state.fleekERC721;
|
||||
|
||||
export const useFleekERC721Store = (): FleekERC721State =>
|
||||
useAppSelector(selectFleekERC721State);
|
||||
|
||||
export default fleekERC721Slice.reducer;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './use-fleek-erc721-billing';
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { useAppDispatch } from '@/store/hooks';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
fleekERC721Actions,
|
||||
FleekERC721State,
|
||||
useFleekERC721Store,
|
||||
} from '../fleek-erc721-slice';
|
||||
|
||||
export const useFleekERC721Billing = (key: FleekERC721State.BillingKeys) => {
|
||||
const { billing, billingState } = useFleekERC721Store();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const refresh = (): void => {
|
||||
dispatch(fleekERC721Actions.fetchBilling(key));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof billingState[key] !== 'undefined') return;
|
||||
|
||||
refresh();
|
||||
}, []);
|
||||
|
||||
return [billing[key], billingState[key], refresh] as const;
|
||||
};
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from './fleek-erc721-slice';
|
||||
export * from './hooks';
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { DropdownItem } from '@/components';
|
||||
import { ComboboxItem } from '@/components';
|
||||
import { githubActions, RootState } from '@/store';
|
||||
import { AppLog } from '@/utils';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { GithubClient } from '../github-client';
|
||||
|
||||
|
|
@ -22,9 +23,11 @@ export const fetchBranchesThunk = createAsyncThunk<void, FetchBranches>(
|
|||
|
||||
const branches = await githubClient.fetchBranches(owner, repository);
|
||||
|
||||
dispatch(githubActions.setBranches(branches as DropdownItem[]));
|
||||
dispatch(githubActions.setBranches(branches as ComboboxItem[]));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
AppLog.errorToast(
|
||||
'We have a problem trying to get your branches. Please try again later.'
|
||||
);
|
||||
dispatch(githubActions.setQueryState('failed'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,16 +16,7 @@ export const fetchRepositoriesThunk = createAsyncThunk(
|
|||
|
||||
const repositories = await githubClient.fetchRepos(url);
|
||||
|
||||
console.log(repositories);
|
||||
|
||||
dispatch(
|
||||
githubActions.setRepositories(
|
||||
repositories.map((repo: any) => ({
|
||||
name: repo.name,
|
||||
url: repo.html_url,
|
||||
}))
|
||||
)
|
||||
);
|
||||
dispatch(githubActions.setRepositories(repositories));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
dispatch(githubActions.setQueryState('failed'));
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue