> ## Documentation Index
> Fetch the complete documentation index at: https://docs.plasma.org/llms.txt
> Use this file to discover all available pages before exploring further.

# 部署合约

> 如何使用 Foundry、Hardhat 和 Ethers.js 在 Plasma 上部署智能合约。无需对 Ethereum 代码做任何更改。

## 前置条件

* Node.js 16+
* 熟悉 Ethereum 开发
* 拥有测试网代币的钱包（参见[测试网链上详情](../network-configuration/testnet-details)）

## 合约准备

本指南将使用以下示例合约：

```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract SimpleStorage {
    string private storedData;
    address public owner;
    
    event DataStored(string data, address indexed by);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
    constructor(string memory initialData) {
        storedData = initialData;
        owner = msg.sender;
        emit DataStored(initialData, msg.sender);
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can perform this action");
        _;
    }
    
    function setData(string memory data) public onlyOwner {
        storedData = data;
        emit DataStored(data, msg.sender);
    }
    
    function getData() public view returns (string memory) {
        return storedData;
    }
    
    function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0), "New owner cannot be zero address");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }
}
```

## 使用 Foundry 部署

[Foundry](https://getfoundry.sh) 是一个基于 Rust 的快速 Ethereum 开发工具包。

1. 安装 Foundry：

   ```shell theme={null}
   curl -L https://foundry.paradigm.xyz | bash
   ```

   你可能需要 *source* 你的 `.bashrc` 或 `.zshrc` 文件

2. 设置新的 Foundry 项目：

   ```shell theme={null}
   foundryup
   forge init my-plasma-project
   cd my-plasma-project
   ```

3. 使用 Plasma 测试网设置更新 `foundry.toml`：

   ```toml theme={null}
   [profile.default]
   src = "src"
   out = "out"
   libs = ["lib"]
   solc_version = "0.8.28"
   optimizer = true
   optimizer_runs = 200

   [rpc_endpoints]
   plasma_testnet = "https://testnet-rpc.plasma.to"
   ```

4. 在项目根目录创建 `.env` 文件：

   ```shell theme={null}
   PRIVATE_KEY=your_private_key_here
   RPC_URL=https://testnet-rpc.plasma.to
   ```

5. 加载环境变量：

   ```shell theme={null}
   source .env
   ```

6. 将[合约代码](#contract-preparation)保存为 `src/SimpleStorage.sol`，然后部署：

   ```shell theme={null}
   forge create src/SimpleStorage.sol:SimpleStorage \
       --rpc-url $RPC_URL \
       --private-key $PRIVATE_KEY \
       --constructor-args "Hello, Plasma!"
   ```

   Foundry 会输出部署交易哈希和合约地址：

   ```plaintext theme={null}
   [⠊] Compiling...
   [⠢] Compiling 1 files with Solc 0.8.28
   [⠆] Solc 0.8.28 finished in 124.81ms
   Compiler run successful!
   Warning: Dry run enabled, not broadcasting transaction

   Contract: SimpleStorage
   Transaction: {
     "from": "0xbd828f7679656f8f830b89611c933017442f2ebf",
     "to": null,
     "maxFeePerGas": "0xf",
     "maxPriorityFeePerGas": "0x1",
     "gas": "0x69f18",

   [...]
   ```

7. 使用 Foundry 的 cast 工具测试已部署的合约。先读取已存储的数据：

   ```shell theme={null}
   cast call 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
       "getData()" \
       --rpc-url $RPC_URL
   ```

8. 然后更新数据并再次读取：

   ```shell theme={null}
   # Update the stored data.
   cast send 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
       "setData(string)" "Updated from Foundry" \
       --private-key $PRIVATE_KEY \
       --rpc-url $RPC_URL

   # Read the stored data again.
   cast call 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
       "getData()" \
       --rpc-url $RPC_URL
   ```

   输出类似如下：

   ```shell theme={null}
   blockHash            0x68fbd2aaf1de9c577869056ca634f2103fa1695673a94d8c049d0b78d3733aac
   blockNumber          1200423
   contractAddress
   cumulativeGasUsed    21712
   effectiveGasPrice    8
   from                 0xBd828F7679656F8f830b89611C933017442F2EbF
   gasUsed              21712
   logs                 []
   logsBloom            0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
   root
   status               1 (success)
   transactionHash      0xcf5b1fcc8d19f7cf7992f6e6a8b3ade74e07afbd0da0b9fce26bda4c9503a12b
   transactionIndex     0
   type                 2
   blobGasPrice
   blobGasUsed
   to                   0x742D35Cc6610c7532C8582D4C371aCb1D5F44D7F
   0x
   ```

## 使用 Hardhat 部署

[Hardhat](https://hardhat.org/) 是一个功能齐全的开发框架，拥有丰富的插件支持。

1. 请确保你具备以下条件：

* **Node.js 20+**（现代 Hardhat 兼容性所需）
* 熟悉 Ethereum 开发
* 拥有测试网代币的钱包（参见[测试网链上详情](../network-configuration/testnet-details)）

<Warning>
  Hardhat 不再支持 Node.js 18 及以下版本，并可能导致 ES module 兼容性问题。
</Warning>

1. 验证 Node.js 版本：

   ```shell theme={null}
   node --version
   ```

   如果使用的是 Node.js 18 或更低版本，请升级至 Node.js 20+：

   ```shell theme={null}
   # Using nvm (recommended)
   nvm install 20
   nvm use 20

   # Or using NodeSource repository (Ubuntu/Debian)
   curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
   sudo apt-get install -y nodejs
   ```

2. 初始化一个新的 Hardhat 项目：

   ```shell theme={null}
   mkdir my-plasma-hardhat-project
   cd my-plasma-hardhat-project
   npm init -y
   npm install --save-dev hardhat@latest @nomicfoundation/hardhat-toolbox@latest
   npm install dotenv@latest ethers@latest
   npx hardhat init
   ```

   <Tip>
     如果遇到兼容性问题，请查阅 Hardhat 文档了解最新支持的版本。
   </Tip>

   在提示时选择 **Create a JavaScript project**。

3. 更新 `hardhat.config.js`：

   ```javascript theme={null}
   require("@nomicfoundation/hardhat-toolbox");
   require("dotenv").config();

   /** @type import('hardhat/config').HardhatUserConfig */
   module.exports = {
     solidity: {
       version: "0.8.28",
       settings: {
         optimizer: {
           enabled: true,
           runs: 200
         }
       }
     },
     networks: {
       plasmaTestnet: {
         url: process.env.RPC_URL,
         chainId: 9746,
         accounts: [process.env.PRIVATE_KEY],
         gasPrice: 1000000000, // 1 gwei
       }
     },
     etherscan: {
       apiKey: {
         plasmaTestnet: process.env.ETHERSCAN_API_KEY
       },
       customChains: [
         {
           network: "plasmaTestnet",
           chainId: 9746,
           urls: {
             apiURL: "https://testnet.plasmascan.to/api",
             browserURL: "https://testnet.plasmascan.to/"
           }
         }
       ]
     }
   };
   ```

4. 在项目根目录创建 `.env` 文件：

   ```shell theme={null}
   PRIVATE_KEY=your_private_key_here
   RPC_URL=https://testnet-rpc.plasma.to
   ETHERSCAN_API_KEY=your_api_key_for_verification
   ```

5. 创建 scripts 目录和部署脚本：

   ```shell theme={null}
   mkdir scripts
   ```

6. 创建 `scripts/deploy.js`：

   ```javascript theme={null}
   const { ethers } = require("hardhat");

   async function main() {
     // Get the contract factory
     const SimpleStorage = await ethers.getContractFactory("SimpleStorage");

     console.log("Deploying SimpleStorage contract...");

     // Deploy the contract with constructor arguments
     const simpleStorage = await SimpleStorage.deploy("Hello, Plasma from Hardhat!");

     // Wait for deployment to complete
     await simpleStorage.waitForDeployment();

     const contractAddress = await simpleStorage.getAddress();
     console.log("SimpleStorage deployed to:", contractAddress);
     console.log("Transaction hash:", simpleStorage.deploymentTransaction().hash);

     // Wait a few blocks for the transaction to be included
     console.log("Waiting for block confirmations...");
     await simpleStorage.deploymentTransaction().wait(5);

     console.log("✅ Deployment completed successfully!");
   }

   main()
     .then(() => process.exit(0))
     .catch((error) => {
       console.error(error);
       process.exit(1);
     });
   ```

7. 将[合约代码](#contract-preparation)保存为 `contracts/SimpleStorage.sol`，然后部署：

   ```shell theme={null}
   npx hardhat run scripts/deploy.js --network plasmaTestnet
   ```

8. 完成！

## 使用 Ethers.js 部署

[Ethers.js](https://ethers.org/) 提供了简洁的、可编程的部署流程。

1. 创建新项目：

   ```shell theme={null}
   mkdir my-plasma-ethers-project
   cd my-plasma-ethers-project
   npm init -y
   npm install ethers dotenv solc
   ```

2. 在项目根目录创建 `.env` 文件：

   ```shell theme={null}
   PRIVATE_KEY=<your_private_key_here>
   RPC_URL=https://testnet-rpc.plasma.to
   ETHERSCAN_API_KEY=your_api_key_for_verification
   ```

3. 创建 `compile.js` 来编译 Solidity 合约：

   ```javascript theme={null}
   const fs = require('fs');
   const solc = require('solc');
   require('dotenv').config();

   // Read the contract source code.
   const contractSource = fs.readFileSync('SimpleStorage.sol', 'utf8');

   // Prepare the input for the Solidity compiler.
   const input = {
     language: 'Solidity',
     sources: {
       'SimpleStorage.sol': {
         content: contractSource,
       },
     },
     settings: {
       outputSelection: {
         '*': {
           '*': ['*'],
         },
       },
       optimizer: {
         enabled: true,
         runs: 200,
       },
     },
   };

   // Compile the contract.
   const output = JSON.parse(solc.compile(JSON.stringify(input)));

   // Check for compilation errors.
   if (output.errors) {
     output.errors.forEach((error) => {
       console.error(error.formattedMessage);
     });
   }

   // Extract the contract data.
   const contract = output.contracts['SimpleStorage.sol']['SimpleStorage'];
   const abi = contract.abi;
   const bytecode = contract.evm.bytecode.object;

   // Save compilation artifacts.
   fs.writeFileSync('SimpleStorage.json', JSON.stringify({
     abi: abi,
     bytecode: bytecode
   }, null, 2));

   console.log('Contract compiled successfully!');
   ```

4. 将你的合约保存为 `SimpleStorage.sol`，然后编译：

   ```shell theme={null}
   node compile.js
   ```

   输出如下：

   ```plaintext theme={null}
   Contract compiled successfully!
   ```

5. 创建 `deploy.js`：

   ```javascript theme={null}
   const { ethers } = require('ethers');
   const fs = require('fs');
   require('dotenv').config();

   async function deploy() {
     // Set up provider and wallet.
     const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
     const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
     
     console.log('Deploying from account:', wallet.address);
     
     // Check account balance.
     const balance = await provider.getBalance(wallet.address);
     console.log('Account balance:', ethers.formatEther(balance), 'XPL');
     
     // Load compiled contract.
     const contractData = JSON.parse(fs.readFileSync('SimpleStorage.json', 'utf8'));
     
     // Create contract factory.
     const factory = new ethers.ContractFactory(
       contractData.abi,
       contractData.bytecode,
       wallet
     );
     
     // Deploy the contract.
     console.log('Deploying contract...');
     const contract = await factory.deploy("Hello, Plasma from Ethers.js!", {
       gasLimit: 500000, // Set a reasonable gas limit.
       gasPrice: ethers.parseUnits('1', 'gwei'), // 1 gwei gas price.
     });
     
     // Wait for deployment.
     await contract.waitForDeployment();
     
     const contractAddress = await contract.getAddress();
     console.log('Contract deployed to:', contractAddress);
     console.log('Transaction hash:', contract.deploymentTransaction().hash);
     
     // Save deployment info.
     const deploymentInfo = {
       contractAddress: contractAddress,
       transactionHash: contract.deploymentTransaction().hash,
       deployer: wallet.address,
       network: 'plasmaTestnet',
       timestamp: new Date().toISOString()
     };
     
     fs.writeFileSync('deployment.json', JSON.stringify(deploymentInfo, null, 2));
     
     return contract;
   }

   async function interact(contract) {
     console.log('\nInteracting with deployed contract...');
     
     // Read initial data.
     const initialData = await contract.getData();
     console.log('Initial data:', initialData);
     
     // Update data.
     const updateTx = await contract.setData("Updated via Ethers.js");
     await updateTx.wait();
     console.log('Data updated. Transaction hash:', updateTx.hash);
     
     // Read updated data.
     const updatedData = await contract.getData();
     console.log('Updated data:', updatedData);
     
     // Get owner.
     const owner = await contract.owner();
     console.log('Contract owner:', owner);
   }

   // Main execution.
   deploy()
     .then(async (contract) => {
       await interact(contract);
       console.log('\nDeployment and interaction completed successfully!');
     })
     .catch((error) => {
       console.error('Error:', error);
       process.exit(1);
     });
   ```

6. 运行部署脚本：

   ```shell theme={null}
   node deploy.js
   ```

   输出类似如下：

   ```shell theme={null}
   Deploying from account: 0xBd828F7679656F8f830b89611C933017442F2EbF
   Account balance: 98539.897261933991888304 XPL
   Deploying contract...
   Contract deployed to: 0xf298A2A7BC526F9228B8C422D38f3c2E0D15449F
   Transaction hash: 0x3d16cc55b9148bac9ac20981d9748a0fc89861c6beb92a2f869bb09b75f685b2

   Interacting with deployed contract...
   Initial data: Hello, Plasma from Ethers.js!
   Data updated. Transaction hash: 0xe3328862ece5d0ca4ca84d25c4130d6749ec872c24bf76eea23dfa8c50c22505
   Updated data: Updated via Ethers.js
   Contract owner: 0xBd828F7679656F8f830b89611C933017442F2EbF

   Deployment and interaction completed successfully!
   ```

## 验证部署

使用上述任一方式部署完毕后，请[验证你的合约部署](./verify-a-contract)。

## 故障排查

### ES Module 兼容性问题

如果遇到 `Error [ERR_REQUIRE_ESM]: require() of ES Module` 之类的错误，通常意味着：

1. **Node.js 版本过旧**：按前置条件中所述升级到 Node.js 20+
2. **依赖版本不匹配**：如果无法升级 Node.js，可以将出问题的依赖降级：

   ```shell theme={null}
   npm install micro-eth-signer@0.10.0
   ```

### Node.js 版本警告

如果看到不支持的 Node.js 版本警告：

```
WARNING: You are currently using Node.js v18.19.1, which is not supported by Hardhat
```

请按照前置条件中的步骤升级到 Node.js 20+。

### 资金不足

```plaintext theme={null}
Error: insufficient funds for intrinsic transaction cost
```

确保钱包有足够的测试网代币。访问[测试网水龙头](../../plasma-chain/tools/faucets)。

### 网络配置错误

```plaintext theme={null}
Error: network with chainId "1" doesn't match the configured chainId "9746"
```

请确认你的网络配置与[测试网链上详情](../network-configuration/testnet-details)一致。

### Gas 估算失败

```plaintext theme={null}
Error: cannot estimate gas
```

在部署配置中显式设置 gas 限额：

```javascript theme={null}
// In your deploy script
const simpleStorage = await SimpleStorage.deploy("Hello, Plasma from Hardhat!", {
  gasLimit: 500000
});
```
