Merge branch 'develop' into feature/sc-838/ui-finish-indexed-nfa-view-after-design-changes

This commit is contained in:
Camila Sosa Morales 2023-05-11 15:53:28 -03:00 committed by GitHub
commit 7475a62548
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 3641 additions and 476 deletions

View File

@ -39,6 +39,7 @@ contract FleekERC721 is
string ENS,
string commitHash,
string gitRepository,
string ipfsHash,
string logo,
uint24 color,
bool accessPointAutoApproval,
@ -108,6 +109,7 @@ contract FleekERC721 is
string calldata ens,
string memory commitHash,
string memory gitRepository,
string memory ipfsHash,
string memory logo,
uint24 color,
bool accessPointAutoApproval,
@ -131,7 +133,7 @@ contract FleekERC721 is
// 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);
app.builds[0] = Build(commitHash, gitRepository, ipfsHash, externalURL);
emit NewMint(
tokenId,
@ -141,6 +143,7 @@ contract FleekERC721 is
ens,
commitHash,
gitRepository,
ipfsHash,
logo,
color,
accessPointAutoApproval,
@ -396,11 +399,14 @@ contract FleekERC721 is
function setTokenBuild(
uint256 tokenId,
string memory _commitHash,
string memory _gitRepository
string memory _gitRepository,
string memory _ipfsHash,
string memory _domain
) public virtual requireTokenRole(tokenId, TokenRoles.Controller) {
_requireMinted(tokenId);
_apps[tokenId].builds[++_apps[tokenId].currentBuild] = Build(_commitHash, _gitRepository);
emit MetadataUpdate(tokenId, "build", [_commitHash, _gitRepository], msg.sender);
_apps[tokenId].builds[++_apps[tokenId].currentBuild] = Build(_commitHash, _gitRepository, _ipfsHash, _domain);
// Note from Nima: should we update the externalURL field with each new domain?
emit MetadataUpdate(tokenId, "build", [_commitHash, _gitRepository, _ipfsHash, _domain], msg.sender);
}
/**

View File

@ -24,7 +24,7 @@ interface IERCX {
*/
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, string[4] value, address indexed triggeredBy);
event MetadataUpdate(uint256 indexed _tokenId, string key, bool value, address indexed triggeredBy);
/**
@ -33,6 +33,8 @@ interface IERCX {
struct Build {
string commitHash;
string gitRepository;
string ipfsHash;
string domain;
}
/**
@ -84,7 +86,13 @@ interface IERCX {
/**
* @dev Sets a minted token's build.
*/
function setTokenBuild(uint256 tokenId, string memory commitHash, string memory gitRepository) external;
function setTokenBuild(
uint256 tokenId,
string memory commitHash,
string memory gitRepository,
string memory ipfsHash,
string memory domain
) external;
/**
* @dev Returns the token metadata for a given tokenId.

View File

@ -369,29 +369,31 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
function test_setTokenBuild() public {
string memory commitHash = "commitHash";
string memory gitRepository = "gitRepository";
string memory ipfsHash = "ipfsHash";
string memory domain = "domain";
// ColletionOwner
vm.prank(collectionOwner);
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
CuT.setTokenBuild(tokenId, commitHash, gitRepository, ipfsHash, domain);
// CollectionVerifier
vm.prank(collectionVerifier);
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
CuT.setTokenBuild(tokenId, commitHash, gitRepository, ipfsHash, domain);
// TokenOwner
vm.prank(tokenOwner);
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
CuT.setTokenBuild(tokenId, commitHash, gitRepository, ipfsHash, domain);
// TokenController
vm.prank(tokenController);
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
CuT.setTokenBuild(tokenId, commitHash, gitRepository, ipfsHash, domain);
// AnyAddress
vm.prank(anyAddress);
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
CuT.setTokenBuild(tokenId, commitHash, gitRepository, ipfsHash, domain);
}
function test_burn() public {

View File

@ -54,6 +54,7 @@ contract Test_FleekERC721_Billing is Test_FleekERC721_Base, Test_FleekERC721_Bil
TestConstants.APP_ENS,
TestConstants.APP_COMMIT_HASH,
TestConstants.APP_GIT_REPOSITORY,
TestConstants.APP_IPFS_HASH,
TestConstants.LOGO_0,
TestConstants.APP_COLOR,
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS,
@ -75,6 +76,7 @@ contract Test_FleekERC721_Billing is Test_FleekERC721_Base, Test_FleekERC721_Bil
TestConstants.APP_ENS,
TestConstants.APP_COMMIT_HASH,
TestConstants.APP_GIT_REPOSITORY,
TestConstants.APP_IPFS_HASH,
TestConstants.LOGO_0,
TestConstants.APP_COLOR,
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS,
@ -98,6 +100,7 @@ contract Test_FleekERC721_Billing is Test_FleekERC721_Base, Test_FleekERC721_Bil
TestConstants.APP_ENS,
TestConstants.APP_COMMIT_HASH,
TestConstants.APP_GIT_REPOSITORY,
TestConstants.APP_IPFS_HASH,
TestConstants.LOGO_0,
TestConstants.APP_COLOR,
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS,

View File

@ -15,6 +15,8 @@ library TestConstants {
string public constant APP_GIT_REPOSITORY = "https://github.com/fleekxyz/non-fungible-apps";
string public constant APP_IPFS_HASH = "mtwirsqawjuoloq2gvtyug2tc3jbf5htm2zeo4rsknfiv3fdp46a";
uint24 public constant APP_COLOR = 0x123456;
bool public constant APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS = true;

View File

@ -39,6 +39,8 @@ contract Test_FleekERC721_GetToken is Test_FleekERC721_Base {
string memory newENS,
string memory newCommitHash,
string memory newRepository,
string memory newIpfsHash,
string memory newDomain,
string memory newLogo,
uint24 newColor
) public {
@ -47,7 +49,7 @@ contract Test_FleekERC721_GetToken is Test_FleekERC721_Base {
CuT.setTokenExternalURL(tokenId, newExternalURL);
transferENS(newENS, deployer);
CuT.setTokenENS(tokenId, newENS);
CuT.setTokenBuild(tokenId, newCommitHash, newRepository);
CuT.setTokenBuild(tokenId, newCommitHash, newRepository, newIpfsHash, newDomain);
CuT.setTokenLogoAndColor(tokenId, newLogo, newColor);
(

View File

@ -36,6 +36,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
"fleek.eth",
"94e8ba38568aea4fb277a37a4c472d94a6ce880a",
"https://github.com/a-different/repository",
"mtwirsqawjuoloq2gvtyug2tc3jbf5htm2zeo4rsknfiv3fdp46a",
TestConstants.LOGO_1,
0x654321,
false,
@ -56,6 +57,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
"fleek.eth",
"94e8ba38568aea4fb277a37a4c472d94a6ce880a",
"https://github.com/a-different/repository",
"mtwirsqawjuoloq2gvtyug2tc3jbf5htm2zeo4rsknfiv3fdp46a",
TestConstants.LOGO_1,
0x654321,
true,
@ -81,6 +83,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
string memory ens,
string memory commitHash,
string memory gitRepository,
string memory ipfsHash,
string memory logo,
uint24 color,
bool autoApprovalAp
@ -95,6 +98,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
ens,
commitHash,
gitRepository,
ipfsHash,
logo,
color,
autoApprovalAp,
@ -115,6 +119,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
TestConstants.APP_ENS,
TestConstants.APP_COMMIT_HASH,
TestConstants.APP_GIT_REPOSITORY,
TestConstants.APP_IPFS_HASH,
TestConstants.LOGO_0,
TestConstants.APP_COLOR,
false,
@ -131,6 +136,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
"",
TestConstants.APP_COMMIT_HASH,
TestConstants.APP_GIT_REPOSITORY,
TestConstants.APP_IPFS_HASH,
TestConstants.LOGO_0,
TestConstants.APP_COLOR,
false,

View File

@ -81,6 +81,7 @@ abstract contract Test_FleekERC721_Base is Test, Test_FleekERC721_Assertions {
TestConstants.APP_ENS,
TestConstants.APP_COMMIT_HASH,
TestConstants.APP_GIT_REPOSITORY,
TestConstants.APP_IPFS_HASH,
TestConstants.LOGO_0,
TestConstants.APP_COLOR,
false, // Auto Approval Is OFF

View File

@ -6,7 +6,7 @@ import "./TestBase.sol";
contract Test_FleekERC721_TokenURIAssertions is Test {
event MetadataUpdate(uint256 indexed _tokenId, string key, string value, address indexed triggeredBy);
event MetadataUpdate(uint256 indexed _tokenId, string key, string[2] value, address indexed triggeredBy);
event MetadataUpdate(uint256 indexed _tokenId, string key, string[4] value, address indexed triggeredBy);
event MetadataUpdate(uint256 indexed _tokenId, string key, uint24 value, address indexed triggeredBy);
function expectMetadataUpdate(
@ -22,7 +22,7 @@ contract Test_FleekERC721_TokenURIAssertions is Test {
function expectMetadataUpdate(
uint256 _tokenId,
string memory key,
string[2] memory value,
string[4] memory value,
address triggeredBy
) public {
vm.expectEmit(true, true, true, true);
@ -57,7 +57,13 @@ contract Test_FleekERC721_TokenURI is Test_FleekERC721_Base, Test_FleekERC721_To
CuT.setTokenExternalURL(tokenId, "https://new-url.com");
transferENS("new-ens.eth", deployer);
CuT.setTokenENS(tokenId, "new-ens.eth");
CuT.setTokenBuild(tokenId, "ce1a3fc141e29f8e1d00a654e156c4982d7711bf", "https://github.com/other/repo");
CuT.setTokenBuild(
tokenId,
"ce1a3fc141e29f8e1d00a654e156c4982d7711bf",
"https://github.com/other/repo",
"ipfsHash",
"domain"
);
CuT.setTokenLogoAndColor(tokenId, TestConstants.LOGO_1, 0x654321);
CuT.setTokenVerified(tokenId, true);
@ -96,10 +102,16 @@ contract Test_FleekERC721_TokenURI is Test_FleekERC721_Base, Test_FleekERC721_To
expectMetadataUpdate(
tokenId,
"build",
["ce1a3fc141e29f8e1d00a654e156c4982d7711bf", "https://github.com/other/repo"],
["ce1a3fc141e29f8e1d00a654e156c4982d7711bf", "https://github.com/other/repo", "ipfshash", "domain"],
deployer
);
CuT.setTokenBuild(tokenId, "ce1a3fc141e29f8e1d00a654e156c4982d7711bf", "https://github.com/other/repo");
CuT.setTokenBuild(
tokenId,
"ce1a3fc141e29f8e1d00a654e156c4982d7711bf",
"https://github.com/other/repo",
"ipfshash",
"domain"
);
expectMetadataUpdate(tokenId, "logo", TestConstants.LOGO_1, deployer);
CuT.setTokenLogo(tokenId, TestConstants.LOGO_1);

View File

@ -21,6 +21,7 @@ describe('FleekERC721.Billing', () => {
MintParams.ens,
MintParams.commitHash,
MintParams.gitRepository,
MintParams.ipfsHash,
MintParams.logo,
MintParams.color,
MintParams.accessPointAutoApprovalSettings,

View File

@ -187,6 +187,7 @@ describe('FleekERC721.CollectionRoles', () => {
TestConstants.MintParams.ens,
TestConstants.MintParams.commitHash,
TestConstants.MintParams.gitRepository,
TestConstants.MintParams.ipfsHash,
TestConstants.MintParams.logo,
TestConstants.MintParams.color,
TestConstants.MintParams.accessPointAutoApprovalSettings,

View File

@ -22,6 +22,7 @@ describe('FleekERC721.ENS', () => {
'app.eth',
MintParams.commitHash,
MintParams.gitRepository,
MintParams.ipfsHash,
MintParams.logo,
MintParams.color,
MintParams.accessPointAutoApprovalSettings,
@ -43,6 +44,7 @@ describe('FleekERC721.ENS', () => {
'app.eth',
MintParams.commitHash,
MintParams.gitRepository,
MintParams.ipfsHash,
MintParams.logo,
MintParams.color,
MintParams.accessPointAutoApprovalSettings,

View File

@ -14,6 +14,7 @@ describe('FleekERC721.GetLastTokenId', () => {
TestConstants.MintParams.ens,
TestConstants.MintParams.commitHash,
TestConstants.MintParams.gitRepository,
TestConstants.MintParams.ipfsHash,
TestConstants.MintParams.logo,
TestConstants.MintParams.color,
false,

View File

@ -23,6 +23,7 @@ export const TestConstants = Object.freeze({
externalUrl: 'https://fleek.co',
commitHash: 'b72e47171746b6a9e29b801af9cb655ecf4d665c',
gitRepository: 'https://github.com/fleekxyz/non-fungible-apps',
ipfsHash: 'mtwirsqawjuoloq2gvtyug2tc3jbf5htm2zeo4rsknfiv3fdp46a',
logo: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI1MDAiIHdpZHRoPSIyMTgzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjQgMTQxLjUzMTk5OTk5OTk5OTk4Ij48cGF0aCBkPSJNMTAuMzgzIDEyNi44OTRMMCAwbDEyNCAuMjU1LTEwLjk3OSAxMjYuNjM5LTUwLjU1MyAxNC42Mzh6IiBmaWxsPSIjZTM0ZjI2Ii8+PHBhdGggZD0iTTYyLjQ2OCAxMjkuMjc3VjEyLjA4NWw1MS4wNjQuMTctOS4xMDYgMTA0Ljg1MXoiIGZpbGw9IiNlZjY1MmEiLz48cGF0aCBkPSJNOTkuNDkgNDEuMzYybDEuNDQ2LTE1LjQ5SDIyLjM4M2w0LjM0IDQ3LjQ5aDU0LjIxM0w3OC44MSA5My42MTdsLTE3LjM2MiA0LjY4LTE3LjYxNy01LjEwNi0uOTM2LTEyLjA4NUgyNy4zMTlsMi4xMjggMjQuNjgxIDMyIDguOTM2IDMyLjI1NS04LjkzNiA0LjM0LTQ4LjE3SDQxLjEwN0wzOS40OSA0MS4zNjJ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+',
color: 0xe34f26,
accessPointAutoApprovalSettings: false,

View File

@ -2,6 +2,6 @@ export const Events = Object.freeze({
MetadataUpdate: {
string: 'MetadataUpdate(uint256,string,string,address)',
uint24: 'MetadataUpdate(uint256,string,uint24,address)',
stringTuple: 'MetadataUpdate(uint256,string,string[2],address)',
stringArray4: 'MetadataUpdate(uint256,string,string[4],address)',
},
});

View File

@ -44,6 +44,7 @@ export abstract class Fixtures {
TestConstants.MintParams.ens,
TestConstants.MintParams.commitHash,
TestConstants.MintParams.gitRepository,
TestConstants.MintParams.ipfsHash,
TestConstants.MintParams.logo,
TestConstants.MintParams.color,
TestConstants.MintParams.accessPointAutoApprovalSettings,

View File

@ -17,6 +17,7 @@ describe('FleekERC721.Minting', () => {
MintParams.ens,
MintParams.commitHash,
MintParams.gitRepository,
MintParams.ipfsHash,
MintParams.logo,
MintParams.color,
MintParams.accessPointAutoApprovalSettings,
@ -40,6 +41,7 @@ describe('FleekERC721.Minting', () => {
MintParams.ens,
MintParams.commitHash,
MintParams.gitRepository,
MintParams.ipfsHash,
MintParams.logo,
MintParams.color,
MintParams.accessPointAutoApprovalSettings,
@ -66,6 +68,7 @@ describe('FleekERC721.Minting', () => {
MintParams.ens,
MintParams.commitHash,
MintParams.gitRepository,
MintParams.ipfsHash,
MintParams.logo,
MintParams.color,
MintParams.accessPointAutoApprovalSettings,
@ -87,6 +90,7 @@ describe('FleekERC721.Minting', () => {
'',
MintParams.commitHash,
MintParams.gitRepository,
MintParams.ipfsHash,
MintParams.logo,
MintParams.color,
MintParams.accessPointAutoApprovalSettings,

View File

@ -18,6 +18,7 @@ describe('FleekERC721.Pausable', () => {
MintParams.ens,
MintParams.commitHash,
MintParams.gitRepository,
MintParams.ipfsHash,
MintParams.logo,
MintParams.color,
false,

View File

@ -51,12 +51,20 @@ describe('FleekERC721.UpdateProperties', () => {
it('should emit event for build change', async () => {
const { contract, tokenId, owner } = fixture;
await expect(contract.setTokenBuild(tokenId, 'commitHash', 'gitRepository'))
.to.emit(contract, Events.MetadataUpdate.stringTuple)
await expect(
contract.setTokenBuild(
tokenId,
'commitHash',
'gitRepository',
'ipfsHash',
'domain'
)
)
.to.emit(contract, Events.MetadataUpdate.stringArray4)
.withArgs(
tokenId,
'build',
['commitHash', 'gitRepository'],
['commitHash', 'gitRepository', 'ipfsHash', 'domain'],
owner.address
);
});

View File

@ -99,6 +99,7 @@ describe('Deploy', () => {
TestConstants.MintParams.ens,
TestConstants.MintParams.commitHash,
TestConstants.MintParams.gitRepository,
TestConstants.MintParams.ipfsHash,
TestConstants.MintParams.logo,
TestConstants.MintParams.color,
TestConstants.MintParams.accessPointAutoApprovalSettings,

View File

@ -33,6 +33,7 @@
"pinst": "^3.0.0",
"prettier": "^2.8.4",
"prettier-plugin-solidity": "^1.0.0",
"serverless-offline": "^12.0.4",
"typescript": "^4.9.5"
}
}

View File

@ -37,6 +37,22 @@ To deploy to development environment:
To deploy to production environment:
`yarn sls deploy --stage prd`
### Running MongoDB
The first step to run MongoDB is making sure the service is installed on the machine locally. You can check the [official MongoDB website](https://www.mongodb.com/docs/manual/installation/#mongodb-installation-tutorials) for more information on the installation process.
To process database transactions such as `create` calls, Prisma needs the MongoDB instance to be running as a replica set. Run the commands below to start a replica set with `mongod` and `mongosh`:
```
// You should replace the dbpath with the actual path on your machine and assign a name to your replica set. (Default path on linux is: /var/lib/mongodb)
// Do not close the terminal tab after running mongod.
$ sudo mongod --port 27017 --dbpath /path/to/db --replSet replicaName --bind_ip localhost,127.0.0.1
// Start a mongosh session and run the replica set initiation command in the mongo shell.
$ mongosh
> rs.initiate()
```
Make sure you copy the connection string that is presented in the `Connecting to` field when the mongosh service starts to run. We need the connection string to access the replica set. Rename the `.env.example` file to `.env` and replace the connection string placeholder in the file with the one you copied.
### Prisma configuration

View File

@ -7,7 +7,8 @@
"build": "yarn tsc",
"invoke:build": "yarn build && serverless invoke local --function submitBuildInfo",
"prisma:generate": "npx prisma generate",
"prisma:pull": "npx prisma db pull"
"prisma:pull": "npx prisma db pull --force",
"start": "serverless offline"
},
"author": "fleek",
"license": "MIT",
@ -30,12 +31,13 @@
"@middy/http-json-body-parser": "^4.2.7",
"@middy/http-response-serializer": "^4.2.8",
"@prisma/client": "^4.13.0",
"aws-sdk": "^2.1342.0",
"prisma": "^4.13.0",
"uuid": "^9.0.0",
"@types/node": "^18.15.11",
"aws-sdk": "^2.1342.0",
"dotenv": "^16.0.3",
"prisma": "^4.13.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4",
"uuid": "^9.0.0",
"web3": "^1.9.0"
}
}

View File

@ -7,10 +7,21 @@ datasource db {
url = env("DATABASE_URL")
}
model tokens {
id String @id @default(auto()) @map("_id") @db.ObjectId
commit_hash String
github_url String
owner String
tokenId Int
model builds {
id String @id @default(auto()) @map("_id") @db.ObjectId
commitHash String
domain String
githubRepository String
ipfsHash String
}
model tokens {
id String @id @default(auto()) @map("_id") @db.ObjectId
commitHash String
domain String
githubRepository String
ipfsHash String
owner String
tokenId Int
verified Boolean
}

View File

@ -1,30 +1,91 @@
import { APIGatewayProxyResult, APIGatewayEvent } from 'aws-lambda';
import { formatJSONResponse } from '@libs/api-gateway';
const querystring = require('querystring');
import { v4 } from 'uuid';
import { nfaContract } from '@libs/nfa-contract';
import { prisma } from '@libs/prisma';
import { account, nfaContract } from '@libs/nfa-contract';
export const submitBuildInfo = async (
event: APIGatewayEvent
): Promise<APIGatewayProxyResult> => {
try {
const eventData = querystring.parse(event.body);
if (event.body === null) {
return formatJSONResponse({
status: 422,
message: 'Required parameters were not passed.',
});
}
const data = JSON.parse(event.body);
const id = v4();
const buildInfo = {
buildId: id,
createdAt: new Date().toISOString(),
submittedData: eventData,
githubRepository: data.githubRepository,
commitHash: data.commitHash,
ipfsHash: data.ipfsHash,
domain: data.domain,
};
// place holder call
nfaContract.methods
.setTokenBuild(1, 'hash', 'repo')
.call((err: string | undefined, res: any) => {
if (err) throw new Error(err);
console.log('result');
console.log(res);
// Add build record to the database, if it's not already added
const buildRecord = await prisma.builds.findMany({
where: {
commitHash: buildInfo.commitHash,
githubRepository: buildInfo.githubRepository,
ipfsHash: buildInfo.ipfsHash,
domain: buildInfo.domain,
},
});
if (buildRecord.length == 0) {
await prisma.builds.create({
data: {
githubRepository: buildInfo.githubRepository,
commitHash: buildInfo.commitHash,
ipfsHash: buildInfo.ipfsHash,
domain: buildInfo.domain,
},
});
}
const mintRecord = await prisma.tokens.findMany({
where: {
ipfsHash: buildInfo.ipfsHash,
domain: buildInfo.domain,
commitHash: buildInfo.commitHash,
githubRepository: buildInfo.githubRepository,
verified: false,
},
});
if (mintRecord.length > 0) {
// Trigger verification
// Mark the token as verified in the contract
// call the `setTokenVerified` method
await nfaContract.methods
.setTokenVerified(mintRecord[0].tokenId, true)
.send({
from: account.address,
gas: '1000000',
})
.catch(console.error);
// Update the database record in the tokens collection
await prisma.tokens.updateMany({
where: {
ipfsHash: buildInfo.ipfsHash,
domain: buildInfo.domain,
commitHash: buildInfo.commitHash,
githubRepository: buildInfo.githubRepository,
verified: false,
},
data: {
verified: true,
},
});
}
return formatJSONResponse({
buildInfo,

View File

@ -6,31 +6,185 @@ import {
import { formatJSONResponse } from '@libs/api-gateway';
import { v4 } from 'uuid';
import { initPrisma, prisma } from '@libs/prisma';
import { account, nfaContract, web3 } from '@libs/nfa-contract';
export const submitMintInfo = async (
event: APIGatewayEvent
///context: APIGatewayEventRequestContext
): Promise<APIGatewayProxyResult> => {
try {
if (event.body === null) {
return formatJSONResponse({
status: 422,
message: 'Required parameters were not passed.',
});
}
const id = v4();
/**if (!verifyAlchemySig(event.headers.xalchemywork)) {
throw new Error('Invalid sig');
}**/
if (event.body == undefined) {
throw new Error('Undefined data');
}
const eventBody = JSON.parse(event.body);
const topics = eventBody.event.data.block.logs[1].slice(1, 3);
const hexCalldata = eventBody.event.data.block.logs[1].data;
const decodedLogs = web3.eth.abi.decodeLog(
[
{
indexed: true,
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
{
indexed: false,
internalType: 'string',
name: 'name',
type: 'string',
},
{
indexed: false,
internalType: 'string',
name: 'description',
type: 'string',
},
{
indexed: false,
internalType: 'string',
name: 'externalURL',
type: 'string',
},
{
indexed: false,
internalType: 'string',
name: 'ENS',
type: 'string',
},
{
indexed: false,
internalType: 'string',
name: 'commitHash',
type: 'string',
},
{
indexed: false,
internalType: 'string',
name: 'gitRepository',
type: 'string',
},
{
indexed: false,
internalType: 'string',
name: 'ipfsHash',
type: 'string',
},
{
indexed: false,
internalType: 'string',
name: 'logo',
type: 'string',
},
{
indexed: false,
internalType: 'uint24',
name: 'color',
type: 'uint24',
},
{
indexed: false,
internalType: 'bool',
name: 'accessPointAutoApproval',
type: 'bool',
},
{
indexed: true,
internalType: 'address',
name: 'minter',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'owner',
type: 'address',
},
{
indexed: false,
internalType: 'address',
name: 'verifier',
type: 'address',
},
],
hexCalldata,
topics
);
const mintInfo = {
buildId: id,
mintId: id,
createdAt: new Date().toISOString(),
body: JSON.parse(event.body),
tokenId: decodedLogs.tokenId,
githubRepository: decodedLogs.gitRepository,
commit_hash: decodedLogs.commitHash,
owner: decodedLogs.owner,
ipfsHash: decodedLogs.ipfsHash,
domain: decodedLogs.externalURL,
};
// check if we have it in mongo
// if so, trigger verification call
// if not, add to mongo
initPrisma();
// Check if there is any build associated with the repository, commit hash, tokenId, and ipfsHash
const build = await prisma.builds.findMany({
where: {
githubRepository: mintInfo.githubRepository,
commitHash: mintInfo.commit_hash,
ipfsHash: mintInfo.ipfsHash,
domain: mintInfo.domain,
},
});
let verified = false;
if (build.length > 0) {
// Mark the token as verified in the contract
try {
// call the `setTokenVerified` method
await nfaContract.methods
.setTokenVerified(mintInfo.tokenId, true)
.send({
from: account.address,
gas: '1000000',
});
verified = true;
} catch (error) {
// catch transaction error
console.error(error);
}
}
// Add the record to the database
const token = await prisma.tokens.findMany({
where: {
tokenId: Number(mintInfo.tokenId),
},
});
if (token.length == 0) {
await prisma.tokens.create({
data: {
tokenId: Number(mintInfo.tokenId),
githubRepository: mintInfo.githubRepository,
commitHash: mintInfo.commit_hash,
owner: mintInfo.owner,
ipfsHash: mintInfo.ipfsHash,
verified: verified,
domain: mintInfo.domain,
},
});
}
return formatJSONResponse({
mintInfo,

View File

@ -1,9 +1,18 @@
import Web3 from 'web3';
import * as abiFile from '../../../contracts/deployments/goerli/FleekERC721.json';
import * as dotenv from 'dotenv';
dotenv.config();
if (process.env.PRIVATE_KEY === undefined) {
throw Error('Private key environment variable not set.');
}
const contract_address = abiFile.address;
const abi = abiFile.abi as any;
const web3 = new Web3('https://rpc.goerli.mudit.blog');
export const abi = abiFile.abi as any;
export const web3 = new Web3('https://rpc.goerli.mudit.blog');
export const nfaContract = new web3.eth.Contract(abi, contract_address);
export const account = web3.eth.accounts.privateKeyToAccount(
process.env.PRIVATE_KEY
);

View File

@ -10,7 +10,9 @@ export async function initPrisma() {
initPrisma()
.catch(async (e) => {
console.error(e);
})
.finally(async () => {
await prisma.$disconnect();
process.exit(1);
})
.finally(() => {
prisma.$disconnect();
});

View File

@ -11,7 +11,6 @@
"outDir": "dist",
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,

View File

@ -1588,7 +1588,7 @@
resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz"
integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==
"@types/node@*", "@types/node@^18.15.5":
"@types/node@*":
version "18.15.5"
resolved "https://registry.npmjs.org/@types/node/-/node-18.15.5.tgz"
integrity sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew==
@ -1598,6 +1598,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
"@types/node@^18.15.11":
version "18.16.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.1.tgz#5db121e9c5352925bb1f1b892c4ae620e3526799"
integrity sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==
"@types/pbkdf2@^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1"
@ -2767,7 +2772,7 @@ dotenv-expand@^9.0.0:
dotenv@^16.0.3:
version "16.0.3"
resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07"
integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
duration@^0.2.2:
@ -6262,10 +6267,10 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
typescript@^5.0.2:
version "5.0.2"
resolved "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz"
integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==
typescript@^5.0.4:
version "5.0.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==
ultron@~1.1.0:
version "1.1.1"

View File

@ -27,6 +27,7 @@ type NewMint @entity(immutable: true) {
ENS: String!
commitHash: String! # string
gitRepository: String! # string
ipfsHash: String!
logo: String!
color: Int!
accessPointAutoApproval: Boolean!
@ -44,7 +45,7 @@ type MetadataUpdate @entity(immutable: true) {
key: String!
stringValue: String
uint24Value: Int
doubleStringValue: [String!]!
multipleStringValue: [String!]!
booleanValue: Boolean
byAddress: Bytes!
blockNumber: BigInt!
@ -76,12 +77,20 @@ type Token @entity {
owner: Owner!
mintedBy: Bytes!
controllers: [Controller!]
gitRepository: GitRepository!
commitHash: String!
accessPoints: [AccessPoint!] @derivedFrom(field: "token")
verifier: Verifier # Address
verified: Boolean!
createdAt: BigInt!
builds: [Build!]!
}
type Build @entity {
id: Bytes! # Token ID
gitRepository: GitRepository!
commitHash: String!
ipfsHash: String!
domain: String!
token: Token! @derivedFrom(field: "builds")
}
# Owner entity for collection, access points, and tokens
@ -106,7 +115,7 @@ type Verifier @entity {
type GitRepository @entity {
id: String! # transaction hash of the first transaction this repository appeared in
tokens: [Token!] @derivedFrom(field: "gitRepository")
builds: [Build!] @derivedFrom(field: "gitRepository")
}
type AccessPoint @entity {

View File

@ -13,6 +13,7 @@ import {
GitRepository as GitRepositoryEntity,
MetadataUpdate,
Token,
Build,
} from '../generated/schema';
export function handleMetadataUpdateWithStringValue(
@ -61,7 +62,7 @@ export function handleMetadataUpdateWithStringValue(
}
}
export function handleMetadataUpdateWithDoubleStringValue(
export function handleMetadataUpdateWithMultipleStringValues(
event: MetadataUpdateEvent3
): void {
/**
@ -73,30 +74,29 @@ export function handleMetadataUpdateWithDoubleStringValue(
entity.key = event.params.key;
entity.tokenId = event.params._tokenId;
entity.doubleStringValue = event.params.value;
entity.multipleStringValue = event.params.value;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
// UPDATE TOKEN
const token = Token.load(
// CREATE BUILD
const build = new Build(
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();
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]);
}
build.commitHash = event.params.value[0];
build.gitRepository = event.params.value[1];
build.ipfsHash = event.params.value[2];
build.domain = event.params.value[3];
build.save();
gitRepositoryEntity.save();
}
}

View File

@ -23,6 +23,7 @@ export function handleNewMint(event: NewMintEvent): void {
const externalURL = event.params.externalURL;
const ENS = event.params.ENS;
const gitRepository = event.params.gitRepository;
const ipfsHash = event.params.ipfsHash;
const commitHash = event.params.commitHash;
const logo = event.params.logo;
const color = event.params.color;
@ -38,6 +39,7 @@ export function handleNewMint(event: NewMintEvent): void {
newMintEntity.ENS = ENS;
newMintEntity.commitHash = commitHash;
newMintEntity.gitRepository = gitRepository;
newMintEntity.ipfsHash = ipfsHash;
newMintEntity.logo = logo;
newMintEntity.color = color;
newMintEntity.accessPointAutoApproval = accessPointAutoApproval;
@ -68,8 +70,6 @@ export function handleNewMint(event: NewMintEvent): void {
token.description = description;
token.externalURL = externalURL;
token.ENS = ENS;
token.gitRepository = gitRepository;
token.commitHash = commitHash;
token.logo = logo;
token.color = color;
token.accessPointAutoApproval = accessPointAutoApproval;

View File

@ -32,7 +32,7 @@ dataSources:
- ChangeAccessPointAutoApproval
abis:
- name: FleekNFA
file: ../contracts/deployments/goerli/FleekERC721.json
file: ../contracts/artifacts/contracts/FleekERC721.sol/FleekERC721.json
eventHandlers:
- event: Approval(indexed address,indexed address,indexed uint256)
handler: handleApproval
@ -41,13 +41,13 @@ dataSources:
# Token Events
- event: MetadataUpdate(indexed uint256,string,string,indexed address)
handler: handleMetadataUpdateWithStringValue
- event: MetadataUpdate(indexed uint256,string,string[2],indexed address)
handler: handleMetadataUpdateWithDoubleStringValue
- event: MetadataUpdate(indexed uint256,string,string[4],indexed address)
handler: handleMetadataUpdateWithMultipleStringValues
- event: MetadataUpdate(indexed uint256,string,uint24,indexed address)
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,address)
- event: NewMint(indexed uint256,string,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

View File

@ -26,6 +26,12 @@ query lastNFAsPaginated(
}
}
query totalTokens($contractId: ID!) {
collection(id: $contractId) {
totalTokens
}
}
query getNFADetail($id: ID!) {
token(id: $id) {
accessPoints {

View File

@ -7,7 +7,8 @@
"scripts": {
"dev": "vite",
"dev:css": "tailwindcss -o ./tailwind.css --watch && yarn dev",
"build": "yarn graphclient build && vite build",
"build:graph": "yarn graphclient build",
"build": "yarn build:graph && vite build",
"postinstall": "graphclient build",
"preview": "vite preview",
"prod": "yarn build && npx serve dist -s"

View File

@ -11,10 +11,8 @@ export abstract class CardStyles {
borderWidth: '$default',
});
static readonly Heading = styled('h3', {
color: '$slate12',
fontSize: '$xl',
fontWeight: '$medium',
static readonly Header = styled('div', {
width: '$full',
});
static readonly Body = styled('div', {

View File

@ -1,7 +1,6 @@
/* eslint-disable react/display-name */
import React, { forwardRef } from 'react';
import { Flex } from '../layout';
import { CardStyles } from './card.styles';
export abstract class Card {
@ -15,21 +14,7 @@ export abstract class Card {
}
);
static readonly Heading = forwardRef<HTMLHeadingElement, Card.HeadingProps>(
({ title, leftIcon, rightIcon, css, ...props }, ref) => {
return (
<Flex css={{ justifyContent: 'space-between', ...css }}>
<Flex>
{leftIcon}
<CardStyles.Heading ref={ref} {...props}>
{title}
</CardStyles.Heading>
</Flex>
{rightIcon}
</Flex>
);
}
);
static readonly Header = CardStyles.Header;
static readonly Body = forwardRef<HTMLDivElement, Card.BodyProps>(
({ children, ...props }, ref) => {
@ -57,12 +42,7 @@ export namespace Card {
typeof CardStyles.Container
>;
export type HeadingProps = {
title: string;
css?: React.CSSProperties;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
} & React.ComponentProps<typeof CardStyles.Heading>;
export type HeadingProps = React.ComponentProps<typeof CardStyles.Header>;
export type BodyProps = React.ComponentProps<typeof CardStyles.Body>;

View File

@ -0,0 +1,18 @@
import { Card, Flex } from '@/components';
import { styled } from '@/theme';
export const CustomCardStyles = {
Container: styled(Card.Container, {
maxWidth: '$107h',
}),
Title: {
Container: styled(Flex, {
justifyContent: 'space-between',
}),
Text: styled('h3', {
color: '$slate12',
fontSize: '$xl',
fontWeight: '$medium',
}),
},
};

View File

@ -0,0 +1,65 @@
import { Card, Flex, Icon, IconButton } from '@/components';
import { forwardStyledRef } from '@/theme';
import { CardStyles } from '../card.styles';
import { CustomCardStyles as S } from './custom-card.styles';
export const CustomCardContainer = S.Container;
export abstract class CustomCardHeader {
static readonly Default = forwardStyledRef<
HTMLHeadingElement,
CustomCard.HeadingProps
>(({ title, onClickBack, ...props }, ref) => {
return (
<Card.Header ref={ref} {...props}>
<S.Title.Container>
<Flex css={{ gap: '$2' }}>
{onClickBack && (
<IconButton
aria-label="back"
colorScheme="gray"
variant="link"
icon={<Icon name="back" />}
onClick={onClickBack}
/>
)}
<S.Title.Text>{title}</S.Title.Text>
</Flex>
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="info" />}
/>
</S.Title.Container>
</Card.Header>
);
});
static readonly Success = forwardStyledRef<
HTMLHeadingElement,
Omit<CustomCard.HeadingProps, 'onClickBack'>
>(({ title, ...props }, ref) => {
return (
<Card.Header ref={ref} {...props}>
<Flex css={{ gap: '$2' }}>
<Icon
name="check-circle"
css={{ color: '$green11', fontSize: '$xl' }}
/>
<S.Title.Text>{title}</S.Title.Text>
</Flex>
</Card.Header>
);
});
}
export namespace CustomCard {
export type ContainerProps = React.ComponentProps<typeof S.Container>;
export type HeadingProps = {
title: string;
onClickBack?: () => void;
} & React.ComponentProps<typeof CardStyles.Header>;
}

View File

@ -0,0 +1 @@
export * from './custom-card';

View File

@ -1 +1,2 @@
export * from './card';
export * from './custom-card';

View File

@ -126,17 +126,6 @@ const getButtonCompoundVariant = ({
'&:focus, &:active': {
backgroundColor: `$${color}3`,
},
'&:disabled': {
backgroundColor: `initial`,
'&:hover': {
color: `$${color}11`,
backgroundColor: `initial`,
},
'& img, & svg': {
filter: 'grayscale(100%)',
},
},
};
default:

View File

@ -10,8 +10,11 @@ export abstract class PageStyles {
width: '100%',
minHeight: '85vh',
maxWidth: '$6xl',
padding: '0 $6',
padding: '$6',
margin: '0 auto',
display: 'grid',
'@md': {
padding: '0 $6',
},
});
}

View File

@ -1,3 +1,4 @@
import { Text } from '@/components';
import { keyframes, styled } from '@/theme';
const Loading = keyframes({
@ -13,7 +14,7 @@ const Loading = keyframes({
});
export const ResolvedAddressStyles = {
Container: styled('span', {
Container: styled(Text, {
'&[data-loading="true"]': {
animation: `${Loading} 1s ease-in-out infinite`,
},

View File

@ -1,5 +1,6 @@
export const media = {
// Breakpoints
xs: '(min-width: 375px)',
sm: '(min-width: 640px)',
md: '(min-width: 768px)',
lg: '(min-width: 1024px)',

View File

@ -1,4 +1,10 @@
import { Card, Flex, Icon, IconButton, Stepper } from '@/components';
import {
Card,
CustomCardContainer,
CustomCardHeader,
Flex,
Stepper,
} from '@/components';
import { CreateAccessPointFormBody } from './create-ap-form-body';
@ -6,28 +12,8 @@ export const CreateAccessPointForm: React.FC = () => {
const { prevStep } = Stepper.useContext();
return (
<Card.Container css={{ maxWidth: '$107h' }}>
<Card.Heading
title="Enter Domain"
leftIcon={
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="back" />}
css={{ mr: '$2' }}
onClick={prevStep}
/>
}
rightIcon={
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="info" />}
/>
}
/>
<CustomCardContainer>
<CustomCardHeader.Default title="Enter Domain" onClickBack={prevStep} />
<Card.Body>
<Flex
css={{
@ -38,6 +24,6 @@ export const CreateAccessPointForm: React.FC = () => {
<CreateAccessPointFormBody />
</Flex>
</Card.Body>
</Card.Container>
</CustomCardContainer>
);
};

View File

@ -1,6 +1,6 @@
import { useEffect, useMemo } from 'react';
import { Button, Card, Grid, SpinnerDot, Stepper, Text } from '@/components';
import { Button, Card, Flex, SpinnerDot, Stepper, Text } from '@/components';
import { bunnyCDNActions, useAppDispatch, useBunnyCDNStore } from '@/store';
import { useAccessPointFormContext } from '../ap-form-step';
@ -49,9 +49,10 @@ export const APRecordCardBody: React.FC = () => {
</Text>
</Card.Text>
) : (
<Grid
<Flex
css={{
rowGap: '$6',
gap: '$6',
flexDirection: 'column',
}}
>
<Text>
@ -73,7 +74,7 @@ export const APRecordCardBody: React.FC = () => {
>
I added the record
</Button>
</Grid>
</Flex>
)}
</Card.Body>
);

View File

@ -1,29 +1,9 @@
import { Card, Icon, IconButton, Stepper } from '@/components';
import { CustomCardHeader, Stepper } from '@/components';
export const APRecordCardHeader: React.FC = () => {
const { prevStep } = Stepper.useContext();
return (
<Card.Heading
title="Create Record"
leftIcon={
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="back" />}
css={{ mr: '$2' }}
onClick={prevStep}
/>
}
rightIcon={
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="info" />}
/>
}
/>
<CustomCardHeader.Default title="Create Record" onClickBack={prevStep} />
);
};

View File

@ -1,13 +1,13 @@
import { Card } from '@/components';
import { CustomCardContainer } from '@/components';
import { APRecordCardBody } from './ap-record-body';
import { APRecordCardHeader } from './ap-record-header';
export const APRecordStep: React.FC = () => {
return (
<Card.Container css={{ maxWidth: '$107h' }}>
<CustomCardContainer>
<APRecordCardHeader />
<APRecordCardBody />
</Card.Container>
</CustomCardContainer>
);
};

View File

@ -5,9 +5,9 @@ import { useAccount } from 'wagmi';
import {
Button,
Card,
CustomCardContainer,
CustomCardHeader,
Flex,
Icon,
IconButton,
ResolvedAddress,
Stepper,
Text,
@ -40,7 +40,9 @@ export const AccessPointDataFragment: React.FC = () => {
label="Owner"
value={
address ? (
<ResolvedAddress truncated={false}>{address || ''}</ResolvedAddress>
<ResolvedAddress truncated={false} ellipsis>
{address}
</ResolvedAddress>
) : (
'Please connect to wallet'
)
@ -111,28 +113,8 @@ export const CreateAccessPointPreview: React.FC = () => {
}, [writeStatus, transactionStatus]);
return (
<Card.Container css={{ maxWidth: '$107h' }}>
<Card.Heading
title="Review Details"
leftIcon={
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="back" />}
css={{ mr: '$2' }}
onClick={prevStep}
/>
}
rightIcon={
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="info" />}
/>
}
/>
<CustomCardContainer>
<CustomCardHeader.Default title="Review Details" onClickBack={prevStep} />
<Card.Body>
<Flex css={{ flexDirection: 'column', gap: '$6' }}>
<AccessPointDataFragment />
@ -148,6 +130,6 @@ export const CreateAccessPointPreview: React.FC = () => {
</Button>
</Flex>
</Card.Body>
</Card.Container>
</CustomCardContainer>
);
};

View File

@ -0,0 +1,13 @@
import { CustomCardContainer } from '@/components';
import { keyframes, styled } from '@/theme';
const CardKeyFrames = keyframes({
'0%': { opacity: 0 },
'100%': { opacity: 1 },
});
export const CreateApSuccessStyles = {
Container: styled(CustomCardContainer, {
animation: `${CardKeyFrames} 0.5s ease-in-out 0s`,
}),
};

View File

@ -1,29 +1,14 @@
import { Button, Card, Flex, Icon, IconButton, Text } from '@/components';
import { Button, Card, CustomCardHeader, Flex, Icon, Text } from '@/components';
import { CreateAccessPoint } from './create-ap.context';
import { AccessPointDataFragment } from './create-ap-preview';
import { CreateAccessPoint } from '../create-ap.context';
import { AccessPointDataFragment } from '../create-ap-preview';
import { CreateApSuccessStyles as S } from './create-ap-success.styles';
export const CreateAccessPointSuccess: React.FC = () => {
const { nfa } = CreateAccessPoint.useContext();
return (
<Card.Container css={{ maxWidth: '$107h' }}>
<Card.Heading
title="Hosting Successful"
leftIcon={
<Icon
name="check-circle"
css={{ color: '$green11', fontSize: '$xl', mr: '$2' }}
/>
}
rightIcon={
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="info" />}
/>
}
/>
<S.Container>
<CustomCardHeader.Success title="Hosting Successful" />
<Card.Body>
<Flex css={{ flexDirection: 'column', gap: '$6' }}>
<Text css={{ fontSize: '$sm', color: '$slate11' }}>
@ -42,6 +27,6 @@ export const CreateAccessPointSuccess: React.FC = () => {
</Flex>
</Flex>
</Card.Body>
</Card.Container>
</S.Container>
);
};

View File

@ -0,0 +1 @@
export * from './create-ap-success';

View File

@ -1,7 +1,6 @@
import { Flex, Text } from '@/components';
import { styled } from '@/theme';
import { Flex } from '../../../components/layout';
export const DisplayTextStyles = {
Container: styled(Flex, {
flexDirection: 'column',
@ -13,7 +12,7 @@ export const DisplayTextStyles = {
fontSize: '$xs',
//TODO add variants
}),
Input: styled('span', {
Input: styled(Text, {
backgroundColor: '$slate1',
borderColor: '$slate1',
color: '$slate12',

View File

@ -11,7 +11,7 @@ export const DisplayText: React.FC<DisplayTextProps> = ({
return (
<S.Container>
<S.Label>{label}</S.Label>
<S.Input>{value}</S.Input>
<S.Input ellipsis>{value}</S.Input>
</S.Container>
);
};

View File

@ -1,7 +1,10 @@
import { useQuery } from '@apollo/client';
import { useState } from 'react';
import { Combobox, InputGroup, InputGroupText } from '@/components';
import { totalTokensDocument } from '@/graphclient';
import { useDebounce } from '@/hooks';
import { FleekERC721 } from '@/integrations/ethereum/contracts';
import { AppLog } from '@/utils';
import { Explore } from '../explore.context';
@ -21,6 +24,7 @@ const orderResults: SortItem[] = [
export const NFASearchFragment: React.FC = () => {
const {
search,
setEndReached,
setOrderBy,
setOrderDirection,
@ -29,6 +33,13 @@ export const NFASearchFragment: React.FC = () => {
} = Explore.useContext();
const [selectedValue, setSelectedValue] = useState<SortItem>(orderResults[0]);
const { data: totalTokens } = useQuery(totalTokensDocument, {
variables: {
contractId: FleekERC721.address,
},
skip: Boolean(search),
});
const handleSortChange = (item: SortItem | undefined): void => {
if (item) {
setSelectedValue(item);
@ -72,8 +83,10 @@ export const NFASearchFragment: React.FC = () => {
return (
<S.Container>
<S.Data.Wrapper>
<S.Data.Text>All NFAs&nbsp;</S.Data.Text>
<S.Data.Number>(3,271)</S.Data.Number>
{totalTokens?.collection && (<>
<S.Data.Text>All NFAs&nbsp;</S.Data.Text>
<S.Data.Number>({totalTokens.collection.totalTokens})</S.Data.Number>
</>)}
</S.Data.Wrapper>
<S.Input.Wrapper>

View File

@ -1,5 +1,10 @@
import { Card, Grid, Stepper } from '@/components';
import { MintCardHeader } from '@/views/mint/mint-card';
import {
Card,
CustomCardContainer,
CustomCardHeader,
Flex,
Stepper,
} from '@/components';
import { GithubButton } from './github-button';
@ -7,10 +12,10 @@ export const GithubConnect: React.FC = () => {
const { prevStep } = Stepper.useContext();
return (
<Card.Container css={{ maxWidth: '$107h' }}>
<MintCardHeader title="Connect GitHub" onClickBack={prevStep} />
<CustomCardContainer>
<CustomCardHeader.Default title="Connect GitHub" onClickBack={prevStep} />
<Card.Body>
<Grid css={{ rowGap: '$6' }}>
<Flex css={{ gap: '$6', flexDirection: 'column' }}>
<GithubButton />
<Card.Text
css={{
@ -24,8 +29,8 @@ export const GithubConnect: React.FC = () => {
After connecting your GitHub, your repositories will show here.
</span>
</Card.Text>
</Grid>
</Flex>
</Card.Body>
</Card.Container>
</CustomCardContainer>
);
};

View File

@ -1,5 +1,5 @@
import { CustomCardHeader } from '@/components';
import { Mint } from '@/views/mint/mint.context';
import { MintCardHeader } from '@/views/mint/mint-card';
import { useMintFormContext } from '@/views/mint/nfa-step/form-step';
export const RepoConfigurationHeader: React.FC = () => {
@ -22,7 +22,7 @@ export const RepoConfigurationHeader: React.FC = () => {
};
return (
<MintCardHeader
<CustomCardHeader.Default
title="Configure Repository"
onClickBack={handlePrevStepClick}
/>

View File

@ -1,13 +1,13 @@
import { Card } from '@/components';
import { CustomCardContainer } from '@/components';
import { RepoConfigurationBody } from './repo-configuration-body';
import { RepoConfigurationHeader } from './repo-configuration-header';
export const GithubRepoConfiguration: React.FC = () => {
return (
<Card.Container css={{ minWidth: '17rem', maxWidth: '$107h' }}>
<CustomCardContainer css={{ minWidth: '17rem' }}>
<RepoConfigurationHeader />
<RepoConfigurationBody />
</Card.Container>
</CustomCardContainer>
);
};

View File

@ -1,13 +1,21 @@
import { Card, Flex, Icon } from '@/components';
import {
Card,
CustomCardContainer,
CustomCardHeader,
Flex,
Icon,
} from '@/components';
import { styled } from '@/theme';
export const GithubRepositorySelectionStyles = {
Card: {
Wrapper: styled(Card.Container, {
maxWidth: '$107h',
Wrapper: styled(CustomCardContainer, {
maxHeight: '$95h',
pr: '$3h',
}),
Header: styled(CustomCardHeader.Default, {
pr: '$3h',
}),
Body: styled(Card.Body, {
pt: '$4',
}),

View File

@ -1,14 +1,6 @@
import React, { useState } from 'react';
import {
Card,
Flex,
Icon,
IconButton,
InputGroup,
InputGroupText,
Spinner,
} from '@/components';
import { Flex, InputGroup, InputGroupText, Spinner } from '@/components';
import { useDebounce } from '@/hooks/use-debounce';
import { useGithubStore } from '@/store';
import { Mint } from '@/views/mint/mint.context';
@ -55,27 +47,9 @@ export const GithubRepositoryConnection: React.FC = () => {
return (
<S.Card.Wrapper>
<Card.Heading
<S.Card.Header
title="Select Repository"
css={{ pr: '$3h' }}
leftIcon={
<IconButton
aria-label="back"
colorScheme="gray"
variant="link"
icon={<Icon name="back" />}
css={{ mr: '$2' }}
onClick={handlePrevStepClick}
/>
}
rightIcon={
<IconButton
aria-label="info"
colorScheme="gray"
variant="link"
icon={<Icon name="info" />}
/>
}
onClickBack={handlePrevStepClick}
/>
<S.Card.Body>
<S.Container>

View File

@ -1 +0,0 @@
export * from './mint-card';

View File

@ -1,35 +0,0 @@
import { Card, Icon, IconButton } from '@/components';
type MintCardHeaderProps = {
title: string;
onClickBack: () => void;
};
export const MintCardHeader: React.FC<MintCardHeaderProps> = ({
title,
onClickBack,
}: MintCardHeaderProps) => {
return (
<Card.Heading
title={title}
leftIcon={
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="back" />}
css={{ mr: '$2' }}
onClick={onClickBack}
/>
}
rightIcon={
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="info" />}
/>
}
/>
);
};

View File

@ -12,6 +12,7 @@ export const MintStepper: React.FC = () => {
const {
transaction: { isSuccess },
} = Mint.useTransactionContext();
const {
form: {
isValid: [, setIsValid],

View File

@ -6,7 +6,14 @@ export const MintStyles = {
height: '100%',
justifyContent: 'center',
'@media (min-width: 1024px)': {
'@md': {
//to align on center
position: 'absolute',
top: '50%',
transform: 'translateY(-50%)',
},
'@lg': {
flexDirection: 'row',
},
}),

View File

@ -1,11 +1,17 @@
import { useAccount } from 'wagmi';
import { Button, Card, Grid, Stepper } from '@/components';
import {
Button,
Card,
CustomCardContainer,
CustomCardHeader,
Flex,
Stepper,
} from '@/components';
import { AppLog } from '@/utils';
import { parseColorToNumber } from '@/utils/color';
import { Mint } from '../../mint.context';
import { MintCardHeader } from '../../mint-card';
import {
AppDescriptionField,
AppNameField,
@ -78,20 +84,24 @@ export const MintFormStep: React.FC = () => {
};
return (
<Card.Container css={{ maxWidth: '$107h' }}>
<MintCardHeader title="NFA Details" onClickBack={handlePrevStep} />
<CustomCardContainer>
<CustomCardHeader.Default
title="NFA Details"
onClickBack={handlePrevStep}
/>
<Card.Body>
<Grid
<Flex
css={{
rowGap: '$6',
gap: '$6',
flexDirection: 'column',
}}
>
<Grid css={{ rowGap: '$4' }}>
<Flex css={{ gap: '$4', flexDirection: 'column' }}>
<AppNameField />
<AppDescriptionField />
<EnsDomainField />
<LogoField />
</Grid>
</Flex>
<Button
disabled={!isValid}
colorScheme="blue"
@ -100,8 +110,8 @@ export const MintFormStep: React.FC = () => {
>
Continue
</Button>
</Grid>
</Flex>
</Card.Body>
</Card.Container>
</CustomCardContainer>
);
};

View File

@ -0,0 +1,74 @@
import { useQuery } from '@apollo/client';
import { useEffect, useMemo } from 'react';
import { getVerifiersDocument } from '@/../.graphclient';
import { Form, ResolvedAddress } from '@/components';
import { useENSStore } from '@/store';
import { useMintFormContext } from '../form-step';
// TODO: remove mocked items after graphql api is fixed
const mockedItems = [
'0xdBb04e00D5ec8C9e3aeF811D315Ee7C147c5DBFD',
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049',
];
export const SelectVerifier: React.FC = () => {
const {
form: { verifier },
} = useMintFormContext();
const {
value: [selectedVerifier, setSelectedVerifier],
} = verifier;
const { addressMap } = useENSStore();
const { data } = useQuery(getVerifiersDocument);
const items = useMemo(() => {
if (!data) return [];
const verifiers = data.verifiers
.map<string>((verifier) => verifier.id.toString())
.concat(mockedItems);
return verifiers.map((verifier) => ({
address: verifier,
ens: addressMap[verifier]?.value,
}));
}, [data, addressMap]);
useEffect(() => {
if (!selectedVerifier && items.length > 0) {
setSelectedVerifier(items[0].address);
}
}, [selectedVerifier, setSelectedVerifier, items]);
return (
<Form.Field context={verifier}>
<Form.Combobox
items={items}
handleValue={(item) => item.address}
queryKey={['address', 'ens']}
>
{({ Field, Options }) => (
<>
<Field>
{(selected) =>
selected ? (
<ResolvedAddress>{selected.address}</ResolvedAddress>
) : (
'Select a Verifier'
)
}
</Field>
<Options>
{(item) => <ResolvedAddress>{item.address}</ResolvedAddress>}
</Options>
</>
)}
</Form.Combobox>
</Form.Field>
);
};

View File

@ -0,0 +1,22 @@
import { Card, Flex, Text } from '@/components';
import { styled } from '@/theme';
export const VerifyNfaStepStyles = {
Body: {
Container: styled(Flex, {
flexDirection: 'column',
gap: '$6',
}),
Text: styled(Text, {
color: '$slate11',
fontSize: '$sm',
}),
VerifyContainer: styled(Card.Text, {
p: '$4',
textAlign: 'left',
flexDirection: 'row',
justifyContent: 'space-between',
borderRadius: '$lg',
}),
},
};

View File

@ -1,88 +1,17 @@
import { useQuery } from '@apollo/client';
import { useEffect, useMemo } from 'react';
import { getVerifiersDocument } from '@/../.graphclient';
import {
Button,
Card,
Flex,
Form,
ResolvedAddress,
CustomCardContainer,
CustomCardHeader,
Stepper,
Switch,
Text,
} from '@/components';
import { useENSStore } from '@/store';
import { Mint } from '../../mint.context';
import { MintCardHeader } from '../../mint-card';
import { useMintFormContext } from '../form-step';
// TODO: remove mocked items after graphql api is fixed
const mockedItems = [
'0xdBb04e00D5ec8C9e3aeF811D315Ee7C147c5DBFD',
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049',
];
const SelectVerifier: React.FC = () => {
const {
form: { verifier },
} = useMintFormContext();
const {
value: [selectedVerifier, setSelectedVerifier],
} = verifier;
const { addressMap } = useENSStore();
const { data } = useQuery(getVerifiersDocument);
const items = useMemo(() => {
if (!data) return [];
const verifiers = data.verifiers
.map<string>((verifier) => verifier.id.toString())
.concat(mockedItems);
return verifiers.map((verifier) => ({
address: verifier,
ens: addressMap[verifier]?.value,
}));
}, [data, addressMap]);
useEffect(() => {
if (!selectedVerifier && items.length > 0) {
setSelectedVerifier(items[0].address);
}
}, [selectedVerifier, setSelectedVerifier, items]);
return (
<Form.Field context={verifier}>
<Form.Combobox
items={items}
handleValue={(item) => item.address}
queryKey={['address', 'ens']}
>
{({ Field, Options }) => (
<>
<Field>
{(selected) =>
selected ? (
<ResolvedAddress>{selected.address}</ResolvedAddress>
) : (
'Select a Verifier'
)
}
</Field>
<Options>
{(item) => <ResolvedAddress>{item.address}</ResolvedAddress>}
</Options>
</>
)}
</Form.Combobox>
</Form.Field>
);
};
import { SelectVerifier } from './select-verifier';
import { VerifyNfaStepStyles as S } from './verify-nfa-step.styles';
export const VerifyNFAStep: React.FC = () => {
const { prevStep } = Stepper.useContext();
@ -100,30 +29,22 @@ export const VerifyNFAStep: React.FC = () => {
};
return (
<Card.Container css={{ maxWidth: '$107h' }}>
<MintCardHeader title="Verify NFA" onClickBack={prevStep} />
<CustomCardContainer>
<CustomCardHeader.Default title="Verify NFA" onClickBack={prevStep} />
<Card.Body>
<Flex css={{ flexDirection: 'column', gap: '$6' }}>
<Text css={{ color: '$slate11', fontSize: '$sm' }}>
<S.Body.Container>
<S.Body.Text>
Below you can allow Fleek to be added as a controller to your NFA.
This will allow Fleek to automatically verify your NFA and update
builds and other metadata. It will not allow Fleek to transfer or
burn your NFT. You can change this setting later on your NFA but
adding it now will save you a transaction in the future. We
recommend it so that your users can get verified NFAs.
</Text>
<Card.Text
css={{
p: '$4',
textAlign: 'left',
flexDirection: 'row',
justifyContent: 'space-between',
borderRadius: '$lg',
}}
>
</S.Body.Text>
<S.Body.VerifyContainer>
<Text css={{ color: '$slate12' }}>Verify NFA</Text>
<Switch checked={verifyNFA} onChange={setVerifyNFA} />
</Card.Text>
</S.Body.VerifyContainer>
<SelectVerifier />
<Button
colorScheme="blue"
@ -133,8 +54,8 @@ export const VerifyNFAStep: React.FC = () => {
>
Continue
</Button>
</Flex>
</S.Body.Container>
</Card.Body>
</Card.Container>
</CustomCardContainer>
);
};

View File

@ -1,4 +1,11 @@
import { Button, Card, Grid } from '@/components';
import {
Button,
Card,
CustomCardContainer,
CustomCardHeader,
Flex,
Text,
} from '@/components';
import { NFAPreview } from '@/components';
import { useMintFormContext } from '../nfa-step/form-step';
@ -16,8 +23,6 @@ type NftCardProps = {
export const NftCard: React.FC<NftCardProps> = ({
title,
leftIcon,
rightIcon,
message,
buttonText,
leftIconButton,
@ -43,23 +48,21 @@ export const NftCard: React.FC<NftCardProps> = ({
} = useMintFormContext();
return (
<Card.Container css={{ maxWidth: '$107h', p: '$0' }}>
<CustomCardContainer css={{ p: '$0' }}>
<NFAPreview
color={logoColor}
logo={appLogo}
name={appName}
ens={ens}
size={size}
className="rounded-t-xhl"
css={{
bt: '1.25rem',
}}
/>
<Card.Body css={{ p: '$7' }}>
<Grid css={{ rowGap: '$6' }}>
<Card.Heading
title={title}
leftIcon={leftIcon}
rightIcon={rightIcon}
/>
<span className="text-slate11 text-sm">{message}</span>
<Flex css={{ gap: '$6', flexDirection: 'column' }}>
<CustomCardHeader.Success title={title} />
<Text css={{ color: '$slate11', fontSize: '$sm' }}>{message}</Text>
<Button
colorScheme="blue"
variant="solid"
@ -70,8 +73,8 @@ export const NftCard: React.FC<NftCardProps> = ({
>
{buttonText}
</Button>
</Grid>
</Flex>
</Card.Body>
</Card.Container>
</CustomCardContainer>
);
};

View File

@ -1,6 +1,6 @@
import { ConnectKitButton } from 'connectkit';
import { Button, Stepper } from '@/components';
import { Stepper } from '@/components';
import { ButtonConnection } from '../button-connection';
@ -9,19 +9,14 @@ export const ConnectWalletButton: React.FC = () => {
return (
<ConnectKitButton.Custom>
{({ isConnected, show, truncatedAddress, address }) => {
{({ isConnected, show, address }) => {
if (isConnected && address) {
return (
<Button onClick={nextStep} css={{ color: '$slate12' }}>
{truncatedAddress}. Continue
</Button>
);
nextStep();
} else {
return (
<ButtonConnection
icon={'ethereum'}
label={' Connect Wallet'}
disabled={isConnected}
label={'Connect Wallet'}
onClick={show}
/>
);

View File

@ -1,23 +1,18 @@
import { Card, Grid, Icon, IconButton } from '@/components';
import {
Card,
CustomCardContainer,
CustomCardHeader,
Flex,
} from '@/components';
import { ConnectWalletButton } from './connect-wallet-button';
export const WalletStep: React.FC = () => {
return (
<Card.Container css={{ maxWidth: '$107h' }}>
<Card.Heading
title="Connect Wallet"
rightIcon={
<IconButton
aria-label="Add"
colorScheme="gray"
variant="link"
icon={<Icon name="info" />}
/>
}
/>
<CustomCardContainer>
<CustomCardHeader.Default title="Connect Wallet" />
<Card.Body>
<Grid css={{ rowGap: '$6' }}>
<Flex css={{ gap: '$6', flexDirection: 'column' }}>
<ConnectWalletButton />
<Card.Text
css={{
@ -29,8 +24,8 @@ export const WalletStep: React.FC = () => {
>
<span>Connect with the wallet you want to mint & own the NFA.</span>
</Card.Text>
</Grid>
</Flex>
</Card.Body>
</Card.Container>
</CustomCardContainer>
);
};

2866
yarn.lock

File diff suppressed because it is too large Load Diff