在今天將會透過 Lession 15 - Build an Oracle - Part 2 學習關於前端與 Oracle Contract 互動的部份
這邊會先實作 Javascript 讀取 Binance API 的 ETH 價格並與 Oracle Contract 互動的元件。
建立步驟如下
備註:
透過以下語法可以讀取 Oracle Contract ABI 資訊
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json');
這個檔案讀出的是 Oracle Contract 的 ABI 資訊,比如 setLatestEthPrice function 的 signature。
而要把 Oracle Contract 實體化則需要透過 web3.eth.Contract 語法如下:
const myContract = new web3js.eth.Contract(myContractJSON.abi, myContractJSON.networks[networkId].address);
要注意得是,這邊的 networkId 是用來區別 Contract 所在發佈的鏈
舉例來說: 假設是發佈到 Extdev ,則 networkId 會是 9545242630824
然而每次要去手動更改 networkId 實在太不方便
比較建議的方式是使用語法 web3.eth.net.getId() 動態讀取 networkId 如下:
const networkId = await web3js.eth.net.getId()
const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []
// Start here
async function getOracleContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}
接下來要撰寫前端監聽 event 的邏輯
首先來看前端如何監聽 event
Oracle Contract 會發起一個 event 。在 Oracle Contract 被呼叫之前,前端 app 必須要先做監聽 event 的動作
以下是 Oracle Contract 發起 GetLatestEthPriceEvent 的邏輯
function getLatestEthPrice() public returns (uint256) {
randNonce++;
uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
pendingRequests[id] = true;
emit GetLatestEthPriceEvent(msg.sender, id);
return id;
}
而當 Oracle Contract 發起 GetLatestEthPriceEvent 時,
前端的 app 接收到該 event 之後應該把該 event 放入 pendingRequests 陣列
如下:
myContract.events.EventName(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
// Do something
})
上面範例只已針對 EventName 做監聽。要做更複雜的邏輯,可以使用 filter 如下
myContract.events.EventName({ filter: { myParam: 1 }}, async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
// Do something
})
const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []
async function getOracleContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}
// Start here
async function filterEvents (oracleContract, web3js) {
oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
await addRequestToQueue(event)
})
oracleContract.events.SetLatestEthPriceEvent(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
// Do something
})
}
目前前端 app 監聽 GetLatestEthPriceEvent 事件後,會執行 addRequestToQueue
addRequestToQueue 的執行邏輯如下
event TransferTokens(address from, address to, uint256 amount)
實作語法會類似以下
async function parseEvent (event) {
const from = event.returnValues.from
const to = event.returnValues.to
const amount = event.returnValues.amount
}
const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []
async function getOracleContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}
async function filterEvents (oracleContract, web3js) {
oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
await addRequestToQueue(event)
})
oracleContract.events.SetLatestEthPriceEvent(async (err, event) => {
if (err) console.error('Error on event', err)
// Do something
})
}
async function addRequestToQueue (event) {
const callerAddress = event.returnValues.callerAddress
const id = event.returnValues.id
pendingRequests.push({ callerAddress, id })
}
// Start here
async function processQueue (oracleContract, ownerAddress) {
let processedRequests = 0
while (pendingRequests.length > 0 && processedRequests < CHUNK_SIZE) {
}
}
處理邏輯如下
const req = pendingRequests.shift()
const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []
async function getOracleContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}
async function filterEvents (oracleContract, web3js) {
oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
await addRequestToQueue(event)
})
oracleContract.events.SetLatestEthPriceEvent(async (err, event) => {
if (err) console.error('Error on event', err)
// Do something
})
}
async function addRequestToQueue (event) {
const callerAddress = event.returnValues.callerAddress
const id = event.returnValues.id
pendingRequests.push({ callerAddress, id })
}
async function processQueue (oracleContract, ownerAddress) {
let processedRequests = 0
while (pendingRequests.length > 0 && processedRequests < CHUNK_SIZE) {
// Start here
const req = pendingRequests.shift()
await processRequest(oracleContract, ownerAddress, req.id, req.callerAddress)
processedRequests++
}
}
由於有時候 fetch ETH 價格的 api 也許會 fail
所以必須要在 fetch fail 時執行 retry 來防止需要整個流程重新執行
然而如果一直 retry 會造成無限回圈
因此需要設定一個 retry 上限值
const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []
async function getOracleContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}
async function filterEvents (oracleContract, web3js) {
oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
await addRequestToQueue(event)
})
oracleContract.events.SetLatestEthPriceEvent(async (err, event) => {
if (err) console.error('Error on event', err)
// Do something
})
}
async function addRequestToQueue (event) {
const callerAddress = event.returnValues.callerAddress
const id = event.returnValues.id
pendingRequests.push({ callerAddress, id })
}
async function processQueue (oracleContract, ownerAddress) {
let processedRequests = 0
while (pendingRequests.length > 0 && processedRequests < CHUNK_SIZE) {
const req = pendingRequests.shift()
await processRequest(oracleContract, ownerAddress, req.id, req.callerAddress)
processedRequests++
}
}
// Start here
async function processRequest (oracleContract, ownerAddress, id, callerAddress) {
let retries = 0
while (retries < MAX_RETRIES) {
try {
} catch (error) {
}
}
}
const ethPrice = await retrieveLatestEthPrice()
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, ethPrice, id)
const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []
async function getOracleContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}
async function filterEvents (oracleContract, web3js) {
oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
await addRequestToQueue(event)
})
oracleContract.events.SetLatestEthPriceEvent(async (err, event) => {
if (err) console.error('Error on event', err)
// Do something
})
}
async function addRequestToQueue (event) {
const callerAddress = event.returnValues.callerAddress
const id = event.returnValues.id
pendingRequests.push({ callerAddress, id })
}
async function processQueue (oracleContract, ownerAddress) {
let processedRequests = 0
while (pendingRequests.length > 0 && processedRequests < CHUNK_SIZE) {
const req = pendingRequests.shift()
await processRequest(oracleContract, ownerAddress, req.id, req.callerAddress)
processedRequests++
}
}
async function processRequest (oracleContract, ownerAddress, id, callerAddress) {
let retries = 0
while (retries < MAX_RETRIES) {
try {
// Start here
const ethPrice = await retrieveLatestEthPrice()
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, ethPrice, id)
return
} catch (error) {
}
}
}
const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []
async function getOracleContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}
async function filterEvents (oracleContract, web3js) {
oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
await addRequestToQueue(event)
})
oracleContract.events.SetLatestEthPriceEvent(async (err, event) => {
if (err) console.error('Error on event', err)
// Do something
})
}
async function addRequestToQueue (event) {
const callerAddress = event.returnValues.callerAddress
const id = event.returnValues.id
pendingRequests.push({ callerAddress, id })
}
async function processQueue (oracleContract, ownerAddress) {
let processedRequests = 0
while (pendingRequests.length > 0 && processedRequests < CHUNK_SIZE) {
const req = pendingRequests.shift()
await processRequest(oracleContract, ownerAddress, req.id, req.callerAddress)
processedRequests++
}
}
async function processRequest (oracleContract, ownerAddress, id, callerAddress) {
let retries = 0
while (retries < MAX_RETRIES) {
try {
const ethPrice = await retrieveLatestEthPrice()
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, ethPrice, id)
return
} catch (error) {
// Start here
if (retries === MAX_RETRIES - 1) {
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, '0', id)
return
}
retries++
}
}
}
在 EVM 中並不支援浮點數運算,所以只能透過先把浮點數數字用一個 10^n 做相成,最後運算完才除回來
因為 Binance API 回傳值是一個具有在小數點前面有8位數的浮點數
所以可以把回傳值乘與 10^10 次方。因為 1 Ether = 10^18 wei
如果是小數點前面有8位數的浮點數,代表小數後面有 10 位數
在 Javascript Number 型別是雙精準度 64 位元的二進位 IEEE 754 數值,只支援到 16位數個精準度
所以只能使用 BN.js 這個函式庫來處理大精準度的數值
假設 Biance API 回傳 169.8700000
以下示範如何使用 BN.js 做轉換
因為 Javascript 是動態型別
所以可以透過以下方式把小數點先拿掉
aNumber = aNumber.replace('.', '')
接著使用以下方式把 aNumber 轉換成 BN 物件
const bNumber = new BN(aNumber, 10)
其中第二個參數代入 10 代表是以 10 進位為主
const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []
async function getOracleContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}
async function retrieveLatestEthPrice () {
const resp = await axios({
url: 'https://api.binance.com/api/v3/ticker/price',
params: {
symbol: 'ETHUSDT'
},
method: 'get'
})
return resp.data.price
}
async function filterEvents (oracleContract, web3js) {
oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
await addRequestToQueue(event)
})
oracleContract.events.SetLatestEthPriceEvent(async (err, event) => {
if (err) console.error('Error on event', err)
// Do something
})
}
async function addRequestToQueue (event) {
const callerAddress = event.returnValues.callerAddress
const id = event.returnValues.id
pendingRequests.push({ callerAddress, id })
}
async function processQueue (oracleContract, ownerAddress) {
let processedRequests = 0
while (pendingRequests.length > 0 && processedRequests < CHUNK_SIZE) {
const req = pendingRequests.shift()
await processRequest(oracleContract, ownerAddress, req.id, req.callerAddress)
processedRequests++
}
}
async function processRequest (oracleContract, ownerAddress, id, callerAddress) {
let retries = 0
while (retries < MAX_RETRIES) {
try {
const ethPrice = await retrieveLatestEthPrice()
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, ethPrice, id)
return
} catch (error) {
if (retries === MAX_RETRIES - 1) {
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, '0', id)
return
}
retries++
}
}
}
async function setLatestEthPrice (oracleContract, callerAddress, ownerAddress, ethPrice, id) {
// Start here
ethPrice = ethPrice.replace('.', '')
const multiplier = new BN(10**10, 10)
const ethPriceInt = (new BN(parseInt(ethPrice), 10)).mul(multiplier)
const idInt = new BN(parseInt(id))
try {
await oracleContract.methods.setLatestEthPrice(ethPriceInt.toString(), callerAddress, idInt.toString()).send({ from: ownerAddress })
} catch (error) {
console.log('Error encountered while calling setLatestEthPrice.')
// Do some error handling
}
}
繼續往下實作 client 與 Oracle 互動的部份之前
需要知道幾個要點:
以下是 Javascript 起動 Oracle Contract 流程
在 Javascript 要回傳多個值,必須要以 JSON 物件或是陣列行時回傳如下
function myAwesomeFunction () {
const one = '1'
const two = '2'
return { one, two }
}
接收方則需用以下語法接收
const { one, two } = myAwesomeFunction()
const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []
async function getOracleContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}
async function retrieveLatestEthPrice () {
const resp = await axios({
url: 'https://api.binance.com/api/v3/ticker/price',
params: {
symbol: 'ETHUSDT'
},
method: 'get'
})
return resp.data.price
}
async function filterEvents (oracleContract, web3js) {
oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
await addRequestToQueue(event)
})
oracleContract.events.SetLatestEthPriceEvent(async (err, event) => {
if (err) console.error('Error on event', err)
// Do something
})
}
async function addRequestToQueue (event) {
const callerAddress = event.returnValues.callerAddress
const id = event.returnValues.id
pendingRequests.push({ callerAddress, id })
}
async function processQueue (oracleContract, ownerAddress) {
let processedRequests = 0
while (pendingRequests.length > 0 && processedRequests < CHUNK_SIZE) {
const req = pendingRequests.shift()
await processRequest(oracleContract, ownerAddress, req.id, req.callerAddress)
processedRequests++
}
}
async function processRequest (oracleContract, ownerAddress, id, callerAddress) {
let retries = 0
while (retries < MAX_RETRIES) {
try {
const ethPrice = await retrieveLatestEthPrice()
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, ethPrice, id)
return
} catch (error) {
if (retries === MAX_RETRIES - 1) {
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, '0', id)
return
}
retries++
}
}
}
async function setLatestEthPrice (oracleContract, callerAddress, ownerAddress, ethPrice, id) {
ethPrice = ethPrice.replace('.', '')
const multiplier = new BN(10**10, 10)
const ethPriceInt = (new BN(parseInt(ethPrice), 10)).mul(multiplier)
const idInt = new BN(parseInt(id))
try {
await oracleContract.methods.setLatestEthPrice(ethPriceInt.toString(), callerAddress, idInt.toString()).send({ from: ownerAddress })
} catch (error) {
console.log('Error encountered while calling setLatestEthPrice.')
// Do some error handling
}
}
async function init () {
// Start here
const { ownerAddress, web3js, client } = common.loadAccount(PRIVATE_KEY_FILE_NAME)
const oracleContract = await getOracleContract(web3js)
filterEvents(oracleContract, web3js)
return { oracleContract, ownerAddress, client }
}
在處理 queue 那部份需要加入 sleep SLEEP_INTERVAL 邏輯
在 javascript 可以使用 setInterval 如下來實現
setInterval(async () => {
doSomething()
}, SLEEP_INTERVAL)
而當整個 app 停止時,需要能夠做到 graceful shutdown 來關閉所有使用過的資源
在 nodejs 可以使用 SIGINT 這個訊號的監聽 app 關閉如下
process.on( 'SIGINT', () => {
// Gracefully shut down the oracle
})
const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []
async function getOracleContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}
async function retrieveLatestEthPrice () {
const resp = await axios({
url: 'https://api.binance.com/api/v3/ticker/price',
params: {
symbol: 'ETHUSDT'
},
method: 'get'
})
return resp.data.price
}
async function filterEvents (oracleContract, web3js) {
oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
if (err) {
console.error('Error on event', err)
return
}
await addRequestToQueue(event)
})
oracleContract.events.SetLatestEthPriceEvent(async (err, event) => {
if (err) console.error('Error on event', err)
// Do something
})
}
async function addRequestToQueue (event) {
const callerAddress = event.returnValues.callerAddress
const id = event.returnValues.id
pendingRequests.push({ callerAddress, id })
}
async function processQueue (oracleContract, ownerAddress) {
let processedRequests = 0
while (pendingRequests.length > 0 && processedRequests < CHUNK_SIZE) {
const req = pendingRequests.shift()
await processRequest(oracleContract, ownerAddress, req.id, req.callerAddress)
processedRequests++
}
}
async function processRequest (oracleContract, ownerAddress, id, callerAddress) {
let retries = 0
while (retries < MAX_RETRIES) {
try {
const ethPrice = await retrieveLatestEthPrice()
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, ethPrice, id)
return
} catch (error) {
if (retries === MAX_RETRIES - 1) {
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, '0', id)
return
}
retries++
}
}
}
async function setLatestEthPrice (oracleContract, callerAddress, ownerAddress, ethPrice, id) {
ethPrice = ethPrice.replace('.', '')
const multiplier = new BN(10**10, 10)
const ethPriceInt = (new BN(parseInt(ethPrice), 10)).mul(multiplier)
const idInt = new BN(parseInt(id))
try {
await oracleContract.methods.setLatestEthPrice(ethPriceInt.toString(), callerAddress, idInt.toString()).send({ from: ownerAddress })
} catch (error) {
console.log('Error encountered while calling setLatestEthPrice.')
// Do some error handling
}
}
async function init () {
const { ownerAddress, web3js, client } = common.loadAccount(PRIVATE_KEY_FILE_NAME)
const oracleContract = await getOracleContract(web3js)
filterEvents(oracleContract, web3js)
return { oracleContract, ownerAddress, client }
}
(async () => {
const { oracleContract, ownerAddress, client } = await init()
process.on( 'SIGINT', () => {
console.log('Calling client.disconnect()')
// 1. Execute client.disconnect
client.disconnect()
process.exit( )
})
setInterval(async () => {
// 2. Run processQueue
await processQueue(oracleContract, ownerAddress)
}, SLEEP_INTERVAL)
})()
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './caller/caller_private_key'
const CallerJSON = require('./caller/build/contracts/CallerContract.json')
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
async function getCallerContract (web3js) {
const networkId = await web3js.eth.net.getId()
return new web3js.eth.Contract(CallerJSON.abi, CallerJSON.networks[networkId].address)
}
async function retrieveLatestEthPrice () {
const resp = await axios({
url: 'https://api.binance.com/api/v3/ticker/price',
params: {
symbol: 'ETHUSDT'
},
method: 'get'
})
return resp.data.price
}
async function filterEvents (callerContract) {
callerContract.events.PriceUpdatedEvent({ filter: { } }, async (err, event) => {
if (err) console.error('Error on event', err)
console.log('* New PriceUpdated event. ethPrice: ' + event.returnValues.ethPrice)
})
callerContract.events.ReceivedNewRequestIdEvent({ filter: { } }, async (err, event) => {
if (err) console.error('Error on event', err)
})
}
async function init () {
const { ownerAddress, web3js, client } = common.loadAccount(PRIVATE_KEY_FILE_NAME)
const callerContract = await getCallerContract(web3js)
filterEvents(callerContract)
return { callerContract, ownerAddress, client, web3js }
}
(async () => {
const { callerContract, ownerAddress, client, web3js } = await init()
process.on( 'SIGINT', () => {
console.log('Calling client.disconnect()')
client.disconnect();
process.exit( );
})
const networkId = await web3js.eth.net.getId()
const oracleAddress = OracleJSON.networks[networkId].address
await callerContract.methods.setOracleInstanceAddress(oracleAddress).send({ from: ownerAddress })
setInterval( async () => {
// Start here
await callerContract.methods.updateEthPrice().send({ from: ownerAddress })
}, SLEEP_INTERVAL);
})()
直接建立 scripts/gen-key.js 如下
const { CryptoUtils } = require('loom-js')
const fs = require('fs')
if (process.argv.length <= 2) {
console.log("Usage: " + __filename + " <filename>.")
process.exit(1);
}
const privateKey = CryptoUtils.generatePrivateKey()
const privateKeyString = CryptoUtils.Uint8ArrayToB64(privateKey)
let path = process.argv[2]
fs.writeFileSync(path, privateKeyString)
接著就可以透過以下指令來產生 private key 給 oracle contract
node scripts/gen-key.js oracle/oracle_private_key
然後透過以下指令來產生 private key 給 caller contract
node scripts/gen-key.js caller/caller_private_key.
為了要 deploy 兩個 Contract ,但兩個 Contract 具有不同的 private key
要做到使用 Truffle 可以分別使用不同 private key deploy 不同 Contract 最簡單作法就是建立兩個不同的設定檔案
const LoomTruffleProvider = require('loom-truffle-provider')
const path = require('path')
const fs = require('fs')
module.exports = {
networks: {
extdev: {
provider: function () {
const privateKey = fs.readFileSync(path.join(__dirname, 'oracle_private_key'), 'utf-8')
const chainId = 'extdev-plasma-us1'
const writeUrl = 'wss://extdev-plasma-us1.dappchains.com/websocket'
const readUrl = 'wss://extdev-plasma-us1.dappchains.com/queryws'
return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey)
},
network_id: '9545242630824'
}
},
compilers: {
solc: {
version: '0.5.0'
}
}
}
const LoomTruffleProvider = require('loom-truffle-provider')
const path = require('path')
const fs = require('fs')
module.exports = {
networks: {
extdev: {
provider: function () {
const privateKey = fs.readFileSync(path.join(__dirname, 'caller_private_key'), 'utf-8')
const chainId = 'extdev-plasma-us1'
const writeUrl = 'wss://extdev-plasma-us1.dappchains.com/websocket'
const readUrl = 'wss://extdev-plasma-us1.dappchains.com/queryws'
return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey)
},
network_id: '9545242630824'
}
},
compilers: {
solc: {
version: '0.5.0'
}
}
}
兩個設定檔分別引入不同 private key
在 ./oracle/migrations/2_eth_price_oracle.js 使用以下內容
const EthPriceOracle = artifacts.require('EthPriceOracle')
module.exports = function (deployer) {
deployer.deploy(EthPriceOracle)
}
在 ./caller/migrations/02_caller_oracle.js 使用以下內容
const CallerContract = artifacts.require('CallerContract')
module.exports = function (deployer) {
deployer.deploy(CallerContract)
}
更新 scripts 欄位如下
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"deploy:oracle": "cd oracle && npx truffle migrate --network extdev --reset -all && cd ..",
"deploy:caller": "cd caller && npx truffle migrate --network extdev --reset -all && cd ..",
"deploy:all": "npm run deploy:oracle && npm run deploy:caller"
},
然後就可以透過 npm run deploy:all 一次發佈兩個 Contract 了
首先使用以下讓 EthPriceOracle.js 跑起來
node EthPriceOracle.js
接者 Client.js 需要透過以下指令做執行
node Client.js
然後就可以看到畫面上有 ethPrice 的訊息了