const path = require('path');
const fetch = require('isomorphic-fetch');
const { DirectSecp256k1HdWallet } = require("@cosmjs/proto-signing");
const { stringToPath } = require("@cosmjs/crypto");
const cosmwasm = require('@cosmjs/cosmwasm-stargate');
const { GasPrice } = require('@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/stargate/build');
require('dotenv').config({ path: path.resolve(__dirname, process.env.NODE_ENV ? `.env.${process.env.NODE_ENV}` : ".env") })
WEBSOCKET_URL=ws://testnet-rpc.orai.io // testnet ip
LCD_URL=http://testnet-lcd.orai.io
CONTRACT_ADDRESS=orai1s60a2vntfuv2ps6fs75fcrlrmea9xzr4k65zlg // testnet contract
BACKEND_URL=https://testnet-aioracle-svr.orai.io
rpc: process.env.NETWORK_RPC || "https://testnet-rpc.orai.io",
const collectWallet = async (mnemonic) => {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(
hdPaths: [stringToPath("m/44'/118'/0'/0/0")],
const execute = async ({ mnemonic, address, handleMsg, memo, amount, gasData = undefined }) => {
const wallet = await collectWallet(mnemonic);
const [firstAccount] = await wallet.getAccounts();
const client = await cosmwasm.SigningCosmWasmClient.connectWithSigner(network.rpc, wallet, { gasPrice: gasData ? GasPrice.fromString(`${gasData.gasAmount}${gasData.denom}`) : undefined, prefix: network.prefix, gasLimits: { exec: 20000000 } });
const input = JSON.parse(handleMsg);
const result = await client.execute(firstAccount.address, address, input, memo, amount);
return result.transactionHash;
console.log("error in executing contract: ", error);
const demo = async () => {
const contractAddr = process.env.CONTRACT_ADDRESS;
console.log("contract addr: ", contractAddr)
const wallet = process.env.MNEMONIC;
const threshold = process.env.THRESHOLD || 1;
const service = process.env.SERVICE || "price";
const lcdUrl = process.env.LCD_URL || "https://testnet-lcd.orai.io";
const backendUrl = process.env.BACKEND_URL || "https://testnet-aioracle-svr.orai.io";
const [feeAmount, boundExecutorFee] = await getServiceFees(contractAddr, lcdUrl, service, threshold);
// const feeAmount = [{ denom: "orai", amount: "1000" }]
let finalFeeAmount = feeAmount.filter(fee => fee.amount !== '0');
if (finalFeeAmount.length === 0) finalFeeAmount = undefined;
const input = JSON.stringify({
threshold: parseInt(threshold),
preference_executor_fee: boundExecutorFee
console.log("input: ", input)
// store the merkle root on-chain
const txHash = await execute({ mnemonic: wallet, address: contractAddr, handleMsg: input, gasData: { gasAmount: "0", denom: "orai" }, amount: finalFeeAmount });
console.log("execute result: ", txHash);
const requestId = await collectRequestId(lcdUrl, txHash);
console.log("request id: ", requestId);
console.log("Collecting the reports, please wait...")
const reports = await collectReports(backendUrl, contractAddr, requestId);
console.log("reports: ", JSON.stringify(reports));
const getServiceFees = async (contractAddr, lcdUrl, service, threshold) => {
const getServiceFeesMsg = JSON.stringify({
const boundExecutorFeeMsg = JSON.stringify({
get_bound_executor_fee: {}
let rawData = await fetch(`${lcdUrl}/wasm/v1beta1/contract/${contractAddr}/smart/${Buffer.from(getServiceFeesMsg).toString('base64')}`).then(data => data.json());
let boundFee = await fetch(`${lcdUrl}/wasm/v1beta1/contract/${contractAddr}/smart/${Buffer.from(boundExecutorFeeMsg).toString('base64')}`).then(data => data.json());
let boundExecutorFee = boundFee.data;
console.log("data: ", rawData);
data.push(["placeholder", boundExecutorFee.denom, boundExecutorFee.amount]);
// ['orai1y88tlgddntj66sn46qqlvtx3tp7tgl8sxxx6uk', 'orai', '1'],
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'orai', '1'],
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'foobar', '1'],
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'orai', '1'],
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'orai', '1'],
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'xyz', '1'],
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'foobar', '1'],
// ['orai1v7ae3ptzqvztcx83fheafltq88hvdp2m5zas6f', 'xyz', '1'],
data = data.map(reward => ({ denom: reward[1], amount: parseInt(reward[2]) })).reduce((prev, curr) => {
if (prev.constructor === Array) {
// find if the current denom exists already in the accumulator
const index = prev.findIndex(prevElement => prevElement.denom === curr.denom);
// if exist then we update the amount of the index in the accumulator, then keep the accumulator
prev[index].amount += curr.amount;
// if does not exist then we append the current obj into the accumulator
if (prev.denom === curr.denom) return [{ ...prev, amount: prev.amount + curr.amount }];
}, []).map(reward => ({ ...reward, amount: String(reward.amount * threshold) }));
return [data, boundExecutorFee];
const collectRequestId = async (lcdUrl, txHash) => {
let count = 0; // break the loop flag
const result = await fetch(`${lcdUrl}/cosmos/tx/v1beta1/txs/${txHash}`).then(data => data.json());
const wasmEvent = result.tx_response.events.filter(event => event.type === "wasm")[0].attributes.filter(attr => attr.key === Buffer.from('stage').toString('base64'))[0].value;
requestId = Buffer.from(wasmEvent, 'base64').toString('ascii');
if (count > 10) break; // break the loop and return the request id.
// sleep for a few seconds then repeat
await new Promise(r => setTimeout(r, 3000));
const collectReports = async (url, contractAddr, requestId) => {
const reportUrl = `${url}/report/reports?contract_addr=${contractAddr}&request_id=${requestId}`;
console.log("report url: ", reportUrl)
reports = await fetch(reportUrl).then(data => data.json());
console.log("reports.data.data.length: ", reports.data.data.length)
if (!reports.data || reports.data.data.length === 0) {
if (count > 20) break; // break the loop and return the request id.
// sleep for a few seconds then repeat
await new Promise(r => setTimeout(r, 5000));
} while (!reports.data || reports.data.data.length === 0);