From da8e3824548f652662cfc46838e220a6a8e00bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Mon, 9 Oct 2023 16:59:00 +0200 Subject: [PATCH] feat(trading): add funding payment history (#4989) --- .../client-pages/market/trade-grid.tsx | 3 + .../client-pages/market/trade-views.tsx | 5 + .../client-pages/portfolio/portfolio.tsx | 4 + .../funding-payments-container.tsx | 46 +++++ .../funding-payments-container/index.ts | 1 + libs/cypress/mock.ts | 1 + libs/funding-payments/.babelrc | 13 ++ libs/funding-payments/.eslintrc.json | 18 ++ libs/funding-payments/README.md | 7 + .../__mocks__/react-markdown.js | 6 + libs/funding-payments/jest.config.ts | 17 ++ libs/funding-payments/postcss.config.js | 10 + libs/funding-payments/project.json | 37 ++++ libs/funding-payments/src/index.ts | 3 + .../src/lib/FundingPayments.graphql | 24 +++ .../src/lib/__generated__/FundingPayments.ts | 71 ++++++++ .../src/lib/funding-payments-data-provider.ts | 69 +++++++ .../src/lib/funding-payments-manager.tsx | 42 +++++ .../src/lib/funding-payments-table.spec.tsx | 92 ++++++++++ .../src/lib/funding-payments-table.tsx | 132 ++++++++++++++ .../src/lib/funding-payments.mock.ts | 74 ++++++++ libs/funding-payments/src/lib/test-helpers.ts | 102 +++++++++++ libs/funding-payments/src/setup-tests.ts | 4 + libs/funding-payments/tailwind.config.js | 17 ++ libs/funding-payments/tsconfig.json | 27 +++ libs/funding-payments/tsconfig.lib.json | 27 +++ libs/funding-payments/tsconfig.spec.json | 20 ++ libs/types/src/__generated__/types.ts | 171 +++++++++++++++--- tsconfig.base.json | 1 + 29 files changed, 1014 insertions(+), 30 deletions(-) create mode 100644 apps/trading/components/funding-payments-container/funding-payments-container.tsx create mode 100644 apps/trading/components/funding-payments-container/index.ts create mode 100644 libs/funding-payments/.babelrc create mode 100644 libs/funding-payments/.eslintrc.json create mode 100644 libs/funding-payments/README.md create mode 100644 libs/funding-payments/__mocks__/react-markdown.js create mode 100644 libs/funding-payments/jest.config.ts create mode 100644 libs/funding-payments/postcss.config.js create mode 100644 libs/funding-payments/project.json create mode 100644 libs/funding-payments/src/index.ts create mode 100644 libs/funding-payments/src/lib/FundingPayments.graphql create mode 100644 libs/funding-payments/src/lib/__generated__/FundingPayments.ts create mode 100644 libs/funding-payments/src/lib/funding-payments-data-provider.ts create mode 100644 libs/funding-payments/src/lib/funding-payments-manager.tsx create mode 100644 libs/funding-payments/src/lib/funding-payments-table.spec.tsx create mode 100644 libs/funding-payments/src/lib/funding-payments-table.tsx create mode 100644 libs/funding-payments/src/lib/funding-payments.mock.ts create mode 100644 libs/funding-payments/src/lib/test-helpers.ts create mode 100644 libs/funding-payments/src/setup-tests.ts create mode 100644 libs/funding-payments/tailwind.config.js create mode 100644 libs/funding-payments/tsconfig.json create mode 100644 libs/funding-payments/tsconfig.lib.json create mode 100644 libs/funding-payments/tsconfig.spec.json diff --git a/apps/trading/client-pages/market/trade-grid.tsx b/apps/trading/client-pages/market/trade-grid.tsx index a25934d4d..ffc59ecf8 100644 --- a/apps/trading/client-pages/market/trade-grid.tsx +++ b/apps/trading/client-pages/market/trade-grid.tsx @@ -138,6 +138,9 @@ const MainGrid = memo( + + + { + + + diff --git a/apps/trading/components/funding-payments-container/funding-payments-container.tsx b/apps/trading/components/funding-payments-container/funding-payments-container.tsx new file mode 100644 index 000000000..aca80c192 --- /dev/null +++ b/apps/trading/components/funding-payments-container/funding-payments-container.tsx @@ -0,0 +1,46 @@ +import { useVegaWallet } from '@vegaprotocol/wallet'; +import { FundingPaymentsManager } from '@vegaprotocol/funding-payments'; +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; +import { useDataGridEvents } from '@vegaprotocol/datagrid'; +import { t } from '@vegaprotocol/i18n'; +import { Splash } from '@vegaprotocol/ui-toolkit'; +import type { DataGridSlice } from '../../stores/datagrid-store-slice'; +import { createDataGridSlice } from '../../stores/datagrid-store-slice'; +import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; + +export const FundingPaymentsContainer = () => { + const onMarketClick = useMarketClickHandler(true); + const { pubKey } = useVegaWallet(); + + const gridStore = useFundingPaymentsStore((store) => store.gridStore); + const updateGridStore = useFundingPaymentsStore( + (store) => store.updateGridStore + ); + + const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => { + updateGridStore(colState); + }); + + if (!pubKey) { + return ( + +

{t('Please connect Vega wallet')}

+
+ ); + } + + return ( + + ); +}; + +const useFundingPaymentsStore = create()( + persist(createDataGridSlice, { + name: 'vega_funding_payments_store', + }) +); diff --git a/apps/trading/components/funding-payments-container/index.ts b/apps/trading/components/funding-payments-container/index.ts new file mode 100644 index 000000000..cf75ae8e1 --- /dev/null +++ b/apps/trading/components/funding-payments-container/index.ts @@ -0,0 +1 @@ +export * from './funding-payments-container'; diff --git a/libs/cypress/mock.ts b/libs/cypress/mock.ts index ba354853b..c179f3634 100644 --- a/libs/cypress/mock.ts +++ b/libs/cypress/mock.ts @@ -10,6 +10,7 @@ export * from '../deposits/src/lib/deposit.mock'; export * from '../environment/src/utils/node.mock'; export * from '../environment/src/components/node-guard/node-guard.mock'; export * from '../fills/src/lib/fills.mock'; +export * from '../funding-payments/src/lib/funding-payments.mock'; export * from '../proposals/src/lib/proposals-data-provider/proposals.mock'; export * from '../market-depth/src/lib/market-depth.mock'; export * from '../markets/src/lib/components/market-info/market-info.mock'; diff --git a/libs/funding-payments/.babelrc b/libs/funding-payments/.babelrc new file mode 100644 index 000000000..6a6b0e302 --- /dev/null +++ b/libs/funding-payments/.babelrc @@ -0,0 +1,13 @@ +{ + "sourceType": "unambiguous", + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/funding-payments/.eslintrc.json b/libs/funding-payments/.eslintrc.json new file mode 100644 index 000000000..f3153d3b4 --- /dev/null +++ b/libs/funding-payments/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "__generated__"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/funding-payments/README.md b/libs/funding-payments/README.md new file mode 100644 index 000000000..566485e48 --- /dev/null +++ b/libs/funding-payments/README.md @@ -0,0 +1,7 @@ +# funding-payments + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test funding-payments` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/funding-payments/__mocks__/react-markdown.js b/libs/funding-payments/__mocks__/react-markdown.js new file mode 100644 index 000000000..70942790b --- /dev/null +++ b/libs/funding-payments/__mocks__/react-markdown.js @@ -0,0 +1,6 @@ +function ReactMarkdown({ children }) { + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>{children}; +} + +export default ReactMarkdown; diff --git a/libs/funding-payments/jest.config.ts b/libs/funding-payments/jest.config.ts new file mode 100644 index 000000000..f93cbd35a --- /dev/null +++ b/libs/funding-payments/jest.config.ts @@ -0,0 +1,17 @@ +/* eslint-disable */ +export default { + displayName: 'funding-payments', + preset: '../../jest.preset.js', + globals: {}, + transform: { + '^.+\\.[tj]sx?$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + }, + ], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/funding-payments', + setupFilesAfterEnv: ['./src/setup-tests.ts'], +}; diff --git a/libs/funding-payments/postcss.config.js b/libs/funding-payments/postcss.config.js new file mode 100644 index 000000000..cbdd9c22c --- /dev/null +++ b/libs/funding-payments/postcss.config.js @@ -0,0 +1,10 @@ +const { join } = require('path'); + +module.exports = { + plugins: { + tailwindcss: { + config: join(__dirname, 'tailwind.config.js'), + }, + autoprefixer: {}, + }, +}; diff --git a/libs/funding-payments/project.json b/libs/funding-payments/project.json new file mode 100644 index 000000000..aed85bd67 --- /dev/null +++ b/libs/funding-payments/project.json @@ -0,0 +1,37 @@ +{ + "name": "funding-payments", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/funding-payments/src", + "projectType": "library", + "tags": [], + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/funding-payments/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/libs/funding-payments"], + "options": { + "jestConfig": "libs/funding-payments/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "build-spec": { + "executor": "nx:run-commands", + "outputs": [], + "options": { + "command": "yarn tsc --project ./libs/funding-payments/tsconfig.spec.json" + } + } + } +} diff --git a/libs/funding-payments/src/index.ts b/libs/funding-payments/src/index.ts new file mode 100644 index 000000000..be290080b --- /dev/null +++ b/libs/funding-payments/src/index.ts @@ -0,0 +1,3 @@ +export * from './lib/funding-payments-manager'; +export * from './lib/funding-payments-data-provider'; +export * from './lib/__generated__/FundingPayments'; diff --git a/libs/funding-payments/src/lib/FundingPayments.graphql b/libs/funding-payments/src/lib/FundingPayments.graphql new file mode 100644 index 000000000..a2e91bbaf --- /dev/null +++ b/libs/funding-payments/src/lib/FundingPayments.graphql @@ -0,0 +1,24 @@ +fragment FundingPaymentFields on FundingPayment { + marketId + partyId + fundingPeriodSeq + amount + timestamp +} + +query FundingPayments($partyId: ID!, $pagination: Pagination) { + fundingPayments(partyId: $partyId, pagination: $pagination) { + edges { + node { + ...FundingPaymentFields + } + cursor + } + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } +} diff --git a/libs/funding-payments/src/lib/__generated__/FundingPayments.ts b/libs/funding-payments/src/lib/__generated__/FundingPayments.ts new file mode 100644 index 000000000..3c31acf32 --- /dev/null +++ b/libs/funding-payments/src/lib/__generated__/FundingPayments.ts @@ -0,0 +1,71 @@ +import * as Types from '@vegaprotocol/types'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} as const; +export type FundingPaymentFieldsFragment = { __typename?: 'FundingPayment', marketId: string, partyId: string, fundingPeriodSeq: number, amount?: string | null, timestamp: any }; + +export type FundingPaymentsQueryVariables = Types.Exact<{ + partyId: Types.Scalars['ID']; + pagination?: Types.InputMaybe; +}>; + + +export type FundingPaymentsQuery = { __typename?: 'Query', fundingPayments: { __typename?: 'FundingPaymentConnection', edges: Array<{ __typename?: 'FundingPaymentEdge', cursor: string, node: { __typename?: 'FundingPayment', marketId: string, partyId: string, fundingPeriodSeq: number, amount?: string | null, timestamp: any } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } }; + +export const FundingPaymentFieldsFragmentDoc = gql` + fragment FundingPaymentFields on FundingPayment { + marketId + partyId + fundingPeriodSeq + amount + timestamp +} + `; +export const FundingPaymentsDocument = gql` + query FundingPayments($partyId: ID!, $pagination: Pagination) { + fundingPayments(partyId: $partyId, pagination: $pagination) { + edges { + node { + ...FundingPaymentFields + } + cursor + } + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } +} + ${FundingPaymentFieldsFragmentDoc}`; + +/** + * __useFundingPaymentsQuery__ + * + * To run a query within a React component, call `useFundingPaymentsQuery` and pass it any options that fit your needs. + * When your component renders, `useFundingPaymentsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useFundingPaymentsQuery({ + * variables: { + * partyId: // value for 'partyId' + * pagination: // value for 'pagination' + * }, + * }); + */ +export function useFundingPaymentsQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(FundingPaymentsDocument, options); + } +export function useFundingPaymentsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(FundingPaymentsDocument, options); + } +export type FundingPaymentsQueryHookResult = ReturnType; +export type FundingPaymentsLazyQueryHookResult = ReturnType; +export type FundingPaymentsQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/libs/funding-payments/src/lib/funding-payments-data-provider.ts b/libs/funding-payments/src/lib/funding-payments-data-provider.ts new file mode 100644 index 000000000..88a9c9f6e --- /dev/null +++ b/libs/funding-payments/src/lib/funding-payments-data-provider.ts @@ -0,0 +1,69 @@ +import type { PageInfo, Cursor } from '@vegaprotocol/data-provider'; +import { + makeDataProvider, + makeDerivedDataProvider, + defaultAppend as append, +} from '@vegaprotocol/data-provider'; +import type { Market } from '@vegaprotocol/markets'; +import { marketsMapProvider } from '@vegaprotocol/markets'; +import { FundingPaymentsDocument } from './__generated__/FundingPayments'; +import type { + FundingPaymentsQuery, + FundingPaymentsQueryVariables, + FundingPaymentFieldsFragment, +} from './__generated__/FundingPayments'; + +export type FundingPayment = Omit & { + market?: Market; +}; + +const getData = ( + responseData: FundingPaymentsQuery | null +): (FundingPaymentFieldsFragment & Cursor)[] => + responseData?.fundingPayments?.edges.map< + FundingPaymentFieldsFragment & Cursor + >((edge) => ({ + ...edge.node, + cursor: edge.cursor, + })) || []; + +const getPageInfo = ( + responseData: FundingPaymentsQuery | null +): PageInfo | null => responseData?.fundingPayments?.pageInfo || null; + +export const fundingPaymentsProvider = makeDataProvider< + Parameters['0'], + ReturnType, + never, + never, + FundingPaymentsQueryVariables +>({ + query: FundingPaymentsDocument, + getData, + pagination: { + getPageInfo, + append, + first: 100, + }, +}); + +export const fundingPaymentsWithMarketProvider = makeDerivedDataProvider< + FundingPayment[], + never, + FundingPaymentsQueryVariables +>( + [ + fundingPaymentsProvider, + (callback, client) => marketsMapProvider(callback, client, undefined), + ], + (partsData): FundingPayment[] | null => { + return ((partsData[0] as ReturnType) || []).map( + (fundingPayment) => ({ + ...fundingPayment, + market: (partsData[1] as Record)[ + fundingPayment.marketId + ], + }) + ); + } +); diff --git a/libs/funding-payments/src/lib/funding-payments-manager.tsx b/libs/funding-payments/src/lib/funding-payments-manager.tsx new file mode 100644 index 000000000..a5f20e986 --- /dev/null +++ b/libs/funding-payments/src/lib/funding-payments-manager.tsx @@ -0,0 +1,42 @@ +import type { AgGridReact } from 'ag-grid-react'; +import { useRef } from 'react'; +import { t } from '@vegaprotocol/i18n'; +import { FundingPaymentsTable } from './funding-payments-table'; +import type { useDataGridEvents } from '@vegaprotocol/datagrid'; +import { useDataProvider } from '@vegaprotocol/data-provider'; +import { fundingPaymentsWithMarketProvider } from './funding-payments-data-provider'; + +interface FundingPaymentsManagerProps { + partyId: string; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; + gridProps: ReturnType; +} + +export const FundingPaymentsManager = ({ + partyId, + onMarketClick, + gridProps, +}: FundingPaymentsManagerProps) => { + const gridRef = useRef(null); + const { data, error } = useDataProvider({ + dataProvider: fundingPaymentsWithMarketProvider, + update: ({ data }) => { + if (data?.length && gridRef.current?.api) { + gridRef.current?.api.setRowData(data); + return true; + } + return false; + }, + variables: { partyId }, + }); + + return ( + + ); +}; diff --git a/libs/funding-payments/src/lib/funding-payments-table.spec.tsx b/libs/funding-payments/src/lib/funding-payments-table.spec.tsx new file mode 100644 index 000000000..86f913e14 --- /dev/null +++ b/libs/funding-payments/src/lib/funding-payments-table.spec.tsx @@ -0,0 +1,92 @@ +import { act, render, screen } from '@testing-library/react'; +import { getDateTimeFormat } from '@vegaprotocol/utils'; +import type { PartialDeep } from 'type-fest'; +import type { FundingPayment } from './funding-payments-data-provider'; +import { FundingPaymentsTable } from './funding-payments-table'; +import { generateFundingPayment } from './test-helpers'; + +describe('FundingPaymentsTable', () => { + let defaultFundingPayment: PartialDeep; + + beforeEach(() => { + defaultFundingPayment = { + marketId: + '69abf5c456c20f4d189cea79a11dfd6b0958ead58ab34bd66f73eea48aee600c', + partyId: + '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65', + fundingPeriodSeq: 84, + amount: '100', + timestamp: '2023-10-06T07:06:43.020994Z', + market: { + decimalPlaces: 2, + positionDecimalPlaces: 5, + tradableInstrument: { + instrument: { + code: 'test market', + product: { + __typename: 'Future', + settlementAsset: { + decimals: 2, + symbol: 'BTC', + }, + }, + }, + }, + }, + }; + }); + + it('correct columns are rendered', async () => { + await act(async () => { + render(); + }); + + const headers = screen.getAllByRole('columnheader'); + const expectedHeaders = ['Market', 'Amount', 'Date']; + expect(headers).toHaveLength(expectedHeaders.length); + expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders); + }); + + it('formats positive cells', async () => { + const fundingPayment = generateFundingPayment({ + ...defaultFundingPayment, + }); + render(); + const cells = screen.getAllByRole('gridcell'); + const expectedValues = [ + fundingPayment.market?.tradableInstrument.instrument.code || '', + '1.00 BTC', + getDateTimeFormat().format(new Date(fundingPayment.timestamp)), + ]; + cells.forEach((cell, i) => { + expect(cell).toHaveTextContent(expectedValues[i]); + }); + + const amountCell = cells.find((c) => c.getAttribute('col-id') === 'amount'); + expect( + amountCell?.querySelector('.ag-cell-value')?.firstElementChild + ).toHaveClass('text-market-green-600'); + }); + + it('formats negative cells', async () => { + const fundingPayment = generateFundingPayment({ + ...defaultFundingPayment, + amount: `-${defaultFundingPayment.amount}`, + }); + render(); + const cells = screen.getAllByRole('gridcell'); + const expectedValues = [ + fundingPayment.market?.tradableInstrument.instrument.code || '', + '-1.00 BTC', + getDateTimeFormat().format(new Date(fundingPayment.timestamp)), + ]; + cells.forEach((cell, i) => { + expect(cell).toHaveTextContent(expectedValues[i]); + }); + + const amountCell = cells.find((c) => c.getAttribute('col-id') === 'amount'); + expect( + amountCell?.querySelector('.ag-cell-value')?.firstElementChild + ).toHaveClass('text-market-red dark:text-market-red'); + }); +}); diff --git a/libs/funding-payments/src/lib/funding-payments-table.tsx b/libs/funding-payments/src/lib/funding-payments-table.tsx new file mode 100644 index 000000000..7811fe42d --- /dev/null +++ b/libs/funding-payments/src/lib/funding-payments-table.tsx @@ -0,0 +1,132 @@ +import { useMemo } from 'react'; +import type { + AgGridReact, + AgGridReactProps, + AgReactUiProps, +} from 'ag-grid-react'; +import type { ColDef } from 'ag-grid-community'; +import { + addDecimalsFormatNumber, + getDateTimeFormat, + isNumeric, + toBigNum, +} from '@vegaprotocol/utils'; +import { t } from '@vegaprotocol/i18n'; + +import { + AgGrid, + DateRangeFilter, + MarketNameCell, + negativeClassNames, + positiveClassNames, +} from '@vegaprotocol/datagrid'; +import type { + VegaValueFormatterParams, + VegaValueGetterParams, +} from '@vegaprotocol/datagrid'; +import { forwardRef } from 'react'; + +import type { FundingPayment } from './funding-payments-data-provider'; + +import { getAsset } from '@vegaprotocol/markets'; +import classNames from 'classnames'; + +const defaultColDef = { + resizable: true, + sortable: true, +}; + +export type Props = (AgGridReactProps | AgReactUiProps) & { + onMarketClick?: (marketId: string, metaKey?: boolean) => void; +}; + +const formatAmount = ({ + value, + data, +}: VegaValueFormatterParams) => { + if (!data?.market || !isNumeric(value)) { + return '-'; + } + const { symbol: assetSymbol, decimals: assetDecimals } = getAsset( + data.market + ); + const valueFormatted = addDecimalsFormatNumber(value, assetDecimals); + return `${valueFormatted} ${assetSymbol}`; +}; + +export const FundingPaymentsTable = forwardRef( + ({ onMarketClick, ...props }, ref) => { + const columnDefs = useMemo( + () => [ + { + headerName: t('Market'), + field: 'market.tradableInstrument.instrument.code', + cellRenderer: 'MarketNameCell', + filter: true, + cellRendererParams: { idPath: 'market.id', onMarketClick }, + }, + { + headerName: t('Amount'), + field: 'amount', + valueFormatter: formatAmount, + type: 'rightAligned', + filter: 'agNumberColumnFilter', + valueGetter: ({ data }: VegaValueGetterParams) => + data?.amount && data?.market + ? toBigNum(data.amount, getAsset(data.market).decimals).toNumber() + : 0, + cellRenderer: ({ data }: { data: FundingPayment }) => { + if (!data?.market || !isNumeric(data.amount)) { + return '-'; + } + const { symbol: assetSymbol, decimals: assetDecimals } = getAsset( + data.market + ); + const valueFormatted = addDecimalsFormatNumber( + data.amount, + assetDecimals + ); + return ( + <> + + {valueFormatted} + + {` ${assetSymbol}`} + + ); + }, + }, + { + headerName: t('Date'), + field: 'timestamp', + type: 'rightAligned', + filter: DateRangeFilter, + valueFormatter: ({ + value, + }: VegaValueFormatterParams) => { + return value ? getDateTimeFormat().format(new Date(value)) : ''; + }, + }, + ], + [onMarketClick] + ); + return ( + + `${data?.marketId}-${data?.fundingPeriodSeq}` + } + components={{ MarketNameCell }} + {...props} + /> + ); + } +); diff --git a/libs/funding-payments/src/lib/funding-payments.mock.ts b/libs/funding-payments/src/lib/funding-payments.mock.ts new file mode 100644 index 000000000..91c65eeec --- /dev/null +++ b/libs/funding-payments/src/lib/funding-payments.mock.ts @@ -0,0 +1,74 @@ +import type { + FundingPaymentsQuery, + FundingPaymentFieldsFragment, +} from './__generated__/FundingPayments'; +import merge from 'lodash/merge'; +import type { PartialDeep } from 'type-fest'; + +export const fundingPaymentsQuery = ( + override?: PartialDeep, + vegaPublicKey?: string +): FundingPaymentsQuery => { + const defaultResult: FundingPaymentsQuery = { + fundingPayments: { + __typename: 'FundingPaymentConnection', + edges: fundingPayments(vegaPublicKey).map((node) => ({ + __typename: 'FundingPaymentEdge', + cursor: '3', + node, + })), + pageInfo: { + __typename: 'PageInfo', + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + }, + }; + + return merge(defaultResult, override); +}; + +export const generateFundingPayment = ( + override?: PartialDeep +) => { + const defaultFundingPayment: FundingPaymentFieldsFragment = { + marketId: 'market-0', + partyId: 'partyId', + fundingPeriodSeq: 84, + amount: '126973', + timestamp: '2023-10-06T07:06:43.020994Z', + }; + + return merge(defaultFundingPayment, override); +}; + +const fundingPayments = ( + partyId = 'partyId' +): FundingPaymentFieldsFragment[] => [ + generateFundingPayment({ + partyId, + fundingPeriodSeq: 78, + amount: '92503', + timestamp: '2023-10-06T04:06:43.652759Z', + }), + generateFundingPayment({ + partyId, + fundingPeriodSeq: 77, + amount: '-37841', + timestamp: '2023-10-06T03:36:43.437139Z', + }), + generateFundingPayment({ + partyId, + fundingPeriodSeq: 76, + amount: '32838', + timestamp: '2023-10-06T03:06:43.430384Z', + }), + generateFundingPayment({ + partyId, + fundingPeriodSeq: 75, + amount: '-298259', + timestamp: '2023-10-06T02:36:43.51153Z', + }), +]; diff --git a/libs/funding-payments/src/lib/test-helpers.ts b/libs/funding-payments/src/lib/test-helpers.ts new file mode 100644 index 000000000..5be8e36a9 --- /dev/null +++ b/libs/funding-payments/src/lib/test-helpers.ts @@ -0,0 +1,102 @@ +import merge from 'lodash/merge'; +import type { PartialDeep } from 'type-fest'; +import * as Schema from '@vegaprotocol/types'; +import type { FundingPayment } from './funding-payments-data-provider'; + +const { MarketState, MarketTradingMode } = Schema; + +export const generateFundingPayment = ( + override?: PartialDeep +) => { + const defaultFundingPayment: FundingPayment = { + marketId: + '69abf5c456c20f4d189cea79a11dfd6b0958ead58ab34bd66f73eea48aee600c', + partyId: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65', + fundingPeriodSeq: 84, + amount: '126973', + timestamp: '2023-10-06T07:06:43.020994Z', + market: { + __typename: 'Market', + id: 'market-id', + positionDecimalPlaces: 0, + decimalPlaces: 5, + state: MarketState.STATE_ACTIVE, + tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, + liquidityMonitoringParameters: { + triggeringRatio: '1', + }, + fees: { + __typename: 'Fees', + factors: { + __typename: 'FeeFactors', + infrastructureFee: '0.1', + liquidityFee: '0.1', + makerFee: '0.1', + }, + }, + marketTimestamps: { + __typename: 'MarketTimestamps', + open: '2005-04-02T19:37:00.000Z', + close: '2005-04-02T19:37:00.000Z', + }, + tradableInstrument: { + __typename: 'TradableInstrument', + instrument: { + __typename: 'Instrument', + id: 'instrument-id', + code: 'instrument-code', + name: 'UNIDAI Monthly (30 Jun 2022)', + metadata: { + __typename: 'InstrumentMetadata', + tags: ['tag-a'], + }, + product: { + __typename: 'Future', + settlementAsset: { + __typename: 'Asset', + id: 'asset-id', + name: 'asset-id', + symbol: 'SYM', + decimals: 18, + quantum: '1', + }, + quoteName: '', + dataSourceSpecForTradingTermination: { + __typename: 'DataSourceSpec', + id: 'oracleId', + data: { + __typename: 'DataSourceDefinition', + sourceType: { + __typename: 'DataSourceDefinitionExternal', + sourceType: { + __typename: 'DataSourceSpecConfiguration', + }, + }, + }, + }, + dataSourceSpecForSettlementData: { + __typename: 'DataSourceSpec', + id: 'oracleId', + data: { + __typename: 'DataSourceDefinition', + sourceType: { + __typename: 'DataSourceDefinitionExternal', + sourceType: { + __typename: 'DataSourceSpecConfiguration', + }, + }, + }, + }, + dataSourceSpecBinding: { + __typename: 'DataSourceSpecToFutureBinding', + tradingTerminationProperty: 'trading-termination-property', + settlementDataProperty: 'settlement-data-property', + }, + }, + }, + }, + }, + }; + + return merge(defaultFundingPayment, override); +}; diff --git a/libs/funding-payments/src/setup-tests.ts b/libs/funding-payments/src/setup-tests.ts new file mode 100644 index 000000000..68773380a --- /dev/null +++ b/libs/funding-payments/src/setup-tests.ts @@ -0,0 +1,4 @@ +import '@testing-library/jest-dom'; +import ResizeObserver from 'resize-observer-polyfill'; + +global.ResizeObserver = ResizeObserver; diff --git a/libs/funding-payments/tailwind.config.js b/libs/funding-payments/tailwind.config.js new file mode 100644 index 000000000..897f6c3cc --- /dev/null +++ b/libs/funding-payments/tailwind.config.js @@ -0,0 +1,17 @@ +const { join } = require('path'); +const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); +const theme = require('../tailwindcss-config/src/theme'); +const vegaCustomClasses = require('../tailwindcss-config/src/vega-custom-classes'); + +module.exports = { + content: [ + join(__dirname, 'src/**/*.{ts,tsx,html,mdx}'), + join(__dirname, '.storybook/preview.js'), + ...createGlobPatternsForDependencies(__dirname), + ], + darkMode: 'class', + theme: { + extend: theme, + }, + plugins: [vegaCustomClasses], +}; diff --git a/libs/funding-payments/tsconfig.json b/libs/funding-payments/tsconfig.json new file mode 100644 index 000000000..60c68a3c2 --- /dev/null +++ b/libs/funding-payments/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + }, + { + "path": "./.storybook/tsconfig.json" + } + ] +} diff --git a/libs/funding-payments/tsconfig.lib.json b/libs/funding-payments/tsconfig.lib.json new file mode 100644 index 000000000..733dfff52 --- /dev/null +++ b/libs/funding-payments/tsconfig.lib.json @@ -0,0 +1,27 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nx/react/typings/cssmodule.d.ts", + "../../node_modules/@nx/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx", + "**/*.stories.ts", + "**/*.stories.js", + "**/*.stories.jsx", + "**/*.stories.tsx", + "jest.config.ts" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/funding-payments/tsconfig.spec.json b/libs/funding-payments/tsconfig.spec.json new file mode 100644 index 000000000..3da863401 --- /dev/null +++ b/libs/funding-payments/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node", "@testing-library/jest-dom"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts", + "jest.config.ts" + ] +} diff --git a/libs/types/src/__generated__/types.ts b/libs/types/src/__generated__/types.ts index ad3606ce2..fc05118b9 100644 --- a/libs/types/src/__generated__/types.ts +++ b/libs/types/src/__generated__/types.ts @@ -1287,6 +1287,39 @@ export type Filter = { key: PropertyKey; }; +/** The funding payment from a perpetual market. */ +export type FundingPayment = { + __typename?: 'FundingPayment'; + /** Amount transferred */ + amount?: Maybe; + /** Sequence number of the funding period the funding payment belongs to. */ + fundingPeriodSeq: Scalars['Int']; + /** Market the funding payment applies to. */ + marketId: Scalars['ID']; + /** Party the funding payment applies to. */ + partyId: Scalars['ID']; + /** RFC3339Nano timestamp when the data point was received. */ + timestamp: Scalars['Timestamp']; +}; + +/** Connection type for funding payment */ +export type FundingPaymentConnection = { + __typename?: 'FundingPaymentConnection'; + /** List of funding payments */ + edges: Array; + /** Pagination information */ + pageInfo: PageInfo; +}; + +/** Edge type for funding payment */ +export type FundingPaymentEdge = { + __typename?: 'FundingPaymentEdge'; + /** Cursor identifying the funding payment */ + cursor: Scalars['String']; + /** The funding payment */ + node: FundingPayment; +}; + /** Details of a funding interval for a perpetual market. */ export type FundingPeriod = { __typename?: 'FundingPeriod'; @@ -4097,6 +4130,8 @@ export type Query = { coreSnapshots?: Maybe; /** Get the current referral program */ currentReferralProgram?: Maybe; + /** Get the current volume discount program */ + currentVolumeDiscountProgram?: Maybe; /** Find a deposit using its ID */ deposit?: Maybe; /** Fetch all deposits */ @@ -4128,6 +4163,8 @@ export type Query = { estimatePosition?: Maybe; /** Query for historic ethereum key rotations */ ethereumKeyRotations: EthereumKeyRotationsConnection; + /** Funding payment for perpetual markets. */ + fundingPayments: FundingPaymentConnection; /** * Funding period data points for a perpetual market. The data points within a funding period are used to calculate the * time-weighted average price (TWAP), funding rate and funding payments for each funding period. @@ -4196,6 +4233,8 @@ export type Query = { /** Get referrer fee and discount stats */ referralFeeStats?: Maybe; referralSetReferees: ReferralSetRefereeConnection; + /** Get referral set statistics */ + referralSetStats: ReferralSetStatsConnection; /** List referral sets */ referralSets: ReferralSetConnection; /** Get statistics about the Vega node */ @@ -4224,6 +4263,8 @@ export type Query = { transfer?: Maybe; /** Get a list of all transfers for a public key */ transfersConnection?: Maybe; + /** Get volume discount statistics */ + volumeDiscountStats: VolumeDiscountStatsConnection; /** Find a withdrawal using its ID */ withdrawal?: Maybe; /** Fetch all withdrawals */ @@ -4369,6 +4410,14 @@ export type QueryethereumKeyRotationsArgs = { }; +/** Queries allow a caller to read data and filter data via GraphQL. */ +export type QueryfundingPaymentsArgs = { + marketId?: InputMaybe; + pagination?: InputMaybe; + partyId: Scalars['ID']; +}; + + /** Queries allow a caller to read data and filter data via GraphQL. */ export type QueryfundingPeriodDataPointsArgs = { dateRange?: InputMaybe; @@ -4568,6 +4617,15 @@ export type QueryreferralSetRefereesArgs = { }; +/** Queries allow a caller to read data and filter data via GraphQL. */ +export type QueryreferralSetStatsArgs = { + epoch?: InputMaybe; + id: Scalars['ID']; + pagination?: InputMaybe; + partyId?: InputMaybe; +}; + + /** Queries allow a caller to read data and filter data via GraphQL. */ export type QueryreferralSetsArgs = { id?: InputMaybe; @@ -4642,6 +4700,14 @@ export type QuerytransfersConnectionArgs = { }; +/** Queries allow a caller to read data and filter data via GraphQL. */ +export type QueryvolumeDiscountStatsArgs = { + epoch?: InputMaybe; + pagination?: InputMaybe; + partyId?: InputMaybe; +}; + + /** Queries allow a caller to read data and filter data via GraphQL. */ export type QuerywithdrawalArgs = { id: Scalars['ID']; @@ -4700,18 +4766,6 @@ export type RecurringTransfer = { startEpoch: Scalars['Int']; }; -export type RefereeStats = { - __typename?: 'RefereeStats'; - /** Discount factor applied to the party. */ - discountFactor: Scalars['String']; - /** Current referee notional taker volume */ - epochNotionalTakerVolume: Scalars['String']; - /** Unique ID of the party. */ - partyId: Scalars['ID']; - /** Reward factor applied to the party. */ - rewardFactor: Scalars['String']; -}; - /** Referral program information */ export type ReferralProgram = { __typename?: 'ReferralProgram'; @@ -4742,22 +4796,10 @@ export type ReferralSet = { id: Scalars['ID']; /** Party that created the set. */ referrer: Scalars['ID']; - /** - * Referral set statistics for the latest or specific epoch. - * If provided the results can be filtered for a specific referee - */ - stats?: Maybe; /** Timestamp as RFC3339Nano when the referral set was updated. */ updatedAt: Scalars['Timestamp']; }; - -/** Data relating to a referral set. */ -export type ReferralSetstatsArgs = { - epoch?: InputMaybe; - referee?: InputMaybe; -}; - /** Connection type for retrieving cursor-based paginated referral set information */ export type ReferralSetConnection = { __typename?: 'ReferralSetConnection'; @@ -4828,14 +4870,36 @@ export type ReferralSetRefereeEdge = { export type ReferralSetStats = { __typename?: 'ReferralSetStats'; - /** Epoch at which the set's statistics are updated. */ - atEpoch?: Maybe; - /** Referees' statistics for that epoch. */ - referees_stats: Array; + /** Epoch at which the statistics are updated. */ + atEpoch: Scalars['Int']; + /** Discount factor applied to the party. */ + discountFactor: Scalars['String']; + /** Current referee notional taker volume */ + epochNotionalTakerVolume: Scalars['String']; + /** Unique ID of the party. */ + partyId: Scalars['ID']; /** Running volume for the set based on the window length of the current referral program. */ referralSetRunningNotionalTakerVolume: Scalars['String']; - /** Unique ID of the set */ - setId: Scalars['ID']; + /** Reward factor applied to the party. */ + rewardFactor: Scalars['String']; +}; + +/** Connection type for retrieving cursor-based paginated referral set statistics information */ +export type ReferralSetStatsConnection = { + __typename?: 'ReferralSetStatsConnection'; + /** The referral set statistics in this connection */ + edges: Array>; + /** The pagination information */ + pageInfo: PageInfo; +}; + +/** Edge type containing the referral set statistics and cursor information returned by a ReferralSetStatsConnection */ +export type ReferralSetStatsEdge = { + __typename?: 'ReferralSetStatsEdge'; + /** The cursor for this referral set statistics */ + cursor: Scalars['String']; + /** The referral set statistics */ + node: ReferralSetStats; }; /** Rewards generated for referrers by each of their referees */ @@ -6137,6 +6201,53 @@ export type VolumeBenefitTier = { volumeDiscountFactor: Scalars['String']; }; +/** Volume discount program information */ +export type VolumeDiscountProgram = { + __typename?: 'VolumeDiscountProgram'; + /** Defined tiers in increasing order. First element will give Tier 1, second element will give Tier 2, etc. */ + benefitTiers: Array; + /** Timestamp as Unix time in nanoseconds, after which when the current epoch ends, the programs will end and benefits will be disabled. */ + endOfProgramTimestamp: Scalars['Timestamp']; + /** Timestamp as RFC3339Nano when the program ended. If present, the current program has ended and no program is currently running. */ + endedAt?: Maybe; + /** Unique ID generated from the proposal that created this program. */ + id: Scalars['ID']; + /** Incremental version of the program. It is incremented each time the volume discount program is edited. */ + version: Scalars['Int']; + /** Number of epochs over which to evaluate parties' running volume. */ + windowLength: Scalars['Int']; +}; + +export type VolumeDiscountStats = { + __typename?: 'VolumeDiscountStats'; + /** Epoch at which the statistics are updated. */ + atEpoch: Scalars['Int']; + /** Discount factor applied to the party. */ + discountFactor: Scalars['String']; + /** Unique ID of the party. */ + partyId: Scalars['ID']; + /** Party's running volume. */ + runningVolume: Scalars['String']; +}; + +/** Connection type for retrieving cursor-based paginated volume discount statistics information */ +export type VolumeDiscountStatsConnection = { + __typename?: 'VolumeDiscountStatsConnection'; + /** The volume discount statistics in this connection */ + edges: Array>; + /** The pagination information */ + pageInfo: PageInfo; +}; + +/** Edge type containing the volume discount statistics and cursor information returned by a VolumeDiscountStatsConnection */ +export type VolumeDiscountStatsEdge = { + __typename?: 'VolumeDiscountStatsEdge'; + /** The cursor for this volume discount statistics */ + cursor: Scalars['String']; + /** The volume discount statistics */ + node: VolumeDiscountStats; +}; + export type Vote = { __typename?: 'Vote'; /** RFC3339Nano time and date when the vote reached Vega network */ diff --git a/tsconfig.base.json b/tsconfig.base.json index ca3289ef3..32183dce5 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -28,6 +28,7 @@ "@vegaprotocol/deposits": ["libs/deposits/src/index.ts"], "@vegaprotocol/environment": ["libs/environment/src/index.ts"], "@vegaprotocol/fills": ["libs/fills/src/index.ts"], + "@vegaprotocol/funding-payments": ["libs/funding-payments/src/index.ts"], "@vegaprotocol/i18n": ["libs/i18n/src/index.ts"], "@vegaprotocol/ledger": ["libs/ledger/src/index.ts"], "@vegaprotocol/liquidity": ["libs/liquidity/src/index.ts"],