Published on

初识 Web3

Authors
  • avatar
    Name
    MissTree
    Twitter

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-721ERC-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);
  })
});

keystore

solidity|Remix

soliditysolidity示例(科学上网) 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可以是任何类型
// 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 作为其主要的去中心化预言机网络(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/