Vue+NodeJs在线获取BSC链上代币最新价格

Chainlink简介

本文主要通过Chainlink来获取链上的最新价格,Chainlink的最新价格能够将您的智能合约与资产的真实市场价格联系起来的最快方式。它们使智能合约能够在一次调用中检索资产的最新价格。
通常,智能合约需要实时根据资产价格采取行动。在DeFi中尤其如此。例如,Synthetix使用价格馈送来确定其衍生品平台上的价格。AAVE等借贷平台使用价格馈送来确保抵押品的总价值。

Chainlink基本请求模型

Chainlink使用其去中心化的预言机网络将智能合约与外部数据连接起来。Chainlink API 请求由预言机 1:1 处理。
基本请求模型描述了从单个预言机源请求数据的链上架构。
Vue+NodeJs在线获取BSC链上代币最新价格

Chainlink去中心化数据模型

要获得更可靠和更可靠的答案,需要汇总来自许多预言机的数据。通过链上聚合,数据从独立预言机节点的去中心化网络聚合。该架构应用于Chainlink Data Feeds,可以聚合资产价格数据等数据。
中心化数据模型描述了数据是如何聚合的,以及消费者合约如何检索这些数据。
Vue+NodeJs在线获取BSC链上代币最新价格
每个数据源都由去中心化的预言机网络更新。每个预言机都会因发布数据而获得奖励。为每个提要做出贡献的预言机数量各不相同。为了进行更新,数据馈送聚合器合约必须接收来自最少数量的预言机的响应,否则将不会更新最新的答案。
可以在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如果能看到下面的效果,说明你也操作成功了
Vue+NodeJs在线获取BSC链上代币最新价格
参考链接
Ethereum Provider API
Data Feeds API Reference
Binance Smart Chain Price Feeds from Chainlink

给TA打赏
共{{data.count}}人
人已打赏
区块链

如何使用ERC20代币实现买、卖功能并完成Dapp部署

2022-9-9 0:14:36

技术文档

为iOS设计类似原生的Progressive Web Apps应用程序

2021-8-18 1:51:27

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
有新私信 私信列表
搜索