@witnet/ethers
Version:
Wit/Oracle SDK Framework package for Solidity projects
1,114 lines • 179 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WitOracle = void 0;
const ethers_1 = require("ethers");
const sdk_1 = require("@witnet/sdk");
const utils_1 = require("./utils");
class ContractWrapper {
constructor(signer, network, abi, target) {
this.address = target;
this.contract = new ethers_1.Contract(target, abi, signer);
this.network = network;
this.provider = signer.provider;
this.signer = signer;
}
/**
* Name of the underlying logic implementation contract.
* @returns Contract name.
*/
async getEvmImplClass() {
return this.contract
.getFunction("class()")
.staticCall();
}
/**
* Get specs identifier of the underlying logic implementation contract.
* @returns 4-byte hex string.
*/
async getEvmImplSpecs() {
return this.contract
.getFunction("specs()")
.staticCall();
}
/**
* Version tag of the underlying logic implementation contract.
* @returns Version tag.
*/
async getEvmImplVersion() {
let version;
try {
version = await this.provider
.call({
to: this.address,
data: "0x54fd4d50", // funcSig for 'version()'
})
.then(result => ethers_1.AbiCoder.defaultAbiCoder().decode(["string"], result))
.then(result => result.toString());
}
catch (_err) {
return "(immutable)";
}
return version;
}
}
class WitArtifactWrapper extends ContractWrapper {
constructor(signer, network, artifact, at) {
const abis = utils_1.ABIs;
const target = at || (0, utils_1.getEvmNetworkAddresses)(network)?.core[artifact];
if (!abis[artifact] || !target) {
throw new Error(`EVM network ${network} => artifact is not available: ${artifact}`);
}
else {
super(signer, network, abis[artifact], target);
}
}
}
class WitApplianceWrapper extends ContractWrapper {
constructor(witOracle, artifact, at) {
const abis = utils_1.ABIs;
const addresses = (0, utils_1.getEvmNetworkAddresses)(witOracle.network);
const target = at || addresses?.core[artifact] || addresses?.apps[artifact];
if (!abis[artifact] || !target) {
throw new Error(`EVM network ${witOracle.network} => artifact not available: ${artifact}`);
}
super(witOracle.signer, witOracle.network, abis[artifact], target);
this.witOracle = witOracle;
}
}
/**
* Wrapper class for the Wit/Oracle contract as deployed in some specified EVM network.
* It provides wrappers to other main artifacts of the Wit/Oracle Framework, as well
* as factory methods for wrapping existing `WitOracleRadonRequestTemplate` and `WitOracleConsumer`
* compliant contracts, provably bound to the Wit/Oracle core contract.
*
*/
class WitOracle extends WitArtifactWrapper {
constructor(signer, network) {
super(signer, network, "WitOracle");
}
/**
* Create a WitOracle attached to the Wit/Oracle main address on the connected EVM network.
* Fails if the EVM network served at the specified JSON ETH/RPC endpoint, is not currently bridged
* to the Witnet blockchain.
* @param url ETH/RPC endpoint URL.
* @param signer Specific signer address, other than default, to use for signing EVM transactions.
*/
static async fromJsonRpcUrl(url, signerId) {
const provider = new ethers_1.JsonRpcProvider(url);
const signer = await provider.getSigner(signerId);
const chainId = Number((await provider.getNetwork()).chainId);
const network = (0, utils_1.getEvmNetworkByChainId)(chainId);
if (!network) {
throw new Error(`WitOracle: unsupported chain id: ${chainId}`);
}
return new WitOracle(signer, network);
}
async estimateBaseFee(evmGasPrice) {
return this.contract
.getFunction("estimateBaseFee(uint256)")
.staticCall(evmGasPrice);
}
async estimateBaseFeeWithCallback(evmGasPrice, evmCallbackGas) {
return this.contract
.getFunction("estimateBaseFeeWithCallback(uint256,uint24)")
.staticCall(evmGasPrice, evmCallbackGas);
}
async estimateExtraFee(evmGasPrice, evmWitPrice, queryParams) {
return this.contract
.getFunction("estimateExtraFee(uint256,uint256,(uint16,uint16,uint64)")
.staticCall(evmGasPrice, evmWitPrice, (0, utils_1.abiEncodeWitOracleQueryParams)(queryParams));
}
async filterWitOracleQueryEvents(options) {
const witOracleQueryEvent = this.contract.filters["WitOracleQuery(address indexed,uint256,uint256,uint64,bytes32,(uint16,uint16,uint64))"](options?.where?.evmRequester);
return this.contract
.queryFilter(witOracleQueryEvent, options.fromBlock, options?.toBlock)
.then(logs => logs.filter(log => !log.removed
// && (!options?.where?.evmRequester || (log as EventLog).args?.requester === options.where.evmRequester)
&& (!options?.where?.queryRadHash || log.args?.radonHash.indexOf(options.where.queryRadHash) >= 0)))
.then(logs => logs.map(log => ({
evmBlockNumber: BigInt(log.blockNumber),
evmRequester: log.args?.evmRequester,
evmTransactionHash: log.transactionHash,
queryId: BigInt(log.args.queryId),
queryRadHash: log.args.radonHash,
queryParams: {
witnesses: log.args.radonParams[1],
unitaryReward: BigInt(log.args.radonParams[2]),
resultMaxSize: log.args.radonParams[0],
},
})));
}
async filterWitOracleReportEvents(options) {
const witOracleReportEvent = this.contract.filters.WitOracleReport(options?.where?.evmOrigin, options?.where?.evmConsumer);
return this.contract
.queryFilter(witOracleReportEvent, options.fromBlock, options?.toBlock)
.then(logs => logs.filter(log => !log.removed
&& (!options?.where?.queryRadHash || log.args?.queryRadHash.indexOf(options.where.queryRadHash) >= 0)))
.then(logs => logs.map(log => ({
evmBlockNumber: BigInt(log.blockNumber),
evmOrigin: log.args.evmOrigin,
evmConsumer: log.args.evmConsumer,
evmReporter: log.args.evmReporter,
evmTransactionHash: log.transactionHash,
witDrTxHash: log.args.witDrTxHash,
queryRadHash: log.args.queryRadHash,
queryParams: {
witnesses: log.args.queryParams[1],
unitaryReward: BigInt(log.args.queryParams[2]),
resultMaxSize: log.args.queryParams[0],
},
resultCborBytes: log.args.resultCborBytes,
resultTimestamp: Number(log.args.resultTimestamp),
})));
}
async getEvmChainId() {
return this.provider.getNetwork().then(network => Number(network.chainId));
}
async getEvmChannel() {
return this.contract
.getFunction("channel()")
.staticCall();
}
async getNextQueryId() {
return this.contract
.getFunction("getNextQueryId()")
.staticCall();
}
async getQueryResultStatusDescription(queryId) {
let reason;
try {
try {
reason = await this.contract.getQueryResultStatusDescription.staticCall(queryId);
}
catch {
const legacy = new ethers_1.Contract(this.address, [
"function getQueryResultError(uint256) public view returns ((uint8,string))",
], this.signer);
reason = await legacy.getQueryResultError.staticCall(queryId).then(result => result[1]);
}
}
catch {
reason = "(unparsable error)";
}
return reason;
}
async getQueryStatuses(queryIds) {
return this.contract
.getQueryStatusBatch
.staticCall(queryIds)
.then((statuses) => statuses.map(value => (0, utils_1.abiDecodeQueryStatus)(value)));
}
async getWitOracleConsumerAt(target) {
return WitOracleConsumer.at(this, target);
}
/**
* Wrapper class for the Wit/Oracle Radon Registry core contract as deployed in some supported EVM network.
* It allows formal verification of Radon Requests and Witnet-compliant data sources into such network,
* as to be securely referred on both Wit/Oracle queries pulled from within smart contracts,
* or Wit/Oracle query results pushed into smart contracts from offchain workflows.
*/
async getWitOracleRadonRegistry() {
return new WitOracleRadonRegistry(this.signer, this.network);
}
/**
* Wrapper class for the Wit/Oracle Request Factory core contract as deployed in some supported EVM network.
* It allows construction of `WitOracleRadonRequestTemplate` minimal-proxy contracts out of one ore more
* parameterized Radon Retievals (Witnet-compliant data sources). Template addresses are counter-factual to
* the set of data sources they are built on.
*/
async getWitOracleRadonRequestFactory() {
return WitOracleRadonRequestFactory.deployed(this, await this.getWitOracleRadonRegistry());
}
/**
* Wrapper class for Wit/Oracle Radon Template artifacts as deployed in some supported EVM network.
* `IWitOracleRadonRequestTemplate` contracts enable smart contracts to formally verify Radon Requests
* built out out of a set of parameterized Witnet-compliant data sources, on the fly.
*/
async getWitOracleRadonRequestTemplateAt(target) {
return WitOracleRadonRequestTemplate.at(this, target);
}
/**
* Wrapper class for Wit/Oracle Radon Modal artifacts as deployed in some supported EVM network.
* `IWitOracleRadonRequestModal` contracts enable smart contracts to formally verify Radon Requests
* built out out of a single Radon Retrieval and multiple data providers, all of them expected to
* provided exactly the same data.
*/
async getWitOracleRadonRequestModalAt(target) {
return WitOracleRadonRequestModal.arguments(this, target);
}
async getWitPriceFeedsAt(target) {
return WitPriceFeeds.at(this, target);
}
async getWitPriceFeedsLegacyAt(target) {
return WitPriceFeedsLegacy.at(this, target);
}
async getWitRandomnessAt(target) {
return WitRandomness.at(this, target);
}
}
exports.WitOracle = WitOracle;
class WitOracleConsumer extends WitApplianceWrapper {
constructor(witOracle, target) {
super(witOracle, "WitOracleConsumer", target);
}
static async at(witOracle, target) {
const consumer = new WitOracleConsumer(witOracle, target);
const consumerWitOracleAddr = await consumer.contract.witOracle.staticCall();
if (consumerWitOracleAddr !== witOracle.address) {
throw new Error(`${this.constructor.name} at ${target}: mismatching Wit/Oracle address (${consumerWitOracleAddr})`);
}
return consumer;
}
async pushDataReport(report, options) {
return this.contract
.pushDataReport
.populateTransaction((0, utils_1.abiEncodeDataPushReport)(report), report?.evm_proof)
.then(tx => {
tx.gasPrice = options?.gasPrice || tx?.gasPrice;
tx.gasLimit = options?.gasLimit || tx?.gasLimit;
return this.signer.sendTransaction(tx);
})
.then(response => {
if (options?.onDataPushReportTransaction)
options.onDataPushReportTransaction(response.hash);
return response.wait(options?.confirmations || 1, options?.timeout);
});
}
}
/**
* Wrapper class for the Wit/Oracle Radon Registry core contract as deployed in some supported EVM network.
* It allows formal verification of Radon Requests and Witnet-compliant data sources into such network,
* as to be securely referred on both Wit/Oracle queries pulled from within smart contracts,
* or Wit/Oracle query results pushed into smart contracts from offchain workflows.
*/
class WitOracleRadonRegistry extends WitArtifactWrapper {
constructor(signer, network) {
super(signer, network, "WitOracleRadonRegistry");
}
/// ===========================================================================================================
/// --- IWitOracleRadonRegistry -------------------------------------------------------------------------------
/**
* Determines the unique hash that would identify the given Radon Retrieval, if it was
* formally verified into the connected EVM network.
* @param retrieval Instance of a Radon Retrieval object.
*/
async determineRadonRetrievalHash(retrieval) {
return this.contract
.getFunction("verifyRadonRetrieval(uint8,string,string,string[2][],bytes)")
.staticCall(...(0, utils_1.abiEncodeRadonAsset)(retrieval))
.then(hash => {
return hash.slice(2);
});
}
/**
* Returns information related to some previously verified Radon Request, on the connected EVM network.
* @param radHash The RAD hash that uniquely identifies the Radon Request.
*/
async lookupRadonRequest(radHash) {
return this.contract
.getFunction("lookupRadonRequestBytecode(bytes32)")
.staticCall(`0x${radHash}`)
.then(bytecode => sdk_1.Witnet.Radon.RadonRequest.fromBytecode(bytecode));
}
/**
* Returns the bytecode of some previously verified Radon Request, on the connected EVM network.
* @param radHash The RAD hash that uniquely identifies the Radon Request.
*/
async lookupRadonRequestBytecode(radHash) {
return this.contract
.getFunction("lookupRadonRequestBytecode(bytes32)")
.staticCall(`0x${radHash}`);
}
/**
* Returns information about some previously verified Radon Retrieval on the connected EVM network.
* This information includes retrieval the method, URL, body, headers and the Radon script in charge
* to transform data before delivery, on the connected EVM network.
* @param radHash The RAD hash that uniquely identifies the Radon Request.
*/
async lookupRadonRetrieval(hash) {
return this.contract
.getFunction("lookupRadonRetrieval(bytes32)")
.staticCall(`0x${hash}`)
.then((result) => {
return new sdk_1.Witnet.Radon.RadonRetrieval({
method: result[1],
url: result[3],
body: result[4],
headers: Object.fromEntries(result[5]),
script: sdk_1.utils.parseRadonScript(result[6]),
});
});
}
/**
* Formally verify the given Radon Request object into the connected EVM network.
* It also verifies all the Radon Retrieval scripts (i.e. data source) the Request
* relies on, if not yet done before.
*
* Verifying Radon assets modifies the EVM storage and therefore requires
* spending gas in proportion to the number and complexity of the data sources,
* and whether these had been previously verified before or not.
*
* If the given Radon Request happened to be already verified, no gas would be actually consumed.
*
* @param request Instance of a Radon Request object.
* @param options Async EVM transaction handlers.
* @returns The RAD hash of the Radon Request, as verified on the connected EVM network.
*/
async verifyRadonRequest(request, options) {
const radHash = request.radHash;
await this.lookupRadonRequest(radHash)
.catch(async () => {
const hashes = [];
for (const index in request.sources) {
const retrieval = request.sources[index];
hashes.push(`0x${await this.verifyRadonRetrieval(retrieval, options)}`);
}
const aggregate = (0, utils_1.abiEncodeRadonAsset)(request.sourcesReducer);
const tally = (0, utils_1.abiEncodeRadonAsset)(request.witnessReducer);
if (options?.onVerifyRadonRequest) {
options.onVerifyRadonRequest(radHash);
}
await this.contract
.getFunction("verifyRadonRequest(bytes32[],(uint8,(uint8,bytes)[]),(uint8,(uint8,bytes)[]))")
.send(hashes, aggregate, tally)
.then(async (tx) => {
const receipt = await tx.wait(options?.confirmations || 1);
if (options?.onVerifyRadonRequestReceipt) {
options.onVerifyRadonRequestReceipt(receipt);
}
});
});
return radHash;
}
/**
* Formally verify the given Radon Retrieval script (i.e. data source), into the connected EVM network.
*
* Verifying Radon assets modifies the EVM storage and therefore requires
* spending gas in proportion to the size of the data source parameters (e.g. URL, body, headers, or Radon script).
*
* If the given Radon Retrieval object happened to be already verified, no EVM gas would be actually consumed.
*
* @param request Instance of a Radon Retrieval object.
* @param options Async EVM transaction handlers.
* @returns The unique hash of the Radon Retrieval object, as verified on the connected EVM network.
*/
async verifyRadonRetrieval(retrieval, options) {
return this.determineRadonRetrievalHash(retrieval)
.then(async (hash) => {
await this.lookupRadonRetrieval(hash)
.catch(async () => {
if (options?.onVerifyRadonRetrieval) {
options.onVerifyRadonRetrieval(hash);
}
await this.contract
.getFunction("verifyRadonRetrieval(uint8,string,string,string[2][],bytes)")
.send(...(0, utils_1.abiEncodeRadonAsset)(retrieval))
.then(async (tx) => {
const receipt = await tx.wait(options?.confirmations || 1);
if (options?.onVerifyRadonRetrievalReceipt) {
options.onVerifyRadonRetrievalReceipt(receipt);
}
});
});
return hash;
});
}
}
class WitOracleRadonRequestFactory extends WitApplianceWrapper {
constructor(witOracle, registry, at) {
super(witOracle, "WitOracleRadonRequestFactory", at);
this.registry = registry;
}
static async deployed(witOracle, registry) {
const deployer = new WitOracleRadonRequestFactory(witOracle, registry);
const witOracleRegistryAddress = await witOracle.contract.registry.staticCall();
if (registry.address !== witOracleRegistryAddress) {
throw new Error(`${this.constructor.name} at ${deployer.address}: uncompliant WitOracleRadonRegistry at ${registry.address})`);
}
return deployer;
}
async deployRadonRequestTemplate(template, options) {
const hashes = [];
for (const index in template.sources) {
const retrieval = template.sources[index];
const hash = `0x${await this.registry.determineRadonRetrievalHash(retrieval)}`;
await this.registry.verifyRadonRetrieval(retrieval, options);
hashes.push(hash);
}
const aggregator = (0, utils_1.abiEncodeRadonAsset)(template.sourcesReducer);
const tally = (0, utils_1.abiEncodeRadonAsset)(template.witnessReducer);
const target = await this.contract
.getFunction("buildRadonRequestTemplate(bytes32[],(uint8,(uint8,bytes)[]),(uint8,(uint8,bytes)[]))")
.staticCall(hashes, aggregator, tally);
if (options?.onDeployRadonRequestTemplate)
options.onDeployRadonRequestTemplate(target);
await this.contract
.getFunction("buildRadonRequestTemplate(bytes32[],(uint8,(uint8,bytes)[]),(uint8,(uint8,bytes)[]))")
.send(hashes, aggregator, tally)
.then(async (tx) => {
const receipt = await tx.wait(options?.confirmations || 1);
if (options?.onDeployRadonRequestTemplateReceipt) {
options.onDeployRadonRequestTemplateReceipt(receipt);
}
});
return await WitOracleRadonRequestTemplate.at(this.witOracle, target);
}
async deployRadonRequestModal(modal, options) {
const retrieval = [
modal.sources[0].method,
modal.sources[0].body || "",
modal.sources[0]?.headers ? Object.entries(modal.sources[0].headers) : [],
modal.sources[0].script?.toBytecode() || "0x",
];
const tally = (0, utils_1.abiEncodeRadonAsset)(modal.witnessReducer);
const target = await this.contract
.buildRadonRequestModal //getFunction("buildRadonRequestModal((uint8,string,string[2][],bytes),(uint8,(uint8,bytes)[]))")
.staticCall(retrieval, tally);
if (options?.onDeployRadonRequestModal)
options.onDeployRadonRequestModal(target);
await this.contract
.buildRadonRequestModal
// .getFunction("buildRadonRequestModal((uint8,string,string[2][],bytes),(uint8,(uint8,bytes)[]))")
.send(retrieval, tally)
.then(async (tx) => {
const receipt = await tx.wait(options?.confirmations || 1);
if (options?.onDeployRadonRequestModalReceipt) {
options.onDeployRadonRequestModalReceipt(receipt);
}
});
return await WitOracleRadonRequestModal.at(this.witOracle, target);
}
async verifyRadonRequest(request, _options) {
// TODO:
//
return request.radHash;
}
}
class WitOracleRadonRequestModal extends WitApplianceWrapper {
constructor(witOracle, at) {
super(witOracle, "IWitOracleRadonRequestModal", at);
}
static async at(witOracle, target) {
const template = new WitOracleRadonRequestModal(witOracle, target);
const templateWitOracleAddr = await template.contract.witOracle.staticCall();
if (templateWitOracleAddr !== witOracle.address) {
throw new Error(`${this.constructor.name} at ${target}: mismatching Wit/Oracle address (${templateWitOracleAddr})`);
}
return template;
}
async getDataResultType() {
return this.contract
.getFunction("getDataResultType()")
.staticCall()
.then((result) => {
switch (Number(result)) {
case 1: return "array";
case 2: return "boolean";
case 3: return "bytes";
case 4: return "integer";
case 5: return "float";
case 6: return "map";
case 7: return "string";
default:
return "any";
}
});
}
async getDataSourcesArgsCount() {
return this.contract
.getFunction("getDataSourcesArgsCount()")
.staticCall()
.then((argsCount) => Number(argsCount));
}
async getRadonModalRetrieval() {
return this.contract
.getFunction("getRadonModalRetrieval()")
.staticCall()
.then((result) => {
return new sdk_1.Witnet.Radon.RadonRetrieval({
method: result[1],
url: result[3],
body: result[4],
headers: Object.fromEntries(result[5]),
script: sdk_1.utils.parseRadonScript(result[6]),
});
});
}
async verifyRadonRequest(dataProviders, commonRetrievalArgs, options) {
const argsCount = await this.getDataSourcesArgsCount();
if (argsCount != 1 + (commonRetrievalArgs?.length || 0)) {
throw TypeError(`${this.constructor.name}@${this.address}: unmatching args count != ${argsCount - 1}.`);
}
const method = this.contract.getFunction("verifyRadonRequest(string[],string[])");
const radHash = (await method.staticCall(commonRetrievalArgs || [], dataProviders)).slice(2);
try {
await (await this.witOracle.getWitOracleRadonRegistry()).lookupRadonRequestBytecode(radHash);
}
catch {
if (options?.onVerifyRadonRequest)
options.onVerifyRadonRequest(radHash);
await method
.send(commonRetrievalArgs || [], dataProviders)
.then(tx => tx.wait(options?.confirmations || 1))
.then(receipt => {
if (options?.onVerifyRadonRequestReceipt) {
options.onVerifyRadonRequestReceipt(receipt);
}
return radHash;
});
}
return radHash;
}
}
class WitOracleRadonRequestTemplate extends WitApplianceWrapper {
constructor(witOracle, at) {
super(witOracle, "IWitOracleRadonRequestTemplate", at);
}
static async at(witOracle, target) {
const template = new WitOracleRadonRequestTemplate(witOracle, target);
const templateWitOracleAddr = await template.contract.witOracle.staticCall();
if (templateWitOracleAddr !== witOracle.address) {
throw new Error(`${this.constructor.name} at ${target}: mismatching Wit/Oracle address (${templateWitOracleAddr})`);
}
return template;
}
async getDataResultType() {
return this.contract
.getFunction("getDataResultType()")
.staticCall()
.then((result) => {
switch (Number(result)) {
case 1: return "array";
case 2: return "boolean";
case 3: return "bytes";
case 4: return "integer";
case 5: return "float";
case 6: return "map";
case 7: return "string";
default:
return "any";
}
});
}
async getDataSources() {
return this.contract
.getFunction("getDataSources()")
.staticCall()
.then((results) => {
return results.map(result => new sdk_1.Witnet.Radon.RadonRetrieval({
method: result[1],
url: result[3],
body: result[4],
headers: Object.fromEntries(result[5]),
script: sdk_1.utils.parseRadonScript(result[6]),
}));
});
}
async getDataSourcesArgsCount() {
return this.contract
.getFunction("getDataSourcesArgsCount()")
.staticCall()
.then((dims) => dims.map(dim => Number(dim)));
}
async verifyRadonRequest(args, options) {
const argsCount = await this.getDataSourcesArgsCount();
let encodedArgs = [];
if (typeof args === 'string') {
if (argsCount.length === 1 && argsCount[0] === 1) {
encodedArgs = [[args]];
}
}
else if (Array.isArray(args)) {
if (Array.isArray(args[0])) {
if (argsCount.length === args.length
&& !args.find((subargs, index) => subargs.length !== argsCount[index])) {
encodedArgs = args;
}
}
else if (args.length === argsCount[0]
&& !args.find(arg => typeof arg !== 'string')) {
encodedArgs = [args];
}
}
if (encodedArgs.length === 0) {
throw TypeError(`${this.constructor.name}@${this.address}: unmatching args count != [${argsCount}, ].`);
}
const method = this.contract.getFunction("verifyRadonRequest(string[][])");
const radHash = (await method.staticCall(encodedArgs)).slice(2);
try {
await (await this.witOracle.getWitOracleRadonRegistry()).lookupRadonRequestBytecode(radHash);
}
catch {
if (options?.onVerifyRadonRequest)
options.onVerifyRadonRequest(radHash);
await method
.send(encodedArgs)
.then(tx => tx.wait(options?.confirmations || 1))
.then(receipt => {
if (options?.onVerifyRadonRequestReceipt) {
options.onVerifyRadonRequestReceipt(receipt);
}
return radHash;
});
}
return radHash;
}
}
class WitPriceFeeds extends WitApplianceWrapper {
constructor(witOracle, at) {
super(witOracle, "WitPriceFeeds", at);
}
static async at(witOracle, target) {
const priceFeeds = new WitPriceFeeds(witOracle, target);
const oracleAddr = await priceFeeds.contract.witOracle.staticCall();
if (oracleAddr !== witOracle.address) {
throw new Error(`${this.constructor.name} at ${target}: mismatching Wit/Oracle address (${oracleAddr})`);
}
return priceFeeds;
}
async createChainlinkAggregator(id4, options) {
const evmTransaction = await this.contract
.createChainlinkAggregator
.populateTransaction(id4);
evmTransaction.gasPrice = options?.evmGasPrice || evmTransaction?.gasPrice;
return this.signer
.sendTransaction(evmTransaction)
.then(response => {
if (options?.onCreateChainlinkAggregatorTransaction) {
options.onCreateChainlinkAggregatorTransaction(response.hash);
}
return response.wait(options?.evmConfirmations || 1, options?.evmTimeout);
})
.then(receipt => {
if (options?.onCreateChainlinkAggregatorTransactionReceipt) {
options.onCreateChainlinkAggregatorTransactionReceipt(receipt);
}
return receipt;
});
}
async determineChainlinkAggregatorAddress(id4) {
return this.contract
.createChainlinkAggregator
.staticCall(id4);
}
async getEvmFootprint() {
return this.contract
.footprint
.staticCall();
}
async getPrice(id4, ema) {
return this.contract
.getPrice
.staticCall(id4, ema)
.then((result) => ({
delta1000: BigInt(result.conf),
exponent: Number(result.expo),
timestamp: BigInt(result.publishTime),
trackHash: result.track,
value: Number(result.price) / 10 ** Number(result.expo),
}));
}
async getPriceNotOlderThan(id4, ema, age) {
return this.contract
.getPriceNotOlderThan
.staticCall(id4, ema, age)
.then((result) => ({
delta1000: BigInt(result.conf),
exponent: Number(result.expo),
timestamp: BigInt(result.publishTime),
trackHash: result.track,
value: Number(result.price) / 10 ** Number(result.expo),
}));
}
async getPriceUnsafe(id4, ema) {
return this.contract
.getPriceUnsafe
.staticCall(id4, ema)
.then((result) => ({
delta1000: BigInt(result.conf),
exponent: Number(result.expo),
timestamp: BigInt(result.publishTime),
trackHash: result.track,
value: Number(result.price) / 10 ** Number(result.expo),
}));
}
async isCaptionSupported(caption) {
return this.contract
.supportsCaption
.staticCall(caption);
}
async lookupPriceFeed(id4) {
return this.contract
.lookupPriceFeed
.staticCall(id4)
.then((result) => ({
id: result.id,
exponent: Number(result.exponent),
symbol: result.symbol,
mapper: {
algorithm: (0, utils_1.abiDecodePriceFeedMappingAlgorithm)(result.mapper.algo),
description: result.mapper.desc,
dependencies: result.mapper.deps,
},
oracle: {
address: result.oracle.addr,
name: result.oracle.name,
dataBytecode: result.oracle.dataBytecode,
dataSources: result.oracle.dataSources,
interfaceId: result.oracle.interfaceId,
},
updateConditions: {
computeEMA: result.computeEma,
cooldownSecs: result.cooldownSecs,
heartbeatSecs: result.heartbeatSecs,
maxDeviation1000: result.maxDeviation100,
},
lastUpdate: {
delta1000: BigInt(result.conf),
exponent: Number(result.expo),
timestamp: BigInt(result.publishTime),
trackHash: result.track,
value: Number(result.price) / 10 ** Number(result.expo),
},
}));
}
async lookupPriceFeedCaption(id4) {
return this.contract
.lookupSymbol
.staticCall(id4);
}
async lookupPriceFeedExponent(id4) {
return this.contract
.lookupPriceFeedExponent
.staticCall(id4)
.then(result => Number(result));
}
async lookupPriceFeedID(id4) {
return this.contract
.lookupPriceFeedID
.staticCall(id4);
}
async lookupPriceFeeds() {
return this.contract
.lookupPriceFeeds
.staticCall()
.then(results => results.map((result) => ({
id: result.id,
exponent: Number(result.exponent),
symbol: result.symbol,
mapper: {
algorithm: "", // todo: PriceFeedMappers[result.mapper.algo],
description: result.mapper.desc,
dependencies: result.mapper.deps,
},
oracle: {
address: result.oracle.addr,
name: result.oracle.name,
dataBytecode: result.oracle.dataBytecode,
dataSources: result.oracle.dataSources,
interfaceId: result.oracle.interfaceId,
},
updateConditions: {
computeEMA: result.computeEma,
cooldownSecs: result.cooldownSecs,
heartbeatSecs: result.heartbeatSecs,
maxDeviation1000: result.maxDeviation100,
},
lastUpdate: {
delta1000: BigInt(result.conf),
exponent: Number(result.expo),
timestamp: BigInt(result.publishTime),
trackHash: result.track,
value: BigInt(result.price),
},
})));
}
}
class WitPriceFeedsLegacy extends WitApplianceWrapper {
constructor(witOracle, at) {
super(witOracle, "WitPriceFeedsLegacy", at);
}
static async at(witOracle, target) {
const priceFeeds = new WitPriceFeedsLegacy(witOracle, target);
const oracleAddr = await priceFeeds.contract.witnet.staticCall();
if (oracleAddr !== witOracle.address) {
throw new Error(`WitPriceFeedsLegacy at ${target}: mismatching Wit/Oracle address (${oracleAddr})`);
}
return priceFeeds;
}
async getEvmFootprint() {
return this.contract
.footprint
.staticCall();
}
// public async getPrice(id4: Witnet.HexString): Promise<PriceFeedUpdate> {
// return this.contract
// .latestPrice
// .staticCall(id4)
// .then((result: any) => ({
// timestamp: BigInt(result?.timestamp),
// trackHash: result?.drTxHash,
// value: Number(result?.value) / 10 ** exponent,
// }))
// }
async isCaptionSupported(caption) {
return this.contract
.supportsCaption
.staticCall(caption);
}
// public async lookupPriceFeed(id4: Witnet.HexString): Promise<PriceFeed> {
// return this.contract
// .lookupPriceFeed
// .staticCall(id4)
// .then((result: any) => ({
// id: result.id,
// exponent: Number(result.exponent),
// symbol: result.symbol,
// mapper: {
// algorithm: abiDecodePriceFeedMappingAlgorithm(result.mapper.algo),
// description: result.mapper.desc,
// dependencies: result.mapper.deps,
// },
// oracle: {
// address: result.oracle.addr,
// name: result.oracle.name,
// dataSources: result.oracle.dataSources,
// interfaceId: result.oracle.interfaceId,
// },
// updateConditions: {
// computeEMA: result.computeEma,
// cooldownSecs: result.cooldownSecs,
// heartbeatSecs: result.heartbeatSecs,
// maxDeviation1000: result.maxDeviation100,
// },
// lastUpdate: {
// delta1000: BigInt(result.conf),
// exponent: Number(result.expo),
// timestamp: BigInt(result.publishTime),
// trackHash: result.track,
// value: BigInt(result.price),
// },
// }))
// }
async lookupPriceFeedCaption(id4) {
return this.contract
.lookupCaption
.staticCall(id4);
}
async lookupPriceFeedExponent(id4) {
return this.contract
.lookupDecimals
.staticCall(id4)
.then(result => Number(result));
}
async lookupPriceFeeds() {
const interfaceId = await this.witOracle.getEvmImplSpecs();
let priceFeeds = await this.contract
.supportedFeeds
.staticCall()
.then(results => {
const [id4s, captions, dataSources] = results;
return id4s.map((id4, index) => ({
id: id4,
exponent: Number(captions[index].split('-').slice(-1)[0]),
symbol: captions[index], //.split('-').slice(1, -1).join('-'),
oracle: {
address: this.witOracle.address,
name: "WitOracle",
dataSources: dataSources[index],
interfaceId,
}
}));
});
let latestPrices = await this.contract
.latestPrices
.staticCall(priceFeeds.map(pf => pf.id));
return priceFeeds.map((pf, index) => ({
...pf,
lastUpdate: {
timestamp: latestPrices[index].timestamp,
trackHash: latestPrices[index].drTxHash,
value: Number(latestPrices[index].value) / 10 ** pf.exponent
}
}));
}
}
class WitRandomness extends WitApplianceWrapper {
constructor(witOracle, at) {
super(witOracle, "WitRandomness", at);
this._legacy = new ethers_1.Contract(at, [
"function fetchRandomnessAfter(uint256) public view returns (bytes32)",
"function fetchRandomnessAfterProof(uint256) public view returns ((bytes32,uint64,bytes32,uint256))",
"function getRandomizePrevBlock(uint256) public view returns (uint256)",
"function getRandomizeStatus(uint256) public view returns (uint8)",
"function isRandomized(uint256) public view returns (bool)",
], this.signer);
}
static async at(witOracle, target) {
const randomizer = new WitRandomness(witOracle, target);
try {
let oracleAddr;
try {
oracleAddr = await randomizer.contract.witOracle.staticCall();
}
catch {
const abi = ["function witnet() public view returns (address)",];
const contract = new ethers_1.Contract(target, abi, randomizer.signer);
oracleAddr = await contract.witnet.staticCall();
}
if (oracleAddr !== witOracle.address) {
throw new Error(`WitRandomness at ${target}: mismatching Wit/Oracle address (${oracleAddr})`);
}
}
catch (error) {
throw new Error(`WitRandomness at ${target}: cannot fetch Wit/Oracle address\n${error?.stack?.split('\n')[0] || error}`);
}
return randomizer;
}
async estimateRandomizeFee(evmGasPrice) {
return this.contract
.getFunction("estimateRandomizeFee(uint256)")
.staticCall(evmGasPrice);
}
async getRandomnessAfter(evmBlockNumber) {
if (await this.isRandomized(evmBlockNumber)) {
let randomness;
try {
try {
randomness = await this.contract.fetchRandomnessAfter.staticCall(evmBlockNumber);
}
catch {
randomness = await this._legacy.fetchRandomnessAfter.staticCall(evmBlockNumber);
}
}
catch (error) {
throw new Error(`${this.constructor.name}: cannot fetch randomness on block ${evmBlockNumber}.\n${error?.stack?.split('\n')[0] || error}`);
}
return randomness;
}
else {
return undefined;
}
}
async fetchRandomnessAfterProof(evmBlockNumber) {
let result;
try {
try {
result = await this.contract.fetchRandomnessAfterProof.staticCall(evmBlockNumber);
}
catch {
result = await this._legacy
.fetchRandomnessAfterProof.staticCall(evmBlockNumber)
.then(result => [result[2], result[1]]);
}
}
catch (error) {
throw new Error(`${this.constructor.name}: cannot fetch randomness proof on block ${evmBlockNumber}.\n${error?.stack?.split('\n')[0] || error}`);
}
return result;
}
async getLastRandomizeBlock() {
return this.contract
.getFunction("getLastRandomizeBlock()")
.staticCall();
}
async getRandomizePrevBlock(evmBlockNumber) {
let evmPrevBlock;
try {
try {
evmPrevBlock = await this.contract.getRandomizePrevBlock.staticCall(evmBlockNumber).then(result => BigInt(result));
}
catch {
evmPrevBlock = await this._legacy.getRandomizePrevBlock.staticCall(evmBlockNumber).then(result => BigInt(result));
}
}
catch (error) {
throw new Error(`${this.constructor.name}: cannot fetch previous randomize block before ${evmBlockNumber}.\n${error?.stack?.split('\n')[0] || error}`);
}
return evmPrevBlock;
}
async getRandomizeStatus(evmBlockNumber) {
let result;
try {
try {
result = await this.contract.getRandomizeStatus.staticCall(evmBlockNumber);
}
catch {
result = await this._legacy.getRandomizeStatus.staticCall(evmBlockNumber);
}
}
catch (error) {
throw new Error(`${this.constructor.name}: cannot get randomize status on block ${evmBlockNumber}.\n${error?.stack?.split('\n')[0] || error}`);
}
switch (Number(result)) {
case 1: return "Awaiting";
case 2: return "Ready";
case 3: return "Error";
case 4: return "Finalizing";
default:
return "Void";
}
}
async getRandomizeStatusDescription(evmBlockNumber) {
try {
return this.contract
.getFunction("getRandomizeStatusDescription(uint96)")
.staticCall(evmBlockNumber);
}
catch {
return this.getRandomizeStatus(evmBlockNumber);
}
}
async getRandomizeHistory(limit = 16) {
const result = [];
let evmBlockNumber = await this.getLastRandomizeBlock();
while (evmBlockNumber && result.length < limit) {
const evmRandomizeStatus = await this.getRandomizeStatus(evmBlockNumber);
const witResult = await this.getRandomnessAfter(evmBlockNumber);
let witResultDrTxHash, witResultTimestamp;
if (witResult) {
[witResultDrTxHash, witResultTimestamp] = await this.fetchRandomnessAfterProof(evmBlockNumber);
}
result.push({ evmBlockNumber, evmRandomizeStatus, witResult, witResultDrTxHash, witResultTimestamp });
evmBlockNumber = await this.getRandomizePrevBlock(evmBlockNumber);
}
return result;
}
async isRandomized(evmBlockNumber) {
let result;
try {
try {
result = await this.contract.isRandomized.staticCall(evmBlockNumber);
}
catch {
result = await this._legacy.isRandomized.staticCall(evmBlockNumber);
}
}
catch (error) {
throw new Error(`${this.constructor.name}: cannot get randomize status on block ${evmBlockNumber}.\n${error?.stack?.split('\n')[0] || error}`);
}
return result;
}
async randomize(options) {
const evmGasPrice = options?.evmGasPrice || (await this.provider.getFeeData()).gasPrice || 0n;
const evmRandomizeFee = await this.estimateRandomizeFee(evmGasPrice);
const evmTransaction = await this.contract
.getFunction("randomize()")
.populateTransaction();
evmTransaction.gasPrice = evmGasPrice || evmTransaction?.gasPrice;
evmTransaction.value = evmRandomizeFee;
return this.signer
.sendTransaction(evmTransaction)
.then(response => {
if (options?.onRandomizeTransaction)
options.onRandomizeTransaction(response.hash);
return response.wait(options?.evmConfirmations || 1, options?.evmTimeout);
})
.then(receipt => {
if (options?.onRandomizeTransactionReceipt)
options.onRandomizeTransactionReceipt(receipt);
return receipt;
});
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid3JhcHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbGliL3dyYXBwZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLG1DQWVlO0FBRWYscUNBQTJDO0FBRTNDLG1DQVNnQjtBQVloQixNQUFlLGVBQWU7SUFFMUIsWUFBYSxNQUFxQixFQUFFLE9BQWUsRUFBRSxHQUE2QixFQUFFLE1BQWM7UUFDOUYsSUFBSSxDQUFDLE9BQU8sR0FBRyxNQUFNLENBQUE7UUFDckIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLGlCQUFRLENBQUMsTUFBTSxFQUFFLEdBQUcsRUFBRSxNQUF3QixDQUFDLENBQUE7UUFDbkUsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUE7UUFDdEIsSUFBSSxDQUFDLFFBQVEsR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFBO1FBQy9CLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFBO0lBQ3hCLENBQUM7SUEyQkQ7OztPQUdHO0lBQ0ksS0FBSyxDQUFDLGVBQWU7UUFDeEIsT0FBTyxJQUFJLENBQUMsUUFBUTthQUNmLFdBQVcsQ0FBQyxTQUFTLENBQUM7YUFDdEIsVUFBVSxFQUFFLENBQUE7SUFDckIsQ0FBQztJQUVEOzs7T0FHRztJQUNJLEtBQUssQ0FBQyxlQUFlO1FBQ3hCLE9BQU8sSUFBSSxDQUFDLFFBQVE7YUFDZixXQUFXLENBQUMsU0FBUyxDQUFDO2FBQ3RCLFVBQVUsRUFBRSxDQUFBO0lBQ3JCLENBQUM7SUFFRDs7O09BR0c7SUFDSSxLQUFLLENBQUMsaUJBQWlCO1FBQzFCLElBQUksT0FBTyxDQUFBO1FBQ1gsSUFBSSxDQUFDO1lBQ0QsT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLFFBQVE7aUJBQ3hCLElBQUksQ0FBQztnQkFDRixFQUFFLEVBQUUsSUFBSSxDQUFDLE9BQU87Z0JBQ2hCLElBQUksRUFBRSxZQUFZLEVBQUUsMEJBQTBCO2FBQ2pELENBQUM7aUJBQ0QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsaUJBQVEsQ0FBQyxlQUFlLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxRQUFRLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQztpQkFDckUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDM0MsQ0FBQztRQUFDLE9BQU8sSUFBSSxFQUFFLENBQUM7WUFDWixPQUFPLGFBQWEsQ0FBQTtRQUN4QixDQUFDO1FBQ0QsT0FBTyxPQUFPLENBQUE7SUFDbEIsQ0FBQztDQUNKO0FBRUQsTUFBZSxrQkFBbUIsU0FBUSxlQUFlO0lBRXJELFlBQWEsTUFBcUIsRUFBRSxPQUFlLEVBQUUsUUFBZ0IsRUFBRSxFQUFXO1FBQzlFLE1BQU0sSUFBSSxHQUE2QyxZQUFJLENBQUE7UUFDM0QsTUFBTSxNQUFNLEdBQUcsRUFBRSxJQUFJLElBQUEsOEJBQXNCLEVBQUMsT0FBTyxDQUFDLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFBO1FBQ3BFLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUM3QixNQUFNLElBQUksS0FBSyxDQUFDLGVBQWUsT0FBTyxrQ0FBa0MsUUFBUSxFQUFFLENBQUMsQ0FBQTtRQUN2RixDQUFDO2FBQU0sQ0FBQztZQUNKLEtBQUssQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQTtRQUNsRCxDQUFDO0lBQ0wsQ0FBQztDQUNKO0FBRUQsTUFBZSxtQkFBb0IsU0FBUSxlQUFlO0lBSXRELFlBQWEsU0FBb0IsRUFBRSxRQUFnQixFQUFFLEVBQVc7UUFDNUQsTUFBTSxJQUFJLEdBQTZDLFlBQUksQ0FBQTtRQUMzRCxNQUFNLFNBQVMsR0FBRyxJQUFBLDhCQUFzQixFQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUMzRCxNQUFNLE1BQU0sR0FBRyxFQUFFLElBQUksU0FBUyxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxTQUFTLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFBO1FBQzNFLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUM3QixNQUFNLElBQUksS0FBSyxDQUFDLGVBQWUsU0FBUyxDQUFDLE9BQU8sK0JBQStCLFFBQVEsRUFBRSxDQUFDLENBQUE7UUFDOUYsQ0FBQztRQUNELEtBQUssQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLFNBQVMsQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFBO1FBQ2xFLElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFBO0lBQzlCLENBQUM7Q0FDSjtBQUVEOzs7Ozs7R0FNRztBQUNILE1BQWEsU0FBVSxTQUFRLGtCQUFrQjtJQUU3QyxZQUFhLE1BQXFCLEVBQUUsT0FBZTtRQUMvQyxLQUFLLENBQUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxXQUFXLENBQUMsQ0FBQTtJQUN2QyxDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0ksTUFBTSxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsR0FBVyxFQUFFLFFBQTBCO1FBQ3RFLE1BQU0sUUFBUSxHQUFHLEl