- Published on
初识 Web3
- Authors

- Name
- MissTree
Web3
NFT(Non-fungible token)
NFT是一种非同质化代币,每个NFT都是独一无二的,具有独特的属性和所有权。NFT可以代表任何数字资产,如艺术品、音乐、游戏道具等。NFT的不可替代性使得它们在交易和收藏中具有很高的价值。
- 土地NFT:未来想象类
- 虚拟资产NFT:游戏类
- 数字艺术品NFT:艺术类(表情版权)
- 门票NFT:活动类
- 版权NFT:知识产权类
- 交易NFT:金融类
ERC-721 和 ERC-1155
ERC-721 和 ERC-1155 是以太坊上两种不同的非同质化代币(NFT)标准。它们之间的主要区别在于它们如何处理通证的数量和所有权。
| 特性 | ERC-721 | ERC-1155 |
|---|---|---|
| 同质化 | 非同质化 | 同质化&非同质化 |
| 批次处理 | 每次处理一个通证 | 每次处理多个通证 |
区块链(Blockchain)
特征:
- 去中心化:对等网络每个节点即使服务器又是客户端
- 共识机制:PoW 和 PoS
- PoW:工作量证明机制,比特币机制,比较消耗资源,但是比较有效
- PoS:权益证明机制
- POA:权威证明机制
- POC:容量证明机制
- CPOC:有条件的容量证明机制
- 不可篡改:修改数据会影响整个链的其他数据
应用场景:
- 零售:供应链管理、防伪溯源
- 众筹:资金筹集、股权分配(资金使用)
- 物联网:设备管理、数据共享
- 数字货币:比特币、以太坊等
- 供应链管理:追踪商品从生产到消费的全过程
- 医疗健康:记录患者的医疗记录和健康数据(医院数据不互通)
- 版权保护:记录数字资产的所有权和交易记录
- 金融科技:去中心化交易所、借贷平台等
- 保险:记录保险理赔和赔付情况
- 能源:记录能源生产和消费的记录
- 媒体:记录版权和交易记录
类型:
- 公有链:任何人都可以参与和访问,如比特币、以太坊
- 私有链:只有授权用户可以参与和访问,如企业内部应用
- 联盟链:多个组织共同维护和访问,如金融行业联盟
- 混合链:结合公有链和私有链的特点,如混合型交易所
类型对比
| 类型 | 公有链 | 私有链 | 联盟链 | 混合链 |
|---|---|---|---|---|
| 访问权限 | 公开 | 私有 | 联盟 | 混合 |
| 写入制 | 公开 | 获批参与者 | 获批参与者 | 混合 |
| 所属者 | 无 | 单一实体 | 多方实体 | 混合 |
| 匿名性 | 是 | 否 | 否 | 否 |
| 共识机制 | PoW、PoS | 权限控制 | 共识算法 | 混合 |
| 交易速度 | 慢 | 快 | 快 | 混合 |
hash算法
- 从hash值不可以反向推导出原始的数据
- 输入数据的微小变化会得到完全不同的hash值
- 相同的数据会得到相同的值
- 执行效率要高效,长的文本也能快速地计算出哈希值
- hash算法的冲突概率要小
DApp(去中心化应用)
智能合约
web3.eth:用于与以太坊区块链和智能合约之间的交互 web3.utils:包含一些辅助方法 web3.shh:用于协议进行通信的P2P和广播。 web3.bzz:用于与群网络交互的Bzz模块。 github地址:https://github.com/web3/web3.js/tree/v1.0.0-beta.34 web3.js开发文档:https://web3js.readthedocs.io/en/v1.8.1/ web3.js 中文文档: https://learnblockchain.cn/docs/web3.js/
web端技术开发
账户创建
import Web3 from 'web3'
// 创建账户
const account = Web3.eth.accounts.create();
// create() 参数它是一个可选项,是一个随机字符串,将作为解锁账号的密码。如果没有传递字符串,则使
// 用random生成随机字符串。
console.log(account);

获取账户余额
// 获取账户余额
const balance = await Web3.eth.getBalance(account.address);
console.log(Web3.utils.fromWei(balance, 'ether'));

发送交易
npm install ethereumjs-tx@1.3.7
import Web3 from 'web3'
import Tx from 'ethereumjs-tx'
// import { ethers } from 'ethers'
// import { keccak256 } from 'ethers/lib/utils'
// import { Buffer } from 'buffer'
// import { keccak256 as keccak256_ } from 'keccak256'
// import { Wallet } from 'ethereumjs-wallet'
// 获取 gasprice
const gasPrice = await Web3.eth.getGasPrice();
// 发送交易
const tx = {
from: account.address, // 转账账户
to: '0x1234567890abcdef1234567890abcdef12345678', // 接收账户
value: Web3.utils.toWei('1', 'ether'), // 转账金额 需要单位转化
// gas: 21000, // 转账需要消耗的gas web3.utils.toWei('1', 'gwei'),
gasPrice: Web3.utils.toWei('10', 'gwei'), // gas价格
};
// 方式一:
// 使用私钥签名交易: privateKey
const signedTx = await Web3.eth.accounts.signTransaction(tx, privateKey);
const receipt = await Web3.eth.sendSignedTransaction(signedTx.rawTransaction);
console.log(receipt);
// 方式二:
// 算出 gas
tx.gas = await Web3.eth.estimateGas(tx);
let t = new Tx(tx);
t.sign(privateKey)
const signedTx = '0x' + t.serialize().toString('hex');
const trans = Web3.eth.sendSignedTransaction(signedTx);
trans.on('transactionHash', function(txid){
console.log("交易id",txid);
console.log("https://goerli.etherscan.io/tx/${txid}");
});
trans.on('receipt', function(receipt){
console.log("交易节点",receipt);
});
trans.on('confirmation', function(confirmationNumber, receipt){
console.log("交易确认",confirmationNumber ,receipt);
});
trans.on('error', function(error){
console.log("交易失败",error);
});
助记词创建账户
需要使用 bip39 协议将助记词转换成种子,再通过 ethereumjs-wallet 库生成hd钱包,根据路径的不同从hd钱包中获取不同的keypair,keypair中就包含有公钥、私钥,再通过ethereumjs-util 库将公钥生成地址,从而根据助记词获取所有关联的账号,能获取到公钥、私钥、地址等数据信息。
npm install bip39 ethereumjs-util ethereumjs-wallet
import Web3 from 'web3'
import {
generateMnemonic,
mnemonicToSeed,
validateMnemonic,
fromEntropy,
} from 'bip39'
import {hdkey} from 'ethereumjs-wallet'
// 根据不同情况安装, vite 就安装vite-plugin-node-polyfills插件,然后在vite.config.js中配置
// 注意:vite-plugin-node-polyfills 0.9.0之后的版本有不兼容问题
import { Buffer } from 'buffer'
// 生成助记词 floor upon canvas income hurt abuse gather bean august hold coffee heavy
const mnemonic = generateMnemonic();
const seed = mnemonicToSeedSync(mnemonic);
// 生成种子
const hdwallet = hdkey.fromMasterSeed(seed);
// 生成密钥对
let keypair = hdwallet.derivePath(`m/44'/60'/0'/0/0`);
// 获取钱包对象
let wallet = keypair.getWallet();
// 获取钱包地址
const address = wallet.getAddressString();
// 获取钱包校验地址 全部转小写后与钱包地址相同
const localAddress = wallet.getChecksumAddressString();
//获取私钥
const privateKey = wallet.getPrivateKeyString();
console.log(address, privateKey);
derivePath相关
// m/44'/60'/0'/0/0
// m: 表示主私钥
// 44': 表示BIP44协议
// 60': 表示以太坊
// 0': 表示第一个账户
// 0: 表示第一个子账户
// 0: 表示第一个密钥对
BIP44 是在 BIP32 和 BIP43 的基础上增加多币种,提出的层次结构非常全面,"它允许处理多个币种,多个帐户,每个帐户有数百万个地址。 在BIP32路径中定义以下5个级别:
m/purpse'/coin_type'/account'/change/address_index
- purpse:在BIP43之后建议将常数设置为44'。表示根据BIP44规范使用该节点的子树。
- Coin_type:币种,表一个主节点(种子)可用于无限数量的独立加密币,如比特币,Litecoin或Namecoin。此级别为每个加密币创建一个单独的子树,避免重用已经在其它链上存在的地址。开发人员可以为他们的项目注册未使用的号码。
- Account:账户,此级别为了设置独立的用户身份可以将所有币种放在一个的帐户中,从0开始按顺序递增
- Change:常量0用于外部链,常量1用于内部链,外部链用于钱包在外部用于接收和付款。内部链用于在钱包外部不可见的地址,如返回交易变更。
- Address_index:地址索引,按顺序递增的方式从索引0开始编号 BIP44的规则使得 HD钱包非常强大,用户只需要保存一个种子,就能控制所有币种,所有账户的钱包,因此由BIP39 生成的助记词非常重要,所以一定安全妥善保管,那么会不会被破解呢?如果一个 HD 钱包助记词是 12 个单词,一共有 2048 个单词可能性,那么随机的生成的助记词所有可能性大概是 5e+39,因此几乎不可能被破解。
导出账户(keystore)
import Web3 from 'web3'
import ethwallet, { Wallet } from 'ethereumjs-wallet'
// 导出账户
// web3方式导出 必须输入正确的密码
web3.eth.accounts.encrypt(privateKey, "123456").then((result) => {
console.log("🚀 ~ result:", result);
// 反向获取私钥 必须输入正确的密码
web3.eth.accounts.decrypt(result, "123456").then((decrypted) => {
console.log("🚀 ~ decrypted:", decrypted.privateKey);
})
});
// wallet 钱包方式导出 必须输入正确的密码
wallet.toV3("123456").then((keystore) => {
console.log("🚀 ~ keystore:", keystore);
// 反向获取私钥 必须输入正确的密码
ethwallet.fromV3(keystore, "123456").then((wallet) => {
let a = decrypted.getPrivateKeyString()
console.log("🚀 ~ privateKey:", a);
})
});

solidity|Remix
solidity、solidity示例(科学上网) Remix官网
solidity数据类型
- 值类型
- bool:布尔类型,true或false
- int/unit:整形 unit8、unit16、unit32、unit64、unit128、unit256、int8、int16、int32、int64、int128、int256
- 默认为uint256
- int:有符号整形
- int8:有符号整形,占8位,范围-128~127
- int16:有符号整形,占16位,范围-32768~32767
- int32:有符号整形,占32位,范围-2147483648~2147483647
- int64:有符号整形,占64位,范围-9223372036854775808~9223372036854775807
- int128:有符号整形,占128位,范围-170141183460469231731687303715884105728~170141183460469231731687303715884105727
- int256:有符号整形,占256位,范围-57896044618658097711785492504343953926634992332820282019728792003956564819968~57896044618658097711785492504343953926634992332820282019728792003956564819967
- uint:无符号整形
- uint8:无符号整形,占8位,范围0~255
- uint16:无符号整形,占16位,范围0~65535
- uint32:无符号整形,占32位,范围0~4294967295
- uint64:无符号整形,占64位,范围0~18446744073709551615
- address:地址类型
- 定长字节数据
- fixed/ufixed:定长浮点
- enum:枚举类型
- function:函数类型
- 引用类型
- array:数组类型,
- 不定长字符串数据 bytes
- 固定长度字符串数据 bytes1~bytes32 因为是固定长度,所以比bytes更节省gas
- struct:结构体类型
- mapping:映射类型,与数组个结构体一样,映射也是引用类型,但是映射是一对一的键值对关系
- mapping(keyType => valueType): keyType可以是任何内置类型,不允许使用引用类型和复杂结构, valueType可以是任何类型
- array:数组类型,
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract DataTypeDemo {
unit public x = 1;
unit8 public z = 1;
address public addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
address public add = msg.sender;
struct Person {
string name;
unit age;
}
Person person;
// 定长数组 不可以直接修改数组长度 fixedArray.length = 4;
unit[3] public fixedArray = [1, 2, 3];
function sumArray() public view returns (unit) {
unit sum = 0;
for (unit i = 0; i < fixedArray.length; i++) {
sum += fixedArray[i];
}
return sum;
}
}
string 类型是用于存储 UTF-8 编码文本的动态长度数据类型
- 动态大小:长度不固定,可以在运行时改变
- UTF-8 编码:支持多字节字符(如中文、表情符号等)
- 引用类型:存储在 storage 中时是引用类型
- 存储方式:类似于 bytes 类型,但有专门的字符串处理功能
string memory myString = "Hello, 世界!"; // 直接 myString.length 语法在 Solidity 中不合法 uint length = bytes(myString).length; // 转换为 bytes 后获取长度
solidity修饰符
- view:只读方法,不会修改状态变量,不会消耗gas
- pure:纯方法,不会修改状态变量,不会消耗gas,不会访问状态变量
- payable:允许接收以太币
- public:可以被合约内部和外部访问
- external:只能被合约外部访问,只有其他合约或者账户可以调用,定义该函数的合约不能调用除非使用
this关键字 - internal:只能被合约内部访问,类似Java的protected
pragma solidity ^0.8.7;
contract HelloWorld {
// 定义一个状态变量
string public name = "Hello, World!";
// 构造函数
constructor() {}
// 不修改变量
function sayName() public view returns (string memory) {
return name;
}
function setName(string calldata _name) public {
name = _name;
}
}
变量
分为局部变量、状态变量和全局变量,局部变量在函数内部定义,状态变量是合约内部定义的变量,可以存储在区块链上,而局部变量是函数内部定义的变量,无法存储在区块链上。全局变量在函数外部定义,全局变量在函数内部也可以使用。
| 全称 | 返回 |
|---|---|
| blockhash(uint blockNumber) returns (bytes32) | 给定区块的哈希值-只适用于256最近区块,不包含当前区块。 |
| block.coinbase (address payable) | 当前区块矿工的地址 |
| block.difficulty (uint) | 当前区块的难度 |
| block.gaslimit (uint) | 当前区块的gaslimit |
| block.number (uint) | 当前区块的number |
| block.timestamp (uint) | 当前区块的时间戳,为unix纪元以来的秒 |
| gasleft() returns (uint256) | 剩余 gas |
| msg.data (bytes calldata) | 完成 calldata |
| msg.sender (address payable) | 消息发送者(当前 caller) |
| msg.sig (bytes4) | kalldata的前四个字节(function identifier) |
| msg.value (uint) | 当前消息的wei值 |
| now (uint) | 当前块的时间戳 |
| tx.gasprice (uint) | 交易的gas价格 |
| tx.origin (address payable) | 交易的发送方 |
变量的存储位置
- storage:存储在区块链上的变量,会永久保存,占用合约的存储空间,消耗gas
- 存储中的数据是永久保存,存储的是KeyValue库
- 存储中的数据写入区块链,因此会修改状态,造成存储使用成本高的原因
- 占用一个256位的槽需要消耗20,000 gas,修改一个256位的槽需要消耗5,000 gas
- 当清零一个槽时,会返回一定数量的gas
- memory:存储在内存中的变量,不会永久保存,不会占用合约的存储空间,不会消耗gas
- 数据仅在函数执行期间存在,执行完毕后被销毁
- 读写一个内存槽会消耗 3gas
- 为避免矿工工作量大,22个操作后的单操作成本会上涨
- calldata:存储在内存中的变量,不会永久保存,不会占用合约的存储空间,不会消耗gas,用于函数参数
- 只能用于函数声明的参数,是不可变的
- 是临时的,函数执行后销毁,是最便宜的存储位置
- 不能作为返回值,不能用于状态变量
- stack:存储在栈中的变量,智能合约免费提供,不会永久保存,不会占用合约的存储空间,不会消耗gas但是数量有限。
pragma solidity ^0.8.7;
contract Demo {
// 定义一个storage变量
unit x = 1;
// 错误定义storage变量: unit storage x = 1;
// 断言不成立,不修改变量
function doAssert() public returns (unit) {
assert(x > 1);
x = 2;
return x;
}
// require不成立,不修改变量
function doRequire() public returns (unit) {
require(x > 1, "x is not equal to 1");
x = 2;
return x;
}
}
变量引入与继承
// 1.直接引入项目文件下的合约
// 2.引入GitHub上的合约
// 3.通过包引入
import "./Factor.sol";
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
import { helloWorld } from "./HelloWorld.sol";
// 继承合约
contract HelloWorldChild is Factor {
// 定义变量
helloWorld[] public helloWorlds;
// 构造函数 调用合约的方法和输入简称
constructor() Factor("functionDo","ft") {}
}
错误处理
- require:用于检查条件,如果条件不满足,则抛出异常,并回滚所有状态更改。
- revert:用于抛出异常,并回滚所有状态更改。
- assert:用于检查条件,如果条件不满足,则抛出异常,并回滚所有状态更改。
pragma solidity ^0.8.7;
contract ErrorDemo {
// 定义一个状态变量
unit public x = 1;
// 断言不成立,不修改变量
function doAssert() public returns (unit) {
assert(x > 1);
x = 2;
return x;
}
// require不成立,不修改变量
function doRequire() public returns (unit) {
require(x > 1, "x is not equal to 1");
x = 2;
return x;
}
}
事件
事件是合约和用户之间的通信方式,用户可以通过事件来获取合约的状态变化,事件在合约中定义,用户可以通过web3.js来监听事件。
pragma solidity ^0.8.7;
contract EventDemo {
event Log(unit);
event Log(string);
event Log(address);
event Log(unit, string, address);
function doEvent() public {
// 打印数字
emit Log(1);
// 打印字符串
emit Log("hello");
// sender就是address
emit Log(msg.sender);
}
}
modifier
修饰器是合约中的一种特殊函数,用于修改函数的行为,修饰器可以用于函数的执行前、执行后、执行中、执行失败等场景。
pragma solidity ^0.8.7;
contract ModifierDemo {
// 定义一个状态变量
unit public x = 1;
// 定义一个修饰器
modifier checkX() {
require(x > 1, "x is not equal to 1");
_;
}
// 使用修饰器
function doCheckX() public checkX {
x = 2;
}
}
虚函数
虚函数是合约中的一种特殊函数,用于定义合约的接口,子合约可以重写虚函数,实现自己的逻辑。
pragma solidity ^0.8.7;
contract Factor {
// 定义一个虚函数
function factor(unit n) public virtual returns (unit) {
return n;
}
}
contract HelloWorld is Factor {
// 重写虚函数 上面的合约已经默认方法了,可以不需要重写
function factor(unit n) public override returns (unit) {
return n * 2;
}
}
合约创建
// 智能合约的许可协议
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract HelloWorld {
// 定义一个状态变量
string public name = "Hello, World!";
// 添加 payable 构造函数
constructor() payable {}
function sayName() public view returns (string memory) {
return name;
}
function setName(string calldata _name) public {
name = _name;
}
}
创建合约后选择编译的版本,尽量不要选当前最新的,因为低版本可以被高版本编译,但是高版本不兼容低版本编译,所以选择一个比较稳定的版本,然后点击创建合约,会自动生成合约地址。 
部署合约
选择合适的环境和选择一个账户,然后根据编译的合约后点击deploy,就可以看到合约定义的变量和方法了 
若是要部署到以太坊线上,要在Remix的deployed选择一个线上方式,在点击deploy,然后在deployed contract中就可以看到合约地址了
部署到线上
若是没有引用文件则直接复制合约地址,然后打开https://sepolia.etherscan.io/,在search中输入合约地址,然后点击search,就可以看到合约的详细信息了
若是引入了文件,在文件目录中右键文件,选择Flatten将文件平铺,然后选择新的合约文件,将新文件内容的合约软件协议删除多余的,然后执行下面的操作

Chainlink
Chainlink 作为其主要的去中心化预言机网络(Decentralized Oracle Network, DON),为智能合约提供安全、可靠的链下数据接入。 主要解决了智能合约与外部数据源之间的信任问题,使得智能合约能够访问到真实世界的数据,从而实现更加智能化的应用。例如,智能合约可以基于天气数据、股票价格、加密货币价格等实时数据来执行交易、调整策略等操作。
ERC
ERC-20: 标准代币协议 ERC-721: 非同质化代币协议 ERC-1155: 多代币协议
ERC-20官网 设置自己的代币。

将自己的代币代码放到Remix编译执行,然后点击deploy,就可以看到合约地址了
调用合约方法
// 判断是否连接了"MetaMask"插件
if (typeof window.ethereum !== 'undefined') {
// 连接到以太坊网络
const web3 = new Web3(window.ethereum);
// 请求用户授权连接
await window.ethereum.enable();
// 获取账户
const accounts = await web3.eth.getAccounts();
// 调用合约方法 contractAddress 为之前Remix线上部署的地址
const contractInstance = new web3.eth.Contract(abi, contractAddress);
// 调用合约方法
const result = await contractInstance.methods.myMethod().call();
console.log(result);
} else {
console.error('MetaMask is not installed');
}
// 连接到以太坊网络
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID');
// 合约ABI
const abi = [
// 合约方法定义
// ...
];
// 合约地址
const contractAddress = '0x1234567890abcdef1234567890abcdef12345678';
// 获取账户
// 调用合约方法
const contractInstance = new web3.eth.Contract(abi, contractAddress);
const result = await contractInstance.methods.myMethod().call();
console.log(result);
web3相关网站
挖矿地址
https://www.tokenpocket.pro/ https://goerli-faucet.pk910.de/