Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统

参考

文档

The Complete Guide to Full Stack Web3 Development - DEV Communityy​​​​​​​y

源码

GitHub - dabit3/full-stack-web3: A full stack web3 on-chain blog and CMS 

框架

博客系统将会部署在polygon,因为polygon交易费用比较低。整体项目框架

  • 区块链:polygon
  • eth开发环境:Hardhat
  • 前端框架:Next.js 和 React
  • 文件存储:IPFS
  • 检索: The Graph Protocol

 前置准备

  • node.js 环境
  • vscode
  • metamask钱包

开始开发

创建项目

 npx create-next-app web3-blog

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第1张图片

cd web3-blog

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第2张图片

 丰富一下package.json,新增

    "@openzeppelin/contracts": "^4.3.2",
    "@walletconnect/web3-provider": "^1.6.6",
    "hardhat": "^2.6.7",
    "ipfs-http-client": "^56.0.0",
    "web3modal": "^1.9.4",
    "react-markdown": "^7.1.0",
    "react-simplemde-editor": "^5.0.2",
    "@emotion/css": "^11.5.0"

  },
  "devDependencies": {
    "@nomiclabs/hardhat-ethers": "^2.0.0",
    "@nomiclabs/hardhat-waffle": "^2.0.0",
    "chai": "^4.2.0",
    "eslint": "7",
    "eslint-config-next": "12.0.1",
    "ethereum-waffle": "^3.0.0",
    "ethers": "^5.0.0"
  }

hardhat - Ethereum 开发环境
web3modal - 方便快速的连接钱包
react-markdown and simplemde - Markdown editor and markdown renderer for the CMS
@emotion/css - A great CSS in JS library
@openzeppelin/contracts -开源的solidity框架

#安装包依赖
npm install

准备hardhat部署脚本


npx hardhat

#选 Create an empty hardhat.config.js

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第3张图片

开始编码

修改 styles/globals.css 文件,具体代码参考github,不贴了

public 文件夹添加 logo.svg and right-arrow.svg 

智能合约

// contracts/Blog.sol
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "hardhat/console.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract Blog {
    string public name;
    address public owner;

    using Counters for Counters.Counter;
    Counters.Counter private _postIds;

    struct Post {
      uint id;
      string title;
      string content;
      bool published;
    }
    /* mappings can be seen as hash tables */
    /* here we create lookups for posts by id and posts by ipfs hash */
    mapping(uint => Post) private idToPost;
    mapping(string => Post) private hashToPost;

    /* events facilitate communication between smart contractsand their user interfaces  */
    /* i.e. we can create listeners for events in the client and also use them in The Graph  */
    event PostCreated(uint id, string title, string hash);
    event PostUpdated(uint id, string title, string hash, bool published);

    /* when the blog is deployed, give it a name */
    /* also set the creator as the owner of the contract */
    constructor(string memory _name) {
        console.log("Deploying Blog with name:", _name);
        name = _name;
        owner = msg.sender;
    }

    /* updates the blog name */
    function updateName(string memory _name) public {
        name = _name;
    }

    /* transfers ownership of the contract to another address */
    function transferOwnership(address newOwner) public onlyOwner {
        owner = newOwner;
    }

    /* fetches an individual post by the content hash */
    function fetchPost(string memory hash) public view returns(Post memory){
      return hashToPost[hash];
    }

    /* creates a new post */
    function createPost(string memory title, string memory hash) public onlyOwner {
        _postIds.increment();
        uint postId = _postIds.current();
        Post storage post = idToPost[postId];
        post.id = postId;
        post.title = title;
        post.published = true;
        post.content = hash;
        hashToPost[hash] = post;
        emit PostCreated(postId, title, hash);
    }

    /* updates an existing post */
    function updatePost(uint postId, string memory title, string memory hash, bool published) public onlyOwner {
        Post storage post =  idToPost[postId];
        post.title = title;
        post.published = published;
        post.content = hash;
        idToPost[postId] = post;
        hashToPost[hash] = post;
        emit PostUpdated(post.id, title, hash, published);
    }

    /* fetches all posts */
    function fetchPosts() public view returns (Post[] memory) {
        uint itemCount = _postIds.current();

        Post[] memory posts = new Post[](itemCount);
        for (uint i = 0; i < itemCount; i++) {
            uint currentId = i + 1;
            Post storage currentItem = idToPost[currentId];
            posts[i] = currentItem;
        }
        return posts;
    }

    /* this modifier means only the contract owner can */
    /* invoke the function */
    modifier onlyOwner() {
      require(msg.sender == owner);
    _;
  }
}

合约允许拥有者创建,编辑博客内容,允许任何人获取内容

测试合约 

test/sample-test.js

onst { expect } = require("chai")
const { ethers } = require("hardhat")

describe("Blog", async function () {
  it("Should create a post", async function () {
    const Blog = await ethers.getContractFactory("Blog")
    const blog = await Blog.deploy("My blog")
    await blog.deployed()
    await blog.createPost("My first post", "12345")

    const posts = await blog.fetchPosts()
    expect(posts[0].title).to.equal("My first post")
  })

  it("Should edit a post", async function () {
    const Blog = await ethers.getContractFactory("Blog")
    const blog = await Blog.deploy("My blog")
    await blog.deployed()
    await blog.createPost("My Second post", "12345")

    await blog.updatePost(1, "My updated post", "23456", true)

    posts = await blog.fetchPosts()
    expect(posts[0].title).to.equal("My updated post")
  })

  it("Should add update the name", async function () {
    const Blog = await ethers.getContractFactory("Blog")
    const blog = await Blog.deploy("My blog")
    await blog.deployed()

    expect(await blog.name()).to.equal("My blog")
    await blog.updateName('My new blog')
    expect(await blog.name()).to.equal("My new blog")
  })
})
npx hardhat test

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第4张图片

 部署合约

部署前先启动本地eth网络

npx hardhat node

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第5张图片

启动成功后,可以看到20个测试账号,后续测试开发可以用

修改部署脚本 scripts/deploy.js

/* scripts/deploy.js */
const hre = require("hardhat");
const fs = require('fs');

async function main() {
  /* these two lines deploy the contract to the network */
  const Blog = await hre.ethers.getContractFactory("Blog");
  const blog = await Blog.deploy("My blog");

  await blog.deployed();
  console.log("Blog deployed to:", blog.address);

  /* this code writes the contract addresses to a local */
  /* file named config.js that we can use in the app */
  fs.writeFileSync('./config.js', `
  export const contractAddress = "${blog.address}"
  export const ownerAddress = "${blog.signer.address}"
  `)
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

执行部署

npx hardhat run scripts/deploy.js --network localhost

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第6张图片 

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第7张图片 部署成功,合约地址:0x5fbdb2315678afecb367f032d93f642f64180aa3 

钱包

前面创建的地址,选一个

Account #0: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

添加网络

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第8张图片

导入账号 ,前面选择的秘钥0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

查看余额

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第9张图片 

Next.js app 

环境配置文件

先创建环境配置文件.env.local

ENVIRONMENT="local"
NEXT_PUBLIC_ENVIRONMENT="local"

变量可以切换localtestnet, and mainnet

context.js

import { createContext } from 'react'

export const AccountContext = createContext(null)

Layout and Nav

打开pages/_app.js ,修改,参考github代码

Entrypoint入口页面

打开pages/index.js,参考github代码

发布博客页面 

pages/create-post.js,参考github代码

查看博客内容页面

博客的详情地址类似为,myapp.com/post/some-post-id,修改文件pages/post/[id].js

编辑博客内容

修改文件 pages/post/[id].js

调试运行

npm run dev

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第10张图片

或者使用vscode调试

launch.json

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch via npm",
            "type": "node",
            "request": "launch",
            "cwd": "${workspaceFolder}",
            "runtimeExecutable": "npm",
            "runtimeArgs": ["run-script", "dev"]
          }
          
    ]
}

 

执行失败,报错

Error: could not detect network (event="noNetwork", code=NETWORK_ERROR, version=providers/5.7.2)

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第11张图片 

搜索代码

    provider = new ethers.providers.JsonRpcProvider()

#改为

    provider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:8545/')

保存后,又报错

Error: Invalid with child. Please remove or use .

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第12张图片 

搜索代码

 

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第13张图片 跑起来了

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第14张图片

连接metamask钱包

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第15张图片 

发帖

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第16张图片 失败了,看了一下infura使用的不太对,改一下

const client = create('https://ipfs.infura.io:5001/api/v0')

改为

const projectId = 'xxxxxxx';
const projectSecret = 'xxxxxxxx';
const auth = 'Basic ' + Buffer.from(projectId + ':' + projectSecret).toString('base64');

/* define the ipfs endpoint */
const client = create({
  host: 'ipfs.infura.io',
  port: 5001,
  protocol: 'https',
  headers: {
      authorization: auth,
  },
})

搞定

 

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第17张图片

查看帖子又报错

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第18张图片 

debug发现多了一个/,去掉

Next.js Polygon, Solidity,The Graph,IPFS,Hardhat web3博客系统_第19张图片 

 

 

你可能感兴趣的:(web3)