跳转到内容

Chainlink 价格数据

Chainlink 价格数据

本页内容正在整理中,欢迎贡献

概述

本教程展示如何在智能合约中集成 Chainlink 价格 Feed,构建一个简单的价格感知 DApp——用户存入 ETH,合约基于实时 ETH/USD 价格计算美元价值并发放对应数量的代币。

主要内容

项目功能

ETH 存款应用:
1. 用户向合约存入任意数量 ETH
2. 合约读取 Chainlink ETH/USD 价格
3. 按 $1 = 1 Token 的比例铸造代币给用户
4. 用户可随时取出 ETH(按当前价格)

安装依赖

Terminal window
npm install @chainlink/contracts
# 或 Foundry
forge install smartcontractkit/chainlink-brownie-contracts

合约实现

// SPDX-License-Identifier: MIT
pragma 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)

test/mocks/MockV3Aggregator.sol
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>
);
}

深入阅读