From 8e0bb97839ae5f7ee58309a4f42b3c4f6505c9d2 Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Mon, 3 Apr 2023 13:30:16 +0200 Subject: [PATCH] Account balances endpoint (#147) * add accounts/positions to api * strong type apis --- src/pages/api/accounts/[id]/debts.ts | 8 +++-- src/pages/api/accounts/[id]/deposits.ts | 8 +++-- src/pages/api/accounts/[id]/index.ts | 5 +-- src/pages/api/markets/borrow.ts | 16 ++++----- src/pages/api/markets/debts.ts | 2 +- src/pages/api/markets/deposits.ts | 8 ++--- src/pages/api/markets/index.ts | 5 +-- .../{accounts.ts => accounts/index.ts} | 0 .../wallets/[address]/accounts/positions.ts | 35 +++++++++++++++++++ src/types/interfaces/account.d.ts | 6 ++++ src/types/interfaces/asset.d.ts | 2 +- src/types/interfaces/market.d.ts | 9 +++++ .../{hooks/useMarkets.d.ts => responses.d.ts} | 17 +++++---- src/utils/api.ts | 9 ++--- src/utils/resolvers.ts | 24 +++++++++++++ 15 files changed, 117 insertions(+), 37 deletions(-) rename src/pages/api/wallets/[address]/{accounts.ts => accounts/index.ts} (100%) create mode 100644 src/pages/api/wallets/[address]/accounts/positions.ts create mode 100644 src/types/interfaces/account.d.ts create mode 100644 src/types/interfaces/market.d.ts rename src/types/interfaces/{hooks/useMarkets.d.ts => responses.d.ts} (80%) create mode 100644 src/utils/resolvers.ts diff --git a/src/pages/api/accounts/[id]/debts.ts b/src/pages/api/accounts/[id]/debts.ts index 8ae3e985..61f40a2a 100644 --- a/src/pages/api/accounts/[id]/debts.ts +++ b/src/pages/api/accounts/[id]/debts.ts @@ -9,10 +9,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const accountId = req.query.id - const account = await (await fetch(`${ENV.URL_API}/accounts/${accountId}${VERCEL_BYPASS}`)).json() + const position: Position = await ( + await fetch(`${ENV.URL_API}/accounts/${accountId}${VERCEL_BYPASS}`) + ).json() - if (account) { - return res.status(200).json(account.debts) + if (position) { + return res.status(200).json(position.debts) } return res.status(404) diff --git a/src/pages/api/accounts/[id]/deposits.ts b/src/pages/api/accounts/[id]/deposits.ts index f6d7e24a..46bdecd0 100644 --- a/src/pages/api/accounts/[id]/deposits.ts +++ b/src/pages/api/accounts/[id]/deposits.ts @@ -9,10 +9,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const accountId = req.query.id - const account = await (await fetch(`${ENV.URL_API}/accounts/${accountId}${VERCEL_BYPASS}`)).json() + const position: Position = await ( + await fetch(`${ENV.URL_API}/accounts/${accountId}${VERCEL_BYPASS}`) + ).json() - if (account) { - return res.status(200).json(account.deposits) + if (position) { + return res.status(200).json(position.deposits) } return res.status(404) diff --git a/src/pages/api/accounts/[id]/index.ts b/src/pages/api/accounts/[id]/index.ts index 33668fdf..84fb8200 100644 --- a/src/pages/api/accounts/[id]/index.ts +++ b/src/pages/api/accounts/[id]/index.ts @@ -2,6 +2,7 @@ import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { NextApiRequest, NextApiResponse } from 'next' import { ENV, ENV_MISSING_MESSAGE } from 'constants/env' +import { resolvePositionResponse } from 'utils/resolvers' export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (!ENV.URL_RPC || !ENV.ADDRESS_CREDIT_MANAGER) { @@ -12,14 +13,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const client = await CosmWasmClient.connect(ENV.URL_RPC) - const data = await client.queryContractSmart(ENV.ADDRESS_CREDIT_MANAGER, { + const data: PositionResponse = await client.queryContractSmart(ENV.ADDRESS_CREDIT_MANAGER, { positions: { account_id: accountId, }, }) if (data) { - return res.status(200).json(data) + return res.status(200).json(resolvePositionResponse(data)) } return res.status(404) diff --git a/src/pages/api/markets/borrow.ts b/src/pages/api/markets/borrow.ts index 1188c322..9b7a1007 100644 --- a/src/pages/api/markets/borrow.ts +++ b/src/pages/api/markets/borrow.ts @@ -10,7 +10,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return res.status(404).json(ENV_MISSING_MESSAGE) } - const marketAssets = getMarketAssets() const $liquidity = fetch(`${ENV.URL_API}/markets/liquidity${VERCEL_BYPASS}`) const $markets = fetch(`${ENV.URL_API}/markets${VERCEL_BYPASS}`) const $prices = fetch(`${ENV.URL_API}/prices${VERCEL_BYPASS}`) @@ -18,16 +17,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const borrow: BorrowAsset[] = await Promise.all([$liquidity, $markets, $prices]).then( async ([$liquidity, $markets, $prices]) => { const liquidity: Coin[] = await $liquidity.json() - const markets: Market[] = await $markets.json() + const borrowEnabledMarkets: Market[] = (await $markets.json()).filter( + (market: Market) => market.borrowEnabled, + ) const prices: Coin[] = await $prices.json() - return marketAssets.map((asset) => { - const currentMarket = markets.find((market) => market.denom === asset.denom) - const price = prices.find((coin) => coin.denom === asset.denom)?.amount ?? '1' - const amount = liquidity.find((coin) => coin.denom === asset.denom)?.amount ?? '0' + return borrowEnabledMarkets.map((market) => { + const price = prices.find((coin) => coin.denom === market.denom)?.amount ?? '1' + const amount = liquidity.find((coin) => coin.denom === market.denom)?.amount ?? '0' return { - denom: asset.denom, - borrowRate: currentMarket?.borrow_rate ?? '0', + denom: market.denom, + borrowRate: market.borrowRate ?? 0, liquidity: { amount: amount, value: new BigNumber(amount).times(price).toString(), diff --git a/src/pages/api/markets/debts.ts b/src/pages/api/markets/debts.ts index 2149f9c4..4d71e556 100644 --- a/src/pages/api/markets/debts.ts +++ b/src/pages/api/markets/debts.ts @@ -21,7 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) { underlying_debt_amount: { denom: "${asset.denom}" - amount_scaled: "${asset.debt_total_scaled}" + amount_scaled: "${asset.debtTotalScaled}" } }`, ) diff --git a/src/pages/api/markets/deposits.ts b/src/pages/api/markets/deposits.ts index 820ee6e5..e8fd34e9 100644 --- a/src/pages/api/markets/deposits.ts +++ b/src/pages/api/markets/deposits.ts @@ -13,15 +13,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) let query = '' - markets.forEach((asset: any) => { + markets.forEach((market: Market) => { query += getContractQuery( - denomToKey(asset.denom), + denomToKey(market.denom), ENV.ADDRESS_RED_BANK || '', ` { underlying_liquidity_amount: { - denom: "${asset.denom}" - amount_scaled: "${asset.collateral_total_scaled}" + denom: "${market.denom}" + amount_scaled: "${market.collateralTotalScaled}" } }`, ) diff --git a/src/pages/api/markets/index.ts b/src/pages/api/markets/index.ts index f779ff3d..d7638c8d 100644 --- a/src/pages/api/markets/index.ts +++ b/src/pages/api/markets/index.ts @@ -4,6 +4,7 @@ import { NextApiRequest, NextApiResponse } from 'next' import { ENV, ENV_MISSING_MESSAGE } from 'constants/env' import { getMarketAssets } from 'utils/assets' import { denomToKey } from 'utils/query' +import { resolveMarketResponses } from 'utils/resolvers' export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (!ENV.URL_GQL || !ENV.ADDRESS_RED_BANK || !ENV.ADDRESS_INCENTIVES) { @@ -35,11 +36,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const market = result.rbwasmkey[`${denomToKey(asset.denom)}`] return market }) - return res.status(200).json(markets) + return res.status(200).json(resolveMarketResponses(markets)) } interface RedBankData { rbwasmkey: { - [key: string]: Market + [key: string]: MarketResponse } } diff --git a/src/pages/api/wallets/[address]/accounts.ts b/src/pages/api/wallets/[address]/accounts/index.ts similarity index 100% rename from src/pages/api/wallets/[address]/accounts.ts rename to src/pages/api/wallets/[address]/accounts/index.ts diff --git a/src/pages/api/wallets/[address]/accounts/positions.ts b/src/pages/api/wallets/[address]/accounts/positions.ts new file mode 100644 index 00000000..3eeaf1c8 --- /dev/null +++ b/src/pages/api/wallets/[address]/accounts/positions.ts @@ -0,0 +1,35 @@ +import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import { NextApiRequest, NextApiResponse } from 'next' + +import { ENV, ENV_MISSING_MESSAGE, VERCEL_BYPASS } from 'constants/env' +import { resolvePositionResponses } from 'utils/resolvers' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (!ENV.URL_RPC || !ENV.ADDRESS_CREDIT_MANAGER || !ENV.URL_API) { + return res.status(404).json(ENV_MISSING_MESSAGE) + } + + const address = req.query.address + + const accounts: string[] = await ( + await fetch(`${ENV.URL_API}/wallets/${address}/accounts${VERCEL_BYPASS}`) + ).json() + + const client = await CosmWasmClient.connect(ENV.URL_RPC) + + const $positions: Promise[] = accounts.map((account) => + client.queryContractSmart(ENV.ADDRESS_CREDIT_MANAGER!, { + positions: { + account_id: `${account}`, + }, + }), + ) + + const positions = await Promise.all($positions).then((positions) => positions) + + if (positions) { + return res.status(200).json(resolvePositionResponses(positions)) + } + + return res.status(404) +} diff --git a/src/types/interfaces/account.d.ts b/src/types/interfaces/account.d.ts new file mode 100644 index 00000000..f85fb3e0 --- /dev/null +++ b/src/types/interfaces/account.d.ts @@ -0,0 +1,6 @@ +interface Position { + account: string + deposits: import('@cosmjs/stargate').Coin[] + debts: import('@cosmjs/stargate').Coin[] + lends: import('@cosmjs/stargate').Coin[] +} diff --git a/src/types/interfaces/asset.d.ts b/src/types/interfaces/asset.d.ts index 570fc418..4ec1c4f1 100644 --- a/src/types/interfaces/asset.d.ts +++ b/src/types/interfaces/asset.d.ts @@ -18,7 +18,7 @@ interface OtherAsset extends Omit { interface BorrowAsset { denom: string - borrowRate: string | null + borrowRate: number | null liquidity: { amount: string value: string diff --git a/src/types/interfaces/market.d.ts b/src/types/interfaces/market.d.ts new file mode 100644 index 00000000..67349067 --- /dev/null +++ b/src/types/interfaces/market.d.ts @@ -0,0 +1,9 @@ +interface Market { + denom: string + borrowRate: number + debtTotalScaled: number + collateralTotalScaled: number + depositEnabled: boolean + borrowEnabled: boolean + depositCap: number +} diff --git a/src/types/interfaces/hooks/useMarkets.d.ts b/src/types/interfaces/responses.d.ts similarity index 80% rename from src/types/interfaces/hooks/useMarkets.d.ts rename to src/types/interfaces/responses.d.ts index ae486710..a1c9af4d 100644 --- a/src/types/interfaces/hooks/useMarkets.d.ts +++ b/src/types/interfaces/responses.d.ts @@ -1,4 +1,11 @@ -interface Market { +interface PositionResponse { + account_id: string + deposits: Coin[] + debts: Coin[] + lends: Coin[] +} + +interface MarketResponse { denom: string max_loan_to_value: string liquidation_threshold: string @@ -21,11 +28,3 @@ interface Market { borrow_enabled: boolean deposit_cap: string } - -interface MarketResult { - wasm: MarketData -} - -interface MarketData { - [key: string]: Market -} diff --git a/src/utils/api.ts b/src/utils/api.ts index 35cc3215..c29df24c 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -11,13 +11,12 @@ export async function callAPI(endpoint: string): Promise { } export async function getBorrowData() { - await sleep() return callAPI('/markets/borrow') } export async function getCreditAccounts(address: string) { if (!address) return [] - return callAPI(`/wallets/${address}/accounts`) + return callAPI(`/wallets/${address}/accounts`) } export async function getMarkets() { @@ -41,11 +40,13 @@ export async function getAccountDeposits(account: string) { if (!account) return [] return callAPI(`/accounts/${account}/deposits`) } + export async function getWalletBalances(wallet: string) { if (!wallet) return [] return callAPI(`/wallets/${wallet}/balances`) } -async function sleep() { - return new Promise((resolve) => setTimeout(resolve, 2500)) +export async function getAccountsPositions(wallet: string) { + if (!wallet) return [] + return callAPI(`/wallets/${wallet}/accounts/positions`) } diff --git a/src/utils/resolvers.ts b/src/utils/resolvers.ts new file mode 100644 index 00000000..a383bb4a --- /dev/null +++ b/src/utils/resolvers.ts @@ -0,0 +1,24 @@ +export function resolvePositionResponses(responses: PositionResponse[]): Position[] { + return responses.map(resolvePositionResponse) +} + +export function resolvePositionResponse(response: PositionResponse): Position { + return { + account: response.account_id, + deposits: response.deposits, + debts: response.debts, + lends: response.lends, + } +} + +export function resolveMarketResponses(responses: MarketResponse[]): Market[] { + return responses.map((response) => ({ + denom: response.denom, + borrowRate: Number(response.borrow_rate), + debtTotalScaled: Number(response.debt_total_scaled), + collateralTotalScaled: Number(response.collateral_total_scaled), + depositEnabled: response.deposit_enabled, + borrowEnabled: response.borrow_enabled, + depositCap: Number(response.deposit_cap), + })) +}