Skip to content

Commit

Permalink
Feat/new open positions (#1731)
Browse files Browse the repository at this point in the history
* persistent user data with prisma

* fix lint

* ignore regular .env too

* remove unnecessary async

* address comments

* rename hooks and provider to userMetadata...

* add trade table and extend providers executeTrade with callback

* fix npm command

* update schema.prisma

* isminting passed in

* better schema without unnecessary stuff

* include view from materializedview

* remove comment

* remove totalFeesUSD

* fix linting

* merge master update prisma schema

* update prisma

* no need toBigInt anymore

* quoteType mapping, and indexcoop refid

* add placeholders for mint and redeem fees

* update prisma schema

* move to apiv2

* add eslint disable to generated files

* ts-nocheck for generated as well

* allow .ts imports in tsconfig

* fix linting and imports

* use api.indexcoop as baseurl

* handle ep errors and send messages back

* exclude admin endpoints from kubb generator

* comment fixes

* change url to cloudflare redirect

* fix kubb conf api baseUrl

* remove /api/v2 from the url for the redirect

* change api url back to render one for now

* remove url replace

* add fees to trade

* add working history and open positions

* underlyingAsset symbol & denominator

* use USD as default denominator

* lint fix

* move history sorting to backend too

* reinstall dependencies

* linting issues on gha

* linting again

* Fix lint

* fix gasestimatoor

* fix typeerrors in tests

* fix viem / wagmi versions

* lint with no cache

* update eslint

* full on transfer data

* adjust netBalance with transferAmount

* dont need to adjust netbalance

* fix unrealised pnl adjustment with tx

* scrollable history and positions

* move back to old your tokens for the ui

* add refId submission

* handle optional refid on app side

* simpify new history route

* remove shadow blur

* remove unnecessary address cast

* recreate lock

* chore: lint peristent user data (#1730)

* update github workflow to include generate

* update docs

* add sentry cli again

* swap to new open positions

* fix formattings and some calculation logic

* fix lint

* fix entry vs current price and pnl

* reset hardcoded addresses

* realign spacings in open positions as well

* remove denominator on frontend as well

* remove transfer amount for now

* revert transferamount adjustment, data integrity issue not code

* use latest preview endpoint

* seems to be working version

* move back to prod api

* update lock

* merge fix

* remove sentry cli

* test if it works on deployed version

* add ration token support for entryprices

* calculate average entryprice

* add todo regarding average entry price

* weighted average entryprice

* show only leverage tokens on leverage your tokens

* refetch history after trade

* short colors and markets

* send proper underlying asset on shorts at trade

* column responsivity

* entry price calculated from buys only

* add lgn custom breakpoint for table

* hide only on lg

* remove infinity for now if there is no cost

* fix infinity%

* take current price if ratio price is not found

* current price correct mappign

* display narrow symbol

* change empty message

* indexes as underlyingAssetSymbol-underlyinAssetUnitPriceDenominator

* remove console.log

* remove unused val

* wrap flex items if necessary un unrealisedpnl

* reset netbalance width to original

* break realised pnl too if has to

* reduce increase/decrease width

* add margins to price columns

* iETH and iBTC

* refetch prices too to fix netbalance, and add a little delay to make sure we have fresh data

* change mobile history column display

* fix increase/decrease action

* hide percentage, show time on mobile

* add the volatility decay tooltip thingy

* refetch prices on user address change too

* fix linting

* add text for not connected wallet

* add new faq sections and tooltip links

* adjust route try catch to catch individual failures of stats

* useQuery in useBalances to fetch balances

* try waiting for transactions before refetching

* add client as dependency in saveTrade

* no need for memoized balances

* align empty columns right

* use rawContract.address instead of metrics as its lying sometimes

* refetch and it works

* dont break if stats fails

* make sure that zero netbalance gets filtered

* remove unnecessary comments

* remove stats log

* calculate cost from balance and avgcost

* refine tx categorization

* more accurate transfers tracking when no db entry

* reset api url

* make sure if there is no response we still default to empty array on open

* temporarily add preview api link

* reset to prod api url

---------

Co-authored-by: 0xonramp <[email protected]>
Co-authored-by: JD <[email protected]>
  • Loading branch information
3 people authored Jan 29, 2025
1 parent 6f507f1 commit e16dfd7
Show file tree
Hide file tree
Showing 17 changed files with 735 additions and 230 deletions.
Binary file added public/leverage-costs-and-fees.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
140 changes: 127 additions & 13 deletions src/app/api/leverage/history/route.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,91 @@
import {
getTokenByChainAndAddress,
isLeverageToken,
} from '@indexcoop/tokenlists'
import { uniqBy } from 'lodash'
import mapKeys from 'lodash/mapKeys'
import { NextRequest, NextResponse } from 'next/server'
import { Address } from 'viem'

import {
GetApiV2UserAddressPositionsQueryParamsChainIdEnum as ApiChainId,
getApiV2UserAddressPositions,
GetApiV2UserAddressPositions200,
} from '@/gen'

type TokenTransferRequest = {
user: Address
chainId: number
}

const fetchCoingeckoPrices = async (
ids: string[],
vs_currencies: string[],
): Promise<Record<string, { [key: string]: number }>> => {
const url = `https://pro-api.coingecko.com/api/v3/simple/price?ids=${ids.join(',')}&vs_currencies=${vs_currencies.join(',')}`
const options = {
method: 'GET',
headers: {
'accept': 'application/json',
'x-cg-pro-api-key': process.env.COINGECKO_API_KEY!,
},
}

const response = await fetch(url, options)
const result = await response.json()

return result
}

const mapCoingeckoIdToSymbol = (id: string) => {
switch (id) {
case 'ethereum':
return 'eth'
case 'bitcoin':
return 'btc'
default:
return id
}
}

const calculateAverageEntryPrice = (
positions: GetApiV2UserAddressPositions200,
) => {
const grouped = positions.reduce(
(acc, position) => {
if (
position.trade &&
position.metrics &&
position.trade.transactionType === 'buy'
) {
const tokenAddress = position.metrics.tokenAddress

if (!acc[tokenAddress]) {
acc[tokenAddress] = { sum: 0, count: 0 }
}

acc[tokenAddress].sum +=
(position.trade.underlyingAssetUnitPrice ?? 0) *
(position.metrics.endingUnits ?? 0)
acc[tokenAddress].count += position.metrics.endingUnits ?? 0
}
return acc
},
{} as Record<string, { sum: number; count: number }>,
)

const averages = Object.keys(grouped).reduce(
(acc, tokenAddress) => {
acc[tokenAddress] =
grouped[tokenAddress].sum / grouped[tokenAddress].count
return acc
},
{} as Record<string, number>,
)

return averages
}

export async function POST(req: NextRequest) {
try {
const { user, chainId } = (await req.json()) as TokenTransferRequest
Expand All @@ -21,33 +95,73 @@ export async function POST(req: NextRequest) {
{ chainId: chainId.toString() as ApiChainId },
)

const history = positions.data.sort(
(a, b) =>
new Date(b.metadata.blockTimestamp).getTime() -
new Date(a.metadata.blockTimestamp).getTime(),
const history = positions.data
.filter(({ rawContract }) => {
const token = getTokenByChainAndAddress(chainId, rawContract.address)

return isLeverageToken(token)
})
.sort(
(a, b) =>
new Date(b.metadata.blockTimestamp).getTime() -
new Date(a.metadata.blockTimestamp).getTime(),
)

const openPositions = history.filter(
(position) =>
position.trade &&
position.metrics &&
position.metrics.positionStatus === 'open',
)

const averages = calculateAverageEntryPrice(openPositions)

const open = uniqBy(
history.filter(
(position) =>
position.trade &&
position.metrics &&
position.metrics.positionStatus === 'open',
),
openPositions.map((position) => ({
...position,
trade: {
...position.trade,
underlyingAssetUnitPrice: averages[position.metrics!.tokenAddress],
},
})),
'metrics.tokenAddress',
)

let prices: Record<string, { [key: string]: number }> = {}
try {
prices = mapKeys(
await fetchCoingeckoPrices(
['ethereum', 'bitcoin'],
['btc', 'eth', 'usd'],
),
(_, key) => mapCoingeckoIdToSymbol(key),
)
} catch (error) {
console.error('Failed to fetch coingecko prices', error)
}

const stats = open.reduce(
(acc, position) => ({
...acc,
[`${position.trade.underlyingAssetSymbol}-${position.trade.underlyingAssetUnitPriceDenominator}`]:
prices[position.trade.underlyingAssetSymbol!.toLowerCase()][
position.trade.underlyingAssetUnitPriceDenominator!.toLowerCase()
] ?? 0,
}),
{},
)

return NextResponse.json(
{ open, history },
{ open, history, stats },
{
status: 200,
headers: {
// Response will be cached for 5 seconds, and will serve stale content while revalidating for 10 seconds
'Cache-Control': 'public, max-age=5, stale-while-revalidate=10',
'Cache-Control': 'public, max-age=1, stale-while-revalidate=1',
},
},
)
} catch (error) {
console.log(JSON.stringify(error, null, 2))
return NextResponse.json(error, { status: 500 })
}
}
4 changes: 3 additions & 1 deletion src/app/api/stats/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { fetchCarryCosts } from '@/lib/utils/fetch'

export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)

const tokenAddress = searchParams.get('address')
const symbol = searchParams.get('symbol')
const base = searchParams.get('base')
Expand All @@ -21,12 +22,13 @@ export async function GET(req: NextRequest) {
const data = await provider.getTokenStats(base, baseCurrency)
const carryCosts = await fetchCarryCosts()
const costOfCarry = carryCosts
? (carryCosts[symbol.toLowerCase()] ?? null)
? carryCosts[symbol.toLowerCase()] ?? null
: null
const metrics = await fetchTokenMetrics({
tokenAddress: tokenAddress,
metrics: ['nav', 'navchange'],
})

return NextResponse.json({
base: { ...data, baseCurrency },
token: {
Expand Down
Loading

0 comments on commit e16dfd7

Please sign in to comment.