@witnet/ethers
Version:
Wit/Oracle SDK Framework package for Solidity projects
382 lines (364 loc) • 13.1 kB
JavaScript
require("dotenv").config()
const { JsonRpcProvider } = require("ethers")
const helpers = require("./helpers")
const { green, yellow, lwhite } = helpers.colors
const { utils } = require("../../dist/src/lib")
/// CONSTANTS AND GLOBALS =============================================================================================
const settings = {
flags: {
all: "List all available Radon assets, even if not yet deployed.",
apps: "Show addresses of Wit/Oracle appliances.",
await: "Hold down until next event is triggered.",
"check-result-status": "Check result status for each oracle query.",
decode: "Decode selected Radon assets, as currently deployed.",
deploy: "Deploy selected Radon assets, if not yet deployed.",
"dry-run": "Dry-run selected Radon asset, as currently deployed (supersedes --decode).",
force: "Force operations without user intervention.",
help: "Describe how to use some command.",
legacy: "Filter to those declared in witnet/assets folder.",
mainnets: "List supported EVM mainnets.",
randomize: "Pay for a new randomize request.",
requests: "Includes WitOracleRequest artifacts.",
templates: "List deployed WitOracleRadonRequestTemplate contracts.",
testnets: "List supported EVM testnets.",
verbose: "Outputs detailed information.",
version: "Print binary name and version as headline.",
},
options: {
confirmations: {
hint: "Number of block confirmations to wait for after an EVM transaction gets mined.",
param: "NUMBER",
},
contract: {
hint: "Path or name of the new mockup contract to be created",
param: "path/to/output",
},
depth: {
hint: "Maximum number of randomize transactions to list, before the latest one (default: 16).",
},
"filter-consumer": {
hint: "Filter events triggered by given consumer.",
param: "EVM_ADDRESS",
},
"filter-requester": {
hint: "Filter events triggered by given requester.",
param: "EVM_ADDRESS",
},
"filter-radHash": {
hint: "Filter events referring given RAD hash.",
param: "RAD_HASH_FRAGMENT",
},
fromBlock: {
hint: "Process events since given EVM block number.",
param: "EVM_BLOCK",
},
gasPrice: {
hint: "EVM gas price to pay for.",
param: "GAS_PRICE",
},
gasLimit: {
hint: "Maximum EVM gas to spend per transaction.",
param: "GAS_LIMIT",
},
limit: {
hint: "Limit number of output records (default: 64).",
param: "LIMIT",
},
into: {
hint: "Address of some WitOracleConsumer contract where to report into.",
param: "EVM_ADDRESS",
},
module: {
hint: "Package where to fetch Radon assets from (supersedes --legacy).",
param: "NPM_PACKAGE",
},
network: {
hint: "Bind mockup contract to immutable Wit/Oracle addresses on this EVM network.",
param: "NETWORK",
},
port: {
hint: "Port on which the local ETH/RPC signing gateway is expected to be listening (default: 8545).",
param: "HTTP_PORT",
},
provider: {
hint: "Force the local gateway to rely on this remote ETH/RPC provider.",
param: "PROVIDER_URL",
},
"dr-tx-hash": {
hint: "Retrieve the finalized result to the given Wit/Oracle query, and push it into some consumer contract (requires: --into).",
param: "WIT_DR_TX_HASH",
},
signer: {
hint: "EVM signer address, other than gateway's default.",
param: "EVM_ADDRESS",
},
target: {
hint: "Address of the contract to interact with.",
param: "EVM_ADDRESS",
},
toBlock: {
hint: "Process events emitted before this EVM block number.",
param: "EVM_BLOCK",
},
witnet: {
hint: "Wit/Oracle RPC provider to connect to, other than default.",
param: "URL",
},
// witnesses: {
// hint: "Number of witnessing nodes required to solve the randomness request in the Witnet blockchain.",
// param: "NUMBER",
// },
},
envars: {
ETHRPC_PRIVATE_KEYS: "=> Private keys used by the ETH/RPC gateway for signing EVM transactions.",
ETHRPC_PROVIDER_URL: "=> Remote ETH/RPC provider to rely on, if no otherwise specified.",
WITNET_KERMIT_PROVIDER_URL: "=> Wit/Kermit API-REST provider to connect to, if no otherwise specified.",
},
}
/// MAIN WORKFLOW =====================================================================================================
main()
async function main () {
let ethRpcPort = 8545
if (process.argv.indexOf("--port") >= 0) {
ethRpcPort = parseInt(process.argv[process.argv.indexOf("--port") + 1])
}
let ethRpcProvider, ethRpcNetwork
try {
ethRpcProvider = new JsonRpcProvider(`http://127.0.0.1:${ethRpcPort}`)
ethRpcNetwork = utils.getEvmNetworkByChainId((await ethRpcProvider.getNetwork()).chainId)
} catch (err) {}
const router = {
...(ethRpcNetwork
? {
assets: {
hint: `Formally verify deployable Radon assets into ${helpers.colors.mcyan(ethRpcNetwork.toUpperCase())}.`,
params: "[RADON_ASSETS ...]",
flags: ["all", "decode", "deploy", "dry-run", "legacy"],
options: [
"module",
"port",
"signer",
],
},
contracts: {
hint: `List available Wit/Oracle Framework addresses in ${helpers.colors.mcyan(ethRpcNetwork.toUpperCase())}.`,
params: "[ARTIFACT_NAMES ...]",
flags: [
// 'apps',
"templates",
],
options: [
"port",
],
},
priceFeeds: {
hint: `Show latest Wit/Price Feeds updates on ${helpers.colors.mcyan(ethRpcNetwork.toUpperCase())}.`,
params: "[EVM_ADDRESS]",
},
queries: {
hint: `Show latest Wit/Oracle queries pulled from ${helpers.colors.mcyan(ethRpcNetwork.toUpperCase())}.`,
// params: "[TOPICS ...]",
flags: [
"check-result-status",
],
options: [
"filter-radHash",
"filter-requester",
"fromBlock",
"limit",
"signer",
"toBlock",
],
envars: [],
},
randomness: {
hint: `Show latest Wit/Randomness seeds randomized from ${helpers.colors.mcyan(ethRpcNetwork.toUpperCase())}.`,
params: "[EVM_ADDRESS]",
flags: [
"randomize",
],
options: [
"confirmations",
"depth",
"gasPrice",
"signer",
],
envars: [],
},
reports: {
hint: `Show latest Wit/Oracle data reports pushed into ${helpers.colors.mcyan(ethRpcNetwork.toUpperCase())}.`,
flags: [
"verbose",
],
options: [
"confirmations",
"dr-tx-hash",
"filter-consumer",
"filter-radHash",
"fromBlock",
"gasPrice",
"gasLimit",
"into",
"limit",
"signer",
"toBlock",
"witnet",
],
envars: [
"WITNET_KERMIT_PROVIDER_URL",
],
},
}
: {}),
gateway: {
hint: "Launch a local ETH/RPC signing gateway connected to some specific EVM network.",
params: ["EVM_NETWORK"],
options: [
"port",
"provider",
],
envars: [
"ETHRPC_PRIVATE_KEYS",
"ETHRPC_PROVIDER_URL",
],
},
networks: {
hint: "List EVM networks currently bridged to the Witnet blockchain.",
params: "[EVM_ECOSYSTEM]",
flags: [
"mainnets",
"testnets",
],
},
wizard: {
hint: "Generate Solidity mockup contracts adapted to your use case.",
options: [
"contract",
"network",
],
},
commands: {
assets: require("./cli/assets"),
contracts: require("./cli/contracts"),
gateway: require("./cli/gateway"),
networks: require("./cli/networks"),
priceFeeds: require("./cli/priceFeeds"),
queries: require("./cli/queries"),
randomness: require("./cli/randomness"),
reports: require("./cli/reports"),
wizard: require("./cli/wizard"),
},
}
let [args, flags] = helpers.extractFlagsFromArgs(process.argv.slice(2), Object.keys(settings.flags))
if (flags.version) {
helpers.showVersion()
}
let options; [args, options] = helpers.extractOptionsFromArgs(args, Object.keys(settings.options))
if (args[0] && router.commands[args[0]] && router[args[0]]) {
const cmd = args[0]
if (flags.help) {
showCommandUsage(router, cmd, router[cmd])
} else {
try {
await router.commands[cmd]({ ...settings, ...flags, ...options }, args.slice(1))
} catch (e) {
showUsageError(router, cmd, router[cmd], e)
}
}
} else {
showMainUsage(router)
}
}
function showMainUsage (router) {
showUsageHeadline(router)
showUsageFlags(["help", "version"])
showUsageOptions(["port"])
console.info("\nCOMMANDS:")
const maxLength = Object.keys(router.commands).map(key => key.length).reduce((prev, curr) => curr > prev ? curr : prev)
Object.keys(router.commands).forEach(cmd => {
if (router[cmd]) console.info(" ", `${cmd}${" ".repeat(maxLength - cmd.length)}`, " ", router[cmd]?.hint)
})
}
function showCommandUsage (router, cmd, specs) {
showUsageHeadline(router, cmd, specs)
showUsageFlags(specs?.flags || [])
showUsageOptions(specs?.options || [])
showUsageEnvars(specs?.envars || [])
}
function showUsageEnvars (envars) {
if (envars.length > 0) {
console.info("\nENVARS:")
const maxWidth = envars.map(envar => envar.length).reduce((curr, prev) => curr > prev ? curr : prev)
envars.forEach(envar => {
if (envar.toUpperCase().indexOf("KEY") < 0 && process.env[envar]) {
console.info(" ", `${yellow(envar.toUpperCase())}${" ".repeat(maxWidth - envar.length)}`, ` => Settled to "${process.env[envar]}"`)
} else {
console.info(" ", `${yellow(envar.toUpperCase())}${" ".repeat(maxWidth - envar.length)}`, ` ${settings.envars[envar]}`)
}
})
}
}
function showUsageError (router, cmd, specs, error) {
showCommandUsage(router, cmd, specs)
if (error) {
console.info()
console.error(error)
// console.error(error?.stack?.split('\n')[0] || error)
}
}
function showUsageFlags (flags) {
if (flags.length > 0) {
const maxWidth = flags.map(flag => flag.length).reduce((curr, prev) => curr > prev ? curr : prev)
console.info("\nFLAGS:")
flags.forEach(flag => {
if (settings.flags[flag]) {
console.info(` --${flag}${" ".repeat(maxWidth - flag.length)} ${settings.flags[flag]}`)
}
})
}
}
function showUsageHeadline (router, cmd, specs) {
console.info("USAGE:")
const flags = cmd && (!specs?.flags || specs.flags.length === 0) ? "" : "[FLAGS] "
const options = specs?.options && specs.options.length > 0 ? "[OPTIONS] " : ""
if (cmd) {
let params
if (specs?.params) {
const optionalize = (str) => str.endsWith(" ...]")
? `[<${str.slice(1, -5)}> ...]`
: (
str[0] === "[" ? `[<${str.slice(1, -1)}>]` : `<${str}>`
)
if (Array.isArray(specs?.params)) {
params = specs.params.map(param => optionalize(param)).join(" ") + " "
} else {
params = optionalize(specs?.params) + " "
}
console.info(` ${lwhite(`npx witeth ${cmd}`)} ${params ? green(params) : ""}${flags}${options}`)
} else {
console.info(` ${lwhite(`npx witeth ${cmd}`)} ${flags}${options}`)
}
console.info("\nDESCRIPTION:")
console.info(` ${router[cmd].hint}`)
} else {
console.info(` ${lwhite("npx witeth")} <COMMAND> ${flags}${options}`)
}
}
function showUsageOptions (options) {
if (options.length > 0) {
console.info("\nOPTIONS:")
const maxLength = options
.map(option => settings.options[option].param
? settings.options[option].param.length + option.length + 3
: option.length
)
.reduce((prev, curr) => curr > prev ? curr : prev)
options.forEach(option => {
if (settings.options[option].hint) {
const str = `${option}${settings.options[option].param ? helpers.colors.gray(` <${settings.options[option].param}>`) : ""}`
console.info(" ", `--${str}${" ".repeat(maxLength - helpers.colorstrip(str).length)}`, " ", settings.options[option].hint)
}
})
}
}