onchain oracle support
This commit is contained in:
parent
beea051c8a
commit
2bcfa0003d
26
package-lock.json
generated
26
package-lock.json
generated
@ -42,6 +42,12 @@
|
||||
"integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mockery": {
|
||||
"version": "1.4.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/mockery/-/mockery-1.4.29.tgz",
|
||||
"integrity": "sha1-m6It838H43gP/4Ux0aOOYz+UV6U=",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.5.tgz",
|
||||
@ -118,6 +124,11 @@
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
|
||||
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
|
||||
@ -180,6 +191,15 @@
|
||||
"type-detect": "^4.0.5"
|
||||
}
|
||||
},
|
||||
"chai-as-promised": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz",
|
||||
"integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"check-error": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
@ -774,6 +794,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mockery": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mockery/-/mockery-2.1.0.tgz",
|
||||
"integrity": "sha512-9VkOmxKlWXoDO/h1jDZaS4lH33aWfRiJiNT/tKj+8OGzrcFDLo8d0syGdbsc3Bc4GvRXPb+NMMvojotmuGJTvA==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"prepare": "npm run build",
|
||||
"prepublishOnly": "npm run lint",
|
||||
"scripts": {
|
||||
"test": "mocha -r ts-node/register tests/*.test.ts",
|
||||
"test": "mocha --timeout 30000 -r ts-node/register tests/*.test.ts",
|
||||
"build": "tsc",
|
||||
"lint": "tslint -p tsconfig.json"
|
||||
},
|
||||
@ -16,14 +16,18 @@
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.11",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/mockery": "^1.4.29",
|
||||
"chai": "^4.2.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"mocha": "^7.2.0",
|
||||
"mockery": "^2.1.0",
|
||||
"tslint": "^6.1.2",
|
||||
"tslint-config-standard": "^9.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^14.0.5",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"ts-node": "^8.10.1",
|
||||
"typescript": "^3.9.3"
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
import { Oracle } from './types';
|
||||
import { OffChainOracle, OnChainOracle } from './types';
|
||||
|
||||
const ethgasstation: Oracle = {
|
||||
const ethgasstation: OffChainOracle = {
|
||||
name: 'ethgasstation',
|
||||
url: 'https://ethgasstation.info/json/ethgasAPI.json',
|
||||
instantPropertyName: 'fastest',
|
||||
@ -11,7 +11,7 @@ const ethgasstation: Oracle = {
|
||||
denominator: 10
|
||||
};
|
||||
|
||||
const zoltu: Oracle = {
|
||||
const zoltu: OffChainOracle = {
|
||||
name: 'zoltu',
|
||||
url: 'https://gas-oracle.zoltu.io/',
|
||||
instantPropertyName: 'percentile_99',
|
||||
@ -21,8 +21,18 @@ const zoltu: Oracle = {
|
||||
denominator: 1
|
||||
};
|
||||
|
||||
const chainLink: OnChainOracle = {
|
||||
name: 'chainLink',
|
||||
callData: '0x50d25bcd',
|
||||
contract: '0xA417221ef64b1549575C977764E651c9FAB50141',
|
||||
denominator: '1000000000'
|
||||
};
|
||||
|
||||
export default {
|
||||
oracles: [
|
||||
offChainOracles: [
|
||||
ethgasstation, zoltu
|
||||
],
|
||||
onChainOracles: [
|
||||
chainLink
|
||||
]
|
||||
};
|
||||
|
73
src/index.ts
73
src/index.ts
@ -1,6 +1,7 @@
|
||||
import fetch from 'node-fetch';
|
||||
import config from './config';
|
||||
import { GasPrice, Oracle } from './types';
|
||||
import { GasPrice, OffChainOracle, OnChainOracle } from './types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
export class GasPriceOracle {
|
||||
lastGasPrice: GasPrice = {
|
||||
@ -9,9 +10,16 @@ export class GasPriceOracle {
|
||||
standard: 10,
|
||||
low: 1
|
||||
};
|
||||
defaultRpc = 'https://api.mycryptoapi.com/eth';
|
||||
|
||||
async fetchGasPrices(): Promise<GasPrice> {
|
||||
for (let oracle of config.oracles) {
|
||||
constructor(defaultRpc?: string) {
|
||||
if (defaultRpc) {
|
||||
this.defaultRpc = defaultRpc;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchGasPricesOffChain(throwIfFailsToFetch = true): Promise<GasPrice> {
|
||||
for (let oracle of config.offChainOracles) {
|
||||
const { name, url, instantPropertyName, fastPropertyName, standardPropertyName, lowPropertyName, denominator } = oracle;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
@ -27,6 +35,7 @@ export class GasPriceOracle {
|
||||
low: parseFloat(gas[lowPropertyName]) / denominator
|
||||
};
|
||||
this.lastGasPrice = gasPrices;
|
||||
return this.lastGasPrice;
|
||||
} else {
|
||||
throw new Error(`Fetch gasPrice from ${name} oracle failed. Trying another one...`);
|
||||
}
|
||||
@ -34,11 +43,63 @@ export class GasPriceOracle {
|
||||
console.error(e.message);
|
||||
}
|
||||
}
|
||||
// TODO: additional arg `throwIfFailsToFetch` that throws if it fails to fetch from all oracles
|
||||
if (throwIfFailsToFetch) {
|
||||
throw new Error('All oracles are down. Probaly network error.');
|
||||
}
|
||||
return this.lastGasPrice;
|
||||
}
|
||||
|
||||
addOracle(oracle: Oracle) {
|
||||
config.oracles.push(oracle);
|
||||
async fetchGasPricesOnChain(throwIfFailsToFetch = true): Promise<GasPrice> {
|
||||
for (let oracle of config.onChainOracles) {
|
||||
const { name, callData, contract, denominator } = oracle;
|
||||
let { rpc } = oracle;
|
||||
rpc = rpc ? rpc : this.defaultRpc;
|
||||
const body = { jsonrpc: '2.0',
|
||||
id: 1337,
|
||||
method: 'eth_call',
|
||||
params: [{ 'data': callData, 'to': contract }, 'latest']
|
||||
};
|
||||
try {
|
||||
const response = await fetch(rpc, {
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
method: 'POST'
|
||||
});
|
||||
if (response.status === 200) {
|
||||
const { result } = await response.json();
|
||||
let fastGasPrice = new BigNumber(result);
|
||||
if (fastGasPrice.isZero()) {
|
||||
throw new Error(`${name} oracle provides corrupted values`);
|
||||
}
|
||||
fastGasPrice = fastGasPrice.div(denominator);
|
||||
const gasPrices: GasPrice = {
|
||||
instant: fastGasPrice.multipliedBy(1.3).toNumber(),
|
||||
fast: fastGasPrice.toNumber(),
|
||||
standard: fastGasPrice.multipliedBy(0.85).toNumber(),
|
||||
low: fastGasPrice.multipliedBy(0.5).toNumber()
|
||||
};
|
||||
this.lastGasPrice = gasPrices;
|
||||
return this.lastGasPrice;
|
||||
} else {
|
||||
throw new Error(`Fetch gasPrice from ${name} oracle failed. Trying another one...`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.message);
|
||||
}
|
||||
}
|
||||
if (throwIfFailsToFetch) {
|
||||
throw new Error('All oracles are down. Probaly network error.');
|
||||
}
|
||||
return this.lastGasPrice;
|
||||
}
|
||||
|
||||
addOffChainOracle(oracle: OffChainOracle) {
|
||||
config.offChainOracles.push(oracle);
|
||||
}
|
||||
|
||||
addOnChainOracle(oracle: OnChainOracle) {
|
||||
config.onChainOracles.push(oracle);
|
||||
}
|
||||
}
|
||||
|
24
src/types.ts
24
src/types.ts
@ -1,11 +1,19 @@
|
||||
export type Oracle = {
|
||||
name: string
|
||||
url: string
|
||||
instantPropertyName: string,
|
||||
fastPropertyName: string,
|
||||
standardPropertyName: string,
|
||||
lowPropertyName: string,
|
||||
denominator: number
|
||||
export type OffChainOracle = {
|
||||
name: string;
|
||||
url: string;
|
||||
instantPropertyName: string;
|
||||
fastPropertyName: string;
|
||||
standardPropertyName: string;
|
||||
lowPropertyName: string;
|
||||
denominator: number;
|
||||
};
|
||||
|
||||
export type OnChainOracle = {
|
||||
name: string;
|
||||
rpc?: string;
|
||||
contract: string;
|
||||
callData: string;
|
||||
denominator: string;
|
||||
};
|
||||
|
||||
export type GasPrice = {
|
||||
|
@ -1,10 +1,91 @@
|
||||
import { GasPriceOracle } from '../src/index';
|
||||
import { GasPrice } from '../src/types';
|
||||
import mockery from 'mockery';
|
||||
import chai from 'chai';
|
||||
|
||||
describe('Get gas price', function () {
|
||||
chai.use(require('chai-as-promised'));
|
||||
chai.should();
|
||||
|
||||
describe('fetchGasPricesOffChain', function () {
|
||||
it('should work', async function () {
|
||||
const oracle = new GasPriceOracle();
|
||||
const gas: GasPrice = await oracle.fetchGasPrices();
|
||||
console.log(gas);
|
||||
const gas: GasPrice = await oracle.fetchGasPricesOffChain();
|
||||
|
||||
gas.instant.should.be.a('number');
|
||||
gas.fast.should.be.a('number');
|
||||
gas.standard.should.be.a('number');
|
||||
gas.low.should.be.a('number');
|
||||
|
||||
gas.instant.should.be.above(gas.fast);
|
||||
gas.fast.should.be.above(gas.standard);
|
||||
gas.standard.should.be.above(gas.low);
|
||||
gas.low.should.not.be.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('throw checks', function () {
|
||||
before('before', function () {
|
||||
// Mocking the mod1 module
|
||||
let fetchMock = () => {
|
||||
throw new Error('Mocked for tests');
|
||||
};
|
||||
|
||||
// replace the module with mock for any `require`
|
||||
mockery.registerMock('node-fetch', fetchMock);
|
||||
|
||||
// set additional parameters
|
||||
mockery.enable({
|
||||
useCleanCache: true,
|
||||
// warnOnReplace: false,
|
||||
warnOnUnregistered: false
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if all offchain oracles are down', async function () {
|
||||
const { GasPriceOracle } = require('../src/index');
|
||||
const oracle = new GasPriceOracle();
|
||||
await oracle.fetchGasPricesOffChain().should.be.rejectedWith('All oracles are down. Probaly network error.');
|
||||
});
|
||||
|
||||
it('should throw if all onchain oracles are down', async function () {
|
||||
const { GasPriceOracle } = require('../src/index');
|
||||
const oracle = new GasPriceOracle();
|
||||
await oracle.fetchGasPricesOnChain().should.be.rejectedWith('All oracles are down. Probaly network error.');
|
||||
});
|
||||
|
||||
it('should not throw if throwIfFailsToFetch is false', async function () {
|
||||
const { GasPriceOracle } = require('../src/index');
|
||||
const oracle = new GasPriceOracle();
|
||||
await oracle.fetchGasPricesOnChain(false);
|
||||
});
|
||||
|
||||
it('should not throw if throwIfFailsToFetch is false', async function () {
|
||||
const { GasPriceOracle } = require('../src/index');
|
||||
const oracle = new GasPriceOracle();
|
||||
await oracle.fetchGasPricesOffChain(false);
|
||||
});
|
||||
|
||||
after('after', function () {
|
||||
after(function () {
|
||||
mockery.disable();
|
||||
mockery.deregisterMock('node-fetch');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchGasPricesOnChain', function () {
|
||||
it('should work', async function () {
|
||||
const oracle = new GasPriceOracle();
|
||||
const gas: GasPrice = await oracle.fetchGasPricesOnChain();
|
||||
|
||||
gas.instant.should.be.a('number');
|
||||
gas.fast.should.be.a('number');
|
||||
gas.standard.should.be.a('number');
|
||||
gas.low.should.be.a('number');
|
||||
|
||||
gas.instant.should.be.above(gas.fast);
|
||||
gas.fast.should.be.above(gas.standard);
|
||||
gas.standard.should.be.above(gas.low);
|
||||
gas.low.should.not.be.equal(0);
|
||||
});
|
||||
});
|
||||
|
@ -3,7 +3,8 @@
|
||||
"defaultSeverity": "error",
|
||||
"rules": {
|
||||
"semicolon": [true, "always"],
|
||||
"space-before-function-paren": [true, {"anonymous": "always", "named": "never", "asyncArrow": "always"}]
|
||||
"space-before-function-paren": [true, {"anonymous": "always", "named": "never", "asyncArrow": "always"}],
|
||||
"type-literal-delimiter": true
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user