Chainlink 价格数据
Chainlink 价格数据
本页内容正在整理中,欢迎贡献。
概述
本教程展示如何在智能合约中集成 Chainlink 价格 Feed,构建一个简单的价格感知 DApp——用户存入 ETH,合约基于实时 ETH/USD 价格计算美元价值并发放对应数量的代币。
主要内容
项目功能
ETH 存款应用: 1. 用户向合约存入任意数量 ETH 2. 合约读取 Chainlink ETH/USD 价格 3. 按 $1 = 1 Token 的比例铸造代币给用户 4. 用户可随时取出 ETH(按当前价格)安装依赖
npm install @chainlink/contracts# 或 Foundryforge install smartcontractkit/chainlink-brownie-contracts合约实现
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";import "@openzeppelin/contracts/token/ERC20/ERC20.sol";import "@openzeppelin/contracts/access/Ownable.sol";
contract ETHVault is ERC20, Ownable { AggregatorV3Interface public immutable priceFeed;
mapping(address => uint256) public ethDeposited; uint256 public constant STALE_PRICE_THRESHOLD = 3600; // 1小时
event Deposited(address indexed user, uint256 ethAmount, uint256 usdValue, uint256 tokensIssued); event Withdrawn(address indexed user, uint256 ethAmount, uint256 tokensBurned);
error StalePriceData(); error InvalidPrice(); error InsufficientBalance();
// Sepolia ETH/USD: 0x694AA1769357215DE4FAC081bf1f309aDC325306 // 主网 ETH/USD: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 constructor(address _priceFeed, address _owner) ERC20("ETH Vault Token", "EVT") Ownable(_owner) { priceFeed = AggregatorV3Interface(_priceFeed); }
// 获取 ETH/USD 价格(带安全检查) function getETHPrice() public view returns (uint256 price, uint8 decimals_) { ( /* uint80 roundId */, int256 answer, /* uint256 startedAt */, uint256 updatedAt, /* uint80 answeredInRound */ ) = priceFeed.latestRoundData();
// 检查数据新鲜度 if (block.timestamp - updatedAt > STALE_PRICE_THRESHOLD) revert StalePriceData(); if (answer <= 0) revert InvalidPrice();
return (uint256(answer), priceFeed.decimals()); }
// ETH 换算为 USD 金额(18 位精度) function ethToUSD(uint256 ethAmount) public view returns (uint256 usdAmount) { (uint256 price, uint8 priceDecimals) = getETHPrice(); // ethAmount(18 decimals) * price(8 decimals) / 10^8 = USD(18 decimals) usdAmount = (ethAmount * price) / (10 ** priceDecimals); }
// 存入 ETH,获取 Token function deposit() external payable { require(msg.value > 0, "Must deposit ETH");
uint256 usdValue = ethToUSD(msg.value); uint256 tokensToMint = usdValue; // 1 USD = 1 Token
ethDeposited[msg.sender] += msg.value; _mint(msg.sender, tokensToMint);
emit Deposited(msg.sender, msg.value, usdValue, tokensToMint); }
// 销毁 Token,取回对应 ETH function withdraw(uint256 tokenAmount) external { require(balanceOf(msg.sender) >= tokenAmount, "Insufficient tokens");
// 计算对应的 ETH 数量 (uint256 price, uint8 priceDecimals) = getETHPrice(); // tokenAmount(18 decimals) * 10^8 / price(8 decimals) = ETH(18 decimals) uint256 ethAmount = (tokenAmount * (10 ** priceDecimals)) / price;
require(address(this).balance >= ethAmount, "Insufficient ETH in vault");
_burn(msg.sender, tokenAmount); ethDeposited[msg.sender] = ethDeposited[msg.sender] > ethAmount ? ethDeposited[msg.sender] - ethAmount : 0;
payable(msg.sender).transfer(ethAmount); emit Withdrawn(msg.sender, ethAmount, tokenAmount); }
// 查询当前 ETH 价格(用于前端显示) function getCurrentPrice() external view returns (uint256 priceUSD) { (uint256 price, uint8 priceDecimals) = getETHPrice(); // 转换为 18 位精度的 USD 价格 return price * (10 ** (18 - priceDecimals)); }}测试(使用 Mock)
pragma solidity ^0.8.20;
contract MockV3Aggregator { int256 private _price; uint8 private _decimals; uint256 private _updatedAt;
constructor(uint8 decimals_, int256 initialPrice) { _decimals = decimals_; _price = initialPrice; _updatedAt = block.timestamp; }
function latestRoundData() external view returns ( uint80, int256, uint256, uint256, uint80 ) { return (1, _price, block.timestamp, _updatedAt, 1); }
function decimals() external view returns (uint8) { return _decimals; }
// 测试用:更新价格 function updateAnswer(int256 newPrice) external { _price = newPrice; _updatedAt = block.timestamp; }}describe("ETHVault", function () { it("应该按 Chainlink 价格铸造 Token", async function () { const MockAggregator = await ethers.getContractFactory("MockV3Aggregator"); const mockFeed = await MockAggregator.deploy(8, 200000000000n); // $2000 (8 decimals)
const ETHVault = await ethers.getContractFactory("ETHVault"); const vault = await ETHVault.deploy(await mockFeed.getAddress(), owner.address);
// 存入 1 ETH await vault.connect(alice).deposit({ value: parseEther("1") });
// 应该得到 2000 Token(1 ETH × $2000/ETH = $2000 = 2000 Token) expect(await vault.balanceOf(alice.address)).to.equal(parseEther("2000")); });});前端集成
// 显示实时 ETH 价格function ETHPriceDisplay() { const { data: price } = useReadContract({ address: VAULT_ADDRESS, abi: VAULT_ABI, functionName: "getCurrentPrice", query: { refetchInterval: 30000 }, // 每 30 秒刷新 });
return ( <p>ETH 当前价格: ${price ? (Number(price) / 1e18).toFixed(2) : "..."} USD</p> );}