使用Chainlink预言机,十分钟开发一个DeFi项目

  • Chainlink
  • 更新于 2020-05-26 14:23
  • 阅读 2492

Chainlink价格参考数据合约是可以在智能合约网络中值得依赖的真实价格数据的链上参考点。这些合约由多个Chainlink节点定时更新,提供高精度,高频率,可定制化的DeFi价格参考数据,可以方便的为DeFi项目的开发提供开箱即用的稳定基础设施。

Chainlink价格参考数据合约是可以在智能合约网络中值得依赖的真实价格数据的链上参考点。这些合约由多个Chainlink节点定时更新,提供高精度,高频率,可定制化的DeFi价格参考数据,可以方便的为DeFi项目的开发提供开箱即用的稳定基础设施。本文我们会教你如何使用这些合约。除此之外,Chainlink还提供了通过获取链下数据的方式,从用户指定的API获取价格数据。我们下面就介绍一下这两种方式。

直接从API获取价格

首先我们先简单回顾一下,一般情况下我们如何使用Chainlink来获取真实世界中的价格数据。我们知道,价格是通过交易来产生的,所以最直接的方式是通过交易所提供的接口来获取某个交易所的某个加密货币的价格。但是这只是来自于一个交易所的数据,可能会有个体性的误差。有一些加密货币行情网站,他们会汇总多个交易所的数据,或者根据自己的指标来计算数据,得到一个偏离度比较小也就是更真实的数据。所以我们就采用从行情网站的接口获得数据,然后通过提交交易,将价格数据送到智能合约中。

我们选择的行情网站的cryptocompare,它提供了一些非常好用的API来提供各类交易市场上的信息。我们就以它文档上给出的一个API来作为例子:

https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD,JPY,EUR

访问这个接口,会返回一个JSON对象,提供当前时间,BTC分别相对于美元、日元、欧元的价格。

{
    "USD": 9460.99,
    "JPY": 1018295.17,
    "EUR": 8640.8
}

好的,下面我们就来编写合约来获取BTC的美元价格。

1 创建truffle项目

mkdir MyChainlinkedContract
cd MyChainlinkedContract
truffle init 

如果您还没有安装truffle suite, 可以通过npm install truffle -g来安装。

2 安装Chainlink开发库

npm install @chainlink/contracts --save

3 创建用户合约

您可以用您喜欢的编辑器工具,比如VS Code, 打开项目目录。目录结构如下:

.
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
└── truffle-config.js

我们在contracts目录中新建一个合约文件MyContract.sol,在文件中新建一个合约,继承自ChainlinkClient合约,并设置构造函数,参数分别是:

  1. _link 所使用网络环境下的LINK token 地址
  2. _oracle 所使用的oracle合约地址。如果您不知道选择什么哪个oracle,可以前往Chainlink市场market.link上选择。
  3. _specId 即jobId,用于完成规范命令序列的任务ID,同样可在Chainlink市场market.link上对应的oracle下选择。
pragma solidity ^0.6.0;

import "@chainlink/contracts/src/v0.6/ChainlinkClient.sol";

// MyContract 通过继承 Chainlinked 合约获得了创建Chainlink请求的功能
contract MyContract is ChainlinkClient {
  constructor(address _link, address _oracle, bytes32 _specId) public {
    setChainlinkToken(_link);
    setChainlinkOracle(_oracle);
    specId = _specId;
  }

    bytes32 internal specId;
}

接下来我们就可以编写创建Chainlink请求的代码

  function requestEthereumPrice(string memory _currency, uint256 _payment) public {
    requestEthereumPriceByCallback(_currency, _payment, address(this));
  }

  function requestEthereumPriceByCallback(string memory _currency, uint256 _payment, address _callback) public {
    Chainlink.Request memory req = buildChainlinkRequest(specId, _callback, this.fulfill.selector);
    req.add("get", "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD,EUR,JPY");
    string[] memory path = new string[](1);
    path[0] = _currency;
    req.addStringArray("path", path);
    sendChainlinkRequest(req, _payment);
  }

其中的主要语句就是通过buildChainlinkRequest 创建了一个Chainlink请求,该请求会发起一次LINK的转账到oracle地址,并附带请求数据。请求数据可以通过add的方法添加到请求中。请求数据可以包括:请求地址、解析路径、倍数等。

另外我们还需要定义一个回调函数来接收oracle获取到的结果,这个函数需要作为参数在构建Chainlink请求时传入到函数buildChainlinkRequest 中:

  event RequestFulfilled(
    bytes32 indexed requestId,  // User-defined ID
    bytes32 indexed price
  );

    function fulfill(bytes32 _requestId, bytes32 _price)
    public
    recordChainlinkFulfillment(_requestId)
  {
    emit RequestFulfilled(_requestId, _price);
    currentPrice = _price;
  }

这样其实一个最简单的Chainlink消费者合约就创建完成了,下面是一段完整的代码,当然你也可以在此之上添加一些其他函数,比如提取LINK、取消请求等等。

pragma solidity ^0.6.0;

import "@chainlink/contracts/src/v0.6/ChainlinkClient.sol";

contract MyContract is ChainlinkClient {

  constructor(address _link, address _oracle, bytes32 _specId) public {
    setChainlinkToken(_link);
    setChainlinkOracle(_oracle);
    specId = _specId;
  }

  bytes32 internal specId;
  bytes32 public currentPrice;

  event RequestFulfilled(
    bytes32 indexed requestId,  // User-defined ID
    bytes32 indexed price
  );

  function requestEthereumPrice(string memory _currency, uint256 _payment) public {
    requestEthereumPriceByCallback(_currency, _payment, address(this));
  }

  function requestEthereumPriceByCallback(string memory _currency, uint256 _payment, address _callback) public {
    Chainlink.Request memory req = buildChainlinkRequest(specId, _callback, this.fulfill.selector);
    req.add("get", "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD,EUR,JPY");
    string[] memory path = new string[](1);
    path[0] = _currency;
    req.addStringArray("path", path);
    sendChainlinkRequest(req, _payment);
  }

  function fulfill(bytes32 _requestId, bytes32 _price)
    public
    recordChainlinkFulfillment(_requestId)
  {
    emit RequestFulfilled(_requestId, _price);
    currentPrice = _price;
  }

}

4 将用户合约部署到Ropsten测试网络上

我们配置好truffle的config文件中的Ropsten network字段和相关的provider,添加一个migration文件,通过下面的命令,将我们的用户合约部署到Ropsten测试网络上。

truffle migrate --network ropsten

5 向合约地址转入LINK

由于发起Chainlink请求需要向oracle支付LINK作为费用,所以我们的用户合约需要拥有LINK才能成功获取数据。所以我们需要向刚刚部署好的MyContract合约转入一部分LINK。每次请求的费用可以根据所选择的oracle要求的费用来支付。

6 编写测试脚本

接下来我们就可以调用合约来获取我们想要的数据了。我们可以使用truffle给我们提供的控制台,也可以自己编写脚本文件来测试。脚本文件的编写也非常简单,我们写一个请求的文件和一个读取的文件

request.js

const MyContract = artifacts.require('MyContract')

module.exports = async callback => {
  const mc = await MyContract.deployed()
  const tx = await mc.requestEthereumPrice('USD', '1000000000000000000')
  callback(tx.tx)
}

read.js

const MyContract = artifacts.require('MyContract')

module.exports = async callback => {
  const mc = await MyContract.deployed()
  const data = await mc.currentPrice.call()
  callback(console.log(parseInt(data)))
}

通过以下命令来调用请求脚本:

npx truffle exec scripts/request.js  --network ropsten

成功之后稍等一段时间,因为需要以太坊网络对交易的确认,然后再通过以下命令读取oracle获取到的数据。

npx truffle exec scripts/read.js  --network ropsten

顺利的话,控制台上会打出当前BTC的价格9352

这样,我们就以一个非常直接的方式,通过一个我们指定的API,完成了一个在合约中获取BTC价格数据的用例。

价格参考数据Reference Data

下面我们就来讲一下Chainlink价格参考数据合约应该怎么来使用

使用价格参考数据合约和上面的直接从链下获取的方式是有很打不同的,我们先来了解一下Chainlink的价格参考数据合约。

Chainlink的价格参考数据是专门为DeFi项目设计的预言机,网址是feeds.chain.link。Chainlink的价格参考数据预言机网络极大提升了以太坊上Dapp数据的安全性和可靠性,而且大大加速了新DeFi产品成功上线的速度。Chainlink在去中心化的预言机网络中提供价格参考数据,这是一个共享资源社区,并受到用户支持。用户使用这些预言机网络的成本要低于自己传输数据的成本,而且由于预言机网络是去中心化的,安全水平也会大幅提升。更多的信息可以参考这篇博文

简单来说,Chainlink价格参考数据提供的价格是在链上可以直接访问的,不需要通过我们指定API来手动获取。

1 新建项目

我们新建一个目录就叫RefereceData,然后用上面的的方式建立一个truffle项目:

mkdir RefereceData
cd RefereceData
truffle init
npm install @chainlink/contracts --save

2 新建用户合约文件

constracts目录下新建一个ReferenceConsumer.sol文件,文件中需要引入聚合接口合约AggregatorInterface:

import "@chainlink/contracts/src/v0.4/interfaces/AggregatorInterface.sol";

3 找到我们需要的参考数据合约地址

Chainlink文档中给我们提供了非常多交易对的参考合约地址,不仅有主网的地址,还有Ropsten、Rinkeby、Kovan测试网的合约地址。

我们就选择Ropsten网络下的BTC/USD交易对的合约,地址为0x882906a758207FeA9F21e0bb7d2f24E561bd0981

4 配置参考合约地址

在合约中通过构造函数或者编写setter方法,配置好我们想要的参考合约的地址

构造函数方式:

AggregatorInterface internal ref;

constructor(address _aggregator) public {
  ref = AggregatorInterface(_aggregator);
}

setter方式:

AggregatorInterface internal ref;

function setReferenceContract(address _aggregator)
  public
  onlyOwner()
{
  ref = AggregatorInterface(_aggregator);
}

5 使用参考合约获取价格数据

AggregatorInterface接口给我们提供了5个方法供我们使用分别是:

latestAnswer() 最新的聚合结果

latestTimestamp() 最新一次聚合的时间戳

latestRound() 最新一次聚合的轮次号

getAnswer(uint256 roundId) 通过轮次号获取历史结果

getTimestamp(uint256 roundId) 通过轮次号获取历史时间戳

返回的价格结果中,所有USD参考数据合约的结果值会乘以100000000,所有ETH参考数据合约的结果值会乘以1000000000000000000

6 完整示例

我们分别就对这个几个方法做个包装就可以然后就可以获取到BTC/USD的价格啦。下面的一段完整的代码,大家可以在这个基础上,加入一些其他的业务逻辑,就可以创建一个DeFi项目啦。

pragma solidity ^0.4.24;

import "@chainlink/contracts/src/v0.4/interfaces/AggregatorInterface.sol";

contract ReferenceConsumer {
  AggregatorInterface internal ref;

  constructor(address _aggregator) public {
    ref = AggregatorInterface(_aggregator);
  }

  function getLatestAnswer() public view returns (int256) {
    return ref.latestAnswer();
  }

  function getLatestTimestamp() public view returns (uint256) {
    return ref.latestTimestamp();
  }

  function getLatestRound() public view returns (uint256) {
    return ref.getLatestRound();
  }

  function getPreviousAnswer(uint256 _back) public view returns (int256) {
    uint256 latest = ref.latestRound();
    require(_back <= latest, "Not enough history");
    return ref.getAnswer(latest - _back);
  }

  function getPreviousTimestamp(uint256 _back) public view returns (uint256) {
    uint256 latest = ref.latestRound();
    require(_back <= latest, "Not enough history");
    return ref.getTimestamp(latest - _back);
  }
}

7 编写测试脚本调用合约

在Migrations目录下新建一个文件2_referenceconsumer_migration.js ,将我们上面查到的BTC/USD的参考合约地址在部署时传入构造函数中:

const ReferenceConsumer = artifacts.require("ReferenceConsumer");

module.exports = function(deployer) {
  deployer.deploy(ReferenceConsumer, "0x882906a758207FeA9F21e0bb7d2f24E561bd0981");
};

然后部署到Ropsten网络上。

在scripts目录下新建一个测试文件,比如叫getdata.js

const ReferenceConsumer = artifacts.require('ReferenceConsumer')

module.exports = async callback => {
  const rc = await ReferenceConsumer.deployed()
  const data = await rc.getLatestAnswer()
  callback(console.log(parseInt(data)))
}

通过以下命令执行该测试脚本:

npx truffle exec scripts/getdata.js  --network ropsten

一切顺利的话就会在控制台得到920089000000的结果,这就是当前BTC的价格,即9200.89

有了Chainlink的价格参考数据合约,DeFi的开发变得非常简单了,开发者只需要关注自己的金融方面的业务逻辑即可。https://feeds.chain.link网站上列出了非常多的使用Chainlink价格参考数据的DeFi项目,他们都是开源的,我们也可以去他们的GitHub页面,去学习如何用Chainlink来设计一个DeFi项目


欢迎加入 Chainlink 开发者社区

IMG_1763.JPG

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

0 条评论

请先 登录 后评论
Chainlink
Chainlink
顶尖的智能合约去中心化预言机网络解决方案 https://chain.link/