十分钟开发一个DEFI项目

直接从 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

}

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 项目。


不懂的可以加我QQ,交流学习,共同进步

QQ:2194141941

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343