Chainlink简介
本文主要通过Chainlink来获取链上的最新价格,Chainlink的最新价格能够将您的智能合约与资产的真实市场价格联系起来的最快方式。它们使智能合约能够在一次调用中检索资产的最新价格。
通常,智能合约需要实时根据资产价格采取行动。在DeFi中尤其如此。例如,Synthetix使用价格馈送来确定其衍生品平台上的价格。AAVE等借贷平台使用价格馈送来确保抵押品的总价值。
Chainlink基本请求模型
Chainlink使用其去中心化的预言机网络将智能合约与外部数据连接起来。Chainlink API 请求由预言机 1:1 处理。
基本请求模型描述了从单个预言机源请求数据的链上架构。
Chainlink去中心化数据模型
要获得更可靠和更可靠的答案,需要汇总来自许多预言机的数据。通过链上聚合,数据从独立预言机节点的去中心化网络聚合。该架构应用于Chainlink Data Feeds,可以聚合资产价格数据等数据。
中心化数据模型描述了数据是如何聚合的,以及消费者合约如何检索这些数据。
每个数据源都由去中心化的预言机网络更新。每个预言机都会因发布数据而获得奖励。为每个提要做出贡献的预言机数量各不相同。为了进行更新,数据馈送聚合器合约必须接收来自最少数量的预言机的响应,否则将不会更新最新的答案。
可以在data.chain.link中查看相应提要的最小预言机数。
MetaMask的安装及BSC网络切换
首先我们会先判断MetaMask钱包是否安装,通过下面的代码可以实现
if (window.ethereum) { // 小狐狸钱包存在,则检测当前是否为 BSC 网络 obj.checkCurrBSCChain() } else { obj.msg = ('Non-Ethereum browser detected. You should consider trying MetaMask!https://metamask.io/download/') }
在chainlist.org上我们可以查询到Binance Smart Chain Mainnet
的ChainID是56
。通过下面的的代码可以判断当前网络是否为Binance Smart Chain Mainnet:
window.ethereum .request({ method: 'eth_chainId' // Returns the currently configured chain id, a value used in replay-protected transaction signing as introduced by EIP-155. }) .then(function (d) { obj.ChainId = Web3.utils.hexToNumber(d) console.log('当前网络ID:' + obj.ChainId) if (obj.ChainId !== 56) { obj.switchBSCChain() } }) .catch((err) => { if (err.code === 4001) { // EIP-1193 userRejectedRequest error // If this happens, the user rejected the connection request. obj.msg = 'Please connect to MetaMask.' } else { obj.msg = ('eth_chainId Error:' + err) } }) } catch (error) { obj.msg = ('checkCurrBSCChain Error:' + error) }
如果当前网络不是Binance Smart Chain Mainnet
,通过下面的代码,请求切换到Binance Smart Chain Mainnet
网络上:
window.ethereum .request({ method: 'wallet_switchEthereumChain', params: [{ chainId: Web3.utils.numberToHex(56) // Binance Smart Chain Mainnet ID }] }).catch((err) => { if (err.code === 4001) { // EIP-1193 userRejectedRequest error // If this happens, the user rejected the connection request. obj.msg = 'Please connect to MetaMask.' } else if (err.message.indexOf('wallet_addEthereumChain') !== -1) { obj.addBSCChain() } else { obj.msg = ('wallet_switchEthereumChain Error:' + err) } }) } catch (error) { obj.msg = ('wallet_switchEthereumChain Error:' + error) }
如果Binance Smart Chain Mainnet
网络不存在,可以通过下面的代码,将Binance Smart Chain Mainnet
添加到MetaMask
:
window.ethereum .request({ method: 'wallet_addEthereumChain', params: [ { chainId: Web3.utils.numberToHex(56), // 目标链ID,根据Ethereum RPC 方法,必须将链的整数 ID 指定为十六进制字符串。 chainName: 'Binance Smart Chain Mainnet', // 网络名称 nativeCurrency: { // name使用、symbol和decimals字段 描述链的主网代币 name: 'BNB', symbol: 'BNB', decimals: 18 }, rpcUrls: ['https://bsc-dataseed.binance.org'], // 指定一个或多个指向可用于与链通信的 RPC 端点的 URL 节点 blockExplorerUrls: ['https://www.bscscan.com'] // 一个或多个指向链的区块浏览器网站的 URL } ] }) .then(obj.handleAccountsChanged) } catch (ee) { obj.msg = ('wallet_addEthereumChain Error:' + ee) }
更多方法可以参考MetaMask RPC API
获取最新价格的代码
本文中主要介绍通过Nodejs + Vue在Binance Smart Chain Mainnet主网上获取合约的最新价格。
要使用价格数据,我们的智能合约应该引用AggregatorV3Interface,它定义了Data Feeds实现的外部功能。
obj.web3 = new Web3(window.ethereum) // 合约的ABI接口 var aggregatorV3InterfaceABI = [{'inputs': [], 'name': 'decimals', 'outputs': [{'internalType': 'uint8', 'name': '', 'type': 'uint8'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'description', 'outputs': [{'internalType': 'string', 'name': '', 'type': 'string'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'uint80', 'name': '_roundId', 'type': 'uint80'}], 'name': 'getRoundData', 'outputs': [{'internalType': 'uint80', 'name': 'roundId', 'type': 'uint80'}, {'internalType': 'int256', 'name': 'answer', 'type': 'int256'}, {'internalType': 'uint256', 'name': 'startedAt', 'type': 'uint256'}, {'internalType': 'uint256', 'name': 'updatedAt', 'type': 'uint256'}, {'internalType': 'uint80', 'name': 'answeredInRound', 'type': 'uint80'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'latestRoundData', 'outputs': [{'internalType': 'uint80', 'name': 'roundId', 'type': 'uint80'}, {'internalType': 'int256', 'name': 'answer', 'type': 'int256'}, {'internalType': 'uint256', 'name': 'startedAt', 'type': 'uint256'}, {'internalType': 'uint256', 'name': 'updatedAt', 'type': 'uint256'}, {'internalType': 'uint80', 'name': 'answeredInRound', 'type': 'uint80'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'version', 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}] var priceFeed = new obj.web3.eth.Contract(aggregatorV3InterfaceABI, addr, { from: obj.CurrentAccount // default from address }) priceFeed.methods.decimals().call() .then((decimals) => { // 获取价格精度 // Do something with roundData console.log('decimals', decimals) // 获取最新一轮的数据 priceFeed.methods.latestRoundData().call() .then((roundData) => { // Do something with roundData console.log('Latest Round Data', roundData) // console.log(this.strtodec(roundData.answer, 18 - decimals)) obj.msg = '当前价格:' + obj.web3.utils.fromWei(this.strtodec(roundData.answer, 18 - decimals), 'ether') }) })
其他链上合约地址可以参考这里
完整代码如下:
$ cat ./src/components/VLWX.vue <template> <div> <span v-html="msg"></span> <p class="greeting">{{ greeting }}</p> <button v-on:click="getBSCPrice('0x9ef1B8c0E4F7dc8bF5719Ea496883DC6401d5b2e')">读取 ETH/USD 最新价格</button> <button v-on:click="getBSCPrice('0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE')">读取 BNB/USD 最新价格</button> </div> </template> <script> import Web3 from 'web3' export default { name: 'VLWX.com', data () { return { msg: '', greeting: 'Hello World! Welcome to mshk.top', GanacheUrl: 'localhost:8545', CurrentAccount: null, ChainId: null } }, methods: { // 请求当前帐号信息 handleAccountsChanged () { var obj = this try { // Request account access if needed // window.ethereum.enable() window.ethereum .request({ method: 'eth_requestAccounts' }) .then(function (ac) { obj.CurrentAccount = ac[0] console.log('handleAccountsChanged:' + obj.CurrentAccount) }) .catch((err) => { if (err.code === 4001) { // EIP-1193 userRejectedRequest error // If this happens, the user rejected the connection request. obj.msg = ('Please connect to MetaMask.') } else { obj.msg = ('handleAccountsChanged Error:' + err) } }) } catch (error) { // User denied account access... } }, // 判断当前网络是否为 Binance Smart Chain Mainnet checkCurrBSCChain () { var obj = this try { window.ethereum .request({ method: 'eth_chainId' // Returns the currently configured chain id, a value used in replay-protected transaction signing as introduced by EIP-155. }) .then(function (d) { obj.ChainId = Web3.utils.hexToNumber(d) console.log('当前网络ID:' + obj.ChainId) if (obj.ChainId !== 56) { obj.switchBSCChain() } else { obj.handleAccountsChanged() } }) .catch((err) => { if (err.code === 4001) { // EIP-1193 userRejectedRequest error // If this happens, the user rejected the connection request. obj.msg = 'Please connect to MetaMask.' } else { obj.msg = ('eth_chainId Error:' + err) } }) } catch (error) { obj.msg = ('checkCurrBSCChain Error:' + error) } }, // 添加 Binance Smart Chain Mainnet 到小狐狸钱包,更多网络添加可以参考 https://chainlist.org/ addBSCChain () { var obj = this try { window.ethereum .request({ method: 'wallet_addEthereumChain', params: [ { chainId: Web3.utils.numberToHex(56), // 目标链ID,根据Ethereum RPC 方法,必须将链的整数 ID 指定为十六进制字符串。 chainName: 'Binance Smart Chain Mainnet', // 网络名称 nativeCurrency: { // name使用、symbol和decimals字段 描述链的主网代币 name: 'BNB', symbol: 'BNB', decimals: 18 }, rpcUrls: ['https://bsc-dataseed.binance.org'], // 指定一个或多个指向可用于与链通信的 RPC 端点的 URL 节点 blockExplorerUrls: ['https://www.bscscan.com'] // 一个或多个指向链的区块浏览器网站的 URL } ] }) .then(obj.handleAccountsChanged) } catch (ee) { obj.msg = ('wallet_addEthereumChain Error:' + ee) } }, // 切换到 Binance Smart Chain Mainnet switchBSCChain () { var obj = this try { console.log(111) window.ethereum .request({ method: 'wallet_switchEthereumChain', params: [{ chainId: Web3.utils.numberToHex(56) // Binance Smart Chain Mainnet ID }] }).catch((err) => { if (err.code === 4001) { // EIP-1193 userRejectedRequest error // If this happens, the user rejected the connection request. obj.msg = 'Please connect to MetaMask.' } else if (err.message.indexOf('wallet_addEthereumChain') !== -1) { obj.addBSCChain() } else { obj.msg = ('wallet_switchEthereumChain Error:' + err) } }) } catch (error) { obj.msg = ('wallet_switchEthereumChain Error:' + error) } }, connectBSCChain () { // 连接到 Binance Smart Chain Mainnet var obj = this if (window.ethereum) { // 当前钱切换用户时的事件 window.ethereum.on('accountsChanged', (accounts) => { // Handle the new accounts, or lack thereof. // "accounts" will always be obj.CurrentAccount = accounts obj.msg = ('AccountsChanged:' + this.CurrentAccount) }) window.ethereum.on('chainChanged', (chainId) => { // Handle the new chain. // Correctly handling chain changes can be complicated. // We recommend reloading the page unless you have good reason not to. obj.ChainId = chainId }) obj.checkCurrBSCChain() } else { obj.msg = ('Non-Ethereum browser detected. You should consider trying MetaMask!https://metamask.io/download/') } }, getBSCPrice (addr) { var obj = this obj.web3 = new Web3(window.ethereum) var aggregatorV3InterfaceABI = [{'inputs': [], 'name': 'decimals', 'outputs': [{'internalType': 'uint8', 'name': '', 'type': 'uint8'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'description', 'outputs': [{'internalType': 'string', 'name': '', 'type': 'string'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'uint80', 'name': '_roundId', 'type': 'uint80'}], 'name': 'getRoundData', 'outputs': [{'internalType': 'uint80', 'name': 'roundId', 'type': 'uint80'}, {'internalType': 'int256', 'name': 'answer', 'type': 'int256'}, {'internalType': 'uint256', 'name': 'startedAt', 'type': 'uint256'}, {'internalType': 'uint256', 'name': 'updatedAt', 'type': 'uint256'}, {'internalType': 'uint80', 'name': 'answeredInRound', 'type': 'uint80'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'latestRoundData', 'outputs': [{'internalType': 'uint80', 'name': 'roundId', 'type': 'uint80'}, {'internalType': 'int256', 'name': 'answer', 'type': 'int256'}, {'internalType': 'uint256', 'name': 'startedAt', 'type': 'uint256'}, {'internalType': 'uint256', 'name': 'updatedAt', 'type': 'uint256'}, {'internalType': 'uint80', 'name': 'answeredInRound', 'type': 'uint80'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'version', 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}] var priceFeed = new obj.web3.eth.Contract(aggregatorV3InterfaceABI, addr, { from: obj.CurrentAccount // default from address // gasPrice: '30000000000000' // 用于此交易的以 wei 为单位的 gas 价格 }) // var priceFeed = new obj.web3.eth.Contract(aggregatorV3InterfaceABI, addr) priceFeed.methods.decimals().call() .then((decimals) => { // Do something with roundData console.log('decimals', decimals) // 获取最新一轮的数据 priceFeed.methods.latestRoundData().call() .then((roundData) => { // Do something with roundData console.log('Latest Round Data', roundData) // console.log(this.strtodec(roundData.answer, 18 - decimals)) obj.msg = '当前价格:' + obj.web3.utils.fromWei(this.strtodec(roundData.answer, 18 - decimals), 'ether') }) }) }, strtodec (amount, dec) { var stringf = '' for (var i = 0; i < dec; i++) { stringf = stringf + '0' } return amount + stringf } }, mounted () { this.connectBSCChain() } } </script> <style> .greeting { color: red; font-weight: bold; } </style>
开发环境搭建
1、安装Vue 和 Vue CLI
# 最新稳定版 $ npm install vue added 21 packages in 6s $ npm i -g @vue/cli-init npm WARN deprecated har-validator@5.1.5: this library is no longer supported npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142 npm WARN deprecated vue-cli@2.9.6: This package has been deprecated in favour of @vue/cli npm WARN deprecated coffee-script@1.12.7: CoffeeScript on NPM has moved to "coffeescript" (no hyphen) added 259 packages in 16s
2、初始化&运行项目
代码已上传到GitHub,可以通过下面的命令获取最新代码
$ git clone https://github.com/idoall/vue-node-bscchain-getprice.git $ cd vue-node-bscchain-getprice $ ll total 2552 -rw-r--r-- 1 lion staff 79 May 23 03:58 README.md drwxr-xr-x 10 lion staff 320 May 23 01:58 build drwxr-xr-x 5 lion staff 160 May 23 01:58 config -rw-r--r-- 1 lion staff 269 May 23 01:58 index.html -rw-r--r-- 1 lion staff 1293351 May 23 02:00 package-lock.json -rw-r--r-- 1 lion staff 2195 May 23 01:58 package.json drwxr-xr-x 7 lion staff 224 May 23 01:58 src drwxr-xr-x 3 lion staff 96 May 23 02:00 static $ npm install npm WARN deprecated source-map-url@0.4.1: See https://github.com/lydell/source-map-url#deprecated npm WARN deprecated mkdirp-promise@5.0.1: This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that. npm WARN deprecated flatten@1.0.3: flatten is deprecated in favor of utility frameworks such as lodash. npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated npm WARN deprecated har-validator@5.1.5: this library is no longer supported npm WARN deprecated eslint-loader@1.9.0: This loader has been deprecated. Please use eslint-webpack-plugin npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated npm WARN deprecated browserslist@1.7.7: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools. npm WARN deprecated browserslist@1.7.7: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools. npm WARN deprecated browserslist@1.7.7: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools. npm WARN deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated npm WARN deprecated browserslist@2.11.3: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools. npm WARN deprecated circular-json@0.3.3: CircularJSON is in maintenance only, flatted is its successor. npm WARN deprecated html-webpack-plugin@2.30.1: out of support npm WARN deprecated fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2. npm WARN deprecated chokidar@2.1.8: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies npm WARN deprecated chokidar@2.1.8: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies npm WARN deprecated fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2. npm WARN deprecated querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. npm WARN deprecated extract-text-webpack-plugin@3.0.2: Deprecated. Please use https://github.com/webpack-contrib/mini-css-extract-plugin npm WARN deprecated ethereumjs-tx@2.1.2: New package name format for new versions: @ethereumjs/tx. Please update. npm WARN deprecated multicodec@1.0.4: This module has been superseded by the multiformats module npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. npm WARN deprecated babel-eslint@8.2.6: babel-eslint is now @babel/eslint-parser. This package will no longer receive updates. npm WARN deprecated uuid@3.3.2: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142 npm WARN deprecated multibase@0.6.1: This module has been superseded by the multiformats module npm WARN deprecated multibase@0.7.0: This module has been superseded by the multiformats module npm WARN deprecated multicodec@0.5.7: This module has been superseded by the multiformats module npm WARN deprecated bfj-node4@5.3.1: Switch to the `bfj` package for fixes and new features! npm WARN deprecated uglify-es@3.3.9: support for ECMAScript is superseded by `uglify-js` as of v3.13.0 npm WARN deprecated svgo@0.7.2: This SVGO version is no longer supported. Upgrade to v2.x.x. npm WARN deprecated ethereumjs-common@1.5.2: New package name format for new versions: @ethereumjs/common. Please update. npm WARN deprecated svgo@1.3.2: This SVGO version is no longer supported. Upgrade to v2.x.x. npm WARN deprecated cids@0.7.5: This module has been superseded by the multiformats module npm WARN deprecated core-js@2.6.12: core-js@ vue-web@1.0.0 dev > webpack-dev-server --inline --progress --config build/webpack.dev.conf.js (node:44832) [DEP0111] DeprecationWarning: Access to process.binding('http_parser') is deprecated. (Use `node --trace-deprecation ...` to show where the warning was created) 13% building modules 29/31 modules 2 active ...ue-node-bscchain-getprice/src/App.vue{ parser: "babylon" } is deprecated; we now treat it as { parser: "babel" }. 95% emitting DONE Compiled successfully in 5838ms 4:01:15 AM I Your application is running here: http://localhost:8080
3、查看效果
打开浏览器,输入http://localhost:8080如果能看到下面的效果,说明你也操作成功了
参考链接
Ethereum Provider API
Data Feeds API Reference
Binance Smart Chain Price Feeds from Chainlink