Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Summary data for pairs #123

Merged
merged 4 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/api/arbs/[...params].ts

This file was deleted.

1 change: 0 additions & 1 deletion app/api/blockTimestamps/[...params].ts

This file was deleted.

1 change: 0 additions & 1 deletion app/api/blocks/[...heights].ts

This file was deleted.

1 change: 0 additions & 1 deletion app/api/lp/[lp_nft_id]/position/route.ts

This file was deleted.

1 change: 0 additions & 1 deletion app/api/lp/[lp_nft_id]/route.ts

This file was deleted.

1 change: 0 additions & 1 deletion app/api/lp/[lp_nft_id]/trades/route.ts

This file was deleted.

1 change: 0 additions & 1 deletion app/api/lp/block/[...params].ts

This file was deleted.

1 change: 0 additions & 1 deletion app/api/lp/positionsByPrice/[...params].ts

This file was deleted.

1 change: 0 additions & 1 deletion app/api/ohlc/[...params].ts

This file was deleted.

1 change: 0 additions & 1 deletion app/api/shieldedPool/[token]/route.ts

This file was deleted.

1 change: 1 addition & 0 deletions app/api/summary/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GET } from '@/shared/api/server/summary';
1 change: 0 additions & 1 deletion app/api/swaps/[...params].ts

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"framer-motion": "^11.3.31",
"grpc-tools": "^1.12.4",
"grpc_tools_node_protoc_ts": "^5.3.3",
"kysely": "^0.27.4",
"lightweight-charts": "^4.2.0",
"lodash": "^4.17.21",
"lucide-react": "^0.445.0",
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions src/pages/trade/model/useSummary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useQuery } from '@tanstack/react-query';
import { usePathToMetadata } from '@/pages/trade/model/use-path-to-metadata.ts';
import { SummaryResponse } from '@/shared/api/server/summary.ts';

export const useSummary = () => {
const { baseAsset, quoteAsset, error: pathError } = usePathToMetadata();

const res = useQuery({
queryKey: ['summary', baseAsset, quoteAsset],
enabled: !!baseAsset && !!quoteAsset,
retry: 1,
queryFn: async () => {
if (!baseAsset || !quoteAsset) {
throw new Error('Missing assets to get summary for');
}

const paramsObj = {
baseAsset: baseAsset.symbol,
quoteAsset: quoteAsset.symbol,
};
const baseUrl = '/api/summary';
const urlParams = new URLSearchParams(paramsObj).toString();
const fetchRes = await fetch(`${baseUrl}?${urlParams}`);
const jsonRes = (await fetchRes.json()) as SummaryResponse;
if ('error' in jsonRes) {
throw new Error(jsonRes.error);
}
return jsonRes;
},
});

return {
...res,
error: pathError ?? res.error,
};
};
4 changes: 4 additions & 0 deletions src/pages/trade/ui/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import { Card } from '@penumbra-zone/ui/Card';
import { PairSelector } from './pair-selector';
import { RouteBook } from './route-book';
import { Chart } from './chart';
import { Summary } from '@/pages/trade/ui/summary.tsx';

export const TradePage = () => {
return (
<div>
<div className='flex gap-2'>
<PairSelector />
</div>
<div>
<Summary />
</div>

<div className='flex flex-wrap lg:gap-2'>
<div className='w-full lg:w-auto lg:flex-grow mb-2'>
Expand Down
24 changes: 24 additions & 0 deletions src/pages/trade/ui/summary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useSummary } from '@/pages/trade/model/useSummary.ts';

export const Summary = () => {
const { data, isLoading, error } = useSummary();

if (error) {
return <div className='text-red-600'>Error: {String(error)}</div>;
}

if (isLoading || !data) {
return <div className='text-white'>Loading...</div>;
}

return (
<div className='text-white flex gap-2 max-w-6'>
<div>current_price: {data.current_price}</div>
<div>low_24h: {data.low_24h}</div>
<div>high_24h: {data.high_24h}</div>
<div>price_24h_ago: {data.price_24h_ago}</div>
<div>swap_volume_24h: {data.swap_volume_24h}</div>
<div>direct_volume_24h: {data.direct_volume_24h}</div>
</div>
);
};
28 changes: 0 additions & 28 deletions src/shared/api/block.tsx

This file was deleted.

178 changes: 2 additions & 176 deletions src/shared/api/indexer/connector.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Pool, QueryConfigValues, QueryResultRow } from 'pg';
import fs from 'fs';
import { BlockInfo, LiquidityPositionEvent } from './lps';
import { positionIdFromBech32 } from '@penumbra-zone/bech32m/plpid';
import { PositionId } from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { BlockInfo } from './lps';
import { JsonValue } from '@bufbuild/protobuf';

const INDEXER_CERT = process.env['PENUMBRA_INDEXER_CA_CERT'];

// TODO: Delete when possible, this is deprecated
export class IndexerQuerier {
private pool: Pool;

Expand Down Expand Up @@ -84,160 +83,6 @@ export class IndexerQuerier {
return value;
}

public async fetchLiquidityPositionEventsOnBech32(
bech32: string,
): Promise<LiquidityPositionEvent[]> {
const queryText = `
SELECT
a.event_id,
e.block_id,
e.tx_id,
e.type,
jsonb_object_agg(additional_attributes.key, additional_attributes.value) AS lpevent_attributes,
tr.tx_hash,
tr.created_at,
tr.index,
b.height as block_height
FROM attributes a
INNER JOIN events e ON a.event_id = e.rowid
INNER JOIN tx_events te ON a.value = te.value and te.composite_key = a.composite_key
INNER JOIN tx_results tr ON e.block_id = tr.block_id and te.index = tr.index
INNER JOIN block_events b ON e.block_id = b.block_id and b.key = 'height' and b.type = 'block'
LEFT JOIN attributes additional_attributes ON additional_attributes.event_id = a.event_id
WHERE a.value = $1 and a.composite_key not like '%EventPositionExecution%'
GROUP BY a.event_id, e.block_id, e.tx_id, e.type, tr.tx_hash, tr.created_at, tr.index, b.height;
`;

const inner = new PositionId(positionIdFromBech32(bech32));

// Use parameterized query to prevent SQL injection
return this.query(queryText, [inner.toJson()]);
}

public async fetchLiquidityPositionExecutionEventsOnBech32(
bech32: string,
): Promise<LiquidityPositionEvent[]> {
// TODO: Refractor once more events are emitted around trades
// This basically pops off the first instance of a trade event for each event_id assuming that was the EventPositionExecution event for opening a position
const queryText = `
WITH RankedTrades AS (
SELECT
a.event_id,
e.block_id,
e.tx_id,
e.type,
jsonb_object_agg(additional_attributes.key, additional_attributes.value) AS execution_event_attributes,
tr.tx_hash,
tr.created_at,
tr.index,
b.height as block_height,
ROW_NUMBER() OVER(PARTITION BY a.event_id ORDER BY tr.index ASC) AS rn,
COUNT(*) OVER(PARTITION BY a.event_id) AS cnt
FROM attributes a
INNER JOIN events e ON a.event_id = e.rowid
INNER JOIN block_events b ON e.block_id = b.block_id AND b.key = 'height'
INNER JOIN tx_results tr ON tr.block_id = e.block_id
LEFT JOIN attributes additional_attributes ON additional_attributes.event_id = a.event_id
WHERE a.value = $1 AND a.composite_key LIKE '%EventPositionExecution%'
GROUP BY a.event_id, e.block_id, e.tx_id, e.type, tr.tx_hash, tr.created_at, tr.index, b.height
)
SELECT
event_id,
block_id,
tx_id,
type,
execution_event_attributes,
tx_hash,
created_at,
index,
block_height
FROM RankedTrades
WHERE
cnt = 1 OR rn > 1; -- Exclude the lowest tr.index when there are duplicates for an event_id
`;

const inner = new PositionId(positionIdFromBech32(bech32));

// Use parameterized query to prevent SQL injection
return this.query(queryText, [inner.toJson()]);
}

public async fetchLiquidityPositionOpenCloseEventsOnBlockHeightRange(
startHeight: number,
endHeight: number,
): Promise<LiquidityPositionEvent[]> {
const queryText = `
SELECT
a.event_id,
e.block_id,
e.tx_id,
e.type,
jsonb_object_agg(additional_attributes.key, additional_attributes.value) AS lpevent_attributes,
tr.tx_hash,
tr.created_at,
tr.index,
b.height as block_height
FROM attributes a
INNER JOIN events e ON a.event_id = e.rowid
INNER JOIN block_events b ON e.block_id = b.block_id and b.key = 'height' and b.type = 'block'
LEFT JOIN attributes additional_attributes ON additional_attributes.event_id = a.event_id
INNER JOIN tx_events te ON a.value = te.value and te.composite_key = a.composite_key
INNER JOIN tx_results tr ON tr.block_id = e.block_id and te.index = tr.index
WHERE b.height >= $1 and b.height < $2 and (a.composite_key like '%PositionOpen.positionId%' or a.composite_key like '%PositionWithdraw.positionId%' or a.composite_key like '%PositionClose.positionId%')
GROUP BY a.event_id, e.block_id, e.tx_id, e.type, tr.tx_hash, tr.created_at, tr.index, b.height;
`;

// Use parameterized query to prevent SQL injection
// const res = await this.query(queryText);
return this.query(queryText, [`${startHeight}`, `${endHeight}`]);
}

public async fetchLiquidityPositionExecutionEventsOnBlockHeight(
blockHeight: number,
): Promise<LiquidityPositionEvent[]> {
// TODO: Refractor once more events are emitted around trades
// This basically pops off the first instance of a trade event for each event_id assuming that was the EventPositionExecution event for opening a position
const queryText = `
WITH RankedTrades AS (
SELECT
a.event_id,
e.block_id,
e.tx_id,
e.type,
jsonb_object_agg(additional_attributes.key, additional_attributes.value) AS execution_event_attributes,
tr.tx_hash,
tr.created_at,
tr.index,
b.height as block_height,
ROW_NUMBER() OVER(PARTITION BY a.event_id ORDER BY tr.index ASC) AS rn,
COUNT(*) OVER(PARTITION BY a.event_id) AS cnt
FROM attributes a
INNER JOIN events e ON a.event_id = e.rowid
INNER JOIN block_events b ON e.block_id = b.block_id AND b.key = 'height'
INNER JOIN tx_events te ON a.value = te.value and te.composite_key = a.composite_key
INNER JOIN tx_results tr ON tr.block_id = e.block_id and te.index = tr.index
LEFT JOIN attributes additional_attributes ON additional_attributes.event_id = a.event_id
WHERE b.height = $1 AND a.composite_key LIKE '%dex%'
GROUP BY a.event_id, e.block_id, e.tx_id, e.type, tr.tx_hash, tr.created_at, tr.index, b.height
)
SELECT
event_id,
block_id,
tx_id,
type,
execution_event_attributes,
tx_hash,
created_at,
index,
block_height
FROM RankedTrades
WHERE
cnt = 1 OR rn > 1 AND type like '%EventPosition.positionId%' OR type like '%EventQueuePositionClose.positionId%'; -- Exclude the lowest tr.index when there are duplicates for an event_id
`;
// Use parameterized query to prevent SQL injection
return await this.query(queryText, [`${blockHeight}`]);
}

public async fetchMostRecentNBlocks(n: number): Promise<BlockInfo[]> {
const queryText = `
SELECT
Expand All @@ -250,21 +95,6 @@ export class IndexerQuerier {
return await this.query(queryText, [`${n}`]);
}

public async fetchBlocksWithinRange(
startHeight: number,
endHeight: number,
): Promise<BlockInfo[]> {
const queryText = `
SELECT
height,
created_at
FROM blocks
WHERE height >= $1 and height < $2
ORDER BY height DESC
`;
return this.query(queryText, [`${startHeight}`, `${endHeight}`]);
}

public async fetchBlocksByHeight(heights: number[]): Promise<BlockInfo[]> {
const queryText = `
SELECT
Expand All @@ -284,7 +114,3 @@ export class IndexerQuerier {
await this.pool.end();
}
}

// Example usage:
// const indexerQuerier = new IndexerQuerier('postgresql://user:password@host:port/database?sslmode=require');
// indexerQuerier.query('SELECT * FROM your_table').then(data => console.log(data)).catch(error => console.error(error));
Loading