Chainlink:Data Feeds 获取 Token 价格实战

ChainlinkDataFeeds是区块链开发者连接智能合约与现实世界数据的桥梁,提供去中心化、可靠的外部数据源,广泛应用于去中心化金融(DeFi)、NFT、保险等领域。本文将通过实际代码示例,带你一步步实现ChainlinkDataFeeds在以太坊上的集成

Chainlink Data Feeds 是区块链开发者连接智能合约与现实世界数据的桥梁,提供去中心化、可靠的外部数据源,广泛应用于去中心化金融(DeFi)、NFT、保险等领域。本文将通过实际代码示例,带你一步步实现 Chainlink Data Feeds 在以太坊上的集成,重点展示如何在智能合约中获取最新的 ETH/USD 价格数据,并分享开发中的注意事项和最佳实践

Data Feeds 2.jpg

1.  什么是 Chainlink Data Feeds?

Chainlink Data Feeds 是一种去中心化的预言机服务,通过多个独立节点从多个优质数据源(例如 Coinbase、Binance 等交易所)聚合数据,提供可靠的链下数据给智能合约。它的核心优势包括:

  • 去中心化:由多个节点操作,避免单点故障
  • 高可靠性:数据经过多层聚合,减少偏差和异常值影响
  • 广泛支持:支持多种区块链(如 evm 链 ,solana 链等)和数据类型(价格、天气、体育赛事等)
  • 透明性:数据可通过链上合约和第三方接口验证

2.  Chainlink Data Feeds 工作原理

Chainlink Data Feeds 的核心目标是为智能合约提供准确、去中心化的外部数据。其工作原理可以分为链下和链上两个阶段:

  1. 链下数据聚合

    1. 节点网络:Chainlink 维护一个由多个独立节点组成的去中心化网络(例如,ETH/USD 价格 Feed 可能由 21 个节点提供数据)
    2. 数据采集:每个节点从多个链下数据源(如 Binance、Coinbase 等交易所)获取数据(例如 ETH/USD 价格)
    3. 数据提交:节点将采集的数据提交到链上的聚合器合约(Aggregator Contract)。提交频率取决于:
      1. 价格偏差:当价格变化超过阈值(如 0.5%)时触发更新
      2. 心跳时间:固定时间间隔(如每小时一次)触发更新
    4. 聚合算法:聚合器合约通过中位数或其他算法对节点提交的数据进行聚合,减少异常值或恶意数据的风险
  2. 链上数据存储与访问

    1. 聚合器合约:聚合后的数据(如 ETH/USD 价格、时间戳等)存储在链上的聚合器合约中
    2. 代理合约(Proxy Contract):Chainlink 部署一个代理合约作为入口,智能合约通过代理地址访问数据。代理合约支持升级,确保即使底层聚合器合约更新,调用地址保持不变
    3. 接口标准:代理合约实现 AggregatorV3Interface,提供标准方法(如 latestRoundData)供智能合约读取数据

3.  开发环境准备

在开始之前,你需要准备以下工具和环境:

  • Web3 钱包:如 MetaMask,用于部署合约和支付 gas 费用。确保你的钱包有足够的 Sepolia 测试网 ETH(可通过 Sepolia faucet 获取)
  • 开发工具:推荐使用 Remix IDE(在线)或 Hardhat/Foundry(本地开发)
  • Solidity 基础:熟悉 Solidity 智能合约开发
  • Chainlink 合约库:通过 npm 安装 @chainlink/contracts 或在 Remix 中直接导入

4.  实现 ETH/USD 价格获取

我们将编写一个简单的 Solidity 智能合约,通过 Chainlink Data Feeds 获取最新的 ETH/USD 价格,并将其部署在链上。以下是完整步骤:

步骤 1:获取 Data Feed 合约地址

​ Chainlink 为不同区块链网络提供预部署的 Data Feed 合约地址。你可以在 Chainlink 文档: Price Feed Contract Addresses 找到对应网络的地址。对于 Arbitrum Sepolia 测试网,ETH/USD 价格 Feed 的代理地址是:

0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165

记录这个地址,我们将在智能合约中使用它

步骤 2:编写智能合约

以下是一个简单的 Solidity 合约示例,用于获取最新的 ETH/USD 价格:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";

contract DataConsumerV3 {
    AggregatorV3Interface internal dataFeed;

    /**
     * Network:  Arbitrum Sepolia
     * Aggregator: ETH/USD
     * Address: 0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165
     */
    constructor() {
        dataFeed = AggregatorV3Interface(
            0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165
        );
    }

    /**
     * 获取价格,取最后一个值
     */
    function getChainlinkDataFeedLatestAnswer() public view returns (int) {
        // 这里我们只取一个返回的参数
        (
            ,
            /* uint80 roundId */ int256 answer /*uint256 startedAt*/ /*uint256 updatedAt*/ /*uint80 answeredInRound*/,
            ,
            ,

        ) = dataFeed.latestRoundData();
        return answer;
    }

    // 获取价格的小数位数
    function getDecimals() public view returns (uint8) {
        return dataFeed.decimals();
    }
}

代码说明:

  • 导入接口:AggregatorV3Interface 是 Chainlink 提供的标准接口,定义了与 Data Feed 交互的函数
  • 构造函数:初始化时传入 Arbitrum Sepolia 测试网的 ETH/USD 代理地址
  • getChainlinkDataFeedLatestAnswer:调用 latestRoundData() 获取最新价格数据,返回值为整数(带小数位)
  • getDecimals:获取价格数据的小数位数,默认 8 位

步骤 3:部署智能合约

  1. 打开 Remix IDE:访问 Remix Remix

  2. 创建新文件:将上述代码保存为 DataConsumerV3.sol

  3. 导入 Chainlink 库:Remix 会自动从 GitHub 拉取 @chainlink/contracts

  4. 编译合约:选择 Solidity 编译器版本(≥0.8.7),EVM 版本选择 cankun,并编译

  5. 部署到 Sepolia 测试网

    • 在 Remix 的 “Deploy & Run Transactions” 面板,选择 “Injected Provider - MetaMask” 环境

    • 连接 MetaMask,切换到  Arbitrum Sepolia 测试网

    • 点击 “Deploy” 部署合约,支付少量 gas 费用

deploy.jpg

步骤 4:验证和测试

部署后,你可以通过以下方式验证结果:

  • 检查价格:在 Remix 中调用 getChainlinkDataFeedLatestAnswer,确保返回的价格合理
  • 查看小数位:调用 getDecimals,确认返回值为 8
  • 链上验证:通过 Arbitrum Sepolia 查看合约的交互记录
  • 监控数据更新:Chainlink Data Feeds 根据价格偏差或心跳时间(heartbeat)更新数据。你可以检查 latestRoundData 的 timeStamp 字段,确认数据是否新鲜

get-value.jpg 可以看到,返回的价格是  261625320000 ,小数位是  8 , 261625320000 除以 10 的八次方,等于  2616.2532 , 结果正确!

5.  案例:简易借贷协议

我们来实现一个简易借贷系统,用户可以存入 ETH 作为抵押品,并根据实时 ETH/USD 价格借出 USDC。这个案例将帮助你理解 Chainlink Data Feeds 在 DeFi 场景中的实际应用:

步骤 1:编写一个 USDC 合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract USDC is ERC20 {
    constructor(uint256 initialSupply) ERC20("USD Coin", "USDC") {
        _mint(msg.sender, initialSupply);
    }

    function decimals() public view virtual override returns (uint8) {
        return 6;
    }
}

步骤 2:  编写一个借贷合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";

contract LendingProtocol {
    IERC20 public USDC;
    AggregatorV3Interface internal priceFeed;
    // 用户的 ETH 抵押余额
    mapping(address => uint256) public ethCollateral;
    // 用户的 USDC 借出余额
    mapping(address => uint256) public usdcBorrowed;
    // 贷款价值比 50%
    uint256 public constant LTV_RATIO = 50;
    // 模拟 USDC 的 6 位小数
    uint256 public constant USDC_DECIMALS = 6;
    // ETH 的 18 位小数
    uint256 public constant ETH_DECIMALS = 18;
    // Chainlink 价格的 8 位小数
    uint256 public constant PRICE_DECIMALS = 8;

    constructor(address usdcAddress) {
        // Arbitrum Sepolia  ETH/USD
        priceFeed = AggregatorV3Interface(
            0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165
        );
        // 初始化 USDC 合约
        USDC = IERC20(usdcAddress);
    }

    // 存入 ETH 作为抵押品
    function depositCollateral() external payable {
        require(msg.value > 0, "Must deposit ETH");
        ethCollateral[msg.sender] += msg.value;
    }

    // 获取最新的 ETH/USD 价格
    function getLatestPrice() internal view returns (uint256) {
        (, int256 price, , , ) = priceFeed.latestRoundData();
        require(price > 0, "Invalid price");
        return uint256(price);
    }

    // 计算用户抵押品价值,算出值多少个U
    function getCollateralValue(address user) public view returns (uint256) {
        uint256 ethAmount = ethCollateral[user];
        uint256 price = getLatestPrice();
        // 计算公式:(ETH 数量 * 价格) / 10^ETH_DECIMALS * 10^USDC_DECIMALS / 10^PRICE_DECIMALS
        return
            (ethAmount * price * 10 ** USDC_DECIMALS) /
            (10 ** ETH_DECIMALS * 10 ** PRICE_DECIMALS);
    }

    // 获取用户最大可借 USDC 金额
    function getMaxBorrowAmount(address user) public view returns (uint256) {
        uint256 collateralValue = getCollateralValue(user);
        // 最大可借 = 抵押品价值 * 50%
        uint256 maxBorrow = (collateralValue * LTV_RATIO) / 100;
        uint256 alreadyBorrowed = usdcBorrowed[user];
        // 剩余可借金额
        if (maxBorrow > alreadyBorrowed) {
            return maxBorrow - alreadyBorrowed;
        }
        return 0;
    }

    // 借出 USDC
    function borrowUSDC(uint256 amount) external {
        require(amount > 0, "Borrow amount must be greater than 0");
        require(
            USDC.balanceOf(address(this)) >= amount,
            "Insufficient USDC in contract"
        );
        uint256 maxBorrow = getMaxBorrowAmount(msg.sender);
        require(amount <= maxBorrow, "Exceeds max borrow amount");
        usdcBorrowed[msg.sender] += amount;
        bool success = USDC.transfer(msg.sender, amount);
        require(success, "USDC transfer failed");
    }

    // 查询用户状态
    function getUserStatus(
        address user
    )
        external
        view
        returns (
            uint256 collateral,
            uint256 collateralValue,
            uint256 borrowed,
            uint256 maxBorrow
        )
    {
        // ETH 余额
        collateral = ethCollateral[user];
        // 抵押品价值
        collateralValue = getCollateralValue(user);
        // 已借 USDC
        borrowed = usdcBorrowed[user];
        // 最大可借 USDC
        maxBorrow = getMaxBorrowAmount(user);
    }
}

步骤 3:先部署 USDC 合约,再拿到 USDC 合约去部署借贷合约: 

deploy-usdc.jpg

deploy-LendingProtocol.jpg

  步骤 4: 转入 USDC 到借贷合约:

send-usdc.jpg

  步骤 5: 存入 ETH 抵押品:

depositCollateral.jpg

  步骤 6: 查看最大的可借贷的 USDC 的数量: 

getMaxBorrowAmount.jpg

  步骤 7: 借出 USDC :  

borrowUSDC.jpg

  步骤 8:  再次查看可借用的最大 USDC 数量,以验证是否正确  :

getMaxBorrowAmount-2.jpg

  步骤 9:  查看用户状态:   

getUserStatus.jpg

到这里,简易的借贷协议大功告成,我们用到了 chainlink Data Feeds 来获取链上的价格,以此来算出借贷的 USDC 数量,如果你完成了这个小案例,那么恭喜你,真棒!给自己一点鼓励吧

6. 总结

Chainlink Data Feeds 为智能合约提供了安全、可靠的链下数据访问方式。通过本教程,你了解了什么是 Chainlink Data Feeds,以及 Chainlink Data Feeds 工作原理。 还学会了如何在 Arbitrum Sepolia 测试网上部署一个简单的价格获取合约,也学会如果使用获取到的价格实现一个简单的借贷和合约。今天的分享到这里就结束了,如果你对 Chainlink 更多的内容感兴趣,那么请持续关注我,我是红烧 6,下一期不见不散,拜拜!

​ 官方网站: chain.link

代码仓库: chainlink-learn

​ ​

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
BraisedSix
BraisedSix
0x6100...b2d4
一个热爱web3的篮球爱好者