走进web3的世界从Ethers开始(四)

最近在学习web3所以根据自己的学习内容每天写一篇笔记出来,算是联系也算是巩固! 希望自己早日可以找到web3的工作。

在我看来ethers就是传统开发中的axios,他使得我们可以非常快速简单的访问区块链上的信息。那么ethers究竟为我们提供了哪些功能呢?让我们来一起学习一下吧!

今天我们来干一件让人激动的事情,来部署一个ERC20代币合约,其中合约部分需要用solidity来实现,如果你对我们如何实现ERC20代币合约感兴趣的话这可能需要你有一定的solidity基础,不过没有也没关系,你也可以把关注点放在使用ethers来如何部署合约。

我们使用ethers.js中的合约工厂ContractFactory类型,利用它来部署合约。

部署智能合约

在以太坊上,智能合约的部署是一种特殊的交易:将编译智能合约得到的字节码发送到0地址。如果这个合约的构造函数有参数的话,需要利用abi.encode将参数编码为字节码,然后附在在合约字节码的尾部一起发送。

合约工厂

ethers.js创造了合约工厂ContractFactory类型,方便开发者部署合约。你可以利用合约abi,编译得到的字节码bytecode和签名者变量signer来创建合约工厂实例,为部署合约做准备。

ini 复制代码
const contractFactory = new ethers.ContractFactory(abi, bytecode, signer);

注意 :如果合约的构造函数有参数,那么在abi中必须包含构造函数。

在创建好合约工厂实例之后,可以调用它的deploy函数并传入合约构造函数的参数args来部署并获得合约实例:

csharp 复制代码
const contract = await contractFactory.deploy(args)

你可以利用下面两种命令,等待合约部署在链上确认,然后再进行交互。

csharp 复制代码
await contractERC20.waitForDeployment();

接下来我们来用具体的代码去实现一个例子吧。

例子:部署ERC20代币合约

在Remix上创建两个文件分别为ERC20.solIERC20.sol,如果你有学过solidity的ERC20的课程的话你一定能明白我在说什么。如果你没有学过的话也没关系,我们只需要拿到我们创建的合约的Bytecode即可,不过多的讨论合约的实现原理和逻辑。

ERC20.sol文件内容

ini 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./IERC20.sol";

contract ERC20 is IERC20 {

    mapping(address => uint256) public override balanceOf;

    mapping(address => mapping(address => uint256)) public override allowance;

    uint256 public override totalSupply;   // 代币总供给

    string public name;   // 名称
    string public symbol;  // 符号
    
    uint8 public decimals = 18; // 小数位数

    // @dev 在合约部署的时候实现合约名称和符号
    constructor(string memory name_, string memory symbol_){
        name = name_;
        symbol = symbol_;
    }

    // @dev 实现`transfer`函数,代币转账逻辑
    function transfer(address recipient, uint amount) external override returns (bool) {
        balanceOf[msg.sender] -= amount;
        balanceOf[recipient] += amount;
        emit Transfer(msg.sender, recipient, amount);
        return true;
    }

    // @dev 实现 `approve` 函数, 代币授权逻辑
    function approve(address spender, uint amount) external override returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    // @dev 实现`transferFrom`函数,代币授权转账逻辑
    function transferFrom(
        address sender,
        address recipient,
        uint amount
    ) external override returns (bool) {
        allowance[sender][msg.sender] -= amount;
        balanceOf[sender] -= amount;
        balanceOf[recipient] += amount;
        emit Transfer(sender, recipient, amount);
        return true;
    }

    // @dev 铸造代币,从 `0` 地址转账给 调用者地址
    function mint(uint amount) external {
        balanceOf[msg.sender] += amount;
        totalSupply += amount;
        emit Transfer(address(0), msg.sender, amount);
    }

    // @dev 销毁代币,从 调用者地址 转账给  `0` 地址
    function burn(uint amount) external {
        balanceOf[msg.sender] -= amount;
        totalSupply -= amount;
        emit Transfer(msg.sender, address(0), amount);
    }

}

IERC20.sol文件内容

php 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @dev ERC20 接口合约.
 */
interface IERC20 {
    /**
     * @dev 释放条件:当 `value` 单位的货币从账户 (`from`) 转账到另一账户 (`to`)时.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev 释放条件:当 `value` 单位的货币从账户 (`owner`) 授权给另一账户 (`spender`)时.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev 返回代币总供给.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev 返回账户`account`所持有的代币数.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev 转账 `amount` 单位代币,从调用者账户到另一账户 `to`.
     *
     * 如果成功,返回 `true`.
     *
     * 释放 {Transfer} 事件.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev 返回`owner`账户授权给`spender`账户的额度,默认为0。
     *
     * 当{approve} 或 {transferFrom} 被调用时,`allowance`会改变.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev 调用者账户给`spender`账户授权 `amount`数量代币。
     *
     * 如果成功,返回 `true`.
     *
     * 释放 {Approval} 事件.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev 通过授权机制,从`from`账户向`to`账户转账`amount`数量代币。转账的部分会从调用者的`allowance`中扣除。
     *
     * 如果成功,返回 `true`.
     *
     * 释放 {Transfer} 事件.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

然后我们在Remix中编译一下我们的合约,如图

然后点击Bytecode就可以拷贝出来我们部署合约所需要的Bytecode了。

下面我们继续回到我们的My Web3文件夹中,在My Web3文件夹中创建06_DeployContract文件夹,并在其中创建文件DeployContract.js

文件内容为:

typescript 复制代码
// 创建合约工厂实例的规则:
// const contractFactory = new ethers.ContractFactory(abi, bytecode, signer);
// 参数分别为合约ABI`abi`,合约字节码`bytecode`,Signer变量`signer`

// 利用合约工厂部署合约:
// contractFactory.deploy(args)
// 其中args为合约构造函数的参数

import { ethers } from "ethers";

// 利用Alchemy的rpc节点连接以太坊测试网络
const ALCHEMY_SEPOLIA_URL = '你申请的Connect to Alchemy URL';
const provider = new ethers.JsonRpcProvider(ALCHEMY_SEPOLIA_URL);

// 利用私钥和provider创建wallet对象
const privateKey = '你的privateKey'
const wallet = new ethers.Wallet(privateKey, provider)

// ERC20的人类可读abi
const abiERC20 = [
    "constructor(string memory name_, string memory symbol_)",
    "function name() view returns (string)",
    "function symbol() view returns (string)",
    "function totalSupply() view returns (uint256)",
    "function balanceOf(address) view returns (uint)",
    "function transfer(address to, uint256 amount) external returns (bool)",
    "function mint(uint amount) external",
];

// 填入合约字节码,在remix中,你可以在两个地方找到Bytecode
// 1. 编译面板的Bytecode按钮
// 2. 文件面板artifact文件夹下与合约同名的json文件中
// 里面"bytecode"属性下的"object"字段对应的数据就是Bytecode,挺长的,608060起始
// "object": "6080604052601260055f6101000a8...
const bytecodeERC20 = "6080604052601260055f6101000a81548160ff021916908360ff16021790555034801561002a575f80fd5b5060405161120e38038061120e833981810160405281019061004c91906101c0565b816003908161005b9190610443565b50806004908161006b9190610443565b505050610512565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6100d28261008c565b810181811067ffffffffffffffff821117156100f1576100f061009c565b5b80604052505050565b5f610103610073565b905061010f82826100c9565b919050565b5f67ffffffffffffffff82111561012e5761012d61009c565b5b6101378261008c565b9050602081019050919050565b8281835e5f83830152505050565b5f61016461015f84610114565b6100fa565b9050828152602081018484840111156101805761017f610088565b5b61018b848285610144565b509392505050565b5f82601f8301126101a7576101a6610084565b5b81516101b7848260208601610152565b91505092915050565b5f80604083850312156101d6576101d561007c565b5b5f83015167ffffffffffffffff8111156101f3576101f2610080565b5b6101ff85828601610193565b925050602083015167ffffffffffffffff8111156102205761021f610080565b5b61022c85828601610193565b9150509250929050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061028457607f821691505b60208210810361029757610296610240565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026102f97fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826102be565b61030386836102be565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f61034761034261033d8461031b565b610324565b61031b565b9050919050565b5f819050919050565b6103608361032d565b61037461036c8261034e565b8484546102ca565b825550505050565b5f90565b61038861037c565b610393818484610357565b505050565b5b818110156103b6576103ab5f82610380565b600181019050610399565b5050565b601f8211156103fb576103cc8161029d565b6103d5846102af565b810160208510156103e4578190505b6103f86103f0856102af565b830182610398565b50505b505050565b5f82821c905092915050565b5f61041b5f1984600802610400565b1980831691505092915050565b5f610433838361040c565b9150826002028217905092915050565b61044c82610236565b67ffffffffffffffff8111156104655761046461009c565b5b61046f825461026d565b61047a8282856103ba565b5f60209050601f8311600181146104ab575f8415610499578287015190505b6104a38582610428565b86555061050a565b601f1984166104b98661029d565b5f5b828110156104e0578489015182556001820191506020850194506020810190506104bb565b868310156104fd57848901516104f9601f89168261040c565b8355505b6001600288020188555050505b505050505050565b610cef8061051f5f395ff3fe608060405234801561000f575f80fd5b50600436106100a7575f3560e01c806342966c681161006f57806342966c681461016557806370a082311461018157806395d89b41146101b1578063a0712d68146101cf578063a9059cbb146101eb578063dd62ed3e1461021b576100a7565b806306fdde03146100ab578063095ea7b3146100c957806318160ddd146100f957806323b872dd14610117578063313ce56714610147575b5f80fd5b6100b361024b565b6040516100c09190610967565b60405180910390f35b6100e360048036038101906100de9190610a18565b6102d7565b6040516100f09190610a70565b60405180910390f35b6101016103c4565b60405161010e9190610a98565b60405180910390f35b610131600480360381019061012c9190610ab1565b6103ca565b60405161013e9190610a70565b60405180910390f35b61014f61056d565b60405161015c9190610b1c565b60405180910390f35b61017f600480360381019061017a9190610b35565b61057f565b005b61019b60048036038101906101969190610b60565b610651565b6040516101a89190610a98565b60405180910390f35b6101b9610665565b6040516101c69190610967565b60405180910390f35b6101e960048036038101906101e49190610b35565b6106f1565b005b61020560048036038101906102009190610a18565b6107c3565b6040516102129190610a70565b60405180910390f35b61023560048036038101906102309190610b8b565b6108d7565b6040516102429190610a98565b60405180910390f35b6003805461025890610bf6565b80601f016020809104026020016040519081016040528092919081815260200182805461028490610bf6565b80156102cf5780601f106102a6576101008083540402835291602001916102cf565b820191905f5260205f20905b8154815290600101906020018083116102b257829003601f168201915b505050505081565b5f8160015f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516103b29190610a98565b60405180910390a36001905092915050565b60025481565b5f8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546104529190610c53565b92505081905550815f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546104a49190610c53565b92505081905550815f808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546104f69190610c86565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161055a9190610a98565b60405180910390a3600190509392505050565b60055f9054906101000a900460ff1681565b805f803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105ca9190610c53565b925050819055508060025f8282546105e29190610c53565b925050819055505f73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516106469190610a98565b60405180910390a350565b5f602052805f5260405f205f915090505481565b6004805461067290610bf6565b80601f016020809104026020016040519081016040528092919081815260200182805461069e90610bf6565b80156106e95780601f106106c0576101008083540402835291602001916106e9565b820191905f5260205f20905b8154815290600101906020018083116106cc57829003601f168201915b505050505081565b805f803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461073c9190610c86565b925050819055508060025f8282546107549190610c86565b925050819055503373ffffffffffffffffffffffffffffffffffffffff165f73ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516107b89190610a98565b60405180910390a350565b5f815f803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461080f9190610c53565b92505081905550815f808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546108619190610c86565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516108c59190610a98565b60405180910390a36001905092915050565b6001602052815f5260405f20602052805f5260405f205f91509150505481565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610939826108f7565b6109438185610901565b9350610953818560208601610911565b61095c8161091f565b840191505092915050565b5f6020820190508181035f83015261097f818461092f565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6109b48261098b565b9050919050565b6109c4816109aa565b81146109ce575f80fd5b50565b5f813590506109df816109bb565b92915050565b5f819050919050565b6109f7816109e5565b8114610a01575f80fd5b50565b5f81359050610a12816109ee565b92915050565b5f8060408385031215610a2e57610a2d610987565b5b5f610a3b858286016109d1565b9250506020610a4c85828601610a04565b9150509250929050565b5f8115159050919050565b610a6a81610a56565b82525050565b5f602082019050610a835f830184610a61565b92915050565b610a92816109e5565b82525050565b5f602082019050610aab5f830184610a89565b92915050565b5f805f60608486031215610ac857610ac7610987565b5b5f610ad5868287016109d1565b9350506020610ae6868287016109d1565b9250506040610af786828701610a04565b9150509250925092565b5f60ff82169050919050565b610b1681610b01565b82525050565b5f602082019050610b2f5f830184610b0d565b92915050565b5f60208284031215610b4a57610b49610987565b5b5f610b5784828501610a04565b91505092915050565b5f60208284031215610b7557610b74610987565b5b5f610b82848285016109d1565b91505092915050565b5f8060408385031215610ba157610ba0610987565b5b5f610bae858286016109d1565b9250506020610bbf858286016109d1565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610c0d57607f821691505b602082108103610c2057610c1f610bc9565b5b50919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610c5d826109e5565b9150610c68836109e5565b9250828203905081811115610c8057610c7f610c26565b5b92915050565b5f610c90826109e5565b9150610c9b836109e5565b9250828201905080821115610cb357610cb2610c26565b5b9291505056fea264697066735822122042b0e38b6958b5ab8e21ba04082b9abc7a69326eb4d24fb496289da48290a35764736f6c63430008190033";

const factoryERC20 = new ethers.ContractFactory(abiERC20, bytecodeERC20, wallet);

const main = async () => {
    // 读取钱包内ETH余额
    const balanceETH = await provider.getBalance(wallet)

    // 如果钱包ETH足够
    if(ethers.formatEther(balanceETH) > 0.002){
        // 1. 利用contractFactory部署ERC20代币合约
        console.log("\n1. 利用contractFactory部署ERC20代币合约")
        // 部署合约,填入constructor的参数
        const contractERC20 = await factoryERC20.deploy("KTC Token", "KTC")
        console.log(`合约地址: ${contractERC20.target}`);
        console.log("部署合约的交易详情")
        console.log(contractERC20.deploymentTransaction())
        console.log("\n等待合约部署上链")
        await contractERC20.waitForDeployment()
        console.log("合约已上链")

        // 2. 打印合约的name()和symbol(),然后调用mint()函数,给自己地址mint 10,000代币
        console.log("\n2. 调用mint()函数,给自己地址mint 10,000代币")
        console.log(`合约名称: ${await contractERC20.name()}`)
        console.log(`合约代号: ${await contractERC20.symbol()}`)
        let tx = await contractERC20.mint("10000")
        console.log("等待交易上链")
        await tx.wait()
        console.log(`mint后地址中代币余额: ${await contractERC20.balanceOf(wallet)}`)
        console.log(`代币总供给: ${await contractERC20.totalSupply()}`)

        // 3. 调用transfer()函数,给自己的某一个账户转账1000代币
        console.log("\n3. 调用transfer()函数,给自己的某一个账户转账1000代币")
        tx = await contractERC20.transfer("0xBE395e1106747dc0FDdB03f9b50C5D09C042c03d", "1000")
        console.log("等待交易上链")
        await tx.wait()
        console.log(`查看自己账户钱包中的代币余额: ${await contractERC20.balanceOf("0xBE395e1106747dc0FDdB03f9b50C5D09C042c03d")}`)

    }else{
        // 如果ETH不足
        console.log("ETH不足,去水龙头领一些Sepolia ETH")
        console.log("https://www.sepoliafaucet.io/")
    }
}

main()

然后我们执行一下这个函数来看看执行结果吧:

可以看到我们完美的部署了一个符合ERC20规范的代币合约,同时也调用了合约的铸币转账等功能,我们在控制台可以看到我们所部署的合约的地址,那么我们再写一个函数来复习一下我们上次所学的与合约交互的操作吧!

My Web3文件夹中创建06_DeployContract文件夹,并在其中创建文件MintKTP.js

javascript 复制代码
// 声明只可写合约的规则:
// const contract = new ethers.Contract(address, abi, signer);
// 参数分别为合约地址`address`,合约ABI `abi`,Signer变量`signer`
import { ethers } from "ethers";
// 利用Alchemy的rpc节点连接以太坊测试网络
const ALCHEMY_SEPOLIA_URL = '你申请的Connect to Alchemy URL';
const provider = new ethers.JsonRpcProvider(ALCHEMY_SEPOLIA_URL);

// 利用私钥和provider创建wallet对象
const privateKey = '你的privateKey'
const wallet = new ethers.Wallet(privateKey, provider)

// KYP合约的人类可读abi
const abiKTP = [
    "constructor(string memory name_, string memory symbol_)",
    "function name() view returns (string)",
    "function symbol() view returns (string)",
    "function totalSupply() view returns (uint256)",
    "function balanceOf(address) view returns (uint)",
    "function transfer(address to, uint256 amount) external returns (bool)",
    "function mint(uint amount) external",
];

// KTP合约地址(sepolia测试网)
const addressKTP = '0x33c4739bb762154a3D916Bdde73B085Db9479585'
// WETH Contract

// 声明可写合约
const contractKTP = new ethers.Contract(addressKTP, abiKTP, wallet)
// 也可以声明一个只读合约,再用connect(wallet)函数转换成可写合约。
// const contractKTP = new ethers.Contract(addressWETH, abiWETH, provider)
// contractKTP.connect(wallet)
const main = async () => {
    console.log(`合约地址: ${contractKTP.target}`);
    // 铸造1000个KTP代币
    let tx = await contractKTP.mint(ethers.parseEther("1000"))
    console.log("等待交易上链")
    await tx.wait()
    console.log(`mint后地址中代币余额: ${ethers.formatEther(await contractKTP.balanceOf(wallet))}`)
    console.log(`代币总供给: ${ethers.formatEther(await contractKTP.totalSupply())}`)
    // 调用transfer()函数,给自己的某一个账户转账100代币
    console.log("\n3. 调用transfer()函数,给自己的某一个账户转账1000代币")
    tx = await contractKTP.transfer("0xBE395e1106747dc0FDdB03f9b50C5D09C042c03d", ethers.parseEther("1000"))
    console.log("等待交易上链")
    await tx.wait()
    console.log(`查看自己账户钱包中的代币余额: ${ethers.formatEther(await contractKTP.balanceOf("0xBE395e1106747dc0FDdB03f9b50C5D09C042c03d"))}`)
}

main()

我们执行此函数,并可以在控制台中输出此次执行结果,我们此次铸造了1000枚KTP币,并且我将这1000枚KTP代币转给了自己的一个账户,和我一起去MetaMask上验证一下吧~

可以看到我们的账户已经获得到了1000枚KTP代币,是不是很有趣呢? 快点和我一起动手试一试吧~

相关推荐
2301_776045236 小时前
加密货币地址的基本概念
区块链
小树苗1932 天前
DePIN潜力项目Spheron解读:激活闲置硬件,赋能Web3与AI
人工智能·web3
CESS_Cloud2 天前
CESS 出席华盛顿区块链政策峰会:参与国家安全与数据隐私保护专题讨论
安全·阿里云·web3·去中心化·区块链
我可是千机伞3 天前
BOB.meme已于12月18日正式部署于BNB Chain
web3
TianXuan_Chain3 天前
web3跨链桥协议-Nomad
web3·区块链·智能合约·跨链桥
CertiK4 天前
Web3.0安全开发实践:探索比特币DeFi生态中的PSBT
区块链
选择不变4 天前
慢牛提速经典K线形态-突破下跌起始位和回档三五线,以及徐徐上升三种形态
区块链·通达信指标公式·炒股技巧·短线指标·炒股指标
飞天阁4 天前
Hyperledger Fabric 2.x 环境搭建
运维·区块链·fabric
Sui_Network4 天前
Sui 基金会任命 Christian Thompson 为新任负责人
大数据·人工智能·物联网·区块链·智能合约
电报号dapp1194 天前
NFT交易所开发攻略:打造未来数字艺术品交易新平台
人工智能·去中心化·区块链·智能合约