了解如何构建您的第一个 Cairo 智能合约!

我们的使命是将开发人员带入 Starknet 生态系统。为实现这一目标,我们正在制作一个内容系列,该系列将通过在网络上构建的基础知识来教育和指导您。
之前,我们解释了什么是 ERC20 代币以及如何在 Starknet 上创建和部署 ERC20 代币。本周,我们将重点关注 ERC721 代币,也称为 NFT。
到本文结束时,您将了解什么是 ERC721 令牌以及如何使用 OpenZeppelin 的库创建一个。
WTF 是 NFT 吗?
NFT(不可替代代币)是存在于区块链上的独特数字资产,例如 Starknet。
这些代币通常被称为 NFT,与它们的对应代币(可替代代币)不同,因为它们不能与其他代币等价交换或交易。
与可替代的 ERC20 令牌不同,这意味着它们可以用相同的令牌替换,类似于金钱的运作方式。NFT 是不可替代的,这意味着它们不能被替换,因为每个 NFT 都有一个独特的标识符和元数据,将它们与其他代币区分开来。
ERC721标准
ERC721 标准由 William Entriken、Dieter Shirley、Jacob Evans 和 Nastassia Sachs 提出,并受到 ERC20 标准的启发,包含类似 ERC20 的方法,例如:
- 名称:此函数定义令牌的名称。
- 符号:此函数定义令牌的符号。
- balanceOf:此函数返回一个地址拥有的 NFT 数量。
ERC721标准还引入了一些新方法:
- ownerOf:此函数返回令牌所有者的地址。
- supportsInterface:此函数查询合约是否实现接口。
- transferFrom:将令牌所有权从一个帐户转移到另一个帐户。
- safeTransferFrom:将令牌所有权从一个帐户安全转移到另一个帐户。安全传输意味着它检查接收者是否有效。它还可以接受发送给接收器的附加数据。
- 批准:此功能授予或批准另一个实体代表所有者转移令牌的权限。
- setApprovalForAll:此函数启用或禁用操作员管理所有所有者资产的批准。
- getApproved:此函数获取特定令牌 ID 的批准地址。
- isApprovedForAll:此函数查询一个地址是否是另一个地址的授权操作员。
设置原星环境
要开始在 Starknet 上构建你的第一个 NFT,你需要设置你的开发者框架。我们的首选是 Protostar。
如果您不熟悉开发环境或想了解有关 Protostar 的更多信息,我们推荐我们关于创建Cairo 开发环境的文章。
设置好 Protostar 后,您就可以构建您的第一个Starknet NFT了。我们称我们的项目为“ argentERC721 ”。
为此,请运行:
原星初始化
将进一步请求项目名称和库的目录名称。这是成功初始化项目所必需的。
创建一个新文件
我们需要在我们的src文件夹中创建一个名为ERC721.cairo的新文件。这是我们要编写合约代码的地方。
进口
在我们的新文件中,我们将从%lang starknet指令开始,该指令指定我们的文件包含 Starknet 合约的代码。
完成后,我们导入所有必要的库函数。
%lang starknet from starkware.cairo.common.cairo_builtins import HashBuiltin from starkware.cairo.common.uint256 import Uint256 from starkware.starknet.common.syscalls import get_caller_address from cairo_contracts.src.openzeppelin.token.erc721.library import ERC721 from cairo_contracts.src.openzeppelin.introspection.erc165.library import ERC165 from cairo_contracts.src.openzeppelin.access.ownable.library import Ownable
从此代码片段中,您可以看到我们从Openzeppelin 的 ERC721 库中导入并将使用ERC721和ERC165命名空间。我们还导入了pedersen哈希相关操作所需的 HashBuiltin,用于创建 Uint256 变量的 Uint256 结构,以及用于获取调用者地址的get_caller_address 。
ERC721 合约接口
这是 ERC721 合约接口,它公开了我们在构建令牌时需要实现的方法:
%lang starknet from starkware.cairo.common.uint256 import Uint256 @contract_interface namespace IERC721 { func name() -> (name: felt) { } func symbol() -> (symbol: felt) { } func balanceOf(owner: felt) -> (balance: Uint256) { } func ownerOf(tokenId: Uint256) -> (owner: felt) { } func safeTransferFrom(from_: felt, to: felt, tokenId: Uint256, data_len: felt, data: felt*) { } func transferFrom(from_: felt, to: felt, tokenId: Uint256) { } func approve(approved: felt, tokenId: Uint256) { } func setApprovalForAll(operator: felt, approved: felt) { } func getApproved(tokenId: Uint256) -> (approved: felt) { } func isApprovedForAll(owner: felt, operator: felt) -> (isApproved: felt) { } func mint(to: felt) { } }
写合同
构造器
对于我们的 ERC721 令牌,我们需要在部署时初始化某些变量,例如name、symbol和owner。为此,我们的合约必须实现一个构造函数:
@constructor func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr} ( name: felt, symbol: felt, owner: felt ) { ERC721.initializer(name, symbol); Ownable.initializer(owner); return (); }
我们还通过使用从 Openzeppelin 导入的ERC721命名空间调用初始化器内部函数,同时传递所需的函数参数 [名称和符号]。我们最终通过调用Ownable命名空间的初始化函数为合约分配所有者。
支持界面
此函数查询以检查合约是否实现了某个接口。
@view func supportsInterface{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr} ( interfaceId: felt ) -> (success: felt) { let (success) = ERC165.supports_interface(interfaceId); return (success,); }
姓名
name 函数是一个视图函数,它在查询时简单地返回令牌的名称。
@view func name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr} () -> (name: felt) { let (name) = ERC721.name(); return (name,); }
象征
symbol 函数在查询时返回令牌的符号。
@view func symbol{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr} () -> (symbol: felt) { let (symbol) = ERC721.symbol(); return (symbol,); }
余额
函数的余额返回一个地址拥有的 NFT 数量。
@view func balanceOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr} (owner: felt) -> (balance: Uint256) { let (balance: Uint256) = ERC721.balance_of(owner); return (balance,); }
所有者
此函数返回令牌所有者的地址。
@view func ownerOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr} (tokenId: Uint256) -> (owner: felt) { let (owner) = ERC721.owner_of(tokenId); return (owner,); }
获得批准
此函数获取特定令牌 ID 的批准地址。
@view func getApproved{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr} (tokenId: Uint256) -> (approved: felt) { let (approved) = ERC721.get_approved(tokenId); return (approved,); }
为所有人批准
此函数查询一个地址是否是另一个地址的授权操作员。
@view func isApprovedForAll{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr} (owner: felt, operator: felt) -> (isApproved: felt) { let (isApproved) = ERC721.is_approved_for_all(owner, operator); return (isApproved,); }
转移
此功能将令牌所有权从一个帐户转移到另一个帐户。
@external func transferFrom{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( _from: felt, to: felt, tokenId: Uint256 ) { ERC721.transfer_from(_from, to, tokenId); return (); }
安全转移自
将令牌所有权从一个帐户安全转移到另一个帐户。
@external func safeTransferFrom{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( _from: felt, to: felt, tokenId: Uint256, data_len: felt, data: felt* ) { ERC721.safe_transfer_from(_from, to, tokenId, data_len, data); return (); }
批准
此功能授予或批准另一个实体代表所有者转移令牌的权限。
@external func approve{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( account: felt, tokenId: Uint256 ) { ERC721.approve(account, tokenId); return (); }
为所有人设置批准
此功能启用或禁用批准操作员管理所有所有者的资产。
@external func setApprovalForAll{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( operator: felt, approved: felt ) { ERC721.set_approval_for_all(operator, approved); return (); }
恭喜,您刚刚完成了第一个 ERC721 合约!您可以在此处找到完整的合同代码。
铸造 NFT
当我们完成我们的 NFT 合约时,我们需要一个额外的功能来为用户铸造具有不同代币 ID 的新代币。为此,我们将实现一个存储变量 token_counter 和一个外部函数 mint。
令牌计数器
token_counter 是一个存储变量,它跟踪创建的令牌数量以确定下一个令牌 ID。
@storage_var func token_counter() -> (id: felt) { }
mint
mint 函数是一个实现铸币逻辑的外部函数。它首先检查调用者是合约所有者,然后计算新的代币 ID,将新代币铸造给接收者,最后更新 token_counter 变量。
@external func mint{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( to: felt ) { Ownable.assert_only_owner(); let (prevTokenId) = token_counter.read(); let tokenId = prevTokenId + 1; ERC721._mint(to, Uint256(tokenId, 0)); token_counter.write(tokenId); return (); }
部署
最后,按照部署合约的常规步骤,我们将首先构建/编译、声明和部署。
建筑
当你构建你的 Starknet NFT 时,你需要在你的 protostar.toml 中指定你的合约的正确路径,并使 Protostar 能够找到你的 lib 文件夹,其中包含你的 Openzeppelin 合约。为此,请添加代码段:
cairo-path = ["lib"] Your protostar.toml file should look like this: [project] protostar-version = "0.9.1" lib-path = "lib" cairo-path = ["lib"] [contracts] ERC721 = ["src/ERC721.cairo"]
要构建你的合约,只需运行以下命令:
原星建造
宣告
在执行“declare”命令之前,您需要在文件或终端中设置与指定账户地址关联的私钥。
要在终端中设置您的私钥,请运行以下命令:
export PROTOSTAR_ACCOUNT_PRIVATE_KEY=[你的私钥在这里]
不要与任何人分享您的私钥。甚至不是银色。它应该只供您使用。
要声明您的合约,只需运行以下命令:
protostar declare ./build/ERC721.json --network testnet --account 0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 --max-fee auto
部署中
最后,我们需要调用传入合约类哈希的“deploy”命令来部署我们的合约。
protostar deploy 0x04dae654c7b6707667a178729b512d61494fe590ab4accc46923d6409b97e617 --network testnet --account 0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 --max-fee auto --inputs 71959616777844 4280903 0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16
结论
恭喜!您刚刚在 StarkNet 上编写并部署了您的第一个 ERC721 合约。
要与已部署的合约进行交互,请在此处检查 Starkscan 。
如果您对此有任何疑问,请联系我@0xdarlington,我很乐意帮助您使用 Argent X 在 StarkNet 上进行构建。
如需更多开发人员资源,请关注我们的社交网站:
推特—— @argentHq
工程 Twitter — @argentDeveloper
领英—— @argentHq
Youtube—— @argentHQ
英文原文链接:https://www.argent.xyz/blog/writing-and-deploying-your-first-nft-on-starknet/