diff --git a/.gitignore b/.gitignore index 4295d0b..5d05b3a 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,7 @@ node_modules cache artifacts -*_raw \ No newline at end of file +# contract type generation +/contracts/types + +*_raw diff --git a/README.md b/README.md index ba8441a..a291672 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,54 @@ -## Full stack NFT marketplace built with Polygon, Solidity, IPFS, & Next.js +# Creative Comomons NFT Playground -![Header](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pfofv47dooojerkmfgr4.png) +## 📢Tips +- 🌏 Every NFT here is licensed under Creative Commons Attribution 4.0 International License. 🅭 +- 🎁 Buying NFT is to donate tokens to the NFT minter(maybe the author). You can't sell it to others. +- 🎓 Please don’t mint anything that doesn’t belong to you. We have a Dweb DAO to help check cheating behavious. (Welcome to join the DAO.) -### Running this project +## 📢注意 +- 🌏 这里的所有NFT作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 🅭 +- 🎁 购买NFT仅仅意味着你向NFT铸造者(可能是作品作者)捐款。你不能将该NFT卖给其他人。 +- 🎓 请不要将任何不属于你的作品铸造为NFT。 我们有一个Dweb DAO帮助检测欺骗行为。(也欢迎加入) -#### Gitpod -To deploy this project to Gitpod, follow these steps: +## The Features +- 🌏 All Creative Commons licensed articles will be stored on IPFS and indexed in dweb search engines(such as Dweb Search). +- 🎁 Authers can mint the articles as NFTs and push them to market with very low gas fee(with Polygon network). Users can buy NFTs (articles) which they like, just for donation. +- 🎓 The NFTs will be stored both on IPFS and Filecoin with nft.storage and Filecoin-Polygon-Bridge so we can help store valuable data on web permanently. -1. Click this link to deploy -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#github.com/dabit3/polygon-ethereum-nextjs-marketplace) +## (O_O)? Question? +- 🔗 We use mumbai test network. You can add it to your Metamask with Chainlist +- 🌏 You can join Dweb Lab Discord +- 🌃 There is also an IPFS version: ipfs://QmSp1Y8TJLQ2dJyYd5uZS7iqGDLPChf2rSsK1R8KkTgY2i -2. In __pages/index.js__, pass in the RPC address given to you by GitPod to the call to `JsonRpcProvider` function: -```javascript -/* update this: */ -const provider = new ethers.providers.JsonRpcProvider() +## License +https://opensource.org/licenses/MIT -/* to this: */ -const provider = new ethers.providers.JsonRpcProvider("https://8545-youendpoint.gitpod.io/") -``` -3. Import the RPC address given to you by GitPod into your MetaMask wallet +## Design +## 设计 +- 1 人类:理想情况下,高质量知识应该免费公开传播 +- 2 作者:作者需要生存 +- 3 读者:读者面对众多作品,注意力有限,难以高效分辨作品质量是否符合自己的需求 +- 4 市场:如果一个作品能被多次交易,容易让人陷入炒作而非关注作品本身;但部分读者有实力支持作者 +- 为了让以上4者更好的共存,提供一个作品发布工具,作品均以CC-BY-SA分享(满足1),无付费墙和传播墙;可选制作为NFT,购买NFT的主要作用是捐赠作者,即使不购买NFT也可以直接给作者捐赠(满足2); 捐赠也帮助增加作品的影响力,用于3次方投票;读者可自由参考3次方投票辅助选择作品,多了一个选择的维度(满足3);单个作品只能被购买一次(满足4) -![MetaMask RPC Import](wallet.png) +## 博弈分析 +- 问题1 作者会考虑自己开多个账号自己买自己的作品来增加影响力。每个策展平台自由选择影响因子算法;而3次方投票仅为其中一个影响因子 +- 问题2 作品均可以免费查看,无需NFT,只需给作者捐赠就好。是的,这只是一个用于支持Creative Comomons内容的NFT实验,nft-for-fun +- 问题3 作者可能会重复发自己的作品赚取费用,由于信息公开可查,检测应该不难 +- 问题4 NFT流动性差。我们不在乎NFT的流动性 -#### Local setup -To run this project locally, follow these steps. +## 名词解释 +- CC-BY-SA 知识共享-署名-相同方式共享 (属于自由文化许可协议) +- DwebVerse Marketplace -1. Clone the project locally, change into the directory, and install the dependencies: -```sh -git clone https://github.com/dabit3/polygon-ethereum-nextjs-marketplace.git - -cd polygon-ethereum-nextjs-marketplace - -# install using NPM or Yarn -npm install - -# or - -yarn -``` - -2. Start the local Hardhat node - -```sh -npx hardhat node -``` - -3. With the network running, deploy the contracts to the local network in a separate terminal window - -```sh -npx hardhat run scripts/deploy.js --network localhost -``` - -4. Start the app - -``` -npm run dev -``` - -### Configuration - -To deploy to Polygon test or main networks, update the configurations located in __hardhat.config.js__ to use a private key and, optionally, deploy to a private RPC like Infura. - -```javascript -require("@nomiclabs/hardhat-waffle"); -const fs = require('fs'); -const privateKey = fs.readFileSync(".secret").toString().trim() || "01234567890123456789"; - -// infuraId is optional if you are using Infura RPC -const infuraId = fs.readFileSync(".infuraid").toString().trim() || ""; - -module.exports = { - defaultNetwork: "hardhat", - networks: { - hardhat: { - chainId: 1337 - }, - mumbai: { - // Infura - // url: `https://polygon-mumbai.infura.io/v3/${infuraId}` - url: "https://rpc-mumbai.matic.today", - accounts: [privateKey] - }, - matic: { - // Infura - // url: `https://polygon-mainnet.infura.io/v3/${infuraId}`, - url: "https://rpc-mainnet.maticvigil.com", - accounts: [privateKey] - } - }, - solidity: { - version: "0.8.4", - settings: { - optimizer: { - enabled: true, - runs: 200 - } - } - } -}; -``` - -If using Infura, update __.infuraid__ with your [Infura](https://infura.io/) project ID. +## 使用方法 +- step1: 发布metadata信息,信息存在dweb search engine等开放接口的引擎里。信息公开 +- step2: mint 只需要支付不到1分钱的gas费用,但作品没有被发到NFT market +- step3: 将NFT发到市场上,可在市场公开查看,需要支付不到1分钱的gas费 +- step4: 买家买走 diff --git a/README_raw.md b/README_raw.md new file mode 100644 index 0000000..ba8441a --- /dev/null +++ b/README_raw.md @@ -0,0 +1,109 @@ +## Full stack NFT marketplace built with Polygon, Solidity, IPFS, & Next.js + +![Header](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pfofv47dooojerkmfgr4.png) + +### Running this project + +#### Gitpod + +To deploy this project to Gitpod, follow these steps: + +1. Click this link to deploy + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#github.com/dabit3/polygon-ethereum-nextjs-marketplace) + +2. In __pages/index.js__, pass in the RPC address given to you by GitPod to the call to `JsonRpcProvider` function: + +```javascript +/* update this: */ +const provider = new ethers.providers.JsonRpcProvider() + +/* to this: */ +const provider = new ethers.providers.JsonRpcProvider("https://8545-youendpoint.gitpod.io/") +``` + +3. Import the RPC address given to you by GitPod into your MetaMask wallet + +![MetaMask RPC Import](wallet.png) + +#### Local setup + +To run this project locally, follow these steps. + +1. Clone the project locally, change into the directory, and install the dependencies: + +```sh +git clone https://github.com/dabit3/polygon-ethereum-nextjs-marketplace.git + +cd polygon-ethereum-nextjs-marketplace + +# install using NPM or Yarn +npm install + +# or + +yarn +``` + +2. Start the local Hardhat node + +```sh +npx hardhat node +``` + +3. With the network running, deploy the contracts to the local network in a separate terminal window + +```sh +npx hardhat run scripts/deploy.js --network localhost +``` + +4. Start the app + +``` +npm run dev +``` + +### Configuration + +To deploy to Polygon test or main networks, update the configurations located in __hardhat.config.js__ to use a private key and, optionally, deploy to a private RPC like Infura. + +```javascript +require("@nomiclabs/hardhat-waffle"); +const fs = require('fs'); +const privateKey = fs.readFileSync(".secret").toString().trim() || "01234567890123456789"; + +// infuraId is optional if you are using Infura RPC +const infuraId = fs.readFileSync(".infuraid").toString().trim() || ""; + +module.exports = { + defaultNetwork: "hardhat", + networks: { + hardhat: { + chainId: 1337 + }, + mumbai: { + // Infura + // url: `https://polygon-mumbai.infura.io/v3/${infuraId}` + url: "https://rpc-mumbai.matic.today", + accounts: [privateKey] + }, + matic: { + // Infura + // url: `https://polygon-mainnet.infura.io/v3/${infuraId}`, + url: "https://rpc-mainnet.maticvigil.com", + accounts: [privateKey] + } + }, + solidity: { + version: "0.8.4", + settings: { + optimizer: { + enabled: true, + runs: 200 + } + } + } +}; +``` + +If using Infura, update __.infuraid__ with your [Infura](https://infura.io/) project ID. diff --git a/contracts/Market.sol b/contracts/Market.sol index 6d9a933..8243629 100644 --- a/contracts/Market.sol +++ b/contracts/Market.sol @@ -4,17 +4,14 @@ pragma solidity ^0.8.3; import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - import "hardhat/console.sol"; contract NFTMarket is ReentrancyGuard { using Counters for Counters.Counter; Counters.Counter private _itemIds; Counters.Counter private _itemsSold; - address payable owner; - // uint256 listingPrice = 0.025 ether; - uint256 listingPrice = 0.001 ether; + uint256 listingPrice = 0.0001 ether; constructor() { owner = payable(msg.sender); @@ -31,6 +28,7 @@ contract NFTMarket is ReentrancyGuard { } mapping(uint256 => MarketItem) private idToMarketItem; + mapping(uint256 => MarketItem) private tidToMarketItem; event MarketItemCreated ( uint indexed itemId, @@ -56,9 +54,9 @@ contract NFTMarket is ReentrancyGuard { require(price > 0, "Price must be at least 1 wei"); require(msg.value == listingPrice, "Price must be equal to listing price"); - _itemIds.increment(); + _itemIds.increment(); // should not use itemid uint256 itemId = _itemIds.current(); - + idToMarketItem[itemId] = MarketItem( itemId, nftContract, @@ -68,6 +66,16 @@ contract NFTMarket is ReentrancyGuard { price, false ); + // fix bug, use tokenId, needed when sale + tidToMarketItem[tokenId] = MarketItem( + itemId, + nftContract, + tokenId, + payable(msg.sender), + payable(address(0)), + price, + false + ); IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId); @@ -88,14 +96,22 @@ contract NFTMarket is ReentrancyGuard { address nftContract, uint256 itemId ) public payable nonReentrant { - uint price = idToMarketItem[itemId].price; - uint tokenId = idToMarketItem[itemId].tokenId; + // Note: idToMarketItem maybe less than allItem(some item-minted-not-goto-market) + // so price = allItem[itemId].price + // so tokenId = allItem[itemId].tokenId // tokenId === itemId + + // uint tokenId = idToMarketItem[itemId].tokenId; + // get price by itemid + uint tokenId = itemId; + uint price = tidToMarketItem[itemId].price; + require(msg.value == price, "Please submit the asking price in order to complete the purchase"); idToMarketItem[itemId].seller.transfer(msg.value); IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId); idToMarketItem[itemId].owner = payable(msg.sender); idToMarketItem[itemId].sold = true; + // tidToMarketItem _itemsSold.increment(); payable(owner).transfer(listingPrice); } @@ -115,9 +131,11 @@ contract NFTMarket is ReentrancyGuard { currentIndex += 1; } } + return items; } + /* Returns only items that a user has purchased */ function fetchMyNFTs() public view returns (MarketItem[] memory) { uint totalItemCount = _itemIds.current(); diff --git a/contracts/NFT.sol b/contracts/NFT.sol index b2e101d..3ce00af 100644 --- a/contracts/NFT.sol +++ b/contracts/NFT.sol @@ -5,47 +5,27 @@ import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; - import "hardhat/console.sol"; -// contract DwebNFT is ERC721, Ownable { contract NFT is ERC721URIStorage { using Counters for Counters.Counter; Counters.Counter private _tokenIds; address contractAddress; - - using Strings for uint256; + using Strings for uint256; // uint256 is uint mapping (uint256 => string) private _tokenURIs; - // constructor() ERC721("DwebNFT", "DNFT") {} - // constructor(address marketplaceAddress) ERC721("Metaverse", "METT") { - constructor(address marketplaceAddress) ERC721("DwebVerse", "DWV") { + + constructor(address marketplaceAddress) ERC721("CC4", "CC4") { contractAddress = marketplaceAddress; } - // function mint + // mint, uri is tokenURI function createToken(string memory uri) public returns (uint) { _tokenIds.increment(); uint256 newItemId = _tokenIds.current(); - // recipient is msg.sender - // uri is tokenURI - // uint256 is uint - _mint(msg.sender, newItemId); + _mint(msg.sender, newItemId); // msg.sender is recipient _setTokenURI(newItemId, uri); setApprovalForAll(contractAddress, true); return newItemId; } - // function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { - // _tokenURIs[tokenId] = _tokenURI; - // } - - // function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { - // require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); - // string memory _tokenURI = _tokenURIs[tokenId]; - // return _tokenURI; - // } - - - - } \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js index e02dab0..494b94c 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -13,7 +13,9 @@ module.exports = { // Infura // url: `https://polygon-mumbai.infura.io/v3/${infuraId}` // url: "https://rpc-mumbai.matic.today", - url: "https://rpc-mumbai.maticvigil.com", + // url: "https://rpc-mumbai.maticvigil.com", + url: "https://matic-mumbai.chainstacklabs.com", + // url: "https://rpc-mumbai.maticvigil.com/v1/35346f853fb4496728602ff72a70eb9a8785064e", accounts: [privateKey] }, matic: { diff --git a/next.config.js b/next.config.js index 0d60710..0cb803b 100644 --- a/next.config.js +++ b/next.config.js @@ -1,3 +1,5 @@ -module.exports = { - reactStrictMode: true, -} +const withTM = require("next-transpile-modules")(["react-markdown"]); + +module.exports = withTM({ + reactStrictMode: true +}); diff --git a/package.json b/package.json index 49b4727..1c8168d 100644 --- a/package.json +++ b/package.json @@ -5,22 +5,30 @@ "scripts": { "dev": "next dev", "build": "next build", - "start": "next start", + "start": "export PORT=4006 next start", "lint": "next lint" }, "dependencies": { + "@headlessui/react": "^1.4.1", + "@heroicons/react": "^1.0.4", "@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-waffle": "^2.0.1", "@openzeppelin/contracts": "^4.2.0", + "@textile/eth-storage": "^1.0.0", "axios": "^0.21.1", "chai": "^4.3.4", "ethereum-waffle": "^3.4.0", "ethers": "^5.4.1", + "graphql-tag": "^2.12.6", + "gray-matter": "^4.0.3", "hardhat": "^2.4.1", "ipfs-http-client": "^50.1.2", - "next": "11.0.1", - "react": "17.0.2", - "react-dom": "17.0.2", + "next": "^12.0.4", + "next-transpile-modules": "^8.0.0", + "nft.storage": "^3.3.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-markdown": "^7.1.0", "web3modal": "^1.9.3" }, "devDependencies": { diff --git a/pages/_app.js b/pages/_app.js index 02d27cf..30c5ddd 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,36 +1,223 @@ import '../styles/globals.css' +import '../styles/markdown.css' import Link from 'next/link' +import Head from "next/head"; +import { useState } from 'react' +import Web3Modal from 'web3modal' +import Web3 from 'web3' + +import { Menu, Transition } from '@headlessui/react' +import { Fragment, useEffect, useRef } from 'react' +import { ChevronDownIcon } from '@heroicons/react/solid' +import { useRouter } from 'next/router' + +let provider function Marketplace({ Component, pageProps }) { + const [ethAccount, setethAccount] = useState(null) + const [Logined, setLogined] = useState(false) + + useEffect(() => { + + if (typeof window !== "undefined") { + const aethAccount = localStorage.getItem("ethAccount") + if (aethAccount){ + setethAccount(aethAccount) + setLogined(true); + } + } + + }, []) + +async function ConnectWallet() { + // const web3Modal = new Web3Modal() + // const connection = await web3Modal.connect() + // const provider = new ethers.providers.Web3Provider(connection) + + const web3Modal = new Web3Modal({ + cacheProvider: false, // optional + // providerOptions, // required + disableInjectedProvider: false, // optional. For MetaMask / Brave / Opera. + }); + console.log("Web3Modal instance is", web3Modal); + console.log("Opening a dialog", web3Modal); + try { + provider = await web3Modal.connect(); + } catch(e) { + console.log("Could not get a wallet connection", e); + return; + } + + // Get a Web3 instance for the wallet + // const web3 = new Web3(provider); + // const web3 = await web3Modal.connect() + // const web3 = new ethers.providers.Web3Provider(provider) + const web3 = new Web3(provider); + console.log("Web3 instance is", web3); + // Get connected chain id from Ethereum node + const chainId = await web3.eth.getChainId(); + // Load chain information over an HTTP API + // const chainData = evmChains.getChain(chainId); + // console.log("chainData is", chainData); + console.log("chainId is", chainId); + + // Get list of accounts of the connected wallet + const accounts = await web3.eth.getAccounts(); + + // MetaMask does not give you all accounts, only the selected account + console.log("Got accounts", accounts); + if(accounts.length > 0) { + setethAccount(accounts[0]) + setLogined(true); + console.log("Got ethAccount", accounts[0]); + + // useEffect(function() { + // // console.log(window.localStorate); + // localStorage.setItem("ethAccount", accounts[0]) + // },[]); + + if (typeof window !== "undefined") { + localStorage.setItem("ethAccount", accounts[0]) + } + + } +} + +async function DisconnectWallet() { + if (typeof window !== "undefined") { + localStorage.removeItem("ethAccount") + } + setLogined(false); + setethAccount(null); + console.log("Killing the wallet connection", provider); + // TODO: Which providers have close method? + if(provider.close) { + await provider.close(); + // If the cached provider is not cleared, + // WalletConnect will default to the existing session + // and does not allow to re-scan the QR code with a new wallet. + // Depending on your use case you may want or want not his behavir. + await web3Modal.clearCachedProvider(); + provider = null; + } + +} + +function getBrief(astr) { + if(!astr) return "" + return astr.substring(0,6)+'...'+astr.substr(astr.length - 4) +} + return (
+ + Creative Comomons NFT Playground + + + + + +
+ + ) } diff --git a/pages/article.js b/pages/article.js new file mode 100644 index 0000000..ac6b04b --- /dev/null +++ b/pages/article.js @@ -0,0 +1,208 @@ +import { ethers } from 'ethers' +import { useEffect, useState } from 'react' +import axios from 'axios' +import Web3Modal from "web3modal" +import { useRouter } from 'next/router' +import matter from 'gray-matter' +// import ReactMarkdown from "react-markdown" +import ReactMarkdown from 'react-markdown/react-markdown.min'; + +import { + nftmarketaddress, nftaddress +} from '../config' + +// const hre = require("hardhat"); + +import NFT from '../artifacts/contracts/NFT.sol/NFT.json' +import Market from '../artifacts/contracts/Market.sol/NFTMarket.json' + +import { providers } from "ethers"; +import { init } from "@textile/eth-storage"; + +let ethAccount +let myethAccount +let cid +let nft = {} +export default function MyAssets() { + // const [nft, setNft] = useState({}) + const [loadingState, setLoadingState] = useState('not-loaded') + const router = useRouter() + console.log(router) // pathname: '/', route: '/', asPath: '/' + if (router.pathname == '/article') { + if (typeof document !== 'undefined') { + var els = document.getElementsByClassName("_nav"); + Array.prototype.forEach.call(els, function(el) { + el.classList.remove('current'); + }); + } + } + + async function createMint() { + /* first, upload to IPFS */ + try { + const url = `https://ipfs.infura.io/ipfs/${cid}` + console.log('!nft.minted', !nft.minted) + /* after file is uploaded to IPFS, pass the URL to save it on Polygon */ + createSale(url) + } catch (error) { + console.log('Error uploading file: ', error) + } + } + + async function storeNFTtoFilecoin() { + await window.ethereum.enable(); + const provider = new providers.Web3Provider(window.ethereum); + const wallet = provider.getSigner(); + + const storage = await init(wallet); + // const blob = new Blob(["Hello, world!"], { type: "text/plain" }); + const jsonse = JSON.stringify(nft); + const blob = new Blob([jsonse], {type: "application/json"}); + const file = new File([blob], "welcome.txt", { + type: "text/plain", + lastModified: new Date().getTime() + }); + try{ + await storage.addDeposit(); // "execution reverted: BridgeProvider: depositee already has deposit" + } catch (error) { + console.error(error); + } + + const { id, cid } = await storage.store(file); + const { request, deals } = await storage.status(id); + + console.log('id, cid, request, deals', id, cid, request, deals) + console.log(request.status_code); + + console.log([...deals]); + console.log('stored~') + alert('Your NFT has been stored on Filecoin Network~') + } + + async function createSale(url) { + const web3Modal = new Web3Modal() + const connection = await web3Modal.connect() + const provider = new ethers.providers.Web3Provider(connection) + const signer = provider.getSigner() + + /* next, create the item */ + let contract = new ethers.Contract(nftaddress, NFT.abi, signer) + console.log(NFT.abi) + console.log(signer) + console.log("signer") + console.log(contract) + let transaction = await contract.createToken(url) + console.log(transaction) + let tx = await transaction.wait() + + let event = tx.events[0] + console.log(tx) + console.log(event) + // console.log(event.getBlock()) + // console.log(event.getTransaction()) + // console.log(event.getTransactionReceipt()) + let value = event.args[2] + let tokenId = value.toNumber() + + const price = ethers.utils.parseUnits('0.1', 'ether') + /* then list the item for sale on the marketplace */ + contract = new ethers.Contract(nftmarketaddress, Market.abi, signer) + let listingPrice = await contract.getListingPrice() + listingPrice = listingPrice.toString() + transaction = await contract.createMarketItem(nftaddress, tokenId, price, { value: listingPrice }) + await transaction.wait() + } + + if (typeof window !== 'undefined') { + myethAccount = localStorage.getItem("ethAccount"); + console.log('myethAccount', myethAccount) + } + + console.log(router.query) + console.log(nft) + console.log(loadingState != 'loaded', !nft) + + // const { cid } = router.query + // console.log(cid) + if (loadingState != 'loaded' && !('name' in nft)) { + loadNFT() + } + + useEffect(() => { + loadNFT() + }, []) + + async function loadNFT() { + console.log(router.query) + // const dweb_search_url = `https://dweb-search-api.anwen.cc/get_meta?cid=${cid}` + if ('cid' in router.query){ + cid = router.query.cid + console.log('cid', cid) + } + console.log('cid2', cid) + if (!cid) { + return + } + const ipfs_gateway_url = `https://ipfs.infura.io/ipfs/${cid}` + const ret = await axios.get(ipfs_gateway_url) // TODO + console.log(ret) + // authors[0].name + if ('data' in ret) { + // const { data, content } = matter(ret.data.description) + // console.log('data, content') + // console.log(data, content) + // const result = await remark().use(html).process(content); + // ret.data.description = result.toString() + console.log(ret.data.description) + // useEffect(() => { setNft(ret.data) }, []) + nft = ret.data + console.log('nft.minted', nft.minted) + console.log('aname', ret.data.authors[0].name) + } + setLoadingState('loaded') + } + + if (loadingState != 'loaded' && !('name' in nft)) return ( +

+ ) + if (loadingState === 'loaded' && !('name' in nft)) return ( +

No creation

+ ) + return ( +
+
+
+ +
+

{nft.name}

+
+ {nft.description} +
+

By: + {nft.authors[0].name} +     Author-Wallet: {nft.authors[0].wallet.eth} +

+ +

Tags: {nft.tags}

+

License: {nft.license}

+ + {!('minted' in nft) && (nft.authors[0].wallet.eth==myethAccount) && + + } +
+ {(nft.authors[0].wallet.eth==myethAccount) && + + } + +
+
+
+
+ + ) + +} \ No newline at end of file diff --git a/pages/articles-all.js b/pages/articles-all.js new file mode 100644 index 0000000..d7cffa6 --- /dev/null +++ b/pages/articles-all.js @@ -0,0 +1,74 @@ +import { ethers } from 'ethers' +import { useEffect, useState } from 'react' +import axios from 'axios' +import Web3Modal from "web3modal" +import { useRouter } from 'next/router' + +import { + nftmarketaddress, nftaddress +} from '../config' + +let ethAccount +export default function MyAssets() { + const [nfts, setNfts] = useState([]) + const [loadingState, setLoadingState] = useState('not-loaded') + const router = useRouter() + console.log(router) // pathname: '/', route: '/', asPath: '/' + if (router.pathname == '/articles-all') { + if (typeof document !== 'undefined') { + var els = document.getElementsByClassName("_nav"); + Array.prototype.forEach.call(els, function(el) { + console.log(el.tagName); + console.log(el.classList); + el.classList.remove('current'); + }); + document.getElementById("_articles_all").classList.add('current'); + } + } + + console.log('router.query', router.query) + ethAccount = '*' + useEffect(() => { + loadNFTs() + }, []) + async function loadNFTs() { + const dweb_search_url = `https://dweb-search-api.anwen.cc/get_meta?eth=${ethAccount}` + console.log(dweb_search_url) + const ret = await axios.get(dweb_search_url) // TODO + console.log(ret); + // setNfts(items) + if ("data" in ret.data){ + setNfts(ret.data.data) + } + setLoadingState('loaded') + } + if (loadingState === 'loaded' && !nfts.length) return ( +

No creations

+ ) + + return ( +
+
+
+ { + nfts.map((nft, i) => ( +
+ +
+ +

{nft.name}

+
+

By: + {nft.authors} +

+ Tags: {nft.tags} +
+
+ + )) + } +
+
+
+ ) +} \ No newline at end of file diff --git a/pages/articles-my.js b/pages/articles-my.js new file mode 100644 index 0000000..cf74794 --- /dev/null +++ b/pages/articles-my.js @@ -0,0 +1,71 @@ +import { ethers } from 'ethers' +import { useEffect, useState } from 'react' +import axios from 'axios' +import Web3Modal from "web3modal" +import { useRouter } from 'next/router' + +import { + nftmarketaddress, nftaddress +} from '../config' + +let ethAccount +export default function MyAssets() { + const [nfts, setNfts] = useState([]) + const [loadingState, setLoadingState] = useState('not-loaded') + const router = useRouter() + + if (router.pathname == '/articles-my') { + if (typeof document !== 'undefined') { + var els = document.getElementsByClassName("_nav"); + Array.prototype.forEach.call(els, function(el) { + el.classList.remove('current'); + }); + document.getElementById("_articles_my").classList.add('current'); + } + } + if (typeof window !== 'undefined') { + ethAccount = localStorage.getItem("ethAccount"); + } + + useEffect(() => { + loadNFTs() + }, []) + async function loadNFTs() { + const dweb_search_url = `https://dweb-search-api.anwen.cc/get_meta?eth=${ethAccount}` + console.log(dweb_search_url) + const ret = await axios.get(dweb_search_url) // TODO + console.log(ret); + if ("data" in ret.data){ + setNfts(ret.data.data) + } + setLoadingState('loaded') + } + if (loadingState === 'loaded' && !nfts.length) return ( +

0 creations

+ ) + + return ( +
+
+
+ { + nfts.map((nft, i) => ( +
+ +
+ +

{nft.name}

+
+

By: + {nft.authors} +

+
+
+ + )) + } +
+
+
+ ) +} \ No newline at end of file diff --git a/pages/articles.js b/pages/articles.js new file mode 100644 index 0000000..fa92078 --- /dev/null +++ b/pages/articles.js @@ -0,0 +1,84 @@ +import { ethers } from 'ethers' +import { useEffect, useState } from 'react' +import axios from 'axios' +import Web3Modal from "web3modal" +import { useRouter } from 'next/router' + +import { + nftmarketaddress, nftaddress +} from '../config' + +let ethAccount +export default function MyAssets() { + const [nfts, setNfts] = useState([]) + const [loadingState, setLoadingState] = useState('not-loaded') + + // useEffect(function() { + // },[]); + const router = useRouter() + console.log(router) + if (router.pathname == '/articles') { + if (typeof document !== 'undefined') { + var els = document.getElementsByClassName("_nav"); + Array.prototype.forEach.call(els, function(el) { + el.classList.remove('current'); + }); + } + } + + console.log(router.query) + if ('author' in router.query){ + ethAccount = router.query.author + console.log('ethAccount', ethAccount) + if (ethAccount=='me'){ + if (typeof window !== 'undefined') { + ethAccount = localStorage.getItem("ethAccount"); + } + } + } + useEffect(() => { + loadNFTs() + }, []) + // loadNFTs() + async function loadNFTs() { + if (ethAccount) { + const dweb_search_url = `https://dweb-search-api.anwen.cc/get_meta?eth=${ethAccount}` + console.log(dweb_search_url, '222') + const ret = await axios.get(dweb_search_url) // TODO + console.log(ret); + // setNfts(items) + if ("data" in ret.data){ + setNfts(ret.data.data) + } + setLoadingState('loaded') + } + } + if (loadingState === 'loaded' && !nfts.length) return ( +

No creations

+ ) + + return ( +
+
+
+ { + nfts.map((nft, i) => ( +
+ +
+ +

{nft.name}

+
+

By: + {nft.authors} +

+
+
+ + )) + } +
+
+
+ ) +} \ No newline at end of file diff --git a/pages/ccmarket.js b/pages/ccmarket.js new file mode 100644 index 0000000..fbda07b --- /dev/null +++ b/pages/ccmarket.js @@ -0,0 +1,94 @@ +import { ethers } from 'ethers' +import { useEffect, useState } from 'react' +import axios from 'axios' +import Web3Modal from "web3modal" + +import { + nftaddress, nftmarketaddress +} from '../config' + +import NFT from '../artifacts/contracts/NFT.sol/NFT.json' +import Market from '../artifacts/contracts/Market.sol/NFTMarket.json' + +export default function Home() { + const [nfts, setNfts] = useState([]) + const [loadingState, setLoadingState] = useState('not-loaded') + useEffect(() => { + loadNFTs() + }, []) + async function loadNFTs() { + const provider = new ethers.providers.JsonRpcProvider("https://rpc-mumbai.maticvigil.com") + // const provider = new ethers.providers.JsonRpcProvider("https://rpc-mumbai.matic.today") + // const provider = new ethers.providers.JsonRpcProvider("https://polygon-mumbai.infura.io/v3/6d993cb640374f1b8baf01f5eddaed8e") + const tokenContract = new ethers.Contract(nftaddress, NFT.abi, provider) + const marketContract = new ethers.Contract(nftmarketaddress, Market.abi, provider) + const data = await marketContract.fetchMarketItems() + + const items = await Promise.all(data.map(async i => { + const tokenUri = await tokenContract.tokenURI(i.tokenId) + const meta = await axios.get(tokenUri) + console.log(i.price.toString(), 'raw price') + let price = ethers.utils.formatUnits(i.price.toString(), 'ether') + let item = { + price, + tokenId: i.tokenId.toNumber(), + seller: i.seller, + owner: i.owner, + image: meta.data.image, + name: meta.data.name, + description: meta.data.description, + } + console.log(price) + return item + })) + console.log(items) + items.reverse() + setNfts(items) + setLoadingState('loaded') + } + async function buyNft(nft) { + const web3Modal = new Web3Modal() + const connection = await web3Modal.connect() + const provider = new ethers.providers.Web3Provider(connection) + const signer = provider.getSigner() + const contract = new ethers.Contract(nftmarketaddress, Market.abi, signer) + console.log(nft.price.toString()) + const price = ethers.utils.parseUnits(nft.price.toString(), 'ether') + // const price = "0.0001" + // const price = nft.price.toString() + console.log(price) + console.log(nftaddress) + console.log(nft.tokenId) + const transaction = await contract.createMarketSale(nftaddress, nft.tokenId, { + value: price + }) + await transaction.wait() + loadNFTs() + } + if (loadingState === 'loaded' && !nfts.length) return (

No items in marketplace

) + return ( +
+
+
+ { + nfts.map((nft, i) => ( +
+ +
+

{nft.name}

+
+

{nft.description}

+
+
+
+

{nft.price} Matic

+ +
+
+ )) + } +
+
+
+ ) +} diff --git a/pages/create.js b/pages/create.js new file mode 100644 index 0000000..c1aab67 --- /dev/null +++ b/pages/create.js @@ -0,0 +1,171 @@ +import { useState } from 'react' +import { ethers } from 'ethers' +import { create as ipfsHttpClient } from 'ipfs-http-client' +import { useRouter } from 'next/router' +import axios from 'axios' +import { NFTStorage } from 'nft.storage' + +const client = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0') +import { + nftaddress, nftmarketaddress +} from '../config' + +import NFT from '../artifacts/contracts/NFT.sol/NFT.json' +import Market from '../artifacts/contracts/Market.sol/NFTMarket.json' + +let ethAccount +export default function CreateItem() { + const [fileUrl, setFileUrl] = useState(null) + const [afile, setAFile] = useState(null) + const [formInput, updateFormInput] = useState({ price: '', name: '', description: '', + s_tags: '', names: '' + }) + const router = useRouter() + console.log(router) + if (router.pathname == '/create') { + if (typeof document !== 'undefined') { + var els = document.getElementsByClassName("_nav"); + Array.prototype.forEach.call(els, function(el) { + el.classList.remove('current'); + }); + document.getElementById("_create").classList.add('current'); + } + } + + if (typeof window !== 'undefined') { + ethAccount = localStorage.getItem("ethAccount"); + } + console.log(ethAccount); + + async function onChange(e) { + const file = e.target.files[0] + console.log(file) + console.log('file will be uploaded') + try { + const added = await client.add( + file, + { + progress: (prog) => console.log(`received: ${prog}`) + } + ) + alert('file is uploaded!') + const url = `https://ipfs.infura.io/ipfs/${added.path}` + console.log(url) + console.log(added) // added.size file.size + setAFile(file) // file.name file.type "image/png" + setFileUrl(url) + } catch (error) { + console.log('Error uploading file: ', error) + alert('Error uploading file: ', error) + } + } + async function PublishIt() { + const { name, description, s_tags, names } = formInput + if (!afile) { + alert('Please upload FEATURED IMAGE') + } + const _filename = afile.name.split('.') + console.log(afile.type, _filename[_filename.length - 1]) + const filetype = afile.type + const filesize = afile.size + const filename = afile.name + if (!name || !description || !s_tags || !fileUrl || !ethAccount) { + alert("Title, Content, Tags, Feature Image, eth-account are not optional") + return + } + /* first, upload metadata to IPFS */ + const license = "CC-BY-SA" + const license_url = "https://creativecommons.org/licenses/by-sa/4.0/" + const tags = s_tags.split(" ") + const authors = [{ + "name": names, + "wallet": { + "eth": ethAccount + } + }] + const data = JSON.stringify({ + name, description, image: fileUrl, + license, license_url, + filesize, filename, + filetype, tags, authors + }) + + // const endpoint = 'https://api.nft.storage' // the default + const API_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJnaXRodWJ8MTQ3Mjg1MCIsImlzcyI6Im5mdC1zdG9yYWdlIiwiaWF0IjoxNjE4ODQ0ODAwOTgzLCJuYW1lIjoiZGVmYXVsdCJ9.bdDjCtOaANp49ysENB4-4xpVrhDRbdeqV39t5aVYsjo' // your API key from https://nft.storage/manage + try { + // const store = new NFTStorage({ endpoint, token }) + const client = new NFTStorage({ token: API_TOKEN }) + const cid = await client.storeBlob(new Blob([data])) + const status = await client.status(cid) + console.log(cid) + console.log(status) + } catch (error) { + console.log('Error uploading file to nft.storage: ', error) + } + + try { + const added = await client.add(data) + console.log(added) + const url = `https://ipfs.infura.io/ipfs/${added.path}` + // We also store metadata in Dweb Search + const dweb_search_url = `https://dweb-search-api.anwen.cc/add_meta?path=${added.path}ð=${ethAccount}&name=${name}&image=${fileUrl}&tags=${s_tags}&authors=${names}` + + const ret = await axios.get(dweb_search_url) // TODO + console.log(ret) + /* after file is uploaded to IPFS, pass the URL to save it on Polygon */ + // createNFT(url) + router.push('/articles-my') + } catch (error) { + console.log('Error uploading file: ', error) + } + } + + + return ( +
+
+ +

Attention:

+

- All your published data and metadata is open to public and with CC-BY-SA License.

+

- They will be on IPFS and Dweb Search Engine too.

+

- It’s forbidden to mint anything which doesn’t belong to you.

+ updateFormInput({ ...formInput, name: e.target.value })} + /> +