Skip to content

Commit a238d31

Browse files
authored
Add SDR calculation (#30)
* - implement SDR price calculation from sdr basket settings * - added log output * - only calculate SDR if not provided from price server * - wrong type of variable * - fixed typo - add fixed price for USD * - type fixed * - move logic from feeder to price server * - move to adjustPrices * - fixed invalid structure of prices * - allow sdr basket being passed by env * - make SDR a correct fiat pair * - rework parsing of website * - remove logs * - bump version
1 parent ba0ca90 commit a238d31

File tree

8 files changed

+102
-15
lines changed

8 files changed

+102
-15
lines changed

feeder/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

feeder/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@classic-terra/oracle-feeder",
3-
"version": "3.1.4",
3+
"version": "3.1.5",
44
"main": "src/index.ts",
55
"license": "Apache-2.0",
66
"scripts": {

price-server/config/default-sample.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,4 +587,12 @@ module.exports = {
587587
apiKey: '', // necessary
588588
},
589589
},
590+
sdrBasket: {
591+
// to calculate SDR value if not available from fiat providers
592+
USD: '0.57813',
593+
EUR: '0.37379',
594+
JPY: '13.452',
595+
CNY: '1.0993',
596+
GBP: '0.080870',
597+
},
590598
}

price-server/config/docker.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,14 @@ module.exports = {
5757
apiKey: process.env.FIAT_PROVIDER_ALPHA_VANTAGE_API_KEY || '', // necessary
5858
},
5959
},
60+
sdrBasket: process.env.SDR_BASKET
61+
? JSON.parse(process.env.SDR_BASKET)
62+
: {
63+
// to calculate SDR value if not available from fiat providers
64+
USD: '0.57813',
65+
EUR: '0.37379',
66+
JPY: '13.452',
67+
CNY: '1.0993',
68+
GBP: '0.080870',
69+
},
6070
}

price-server/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

price-server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@classic-terra/price-server",
3-
"version": "3.1.4",
3+
"version": "3.1.5",
44
"main": "src/main.ts",
55
"license": "Apache-2.0",
66
"scripts": {

price-server/src/provider/fiat/FiatProvider.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import * as config from 'config'
2-
import { Provider, ProviderOptions } from 'provider/base'
2+
import { PriceBySymbol, Provider, ProviderOptions } from 'provider/base'
3+
import * as logger from 'lib/logger'
34
import { CurrencyLayer, AlphaVantage, Fixer, ExchangeRate, Fer, Frankfurter, Fastforex, IMF } from './quoter'
5+
import BigNumber from 'bignumber.js'
6+
import { getBaseCurrency } from 'lib/currency'
47

58
class FiatProvider extends Provider {
69
constructor(options: ProviderOptions) {
@@ -32,6 +35,54 @@ class FiatProvider extends Provider {
3235
await this.tick(Date.now())
3336
}
3437

38+
protected calculateSDR(prices: PriceBySymbol): BigNumber | undefined {
39+
if (!config.sdrBasket) {
40+
logger.error(`calculateSDR: config.sdrBasket not found`)
41+
return undefined
42+
}
43+
44+
const priceList = Object.keys(prices).map((symbol) => ({
45+
denom: getBaseCurrency(symbol),
46+
price: prices[symbol].toFixed(8),
47+
}))
48+
49+
// check if all prices from the basket are available
50+
for (const denom of Object.keys(config.sdrBasket)) {
51+
if (denom === 'USD') {
52+
continue
53+
}
54+
55+
if (!priceList.find((p) => p.denom === denom)) {
56+
logger.error(`calculateSDR price for ${denom} not found`)
57+
return undefined
58+
}
59+
}
60+
61+
// calculate SDR price
62+
let sdrPrice: BigNumber | undefined = undefined
63+
64+
try {
65+
sdrPrice = Object.entries(config.sdrBasket).reduce((acc, [denom, weight]: [string, string]) => {
66+
const price = denom === 'USD' ? BigNumber(1) : priceList.find((p) => p.denom === denom)?.price || BigNumber(0)
67+
if (!price) {
68+
throw new Error(`price for ${denom} not found`)
69+
}
70+
return acc.plus(new BigNumber(price).times(weight))
71+
}, new BigNumber(0))
72+
} catch (err) {
73+
logger.error(`getPrices: error calculating SDR price: ${err.message}`)
74+
return undefined
75+
}
76+
77+
if (!sdrPrice) {
78+
logger.error(`getPrices: error calculating SDR price`)
79+
return undefined
80+
}
81+
82+
logger.info(`getPrices: calculated SDR price: ${sdrPrice.toString()}`)
83+
return sdrPrice
84+
}
85+
3586
protected adjustPrices(): void {
3687
for (const symbol of this.symbols) {
3788
delete this.priceBySymbol[symbol]
@@ -46,6 +97,16 @@ class FiatProvider extends Provider {
4697
}
4798
}
4899
}
100+
101+
if (!this.priceBySymbol['SDR/USD']) {
102+
logger.info(`No SDR price found, falling back to calculation.`)
103+
const sdrPrice = this.calculateSDR(this.priceBySymbol)
104+
if (sdrPrice && sdrPrice.isNaN() === false) {
105+
this.priceBySymbol['SDR/USD'] = sdrPrice
106+
} else {
107+
logger.error(`No SDR price found, calculation failed.`)
108+
}
109+
}
49110
}
50111
}
51112

price-server/src/provider/fiat/quoter/IMF.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,24 @@ const SDR_VALUATION_URL = 'https://www.imf.org/external/np/fin/data/rms_sdrv.asp
99
async function fetchQuote() {
1010
const text = await fetch(SDR_VALUATION_URL).then((res) => res.text())
1111

12-
const doc = parse(text)
13-
const tds = doc.querySelectorAll('.tightest td')
14-
const idx = tds.findIndex((el) => el.structuredText === ' SDR1 = US$')
15-
16-
if (idx === -1) {
17-
throw new Error('cannot find SDR/USD element from HTML document')
12+
let idx = -1
13+
const tables = text.match(/<table[^>]*>([\s\S]*?)<\/table>/gi) || []
14+
for (const table of tables) {
15+
const doc = parse(table.trim())
16+
const tds = doc.querySelectorAll('td')
17+
18+
idx = tds.findIndex((el) => {
19+
return el.structuredText.trim() === 'SDR1 = US$'
20+
})
21+
22+
if (idx !== -1) {
23+
// sample format: ' 1.32149 2'
24+
return num(tds[idx + 1].structuredText.split(' ')[1])
25+
}
1826
}
1927

20-
// sample format: ' 1.32149 2'
21-
return num(tds[idx + 1].structuredText.split(' ')[1])
28+
// nothing found
29+
throw new Error('cannot find SDR/USD element from HTML document')
2230
}
2331

2432
// fetchQuote().then(console.log).catch(console.error) // For test

0 commit comments

Comments
 (0)