# How to Construct Starknet Erc721 Contract

By [ens4me.eth](https://paragraph.com/@ens4me) · 2023-11-20

---

要开始在 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 代币，我们希望在部署时初始化变量，例如**name**、**symbol**、**decimals**和**totalSupply**。

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

    @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 令牌，我们需要在部署时初始化某些变量，例如**name**、**symbol**和**owner**。为此，我们的合约必须实现一个构造函数

我们还通过使用从 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 合约！[您可以在此处](https://github.com/argentlabs/starknet-build/tree/main/argentERC721)找到完整的合同代码。

### **铸造 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 合约。

要与已部署的合约进行交互，请[在此处](https://testnet.starkscan.co/contract/0x00d1279db0c32300e0f773025f931cc47f92391fa1b8dcb2cd7c86d42a9f68c2)检查 Starkscan 。

---

*Originally published on [ens4me.eth](https://paragraph.com/@ens4me/how-to-construct-starknet-erc721-contract)*
