这是一个基于Solidity的NFT拍卖系统,支持多代币出价、动态手续费和可升级的智能合约架构。系统使用Chainlink预言机获取实时价格数据,确保拍卖的公平性和透明度。
- 🎯 多代币支持: 支持ETH和ERC20代币出价
- 🔄 动态手续费: 根据拍卖金额自动调整手续费率
- 📈 实时价格: 集成Chainlink预言机获取准确的价格数据
- 🔧 可升级架构: 使用UUPS代理模式支持合约升级
- 🛡️ 安全机制: 重入攻击保护、权限控制等安全特性
- 🎨 NFT支持: 内置SimpleNFT合约用于测试
功能: 创建和管理NFT拍卖的工厂合约
主要函数:
createAuction(address nftContract, uint256 tokenId, uint256 duration, uint256 startingPriceUsd): 创建新的NFT拍卖getAllAuctions(): 获取所有拍卖合约地址getAuctionByNFT(address nftContract, uint256 tokenId): 根据NFT获取拍卖地址getEthUsdPrice(uint256 amount): 获取ETH的美元价格getTokenUsdPrice(address token, uint256 amount): 获取ERC20代币的美元价格setFeeRecipient(address newFeeRecipient): 设置手续费接收者setEthUsdFeed(address _ethUsdFeed): 设置ETH/USD预言机地址setTokenUsdFeed(address token, address feed): 设置ERC20/USD预言机地址upgradeImplementation(address newImplementation, uint16 newVersion): 升级拍卖实现合约
功能: 管理单个NFT的拍卖过程
主要函数:
placeBidwithETH(): 使用ETH出价placeBidbyToken(address token, uint256 amount): 使用ERC20代币出价endAuction(): 结束拍卖cancelAuction(): 取消拍卖getAuctionInfo(): 获取完整拍卖信息calculateDynamicFee(uint256 bidAmountUsd, uint256 bidAmount, uint256 tokenPriceUsd): 计算动态手续费
动态手续费规则:
- 0-1000美元: 5%手续费
- 1000-10000美元: 3%手续费
- 10000美元以上: 1%手续费
- 最低手续费: 1美元
功能: ERC721 NFT合约,用于测试和演示
主要函数:
mintNFT(address to, string calldata uri): 铸造单个NFTbatchMintNFT(address to, string[] calldata uris): 批量铸造NFTmaxSupply(): 获取最大供应量currentSupply(): 获取当前供应量remainingSupply(): 获取剩余可铸造数量
定义了拍卖工厂合约必须实现的所有函数和事件。
定义了单场拍卖合约必须实现的所有函数、事件和数据结构。
功能: 定义拍卖系统的常量参数
常量定义:
MIN_BID_INCREMENT_USD: 最小加价限制 (1美元)MIN_AUCTION_DURATION: 最小拍卖时长 (5分钟)MAX_AUCTION_DURATION: 最大拍卖时长 (7天)MIN_STARTING_PRICE_USD: 最小起始价格 (1美元)MAX_AUCTION_FEE: 最大手续费 (10%)
继承自AuctionFactory,添加版本信息功能。
继承自SingleAuction,添加版本信息功能。
功能: 模拟Chainlink价格预言机,用于测试
主要函数:
setPrice(int256 price): 设置价格latestRoundData(): 获取最新价格数据
功能: 模拟ERC20代币,用于测试
主要函数:
mint(address to, uint256 amount): 铸造代币
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ SimpleNFT │ │ AuctionFactory │ │ Chainlink │
│ (ERC721) │ │ (Factory) │ │ Oracle Feeds │
└─────────┬───────┘ └─────────┬───────┘ └─────────┬───────┘
│ │ │
│ 1. 转移NFT │ │
├─────────────────────►│ │
│ │ │
│ │ 2. 创建代理合约 │
│ ├─────────────────────►│
│ │ │
│ │ 3. 获取价格数据 │
│ │◄─────────────────────┤
│ │ │
│ │ 4. 转移NFT到拍卖合约 │
│◄─────────────────────┤ │
│ │ │
│ │ │
┌─────────▼───────┐ │ │
│ SingleAuction │◄─────────────┤ │
│ (Proxy) │ │ │
└─────────┬───────┘ │ │
│ │ │
│ 5. 拍卖过程 │ │
│ │ │
│ 6. 出价 (ETH/Token) │ │
│ │ │
│ 7. 结束拍卖 │ │
│ │ │
│ 8. 转移NFT给获胜者 │ │
├─────────────────────►│ │
│ │ │
│ 9. 分配资金 │ │
│ │ │
│ 10. 支付手续费 │ │
│ │ │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 创建拍卖 │───►│ 拍卖进行中 │───►│ 拍卖结束 │───►│ 拍卖完成 │
│ (Created) │ │ (Active) │ │ (Ended) │ │ (Completed) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 取消拍卖 │ │ 出价过程 │ │ 成功拍卖 │ │ 失败拍卖 │
│ (Cancelled) │ │ (Bidding) │ │ (Successful)│ │ (Failed) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 出价者 │ │ 拍卖合约 │ │ 卖家 │
│ (Bidder) │ │ (Auction) │ │ (Seller) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ 1. 出价资金 │ │
├──────────────────►│ │
│ │ │
│ │ 2. 拍卖成功 │
│ ├──────────────────►│
│ │ 3. 支付手续费 │
│ ├──────────────────►│
│ │ │
│ │ 4. 退还失败出价 │
│◄──────────────────┤ │
│ │ │
│ │ 5. 转移NFT │
│◄──────────────────┤ │
用户 工厂合约 NFT合约 拍卖合约 预言机
│ │ │ │ │
│ 1.创建拍卖 │ │ │ │
├───────────►│ │ │ │
│ │ 2.验证NFT │ │ │
│ ├──────────►│ │ │
│ │ 3.转移NFT │ │ │
│ │◄──────────┤ │ │
│ │ 4.创建代理 │ │ │
│ ├───────────┐ │ │
│ │ │ 5.初始化 │ │
│ │ ├──────────►│ │
│ │ 6.获取价格 │ │ │
│ ├───────────┐ ├────────►│
│ │ │ │◄────────┤
│ │ 7.转移NFT │ │ │
│ ├──────────►│ │ │
│ │ │ 8.开始拍卖 │ │
│ │ ├──────────►│ │
│ │ │ │ │
│ 9.出价 │ │ │ │
├───────────►│ │ │ │
│ │ │ 10.处理出价 │ │
│ │ ├───────────►│ │
│ │ │ 11.获取价格 │ │
│ │ ├───────────┐├────────►│
│ │ │ │◄────────┤
│ │ │ 12.更新出价 │ │
│ │ ├───────────►│ │
│ │ │ │ │
│ 13.结束拍卖 │ │ │ │
├───────────►│ │ │ │
│ │ │ 14.结束拍卖 │ │
│ │ ├───────────►│ │
│ │ │ 15.转移NFT │ │
│ │ ├───────────►│ │
│ │ │ 16.分配资金 │ │
│ │ ├───────────►│ │
# 步骤1: 编译合约
npx hardhat compile
# 步骤2: 运行测试验证
npx hardhat test
# 步骤3: 启动本地网络
npx hardhat node
# 步骤4: 部署合约系统
npx hardhat ignition deploy ./ignition/modules/CompleteAuctionSystem.js-
部署基础合约
- SimpleNFT (NFT合约)
- MockAggregatorV3 (预言机模拟)
- MockERC20 (代币模拟)
-
部署核心合约
- SingleAuction (拍卖实现合约)
- AuctionFactory (工厂合约)
-
配置系统参数
- 设置预言机地址
- 设置手续费接收者
- 配置代币价格源
// 步骤1: 准备NFT
SimpleNFT nft = SimpleNFT(nftAddress);
uint256 tokenId = nft.mintNFT(sellerAddress, "https://example.com/metadata");
// 步骤2: 创建拍卖
AuctionFactory factory = AuctionFactory(factoryAddress);
address auctionAddress = factory.createAuction(
nftAddress, // NFT合约地址
tokenId, // NFT token ID
3600, // 拍卖时长(1小时)
startingPrice // 起始价格(美元)
);
// 步骤3: 获取拍卖合约实例
SingleAuction auction = SingleAuction(auctionAddress);// 步骤1: 检查拍卖状态
(bool isActive, uint256 timeRemaining) = auction.getAuctionInfo();
// 步骤2: 计算出价金额
uint256 bidAmount = 1 ether; // 1 ETH
// 步骤3: 提交出价
auction.placeBidwithETH{value: bidAmount}();// 步骤1: 授权代币转账
IERC20 token = IERC20(tokenAddress);
token.approve(auctionAddress, bidAmount);
// 步骤2: 提交代币出价
auction.placeBidbyToken(tokenAddress, bidAmount);// 步骤1: 检查拍卖是否结束
(bool isActive, uint256 timeRemaining) = auction.getAuctionInfo();
// 步骤2: 结束拍卖 (可由卖家、管理员或时间到期自动触发)
auction.endAuction();
// 步骤3: 处理结果
// - 如果拍卖成功: NFT转移给最高出价者,资金分配给卖家和平台
// - 如果拍卖失败: NFT退还卖家,资金退还出价者1. 用户调用 AuctionFactory.createAuction()
├── 验证NFT所有权
├── 验证拍卖参数
├── 转移NFT到工厂合约
├── 创建SingleAuction代理合约
├── 初始化拍卖参数
├── 转移NFT到拍卖合约
└── 记录拍卖信息
2. 拍卖合约初始化
├── 设置拍卖参数
├── 设置权限控制
├── 设置重入保护
└── 发出AuctionCreated事件
1. 用户调用出价函数
├── 验证拍卖状态
├── 验证出价金额
├── 获取价格数据 (通过Chainlink)
├── 计算美元价值
├── 退还之前出价者资金
├── 更新最高出价信息
└── 发出BidPlaced事件
1. 触发结束拍卖
├── 验证权限 (卖家/管理员/时间到期)
├── 标记拍卖结束
├── 处理拍卖结果
│ ├── 成功: 转移NFT + 分配资金
│ └── 失败: 退还NFT + 退还资金
└── 发出AuctionEnded事件
// 1. 部署和配置
const factory = await AuctionFactory.deploy();
await factory.setEthUsdFeed(ethFeedAddress);
await factory.setFeeRecipient(feeRecipientAddress);
// 2. 创建NFT
const nft = await SimpleNFT.deploy("MyNFT", "MNFT", 1000);
const tokenId = await nft.mintNFT(sellerAddress, "https://example.com/1");
// 3. 创建拍卖
const tx = await factory.createAuction(
nftAddress,
tokenId,
3600, // 1小时
ethers.parseEther("100") // 100美元起始价
);
const receipt = await tx.wait();
const auctionAddress = receipt.logs[0].args.auctionContract;
// 4. 参与拍卖
const auction = SingleAuction.attach(auctionAddress);
await auction.placeBidwithETH({value: ethers.parseEther("1")});
// 5. 结束拍卖
await auction.endAuction();// 使用USDC出价
await usdc.approve(auctionAddress, ethers.parseUnits("1000", 6));
await auction.placeBidbyToken(usdcAddress, ethers.parseUnits("1000", 6));
// 使用ETH出价
await auction.placeBidwithETH({value: ethers.parseEther("0.5")});// 推荐的部署顺序
async function deployAuctionSystem() {
// 1. 部署基础合约
const nft = await SimpleNFT.deploy("MyNFT", "MNFT", 1000);
const mockEthFeed = await MockAggregatorV3.deploy();
const mockUsdcFeed = await MockAggregatorV3.deploy();
const usdc = await MockERC20.deploy("USDC", "USDC", 6, 1000000);
// 2. 设置价格
await mockEthFeed.setPrice(2000); // $2000/ETH
await mockUsdcFeed.setPrice(1); // $1/USDC
// 3. 部署核心合约
const auctionImpl = await SingleAuction.deploy();
const factoryImpl = await AuctionFactory.deploy();
// 4. 创建代理合约
const factory = await createProxy(factoryImpl, [
auctionImpl.address,
feeRecipientAddress
]);
// 5. 配置系统
await factory.setEthUsdFeed(mockEthFeed.address);
await factory.setTokenUsdFeed(usdc.address, mockUsdcFeed.address);
return { factory, nft, usdc, mockEthFeed, mockUsdcFeed };
}// 监控拍卖状态
async function monitorAuction(auctionAddress) {
const auction = SingleAuction.attach(auctionAddress);
// 获取拍卖信息
const info = await auction.getAuctionInfo();
console.log(`拍卖状态: ${info.isActive ? '进行中' : '已结束'}`);
console.log(`剩余时间: ${info.timeRemaining}秒`);
console.log(`当前最高出价: ${ethers.formatEther(info.auctionData.highestBidUsd)}美元`);
// 检查是否需要结束拍卖
if (info.isActive && info.timeRemaining === 0) {
await auction.endAuction();
console.log('拍卖已自动结束');
}
}// 安全的出价函数
async function safePlaceBid(auctionAddress, bidAmount, tokenAddress = null) {
try {
const auction = SingleAuction.attach(auctionAddress);
// 检查拍卖状态
const info = await auction.getAuctionInfo();
if (!info.isActive) {
throw new Error('拍卖未进行中');
}
if (tokenAddress) {
// ERC20代币出价
const token = IERC20.attach(tokenAddress);
await token.approve(auctionAddress, bidAmount);
await auction.placeBidbyToken(tokenAddress, bidAmount);
} else {
// ETH出价
await auction.placeBidwithETH({value: bidAmount});
}
console.log('出价成功');
} catch (error) {
console.error('出价失败:', error.message);
// 处理常见错误
if (error.message.includes('Bid must be higher')) {
console.log('出价必须高于当前最高出价');
} else if (error.message.includes('Auction has ended')) {
console.log('拍卖已结束');
}
}
}// 批量创建拍卖
async function batchCreateAuctions(factory, nft, tokenIds, duration, startingPrice) {
const auctions = [];
for (const tokenId of tokenIds) {
try {
const tx = await factory.createAuction(
nft.address,
tokenId,
duration,
startingPrice
);
const receipt = await tx.wait();
const auctionAddress = receipt.logs[0].args.auctionContract;
auctions.push(auctionAddress);
console.log(`拍卖 ${tokenId} 创建成功: ${auctionAddress}`);
} catch (error) {
console.error(`拍卖 ${tokenId} 创建失败:`, error.message);
}
}
return auctions;
}A: 系统使用动态手续费机制,根据拍卖金额自动调整:
- 0-1000美元: 5%
- 1000-10000美元: 3%
- 10000美元以上: 1%
- 最低手续费: 1美元
A: 如果拍卖失败(没有达到起始价格),系统会:
- 将NFT退还卖家
- 将出价资金退还给最高出价者
A: 使用UUPS代理模式,只有合约所有者可以升级:
await factory.upgradeImplementation(newImplAddress, newVersion);A: 支持任何ERC20代币,但需要先配置对应的价格预言机:
await factory.setTokenUsdFeed(tokenAddress, priceFeedAddress);A: 可以通过事件监听或定期查询:
// 监听出价事件
auction.on("BidPlaced", (bidder, amount, token, priceUsd) => {
console.log(`新出价: ${ethers.formatEther(amount)} from ${bidder}`);
});这个配置文件包含了在Sepolia测试网上已部署的所有合约地址:
{
"auctionFactory": "0xaEcb00Da8656C18a16415085b49da518B0984CBB", // 拍卖工厂代理合约
"simpleNFT": "0x3DB1Ef472Fd65DE888aCeBABB0A78931f6644E4f", // 简单NFT合约
"singleAuction": "0x3386618aE692Cfd51d94d9b456DE198406160541", // 单场拍卖实现合约
"usdc": "0x8e3A570e170fba92E50BA83510c60F897F283C40", // USDC代币合约
"usdt": "0x7381fe1C126B2B62Fa3E766F0ED079d91Def9188", // USDT代币合约
"weth": "0x34a56f192001Ba8Ea16eb55B54B683Aa9F325C84", // Wrapped ETH合约
"wbtc": "0x2Fc62A208B330F4673F828B7B54e661ec539E4e9", // Wrapped BTC合约
"ethUsdFeed": "0x694AA1769357215DE4FAC081bf1f309aDC325306", // ETH/USD价格源
"usdcUsdFeed": "0xA2F78ab2355fe2f984D808B5CeE7FD0A93D5270E", // USDC/USD价格源
"usdtUsdFeed": "0x1a81afB8146aeFfCFc5E50e8479e826E7D55b910", // USDT/USD价格源
"btcUsdFeed": "0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43", // BTC/USD价格源
"auctionFactoryImpl": "0x470f867661F61a76CcC7926F20c091600d61C906" // 拍卖工厂实现合约
}配置说明:
- 核心合约: 包含拍卖工厂、NFT合约和拍卖实现合约
- 代币合约: 支持USDC、USDT、WETH、WBTC四种代币出价
- 价格预言机: 使用Chainlink官方价格源获取实时价格数据
- 代理模式: 使用UUPS代理模式,支持合约升级
项目已在Sepolia测试网上部署,以下是合约地址和Etherscan链接:
| 合约名称 | 地址 | Etherscan链接 |
|---|---|---|
| AuctionFactory (代理) | 0xaEcb00Da8656C18a16415085b49da518B0984CBB |
查看合约 |
| AuctionFactory (实现) | 0x470f867661F61a76CcC7926F20c091600d61C906 |
查看合约 |
| SingleAuction (实现) | 0x3386618aE692Cfd51d94d9b456DE198406160541 |
查看合约 |
| SimpleNFT | 0x3DB1Ef472Fd65DE888aCeBABB0A78931f6644E4f |
查看合约 |
| 代币名称 | 地址 | Etherscan链接 |
|---|---|---|
| USDC | 0x8e3A570e170fba92E50BA83510c60F897F283C40 |
查看合约 |
| USDT | 0x7381fe1C126B2B62Fa3E766F0ED079d91Def9188 |
查看合约 |
| WETH | 0x34a56f192001Ba8Ea16eb55B54B683Aa9F325C84 |
查看合约 |
| WBTC | 0x2Fc62A208B330F4673F828B7B54e661ec539E4e9 |
查看合约 |
| 价格源 | 地址 | Etherscan链接 |
|---|---|---|
| ETH/USD | 0x694AA1769357215DE4FAC081bf1f309aDC325306 |
查看合约 |
| USDC/USD | 0xA2F78ab2355fe2f984D808B5CeE7FD0A93D5270E |
查看合约 |
| USDT/USD | 0x1a81afB8146aeFfCFc5E50e8479e826E7D55b910 |
查看合约 |
| BTC/USD | 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43 |
查看合约 |
// 连接到已部署的合约
const factory = await ethers.getContractAt("AuctionFactory", "0xaEcb00Da8656C18a16415085b49da518B0984CBB");
const nft = await ethers.getContractAt("SimpleNFT", "0x3DB1Ef472Fd65DE888aCeBABB0A78931f6644E4f");
// 创建拍卖
const tx = await factory.createAuction(
nft.target,
tokenId,
3600, // 1小时
ethers.parseEther("100") // $100起始价
);# 1. 克隆项目
git clone <repository-url>
cd solidity_task_3
# 2. 安装依赖
npm install
# 3. 编译合约
npx hardhat compile
# 4. 运行测试
npx hardhat test
# 5. 启动本地网络
npx hardhat node
# 6. 部署合约 (新终端)
npx hardhat ignition deploy ./ignition/modules/CompleteAuctionSystem.js# 配置环境变量
export SEPOLIA_RPC_URL="your_sepolia_rpc_url"
export PRIVATE_KEY="your_private_key"
# 部署到Sepolia
npx hardhat ignition deploy ./ignition/modules/SepoliaDeployment.js --network sepolia// 连接到Sepolia测试网
const provider = new ethers.JsonRpcProvider("https://sepolia.infura.io/v3/YOUR_PROJECT_ID");
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
// 连接到已部署的合约
const factory = new ethers.Contract(
"0xaEcb00Da8656C18a16415085b49da518B0984CBB",
AuctionFactoryABI,
wallet
);
const nft = new ethers.Contract(
"0x3DB1Ef472Fd65DE888aCeBABB0A78931f6644E4f",
SimpleNFTABI,
wallet
);
// 创建测试NFT
const mintTx = await nft.mintNFT(wallet.address, "https://example.com/metadata/1");
await mintTx.wait();
// 创建拍卖
const createTx = await factory.createAuction(
nft.target,
0, // tokenId
3600, // 1小时
ethers.parseEther("100") // $100起始价
);
const receipt = await createTx.wait();
console.log("拍卖创建成功!");在Sepolia测试网上,你可以通过以下方式获取测试代币:
- ETH: 从 Sepolia Faucet 获取
- USDC/USDT: 使用已部署的MockERC20合约铸造
- WETH/WBTC: 使用已部署的MockERC20合约铸造
// 铸造测试代币
const usdc = new ethers.Contract(usdcAddress, MockERC20ABI, wallet);
await usdc.mint(wallet.address, ethers.parseUnits("1000", 6)); // 铸造1000 USDC// quick-test.js
const { ethers } = require("hardhat");
async function quickTest() {
// 获取签名者
const [owner, seller, bidder1, bidder2] = await ethers.getSigners();
// 部署合约
const factory = await ethers.getContractAt("AuctionFactory", "0x...");
const nft = await ethers.getContractAt("SimpleNFT", "0x...");
// 创建NFT
await nft.mintNFT(seller.address, "https://example.com/1");
// 创建拍卖
const tx = await factory.createAuction(
nft.target,
0, // tokenId
300, // 5分钟
ethers.parseEther("100") // $100起始价
);
console.log("拍卖创建成功!");
}
quickTest().catch(console.error);| 函数名 | 参数 | 返回值 | 描述 |
|---|---|---|---|
createAuction |
(address, uint256, uint256, uint256) |
address |
创建新拍卖 |
getAllAuctions |
() |
address[] |
获取所有拍卖地址 |
getAuctionByNFT |
(address, uint256) |
address |
根据NFT获取拍卖 |
getEthUsdPrice |
(uint256) |
uint256 |
获取ETH美元价格 |
getTokenUsdPrice |
(address, uint256) |
uint256 |
获取代币美元价格 |
setFeeRecipient |
(address) |
void |
设置手续费接收者 |
setEthUsdFeed |
(address) |
void |
设置ETH价格源 |
setTokenUsdFeed |
(address, address) |
void |
设置代币价格源 |
| 函数名 | 参数 | 返回值 | 描述 |
|---|---|---|---|
placeBidwithETH |
() |
void |
使用ETH出价 |
placeBidbyToken |
(address, uint256) |
void |
使用代币出价 |
endAuction |
() |
void |
结束拍卖 |
cancelAuction |
() |
void |
取消拍卖 |
getAuctionInfo |
() |
(AuctionInfo, uint256, address, address, uint256, bool, uint256) |
获取拍卖信息 |
calculateDynamicFee |
(uint256, uint256, uint256) |
uint256 |
计算动态手续费 |
| 事件名 | 参数 | 描述 |
|---|---|---|
AuctionCreated |
(address, address, uint256, address, uint256, uint256, uint256) |
拍卖创建 |
BidPlaced |
(address, uint256, address, uint256) |
出价事件 |
AuctionEnded |
(address, uint256, address, uint256) |
拍卖结束 |
AuctionCancelled |
() |
拍卖取消 |
- 重入攻击保护: 使用ReentrancyGuard防止重入攻击
- 权限控制: 使用Ownable模式控制关键函数访问
- 输入验证: 对所有输入参数进行严格验证
- 溢出保护: 使用SafeMath防止数值溢出
- 升级安全: 使用UUPS代理模式确保升级安全
项目包含完整的测试套件,覆盖所有核心功能:
# 运行所有测试
npx hardhat test
# 运行特定测试
npx hardhat test test/AuctionFactory.test.js
npx hardhat test test/SingleAuction.test.js
npx hardhat test test/SimpleNFT.test.js
# 运行升级测试
npx hardhat test test/UUPSUpgrade.test.js
# 生成测试覆盖率报告
npx hardhat coverage- Solidity: ^0.8.20
- OpenZeppelin: 安全合约库
- Chainlink: 价格预言机
- Hardhat: 开发框架
- Ethers.js: 以太坊交互库
MIT License