fortress-js
Version:
A JavaScript SDK for Binance Smart Chain and the Fortress Protocol.
169 lines (143 loc) • 6.19 kB
text/typescript
/**
* @file Price Feed
* @desc These methods facilitate interactions with the Open Price Feed smart
* contracts.
*/
import * as eth from './eth';
import { netId } from './helpers';
import {
constants, address, abi, cTokens, underlyings, decimals, opfAssets
} from './constants';
import { BigNumber } from '@ethersproject/bignumber/lib/bignumber';
import { CallOptions } from './types';
function validateAsset(
asset: string,
argument: string,
errorPrefix: string
) : (boolean | string | number)[] {
if (typeof asset !== 'string' || asset.length < 1) {
throw Error(errorPrefix + 'Argument `' + argument + '` must be a non-empty string.');
}
const assetIsFToken = asset[0] === 'f';
const fTokenName = assetIsFToken ? asset : 'f' + asset;
const fTokenAddress = address[this._network.name][fTokenName];
let underlyingName = assetIsFToken ? asset.slice(1, asset.length) : asset;
const underlyingAddress = address[this._network.name][underlyingName];
if (
(!cTokens.includes(fTokenName) || !underlyings.includes(underlyingName)) &&
!opfAssets.includes(underlyingName)
) {
throw Error(errorPrefix + 'Argument `' + argument + '` is not supported.');
}
const underlyingDecimals = decimals[underlyingName];
// The open price feed reveals BTC, not WBTC.
underlyingName = underlyingName === 'WBTC' ? 'BTC' : underlyingName;
return [assetIsFToken, fTokenName, fTokenAddress, underlyingName, underlyingAddress, underlyingDecimals];
}
async function fTokenExchangeRate(
fTokenAddress: string,
fTokenName: string,
underlyingDecimals: number
) : Promise<number> {
const address = fTokenAddress;
const method = 'exchangeRateCurrent';
const options = {
_compoundProvider: this._provider,
abi: fTokenName === constants.fBNB ? abi.fBNB : abi.fBep20,
};
const exchangeRateCurrent = await eth.read(address, method, [], options);
const mantissa = 18 + underlyingDecimals - 8; // fToken always 8 decimals
const oneFTokenInUnderlying = exchangeRateCurrent / Math.pow(10, mantissa);
return oneFTokenInUnderlying;
}
/**
* Gets an asset's price from the Fortress Protocol open price feed. The price
* of the asset can be returned in any other supported asset value, including
* all fTokens and underlyings.
*
* @param {string} asset A string of a supported asset in which to find the
* current price.
* @param {string} [inAsset] A string of a supported asset in which to express
* the `asset` parameter's price. This defaults to USD.
*
* @returns {string} Returns a string of the numeric value of the asset.
*
* @example
* ```
* const fortress = new Fortress(window.ethereum);
* let price;
*
* (async function () {
*
* price = await fortress.getPrice(Fortress.BNB);
* console.log('BNB in USD', price);
*
* price = await fortress.getPrice(Fortress.DAI, Fortress.USDC); // supports fTokens too
* console.log('DAI in USDC', price);
*
* })().catch(console.error);
* ```
*/
export async function getPrice(
asset: string,
inAsset: string = constants.USDC
) : Promise<number> {
await netId(this);
const errorPrefix = 'Fortress [getPrice] | ';
const [
// eslint-disable-next-line @typescript-eslint/no-unused-vars
assetIsFToken, fTokenName, fTokenAddress, underlyingName, underlyingAddress, underlyingDecimals
] = validateAsset.bind(this)(asset, 'asset', errorPrefix);
const [
// eslint-disable-next-line @typescript-eslint/no-unused-vars
inAssetIsFToken, inAssetFTokenName, inAssetFTokenAddress, inAssetUnderlyingName, inAssetUnderlyingAddress, inAssetUnderlyingDecimals
] = validateAsset.bind(this)(inAsset, 'inAsset', errorPrefix);
// const priceFeedAddress = address[this._network.name].PriceFeed;
const comptrollerAddress = address[this._network.name].Comptroller;
const oracleTrxOptions: CallOptions = {
_compoundProvider: this._provider,
abi: abi.Comptroller,
};
const priceOracleAddress = await eth.read(comptrollerAddress, 'oracle', [], oracleTrxOptions);
// const trxOptions: CallOptions = {
// _compoundProvider: this._provider,
// abi: abi.PriceFeed,
// };
// const assetUnderlyingPrice = await eth.read(priceFeedAddress, 'price', [ underlyingName ], trxOptions);
// const inAssetUnderlyingPrice = await eth.read(priceFeedAddress, 'price', [ inAssetUnderlyingName ], trxOptions);
const trxOptions: CallOptions = {
_compoundProvider: this._provider,
abi: abi.PriceOracle,
};
let assetUnderlyingPrice = await eth.read(priceOracleAddress, 'getUnderlyingPrice', [ fTokenAddress ], trxOptions);
const inAssetUnderlyingPrice = await eth.read(priceOracleAddress, 'getUnderlyingPrice', [ inAssetFTokenAddress ], trxOptions);
const assetDecimal = decimals[asset];
const inAssetDecimal = decimals[inAsset];
if ((assetDecimal-inAssetDecimal) > 0) {
assetUnderlyingPrice = assetUnderlyingPrice.mul(BigNumber.from("10").pow(assetDecimal-inAssetDecimal));
} else {
assetUnderlyingPrice = assetUnderlyingPrice.div(BigNumber.from("10").pow(inAssetDecimal-assetDecimal));
}
let assetFTokensInUnderlying, inAssetFTokensInUnderlying;
if (assetIsFToken) {
assetFTokensInUnderlying = await fTokenExchangeRate.bind(this)(fTokenAddress, fTokenName, underlyingDecimals);
}
if (inAssetIsFToken) {
inAssetFTokensInUnderlying = await fTokenExchangeRate.bind(this)(inAssetFTokenAddress, inAssetFTokenName, inAssetUnderlyingDecimals);
}
let result;
if (!assetIsFToken && !inAssetIsFToken) {
result = assetUnderlyingPrice / inAssetUnderlyingPrice;
} else if (assetIsFToken && !inAssetIsFToken) {
const assetInOther = assetUnderlyingPrice / inAssetUnderlyingPrice;
result = assetInOther * assetFTokensInUnderlying;
} else if (!assetIsFToken && inAssetIsFToken) {
const assetInOther = assetUnderlyingPrice / inAssetUnderlyingPrice;
result = assetInOther / inAssetFTokensInUnderlying;
} else {
const assetInOther = assetUnderlyingPrice / inAssetUnderlyingPrice;
const fTokensInUnderlying = assetInOther / assetFTokensInUnderlying;
result = inAssetFTokensInUnderlying * fTokensInUnderlying;
}
return result;
}