本文介绍了如何创建一个代币销售智能合约,该合约允许用户使用以太币购买 ERC-20 代币。文章详细讲解了合约的关键功能、设计以及代码实现,包括合约的构造函数、购买代币函数、设置价格函数和提款函数,并提供了在 Remix IDE 中的部署和交互步骤。
你已经成功编写、编译和部署了你自己的 ERC-20 代币。我们的代币已经诞生并存在于测试网络上——但现在,它只是孤零零地呆在你的钱包里。那么,接下来呢?我们如何让它为世界所用?
答案是 代币销售智能合约。可以把它想象成一台自动售货机,一台出售你的代币 ($TCT) 的自动售货机。你发送你的以太币(就像投入一美元),代币销售合约会自动将正确数量的 TCT 代币分配到你的钱包。这是分发我们新的数字资产的自动化、安全和透明的方式。
在最后一部分中,我们将为 TCT 构建和部署这个智能售货机。到最后,你将能够将测试 ETH 发送到你的新合约,并看到 TCT 代币神奇地出现在你的钱包中——准备好在未来的区块链咖啡店中使用!
要开始,你需要从第 2 部分中部署的 TCT 代币的地址(你可以在本文末尾与我一起重新部署它)。我们将使用相同的工具:Remix IDE、Solidity 和 OpenZeppelin。
在编写代码之前,我们需要了解我们想要构建的东西、关键特性和设计。我们的代币销售合约将是简单而强大的。它需要做三件关键的事情:
事不宜迟,让我们开始构建我们的自动售货机!!在同一个目录下,让我们创建一个名为“TokenSale.sol”的新文件。所有的工作都将在这个 TokenSale.sol 中编写。
代码块 1:导入和状态变量:
导入必要的合约( IERC20.sol
、Ownable.sol
)。我们需要导入两个合约,IERC20.sol 和 Ownable.sol。在 IERC20.sol 中,我们导入 ERC20 代币的接口。接口是蓝图或一组规则。它定义了一个合约中必须存在的函数,包括它们的名称、参数和返回类型,但它不包含这些函数中的任何代码(实现)。“I”在 IERC20
中代表接口。它的唯一目的是告诉其他合约对 ERC-20 代币合约的期望。
然后 Ownable.sol 包含一个 modifier onlyOwner,可用于限制某些功能中的权限。此修饰符将确保只有合约的所有者才能执行被调用的函数。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
interface ICoffeeToken is IERC20{
function mint(address to, uint256 amount) external;
}
因为 IERC20 仅定义了 ERC20 标准的核心、强制性函数:totalSupply()、balanceOf()、transfer()、allowance()、approve() 和 transferFrom(),所以我们需要添加我们自定义的 mint() 函数。
接下来,我们将声明我们的状态变量和事件。我们的合约将有两个关键的状态变量,coffeeToken 和 priceInWei。coffeeToken 将具有 ICoffeeToken 的数据类型,这是我们之前制作的接口。然后 priceInWei 将以 Wei 为单位存储代币的价格。
contract CoffeeTokenSale is Ownable {
ICoffeeToken public coffeeToken;
uint256 public priceInWei;
event purchasedToken(address buyerAddress, uint256 amountETH, uint256 amountToken);
event newTokenPrice(address changer, uint256 newPrice);
还有两个事件,purchasedToken 和 newTokenPrice,将用于在交易历史记录中写入日志。purchasedToken() 将在调用 buyToken() 函数时发出。它将包含三个信息:买家的地址、发送的 ETH 数量和铸造的代币数量。newTokenPrice() 将在调用 setPriceInWei() 函数时发出。它将包含两条信息:更改者的地址和代币的新价格。
代码块 2:构造函数:
constructor(address _coffeeTokenAddress, uint256 _priceInWei)
Ownable(msg.sender)
{
require(_coffeeTokenAddress != address(0), "Token address must be valid, You input address 0x0");
require(_priceInWei > 0, "Price must be greater than 0");
coffeeToken = ICoffeeToken(_coffeeTokenAddress);
priceInWei = _priceInWei;
}
构造函数是一个特殊的函数,仅在首次部署我们的智能合约时运行一次。它就像我们代币售货机的初始设置向导。
在我们的代码中,构造函数接受两个参数:_coffeeTokenAddress
(我们在第 2 部分中部署的 TCT 合约的地址)和 _priceInWei
(代币以 Wei 为单位的价格)。我们还调用 Ownable(msg.sender)
将合约的所有者设置为部署它的地址。
因为构造函数本质上是一个函数,所以我们可以向其中添加规则。在这里,我们使用 require
语句来验证我们的输入:我们确保代币地址不是零地址( address(0)
),并且代币价格大于零。之后,我们将我们的两个状态变量 coffeeToken
和 priceInWei
设置为传递给构造函数的值。
代码块 3:buyTokens
函数(核心逻辑):
这是我们合约中最关键的部分,buyTokens()
函数。我们在这里处理我们代币的所有购买交易。首先,此函数将接收发送到合约的 ETH,计算要转移的代币数量,然后在我们的 TCT 合约上调用 transfer()
函数,以将相应的数量发送给发送者。
function buyToken() public payable {
require(msg.value > 0, "Not enough ETH");
uint256 tokenToTransfer = msg.value / priceInWei;
require(tokenToTransfer > 0, "Not enough ETH to buy any token");
require(tokenToTransfer <= coffeeToken.balanceOf(address(this)), "Not enough tokens available in this contract");
coffeeToken.transfer(msg.sender, tokenToTransfer);
emit purchasedToken(msg.sender, msg.value, tokenToTransfer);
}
要使函数具有接收 ETH 的能力,我们必须在其定义中添加 payable
修饰符。我们在函数内部做的第一件事是检查 msg.value
(以 Wei 为单位发送的 ETH 数量)是否大于零。如果不是,它将回滚并显示错误消息。
接下来,我们计算要转移的代币数量。我们将其存储在一个名为 tokenToTransfer
的局部变量中,该变量只是 msg.value
除以我们预设的代币价格( priceInWei
)。然后,我们使用 require
语句来确保计算出的金额大于零,然后再继续。
最后,我们调用我们的 TCT 合约上的 transfer()
函数来创建新的代币并将它们发送到发送者的地址( msg.sender
)。这是可能的,因为如果你还记得我们的构造函数,我们将 coffeeToken
变量设置为我们的 TCT 合约的地址,因此我们的 buyToken()
函数确切地知道要调用哪个合约。
coffeeToken = ICoffeeToken(_coffeeTokenAddress);
代码块 4:setPriceInWei
函数:
此函数允许代币销售的所有者设置代币的新价格。虽然给一个人这么大的权力对于一个真正的去中心化项目来说并不理想(它通常由更高级系统中的社区投票来处理),但我们将以这种方式构建它以了解至关重要的 onlyOwner
修饰符。
function setPriceInWei(uint256 _newPriceInWei) public onlyOwner {
require(_newPriceInWei > 0, "New price must be greater than zero");
priceInWei = _newPriceInWei;
emit newTokenPrice(owner(), _newPriceInWei);
}
首先,我们定义该函数以接受一个参数 _newPriceInWei
。该函数可以公开看到( public)
,但由于 onlyOwner
修饰符,它只能由合约的所有者调用。
在函数内部,我们使用 require
语句来确保新价格大于零。然后,我们使用新值更新 priceInWei
状态变量。最后,我们 emit
一个事件,以创建此操作的公共日志,这有助于区块链上的透明度和文档记录。
代码块 5:withdraw
函数:
function withdrawETH() public onlyOwner {
payable(owner()).transfer(address(this).balance);
}
这是我们合约中的最后一个函数。有了这个函数,所有者将能够收集合约拥有的 ETH。你可能在想:“等等,让所有者拿走所有的 ETH 不是有危险吗?” 这是一个很好的问题,答案在于合约的目的。
在我们的例子中,代币销售合约是一种筹款工具,因此 withdrawETH()
函数不仅是预期的,而且是绝对必要的。它允许所有者安全地收取出售代币筹集的资金。onlyOwner
修饰符确保只有授权人员才能调用此函数,使其成为我们项目的安全机制。
关于安全性的一些说明:如果你在不是代币销售的合约(例如贷款协议或去中心化交易所)中看到
withdrawAll()
函数,那将是潜在的地毯式欺诈的主要危险信号。始终检查合约的代码以了解其目的以及其函数授予所有者的权限。
payable(owner()).transfer(address(this).balance);
payable(owner()).transfer(address(this).balance);
这一行是此函数中最关键的部分。让我们分解一下:
address(this).balance
:这获取智能合约当前持有的 ETH 的总余额。payable(owner())
:这会将所有者的地址转换为 payable
类型,这是将 ETH 发送到该地址所必需的。.transfer()
:这是实际的发送 ETH 的函数。总而言之,该行将合约持有的所有 ETH 转移到所有者的地址。
接下来是部署。在此步骤中,我们将部署两个合约,TurrasCoffeeToken.sol 和 TokenSale.sol。因此,首先打开 TurrasCoffeeToken.sol,转到 Deploy & run transactions 向下滚动,直到找到如下所示的构造函数:
你可以根据需要设置 initialSupply 和 capSupply,就像在此示例中,我们将 initial 设置为 1000,将 max/cap 设置为 2000。然后单击 transact。
太棒了,下一步是部署我们的代币销售合约。首先复制已部署合约的地址,向下滚动并找到已部署合约部分中的 TURRASCOFFEETOKEN。
然后,对 TokenSale.sol 执行相同的操作。打开并填写构造函数中的参数。在此示例中,我们将价格设置为 0.001 ether,相当于 wei 中的 1000000000000000。你可以使用在线工具来帮助你进行转换。
现在让我们尝试购买代币。首先在 value 中输入 1000000000000000,然后单击 buyToken。
它会给你一个错误,说该合约没有足够的代币。
要解决此问题,我们需要将一些代币从 TCT 合约转移到代币销售合约。确保使用部署 TCT 合约的地址。
让我们再次尝试购买代币。如果交易成功,你将看到一个清单。
尝试从另一个地址以 0.01 ETH 的价值购买。
让我们尝试将合约中筹集到的所有 ETH 提取给所有者。
之前:
之后:
正如你所看到的,所有者地址获得了合约中的所有 ETH。
注意:实际上有两种方法可以实现 buyToken() 功能,第一种是我们使用的方法,我们调用 transfer() 函数,这是推荐的方法,因为它只会给代币销售合约购买/出售其自己的代币的能力。该合约将从 TCT 合约购买一定数量的代币,然后他们通过自己的合约出售它,最大金额是他们拥有或已经购买的代币。另一种方法是使用 mint() 进行每次购买交易。此逻辑将使代币销售合约能够在每次购买交易时 mint() 或创建新代币。这种方法可能存在重大的安全风险,例如过度授权合约和地毯式欺诈的载体。
你做到了!你已经从对 ERC-20 的理论理解转变为完全正常运行的数字经济。你的 Turra's Coffee 代币不仅存在于区块链上,而且现在是一种可交易的资产,人们可以用 ETH 购买。
从这里,我们可以构建一个网站来与我们的代币销售互动,围绕 TCT 创建一个完整的去中心化应用程序 (dApp),甚至实施更高级的功能,如代币归属、价格等级或空投。现在的基础由你来构建!
- 原文链接: blog.blockmagnates.com/t...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!