@swipewallet/venus-js
Version:
A JavaScript SDK for Ethereum and the Venus Protocol.
169 lines (143 loc) • 6.16 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 assetIsVToken = asset[0] === 'v';
const vTokenName = assetIsVToken ? asset : 'v' + asset;
const vTokenAddress = address[this._network.name][vTokenName];
let underlyingName = assetIsVToken ? asset.slice(1, asset.length) : asset;
const underlyingAddress = address[this._network.name][underlyingName];
if (
(!cTokens.includes(vTokenName) || !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 [assetIsVToken, vTokenName, vTokenAddress, underlyingName, underlyingAddress, underlyingDecimals];
}
async function vTokenExchangeRate(
vTokenAddress: string,
vTokenName: string,
underlyingDecimals: number
) : Promise<number> {
const address = vTokenAddress;
const method = 'exchangeRateCurrent';
const options = {
_compoundProvider: this._provider,
abi: vTokenName === constants.vBNB ? abi.vBNB : abi.vBep20,
};
const exchangeRateCurrent = await eth.read(address, method, [], options);
const mantissa = 18 + underlyingDecimals - 8; // vToken always 8 decimals
const oneVTokenInUnderlying = exchangeRateCurrent / Math.pow(10, mantissa);
return oneVTokenInUnderlying;
}
/**
* Gets an asset's price from the Venus Protocol open price feed. The price
* of the asset can be returned in any other supported asset value, including
* all vTokens 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 venus = new Venus(window.ethereum);
* let price;
*
* (async function () {
*
* price = await venus.getPrice(Venus.BNB);
* console.log('BNB in USD', price);
*
* price = await venus.getPrice(Venus.SXP, Venus.USDC); // supports vTokens too
* console.log('SXP in USDC', price);
*
* })().catch(console.error);
* ```
*/
export async function getPrice(
asset: string,
inAsset: string = constants.USDC
) : Promise<number> {
await netId(this);
const errorPrefix = 'Venus [getPrice] | ';
const [
// eslint-disable-next-line @typescript-eslint/no-unused-vars
assetIsVToken, vTokenName, vTokenAddress, underlyingName, underlyingAddress, underlyingDecimals
] = validateAsset.bind(this)(asset, 'asset', errorPrefix);
const [
// eslint-disable-next-line @typescript-eslint/no-unused-vars
inAssetIsVToken, inAssetVTokenName, inAssetVTokenAddress, 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', [ vTokenAddress ], trxOptions);
const inAssetUnderlyingPrice = await eth.read(priceOracleAddress, 'getUnderlyingPrice', [ inAssetVTokenAddress ], 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 assetVTokensInUnderlying, inAssetVTokensInUnderlying;
if (assetIsVToken) {
assetVTokensInUnderlying = await vTokenExchangeRate.bind(this)(vTokenAddress, vTokenName, underlyingDecimals);
}
if (inAssetIsVToken) {
inAssetVTokensInUnderlying = await vTokenExchangeRate.bind(this)(inAssetVTokenAddress, inAssetVTokenName, inAssetUnderlyingDecimals);
}
let result;
if (!assetIsVToken && !inAssetIsVToken) {
result = assetUnderlyingPrice / inAssetUnderlyingPrice;
} else if (assetIsVToken && !inAssetIsVToken) {
const assetInOther = assetUnderlyingPrice / inAssetUnderlyingPrice;
result = assetInOther * assetVTokensInUnderlying;
} else if (!assetIsVToken && inAssetIsVToken) {
const assetInOther = assetUnderlyingPrice / inAssetUnderlyingPrice;
result = assetInOther / inAssetVTokensInUnderlying;
} else {
const assetInOther = assetUnderlyingPrice / inAssetUnderlyingPrice;
const vTokensInUnderlying = assetInOther / assetVTokensInUnderlying;
result = inAssetVTokensInUnderlying * vTokensInUnderlying;
}
return result;
}