Feat/462 fills table mvp (no pagination) (#645)

* feat: scaffold fills components

* feat: add query for fills and populate data in basic table

* feat: refactor portfolio page to use grid and add fills container

* feat: add infinite scroll for fills

* feat: try with data provider, get infinite scroll working

* chore: remove infinite scrolling as subsequent pr will add it

* chore: reorder columns

* chore: remove Autosizer from portfolio grid children as its not needed

* chore: move fills tab to the end

* feat: add storybook, format cells

* feat: add unit test for fills table

* feat: convert lib to next lib

* feat: add pagination variables to fills query to only get latest 300

* fix: fills data provider update function to return result of produce

* fix: yarn.lock

* fix: cypress run by moving test helpers

* fix: re add test helpers for unit tests

* fix: global connection tests

* fix: use fills from mocks

* feat: add update handler for fills

* chore: move value formatter functions into module scope
This commit is contained in:
Matthew Russell 2022-06-30 00:52:25 -07:00 committed by GitHub
parent c0a744ff78
commit 8e6c066b99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 2043 additions and 69 deletions

View File

@ -10,6 +10,7 @@ describe('vega wallet', () => {
beforeEach(() => { beforeEach(() => {
// Using portfolio page as it requires vega wallet connection // Using portfolio page as it requires vega wallet connection
cy.visit('/portfolio'); cy.visit('/portfolio');
cy.get('main[data-testid="portfolio"]').should('exist');
}); });
it('can connect', () => { it('can connect', () => {
@ -64,8 +65,10 @@ describe('vega wallet', () => {
describe('ethereum wallet', () => { describe('ethereum wallet', () => {
beforeEach(() => { beforeEach(() => {
cy.mockWeb3Provider(); cy.mockWeb3Provider();
// Using portfolio is it requires Ethereum wallet connection // Using portfolio withdrawals tab is it requires Ethereum wallet connection
cy.visit('/portfolio'); cy.visit('/portfolio');
cy.get('main[data-testid="portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click();
}); });
it('can connect', () => { it('can connect', () => {
@ -73,6 +76,6 @@ describe('ethereum wallet', () => {
cy.getByTestId('web3-connector-list').should('exist'); cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-MetaMask').click(); cy.getByTestId('web3-connector-MetaMask').click();
cy.getByTestId('web3-connector-list').should('not.exist'); cy.getByTestId('web3-connector-list').should('not.exist');
cy.getByTestId('portfolio-grid').should('exist'); cy.getByTestId('tab-withdrawals').should('not.be.empty');
}); });
}); });

View File

@ -0,0 +1,120 @@
import { aliasQuery } from '@vegaprotocol/cypress';
import { generateFill, generateFills } from '../support/mocks/generate-fills';
import { Side } from '@vegaprotocol/types';
import { connectVegaWallet } from '../support/vega-wallet';
describe('fills', () => {
before(() => {
const fills = [
generateFill({
buyer: {
id: Cypress.env('VEGA_PUBLIC_KEY'),
},
}),
generateFill({
id: '1',
seller: {
id: Cypress.env('VEGA_PUBLIC_KEY'),
},
aggressor: Side.Sell,
buyerFee: {
infrastructureFee: '5000',
},
market: {
name: 'Apples Daily v3',
positionDecimalPlaces: 2,
},
}),
generateFill({
id: '2',
seller: {
id: Cypress.env('VEGA_PUBLIC_KEY'),
},
aggressor: Side.Buy,
}),
generateFill({
id: '3',
aggressor: Side.Sell,
market: {
name: 'ETHBTC Quarterly (30 Jun 2022)',
},
buyer: {
id: Cypress.env('VEGA_PUBLIC_KEY'),
},
}),
];
const result = generateFills({
party: {
tradesPaged: {
edges: fills.map((f, i) => {
return {
__typename: 'TradeEdge',
node: f,
cursor: i.toString(),
};
}),
},
},
});
cy.mockGQL((req) => {
aliasQuery(req, 'Fills', result);
});
cy.visit('/portfolio');
cy.get('main[data-testid="portfolio"]').should('exist');
});
it('renders fills', () => {
cy.getByTestId('Fills').click();
cy.getByTestId('tab-fills').contains('Please connect Vega wallet');
connectVegaWallet();
cy.getByTestId('tab-fills').should('be.visible');
cy.getByTestId('tab-fills')
.get('[role="gridcell"][col-id="market.name"]')
.each(($marketSymbol) => {
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-fills')
.get('[role="gridcell"][col-id="size"]')
.each(($amount) => {
cy.wrap($amount).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-positions')
.get('[role="gridcell"][col-id="price"]')
.each(($prices) => {
cy.wrap($prices).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-positions')
.get('[role="gridcell"][col-id="price_1"]')
.each(($total) => {
cy.wrap($total).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-positions')
.get('[role="gridcell"][col-id="aggressor"]')
.each(($role) => {
cy.wrap($role)
.invoke('text')
.then((text) => {
const roles = ['Maker', 'Taker'];
expect(roles.indexOf(text.trim())).to.be.greaterThan(-1);
});
});
cy.getByTestId('tab-positions')
.get(
'[role="gridcell"][col-id="market.tradableInstrument.instrument.product"]'
)
.each(($fees) => {
cy.wrap($fees).invoke('text').should('not.be.empty');
});
const dateTimeRegex =
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
cy.get('[col-id="createdAt"]').each(($tradeDateTime, index) => {
if (index != 0) {
//ignore header
cy.wrap($tradeDateTime).invoke('text').should('match', dateTimeRegex);
}
});
});
});

View File

@ -1,6 +0,0 @@
describe('portfolio', () => {
it('requires connecting', () => {
cy.visit('/portfolio');
cy.get('main[data-testid="portfolio"]').should('exist');
});
});

View File

@ -0,0 +1,134 @@
import type {
Fills,
Fills_party_tradesPaged_edges_node,
} from '@vegaprotocol/fills';
import { Side } from '@vegaprotocol/types';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
export const generateFills = (override?: PartialDeep<Fills>): Fills => {
const fills: Fills_party_tradesPaged_edges_node[] = [
generateFill({
buyer: {
id: Cypress.env('VEGA_PUBLIC_KEY'),
},
}),
generateFill({
id: '1',
seller: {
id: Cypress.env('VEGA_PUBLIC_KEY'),
},
aggressor: Side.Sell,
buyerFee: {
infrastructureFee: '5000',
},
market: {
name: 'Apples Daily v3',
positionDecimalPlaces: 2,
},
}),
generateFill({
id: '2',
seller: {
id: Cypress.env('VEGA_PUBLIC_KEY'),
},
aggressor: Side.Buy,
}),
generateFill({
id: '3',
aggressor: Side.Sell,
market: {
name: 'ETHBTC Quarterly (30 Jun 2022)',
},
buyer: {
id: Cypress.env('VEGA_PUBLIC_KEY'),
},
}),
];
const defaultResult: Fills = {
party: {
id: 'buyer-id',
tradesPaged: {
__typename: 'TradeConnection',
totalCount: 1,
edges: fills.map((f) => {
return {
__typename: 'TradeEdge',
node: f,
cursor: '3',
};
}),
pageInfo: {
__typename: 'PageInfo',
startCursor: '1',
endCursor: '2',
},
},
__typename: 'Party',
},
};
return merge(defaultResult, override);
};
export const generateFill = (
override?: PartialDeep<Fills_party_tradesPaged_edges_node>
) => {
const defaultFill: Fills_party_tradesPaged_edges_node = {
__typename: 'Trade',
id: '0',
createdAt: new Date().toISOString(),
price: '10000000',
size: '50000',
buyOrder: 'buy-order',
sellOrder: 'sell-order',
aggressor: Side.Buy,
buyer: {
__typename: 'Party',
id: 'buyer-id',
},
seller: {
__typename: 'Party',
id: 'seller-id',
},
buyerFee: {
__typename: 'TradeFee',
makerFee: '100',
infrastructureFee: '100',
liquidityFee: '100',
},
sellerFee: {
__typename: 'TradeFee',
makerFee: '200',
infrastructureFee: '200',
liquidityFee: '200',
},
market: {
__typename: 'Market',
id: 'market-id',
name: 'UNIDAI Monthly (30 Jun 2022)',
positionDecimalPlaces: 0,
decimalPlaces: 5,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
id: 'instrument-id',
code: 'instrument-code',
product: {
__typename: 'Future',
settlementAsset: {
__typename: 'Asset',
id: 'asset-id',
symbol: 'SYM',
decimals: 18,
},
},
},
},
},
};
return merge(defaultFill, override);
};

View File

@ -5,71 +5,76 @@ import { OrderListContainer } from '@vegaprotocol/order-list';
import { AccountsContainer } from '@vegaprotocol/accounts'; import { AccountsContainer } from '@vegaprotocol/accounts';
import { AnchorButton, Tab, Tabs } from '@vegaprotocol/ui-toolkit'; import { AnchorButton, Tab, Tabs } from '@vegaprotocol/ui-toolkit';
import { WithdrawalsContainer } from './withdrawals/withdrawals-container'; import { WithdrawalsContainer } from './withdrawals/withdrawals-container';
import { FillsContainer } from '@vegaprotocol/fills';
import classNames from 'classnames';
import type { ReactNode } from 'react';
const Portfolio = () => { const Portfolio = () => {
const tabClassName = 'p-[16px] pl-[316px]'; const wrapperClasses = classNames(
'h-full max-h-full',
'grid gap-4 grid-rows-[1fr_300px]',
'bg-black-10 dark:bg-white-10',
'text-ui'
);
const tabContentClassName = 'h-full grid gap-4 grid-rows-[min-content_1fr]';
return ( return (
<Web3Container> <div className={wrapperClasses}>
<div className="h-full text-ui"> <PortfolioGridChild>
<main className="relative h-[calc(100%-200px)]">
<aside className="absolute px-[8px] py-[16px] w-[300px] mt-[28px] h-[calc(100%-28px)] w-[300px] overflow-auto">
<h2 className="text-h4 text-black dark:text-white">
{t('Filters')}
</h2>
</aside>
<section data-testid="portfolio-grid">
<Tabs> <Tabs>
<Tab id="positions" name={t('Positions')}> <Tab id="positions" name={t('Positions')}>
<div className={tabClassName}> <div className={tabContentClassName}>
<h4 className="text-h4 text-black dark:text-white"> <h4 className="text-h4 text-black dark:text-white p-8">
{t('Positions')} {t('Positions')}
</h4> </h4>
<div>
<PositionsContainer /> <PositionsContainer />
</div> </div>
</div>
</Tab> </Tab>
<Tab id="orders" name={t('Orders')}> <Tab id="orders" name={t('Orders')}>
<div className={tabClassName}> <div className={tabContentClassName}>
<h4 className="text-h4 text-black dark:text-white"> <h4 className="text-h4 text-black dark:text-white p-8">
{t('Orders')} {t('Orders')}
</h4> </h4>
<div>
<OrderListContainer /> <OrderListContainer />
</div> </div>
</div>
</Tab> </Tab>
<Tab id="fills" name={t('Fills')}> <Tab id="fills" name={t('Fills')}>
<div className={tabClassName}> <div className={tabContentClassName}>
<h4 className="text-h4 text-black dark:text-white"> <h4 className="text-h4 text-black dark:text-white p-8">
{t('Fills')} {t('Fills')}
</h4> </h4>
<div>
<FillsContainer />
</div> </div>
</Tab>
<Tab id="history" name={t('History')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('History')}
</h4>
</div> </div>
</Tab> </Tab>
</Tabs> </Tabs>
</section> </PortfolioGridChild>
</main> <PortfolioGridChild>
<section className="fixed bottom-0 left-0 w-full h-[200px]">
<Tabs> <Tabs>
<Tab id="collateral" name={t('Collateral')}> <Tab id="collateral" name={t('Collateral')}>
<AccountsContainer /> <AccountsContainer />
</Tab> </Tab>
<Tab id="deposits" name={t('Deposits')}> <Tab id="deposits" name={t('Deposits')}>
<div className={tabContentClassName}>
<div className="p-8">
<AnchorButton data-testid="deposit" href="/portfolio/deposit"> <AnchorButton data-testid="deposit" href="/portfolio/deposit">
{t('Deposit')} {t('Deposit')}
</AnchorButton> </AnchorButton>
</div>
</div>
</Tab> </Tab>
<Tab id="withdrawals" name={t('Withdrawals')}> <Tab id="withdrawals" name={t('Withdrawals')}>
<Web3Container>
<WithdrawalsContainer /> <WithdrawalsContainer />
</Web3Container>
</Tab> </Tab>
</Tabs> </Tabs>
</section> </PortfolioGridChild>
</div> </div>
</Web3Container>
); );
}; };
@ -78,3 +83,16 @@ Portfolio.getInitialProps = () => ({
}); });
export default Portfolio; export default Portfolio;
interface PortfolioGridChildProps {
children: ReactNode;
className?: string;
}
const PortfolioGridChild = ({
children,
className,
}: PortfolioGridChildProps) => {
const gridChildClasses = classNames('bg-white dark:bg-black', className);
return <section className={gridChildClasses}>{children}</section>;
};

12
libs/fills/.babelrc Normal file
View File

@ -0,0 +1,12 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

18
libs/fills/.eslintrc.json Normal file
View File

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,28 @@
const rootMain = require('../../../.storybook/main');
module.exports = {
...rootMain,
core: { ...rootMain.core, builder: 'webpack5' },
stories: [
...rootMain.stories,
'../src/lib/**/*.stories.mdx',
'../src/lib/**/*.stories.@(js|jsx|ts|tsx)',
],
addons: [
...rootMain.addons,
'@nrwl/react/plugins/storybook',
'storybook-addon-themes',
],
webpackFinal: async (config, { configType }) => {
// apply any global webpack configs that might have been specified in .storybook/main.js
if (rootMain.webpackFinal) {
config = await rootMain.webpackFinal(config, { configType });
}
// add your own webpack tweaks if needed
return config;
},
};

View File

@ -0,0 +1 @@
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />

View File

@ -0,0 +1,51 @@
import './styles.css';
import { ThemeContext } from '@vegaprotocol/react-helpers';
import { useEffect, useState } from 'react';
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
backgrounds: { disable: true },
themes: {
default: 'dark',
list: [
{ name: 'dark', class: ['dark', 'bg-black'], color: '#000' },
{ name: 'light', class: '', color: '#FFF' },
],
},
};
export const decorators = [
(Story, context) => {
// storybook-addon-themes doesnt seem to provide the current selected
// theme in context, we need to provid it in JS as some components
// rely on it for rendering
const [theme, setTheme] = useState(context.parameters.themes.default);
useEffect(() => {
const observer = new MutationObserver((mutationList) => {
if (mutationList.length) {
const body = mutationList[0].target;
if (body.classList.contains('dark')) {
setTheme('dark');
} else {
setTheme('light');
}
}
});
observer.observe(document.body, { attributes: true });
return () => {
observer.disconnect();
};
}, []);
return (
<div style={{ width: '100%', height: 500 }}>
<ThemeContext.Provider value={theme}>
<Story />
</ThemeContext.Provider>
</div>
);
},
];

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,19 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"emitDecoratorMetadata": true,
"outDir": ""
},
"files": [
"../../../node_modules/@nrwl/react/typings/styled-jsx.d.ts",
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": [
"../**/*.spec.ts",
"../**/*.spec.js",
"../**/*.spec.tsx",
"../**/*.spec.jsx"
],
"include": ["../src/**/*", "*.js"]
}

7
libs/fills/README.md Normal file
View File

@ -0,0 +1,7 @@
# fills
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test fills` to execute the unit tests via [Jest](https://jestjs.io).

15
libs/fills/jest.config.js Normal file
View File

@ -0,0 +1,15 @@
module.exports = {
displayName: 'fills',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/fills',
setupFilesAfterEnv: ['./src/setup-tests.ts'],
};

4
libs/fills/package.json Normal file
View File

@ -0,0 +1,4 @@
{
"name": "@vegaprotocol/fills",
"version": "0.0.1"
}

View File

@ -0,0 +1,10 @@
const { join } = require('path');
module.exports = {
plugins: {
tailwindcss: {
config: join(__dirname, 'tailwind.config.js'),
},
autoprefixer: {},
},
};

74
libs/fills/project.json Normal file
View File

@ -0,0 +1,74 @@
{
"root": "libs/fills",
"sourceRoot": "libs/fills/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/fills",
"tsConfig": "libs/fills/tsconfig.lib.json",
"project": "libs/fills/package.json",
"entryFile": "libs/fills/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/fills/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/fills/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/libs/fills"],
"options": {
"jestConfig": "libs/fills/jest.config.js",
"passWithNoTests": true
}
},
"storybook": {
"executor": "@nrwl/storybook:storybook",
"options": {
"uiFramework": "@storybook/react",
"port": 4400,
"config": {
"configFolder": "libs/fills/.storybook"
}
},
"configurations": {
"ci": {
"quiet": true
}
}
},
"build-storybook": {
"executor": "@nrwl/storybook:build",
"outputs": ["{options.outputPath}"],
"options": {
"uiFramework": "@storybook/react",
"outputPath": "dist/storybook/fills",
"config": {
"configFolder": "libs/fills/.storybook"
}
},
"configurations": {
"ci": {
"quiet": true
}
}
}
}
}

4
libs/fills/src/index.ts Normal file
View File

@ -0,0 +1,4 @@
export * from './lib/fills-container';
export * from './lib/__generated__/FillFields';
export * from './lib/__generated__/Fills';
export * from './lib/__generated__/FillsSub';

View File

@ -0,0 +1,197 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { Side } from "@vegaprotocol/types";
// ====================================================
// GraphQL fragment: FillFields
// ====================================================
export interface FillFields_buyer {
__typename: "Party";
/**
* Party identifier
*/
id: string;
}
export interface FillFields_seller {
__typename: "Party";
/**
* Party identifier
*/
id: string;
}
export interface FillFields_buyerFee {
__typename: "TradeFee";
/**
* The maker fee, aggressive party to the other party (the one who had an order in the book)
*/
makerFee: string;
/**
* The infrastructure fee, a fee paid to the node runner to maintain the vega network
*/
infrastructureFee: string;
/**
* The fee paid to the market makers to provide liquidity in the market
*/
liquidityFee: string;
}
export interface FillFields_sellerFee {
__typename: "TradeFee";
/**
* The maker fee, aggressive party to the other party (the one who had an order in the book)
*/
makerFee: string;
/**
* The infrastructure fee, a fee paid to the node runner to maintain the vega network
*/
infrastructureFee: string;
/**
* The fee paid to the market makers to provide liquidity in the market
*/
liquidityFee: string;
}
export interface FillFields_market_tradableInstrument_instrument_product_settlementAsset {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The precision of the asset
*/
decimals: number;
}
export interface FillFields_market_tradableInstrument_instrument_product {
__typename: "Future";
/**
* The name of the asset (string)
*/
settlementAsset: FillFields_market_tradableInstrument_instrument_product_settlementAsset;
}
export interface FillFields_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* Uniquely identify an instrument across all instruments available on Vega (string)
*/
id: string;
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
*/
code: string;
/**
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
*/
product: FillFields_market_tradableInstrument_instrument_product;
}
export interface FillFields_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: FillFields_market_tradableInstrument_instrument;
}
export interface FillFields_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
* GBP 100 0 GBP 100
* GBP 100 2 GBP 1.00
* GBP 100 4 GBP 0.01
* GBP 1 4 GBP 0.0001 ( 0.01p )
*
* GBX (pence) 100 0 GBP 1.00 (100p )
* GBX (pence) 100 2 GBP 0.01 ( 1p )
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/
decimalPlaces: number;
/**
* positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
*/
positionDecimalPlaces: number;
/**
* An instance of or reference to a tradable instrument.
*/
tradableInstrument: FillFields_market_tradableInstrument;
}
export interface FillFields {
__typename: "Trade";
/**
* The hash of the trade data
*/
id: string;
/**
* RFC3339Nano time for when the trade occurred
*/
createdAt: string;
/**
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
*/
price: string;
/**
* The number of contracts trades, will always be <= the remaining size of both orders immediately before the trade (uint64)
*/
size: string;
/**
* The order that bought
*/
buyOrder: string;
/**
* The order that sold
*/
sellOrder: string;
/**
* The aggressor indicates whether this trade was related to a BUY or SELL
*/
aggressor: Side;
/**
* The party that bought
*/
buyer: FillFields_buyer;
/**
* The party that sold
*/
seller: FillFields_seller;
/**
* The fee paid by the buyer side of the trade
*/
buyerFee: FillFields_buyerFee;
/**
* The fee paid by the seller side of the trade
*/
sellerFee: FillFields_sellerFee;
/**
* The market the trade occurred on
*/
market: FillFields_market;
}

247
libs/fills/src/lib/__generated__/Fills.ts generated Normal file
View File

@ -0,0 +1,247 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { Pagination, Side } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: Fills
// ====================================================
export interface Fills_party_tradesPaged_edges_node_buyer {
__typename: "Party";
/**
* Party identifier
*/
id: string;
}
export interface Fills_party_tradesPaged_edges_node_seller {
__typename: "Party";
/**
* Party identifier
*/
id: string;
}
export interface Fills_party_tradesPaged_edges_node_buyerFee {
__typename: "TradeFee";
/**
* The maker fee, aggressive party to the other party (the one who had an order in the book)
*/
makerFee: string;
/**
* The infrastructure fee, a fee paid to the node runner to maintain the vega network
*/
infrastructureFee: string;
/**
* The fee paid to the market makers to provide liquidity in the market
*/
liquidityFee: string;
}
export interface Fills_party_tradesPaged_edges_node_sellerFee {
__typename: "TradeFee";
/**
* The maker fee, aggressive party to the other party (the one who had an order in the book)
*/
makerFee: string;
/**
* The infrastructure fee, a fee paid to the node runner to maintain the vega network
*/
infrastructureFee: string;
/**
* The fee paid to the market makers to provide liquidity in the market
*/
liquidityFee: string;
}
export interface Fills_party_tradesPaged_edges_node_market_tradableInstrument_instrument_product_settlementAsset {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The precision of the asset
*/
decimals: number;
}
export interface Fills_party_tradesPaged_edges_node_market_tradableInstrument_instrument_product {
__typename: "Future";
/**
* The name of the asset (string)
*/
settlementAsset: Fills_party_tradesPaged_edges_node_market_tradableInstrument_instrument_product_settlementAsset;
}
export interface Fills_party_tradesPaged_edges_node_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* Uniquely identify an instrument across all instruments available on Vega (string)
*/
id: string;
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
*/
code: string;
/**
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
*/
product: Fills_party_tradesPaged_edges_node_market_tradableInstrument_instrument_product;
}
export interface Fills_party_tradesPaged_edges_node_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: Fills_party_tradesPaged_edges_node_market_tradableInstrument_instrument;
}
export interface Fills_party_tradesPaged_edges_node_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
* GBP 100 0 GBP 100
* GBP 100 2 GBP 1.00
* GBP 100 4 GBP 0.01
* GBP 1 4 GBP 0.0001 ( 0.01p )
*
* GBX (pence) 100 0 GBP 1.00 (100p )
* GBX (pence) 100 2 GBP 0.01 ( 1p )
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/
decimalPlaces: number;
/**
* positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
*/
positionDecimalPlaces: number;
/**
* An instance of or reference to a tradable instrument.
*/
tradableInstrument: Fills_party_tradesPaged_edges_node_market_tradableInstrument;
}
export interface Fills_party_tradesPaged_edges_node {
__typename: "Trade";
/**
* The hash of the trade data
*/
id: string;
/**
* RFC3339Nano time for when the trade occurred
*/
createdAt: string;
/**
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
*/
price: string;
/**
* The number of contracts trades, will always be <= the remaining size of both orders immediately before the trade (uint64)
*/
size: string;
/**
* The order that bought
*/
buyOrder: string;
/**
* The order that sold
*/
sellOrder: string;
/**
* The aggressor indicates whether this trade was related to a BUY or SELL
*/
aggressor: Side;
/**
* The party that bought
*/
buyer: Fills_party_tradesPaged_edges_node_buyer;
/**
* The party that sold
*/
seller: Fills_party_tradesPaged_edges_node_seller;
/**
* The fee paid by the buyer side of the trade
*/
buyerFee: Fills_party_tradesPaged_edges_node_buyerFee;
/**
* The fee paid by the seller side of the trade
*/
sellerFee: Fills_party_tradesPaged_edges_node_sellerFee;
/**
* The market the trade occurred on
*/
market: Fills_party_tradesPaged_edges_node_market;
}
export interface Fills_party_tradesPaged_edges {
__typename: "TradeEdge";
node: Fills_party_tradesPaged_edges_node;
cursor: string;
}
export interface Fills_party_tradesPaged_pageInfo {
__typename: "PageInfo";
startCursor: string;
endCursor: string;
}
export interface Fills_party_tradesPaged {
__typename: "TradeConnection";
/**
* The total number of trades in this connection
*/
totalCount: number;
/**
* The trade in this connection
*/
edges: Fills_party_tradesPaged_edges[];
/**
* The pagination information
*/
pageInfo: Fills_party_tradesPaged_pageInfo;
}
export interface Fills_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
tradesPaged: Fills_party_tradesPaged;
}
export interface Fills {
/**
* An entity that is trading on the VEGA network
*/
party: Fills_party | null;
}
export interface FillsVariables {
partyId: string;
marketId?: string | null;
pagination?: Pagination | null;
}

View File

@ -0,0 +1,208 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { Side } from "@vegaprotocol/types";
// ====================================================
// GraphQL subscription operation: FillsSub
// ====================================================
export interface FillsSub_trades_buyer {
__typename: "Party";
/**
* Party identifier
*/
id: string;
}
export interface FillsSub_trades_seller {
__typename: "Party";
/**
* Party identifier
*/
id: string;
}
export interface FillsSub_trades_buyerFee {
__typename: "TradeFee";
/**
* The maker fee, aggressive party to the other party (the one who had an order in the book)
*/
makerFee: string;
/**
* The infrastructure fee, a fee paid to the node runner to maintain the vega network
*/
infrastructureFee: string;
/**
* The fee paid to the market makers to provide liquidity in the market
*/
liquidityFee: string;
}
export interface FillsSub_trades_sellerFee {
__typename: "TradeFee";
/**
* The maker fee, aggressive party to the other party (the one who had an order in the book)
*/
makerFee: string;
/**
* The infrastructure fee, a fee paid to the node runner to maintain the vega network
*/
infrastructureFee: string;
/**
* The fee paid to the market makers to provide liquidity in the market
*/
liquidityFee: string;
}
export interface FillsSub_trades_market_tradableInstrument_instrument_product_settlementAsset {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The precision of the asset
*/
decimals: number;
}
export interface FillsSub_trades_market_tradableInstrument_instrument_product {
__typename: "Future";
/**
* The name of the asset (string)
*/
settlementAsset: FillsSub_trades_market_tradableInstrument_instrument_product_settlementAsset;
}
export interface FillsSub_trades_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* Uniquely identify an instrument across all instruments available on Vega (string)
*/
id: string;
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
*/
code: string;
/**
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
*/
product: FillsSub_trades_market_tradableInstrument_instrument_product;
}
export interface FillsSub_trades_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: FillsSub_trades_market_tradableInstrument_instrument;
}
export interface FillsSub_trades_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
* GBP 100 0 GBP 100
* GBP 100 2 GBP 1.00
* GBP 100 4 GBP 0.01
* GBP 1 4 GBP 0.0001 ( 0.01p )
*
* GBX (pence) 100 0 GBP 1.00 (100p )
* GBX (pence) 100 2 GBP 0.01 ( 1p )
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/
decimalPlaces: number;
/**
* positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
*/
positionDecimalPlaces: number;
/**
* An instance of or reference to a tradable instrument.
*/
tradableInstrument: FillsSub_trades_market_tradableInstrument;
}
export interface FillsSub_trades {
__typename: "Trade";
/**
* The hash of the trade data
*/
id: string;
/**
* RFC3339Nano time for when the trade occurred
*/
createdAt: string;
/**
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
*/
price: string;
/**
* The number of contracts trades, will always be <= the remaining size of both orders immediately before the trade (uint64)
*/
size: string;
/**
* The order that bought
*/
buyOrder: string;
/**
* The order that sold
*/
sellOrder: string;
/**
* The aggressor indicates whether this trade was related to a BUY or SELL
*/
aggressor: Side;
/**
* The party that bought
*/
buyer: FillsSub_trades_buyer;
/**
* The party that sold
*/
seller: FillsSub_trades_seller;
/**
* The fee paid by the buyer side of the trade
*/
buyerFee: FillsSub_trades_buyerFee;
/**
* The fee paid by the seller side of the trade
*/
sellerFee: FillsSub_trades_sellerFee;
/**
* The market the trade occurred on
*/
market: FillsSub_trades_market;
}
export interface FillsSub {
/**
* Subscribe to the trades updates
*/
trades: FillsSub_trades[] | null;
}
export interface FillsSubVariables {
partyId: string;
}

View File

@ -0,0 +1,18 @@
import { t } from '@vegaprotocol/react-helpers';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { FillsManager } from './fills-manager';
export const FillsContainer = () => {
const { keypair } = useVegaWallet();
if (!keypair) {
return (
<Splash>
<p>{t('Please connect Vega wallet')}</p>
</Splash>
);
}
return <FillsManager partyId={keypair.pub} />;
};

View File

@ -0,0 +1,117 @@
import { gql } from '@apollo/client';
import { makeDataProvider } from '@vegaprotocol/react-helpers';
import produce from 'immer';
import type { FillFields } from './__generated__/FillFields';
import type {
Fills,
Fills_party_tradesPaged_edges_node,
} from './__generated__/Fills';
import type { FillsSub } from './__generated__/FillsSub';
const FILL_FRAGMENT = gql`
fragment FillFields on Trade {
id
createdAt
price
size
buyOrder
sellOrder
aggressor
buyer {
id
}
seller {
id
}
buyerFee {
makerFee
infrastructureFee
liquidityFee
}
sellerFee {
makerFee
infrastructureFee
liquidityFee
}
market {
id
name
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
id
code
product {
... on Future {
settlementAsset {
id
symbol
decimals
}
}
}
}
}
}
}
`;
export const FILLS_QUERY = gql`
${FILL_FRAGMENT}
query Fills($partyId: ID!, $marketId: ID, $pagination: Pagination) {
party(id: $partyId) {
id
tradesPaged(marketId: $marketId, pagination: $pagination) {
totalCount
edges {
node {
...FillFields
}
cursor
}
pageInfo {
startCursor
endCursor
}
}
}
}
`;
export const FILLS_SUB = gql`
${FILL_FRAGMENT}
subscription FillsSub($partyId: ID!) {
trades(partyId: $partyId) {
...FillFields
}
}
`;
const update = (data: FillFields[], delta: FillFields[]) => {
// Add or update incoming trades
return produce(data, (draft) => {
delta.forEach((trade) => {
const index = draft.findIndex((t) => t.id === trade.id);
if (index === -1) {
draft.unshift(trade);
} else {
draft[index] = trade;
}
});
});
};
const getData = (
responseData: Fills
): Fills_party_tradesPaged_edges_node[] | null =>
responseData.party?.tradesPaged.edges.map((e) => e.node) || null;
const getDelta = (subscriptionData: FillsSub) => subscriptionData.trades || [];
export const fillsDataProvider = makeDataProvider(
FILLS_QUERY,
FILLS_SUB,
update,
getData,
getDelta
);

View File

@ -0,0 +1,80 @@
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useMemo, useRef } from 'react';
import { FillsTable } from './fills-table';
import { fillsDataProvider } from './fills-data-provider';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { FillsVariables } from './__generated__/Fills';
import type { FillFields } from './__generated__/FillFields';
import type { FillsSub_trades } from './__generated__/FillsSub';
import isEqual from 'lodash/isEqual';
interface FillsManagerProps {
partyId: string;
}
export const FillsManager = ({ partyId }: FillsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo<FillsVariables>(
() => ({
partyId,
pagination: {
last: 300,
},
}),
[partyId]
);
const update = useCallback((delta: FillsSub_trades[]) => {
if (!gridRef.current) {
return false;
}
const updateRows: FillFields[] = [];
const add: FillFields[] = [];
delta.forEach((d) => {
if (!gridRef.current?.api) {
return;
}
const rowNode = gridRef.current.api.getRowNode(d.id);
if (rowNode) {
if (!isEqual(d, rowNode.data)) {
updateRows.push(d);
}
} else {
add.push(d);
}
});
if (updateRows.length || add.length) {
gridRef.current.api.applyTransactionAsync({
update: updateRows,
add,
addIndex: 0,
});
}
return true;
}, []);
const { data, loading, error } = useDataProvider(
fillsDataProvider,
update,
variables
);
const fills = useMemo(() => {
if (!data?.length) {
return [];
}
return data;
}, [data]);
return (
<AsyncRenderer data={fills} loading={loading} error={error}>
<FillsTable ref={gridRef} partyId={partyId} fills={fills} />
</AsyncRenderer>
);
};

View File

@ -0,0 +1,177 @@
import { render, act, screen, waitFor } from '@testing-library/react';
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
import { Side } from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest';
import { FillsTable } from './fills-table';
import { generateFill } from './test-helpers';
import type { FillFields } from './__generated__/FillFields';
describe('FillsTable', () => {
let defaultFill: PartialDeep<FillFields>;
beforeEach(() => {
defaultFill = {
price: '100',
size: '300000',
market: {
name: 'test market',
decimalPlaces: 2,
positionDecimalPlaces: 5,
tradableInstrument: {
instrument: {
product: {
settlementAsset: {
decimals: 2,
symbol: 'BTC',
},
},
},
},
},
createdAt: new Date('2022-02-02T14:00:00').toISOString(),
};
});
it('correct columns are rendered', async () => {
await act(async () => {
render(<FillsTable partyId="party-id" fills={[generateFill()]} />);
});
const headers = screen.getAllByRole('columnheader');
expect(headers).toHaveLength(7);
expect(headers.map((h) => h.textContent?.trim())).toEqual([
'Market',
'Amount',
'Value',
'Filled value',
'Role',
'Fee',
'Date',
]);
});
it('formats cells correctly for buyer fill', async () => {
const partyId = 'party-id';
const buyerFill = generateFill({
...defaultFill,
buyer: {
id: partyId,
},
aggressor: Side.Sell,
buyerFee: {
makerFee: '2',
infrastructureFee: '2',
liquidityFee: '2',
},
});
const { container } = render(
<FillsTable partyId={partyId} fills={[buyerFill]} />
);
// Check grid has been rendered
await waitFor(() => {
expect(container.querySelector('.ag-root-wrapper')).toBeInTheDocument();
});
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
buyerFill.market.name,
'+3.00000',
'1.00 BTC',
'3.00 BTC',
'Maker',
'0.06 BTC',
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
});
const amountCell = cells.find((c) => c.getAttribute('col-id') === 'size');
expect(amountCell).toHaveClass('text-vega-green');
});
it('formats cells correctly for seller fill', async () => {
const partyId = 'party-id';
const buyerFill = generateFill({
...defaultFill,
seller: {
id: partyId,
},
aggressor: Side.Sell,
sellerFee: {
makerFee: '1',
infrastructureFee: '1',
liquidityFee: '1',
},
});
const { container } = render(
<FillsTable partyId={partyId} fills={[buyerFill]} />
);
// Check grid has been rendered
await waitFor(() => {
expect(container.querySelector('.ag-root-wrapper')).toBeInTheDocument();
});
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
buyerFill.market.name,
'-3.00000',
'1.00 BTC',
'3.00 BTC',
'Taker',
'0.03 BTC',
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
});
const amountCell = cells.find((c) => c.getAttribute('col-id') === 'size');
expect(amountCell).toHaveClass('text-vega-red');
});
it('renders correct maker or taker role', async () => {
const partyId = 'party-id';
const takerFill = generateFill({
seller: {
id: partyId,
},
aggressor: Side.Sell,
});
const { container, rerender } = render(
<FillsTable partyId={partyId} fills={[takerFill]} />
);
// Check grid has been rendered
await waitFor(() => {
expect(container.querySelector('.ag-root-wrapper')).toBeInTheDocument();
});
expect(
screen
.getAllByRole('gridcell')
.find((c) => c.getAttribute('col-id') === 'aggressor')
).toHaveTextContent('Taker');
const makerFill = generateFill({
seller: {
id: partyId,
},
aggressor: Side.Buy,
});
rerender(<FillsTable partyId={partyId} fills={[makerFill]} />);
expect(
screen
.getAllByRole('gridcell')
.find((c) => c.getAttribute('col-id') === 'aggressor')
).toHaveTextContent('Maker');
});
});

View File

@ -0,0 +1,18 @@
import type { Story, Meta } from '@storybook/react';
import type { FillsTableProps } from './fills-table';
import { FillsTable } from './fills-table';
import { generateFills } from './test-helpers';
export default {
component: FillsTable,
title: 'FillsTable',
} as Meta;
const Template: Story<FillsTableProps> = (args) => <FillsTable {...args} />;
export const Default = Template.bind({});
const fills = generateFills();
Default.args = {
partyId: 'party-id',
fills: fills.party?.tradesPaged.edges.map((e) => e.node),
};

View File

@ -0,0 +1,160 @@
import type { AgGridReact } from 'ag-grid-react';
import {
addDecimal,
addDecimalsFormatNumber,
formatNumber,
getDateTimeFormat,
t,
} from '@vegaprotocol/react-helpers';
import { AgGridColumn } from 'ag-grid-react';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import { forwardRef } from 'react';
import type { FillFields } from './__generated__/FillFields';
import type { ValueFormatterParams } from 'ag-grid-community';
import BigNumber from 'bignumber.js';
import { Side } from '@vegaprotocol/types';
export interface FillsTableProps {
partyId: string;
fills: FillFields[];
}
export const FillsTable = forwardRef<AgGridReact, FillsTableProps>(
({ partyId, fills }, ref) => {
return (
<AgGrid
ref={ref}
rowData={fills}
overlayNoRowsTemplate={t('No fills')}
defaultColDef={{ flex: 1, resizable: true }}
style={{ width: '100%', height: '100%' }}
getRowId={({ data }) => data.id}
>
<AgGridColumn headerName={t('Market')} field="market.name" />
<AgGridColumn
headerName={t('Amount')}
field="size"
cellClass={({ data }: { data: FillFields }) => {
let className = '';
if (data.buyer.id === partyId) {
className = 'text-vega-green';
} else if (data.seller.id) {
className = 'text-vega-red';
}
return className;
}}
valueFormatter={formatSize(partyId)}
/>
<AgGridColumn
headerName={t('Value')}
field="price"
valueFormatter={formatPrice}
/>
<AgGridColumn
headerName={t('Filled value')}
field="price"
valueFormatter={formatTotal}
/>
<AgGridColumn
headerName={t('Role')}
field="aggressor"
valueFormatter={formatRole(partyId)}
/>
<AgGridColumn
headerName={t('Fee')}
field="market.tradableInstrument.instrument.product"
valueFormatter={formatFee(partyId)}
/>
<AgGridColumn
headerName={t('Date')}
field="createdAt"
valueFormatter={({ value }: ValueFormatterParams) => {
return getDateTimeFormat().format(new Date(value));
}}
/>
</AgGrid>
);
}
);
const formatPrice = ({ value, data }: ValueFormatterParams) => {
const asset =
data.market.tradableInstrument.instrument.product.settlementAsset.symbol;
const valueFormatted = addDecimalsFormatNumber(
value,
data.market.decimalPlaces
);
return `${valueFormatted} ${asset}`;
};
const formatSize = (partyId: string) => {
return ({ value, data }: ValueFormatterParams) => {
let prefix;
if (data.buyer.id === partyId) {
prefix = '+';
} else if (data.seller.id) {
prefix = '-';
}
const size = addDecimalsFormatNumber(
value,
data.market.positionDecimalPlaces
);
return `${prefix}${size}`;
};
};
const formatTotal = ({ value, data }: ValueFormatterParams) => {
const asset =
data.market.tradableInstrument.instrument.product.settlementAsset.symbol;
const size = new BigNumber(
addDecimal(data.size, data.market.positionDecimalPlaces)
);
const price = new BigNumber(addDecimal(value, data.market.decimalPlaces));
const total = size.times(price).toString();
const valueFormatted = formatNumber(total, data.market.decimalPlaces);
return `${valueFormatted} ${asset}`;
};
const formatRole = (partyId: string) => {
return ({ value, data }: ValueFormatterParams) => {
const taker = t('Taker');
const maker = t('Maker');
if (data.buyer.id === partyId) {
if (value === Side.Buy) {
return taker;
} else {
return maker;
}
} else if (data.seller.id === partyId) {
if (value === Side.Sell) {
return taker;
} else {
return maker;
}
} else {
return '-';
}
};
};
const formatFee = (partyId: string) => {
return ({ value, data }: ValueFormatterParams) => {
const asset = value.settlementAsset;
let feesObj;
if (data.buyer.id === partyId) {
feesObj = data.buyerFee;
} else if (data.seller.id === partyId) {
feesObj = data.sellerFee;
} else {
return '-';
}
const fee = new BigNumber(feesObj.makerFee)
.plus(feesObj.infrastructureFee)
.plus(feesObj.liquidityFee);
const totalFees = addDecimalsFormatNumber(fee.toString(), asset.decimals);
return `${totalFees} ${asset.symbol}`;
};
};

View File

@ -0,0 +1,134 @@
import { Side } from '@vegaprotocol/types';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type {
Fills,
Fills_party_tradesPaged_edges_node,
} from './__generated__/Fills';
export const generateFills = (override?: PartialDeep<Fills>): Fills => {
const fills: Fills_party_tradesPaged_edges_node[] = [
generateFill({
buyer: {
id: 'party-id',
},
}),
generateFill({
id: '1',
seller: {
id: 'party-id',
},
aggressor: Side.Sell,
buyerFee: {
infrastructureFee: '5000',
},
market: {
name: 'Apples Daily v3',
positionDecimalPlaces: 2,
},
}),
generateFill({
id: '2',
seller: {
id: 'party-id',
},
aggressor: Side.Buy,
}),
generateFill({
id: '3',
aggressor: Side.Sell,
market: {
name: 'ETHBTC Quarterly (30 Jun 2022)',
},
buyer: {
id: 'party-id',
},
}),
];
const defaultResult: Fills = {
party: {
id: 'buyer-id',
tradesPaged: {
__typename: 'TradeConnection',
totalCount: 1,
edges: fills.map((f) => {
return {
__typename: 'TradeEdge',
node: f,
cursor: '3',
};
}),
pageInfo: {
__typename: 'PageInfo',
startCursor: '1',
endCursor: '2',
},
},
__typename: 'Party',
},
};
return merge(defaultResult, override);
};
export const generateFill = (
override?: PartialDeep<Fills_party_tradesPaged_edges_node>
) => {
const defaultFill: Fills_party_tradesPaged_edges_node = {
__typename: 'Trade',
id: '0',
createdAt: new Date().toISOString(),
price: '10000000',
size: '50000',
buyOrder: 'buy-order',
sellOrder: 'sell-order',
aggressor: Side.Buy,
buyer: {
__typename: 'Party',
id: 'buyer-id',
},
seller: {
__typename: 'Party',
id: 'seller-id',
},
buyerFee: {
__typename: 'TradeFee',
makerFee: '100',
infrastructureFee: '100',
liquidityFee: '100',
},
sellerFee: {
__typename: 'TradeFee',
makerFee: '200',
infrastructureFee: '200',
liquidityFee: '200',
},
market: {
__typename: 'Market',
id: 'market-id',
name: 'UNIDAI Monthly (30 Jun 2022)',
positionDecimalPlaces: 0,
decimalPlaces: 5,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
id: 'instrument-id',
code: 'instrument-code',
product: {
__typename: 'Future',
settlementAsset: {
__typename: 'Asset',
id: 'asset-id',
symbol: 'SYM',
decimals: 18,
},
},
},
},
},
};
return merge(defaultFill, override);
};

View File

@ -0,0 +1 @@
import '@testing-library/jest-dom';

View File

@ -0,0 +1,17 @@
const { join } = require('path');
const { createGlobPatternsForDependencies } = require('@nrwl/next/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],
};

28
libs/fills/tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"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
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./.storybook/tsconfig.json"
}
]
}

View File

@ -0,0 +1,26 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/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"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

View File

@ -0,0 +1,19 @@
{
"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"
]
}

View File

@ -288,6 +288,16 @@ export enum WithdrawalStatus {
Rejected = "Rejected", Rejected = "Rejected",
} }
/**
* Pagination constructs to support cursor based pagination in the API
*/
export interface Pagination {
first?: number | null;
after?: string | null;
last?: number | null;
before?: string | null;
}
//============================================================== //==============================================================
// END Enums and Input Objects // END Enums and Input Objects
//============================================================== //==============================================================

View File

@ -22,6 +22,7 @@
"@vegaprotocol/deal-ticket": ["libs/deal-ticket/src/index.ts"], "@vegaprotocol/deal-ticket": ["libs/deal-ticket/src/index.ts"],
"@vegaprotocol/deposits": ["libs/deposits/src/index.ts"], "@vegaprotocol/deposits": ["libs/deposits/src/index.ts"],
"@vegaprotocol/environment": ["libs/environment/src/index.ts"], "@vegaprotocol/environment": ["libs/environment/src/index.ts"],
"@vegaprotocol/fills": ["libs/fills/src/index.ts"],
"@vegaprotocol/market-depth": ["libs/market-depth/src/index.ts"], "@vegaprotocol/market-depth": ["libs/market-depth/src/index.ts"],
"@vegaprotocol/market-list": ["libs/market-list/src/index.ts"], "@vegaprotocol/market-list": ["libs/market-list/src/index.ts"],
"@vegaprotocol/network-stats": ["libs/network-stats/src/index.ts"], "@vegaprotocol/network-stats": ["libs/network-stats/src/index.ts"],

View File

@ -9,6 +9,7 @@
"environment": "libs/environment", "environment": "libs/environment",
"explorer": "apps/explorer", "explorer": "apps/explorer",
"explorer-e2e": "apps/explorer-e2e", "explorer-e2e": "apps/explorer-e2e",
"fills": "libs/fills",
"market-depth": "libs/market-depth", "market-depth": "libs/market-depth",
"market-list": "libs/market-list", "market-list": "libs/market-list",
"network-stats": "libs/network-stats", "network-stats": "libs/network-stats",