feat: replace graphql requests with cosmwasm queries (#225)

* feat: replace graphql requests with cosmwasm queries

* feat: concurent price and market fetching
This commit is contained in:
Yusuf Seyrek 2023-05-30 12:16:03 +03:00 committed by GitHub
parent de89ecb7ed
commit 0c959d5097
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 96 additions and 240 deletions

View File

@ -20,7 +20,6 @@
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"bignumber.js": "^9.1.1", "bignumber.js": "^9.1.1",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"graphql-request": "^6.0.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"next": "^13.4.3", "next": "^13.4.3",
"react": "^18.2.0", "react": "^18.2.0",

View File

@ -7,7 +7,7 @@ let _cosmWasmClient: CosmWasmClient
const getClient = async () => { const getClient = async () => {
try { try {
if (!_cosmWasmClient) { if (!_cosmWasmClient) {
_cosmWasmClient = await CosmWasmClient.connect(ENV.URL_RPC || '') _cosmWasmClient = await CosmWasmClient.connect(ENV.URL_RPC)
} }
return _cosmWasmClient return _cosmWasmClient

View File

@ -1,29 +0,0 @@
import { gql, request as gqlRequest } from 'graphql-request'
import { ENV } from 'constants/env'
export default async function getBalances() {
const result = await gqlRequest<Result>(
ENV.URL_GQL,
gql`
query RedbankBalances {
bank {
balance(
address: "${ENV.ADDRESS_RED_BANK}"
) {
amount
denom
}
}
}
`,
)
return result.bank.balance
}
interface Result {
bank: {
balance: Coin[]
}
}

View File

@ -1,54 +1,24 @@
import { gql, request as gqlRequest } from 'graphql-request'
import { ENV } from 'constants/env' import { ENV } from 'constants/env'
import { denomToKey, getContractQuery, keyToDenom } from 'utils/query'
import getMarkets from 'api/markets/getMarkets' import getMarkets from 'api/markets/getMarkets'
import { getClient } from 'api/cosmwasm-client'
export default async function getMarketDebts(): Promise<Coin[]> { export default async function getMarketDebts(): Promise<Coin[]> {
const markets: Market[] = await getMarkets() try {
const markets: Market[] = await getMarkets()
const client = await getClient()
let query = '' const debtQueries = markets.map((asset) =>
client.queryContractSmart(ENV.ADDRESS_RED_BANK, {
markets.forEach((asset) => {
query += getContractQuery(
denomToKey(asset.denom),
ENV.ADDRESS_RED_BANK || '',
`
{
underlying_debt_amount: { underlying_debt_amount: {
denom: "${asset.denom}" denom: asset.denom,
amount_scaled: "${asset.debtTotalScaled}" amount_scaled: asset.debtTotalScaled,
} },
}`, }),
) )
}) const debtsResults = await Promise.all(debtQueries)
const result = await gqlRequest<DebtsQuery>( return debtsResults.map<Coin>((debt, index) => ({ denom: markets[index].denom, amount: debt }))
ENV.URL_GQL, } catch (ex) {
gql` throw ex
query RedbankBalances {
debts: wasm {
${query}
}
}
`,
)
if (result) {
const debts = Object.keys(result.debts).map((key) => {
return {
denom: keyToDenom(key),
amount: result.debts[key],
}
})
return debts
}
return new Promise((_, reject) => reject('No data'))
}
interface DebtsQuery {
debts: {
[key: string]: string
} }
} }

View File

@ -1,54 +1,27 @@
import { gql, request as gqlRequest } from 'graphql-request'
import { ENV } from 'constants/env' import { ENV } from 'constants/env'
import { denomToKey, getContractQuery, keyToDenom } from 'utils/query'
import getMarkets from 'api/markets/getMarkets' import getMarkets from 'api/markets/getMarkets'
import { getClient } from 'api/cosmwasm-client'
export default async function getMarketDeposits(): Promise<Coin[]> { export default async function getMarketDeposits(): Promise<Coin[]> {
const markets = await getMarkets() try {
const markets: Market[] = await getMarkets()
const client = await getClient()
let query = '' const depositQueries = markets.map((asset) =>
client.queryContractSmart(ENV.ADDRESS_RED_BANK, {
markets.forEach((market: Market) => { underlying_liquidity_amount: {
query += getContractQuery( denom: asset.denom,
denomToKey(market.denom), amount_scaled: asset.collateralTotalScaled,
ENV.ADDRESS_RED_BANK || '', },
` }),
{
underlying_liquidity_amount: {
denom: "${market.denom}"
amount_scaled: "${market.collateralTotalScaled}"
}
}`,
) )
}) const depositsResults = await Promise.all(depositQueries)
const result = await gqlRequest<DepositsQuery>( return depositsResults.map<Coin>((deposit, index) => ({
ENV.URL_GQL, denom: markets[index].denom,
gql` amount: deposit,
query RedbankBalances { }))
deposits: wasm { } catch (ex) {
${query} throw ex
}
}
`,
)
if (result) {
const deposits = Object.keys(result.deposits).map((key) => {
return {
denom: keyToDenom(key),
amount: result.deposits[key],
}
})
return deposits
}
return new Promise((_, reject) => reject('No data'))
}
interface DepositsQuery {
deposits: {
[key: string]: string
} }
} }

View File

@ -1,41 +1,24 @@
import { gql, request as gqlRequest } from 'graphql-request'
import { ENV } from 'constants/env' import { ENV } from 'constants/env'
import { getMarketAssets } from 'utils/assets' import { getEnabledMarketAssets } from 'utils/assets'
import { denomToKey } from 'utils/query'
import { resolveMarketResponses } from 'utils/resolvers' import { resolveMarketResponses } from 'utils/resolvers'
import { getClient } from 'api/cosmwasm-client'
export default async function getMarkets(): Promise<Market[]> { export default async function getMarkets(): Promise<Market[]> {
const marketAssets = getMarketAssets() try {
const enabledAssets = getEnabledMarketAssets()
const client = await getClient()
const marketQueries = marketAssets.map( const marketQueries = enabledAssets.map((asset) =>
(asset: Asset) => client.queryContractSmart(ENV.ADDRESS_RED_BANK, {
`${denomToKey(asset.denom)}: contractQuery( market: {
contractAddress: "${ENV.ADDRESS_RED_BANK}" denom: asset.denom,
query: { market: { denom: "${asset.denom}" } } },
)`, }),
) )
const marketResults = await Promise.all(marketQueries)
const result = await gqlRequest<RedBankData>( return resolveMarketResponses(marketResults)
ENV.URL_GQL, } catch (ex) {
gql` throw ex
query RedbankQuery {
rbwasmkey: wasm {
${marketQueries}
}
}
`,
)
const markets = marketAssets.map((asset) => {
const market = result.rbwasmkey[`${denomToKey(asset.denom)}`]
return market
})
return resolveMarketResponses(markets)
}
interface RedBankData {
rbwasmkey: {
[key: string]: MarketResponse
} }
} }

View File

@ -1,59 +1,36 @@
import { gql, request as gqlRequest } from 'graphql-request'
import { ASSETS } from 'constants/assets' import { ASSETS } from 'constants/assets'
import { ENV } from 'constants/env' import { ENV } from 'constants/env'
import { getMarketAssets } from 'utils/assets' import { getEnabledMarketAssets } from 'utils/assets'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { getClient } from 'api/cosmwasm-client'
export default async function getPrices(): Promise<Coin[]> { export default async function getPrices(): Promise<Coin[]> {
const marketAssets = getMarketAssets() try {
const baseCurrency = ASSETS[0] const enabledAssets = getEnabledMarketAssets()
const client = await getClient()
const baseCurrency = ASSETS[0]
const result = await gqlRequest<TokenPricesResult>( const priceQueries = enabledAssets.map((asset) =>
ENV.URL_GQL, client.queryContractSmart(ENV.ADDRESS_ORACLE, {
gql` price: {
query PriceOracle { denom: asset.denom,
prices: wasm { },
${marketAssets.map((asset) => { }),
return `${asset.id}: contractQuery( )
contractAddress: "${ENV.ADDRESS_ORACLE}" const priceResults: PriceResult[] = await Promise.all(priceQueries)
query: {
price: { const assetPrices = priceResults.map(({ denom, price }, index) => {
denom: "${asset.denom}" const asset = enabledAssets[index]
} const decimalDiff = asset.decimals - baseCurrency.decimals
}
)` return {
})} denom,
} amount: BN(price).shiftedBy(decimalDiff).toString(),
} }
`, })
)
const data: Coin[] = Object.values(result?.prices).reduce((acc: Coin[], curr) => { return assetPrices
const asset = marketAssets.find((asset) => asset.denom === curr.denom) } catch (ex) {
const additionalDecimals = asset throw ex
? asset.decimals > baseCurrency.decimals
? asset.decimals - baseCurrency.decimals
: 0
: 0
return [
...acc,
{
denom: curr.denom,
amount: BN(curr.price).shiftedBy(additionalDecimals).toString(),
},
] as Coin[]
}, [])
return data
}
interface TokenPricesResult {
prices: {
[key: string]: {
denom: string
price: string
}
} }
} }

View File

@ -2,7 +2,7 @@ import { Row } from '@tanstack/react-table'
import Button from 'components/Button' import Button from 'components/Button'
import useStore from 'store' import useStore from 'store'
import { getMarketAssets } from 'utils/assets' import { getEnabledMarketAssets } from 'utils/assets'
type AssetRowProps = { type AssetRowProps = {
row: Row<BorrowAsset | BorrowAssetActive> row: Row<BorrowAsset | BorrowAssetActive>
@ -12,7 +12,7 @@ type AssetRowProps = {
} }
export default function AssetExpanded(props: AssetRowProps) { export default function AssetExpanded(props: AssetRowProps) {
const marketAssets = getMarketAssets() const marketAssets = getEnabledMarketAssets()
const asset = marketAssets.find((asset) => asset.denom === props.row.original.denom) const asset = marketAssets.find((asset) => asset.denom === props.row.original.denom)
let isActive: boolean = false let isActive: boolean = false

View File

@ -1,7 +1,7 @@
import { flexRender, Row } from '@tanstack/react-table' import { flexRender, Row } from '@tanstack/react-table'
import classNames from 'classnames' import classNames from 'classnames'
import { getMarketAssets } from 'utils/assets' import { getEnabledMarketAssets } from 'utils/assets'
type AssetRowProps = { type AssetRowProps = {
row: Row<BorrowAsset | BorrowAssetActive> row: Row<BorrowAsset | BorrowAssetActive>
@ -9,7 +9,7 @@ type AssetRowProps = {
} }
export const AssetRow = (props: AssetRowProps) => { export const AssetRow = (props: AssetRowProps) => {
const marketAssets = getMarketAssets() const marketAssets = getEnabledMarketAssets()
const asset = marketAssets.find((asset) => asset.denom === props.row.original.denom) const asset = marketAssets.find((asset) => asset.denom === props.row.original.denom)
if (!asset) return null if (!asset) return null

View File

@ -17,7 +17,7 @@ import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import Text from 'components/Text' import Text from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell' import TitleAndSubCell from 'components/TitleAndSubCell'
import { getMarketAssets } from 'utils/assets' import { getEnabledMarketAssets } from 'utils/assets'
import { formatPercent } from 'utils/formatters' import { formatPercent } from 'utils/formatters'
type Props = { type Props = {
@ -26,7 +26,7 @@ type Props = {
export const BorrowTable = (props: Props) => { export const BorrowTable = (props: Props) => {
const [sorting, setSorting] = React.useState<SortingState>([]) const [sorting, setSorting] = React.useState<SortingState>([])
const marketAssets = getMarketAssets() const marketAssets = getEnabledMarketAssets()
const columns = React.useMemo<ColumnDef<BorrowAsset | BorrowAssetActive>[]>( const columns = React.useMemo<ColumnDef<BorrowAsset | BorrowAssetActive>[]>(
() => [ () => [

View File

@ -2,7 +2,7 @@ import { Suspense } from 'react'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import Card from 'components/Card' import Card from 'components/Card'
import { getMarketAssets } from 'utils/assets' import { getEnabledMarketAssets } from 'utils/assets'
import { BorrowTable } from 'components/Borrow/BorrowTable' import { BorrowTable } from 'components/Borrow/BorrowTable'
import useAccountDebts from 'hooks/useAccountDebts' import useAccountDebts from 'hooks/useAccountDebts'
import useMarketBorrowings from 'hooks/useMarketBorrowings' import useMarketBorrowings from 'hooks/useMarketBorrowings'
@ -16,7 +16,7 @@ function Content(props: Props) {
const { data: debtData } = useAccountDebts(accountId) const { data: debtData } = useAccountDebts(accountId)
const { data: borrowData } = useMarketBorrowings() const { data: borrowData } = useMarketBorrowings()
const marketAssets = getMarketAssets() const marketAssets = getEnabledMarketAssets()
function getBorrowAssets() { function getBorrowAssets() {
return marketAssets.reduce( return marketAssets.reduce(
@ -60,7 +60,7 @@ function Content(props: Props) {
} }
function Fallback() { function Fallback() {
const marketAssets = getMarketAssets() const marketAssets = getEnabledMarketAssets()
const available: BorrowAsset[] = marketAssets.reduce((prev: BorrowAsset[], curr) => { const available: BorrowAsset[] = marketAssets.reduce((prev: BorrowAsset[], curr) => {
prev.push({ ...curr, borrowRate: null, liquidity: null }) prev.push({ ...curr, borrowRate: null, liquidity: null })

View File

@ -18,7 +18,7 @@ import Text from 'components/Text'
import { IS_TESTNET } from 'constants/env' import { IS_TESTNET } from 'constants/env'
import useToggle from 'hooks/useToggle' import useToggle from 'hooks/useToggle'
import useStore from 'store' import useStore from 'store'
import { getBaseAsset, getMarketAssets } from 'utils/assets' import { getBaseAsset, getEnabledMarketAssets } from 'utils/assets'
import { formatValue, truncate } from 'utils/formatters' import { formatValue, truncate } from 'utils/formatters'
import useWalletBalances from 'hooks/useWalletBalances' import useWalletBalances from 'hooks/useWalletBalances'
@ -26,7 +26,7 @@ export default function ConnectedButton() {
// --------------- // ---------------
// EXTERNAL HOOKS // EXTERNAL HOOKS
// --------------- // ---------------
const marketAssets = getMarketAssets() const marketAssets = getEnabledMarketAssets()
const { disconnect } = useWallet() const { disconnect } = useWallet()
const { disconnect: terminate } = useWalletManager() const { disconnect: terminate } = useWalletManager()
const address = useStore((s) => s.address) const address = useStore((s) => s.address)

View File

@ -29,3 +29,8 @@ interface MarketResponse {
borrow_enabled: boolean borrow_enabled: boolean
deposit_cap: string deposit_cap: string
} }
interface PriceResult {
denom: string
price: string
}

View File

@ -8,7 +8,7 @@ export function getAssetBySymbol(symbol: string) {
return ASSETS.find((asset) => asset.symbol === symbol) return ASSETS.find((asset) => asset.symbol === symbol)
} }
export function getMarketAssets(): Asset[] { export function getEnabledMarketAssets(): Asset[] {
return ASSETS.filter((asset) => asset.isEnabled && asset.isMarket) return ASSETS.filter((asset) => asset.isEnabled && asset.isMarket)
} }

View File

@ -1,6 +1,6 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { getMarketAssets } from 'utils/assets' import { getEnabledMarketAssets } from 'utils/assets'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
export function truncate(text = '', [h, t]: [number, number] = [6, 6]): string { export function truncate(text = '', [h, t]: [number, number] = [6, 6]): string {
@ -126,7 +126,7 @@ export function formatPercent(percent: number | string, minDecimals?: number) {
} }
export function formatAmountWithSymbol(coin: Coin) { export function formatAmountWithSymbol(coin: Coin) {
const marketAssets = getMarketAssets() const marketAssets = getEnabledMarketAssets()
const asset = marketAssets.find((asset) => asset.denom === coin.denom) const asset = marketAssets.find((asset) => asset.denom === coin.denom)
@ -157,7 +157,7 @@ export function demagnify(amount: number | string | BigNumber, asset: Asset) {
export function convertToDisplayAmount(coin: Coin, displayCurrency: Asset, prices: Coin[]) { export function convertToDisplayAmount(coin: Coin, displayCurrency: Asset, prices: Coin[]) {
const price = prices.find((price) => price.denom === coin.denom) const price = prices.find((price) => price.denom === coin.denom)
const asset = getMarketAssets().find((asset) => asset.denom === coin.denom) const asset = getEnabledMarketAssets().find((asset) => asset.denom === coin.denom)
const displayPrice = prices.find((price) => price.denom === displayCurrency.denom) const displayPrice = prices.find((price) => price.denom === displayCurrency.denom)
if (!price || !asset || !displayPrice) return '0' if (!price || !asset || !displayPrice) return '0'

View File

@ -2059,7 +2059,7 @@
"@ethersproject/properties" "^5.7.0" "@ethersproject/properties" "^5.7.0"
"@ethersproject/strings" "^5.7.0" "@ethersproject/strings" "^5.7.0"
"@graphql-typed-document-node/core@^3.1.1", "@graphql-typed-document-node/core@^3.2.0": "@graphql-typed-document-node/core@^3.1.1":
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
@ -4719,13 +4719,6 @@ create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
sha.js "^2.4.8" sha.js "^2.4.8"
cross-fetch@^3.1.5:
version "3.1.5"
resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz"
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
dependencies:
node-fetch "2.6.7"
cross-spawn@^7.0.2, cross-spawn@^7.0.3: cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3" version "7.0.3"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
@ -6072,14 +6065,6 @@ graphemer@^1.4.0:
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
graphql-request@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-6.0.0.tgz#9c8b6a0c341f289e049936d03cc9205300faae1c"
integrity sha512-2BmHTuglonjZvmNVw6ZzCfFlW/qkIPds0f+Qdi/Lvjsl3whJg2uvHmSvHnLWhUTEw6zcxPYAHiZoPvSVKOZ7Jw==
dependencies:
"@graphql-typed-document-node/core" "^3.2.0"
cross-fetch "^3.1.5"
graphql-tag@^2.12.6: graphql-tag@^2.12.6:
version "2.12.6" version "2.12.6"
resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz" resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz"
@ -7532,13 +7517,6 @@ node-addon-api@^5.0.0:
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz" resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz"
integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==
node-fetch@2.6.7:
version "2.6.7"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
node-fetch@^2.6.7: node-fetch@^2.6.7:
version "2.6.9" version "2.6.9"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz"