Oraichain

Introduction

Oraichain Oracle Price Feeds enable developers to integrate on-chain price data from crypto markets into their smart contracts with ease.
Oraichain Oracle Price Feeds meanwhile supports a wide range of blockchain networks such as Ethereum, Binance Chain, Oraichain, and more in the future.
Data have been extracted from trustworthy exchanges in addition to timely updates with low latency.
Supported assets supported by Oraichain Oracle at the moment are as follows:

Smart contract

Contract address on Oraichain testnet: orai1s60a2vntfuv2ps6fs75fcrlrmea9xzr4k65zlg
Price Data Requests

Integrate with Node.js

1
const path = require('path');
2
const fetch = require('isomorphic-fetch');
3
const { DirectSecp256k1HdWallet } = require("@cosmjs/proto-signing");
4
const { stringToPath } = require("@cosmjs/crypto");
5
const cosmwasm = require('@cosmjs/cosmwasm-stargate');
6
const { GasPrice } = require('@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/stargate/build');
7
require('dotenv').config({ path: path.resolve(__dirname, process.env.NODE_ENV ? `.env.${process.env.NODE_ENV}` : ".env") })
8
9
// .env file
10
11
/**
12
* MNEMONIC=""
13
WEBSOCKET_URL=ws://testnet-rpc.orai.io // testnet ip
14
LCD_URL=http://testnet-lcd.orai.io
15
CONTRACT_ADDRESS=orai1s60a2vntfuv2ps6fs75fcrlrmea9xzr4k65zlg // testnet contract
16
BACKEND_URL=https://testnet-aioracle-svr.orai.io
17
*/
18
19
const network = {
20
rpc: process.env.NETWORK_RPC || "https://testnet-rpc.orai.io",
21
prefix: "orai",
22
}
23
24
const collectWallet = async (mnemonic) => {
25
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(
26
mnemonic,
27
{
28
hdPaths: [stringToPath("m/44'/118'/0'/0/0")],
29
prefix: network.prefix,
30
}
31
);
32
return wallet;
33
}
34
35
const execute = async ({ mnemonic, address, handleMsg, memo, amount, gasData = undefined }) => {
36
try {
37
const wallet = await collectWallet(mnemonic);
38
const [firstAccount] = await wallet.getAccounts();
39
const client = await cosmwasm.SigningCosmWasmClient.connectWithSigner(network.rpc, wallet, { gasPrice: gasData ? GasPrice.fromString(`${gasData.gasAmount}${gasData.denom}`) : undefined, prefix: network.prefix, gasLimits: { exec: 20000000 } });
40
const input = JSON.parse(handleMsg);
41
const result = await client.execute(firstAccount.address, address, input, memo, amount);
42
return result.transactionHash;
43
} catch (error) {
44
console.log("error in executing contract: ", error);
45
throw error;
46
}
47
}
48
49
const demo = async () => {
50
const contractAddr = process.env.CONTRACT_ADDRESS;
51
console.log("contract addr: ", contractAddr)
52
const wallet = process.env.MNEMONIC;
53
const threshold = process.env.THRESHOLD || 1;
54
const service = process.env.SERVICE || "price";
55
const lcdUrl = process.env.LCD_URL || "https://testnet-lcd.orai.io";
56
const backendUrl = process.env.BACKEND_URL || "https://testnet-aioracle-svr.orai.io";
57
const [feeAmount, boundExecutorFee] = await getServiceFees(contractAddr, lcdUrl, service, threshold);
58
// const feeAmount = [{ denom: "orai", amount: "1000" }]
59
let finalFeeAmount = feeAmount.filter(fee => fee.amount !== '0');
60
if (finalFeeAmount.length === 0) finalFeeAmount = undefined;
61
const input = JSON.stringify({
62
request: {
63
threshold: parseInt(threshold),
64
service,
65
preference_executor_fee: boundExecutorFee
66
}
67
})
68
console.log("input: ", input)
69
70
// store the merkle root on-chain
71
const txHash = await execute({ mnemonic: wallet, address: contractAddr, handleMsg: input, gasData: { gasAmount: "0", denom: "orai" }, amount: finalFeeAmount });
72
console.log("execute result: ", txHash);
73
const requestId = await collectRequestId(lcdUrl, txHash);
74
console.log("request id: ", requestId);
75
console.log("Collecting the reports, please wait...")
76
const reports = await collectReports(backendUrl, contractAddr, requestId);
77
console.log("reports: ", JSON.stringify(reports));
78
}
79
80
const getServiceFees = async (contractAddr, lcdUrl, service, threshold) => {
81
const getServiceFeesMsg = JSON.stringify({
82
get_service_fees: {
83
service,
84
}
85
})
86
const boundExecutorFeeMsg = JSON.stringify({
87
get_bound_executor_fee: {}
88
})
89
let rawData = await fetch(`${lcdUrl}/wasm/v1beta1/contract/${contractAddr}/smart/${Buffer.from(getServiceFeesMsg).toString('base64')}`).then(data => data.json());
90
let data = rawData.data;
91
let boundFee = await fetch(`${lcdUrl}/wasm/v1beta1/contract/${contractAddr}/smart/${Buffer.from(boundExecutorFeeMsg).toString('base64')}`).then(data => data.json());
92
let boundExecutorFee = boundFee.data;
93
console.log("data: ", rawData);
94
data.push(["placeholder", boundExecutorFee.denom, boundExecutorFee.amount]);
95
// let data = [
96
// ['orai1y88tlgddntj66sn46qqlvtx3tp7tgl8sxxx6uk', 'orai', '1'],
97
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'orai', '1'],
98
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'foobar', '1'],
99
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'orai', '1'],
100
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'orai', '1'],
101
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'xyz', '1'],
102
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'foobar', '1'],
103
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'xyz', '1'],
104
// ];
105
data = data.map(reward => ({ denom: reward[1], amount: parseInt(reward[2]) })).reduce((prev, curr) => {
106
if (prev.constructor === Array) {
107
// find if the current denom exists already in the accumulator
108
const index = prev.findIndex(prevElement => prevElement.denom === curr.denom);
109
if (index !== -1) {
110
// if exist then we update the amount of the index in the accumulator, then keep the accumulator
111
prev[index].amount += curr.amount;
112
return prev;
113
}
114
// if does not exist then we append the current obj into the accumulator
115
return [...prev, curr];
116
} else {
117
if (prev.denom === curr.denom) return [{ ...prev, amount: prev.amount + curr.amount }];
118
}
119
return [...prev, curr];
120
}, []).map(reward => ({ ...reward, amount: String(reward.amount * threshold) }));
121
return [data, boundExecutorFee];
122
}
123
124
const collectRequestId = async (lcdUrl, txHash) => {
125
let requestId = -1;
126
let count = 0; // break the loop flag
127
let hasRequestId = true;
128
do {
129
hasRequestId = true;
130
try {
131
const result = await fetch(`${lcdUrl}/cosmos/tx/v1beta1/txs/${txHash}`).then(data => data.json());
132
const wasmEvent = result.tx_response.events.filter(event => event.type === "wasm")[0].attributes.filter(attr => attr.key === Buffer.from('stage').toString('base64'))[0].value;
133
requestId = Buffer.from(wasmEvent, 'base64').toString('ascii');
134
} catch (error) {
135
hasRequestId = false;
136
count++;
137
if (count > 10) break; // break the loop and return the request id.
138
// sleep for a few seconds then repeat
139
await new Promise(r => setTimeout(r, 3000));
140
}
141
} while (!hasRequestId);
142
return requestId;
143
}
144
145
const collectReports = async (url, contractAddr, requestId) => {
146
let count = 0;
147
let reports = {};
148
const reportUrl = `${url}/report/reports?contract_addr=${contractAddr}&request_id=${requestId}`;
149
console.log("report url: ", reportUrl)
150
do {
151
reports = await fetch(reportUrl).then(data => data.json());
152
console.log("reports.data.data.length: ", reports.data.data.length)
153
if (!reports.data || reports.data.data.length === 0) {
154
count++;
155
if (count > 20) break; // break the loop and return the request id.
156
// sleep for a few seconds then repeat
157
await new Promise(r => setTimeout(r, 5000));
158
}
159
160
} while (!reports.data || reports.data.data.length === 0);
161
return reports.data;
162
}
163
164
demo();
Copied!
Copy link
Edit on GitHub