How to Construct Starknet Erc721 Contract

要开始在 Starknet 上构建你的第一个 NFT,你需要设置你的开发者框架。我们的首选是 Protostar。

设置好 Protostar 后,您就可以构建您的第一个starknet NFT了。我们称我们的项目为“ StarknetERC721 ”。

为此,请运行:

原星初始化

将进一步请求项目名称和库的目录名称。这是成功初始化项目所必需的。

创建一个新文件

我们需要在我们的src文件夹中创建一个名为ERC721.cairo的新文件。这是我们要编写合约代码的地方。

Entry:

在我们的新文件中,我们将从

  1. %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

ERC721 合约接口

这是 ERC721 合约接口,它公开了我们在构建令牌时需要实现的方法:

%lang starknet
 
 form starkware.cairo.common.uint256 import Uint256
 
@contract_interface
 
namespace IERC20 { 
   func name() -> (name: felt) { 
   }
 
   func symbol() -> (symbol: felt) { 
   }
 
   func decimals() -> (decimals: felt) { 
   }
 
   func totalSupply() -> (totalSupply: Uint256) { 
   }
 
   func balanceOf(account: felt) -> (balance: Uint256) { 
   }
 
   func allowance(owner: felt, spender: felt) -> (remaining: Uint256) { 
   }
 
   func transfer(recipient: felt, amount: Uint256) -> (success: felt) {
   }
 
   func transferFrom(sender:felt,recipient:felt,amount:Uint256)->(success:felt){
   }
 
   func approve(spender: felt, amount: Uint256) -> (success: felt) {
 
   }
 
}

构造函数逻辑

构造函数主要用于在合约部署时初始化某些状态变量。

要在 Cairo 中创建构造函数,您需要使用**@constructor**装饰器。

对于我们的 ERC20 代币,我们希望在部署时初始化变量,例如namesymboldecimalstotalSupply

为此,我们的合约必须实现以下构造函数:

@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 ();
 
}

如上面的代码片段所示,我们的构造函数必须命名为constructor并接受 5 个参数 [_name, _symbol, _decimals, initialSupply, recipient]。

我们还通过使用从 Openzeppelin 导入的ERC20命名空间调用初始化器内部函数,同时传递所需的函数参数 [名称、符号和小数]。初始化函数创建/实例化一个总供应量为 0 的新 ERC20 代币。

为了在部署时创建固定的总供应量,我们调用传入函数参数 [recipient, initialSupply]的**_mint函数。_mint函数铸造**提供给收件人地址的 initialSupply。

在Cairo的功能

实现我们的构造函数逻辑后,我们现在可以实现我们的代币符合 ERC20 标准所需的其他功能,但在我们继续之前,让我们拿出几行来区分 Cairo 中的两种主要类型的功能。

  1. 外部函数——外部函数是改变区块链状态的函数,是使用**@external**装饰器创建的。

  2. 视图函数——视图函数是 getter 函数。它们不会改变区块链的状态,并且是使用**@view装饰器创建的。**

实现其他功能

1.姓名

name 函数是一个视图函数,它在查询时简单地返回令牌的名称。

@view

func name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr} () -> (name: felt) {

let (name) = ERC721.name();

return (name,);

}

Write Constract

构造器

对于我们的 ERC721 令牌,我们需要在部署时初始化某些变量,例如namesymbolowner。为此,我们的合约必须实现一个构造函数

我们还通过使用从 Openzeppelin 导入的ERC721命名空间调用初始化器内部函数,同时传递所需的函数参数 [名称和符号]。我们最终通过调用Ownable命名空间的初始化函数为合约分配所有者。

批准

approve 函数也是一个外部函数,用于批准支出者从所有者的钱包中花费一定数量的代币。

它有两个参数 [spender, amount]。

@view
 
func getApproved{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr} (tokenId: Uint256) -> (approved: felt) {
 
   let (approved) = ERC721.get_approved(tokenId);
 
   return (approved,);
 
}

转移

此功能将令牌所有权从一个帐户转移到另一个帐户。

@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 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 ();
 
}

部署

最后,按照部署合约的常规步骤,我们将首先构建/编译、声明和部署。

Build

当你构建你的 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"]
protostar declare ./ERC721.json --network testnet --account xx --max-fee auto

Declare:

在执行“declare”命令之前,您需要在文件或终端中设置与指定账户地址关联的私钥。

要在终端中设置您的私钥,请运行以下命令:

export PROTOSTAR_ACCOUNT_PRIVATE_KEY=[privatekey]
protostar deploy xx --network testnet --account xx --max-fee auto --inputs 71959616777844 4280903 xx

不要与任何人分享您的私钥,它应该只供您使用

结论

恭喜!您刚刚在 StarkNet 上编写并部署了您的第一个 ERC721 合约。

要与已部署的合约进行交互,请在此处检查 Starkscan 。