---
# System prepended metadata

title: Foundry NFT實作教學/介紹 (2)開發並部署合約

---

# Foundry NFT實作教學/介紹 (2)開發並部署合約

# 前言
上篇: [Foundry NFT實作教學/介紹 (1)你的第一個Foundry專案](https://hackmd.io/@RainFox/HyFJFaaJ2)

在上次我們已經建立了一個Foundry的專案，再來我們就要開始開發NFT的合約，這邊的部署我們會以Localhost為主，如果需要部署到Sepolia/Goerli 請自行更換RPC-URL

# 開始開發

### 1. 安裝套件 openzeppelin contracts

```
forge install OpenZeppelin/openzeppelin-contracts
```

如果成功你就會看到.gitmodules 多了下面這個

```
[submodule "lib/openzeppelin-contracts"]
	path = lib/openzeppelin-contracts
	url = https://github.com/OpenZeppelin/openzeppelin-contracts
	branch = v4.8.2
```

如果安裝失敗，並出現下面訊息，代表你需要先去清空你的working跟stage area，這部分是git的相關問題，因為一般狀況下只要安裝一個套件他就幫你自動做一個commit，如果你不清楚git的相關操作，那麼你就先把你現在的改動都先commit，再`forge install OpenZeppelin/openzeppelin-contracts` 即可

```
Error:
The target directory is a part of or on its own an already initialized git repository,
and it requires clean working and staging areas, including no untracked files.

Check the current git repository's status with `git status`.
Then, you can track files with `git add ...` and then commit them with `git commit`,
ignore them in the `.gitignore` file, or run this command again with the `--no-commit` flag.

If none of the previous steps worked, please open an issue at:
https://github.com/foundry-rs/foundry/issues/new/choose
```

> 若在安裝上有其他疑問跟想嘗試其他功能，可以參考 [forge-install](https://book.getfoundry.sh/reference/forge/forge-install)
> 
> Foundry安裝方法是採用git submodule，所以只要是git上的專案都可以抓下來使用，而forge install會自動幫你加入進lib資料夾中，但你也可以採用一般git submodule方法來裝套件

### 2. Remapping

這並非開發的中的必要步驟，但是這個是Foundry提供的有趣功能，讓你在contract做import時，可以自定義import的方式，而不用把所有的路徑都打上去

```
forge remappings
```

Output: 
```
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/
```

這邊會顯示你所有的套件跟路徑，以`forge-std`來說，`forge-std`的實際位置在lib/forge-std/src/，假設我們的contract想要import forge-std的Test.sol進來做使用，那麼就只需要
`import "forge-std/Test.sol"`即可完成import，不需要把所有路徑都打上去

上面的Output出來的是預設的remapping方式，但是你也可以另外生成一個remapping的檔案來去自定義你喜歡的方式

```
forge remappings > remappings.txt
```

執行完上面指令你就會發現專案中多了一個`remappings.txt`的檔案，可以藉由更改裡面的資訊來完成自定義

可參考 [forge-remappings](https://book.getfoundry.sh/reference/forge/forge-remappings)

### 3.創建合約

在`src`資料夾創建一個`NFT.sol`的新檔案，並將程式碼複製上去

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";

contract NFT is ERC721 {

    constructor() ERC721("NFT", "NFT") {}

    function mint(address to, uint256 tokenId) public {
        _mint(to, tokenId);
    }
}

```

這邊就不解釋solidity相關的語法，唯一要注意的地方在於import的部分要去注意你的remapping有沒有改動，有的話上面的import路徑也要隨之修改

### 4. Deploy

這個步驟我們需要把我們的合約部署到本地鏈上

所以我們必須先開啟本地鏈

```
anvil
```

anvil是Foundry裡面其中一個套件，負責鏈上等等的功能，類似於`npx hardhat node` 

開啟後我們需要先創建.env檔，用來儲存等下部署需要的參數

```
LOCALHOST="http://localhost:8545"
PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
```

> 如果要部署在本地鏈上，私鑰請用anvil裡面提供的

接著輸入以下指令

```
source .env
```

都設定好之後，就可以來部署合約了，部署指令如下
```
forge create --rpc-url=$LOCALHOST --private-key=$PRIVATE_KEY NFT
```

上面指令由左而右我們分別設定RPC-URL，以及部署者的private key跟要部署的合約
> 如果constructor有需要設定參數，指令內要使用`--constructor-args`指定參數，可參考 [forge-create](https://book.getfoundry.sh/reference/forge/forge-create)


Output:
```
[⠔] Compiling...
[⠆] Compiling 11 files with 0.8.18
[⠔] Solc 0.8.18 finished in 684.41ms
Compiler run successful
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Transaction hash: 0x8c876aea8faeb25d18d376db491dfbb82ef33ca9bc3814615464a98bab458d46
```
若成功部署可以看到上面有部署的相關資訊，而anvil上面也會有交易紀錄
```
// On anvil
eth_sendRawTransaction

    Transaction: 0x8c876aea8faeb25d18d376db491dfbb82ef33ca9bc3814615464a98bab458d46
    Contract created: 0x5fbdb2315678afecb367f032d93f642f64180aa3
    Gas used: 1139494

    Block Number: 1
    Block Hash: 0x71e51ecf4deaebaf20509be115d8ef4604bcb6b6341b4dcfcc77618324197b42
    Block Time: "Wed, 12 Apr 2023 05:58:53 +0000"

eth_getTransactionByHash
```

### 5. 確認部署結果

為了確定我們的合約是不是真的部署到localhost上了，所以我們實際來調用合約來mint一個tokenId為1的NFT 試試

```
cast send --private-key $USER_PRIVATE_KEY $CONTRACT "mint(address,uint256)" $USER_ADDR 1
```

Output:
```
//On anvil

blockHash               0x9903daae0e200f79fc952fec16aeb0f55f8f65a253d7e3990294731da27b5441
blockNumber             2
contractAddress
cumulativeGasUsed       68758
effectiveGasPrice       3884495783
gasUsed                 68758
logs                    [{"address":"0x5fbdb2315678afecb367f032d93f642f64180aa3","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x","blockHash":"0x9903daae0e200f79fc952fec16aeb0f55f8f65a253d7e3990294731da27b5441","blockNumber":"0x2","transactionHash":"0x60363e078c8b2c95814535d5099e2fb29cf8023eedce7640e6558bf8a18a5843","transactionIndex":"0x0","logIndex":"0x0","transactionLogIndex":"0x0","removed":false}]
logsBloom               0x00000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000008000000000000000000040000000000000000000000000840020000000000000000000800000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000060000000000000000000000000000000000001000000000000000000000000000000
root
status                  1
transactionHash         0x60363e078c8b2c95814535d5099e2fb29cf8023eedce7640e6558bf8a18a5843
transactionIndex        0
type
```

這邊我們用的cast是Foundry的其中一個套件，可以把他想成provider，負責獲取一些鏈上的資訊，跟鏈上做互動等等，我們這邊做的是送一個交易到鏈上，所以使用的是`cast send`

上面`$USER_PRIVATE_KEY` `$USER_ADDR`都是後面新增的，可隨意替換成自己想要的address(address跟private key必須對應)

`$CONTRACT` 指得是你部署的合約地址，以上面的deploy出來的資訊來看，`$CONTRACT` 就是 0x5FbDB2315678afecb367f032d93F642f64180aa3

> 上述這些是環境變數的用法，也可以直接輸入值進去

mint完之後，我們必須確定這個交易是不是真的有成功更改到合約的狀態，除了可以像deploy那樣查看anvil上面的資訊外，我們可以選擇調用ownerOf這個function，查看tokenId 1 的擁有者是不是為你剛剛輸入的`$USER_ADDR`

```
cast call $CONTRACT "ownerOf(uint256)" 1
```

Output:
```
0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8
```

這邊確實有返回一個值給我，且確認這個值是不是跟你剛剛輸入的`$USER_ADDR`一樣

# 結尾

到這邊都沒問題的話，恭喜你! Foundry的基本操作你已經掌握一部分了，但有些人此時可能會發現，指令比起hardhat相比還要繁瑣，要下的參數又更多，因此這邊只能透過撰寫shell script來解決

下一篇我們會介紹 [Foundry NFT實作教學/介紹 (3)Unit Test](https://hackmd.io/@RainFox/ByVFmR7Gn)

若在製作上有問題，可以參考原始碼: [hello-foundry](https://github.com/LI-YONG-QI/hello-foundry)


