从0开始搭建基于eth私有链的NFT交易平台
从0开始搭建基于eth私有链的NFT交易平台
基本信息
注:以下内容参考了许多网络上其他教程,基于前辈们的基础上作者才成功完成私有链的搭建与使用,撰写此文是希望可以帮助到更多新手小白有一个相对完整的教程来快速起步!如有不足之处还请各路大神多多指教!
本文基于 Windows 10 操作系统,使用 Geth 客户端搭建 ETH 私有链、使用 Remix 编写及部署智能合约,使用 MetaMask 钱包管理账户。
根据本教程的步骤,你最终可以获得一个基于 ETH 私有链搭建的、搭载了智能合约的、可创建并交易 NFT 的一个平台...? 希望文章里的内容可以帮助到你。
ETH私有链搭建
前期准备
Step 1 :客户端下载
下载geth客户端 https://geth.ethereum.org/downloads/
以下为本文使用的Geth客户端版本信息
Geth
Version: 1.10.26-stable
Git Commit: e5eb32acee19cc9fca6a03b10283b7484246b15a
Git Commit Date: 20221103
Architecture: amd64
Go Version: go1.18.5
Operating System: windows
GOPATH=C:\MyCode
GOROOT=go
Step 2:准备目录结构
随便新建个文件夹放数据,目录结构如下:
document
|--file_location // 这个里面放geth生成的数据啥的
|
|--genesis.json // 创世区块配置文件
Step 3:准备创世区块配置文件
genesis.json
{
"config": {
//区块链的ID,随便起
"chainId": 21,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0
},
// 用来预置账号以及账号内的以太币数量,应该也就是所谓的预挖
// 我这里不需要预挖,所以给了个空对象
// 如果需要可以这样加
// "alloc": {
// "0x0000000000000000000000000000000000000001": {"balance": "111111111"},
// "0x0000000000000000000000000000000000000002": {"balance": "222222222"}
// }
"alloc": {},
// 币基地址,也就是默认的钱包地址,因为我没有地址,所以全0,为空
// 后面运行Geth后创建新账户时,如果Geth发现没有币基地址,会默认将第一个账户的地址设置为币基地址,俗称矿工账号
"coinbase": "0x0000000000000000000000000000000000000000",
// 出块/挖矿难度,可以随便控制,这里设置的难度比较小
"difficulty": "0x4000",
// 附加信息,随便填个文本或不填也行,类似中本聪在比特币创世块中写的报纸新闻
"extraData": "",
// gas最高限制,以太坊运行交易,合约等所消耗的gas最高限制,这里设置为最高
"gasLimit": "0xffffffff",
// 64位随机数,用于挖矿,注意他和mixhash的设置需要满足以太坊黄皮书中的要求
"nonce": "0x0000000000000042",
// 与nonce共同用于挖矿,注意他和nonce的设置需要满足以太坊黄皮书中的要求
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
// 上一个区块的Hash值,因为是创世块,石头里蹦出来的,没有在它前面的,所以是0
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
// 创世块的时间戳,这里给0就好
"timestamp": "0x00"
}
注意!
genesis.json
文件里不能有注释,用的时候记得删了!
以下是对应字段的解释说明,具体请以官网信息为准:
key | 说明 |
---|---|
chainId | 网络ID,区分不同的区块链网络,值为0代表以太坊主网 |
coinbase | 一个账户地址,挖矿收益最终归属的账户 |
difficulty | 挖矿难度 |
gasLimit | 创世块能够消耗gas的上限,即最多消耗的gas值;智能合约运行在EVM上,运行机器码指令,每个指令都会对应相应的gas消耗,gas与以太不是等价的,它们之前有换算关系,gas * gasPrice = ether, gasPrice是gas单价(单位wei),可以上下浮动(感觉跟市场油价一样会发生变动) |
nonce | 随机数,挖矿的时候寻找到符合条件的nonce值 |
mixhash | 本块的hash值,因为是创世块,所以没有hash值,初始化为0 |
parentHash | 父块hash值,因为是创世块,所以没有父块hash值,初始化为0 |
timestamp | 时间戳,是从1970-01-01 00:00:00开始计算以秒为单位 |
Geth常用参数说明
可以通过
geth -h
帮助指令查看所以指令及对应功能说明,以下常用指令说明
指令 | 说明 |
---|---|
--datadir | 指定之前初始化的数据目录文件(指定工作目录) |
--networkid | 区分不同的区块链网络,与创世块chainId一样,0为以太坊主网 |
--port | 节点之间互相通信的端囗,默认是30303 |
--rpc | 开启远程调用服务,执行智能合约时连接的节点是借助于rpc服务 |
--rpcport | 远程服务端囗,默认是8545 |
--rpcapi | 远程服务提供的远程服务调用函数集(db、net、eth、web3、personal等) |
--rpccorsdomain | 指定可以接收请求来源的域名列表(浏览器访问时必须开启),默认为 “* ” |
--gasprice | gas的单价,单位wei |
--allow-insecure-unlock | 允许在Geth命令窗囗解锁账户(新版本1.9.0+增加的选项) |
--console | 进入管理后台(如修改rpc端囗) |
Geth常用命令说明
初始化命令
初次创建 Geth 私有链需要对区块链进行初始化,以后需要修改创世块设置时也需要初始化,之后其他情况不再需要初始化。
cmd 进入到 file_location 文件夹(也就是 genesis.json 所在的文件夹)键入以下命令:
geth --datadir file_location init genesis.json
命令解读:
geth ...... init
:初始化区块链
参数:
--datadir file_location
:指定数据存放目录为 file_location 位置,file_location 为目录名genesis.json
是init
的命令参数,表示使用此 json 文件进行初始化
初始化最后输出
Successfully xxxxx
就代表成功了
成功后会生成下列目录结构
document
├──file_location
│ ├── geth
│ │ ├── chaindata
│ │ │ ├── 000001.log
│ │ │ ├── CURRENT
│ │ │ ├── LOCK
│ │ │ ├── LOG
│ │ │ └── MANIFEST-000000
│ │ └── lightchaindata
│ │ ├── 000001.log
│ │ ├── CURRENT
│ │ ├── LOCK
│ │ ├── LOG
│ │ └── MANIFEST-000000
│ └── keystore // 这里以后会放在此节点创建的账号文件啥的
│ ├──
└── genesis.json
开启节点命令
初始化好区块链之后,我们需要开启这个区块链的节点,相当于是“启动”区块链。
cmd 进入到 document 文件夹(也就是 file_location 上层文件夹)键入以下命令:
geth --datadir file_location --networkid 202339 --http --http.addr 0.0.0.0 --http.port 8545 --http.corsdomain "*" --port 30305 --allow-insecure-unlock console
命令解读:
--networkid
:用于区分不同的区块链网络,与创世块chainId一样,0为以太坊主网--http
.addr
.port
指定区块链启动端口为8545.corsdomain
--port
--allow-insecure-unlock
运行完上面的代码后cmd内应该输出 Welcome to the Geth JavaScript console!
title:可能出现的报错
- 错误1:`Fatal: Error starting protocol stack: Access is denied.`
- 错误原因:因为你开启了 2 个或以上的 geth 进程。
- 解决方法:需要关闭另外的 ethereum 客户端。
其他相关命令
从cmd连接刚刚启动的私有链的控制台
geth attach ipc:http://127.0.0.1:8545
删除 filename位置的块
geth removedb --datadir filename
日志相关指令
在启动 Geth 的指令里加 console
指令,这样在开启节点的同时开启了 console 控制台。
但是节点启动后会一直在终端打印日志,会有点影响我们后续的操作观感。
这个时候可以使用以下两种指令来停止终端打印日志。
# 停止输出日志
geth <other flags> console 2> /dev/null
# 保存日志到某个位置
geth <other flags> console --verbosity 3 2> geth-logs.log
- 如果不需要日志,将他们重定向到
dev/null
的位置即可; - 如果还需要日志,那么可以把
dev/null
的路径换成一个文本文件的路径,日志将会被存入其中。
日志中提供的详细程度可以通过向 --verbosity
标志提供一个1-6之间的值来调整,如上述命令里的数字 3 。
Geth管理后台常用命令
管理后台使用的是 Javascript 语言。
如果你不太了解这门语言也不用担心,下面会介绍一些基础的指令,掌握这些指令足够满足你的基础使用需求。
查看此节点所有账户
> eth.accounts
此指令会返回一个数组,里面装载着此区块链里导入的所有账户地址。
因为我们刚刚才创建完一个全新的区块链,且我们在创世块文件 genesis.json 里并没有预设币基地址,所以此时在 Geth 客户端里键入这个命令只会返回一个空数组 []
。
新建账户
让我们来创建一个全新的账户用来存储在私有链上挖矿获得的奖励吧!
使用 personal.newAccount()
函数指令来创建一个账户:
> personal.newAccount()
Passphrase: // 输入密码
Repeat passphrase: // 再次输入密码
"0xc232e2add308136571bb8f9197ba4ae4e5ba9836"
注意:在输入密码时控制台不会显示任何字符或提示,这是正常的!
输入完成后就会显示新创建的账户地址。
账户默认会保存在数据目录的 keystore 文件夹中(也就是 file_location/keystore 路径下),一个文件对应刚才创建的一个账户。
创建账户会生成一个 json 格式的文本文件,可以打开查看,里面存的是私钥经过密码加密后的信息。
余额查询
使用此命令可以查看指定账户在区块链上的余额。
> eth.getBalance("0x26eb81b7bb07a517b08833f16061cb62e061eb7d") #(1)根据账户地址查询
> eth.getBalance(eth.accounts[0]) #(2)根据账户列表索引值查询
>
> acc0=eth.accounts[0] #设置变量
"0x26eb81b7bb07a517b08833f16061cb62e061eb7d"
> eth.getBalance(acc0) #(3)根据设置的变量查询
可以套娃查询: eth.getBalance(eth.accounts[0])
查询结果的单位是 Wei
。Wei
是以太币的最小单位,1 个以太币 = 10 的 18 次方个 Wei
。
单位转换
以 Wei
为单位查到的结果是一个很大的数,有很多零,不太方便我们更直观的了解账户里到底有多少币,此时可以使用“单位转换”命令来方便我们理解。
将返回值换算成以太币:
web3.fromWei()
套娃换算成 ETH:web3.fromWei(eth.getBalance(eth.accounts[0]),'ether')
矿工相关指令
启动挖矿
miner.start(1)
其中 start
的参数表示挖矿使用的线程数。
第一次启动挖矿会先生成挖矿所需的 DAG
文件,这个过程有点慢,等进度达到 100% 后,就会开始挖矿,此时屏幕会被挖矿信息刷屏。
注意:有时候键入指令后并没有马上启动挖矿,需要耐心等一会儿。或者你也可以键入停止挖矿指令后再重新键入启动挖矿指令。
停止挖矿
miner.stop()
查看 Coinbase 账户
挖到一个区块会奖励 5 个以太币,挖矿所得的奖励会进入矿工的账户,这个账户就是 coinbase
。
默认情况下 coinbase
是本地账户中的第一个账户:
> eth.coinbase
"0xc232e2add308136571bb8f9197ba4ae4e5ba9836"
修改 Coinbase 账户
可以通过 miner.setEtherbase()
指令将其他账户设置成 coinbase
,使挖矿奖励进入其他账户
> miner.setEtherbase(eth.accounts[1])
true
> eth.coinbase
"0x814d39aa21f3eed069f2b21da7b5f857f7343afa"
查看当前区块总数
> eth.blockNumber
33
查看区块内容
通过区块号查看区块:
> eth.getBlock(33)
{
difficulty: 132928,
extraData: "0xd783010506846765746887676f312e372e33856c696e7578",
gasLimit: 3244382,
gasUsed: 21000,
hash: "0xf5d3da50065ce5793c9571a031ad6fe5f1af326a3c4fb7ce16458f4d909c1613",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0xc232e2add308136571bb8f9197ba4ae4e5ba9836",
mixHash: "0x09849dff7c8b8467812fa80d1fa2a27bc61f1cf16d5b2c05a6ce1b77ee18f3f1",
nonce: "0x5b3939449dbdbea0",
number: 33,
parentHash: "0xeca34637642f56f7cfe5b699031c7ddbc43aee00fb10c7f054e0a9719cf226da",
receiptsRoot: "0xd5f5b7ee944e57cbff496f7bdda7ceffd5eedffe6d5be5320008190502adc07a",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 649,
stateRoot: "0xc7add6b756980ab9e482766e455597ef1583e747ad62e2924a8e66c6f9170112",
timestamp: 1490183209,
totalDifficulty: 4358016,
transactions: ["0x0c59f431068937cbe9e230483bc79f59bd7146edc8ff5ec37fea6710adcab825"],
transactionsRoot: "0x7335a362b2c3925e7ba1b41bf7772aa9645a13d4f9c12edd5892b87887264232",
uncles: []
}
钱包账户导入
现在我们来介绍如何将刚刚创建在私有链里的账户导入到 MetaMask 里,这样我们就可以使用 MetaMask 来帮助我们更好的处理和进行后续的操作。
作者使用的是 Chrome 浏览器提供的 MetaMask 插件。
本地账户导入Metamask
- 打开 Metamask 的汇入账户页面
- 选择 JSON 格式档案,即 file_location/keystore 文件夹下的所需账户的密钥文件
- 输入本地账户的密码
- 等待汇入成功
Metamask账户导入本地区块链
- 打开 Metamask ,选择账户详情-汇出私钥
- 输入 Metamask 账户密码(用助记词导入账户后你创建的密码)
- 复制私钥,随便创建一个 txt 文件并将私钥复制进去
- 输入
geth account import 刚刚创建的 txt 文件路径
- 如果路径无误,会提示你输入密码,这个密码就是刚刚那个 Metamask 账户密码
- 输入
geth account list
获取刚刚生成的密钥文件位置 - 将密钥文件复制到 file_location/keystore 文件夹下即可汇入成功
- 启动本地区块链节点,输入
eth.accounts
查看是否新增账户成功
智能合约的编写与部署
编写合约
经过上述操作,我们基本搭建出了一个与 MetaMask 连接好了的私有链,接下来就到了如何创建并编写一个可以创建并交易 NFT 的智能合约啦!
不过编写智能合约的语言 Solidity 上手难度还是比 Javascript 大了不少,作者自己的水平也就是个复制粘贴搬运工的水准,更别提从头开始教你学会这么语言啦。
但是!聪明的作者想到另外的办法,他山之石可以攻玉,网络上这么多优秀的教程,相信同样聪明的你们可以自己找到其他办法学习这门语言。
这里作者提供几个学习 Solidity 很有用的网站:
第一当然是官网啦,任何教程都比不过官方解答!
第二个推荐的是一个叫cryptozombies的团队提供的教程,这个教程通过游戏闯关一般的模式超详细的、从零开始手把手的教你如何完成一个属于自己的“僵尸 NFT ”智能合约。
此教程深度解析了每一步智能合约的书写与其背后的意义,从如何让智能合约输出“hello world”到如何使用web3.js调用智能合约生成专属于你的“僵尸 NFT ”,每一步都很清晰明了,强烈推荐大家参考!!!
美中不足的是这个教程是基于0.4.19
版本的 Solidity 语言,里面有部分语法已经不再使用了,如果你需要使用高版本的 Solidity 语言进行智能合约的开发,那么这个教程对你的帮助可能没有那么大。
第三个推荐的是由WTF学院提供的 Solidity 入门教学。他们的教学内容也十分详细,由浅入深的讲解了 Solidity 语言,每一小节也都有一个对应的小测试来帮助你掌握与巩固知识,是很不错的教程!不过学起来没有第二个教程那么有趣。
部署合约
那么假设你已经获得了一个拥有基本功能的合约,接下来就是如何使用将此合约部署到我们的私有链上。
使用 Remix 可以帮助我们完成对智能合约的编译与部署。这里我们使用网页版的 Remix 。
- 将你的智能合约导入到 Remix 里,在左侧侧边栏里选中第三个图标(也就是 SOLIDITY COMPILER 选项)
- 点击蓝色的 compiler <你的.sol文件名> ,将 .sol 文件编译成 ABI 。
- 点击左侧侧边栏里第四个图标(也就是 DEPLOY & RUN TRANSACTIONS )
- ENVIRONMENT 选项选择第一个选项:Injected Provider - MetaMask 。此时会弹出 MetaMask 窗口让你选择账户,这里我们选择刚刚导入到 MetaMask 里的本地账户。
- ACCOUNT 选项会自动选择为上一小步选中的账户,注意此账户里需要有币才能完成接下来的操作
- GAS LIMIT 和 VALUE 可以都不改,默认值就行。
- CONTRACT 选项选择第二步编译出来的 .sol 文件
- 点击 Deploy 按钮确认部署
- 第三步完成后 MetaMask 会弹出一个窗口,表示要发起一笔交易,from 方是我们的本地账户,to 方是即将被部署上去的智能合约的地址,点击确认。
- 启动私有链节点,输入指令开启挖矿,交易被确认后才可停止挖矿。
完成上述步骤后,你的智能合约就已经成功的被部署到私有链上啦!Great Job!
注意:第二步编译出来的 ABI 文件和部署完成后智能合约的地址需要保存下来,下面我们还需要用到它们。
Web3.js 调用合约函数
下面我们就来看如何使用已部署到区块链里的智能合约里的方法。
这里作者提供两种方法:
- 使用 Remix 部署完合约后,DEPLOY & RUN TRANSACTIONS 里最下面的 Deployed Contracts 会出现我们刚刚部署好的合约,可以在这里查看到合约的地址和合约里的方法,还可以进行方法的调用。
- 使用 Web3.js 调用智能合约方法
接下来详细介绍一下如何使用 Web3.js 来调用智能合约里的方法。
以在 Vue3 框架里使用 Web3.js 为例。
前期准备
Step 1 :npm下载Web3.js 依赖
npm install web3
Step 2 :准备ABI文件和合约地址
在 src 目录下创建:
————src
|
|--contract.json // 这个 json 里存刚刚编译出来的 ABI
|
|--address.js // 这个 js 里存智能合约的地址
使用
如果想要使用 Web3.js 和 MetaMask 进行联动操作智能合约,还需要进行一些操作。
下面的 js 代码文件提供了一些函数,包括初始化web3.js和调用函数的两种方法,希望给你提供一些最基本的参考思路。它可以直接搬入到你的 utils 目录下,方便之后在 .vue 里使用。
调用智能合约函数有两种方法:
- 一种是
call()
方法,此方法用于view
和pure
类型的这种不需要花费 gas 的操作 - 另一种是
send()
方法,此方法用于所有需要花费 gas 的操作
需要注意的是,
send()
方法必须传如调用者的账户地址,例如:contract.methods.你的智能合约方法名(...参数).send({from: "0xavyk..."})
import Web3 from "web3"
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
} else {
// set the provider you want from Web3.providers
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
let contract=null;
/**
* 初始化
* @param {JSON} contractABI 智能合约ABI
* @param {string} contractAddr 智能合约地址
* @param {function} callback 回调函数
*/
function Init(contractABI,contractAddr,callback){
//判断用户是否安装MetaMask钱包插件
if (typeof window.ethereum === "undefined") {
//没安装MetaMask钱包进行弹框提示
alert("Looks like you need a Dapp browser to get started.");
alert("Consider installing MetaMask!");
} else {
//如果用户安装了MetaMask,你可以要求他们授权应用登录并获取其账号
ethereum
.request({ method: "eth_requestAccounts" })
.catch(function (reason) {
//如果用户拒绝了登录请求
if (reason === "User rejected provider access") {
// 用户拒绝登录后执行语句;
} else {
// 本不该执行到这里,但是真到这里了,说明发生了意外
alert("There was an issue signing you in.");
}
}).then(function (accounts) {
// Creates a new contract instance with all its methods and events defined in its json interface object.
contract = new web3.eth.Contract(contractABI,contractAddr);
//这里返回用户钱包地址
callback(accounts[0]);
});
}
}
/**
* 你的智能合约函数
* @param {*} 参数1
* @param {*} 参数2
* @param {*} 账户地址
* @param {function} 回调函数
*/
function createNFT(参数1,参数2,账户地址,回调函数) {
contract.methods
// createNFT需要改为你自己智能合约内想调用的方法名(所需的参数)
.createNFT(参数1,参数2)
.send({from:账户地址})
.on('transactionHash', (hash) => {
console.log('Transaction hash:', hash); // 获取交易单号
})
.on('receipt', (receipt) => {
console.log('Transaction receipt:', receipt); // 获取交易收据
})
.then((res) => {
console.log('创建nft成功', res)
callback(res);
})
.catch((err) => {
alert('创建nft失败,稍后再试:', err)
});
}
/**
* 你的智能合约函数
* @author [MosterSeven](https://github.com/MosterSeven)
* @param {integer} index 下标
* @param {function} callback 回调函数
*/
function getNFTinfo(index,callback) {
contract.methods
// nfts需要改为你自己智能合约内想调用的方法名(所需的参数)
.nfts(index)
.call()
.then((res) => {
console.log('获取nftsInfo', res)
callback(res);
})
.catch((err) => {
alert('获取nftsInfo失败,稍后再试:', err)
});
}
//导出相应的方法
export default {
Init,
createNFT,
getNFTinfo
}
在具体位置的使用
// 导入上面那段js
import weB3 from "@/utils/web3.js";
// 导入智能合约ABI和地址
import { NFTownership_address } from "@/abi/address.js";
import NFTownership_abi from "@/abi/NFTownership.json";
// 这里需要注意的是,如果用的是 vue 需要在 onMounted 生命周期里调用!
weB3.Init(NFTownership_abi, NFTownership_address, (addr) => {
console.log(addr, "weB3 NFTownership_abi");
// 使用你的合约方法
weB3.createNFT(...,()=>{
...
})
});
结语
到这里为止,你已经基本完成了全部内容!希望本文可以对你搭建有所帮助。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~