Compare commits

...
Author SHA1 Message Date
Dariusz Majcherczyk 17bc4541e9 chore: multi click for toast 2023-01-29 19:51:12 +01:00
Dariusz Majcherczyk 2bd8044a6c chore: reverted proper assertion 2023-01-29 16:03:16 +01:00
Dariusz Majcherczyk b3a82fd6f0 chore: capsule tests refactor and increase deposit AC test coverage 2023-01-29 14:09:43 +01:00
mattrussell36 f22fb56afd chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 12:06:12 +00:00
mattrussell36 f2627e6ebf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 06:07:03 +00:00
mattrussell36 24e6a13d93 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 00:13:02 +00:00
mattrussell36 b56050ad1c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 18:07:37 +00:00
mattrussell36 cff83ed062 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 12:06:05 +00:00
mattrussell36 8ecce874b0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 06:08:30 +00:00
Bartłomiej Głownia 00e319b3c6 chore: handle not found errors as correct response (#2759) 2023-01-27 18:35:45 -08:00
mattrussell36 05559f3ea0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 00:11:14 +00:00
m.ray 4b3b5c322a fix: trades grid colors (#2766) 2023-01-27 18:55:20 +00:00
Bartłomiej Głownia b1280c8285 fix: hide close button if openVolume is zero (#2768) 2023-01-27 18:44:20 +00:00
Edd 1e0e1c7859 feat(explorer): add long text component for hashes and tx viewer (#2765) 2023-01-27 18:37:59 +00:00
mattrussell36 ec2bb81ec8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-27 18:08:36 +00:00
m.ray fe95c6fcbc fix: show markets link & deal ticket validation (#2763) 2023-01-27 18:10:20 +01:00
macqbat 853ec8f69c chore(2351): improve handling wallet errors (#2729) 2023-01-27 15:45:29 +01:00
92 changed files with 1131 additions and 920 deletions
@@ -4,6 +4,7 @@ import { useExplorerAssetQuery } from './__generated__/Asset';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
@@ -27,7 +28,7 @@ const AssetLink = ({ id, ...props }: AssetLinkProps) => {
return (
<Link className="underline" {...props} to={`/${Routes.ASSETS}#${id}`}>
{label}
<Hash text={label} />
</Link>
);
};
@@ -3,6 +3,7 @@ import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
height: string;
@@ -11,7 +12,7 @@ export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
const BlockLink = ({ height, ...props }: BlockLinkProps) => {
return (
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
{height}
<Hash text={height} />
</Link>
);
};
@@ -1,6 +1,7 @@
import React from 'react';
import { DATA_SOURCES } from '../../../config';
import Hash from '../hash';
export enum EthExplorerLinkTypes {
block = 'block',
@@ -27,7 +28,7 @@ export const EthExplorerLink = ({
{...props}
href={link}
>
{id}
<Hash text={id} />
</a>
);
};
@@ -0,0 +1,18 @@
export type HashProps = {
text: string;
};
/**
* A simple component that ensures long text things like hashes
* are broken when they need to wrap. This will remove the need
* for a lot of the overflow scrolling that currently exists.
*/
const Hash = ({ text }: HashProps) => {
return (
<code className="break-all font-mono" style={{ wordWrap: 'break-word' }}>
{text}
</code>
);
};
export default Hash;
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import Hash from '../hash';
export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
@@ -35,7 +36,8 @@ const MarketLink = ({
<span role="img" aria-label="Unknown market" className="img">
&nbsp;{t('Invalid market')}
</span>
&nbsp;{id}
&nbsp;
<Hash text={id} />
</div>
);
}
@@ -55,7 +57,7 @@ const MarketLink = ({
} else {
return (
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
{id}
<Hash text={id} />
</Link>
);
}
@@ -4,6 +4,7 @@ import { useExplorerNodeQuery } from './__generated__/Node';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type NodeLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
@@ -22,7 +23,7 @@ const NodeLink = ({ id, ...props }: NodeLinkProps) => {
return (
<Link className="underline" {...props} to={`/${Routes.VALIDATORS}#${id}`}>
<code>{label}</code>
<Hash text={label} />
</Link>
);
};
@@ -2,6 +2,7 @@ import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type OracleLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
@@ -14,7 +15,7 @@ const OracleLink = ({ id, ...props }: OracleLinkProps) => {
{...props}
to={`/${Routes.ORACLES}/${id}`}
>
{id}
<Hash text={id} />
</Link>
);
};
@@ -2,6 +2,7 @@ import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
@@ -14,7 +15,7 @@ const PartyLink = ({ id, ...props }: PartyLinkProps) => {
{...props}
to={`/${Routes.PARTIES}/${id}`}
>
{id}
<Hash text={id} />
</Link>
);
};
@@ -22,7 +22,7 @@ describe('Proposal link component', () => {
expect(res.getByText('123')).toBeInTheDocument();
});
it('Renders the ID with an emoji on error', async () => {
it('Renders the ID on error', async () => {
const mock = {
request: {
query: ExplorerProposalDocument,
@@ -37,9 +37,6 @@ describe('Proposal link component', () => {
const res = render(renderComponent('456', [mock]));
// The ID
expect(res.getByText('456')).toBeInTheDocument();
// The emoji
expect(await res.findByRole('img')).toBeInTheDocument();
});
it('Renders the proposal title when the query returns a result', async () => {
@@ -1,6 +1,7 @@
import { useExplorerProposalQuery } from './__generated__/Proposal';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { ENV } from '../../../config/env';
import Hash from '../hash';
export type ProposalLinkProps = {
id: string;
};
@@ -17,7 +18,11 @@ const ProposalLink = ({ id }: ProposalLinkProps) => {
const base = ENV.dataSources.governanceUrl;
const label = data?.proposal?.rationale.title || id;
return <ExternalLink href={`${base}/proposals/${id}`}>{label}</ExternalLink>;
return (
<ExternalLink href={`${base}/proposals/${id}`}>
<Hash text={label} />
</ExternalLink>
);
};
export default ProposalLink;
@@ -63,15 +63,21 @@ describe('Chain Event: Builtin asset deposit', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.partyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.partyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
});
@@ -69,15 +69,21 @@ describe('Chain Event: Builtin asset withdrawal', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.partyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.partyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
});
@@ -60,8 +60,11 @@ describe('Chain Event: ERC20 Asset Delist', () => {
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
});
@@ -76,14 +76,20 @@ describe('Chain Event: ERC20 Asset limits updated', () => {
expect(screen.getByText(t('Vega asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('ERC20 asset'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.sourceEthereumAddress}`
);
});
@@ -62,14 +62,20 @@ describe('Chain Event: ERC20 Asset List', () => {
expect(screen.getByText(t('Added Vega asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.assetSource}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.assetSource}`
);
});
@@ -62,21 +62,30 @@ describe('Chain Event: ERC20 asset deposit', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.targetPartyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.targetPartyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.sourceEthereumAddress}`
);
});
@@ -57,14 +57,20 @@ describe('Chain Event: ERC20 asset deposit', () => {
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.targetEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.targetEthereumAddress}`
);
});
@@ -64,14 +64,20 @@ describe('Chain Event: Stake deposit', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.vegaPublicKey}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.ethereumAddress}`
);
});
@@ -64,14 +64,20 @@ describe('Chain Event: Stake remove', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.vegaPublicKey}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.ethereumAddress}`
);
});
@@ -66,7 +66,10 @@ describe('Chain Event: Stake total supply change', () => {
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.tokenAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.tokenAddress}`
);
});
@@ -7,6 +7,8 @@ import type { BlockExplorerTransactionResult } from '../../../../routes/types/bl
import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response';
import { Time } from '../../../time';
import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
import { TxDataView } from '../../tx-data-view';
import Hash from '../../../links/hash';
interface TxDetailsSharedProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -46,7 +48,7 @@ export const TxDetailsShared = ({
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
<TableCell>
<code>{txData.hash}</code>
<Hash text={txData.hash} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -82,6 +84,12 @@ export const TxDetailsShared = ({
<ChainResponseCode code={txData.code} error={txData.error} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Transaction')}</TableCell>
<TableCell>
<TxDataView blockData={blockData} txData={txData} />
</TableCell>
</TableRow>
</>
);
};
@@ -8,10 +8,8 @@ import { TxDetailsHeartbeat } from './tx-hearbeat';
import { TxDetailsGeneric } from './tx-generic';
import { TxDetailsBatch } from './tx-batch';
import { TxDetailsChainEvent } from './tx-chain-event';
import { TxContent } from '../../../routes/txs/id/tx-content';
import { TxDetailsNodeVote } from './tx-node-vote';
import { TxDetailsOrderCancel } from './tx-order-cancel';
import get from 'lodash/get';
import { TxDetailsOrderAmend } from './tx-order-amend';
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
import { TxDetailsDelegate } from './tx-delegation';
@@ -46,23 +44,9 @@ export const TxDetailsWrapper = ({
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const raw = get(blockData, `result.block.data.txs[${txData.index}]`);
return (
<div key={`txd-${txData.hash}`}>
<section>{child({ txData, pubKey, blockData })}</section>
<details title={t('Decoded transaction')} className="mt-3">
<summary className="cursor-pointer">{t('Decoded transaction')}</summary>
<TxContent data={txData} />
</details>
{raw ? (
<details title={t('Raw transaction')} className="mt-3">
<summary className="cursor-pointer">{t('Raw transaction')}</summary>
<code className="break-all font-mono text-xs">{raw}</code>
</details>
) : null}
</div>
);
};
@@ -6,6 +6,7 @@ import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import DeterministicOrderDetails from '../../order-details/deterministic-order-details';
import Hash from '../../links/hash';
interface TxDetailsOrderProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -47,7 +48,7 @@ export const TxDetailsOrder = ({
<TableRow modifier="bordered">
<TableCell>{t('Order')}</TableCell>
<TableCell>
<code>{deterministicId}</code>
<Hash text={deterministicId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -0,0 +1,75 @@
import { useState } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import get from 'lodash/get';
import { Select } from '@vegaprotocol/ui-toolkit';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../routes/blocks/tendermint-blocks-response';
export function getClassName(showTxData: ShowTxDataType) {
const baseClasses =
'font-mono bg-neutral-300 text-[11px] leading-3 text-gray-900 w-full p-2 max-w-[615px]';
if (showTxData === 'JSON') {
return `${baseClasses} whitespace-pre overflow-x-scroll`;
} else {
return baseClasses;
}
}
export function getContents(
showTxData: ShowTxDataType,
txData: BlockExplorerTransactionResult | null,
blockData: TendermintBlocksResponse | null | undefined
) {
if (showTxData === 'JSON') {
if (txData) {
return JSON.stringify(txData.command, undefined, 1);
}
} else {
if (txData && blockData) {
return get(blockData, `result.block.data.txs[${txData.index}]`);
}
}
return '-';
}
type ShowTxDataType = 'JSON' | 'base64';
interface TxDataViewProps {
txData: BlockExplorerTransactionResult | undefined;
blockData: TendermintBlocksResponse | undefined;
}
export const TxDataView = ({ txData, blockData }: TxDataViewProps) => {
const [showTxData, setShowTxData] = useState<ShowTxDataType>('JSON');
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<details title={t('Show raw transaction')}>
<summary className="cursor-pointer">{t('Show raw transaction')}</summary>
<div className="py-4">
<textarea
readOnly={true}
className={getClassName(showTxData)}
rows={12}
cols={120}
value={getContents(showTxData, txData, blockData)}
/>
<div className="w-40">
<Select
placeholder="View as..."
onChange={(v) => setShowTxData(v.target.value as ShowTxDataType)}
value={'JSON'}
>
<option value={'JSON'}>JSON</option>
<option value={'base64'}>Base64</option>
</Select>
</div>
</div>
</details>
);
};
@@ -115,7 +115,7 @@ export const TxsInfiniteList = ({
className="List"
height={995}
itemCount={itemCount}
itemSize={isStacked ? 134 : 72}
itemSize={isStacked ? 134 : 50}
onItemsRendered={onItemsRendered}
ref={ref}
width={'100%'}
+329 -83
View File
@@ -5,7 +5,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "86666.297",
"total_removed": "0",
"locked_amount": "73961.6625805406210086218",
"locked_amount": "73487.251446645509463922",
"deposits": [
{
"amount": "86666.297",
@@ -71,7 +71,7 @@
"tranche_end": "2023-06-01T00:00:00.000Z",
"total_added": "2500",
"total_removed": "0",
"locked_amount": "1710.0785065628815",
"locked_amount": "1682.6333244301995",
"deposits": [
{
"amount": "2500",
@@ -450,7 +450,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45",
"total_removed": "0",
"locked_amount": "73894.181869649098296045",
"locked_amount": "73420.20357622098315111",
"deposits": [
{
"amount": "129999.45",
@@ -516,7 +516,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "62600",
"total_removed": "0",
"locked_amount": "37473.16870243530666",
"locked_amount": "37130.49642947742434",
"deposits": [
{
"amount": "10000",
@@ -709,7 +709,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000",
"total_removed": "0",
"locked_amount": "3184.845414764079",
"locked_amount": "3157.4754249112125",
"deposits": [
{
"amount": "5000",
@@ -920,7 +920,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "97499.58",
"total_removed": "0",
"locked_amount": "15705.5104826322582056334",
"locked_amount": "15240.5819298544871083824",
"deposits": [
{
"amount": "97499.58",
@@ -953,7 +953,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "135173.4239508",
"total_removed": "98230.390980249184455396",
"locked_amount": "21466.721386488566701750705764",
"locked_amount": "20831.244321407448710024671284",
"deposits": [
{
"amount": "135173.4239508",
@@ -999,7 +999,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "32499.86",
"total_removed": "0",
"locked_amount": "6607.0370403844561078578",
"locked_amount": "6411.4496271174975967098",
"deposits": [
{
"amount": "32499.86",
@@ -1032,7 +1032,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "10833.29",
"total_removed": "0",
"locked_amount": "2150.5264434752857381272",
"locked_amount": "2086.8646383922243299952",
"deposits": [
{
"amount": "10833.29",
@@ -1065,7 +1065,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "22749.93",
"total_removed": "0",
"locked_amount": "8039.148157117145325033",
"locked_amount": "7801.1661111092940288519",
"deposits": [
{
"amount": "6500",
@@ -1203,8 +1203,8 @@
"tranche_start": "2022-11-01T00:00:00.000Z",
"tranche_end": "2023-05-01T00:00:00.000Z",
"total_added": "22500",
"total_removed": "3539.640404325",
"locked_amount": "11622.146926795578",
"total_removed": "3707.308452225",
"locked_amount": "11373.775610036832",
"deposits": [
{
"amount": "7500",
@@ -1218,6 +1218,11 @@
}
],
"withdrawals": [
{
"amount": "167.6680479",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x3bd3579f34ddc1eee597ba9b3fbbf18b6c268d085cd299e964b7239b2434fcf3"
},
{
"amount": "305.3119245",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -1281,6 +1286,12 @@
}
],
"withdrawals": [
{
"amount": "167.6680479",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 33,
"tx": "0x3bd3579f34ddc1eee597ba9b3fbbf18b6c268d085cd299e964b7239b2434fcf3"
},
{
"amount": "305.3119245",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -1343,8 +1354,8 @@
}
],
"total_tokens": "7500",
"withdrawn_tokens": "3539.640404325",
"remaining_tokens": "3960.359595675"
"withdrawn_tokens": "3707.308452225",
"remaining_tokens": "3792.691547775"
},
{
"address": "0x2539b51EbDE65a75672aBcfE9439a706a99D18D1",
@@ -1369,7 +1380,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38",
"total_removed": "928642.9598472029154",
"locked_amount": "666983.06788767317384942",
"locked_amount": "656363.903872495430316328",
"deposits": [
{
"amount": "1852091.69",
@@ -1721,7 +1732,7 @@
"tranche_end": "2023-02-01T00:00:00.000Z",
"total_added": "42500",
"total_removed": "24434.0787288",
"locked_amount": "1037.950539704105075",
"locked_amount": "576.45383579911392",
"deposits": [
{
"amount": "12500",
@@ -6711,10 +6722,65 @@
"tranche_id": 11,
"tranche_start": "2021-09-03T00:00:00.000Z",
"tranche_end": "2022-09-03T00:00:00.000Z",
"total_added": "53684.000000000000000003",
"total_removed": "42606.21518131551",
"total_added": "53995.000000000000000003",
"total_removed": "42939.21518131551",
"locked_amount": "0",
"deposits": [
{
"amount": "45",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0xa6c36806aba504d0a30c574f6ab4dc2d76aad26d8759c778feb6bf27729cddcf"
},
{
"amount": "46",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0x0f0f261dd8e05919d3c6c1d4358434e2c9c1b9087347702d2b8a4b1aeecd7e4b"
},
{
"amount": "50",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0xe66d0dc85202757228ed7c7ea7138710b00755f69d604e6a7199c2759370aef8"
},
{
"amount": "30",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0x470591c033f228afd59d498fcf42ecba8b83d389042d5c43e5af277e8deed9fe"
},
{
"amount": "30",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0x86543ba39726b072d1b338fc9bf4852b5707ccfdf9abaf6ce21b85d76591b3cd"
},
{
"amount": "15",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0x0f98bf0636a4f08d09d40b227beb78d7aaf57d6c856dd44ea0c6c29cc965c6c2"
},
{
"amount": "20",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0xca73109b8c348b6df6723c5a67616d2406784d56718731264e97a49cf4d314fb"
},
{
"amount": "20",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0x1a3c1319ece46cec553198ce7fb164670a5322a97afbcf432a44d524ace7267d"
},
{
"amount": "20",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0xb2617627421f96ca8e732982a756dd98ff8889871f64def89f2971ead24c8fe7"
},
{
"amount": "20",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0x8b2831993c44c6baa40677a9ac5055f244f15ed005cddf3419226c86280ff93a"
},
{
"amount": "15",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0x6957b2bd0f9f04f7cc124c11638be50ff5e2a26412e0be0c16f7aac1f1b73bc6"
},
{
"amount": "20",
"user": "0xB12Cdb63E38f9a6ff2C0F68aBC3CA9beFCb7148c",
@@ -16647,6 +16713,21 @@
}
],
"withdrawals": [
{
"amount": "311",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tx": "0xa94f27a51d17008d4f8c62a11f65db9dc3f94eb430b2842b0cb8403c29951658"
},
{
"amount": "10",
"user": "0xa2920abaA03b696C7B90486600D578b455E11609",
"tx": "0xc454f6f2572533714b74636b4243ba8fd5456a6f67ef8b93051588b156ab2093"
},
{
"amount": "12",
"user": "0xa10aD7E7712617fc4ABe0811D8a30fD96cE48F9f",
"tx": "0x7ffe3e795b50a8449052143a739d53aec81c2f53c7981f31496b8c121bd4ec81"
},
{
"amount": "200",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
@@ -17609,6 +17690,112 @@
}
],
"users": [
{
"address": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"deposits": [
{
"amount": "45",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0xa6c36806aba504d0a30c574f6ab4dc2d76aad26d8759c778feb6bf27729cddcf"
},
{
"amount": "46",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x0f0f261dd8e05919d3c6c1d4358434e2c9c1b9087347702d2b8a4b1aeecd7e4b"
},
{
"amount": "50",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0xe66d0dc85202757228ed7c7ea7138710b00755f69d604e6a7199c2759370aef8"
},
{
"amount": "30",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x470591c033f228afd59d498fcf42ecba8b83d389042d5c43e5af277e8deed9fe"
},
{
"amount": "30",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x86543ba39726b072d1b338fc9bf4852b5707ccfdf9abaf6ce21b85d76591b3cd"
},
{
"amount": "15",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x0f98bf0636a4f08d09d40b227beb78d7aaf57d6c856dd44ea0c6c29cc965c6c2"
},
{
"amount": "20",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0xca73109b8c348b6df6723c5a67616d2406784d56718731264e97a49cf4d314fb"
},
{
"amount": "20",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x1a3c1319ece46cec553198ce7fb164670a5322a97afbcf432a44d524ace7267d"
},
{
"amount": "20",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0xb2617627421f96ca8e732982a756dd98ff8889871f64def89f2971ead24c8fe7"
},
{
"amount": "20",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x8b2831993c44c6baa40677a9ac5055f244f15ed005cddf3419226c86280ff93a"
},
{
"amount": "15",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x6957b2bd0f9f04f7cc124c11638be50ff5e2a26412e0be0c16f7aac1f1b73bc6"
},
{
"amount": "100",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x4b249ec22c90d0f4d1d472c407e8c27b1a87db2c5d822d600636f9b17406588a"
},
{
"amount": "60",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x6a8d2bd962a14937de4beb99bff97cf55fd823de00b96c49e5e4e97b5b68f40f"
},
{
"amount": "40",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0xea7d189a9ad2d7ee3a93dd352201d19b1128ccf46246b1ec346c8ee1b9f7ea62"
}
],
"withdrawals": [
{
"amount": "311",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0xa94f27a51d17008d4f8c62a11f65db9dc3f94eb430b2842b0cb8403c29951658"
},
{
"amount": "200",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0xf7ad77b4bb44e24178d182db393ace12f22021706d6583d438b285621eaf8b01"
}
],
"total_tokens": "511",
"withdrawn_tokens": "511",
"remaining_tokens": "0"
},
{
"address": "0xB12Cdb63E38f9a6ff2C0F68aBC3CA9beFCb7148c",
"deposits": [
@@ -18953,40 +19140,6 @@
"withdrawn_tokens": "0",
"remaining_tokens": "905"
},
{
"address": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"deposits": [
{
"amount": "100",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x4b249ec22c90d0f4d1d472c407e8c27b1a87db2c5d822d600636f9b17406588a"
},
{
"amount": "60",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0x6a8d2bd962a14937de4beb99bff97cf55fd823de00b96c49e5e4e97b5b68f40f"
},
{
"amount": "40",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0xea7d189a9ad2d7ee3a93dd352201d19b1128ccf46246b1ec346c8ee1b9f7ea62"
}
],
"withdrawals": [
{
"amount": "200",
"user": "0xa7d3D2CC58e1E82e3ff2C2Cd3B750442522aBbfb",
"tranche_id": 11,
"tx": "0xf7ad77b4bb44e24178d182db393ace12f22021706d6583d438b285621eaf8b01"
}
],
"total_tokens": "200",
"withdrawn_tokens": "200",
"remaining_tokens": "0"
},
{
"address": "0x6ebf587df7C5C5eb49BB570c240563a98E1b8f4f",
"deposits": [
@@ -30881,10 +31034,17 @@
"tx": "0xee0be2716f9467edff80ca7225263c95e69db8d7f020655ab3621b0966d0a311"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "12",
"user": "0xa10aD7E7712617fc4ABe0811D8a30fD96cE48F9f",
"tranche_id": 11,
"tx": "0x7ffe3e795b50a8449052143a739d53aec81c2f53c7981f31496b8c121bd4ec81"
}
],
"total_tokens": "12",
"withdrawn_tokens": "0",
"remaining_tokens": "12"
"withdrawn_tokens": "12",
"remaining_tokens": "0"
},
{
"address": "0x804dEbc8807aEe993a727B37d81D7A379DA59fE6",
@@ -31310,10 +31470,17 @@
"tx": "0xaa605daf521cdcdbbc1d6942a69e1efc86eac89d5804e05f088bce2fabe3d52a"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "10",
"user": "0xa2920abaA03b696C7B90486600D578b455E11609",
"tranche_id": 11,
"tx": "0xc454f6f2572533714b74636b4243ba8fd5456a6f67ef8b93051588b156ab2093"
}
],
"total_tokens": "10",
"withdrawn_tokens": "0",
"remaining_tokens": "10"
"withdrawn_tokens": "10",
"remaining_tokens": "0"
},
{
"address": "0xA2B5F0114E7935EFcBd7c038d789Bfa71f2Bfe5D",
@@ -33128,7 +33295,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671",
"total_removed": "442882.3484327902809",
"locked_amount": "1049422.081232611329856667873",
"locked_amount": "1033104.123341510407167357286",
"deposits": [
{
"amount": "1998.95815",
@@ -34419,8 +34586,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001",
"total_removed": "547359.63874938618402952",
"locked_amount": "9020870.9063540351013617047345897584630981",
"total_removed": "549182.48543502092691952",
"locked_amount": "8963008.4753852283836950014010192461651798",
"deposits": [
{
"amount": "16249.93",
@@ -34924,6 +35091,16 @@
}
],
"withdrawals": [
{
"amount": "856.08784586478614",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tx": "0x7882fc86536accee89368b825b374eb365ee5f051cb89fb3710c7e2d24b0d29d"
},
{
"amount": "966.75883976995675",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x6c49f9f742a84f7889b90e6f978f3fb1f642ea447f737f348b3fac91716b9717"
},
{
"amount": "858.360074993579125",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -36507,6 +36684,12 @@
}
],
"withdrawals": [
{
"amount": "966.75883976995675",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0x6c49f9f742a84f7889b90e6f978f3fb1f642ea447f737f348b3fac91716b9717"
},
{
"amount": "858.360074993579125",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -37595,8 +37778,8 @@
}
],
"total_tokens": "259998.8875",
"withdrawn_tokens": "112175.254589489570875",
"remaining_tokens": "147823.632910510429125"
"withdrawn_tokens": "113142.013429259527625",
"remaining_tokens": "146856.874070740472375"
},
{
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
@@ -37817,6 +38000,12 @@
}
],
"withdrawals": [
{
"amount": "856.08784586478614",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tranche_id": 2,
"tx": "0x7882fc86536accee89368b825b374eb365ee5f051cb89fb3710c7e2d24b0d29d"
},
{
"amount": "1293.67099136315494",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
@@ -38017,8 +38206,8 @@
}
],
"total_tokens": "150551.801",
"withdrawn_tokens": "64216.65872637537368",
"remaining_tokens": "86335.14227362462632"
"withdrawn_tokens": "65072.74657224015982",
"remaining_tokens": "85479.05442775984018"
},
{
"address": "0x4d982Ab0823fD2f48e934a7be2bb0a5374a26148",
@@ -39743,8 +39932,8 @@
"tranche_start": "2021-11-05T00:00:00.000Z",
"tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999",
"total_removed": "3708107.46955489707527943",
"locked_amount": "2606565.19565117133351441770201319",
"total_removed": "3709441.39326687814680893",
"locked_amount": "2553146.968835877717601199418901287",
"deposits": [
{
"amount": "129284.449",
@@ -39953,6 +40142,11 @@
}
],
"withdrawals": [
{
"amount": "1333.9237119810715295",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0xc8aae35d9d474b83dd5524de9db59f994fd77040397c00a1a4cf30a5c9315826"
},
{
"amount": "8950.14985089483210984",
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
@@ -42754,6 +42948,12 @@
}
],
"withdrawals": [
{
"amount": "1333.9237119810715295",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0xc8aae35d9d474b83dd5524de9db59f994fd77040397c00a1a4cf30a5c9315826"
},
{
"amount": "1192.05386354121365675",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@@ -45144,8 +45344,8 @@
}
],
"total_tokens": "359123.469575",
"withdrawn_tokens": "294949.4170736759185085",
"remaining_tokens": "64174.0525013240814915"
"withdrawn_tokens": "296283.340785656990038",
"remaining_tokens": "62840.128789343009962"
},
{
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
@@ -46456,7 +46656,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303",
"total_removed": "2622261.560853924298939789",
"locked_amount": "712966.269636651830718004793872006",
"locked_amount": "691860.405151183386025619771012724",
"deposits": [
{
"amount": "552496.6455",
@@ -48350,8 +48550,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996",
"total_removed": "30192.1031304386685",
"locked_amount": "166286.92752366611650390355859972",
"total_removed": "31616.1712341930685",
"locked_amount": "163701.253818397273052118746829",
"deposits": [
{
"amount": "3000",
@@ -55050,6 +55250,21 @@
"user": "0x83e600Ae7f4cf265C112314839cDA2341198840B",
"tx": "0x871fbcbfe5c488654b8aff67f1c4afff2f5940ea3eb0cdf165b159bb96ee7ffd"
},
{
"amount": "157.9045256224",
"user": "0x74Da54F44975a1C224ABb5CAB8e5fc38a268B425",
"tx": "0x65f234b1c4da032348199c9b165cdaee8817e428d1bf357309260ac5d33279d5"
},
{
"amount": "1167.346518264",
"user": "0x17197a34E926539066de1987d462c996c797b772",
"tx": "0xba73b47fe051e5544519f878f34a69a9251494ea5d6a883f308df59c9f0e29c5"
},
{
"amount": "98.817059868",
"user": "0x175BEB5A0b07C9FBb640CF8b97352B3F1534E7b3",
"tx": "0x40192637adf93f4df1f1e9edb9d5e431e9a18e4642d0d843e1c6f64cba60ba44"
},
{
"amount": "229.648541348",
"user": "0x65C13724928ea0AfA68e09c7b70449bB8f6f3Fc8",
@@ -67904,6 +68119,12 @@
}
],
"withdrawals": [
{
"amount": "98.817059868",
"user": "0x175BEB5A0b07C9FBb640CF8b97352B3F1534E7b3",
"tranche_id": 5,
"tx": "0x40192637adf93f4df1f1e9edb9d5e431e9a18e4642d0d843e1c6f64cba60ba44"
},
{
"amount": "30.893512176",
"user": "0x175BEB5A0b07C9FBb640CF8b97352B3F1534E7b3",
@@ -67912,8 +68133,8 @@
}
],
"total_tokens": "200",
"withdrawn_tokens": "30.893512176",
"remaining_tokens": "169.106487824"
"withdrawn_tokens": "129.710572044",
"remaining_tokens": "70.289427956"
},
{
"address": "0xB95C140fB5c6c881eDc32be1e220D2bD5D8f9c36",
@@ -72101,6 +72322,12 @@
}
],
"withdrawals": [
{
"amount": "157.9045256224",
"user": "0x74Da54F44975a1C224ABb5CAB8e5fc38a268B425",
"tranche_id": 5,
"tx": "0x65f234b1c4da032348199c9b165cdaee8817e428d1bf357309260ac5d33279d5"
},
{
"amount": "49.4479147616",
"user": "0x74Da54F44975a1C224ABb5CAB8e5fc38a268B425",
@@ -72109,8 +72336,8 @@
}
],
"total_tokens": "320",
"withdrawn_tokens": "49.4479147616",
"remaining_tokens": "270.5520852384"
"withdrawn_tokens": "207.352440384",
"remaining_tokens": "112.647559616"
},
{
"address": "0xB7D725753a300FeD6D13f3951D890856EF0C6e30",
@@ -74821,10 +75048,17 @@
"tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "1167.346518264",
"user": "0x17197a34E926539066de1987d462c996c797b772",
"tranche_id": 5,
"tx": "0xba73b47fe051e5544519f878f34a69a9251494ea5d6a883f308df59c9f0e29c5"
}
],
"total_tokens": "1800",
"withdrawn_tokens": "0",
"remaining_tokens": "1800"
"withdrawn_tokens": "1167.346518264",
"remaining_tokens": "632.653481736"
},
{
"address": "0x854a6b84ad48645eac476D0D8197f854C06DD79D",
@@ -76802,7 +77036,7 @@
"tranche_start": "2021-12-05T00:00:00.000Z",
"tranche_end": "2022-06-05T00:00:00.000Z",
"total_added": "171288.42",
"total_removed": "64196.1049690697989",
"total_removed": "64226.1049690697989",
"locked_amount": "0",
"deposits": [
{
@@ -81027,6 +81261,11 @@
}
],
"withdrawals": [
{
"amount": "30",
"user": "0x4A13d4dC5e06ACdA81C011D55a7DaAc332bC5Dbf",
"tx": "0x72e95b2e51cae897d2c09ca7294c630fb3b969aea950a53e10158570faee89c7"
},
{
"amount": "250",
"user": "0xbd09687340A09BeB0B5EE0D3C2bCa8d78eBF6E63",
@@ -97234,10 +97473,17 @@
"tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "30",
"user": "0x4A13d4dC5e06ACdA81C011D55a7DaAc332bC5Dbf",
"tranche_id": 6,
"tx": "0x72e95b2e51cae897d2c09ca7294c630fb3b969aea950a53e10158570faee89c7"
}
],
"total_tokens": "30",
"withdrawn_tokens": "0",
"remaining_tokens": "30"
"withdrawn_tokens": "30",
"remaining_tokens": "0"
},
{
"address": "0x02e3d96c448d38790151930A39c36273904C1b0B",
+9 -2
View File
@@ -5,10 +5,12 @@ import { Heading } from '../../components/heading';
import { SplashLoader } from '../../components/splash-loader';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
import {
useWithdrawals,
withdrawalProvider,
useWithdrawalDialog,
WithdrawalsTable,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { useDocumentTitle } from '../../hooks/use-document-title';
import type { RouteChildProps } from '../index';
@@ -29,7 +31,12 @@ const Withdrawals = ({ name }: RouteChildProps) => {
const WithdrawPendingContainer = () => {
const openWithdrawalDialog = useWithdrawalDialog((state) => state.open);
const { t } = useTranslation();
const { data, loading, error } = useWithdrawals();
const { pubKey } = useVegaWallet();
const { data, loading, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
if (error) {
return (
+66 -41
View File
@@ -21,15 +21,21 @@ const orderUpdatedAt = 'updatedAt';
const assetSelectField = 'select[name="asset"]';
const amountField = 'input[name="amount"]';
const txTimeout = Cypress.env('txTimeout');
const btcName = 'BTC (local)';
const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
const btcName =
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC';
const btcSymbol = 'tBTC';
const usdcSymbol = 'fUSDC';
const toastContent = 'toast-content';
const ordersTab = 'Orders';
const depositsTab = 'Deposits';
const toastCloseBtn = 'toast-close';
const price = '390';
const size = '0.0005';
const newPrice = '200';
// TODO: ensure this test runs only if capsule is running via workflow
// Because the tests are run on a live network to optimize time, the tests are interdependent and must be run in the given order.
describe('capsule', { tags: '@slow' }, () => {
before(() => {
cy.createMarket();
@@ -50,8 +56,8 @@ describe('capsule', { tags: '@slow' }, () => {
marketId: market.id,
type: Schema.OrderType.TYPE_LIMIT,
side: Schema.Side.SIDE_BUY,
size: '0.0005',
price: '390',
size: size,
price: price,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
};
const rawPrice = removeDecimal(order.price, market.decimalPlaces);
@@ -64,7 +70,8 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+0.0005 @ 390.00 ${usdcSymbol}`
`ConfirmedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+${order.size} @ ${order.price}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
// orderbook cells are keyed by price level
@@ -75,7 +82,7 @@ describe('capsule', { tags: '@slow' }, () => {
.should('contain.text', rawSize);
cy.getByTestId(ordersTab).click();
cy.getByTestId('edit').should('contain.text', 'Edit');
cy.getByTestId('edit', txTimeout).should('contain.text', 'Edit');
cy.getByTestId('tab-orders').within(() => {
cy.get('.ag-center-cols-container')
.children()
@@ -113,35 +120,41 @@ describe('capsule', { tags: '@slow' }, () => {
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderCreatedAt);
});
});
//edit order
});
it('can edit order', function () {
cy.getByTestId(ordersTab).click();
cy.getByTestId('edit').first().should('be.visible').click();
cy.getByTestId('dialog-title').should('contain.text', 'Edit order');
cy.get('#limitPrice').focus().clear().type('200');
cy.get('#limitPrice').focus().clear().type(newPrice);
cy.getByTestId('edit-order').find('[type="submit"]').click();
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+0.0005 @ 200.00 ${usdcSymbol}+0.0005 @ 200.00 ${usdcSymbol}`
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(ordersTab).click();
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.get('.ag-center-cols-container')
.children()
.first()
.within(() => {
cy.get(`[col-id='${orderPrice}']`).then(($price) => {
expect(parseFloat($price.text())).to.equal(parseFloat('200'));
expect(parseFloat($price.text())).to.equal(parseFloat(newPrice));
});
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderUpdatedAt);
});
//cancel order
});
it('can cancel order', function () {
cy.getByTestId(ordersTab).click();
cy.getByTestId('cancel').first().click();
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+0.0005 @ 200.00 ${usdcSymbol}`
`ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId('tab-orders')
.get('.ag-center-cols-container')
@@ -151,7 +164,10 @@ describe('capsule', { tags: '@slow' }, () => {
.should('contain.text', OrderStatusMapping.STATUS_CANCELLED);
});
it('can deposit and withdrawal', function () {
it('can deposit', function () {
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
// 1001-DEPO-001
// 1001-DEPO-002
// 1001-DEPO-003
@@ -160,27 +176,12 @@ describe('capsule', { tags: '@slow' }, () => {
// 1001-DEPO-007
// 1001-DEPO-008
// 1001-DEPO-009
// 1002-WITH-001
// 1002-WITH-006
// 1002-WITH-009
// 002-WITH-011
// 1002-WITH-024
// 1002-WITH-012
// 1002-WITH-013
// 1002-WITH-014
// 1002-WITH-015
// 1002-WITH-016
// 1002-WITH-019
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.highlight('creating deposit');
// 1001-DEPO-010
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName);
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.getByTestId('deposit-approve-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
@@ -188,7 +189,8 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId('deposit-submit').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
`Transaction completedYour transaction has been completedView on EtherscanDeposit 1.00 ${btcSymbol}`
`Transaction confirmedYour transaction has been confirmed.View on EtherscanDeposit 1.00 ${btcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId('Collateral').click();
@@ -196,8 +198,6 @@ describe('capsule', { tags: '@slow' }, () => {
cy.highlight('deposit verification');
cy.getByTestId('asset', txTimeout).should('contain.text', btcSymbol);
// need to reload page to see deposit history complete
cy.reload();
cy.getByTestId(depositsTab).click();
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
@@ -214,15 +214,29 @@ describe('capsule', { tags: '@slow' }, () => {
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/tx');
.and('contain', `${sepoliaUrl}/tx/0x`);
});
});
cy.highlight('creating withdrawals');
it('can withdrawal', function () {
// 1002-WITH-001
// 1002-WITH-006
// 1002-WITH-009
// 1002-WITH-011
// 1002-WITH-024
// 1002-WITH-012
// 1002-WITH-013
// 1002-WITH-014
// 1002-WITH-015
// 1002-WITH-016
// 1002-WITH-019
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField).select(btcName);
cy.get(assetSelectField, txTimeout).select(
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC',
{ force: true }
);
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -243,7 +257,7 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId('toast-complete-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Transaction completed'
'Transaction confirmed'
);
cy.getByTestId('complete-withdrawal', txTimeout).should('not.exist');
@@ -258,17 +272,28 @@ describe('capsule', { tags: '@slow' }, () => {
cy.get('[col-id="details.receiverAddress"]')
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/address/');
.and('contain', `${sepoliaUrl}/address/`);
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="withdrawnTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Completed');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/tx/0x');
.and('contain', `${sepoliaUrl}/tx/0x`);
});
});
it('deposit - if approved amount is less than deposit: must see that an approval is needed and be prompted to approve more', function () {
// 1001-DEPO-006
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('20000000');
cy.getByTestId('deposit-approve-submit').should('be.visible');
});
});
function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) {
cy.get(`[col-id='${date}'] .ag-cell-wrapper`)
.children('span')
@@ -56,6 +56,7 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
});
it('insufficient funds', () => {
// 1001-DEPO-005
// Deposit amount is valid, but less than approved. This will always be the case because our
// CI wallet wont have approved any assets
cy.get(amountField)
@@ -454,8 +454,4 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
);
});
});
it.skip('tbd for 7003-MORD', () => {
// NOT COVERED: must see the reference, offset and direction for each part pegged order - waiting for clarification
// NOT COVERED: must see the reference, offset and direction for each part liquidity order order - waiting for clarification
});
});
@@ -1,22 +1,28 @@
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { useDeposits } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/react-helpers';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
export const DepositsContainer = () => {
const { deposits, loading, error } = useDeposits();
const { pubKey } = useVegaWallet();
const { data, loading, error } = useDataProvider({
dataProvider: depositsProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const openDepositDialog = useDepositDialog((state) => state.open);
return (
<div className="h-full grid grid-rows-[1fr,min-content]">
<div className="h-full relative">
<DepositsTable
rowData={deposits || []}
rowData={data || []}
noRowsOverlayComponent={() => null}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={deposits}
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}
@@ -1,14 +1,20 @@
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import {
useWithdrawals,
withdrawalProvider,
useWithdrawalDialog,
WithdrawalsTable,
} from '@vegaprotocol/withdraws';
import { t } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
export const WithdrawalsContainer = () => {
const { data, loading, error } = useWithdrawals();
const { pubKey } = useVegaWallet();
const { data, loading, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const openWithdrawDialog = useWithdrawalDialog((state) => state.open);
return (
@@ -170,7 +170,7 @@ export const SelectMarketPopover = ({
</div>
) : (
<table className="relative text-sm w-full whitespace-nowrap">
{pubKey && (positions?.length ?? 0) > 0 ? (
{pubKey && (positions?.length ?? 0) && (markets?.length ?? 0) ? (
<>
<TableTitle>{t('My markets')}</TableTitle>
<SelectAllMarketsTableBody
@@ -50,10 +50,10 @@ export type Account = Omit<AccountFieldsFragment, 'market' | 'asset'> & {
};
const update = (
data: AccountFieldsFragment[],
data: AccountFieldsFragment[] | null,
deltas: AccountEventsSubscription['accounts']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas.forEach((delta) => {
const id = getId(delta);
const index = draft.findIndex((a) => getId(a) === id);
@@ -73,15 +73,8 @@ const update = (
});
};
const getData = (
responseData: AccountsQuery
): AccountFieldsFragment[] | null => {
return (
removePaginationWrapper(responseData.party?.accountsConnection?.edges) ??
null
);
};
const getData = (responseData: AccountsQuery | null): AccountFieldsFragment[] =>
removePaginationWrapper(responseData?.party?.accountsConnection?.edges) || [];
const getDelta = (
subscriptionData: AccountEventsSubscription
): AccountEventsSubscription['accounts'] => {
+1 -20
View File
@@ -1,4 +1,4 @@
import type { ApolloError, InMemoryCacheConfig } from '@apollo/client';
import type { InMemoryCacheConfig } from '@apollo/client';
import {
ApolloClient,
from,
@@ -13,7 +13,6 @@ import { createClient as createWSClient } from 'graphql-ws';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
import ApolloLinkTimeout from 'apollo-link-timeout';
import type { GraphQLErrors } from '@apollo/client/errors';
import { localLoggerFactory } from '@vegaprotocol/react-helpers';
const isBrowser = typeof window !== 'undefined';
@@ -110,21 +109,3 @@ export function createClient({
connectToDevTools,
});
}
const isApolloGraphQLError = (
error: ApolloError | Error | undefined
): error is ApolloError => {
return !!error && !!(error as ApolloError).graphQLErrors;
};
const hasNotFoundGraphQLErrors = (errors: GraphQLErrors) => {
return errors.some((e) => e.extensions && e.extensions['type'] === NOT_FOUND);
};
export const isNotFoundGraphQLError = (
error: Error | ApolloError | undefined
) => {
return (
isApolloGraphQLError(error) && hasNotFoundGraphQLErrors(error.graphQLErrors)
);
};
+3 -8
View File
@@ -6,20 +6,15 @@ import { AssetDocument } from './__generated__/Asset';
export type Asset = AssetFieldsFragment;
const getData = (responseData: AssetQuery) => {
const foundAssets = responseData.assetsConnection?.edges
const getData = (responseData: AssetQuery | null) => {
const foundAssets = responseData?.assetsConnection?.edges
?.filter((e) => Boolean(e?.node))
.map((e) => e?.node as Asset);
if (foundAssets && foundAssets?.length > 0) return foundAssets[0];
return null;
};
export const assetProvider = makeDataProvider<
AssetQuery,
Asset | null,
never,
never
>({
export const assetProvider = makeDataProvider<AssetQuery, Asset, never, never>({
query: AssetDocument,
getData,
});
+3 -3
View File
@@ -16,14 +16,14 @@ export type BuiltinAsset = Omit<Asset, 'source'> & {
source: BuiltinAssetSource;
};
const getData = (responseData: AssetsQuery) =>
responseData.assetsConnection?.edges
const getData = (responseData: AssetsQuery | null) =>
responseData?.assetsConnection?.edges
?.filter((e) => Boolean(e?.node))
.map((e) => e?.node as Asset) ?? [];
export const assetsProvider = makeDataProvider<
AssetsQuery,
Asset[] | null,
Asset[],
never,
never
>({
@@ -48,7 +48,7 @@ function createNewMarketProposal(): ProposalSubmissionBody {
signers: [
{
pubKey: {
key: '0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC',
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
},
},
],
@@ -74,6 +74,7 @@ export const DealTicket = ({ market, submit }: DealTicketProps) => {
usePersistedOrderStoreSubscription(market.id, (storedOrder) => {
if (order.price !== storedOrder.price) {
clearErrors('price');
setValue('price', storedOrder.price);
}
});
@@ -0,0 +1,49 @@
import uniqBy from 'lodash/uniqBy';
import orderBy from 'lodash/orderBy';
import {
getEvents,
makeDataProvider,
removePaginationWrapper,
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import {
DepositsDocument,
DepositEventDocument,
} from './__generated__/Deposit';
import type {
DepositFieldsFragment,
DepositsQuery,
DepositEventSubscription,
DepositEventSubscriptionVariables,
} from './__generated__/Deposit';
export const depositsProvider = makeDataProvider<
DepositsQuery,
DepositFieldsFragment[],
DepositEventSubscription,
DepositEventSubscription,
DepositEventSubscriptionVariables
>({
query: DepositsDocument,
subscriptionQuery: DepositEventDocument,
getData: (data: DepositsQuery | null) =>
orderBy(
removePaginationWrapper(data?.party?.depositsConnection?.edges || []),
['createdTimestamp'],
['desc']
),
getDelta: (data: DepositEventSubscription) => data,
update: (
data: DepositFieldsFragment[] | null,
delta: DepositEventSubscription
) => {
if (!delta.busEvents?.length) {
return data;
}
const incoming = getEvents<DepositFieldsFragment>(
Schema.BusEventType.Deposit,
delta.busEvents
);
return uniqBy([...incoming, ...(data || [])], 'id');
},
});
+1 -1
View File
@@ -5,7 +5,7 @@ export * from './deposit-limits';
export * from './deposit-manager';
export * from './deposits-table';
export * from './use-deposit-balances';
export * from './use-deposits';
export * from './deposits-provider';
export * from './use-get-allowance';
export * from './use-get-balance-of-erc20-token';
export * from './use-get-deposit-maximum';
-100
View File
@@ -1,100 +0,0 @@
import uniqBy from 'lodash/uniqBy';
import orderBy from 'lodash/orderBy';
import { getNodes, getEvents } from '@vegaprotocol/react-helpers';
import type { UpdateQueryFn } from '@apollo/client/core/watchQueryOptions';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useEffect, useMemo } from 'react';
import * as Schema from '@vegaprotocol/types';
import {
useDepositsQuery,
DepositEventDocument,
} from './__generated__/Deposit';
import type {
DepositFieldsFragment,
DepositsQuery,
DepositEventSubscription,
DepositEventSubscriptionVariables,
} from './__generated__/Deposit';
export const useDeposits = () => {
const { pubKey } = useVegaWallet();
const { data, loading, error, subscribeToMore } = useDepositsQuery({
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const deposits = useMemo(() => {
if (!data?.party?.depositsConnection?.edges?.length) {
return [];
}
return orderBy(
getNodes<DepositFieldsFragment>(data.party?.depositsConnection),
['createdTimestamp'],
['desc']
);
}, [data]);
useEffect(() => {
if (!pubKey) return;
const unsub = subscribeToMore<
DepositEventSubscription,
DepositEventSubscriptionVariables
>({
document: DepositEventDocument,
variables: { partyId: pubKey },
updateQuery,
});
return () => {
unsub();
};
}, [pubKey, subscribeToMore]);
return { data, loading, error, deposits };
};
const updateQuery: UpdateQueryFn<
DepositsQuery,
DepositEventSubscriptionVariables,
DepositEventSubscription
> = (prev, { subscriptionData, variables }) => {
if (!subscriptionData.data.busEvents?.length || !variables?.partyId) {
return prev;
}
const curr = getNodes<DepositFieldsFragment>(prev.party?.depositsConnection);
const incoming = getEvents<DepositFieldsFragment>(
Schema.BusEventType.Deposit,
subscriptionData.data.busEvents
);
const deposits = uniqBy([...incoming, ...curr], 'id');
if (!prev.party) {
return {
...prev,
party: {
__typename: 'Party',
id: variables?.partyId,
depositsConnection: {
__typename: 'DepositsConnection',
edges: deposits.map((d) => ({ __typename: 'DepositEdge', node: d })),
},
},
};
}
return {
...prev,
party: {
...prev.party,
id: variables?.partyId,
depositsConnection: {
__typename: 'DepositsConnection',
edges: deposits.map((d) => ({ __typename: 'DepositEdge', node: d })),
},
},
};
};
+2 -2
View File
@@ -59,8 +59,8 @@ const update = (
export type Trade = Omit<FillFieldsFragment, 'market'> & { market?: Market };
export type TradeEdge = Edge<Trade>;
const getData = (responseData: FillsQuery): FillEdgeFragment[] =>
responseData.party?.tradesConnection?.edges || [];
const getData = (responseData: FillsQuery | null): FillEdgeFragment[] =>
responseData?.party?.tradesConnection?.edges || [];
const getPageInfo = (responseData: FillsQuery): PageInfo | null =>
responseData.party?.tradesConnection?.pageInfo || null;
@@ -5,8 +5,8 @@ import type {
} from './__generated__/Proposals';
import { ProposalsListDocument } from './__generated__/Proposals';
const getData = (responseData: ProposalsListQuery) =>
responseData.proposalsConnection?.edges
const getData = (responseData: ProposalsListQuery | null) =>
responseData?.proposalsConnection?.edges
?.filter((edge) => Boolean(edge?.node))
.map((edge) => edge?.node as ProposalListFieldsFragment) || null;
@@ -35,12 +35,12 @@ export type LedgerEntry = LedgerEntryFragment & {
export type AggregatedLedgerEntriesEdge = Schema.AggregatedLedgerEntriesEdge;
const getData = (responseData: LedgerEntriesQuery) => {
return responseData.ledgerEntries?.edges || [];
const getData = (responseData: LedgerEntriesQuery | null) => {
return responseData?.ledgerEntries?.edges || [];
};
export const update = (
data: ReturnType<typeof getData>,
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getData>,
reload: () => void,
variables?: LedgerEntriesQueryVariables
@@ -35,10 +35,10 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
query: LiquidityProvisionsDocument,
subscriptionQuery: LiquidityProvisionsUpdateDocument,
update: (
data: LiquidityProvisionFieldsFragment[],
data: LiquidityProvisionFieldsFragment[] | null,
deltas: LiquidityProvisionsUpdateSubscription['liquidityProvisions']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas?.forEach((delta) => {
const id = getId(delta);
const index = draft.findIndex((a) => getId(a) === id);
@@ -63,9 +63,9 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
});
});
},
getData: (responseData: LiquidityProvisionsQuery) => {
getData: (responseData: LiquidityProvisionsQuery | null) => {
return (
responseData.market?.liquidityProvisionsConnection?.edges?.map(
responseData?.market?.liquidityProvisionsConnection?.edges?.map(
(e) => e?.node
) ?? []
).filter((e) => !!e) as LiquidityProvisionFieldsFragment[];
@@ -105,7 +105,7 @@ export const marketLiquidityDataProvider = makeDataProvider<
never
>({
query: MarketLpDocument,
getData: (responseData: MarketLpQuery) => {
getData: (responseData: MarketLpQuery | null) => {
return responseData;
},
});
@@ -119,10 +119,10 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
query: LiquidityProviderFeeShareDocument,
subscriptionQuery: LiquidityProviderFeeShareUpdateDocument,
update: (
data: LiquidityProviderFeeShareFieldsFragment[],
data: LiquidityProviderFeeShareFieldsFragment[] | null,
deltas: LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas?.forEach((delta) => {
const id = delta.partyId;
const index = draft.findIndex((a) => a.party.id === id);
@@ -143,7 +143,7 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
});
},
getData: (data) => {
return data.market?.data?.liquidityProviderFeeShare || [];
return data?.market?.data?.liquidityProviderFeeShare || [];
},
getDelta: (subscriptionData: LiquidityProviderFeeShareUpdateSubscription) => {
return subscriptionData.marketsData[0].liquidityProviderFeeShare;
@@ -58,7 +58,7 @@ export interface Markets {
}
const getData = (
responseData: LiquidityProvisionMarketsQuery
responseData: LiquidityProvisionMarketsQuery | null
): LiquidityProvisionMarket[] | null => {
return (
responseData?.marketsConnection?.edges.map((edge) => {
@@ -53,7 +53,7 @@ export const update: Update<
return data;
};
const getData = (responseData: MarketDepthQuery) => responseData.market;
const getData = (responseData: MarketDepthQuery | null) => responseData?.market;
const getDelta = (subscriptionData: MarketDepthUpdateSubscription) =>
subscriptionData.marketsDepthUpdate;
@@ -9,5 +9,5 @@ export const marketInfoDataProvider = makeDataProvider<
never
>({
query: MarketInfoDocument,
getData: (responseData: MarketInfoQuery) => responseData,
getData: (responseData: MarketInfoQuery | null) => responseData,
});
@@ -24,7 +24,7 @@ export const update = (data: Candle[] | null, delta: Candle) => {
return [delta];
};
const getData = (responseData: MarketCandlesQuery): Candle[] | null =>
const getData = (responseData: MarketCandlesQuery | null): Candle[] | null =>
responseData?.marketsConnection?.edges[0]?.node.candlesConnection?.edges
?.filter((edge) => edge?.node)
.map((edge) => edge?.node as Candle) || null;
@@ -18,14 +18,20 @@ import type {
export type MarketData = MarketDataFieldsFragment;
const update = (data: MarketData, delta: MarketDataUpdateFieldsFragment) => {
return produce(data, (draft) => {
const { marketId, __typename, ...marketData } = delta;
Object.assign(draft, marketData);
});
const update = (
data: MarketData | null,
delta: MarketDataUpdateFieldsFragment
) => {
return (
data &&
produce(data, (draft) => {
const { marketId, __typename, ...marketData } = delta;
Object.assign(draft, marketData);
})
);
};
const getData = (responseData: MarketDataQuery): MarketData | null =>
const getData = (responseData: MarketDataQuery | null): MarketData | null =>
responseData?.marketsConnection?.edges[0].node.data || null;
const getDelta = (
+1 -1
View File
@@ -11,7 +11,7 @@ import type { MarketData } from './market-data-provider';
import { marketDataProvider } from './market-data-provider';
const getData = (
responseData: MarketQuery
responseData: MarketQuery | null
): SingleMarketFieldsFragment | null => responseData?.market || null;
export const marketProvider = makeDataProvider<
@@ -8,7 +8,9 @@ export interface MarketCandles {
candles: Candle[] | undefined;
}
const getData = (responseData: MarketsCandlesQuery): MarketCandles[] | null =>
const getData = (
responseData: MarketsCandlesQuery | null
): MarketCandles[] | null =>
responseData?.marketsConnection?.edges.map((edge) => ({
marketId: edge.node.id,
candles: edge.node.candlesConnection?.edges
@@ -3,8 +3,8 @@ import type { MarketsDataQuery } from './__generated__/markets-data';
import { MarketsDataDocument } from './__generated__/markets-data';
import type { MarketData } from './market-data-provider';
const getData = (responseData: MarketsDataQuery): MarketData[] | null =>
responseData.marketsConnection?.edges
const getData = (responseData: MarketsDataQuery | null): MarketData[] | null =>
responseData?.marketsConnection?.edges
.filter((edge) => edge.node.data)
.map((edge) => edge.node.data as MarketData) || null;
+1 -1
View File
@@ -21,7 +21,7 @@ import type { Candle } from './market-candles-provider';
export type Market = MarketFieldsFragment;
const getData = (responseData: MarketsQuery): Market[] | null =>
const getData = (responseData: MarketsQuery | null): Market[] | null =>
responseData?.marketsConnection?.edges.map((edge) => edge.node) || null;
export const marketsProvider = makeDataProvider<
@@ -62,7 +62,7 @@ describe('order data provider', () => {
expect(updatedData && updatedData[1].node.updatedAt).toEqual(
delta[4].updatedAt
);
expect(update([], delta, () => null, { partyId: '0x123' }).length).toEqual(
expect(update([], delta, () => null, { partyId: '0x123' })?.length).toEqual(
4
);
});
@@ -71,7 +71,7 @@ const orderMatchFilters = (
return true;
};
const getData = (responseData: OrdersQuery) =>
const getData = (responseData: OrdersQuery | null) =>
responseData?.party?.ordersConnection?.edges || [];
const getDelta = (subscriptionData: OrdersUpdateSubscription) =>
@@ -81,7 +81,7 @@ const getPageInfo = (responseData: OrdersQuery): PageInfo | null =>
responseData.party?.ordersConnection?.pageInfo || null;
export const update = (
data: ReturnType<typeof getData>,
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getDelta>,
reload: () => void,
variables?: OrdersQueryVariables
@@ -14,10 +14,10 @@ import type {
} from './__generated__/Positions';
const update = (
data: MarginFieldsFragment[],
data: MarginFieldsFragment[] | null,
delta: MarginsSubscriptionSubscription['margins']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
const { marketId } = delta;
const index = draft.findIndex((node) => node.market.id === marketId);
if (index !== -1) {
@@ -49,8 +49,9 @@ const update = (
});
};
const getData = (responseData: MarginsQuery) =>
removePaginationWrapper(responseData.party?.marginsConnection?.edges) || [];
const getData = (responseData: MarginsQuery | null) =>
removePaginationWrapper(responseData?.party?.marginsConnection?.edges) || [];
const getDelta = (subscriptionData: MarginsSubscriptionSubscription) =>
subscriptionData.margins;
@@ -4,8 +4,8 @@ import BigNumber from 'bignumber.js';
import sortBy from 'lodash/sortBy';
import type { Account } from '@vegaprotocol/accounts';
import { accountsDataProvider } from '@vegaprotocol/accounts';
import { toBigNum } from '@vegaprotocol/react-helpers';
import {
toBigNum,
makeDataProvider,
makeDerivedDataProvider,
removePaginationWrapper,
@@ -171,10 +171,10 @@ export const getMetrics = (
};
export const update = (
data: PositionFieldsFragment[],
data: PositionFieldsFragment[] | null,
deltas: PositionsSubscriptionSubscription['positions']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas.forEach((delta) => {
const index = draft.findIndex(
(node) => node.market.id === delta.marketId
@@ -212,8 +212,8 @@ export const positionsDataProvider = makeDataProvider<
query: PositionsDocument,
subscriptionQuery: PositionsSubscriptionDocument,
update,
getData: (responseData: PositionsQuery) =>
removePaginationWrapper(responseData.party?.positionsConnection?.edges) ||
getData: (responseData: PositionsQuery | null) =>
removePaginationWrapper(responseData?.party?.positionsConnection?.edges) ||
[],
getDelta: (subscriptionData: PositionsSubscriptionSubscription) =>
subscriptionData.positions,
@@ -172,3 +172,33 @@ it('displays realised and unrealised PNL', async () => {
expect(cells[9].textContent).toEqual('1.23');
expect(cells[10].textContent).toEqual('4.56');
});
it('displays close button', async () => {
await act(async () => {
render(
<PositionsTable
rowData={singleRowData}
onClose={() => {
return;
}}
/>
);
});
const cells = screen.getAllByRole('gridcell');
expect(cells[12].textContent).toEqual('Close');
});
it('do not display close button if openVolume is zero', async () => {
await act(async () => {
render(
<PositionsTable
rowData={[{ ...singleRow, openVolume: '0' }]}
onClose={() => {
return;
}}
/>
);
});
const cells = screen.getAllByRole('gridcell');
expect(cells[12].textContent).toEqual('');
});
+10 -8
View File
@@ -381,14 +381,16 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
{onClose ? (
<AgGridColumn
type="rightAligned"
cellRenderer={({ data }: VegaICellRendererParams<Position>) => (
<ButtonLink
data-testid="close-position"
onClick={() => data && onClose(data)}
>
{t('Close')}
</ButtonLink>
)}
cellRenderer={({ data }: VegaICellRendererParams<Position>) =>
data?.openVolume && data?.openVolume !== '0' ? (
<ButtonLink
data-testid="close-position"
onClick={() => data && onClose(data)}
>
{t('Close')}
</ButtonLink>
) : null
}
/>
) : null}
</AgGrid>
@@ -2,10 +2,7 @@ import { useCallback, useState } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { positionsDataProvider } from './positions-data-providers';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import type {
PositionFieldsFragment,
PositionsSubscriptionSubscription,
} from './__generated__/Positions';
import type { PositionFieldsFragment } from './__generated__/Positions';
export const useMarketPositionOpenVolume = (marketId: string) => {
const { pubKey } = useVegaWallet();
@@ -21,10 +18,7 @@ export const useMarketPositionOpenVolume = (marketId: string) => {
[setOpenVolume, marketId]
);
useDataProvider<
PositionFieldsFragment[],
PositionsSubscriptionSubscription['positions']
>({
useDataProvider({
dataProvider: positionsDataProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey || !marketId,
+1 -19
View File
@@ -1,20 +1,2 @@
export * from './hooks';
export * from './lib/format';
export * from './lib/generic-data-provider';
export * from './lib/get-nodes';
export * from './lib/get-events';
export * from './lib/grid';
export * from './lib/i18n';
export * from './lib/pagination';
export * from './lib/ag-grid-update';
export * from './lib/remove-0x';
export * from './lib/storage';
export * from './lib/time';
export * from './lib/validate';
export * from './lib/links';
export * from './lib/is-asset-erc20';
export * from './lib/remove-pagination-wrapper';
export * from './lib/__generated__/ChainId';
export * from './lib/data-grid';
export * from './lib/local-logger';
export * from './lib/market-expires';
export * from './lib';
@@ -0,0 +1,29 @@
import type { ApolloError } from '@apollo/client';
import type { GraphQLErrors } from '@apollo/client/errors';
const NOT_FOUND = 'NotFound';
const isApolloGraphQLError = (
error: ApolloError | Error | undefined
): error is ApolloError => {
return !!error && !!(error as ApolloError).graphQLErrors;
};
const hasNotFoundGraphQLErrors = (errors: GraphQLErrors, path?: string) => {
return errors.some(
(e) =>
e.extensions &&
e.extensions['type'] === NOT_FOUND &&
(!path || e.path?.[0] === path)
);
};
export const isNotFoundGraphQLError = (
error: Error | ApolloError | undefined,
path?: string
) => {
return (
isApolloGraphQLError(error) &&
hasNotFoundGraphQLErrors(error.graphQLErrors, path)
);
};
@@ -62,7 +62,7 @@ const subscribe = makeDataProvider<QueryData, Data, SubscriptionData, Delta>({
query,
subscriptionQuery,
update,
getData: (r) => r.data,
getData: (r) => r?.data || null,
getDelta: (r) => r.data,
});
@@ -91,7 +91,7 @@ const paginatedSubscribe = makeDataProvider<
query,
subscriptionQuery,
update,
getData: (r) => r.data,
getData: (r) => r?.data || null,
getDelta: (r) => r.data,
pagination: {
first,
@@ -215,7 +215,7 @@ describe('data provider', () => {
const subscription = subscribe(callback, client);
await resolveQuery({ data });
const delta: Item[] = [];
update.mockImplementationOnce((data, delta) => [...data, ...delta]);
update.mockImplementationOnce((data, delta) => [...(data || []), ...delta]);
// calling onNext from client.subscribe({ query }).subscribe(onNext)
await clientSubscribeSubscribe.mock.calls[
clientSubscribeSubscribe.mock.calls.length - 1
@@ -234,7 +234,7 @@ describe('data provider', () => {
const subscription = subscribe(callback, client);
await resolveQuery({ data });
const delta: Item[] = [];
update.mockImplementationOnce((data, delta) => data);
update.mockImplementationOnce((data, delta) => data || []);
const callbackCallsLength = callback.mock.calls.length;
// calling onNext from client.subscribe({ query }).subscribe(onNext)
await clientSubscribeSubscribe.mock.calls[
@@ -591,7 +591,7 @@ describe('derived data provider', () => {
await resolveQuery({ data: part2 });
expect(combineData).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
update.mockImplementation((data, delta) => [...data, ...delta]);
update.mockImplementation((data, delta) => [...(data || []), ...delta]);
combineData.mockReturnValueOnce({ ...data });
const combinedDelta = {};
combineDelta.mockReturnValueOnce(combinedDelta);
@@ -629,7 +629,7 @@ describe('derived data provider', () => {
await resolveQuery({ data: [] });
expect(combineData).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
update.mockImplementation((data, delta) => [...data, ...delta]);
update.mockImplementation((data, delta) => [...(data || []), ...delta]);
combineData.mockReturnValueOnce({ ...data });
const combinedInsertionData = {};
combineInsertionData.mockReturnValueOnce(combinedInsertionData);
@@ -8,6 +8,7 @@ import type {
} from '@apollo/client';
import type { Subscription } from 'zen-observable-ts';
import isEqual from 'lodash/isEqual';
import { isNotFoundGraphQLError } from './apollo-client';
import type * as Schema from '@vegaprotocol/types';
interface UpdateData<Data, Delta> {
delta?: Delta;
@@ -71,7 +72,12 @@ export interface Update<
Delta,
Variables extends OperationVariables = OperationVariables
> {
(data: Data, delta: Delta, reload: Reload, variables?: Variables): Data;
(
data: Data | null,
delta: Delta,
reload: Reload,
variables?: Variables
): Data;
}
export interface Append<Data> {
@@ -88,7 +94,7 @@ export interface Append<Data> {
}
interface GetData<QueryData, Data, Variables> {
(queryData: QueryData, variables?: Variables): Data | null;
(queryData: QueryData | null, variables?: Variables): Data | null;
}
interface GetPageInfo<QueryData> {
@@ -160,7 +166,7 @@ interface DataProviderParams<
> {
query: Query<QueryData>;
subscriptionQuery?: Query<SubscriptionData>;
update?: Update<Data, Delta, Variables>;
update?: Update<Data | null, Delta, Variables>;
getData: GetData<QueryData, Data, Variables>;
getDelta?: GetDelta<SubscriptionData, Delta, Variables>;
pagination?: {
@@ -349,6 +355,11 @@ function makeDataProviderInternal<
}
loaded = true;
} catch (e) {
if (isNotFoundGraphQLError(e as Error, 'party')) {
data = getData(null, variables);
loaded = true;
return;
}
// if error will occur data provider stops subscription
error = e as Error;
if (subscription) {
@@ -384,7 +395,7 @@ function makeDataProviderInternal<
return;
}
const delta = getDelta(subscriptionData, variables);
if (loading || !data) {
if (loading) {
updateQueue.push(delta);
} else {
const updatedData = update(data, delta, reload, variables);
@@ -409,7 +420,6 @@ function makeDataProviderInternal<
if (!client) {
return;
}
if (subscriptionQuery && getDelta && update) {
subscription = client
.subscribe<SubscriptionData>({
+17 -13
View File
@@ -1,16 +1,20 @@
export * from './format';
export * from './grid';
export * from './storage';
export * from './validate';
export * from './generic-data-provider';
export * from './get-nodes';
export * from './get-events';
export * from './i18n';
export * from './pagination';
export * from './remove-0x';
export * from './time';
export * from './links';
export * from './remove-pagination-wrapper';
export * from './__generated__/ChainId';
export * from './ag-grid-update';
export * from './apollo-client';
export * from './data-grid';
export * from './format';
export * from './generic-data-provider';
export * from './get-events';
export * from './get-nodes';
export * from './grid';
export * from './i18n';
export * from './is-asset-erc20';
export * from './links';
export * from './local-logger';
export * from './market-expires';
export * from './pagination';
export * from './remove-0x';
export * from './remove-pagination-wrapper';
export * from './storage';
export * from './time';
export * from './validate';
+2
View File
@@ -3,6 +3,7 @@ fragment TradeFields on Trade {
price
size
createdAt
aggressor
market {
id
}
@@ -35,5 +36,6 @@ subscription TradesUpdate($marketId: ID!) {
size
createdAt
marketId
aggressor
}
}
+5 -3
View File
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type TradeFieldsFragment = { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, market: { __typename?: 'Market', id: string } };
export type TradeFieldsFragment = { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, market: { __typename?: 'Market', id: string } };
export type TradesQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
@@ -11,14 +11,14 @@ export type TradesQueryVariables = Types.Exact<{
}>;
export type TradesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, tradesConnection?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, market: { __typename?: 'Market', id: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null } | null };
export type TradesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, tradesConnection?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, market: { __typename?: 'Market', id: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null } | null };
export type TradesUpdateSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type TradesUpdateSubscription = { __typename?: 'Subscription', trades?: Array<{ __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string }> | null };
export type TradesUpdateSubscription = { __typename?: 'Subscription', trades?: Array<{ __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side }> | null };
export const TradeFieldsFragmentDoc = gql`
fragment TradeFields on Trade {
@@ -26,6 +26,7 @@ export const TradeFieldsFragmentDoc = gql`
price
size
createdAt
aggressor
market {
id
}
@@ -89,6 +90,7 @@ export const TradesUpdateDocument = gql`
size
createdAt
marketId
aggressor
}
}
`;
+2 -2
View File
@@ -20,7 +20,7 @@ import produce from 'immer';
export const MAX_TRADES = 50;
const getData = (
responseData: TradesQuery
responseData: TradesQuery | null
): ({
cursor: string;
node: TradeFieldsFragment;
@@ -30,7 +30,7 @@ const getDelta = (subscriptionData: TradesUpdateSubscription) =>
subscriptionData?.trades || [];
const update = (
data: ReturnType<typeof getData>,
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getDelta>
) => {
return produce(data, (draft) => {
+54 -53
View File
@@ -1,13 +1,15 @@
import { act, render, screen } from '@testing-library/react';
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
import { DOWN_CLASS, TradesTable, UP_CLASS } from './trades-table';
import { SELL_CLASS, TradesTable, BUY_CLASS } from './trades-table';
import type { Trade } from './trades-data-provider';
import { Side } from '@vegaprotocol/types';
const trade: Trade = {
__typename: 'Trade',
id: 'trade-id',
price: '111122200',
size: '2000',
aggressor: Side.SIDE_BUY,
createdAt: new Date('2022-04-06T19:00:00').toISOString(),
market: {
__typename: 'Market',
@@ -17,59 +19,58 @@ const trade: Trade = {
} as Trade['market'],
};
it('Correct columns are rendered', async () => {
await act(async () => {
render(<TradesTable rowData={[trade]} />);
});
const expectedHeaders = ['Price', 'Size', 'Created at'];
const headers = screen.getAllByRole('columnheader');
expect(headers).toHaveLength(expectedHeaders.length);
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
});
it('Number and data columns are formatted', async () => {
await act(async () => {
render(<TradesTable rowData={[trade]} />);
describe('TradesTable', () => {
it('should render correct columns', async () => {
await act(async () => {
render(<TradesTable rowData={[trade]} />);
});
const expectedHeaders = ['Price', 'Size', 'Created at'];
const headers = screen.getAllByRole('columnheader');
expect(headers).toHaveLength(expectedHeaders.length);
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
});
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
'1,111,222.00',
'20.00',
getDateTimeFormat().format(new Date(trade.createdAt)),
];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
it('should format number and data columns', async () => {
await act(async () => {
render(<TradesTable rowData={[trade]} />);
});
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
'1,111,222.00',
'20.00',
getDateTimeFormat().format(new Date(trade.createdAt)),
];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
});
});
it('should format price and size columns', async () => {
const trade2 = {
...trade,
id: 'trade-id-2',
price: (Number(trade.price) + 10).toString(),
size: (Number(trade.size) - 10).toString(),
};
await act(async () => {
render(<TradesTable rowData={[trade2, trade]} />);
});
const cells = screen.getAllByRole('gridcell');
const priceCells = cells.filter(
(cell) => cell.getAttribute('col-id') === 'price'
);
const sizeCells = cells.filter(
(cell) => cell.getAttribute('col-id') === 'size'
);
// For first trade price should have green class
// row 1
expect(priceCells[0]).toHaveClass(BUY_CLASS);
expect(priceCells[1]).not.toHaveClass(SELL_CLASS);
expect(sizeCells[1]).not.toHaveClass(SELL_CLASS);
expect(sizeCells[1]).not.toHaveClass(BUY_CLASS);
});
});
it('Price and size columns are formatted', async () => {
const trade2 = {
...trade,
id: 'trade-id-2',
price: (Number(trade.price) + 10).toString(),
size: (Number(trade.size) - 10).toString(),
};
await act(async () => {
render(<TradesTable rowData={[trade2, trade]} />);
});
const cells = screen.getAllByRole('gridcell');
const priceCells = cells.filter(
(cell) => cell.getAttribute('col-id') === 'price'
);
const sizeCells = cells.filter(
(cell) => cell.getAttribute('col-id') === 'size'
);
// For first trade price should have green class and size should have red class
// row 1
expect(priceCells[0]).toHaveClass(UP_CLASS);
expect(priceCells[1]).not.toHaveClass(DOWN_CLASS);
expect(priceCells[1]).not.toHaveClass(UP_CLASS);
expect(sizeCells[0]).toHaveClass(DOWN_CLASS);
expect(sizeCells[1]).not.toHaveClass(DOWN_CLASS);
expect(sizeCells[1]).not.toHaveClass(UP_CLASS);
});
+13 -23
View File
@@ -13,31 +13,22 @@ import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
import type { CellClassParams, ValueFormatterParams } from 'ag-grid-community';
import type { AgGridReactProps } from 'ag-grid-react';
import type { Trade } from './trades-data-provider';
import BigNumber from 'bignumber.js';
import { Side } from '@vegaprotocol/types';
export const UP_CLASS = 'text-vega-green dark:text-vega-green';
export const DOWN_CLASS = 'text-vega-pink dark:text-vega-pink';
export const BUY_CLASS = 'text-vega-green dark:text-vega-green';
export const SELL_CLASS = 'text-vega-pink dark:text-vega-pink';
const changeCellClass =
(dataKey: string) =>
({ api, value, node }: CellClassParams) => {
const rowIndex = node?.rowIndex;
let colorClass = '';
const changeCellClass = ({ node }: CellClassParams) => {
let colorClass = '';
if (typeof rowIndex === 'number') {
const prevRowNode = api.getModel().getRow(rowIndex + 1);
const prevValue = prevRowNode?.data && prevRowNode.data[dataKey];
const valueNum = new BigNumber(value);
if (node.data?.aggressor === Side.SIDE_BUY) {
colorClass = BUY_CLASS;
} else if (node.data?.aggressor === Side.SIDE_SELL) {
colorClass = SELL_CLASS;
}
if (valueNum.isGreaterThan(prevValue)) {
colorClass = UP_CLASS;
} else if (valueNum.isLessThan(prevValue)) {
colorClass = DOWN_CLASS;
}
}
return ['font-mono text-right', colorClass].join(' ');
};
return ['font-mono text-right', colorClass].join(' ');
};
export interface GetRowsParams extends Omit<IGetRowsParams, 'successCallback'> {
successCallback(rowsThisBlock: (Trade | null)[], lastRow?: number): void;
@@ -77,7 +68,7 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
field="price"
type="rightAligned"
width={130}
cellClass={changeCellClass('price')}
cellClass={changeCellClass}
valueFormatter={({
value,
data,
@@ -127,7 +118,6 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
}
return addDecimal(value, data.market.positionDecimalPlaces);
}}
cellClass={changeCellClass('size')}
/>
<AgGridColumn
headerName={t('Created at')}
+5
View File
@@ -1,3 +1,4 @@
import { Side } from '@vegaprotocol/types';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type {
@@ -47,6 +48,7 @@ export const tradesUpdateSubscription = (
size: '24',
createdAt: '2022-04-06T16:19:42.692598951Z',
marketId: 'market-0',
aggressor: Side.SIDE_BUY,
},
],
};
@@ -59,6 +61,7 @@ const trades: TradeFieldsFragment[] = [
price: '17116898',
size: '24',
createdAt: '2022-04-06T16:19:42.692598951Z',
aggressor: Side.SIDE_BUY,
market: {
id: 'market-0',
__typename: 'Market',
@@ -70,6 +73,7 @@ const trades: TradeFieldsFragment[] = [
price: '17209102',
size: '7',
createdAt: '2022-04-07T06:59:44.835686754Z',
aggressor: Side.SIDE_SELL,
market: {
id: 'market-0',
__typename: 'Market',
@@ -81,6 +85,7 @@ const trades: TradeFieldsFragment[] = [
price: '17106734',
size: '18',
createdAt: '2022-04-07T17:56:47.997938583Z',
aggressor: Side.SIDE_BUY,
market: {
id: 'market-0',
__typename: 'Market',
@@ -1,7 +1,6 @@
import { Splash } from '../splash';
import type { ReactNode } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import { isNotFoundGraphQLError } from '@vegaprotocol/apollo-client';
interface AsyncRendererProps<T> {
loading: boolean;
@@ -26,7 +25,7 @@ export function AsyncRenderer<T = object>({
children,
render,
}: AsyncRendererProps<T>) {
if (error && !isNotFoundGraphQLError(error)) {
if (error) {
return (
<Splash>
{errorMessage
@@ -9,6 +9,7 @@ import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { VegaWalletProvider } from '../provider';
import { VegaConnectDialog, CLOSE_DELAY } from './connect-dialog';
import type { VegaWalletDialogStore } from './connect-dialog';
import type { VegaConnectDialogProps } from '..';
import {
ClientErrors,
@@ -23,13 +24,15 @@ import { ChainIdDocument } from '@vegaprotocol/react-helpers';
const mockUpdateDialogOpen = jest.fn();
const mockCloseVegaDialog = jest.fn();
const mockStoreObj: Partial<VegaWalletDialogStore> = {
updateVegaWalletDialog: mockUpdateDialogOpen,
closeVegaWalletDialog: mockCloseVegaDialog,
vegaWalletDialogOpen: true,
};
jest.mock('zustand', () => ({
create: () => () => ({
updateVegaWalletDialog: mockUpdateDialogOpen,
closeVegaWalletDialog: mockCloseVegaDialog,
vegaWalletDialogOpen: true,
}),
create: () => (storeGetter: (store: VegaWalletDialogStore) => unknown) =>
storeGetter(mockStoreObj as VegaWalletDialogStore),
}));
let defaultProps: VegaConnectDialogProps;
@@ -301,7 +304,9 @@ describe('VegaConnectDialog', () => {
spyOnConnectWallet
.mockClear()
.mockImplementation(() =>
delayedReject(new WalletError('message', 3001, 'data'))
delayedReject(
new WalletError('User error', 3001, 'The user rejected the request')
)
);
render(generateJSX());
@@ -322,9 +327,9 @@ describe('VegaConnectDialog', () => {
await act(async () => {
jest.advanceTimersByTime(delay);
});
expect(screen.getByText('Connection declined')).toBeInTheDocument();
expect(screen.getByText('User error')).toBeInTheDocument();
expect(
screen.getByText('Your wallet connection was rejected')
screen.getByText('The user rejected the request')
).toBeInTheDocument();
});
@@ -9,8 +9,9 @@ import {
Loader,
} from '@vegaprotocol/ui-toolkit';
import { useCallback, useState } from 'react';
import type { WalletClientError } from '@vegaprotocol/wallet-client';
import { ExternalLinks, t, useChainIdQuery } from '@vegaprotocol/react-helpers';
import type { VegaConnector, WalletError } from '../connectors';
import type { VegaConnector } from '../connectors';
import { ViewConnector } from '../connectors';
import { JsonRpcConnector, RestConnector } from '../connectors';
import { RestConnectorForm } from './rest-connector-form';
@@ -44,7 +45,7 @@ export const useVegaWalletDialogStore = create<VegaWalletDialogStore>(
})
);
interface VegaWalletDialogStore {
export interface VegaWalletDialogStore {
vegaWalletDialogOpen: boolean;
updateVegaWalletDialog: (open: boolean) => void;
openVegaWalletDialog: () => void;
@@ -55,25 +56,25 @@ export const VegaConnectDialog = ({
connectors,
onChangeOpen,
}: VegaConnectDialogProps) => {
const {
vegaWalletDialogOpen,
closeVegaWalletDialog,
updateVegaWalletDialog,
} = useVegaWalletDialogStore((store) => ({
vegaWalletDialogOpen: store.vegaWalletDialogOpen,
updateVegaWalletDialog: onChangeOpen
const vegaWalletDialogOpen = useVegaWalletDialogStore(
(store) => store.vegaWalletDialogOpen
);
const updateVegaWalletDialog = useVegaWalletDialogStore((store) =>
onChangeOpen
? (open: boolean) => {
store.updateVegaWalletDialog(open);
onChangeOpen(open);
}
: store.updateVegaWalletDialog,
closeVegaWalletDialog: onChangeOpen
: store.updateVegaWalletDialog
);
const closeVegaWalletDialog = useVegaWalletDialogStore((store) =>
onChangeOpen
? () => {
store.closeVegaWalletDialog();
onChangeOpen(false);
}
: store.closeVegaWalletDialog,
}));
: store.closeVegaWalletDialog
);
const { data, error, loading } = useChainIdQuery();
@@ -254,7 +255,7 @@ const SelectedForm = ({
appChainId: string;
jsonRpcState: {
status: Status;
error: WalletError | null;
error: WalletClientError | null;
};
reset: () => void;
onConnect: () => void;
@@ -8,16 +8,15 @@ import {
Tick,
} from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import type { WalletClientError } from '@vegaprotocol/wallet-client';
import type { JsonRpcConnector } from '../connectors';
import { ClientErrors } from '../connectors';
import type { WalletError } from '../connectors';
import { ConnectDialogTitle } from './connect-dialog-elements';
import { Status } from '../use-json-rpc-connect';
import { useEnvironment } from '@vegaprotocol/environment';
export const ServiceErrors = {
NO_HEALTHY_NODE: 1000,
CONNECTION_DECLINED: 3001,
REQUEST_PROCESSING: -32000,
};
@@ -31,7 +30,7 @@ export const JsonRpcConnectorForm = ({
connector: JsonRpcConnector;
appChainId: string;
status: Status;
error: WalletError | null;
error: WalletClientError | null;
onConnect: () => void;
reset: () => void;
}) => {
@@ -58,7 +57,7 @@ const Connecting = ({
reset,
}: {
status: Status;
error: WalletError | null;
error: WalletClientError | null;
connector: JsonRpcConnector;
appChainId: string;
reset: () => void;
@@ -141,7 +140,7 @@ const Error = ({
appChainId,
onTryAgain,
}: {
error: WalletError | null;
error: WalletClientError | null;
connectorUrl: string | null;
appChainId: string;
onTryAgain: () => void;
@@ -158,18 +157,20 @@ const Error = ({
if (error) {
if (error.code === ClientErrors.NO_SERVICE.code) {
title = t('No wallet detected');
text = t(`No wallet application running at ${connectorUrl}`);
text = connectorUrl
? t('No wallet application running at %s', connectorUrl)
: t('No Vega Wallet application running');
} else if (error.code === ClientErrors.WRONG_NETWORK.code) {
title = t('Wrong network');
text = `To complete your wallet connection, set your wallet network in your app to "${appChainId}".`;
} else if (error.code === ServiceErrors.CONNECTION_DECLINED) {
title = t('Connection declined');
text = t('Your wallet connection was rejected');
text = t(
'To complete your wallet connection, set your wallet network in your app to "%s".',
appChainId
);
} else if (error.code === ServiceErrors.NO_HEALTHY_NODE) {
title = error.message;
title = error.title;
text = (
<>
{capitalize(error.data)}
{capitalize(error.message)}
{'. '}
{VEGA_DOCS_URL && (
<Link
@@ -188,20 +189,33 @@ const Error = ({
title = t('Wrong network');
text = (
<>
{t(`To complete your wallet connection, set your wallet network in your
app to ${appChainId}.`)}
{t(
`To complete your wallet connection, set your wallet network in your
app to %s.`,
appChainId
)}
</>
);
} else if (error.code === ClientErrors.INVALID_WALLET.code) {
title = error.title;
const errorData = error.message?.split('\n ') || [];
text = (
<span className="flex flex-col">
{errorData.map((str, i) => (
<span key={i}>{str}</span>
))}
</span>
);
} else {
title = error.message;
text = `${error.data} (${error.code})`;
title = t(error.title);
text = t(error.message);
}
}
return (
<>
<ConnectDialogTitle>{title}</ConnectDialogTitle>
<p className="text-center mb-2">{text}</p>
<p className="text-center mb-2 first-letter:uppercase">{text}</p>
{tryAgain}
</>
);
@@ -8,8 +8,6 @@ const VERSION = 'v2';
export const ClientErrors = {
NO_SERVICE: new WalletError(t('No service'), 100),
NO_TOKEN: new WalletError(t('No token'), 101),
INVALID_RESPONSE: new WalletError(t('Something went wrong'), 102),
INVALID_WALLET: new WalletError(t('Wallet version invalid'), 103),
WRONG_NETWORK: new WalletError(
t('Wrong network'),
@@ -22,11 +20,6 @@ export const ClientErrors = {
t('Unknown error occurred')
),
NO_CLIENT: new WalletError(t('No client found.'), 106),
REQUEST_REJECTED: new WalletError(
t('Request rejected'),
107,
t('The request has been rejected by the user')
),
} as const;
export class JsonRpcConnector implements VegaConnector {
@@ -70,7 +63,9 @@ export class JsonRpcConnector implements VegaConnector {
}),
});
}
get url() {
return this._url || '';
}
async getChainId() {
if (!this.client) {
throw ClientErrors.NO_CLIENT;
@@ -79,7 +74,12 @@ export class JsonRpcConnector implements VegaConnector {
const { result } = await this.client.GetChainId();
return result;
} catch (err) {
throw ClientErrors.INVALID_RESPONSE;
const {
code = ClientErrors.UNKNOWN.code,
message = ClientErrors.UNKNOWN.message,
title,
} = err as WalletClientError;
throw new WalletError(title, code, message);
}
}
@@ -92,11 +92,12 @@ export class JsonRpcConnector implements VegaConnector {
await this.client.ConnectWallet();
return null;
} catch (err) {
const clientErr =
err instanceof WalletClientError && err.code === 3001
? ClientErrors.REQUEST_REJECTED
: ClientErrors.INVALID_RESPONSE;
throw clientErr;
const {
code = ClientErrors.UNKNOWN.code,
message = ClientErrors.UNKNOWN.message,
title,
} = err as WalletClientError;
throw new WalletError(title, code, message);
}
}
@@ -111,7 +112,12 @@ export class JsonRpcConnector implements VegaConnector {
const { result } = await this.client.ListKeys();
return result.keys;
} catch (err) {
throw ClientErrors.INVALID_RESPONSE;
const {
code = ClientErrors.UNKNOWN.code,
message = ClientErrors.UNKNOWN.message,
title,
} = err as WalletClientError;
throw new WalletError(title, code, message);
}
}
@@ -147,15 +153,21 @@ export class JsonRpcConnector implements VegaConnector {
try {
const result = await fetch(`${this._url}/api/${this.version}/methods`);
if (!result.ok) {
const err = ClientErrors.INVALID_WALLET;
err.data = t(
`The wallet running at ${this._url} is not supported. Required version is ${this.version}`
const sent1 = t(
'The version of the wallet service running at %s is not supported.',
this._url as string
);
throw err;
const sent2 = t(
'Update the wallet software to a version that expose the API %s.',
this.version
);
const data = `${sent1}\n ${sent2}`;
const title = t('Wallet version invalid');
throw new WalletError(title, ClientErrors.INVALID_WALLET.code, data);
}
return true;
} catch (err) {
if (err instanceof WalletError) {
if (err instanceof WalletClientError) {
throw err;
}
+5 -7
View File
@@ -1,3 +1,4 @@
import { WalletClientError } from '@vegaprotocol/wallet-client';
import type * as Schema from '@vegaprotocol/types';
export interface DelegateSubmissionBody {
@@ -329,14 +330,11 @@ export interface TransactionResponse {
receivedAt: string;
sentAt: string;
}
export class WalletError {
message: string;
code: number;
data?: string;
export class WalletError extends WalletClientError {
data: string;
constructor(message: string, code: number, data?: string) {
this.message = message;
this.code = code;
constructor(message: string, code: number, data = 'Wallet error') {
super({ code, message, data });
this.data = data;
}
}
+2 -2
View File
@@ -1,6 +1,7 @@
import { LocalStorage } from '@vegaprotocol/react-helpers';
import type { ReactNode } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { WalletClientError } from '@vegaprotocol/wallet-client';
import type { VegaWalletContextShape } from '.';
import type {
PubKey,
@@ -9,7 +10,6 @@ import type {
} from './connectors/vega-connector';
import { VegaWalletContext } from './context';
import { WALLET_KEY } from './storage';
import { WalletError } from './connectors/vega-connector';
import { ViewConnector } from './connectors';
interface VegaWalletProviderProps {
@@ -53,7 +53,7 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
return null;
}
} catch (err) {
if (err instanceof WalletError) {
if (err instanceof WalletClientError) {
throw err;
}
return null;
+3 -3
View File
@@ -1,7 +1,7 @@
import { useCallback, useState } from 'react';
import { WalletClientError } from '@vegaprotocol/wallet-client';
import type { JsonRpcConnector } from './connectors';
import { ClientErrors } from './connectors';
import { WalletError } from './connectors';
import { useVegaWallet } from './use-vega-wallet';
export enum Status {
@@ -18,7 +18,7 @@ export enum Status {
export const useJsonRpcConnect = (onConnect: () => void) => {
const { connect } = useVegaWallet();
const [status, setStatus] = useState(Status.Idle);
const [error, setError] = useState<WalletError | null>(null);
const [error, setError] = useState<WalletClientError | null>(null);
const attemptConnect = useCallback(
async (connector: JsonRpcConnector, appChainId: string) => {
@@ -56,7 +56,7 @@ export const useJsonRpcConnect = (onConnect: () => void) => {
setStatus(Status.Connected);
onConnect();
} catch (err) {
if (err instanceof WalletError) {
if (err instanceof WalletClientError) {
setError(err);
}
setStatus(Status.Error);
@@ -1,7 +1,6 @@
import { useVegaWallet } from './use-vega-wallet';
import { useEffect, useRef } from 'react';
import { ClientErrors } from './connectors';
import { WalletError } from './connectors';
import { VegaTxStatus } from './use-vega-transaction';
import { useVegaTransactionStore } from './use-vega-transaction-store';
import { WalletClientError } from '@vegaprotocol/wallet-client';
@@ -40,10 +39,7 @@ export const useVegaTransactionManager = () => {
})
.catch((err) => {
update(transaction.id, {
error:
err instanceof WalletError || err instanceof WalletClientError
? err
: ClientErrors.UNKNOWN,
error: err instanceof WalletClientError ? err : ClientErrors.UNKNOWN,
status: VegaTxStatus.Error,
});
});
@@ -98,10 +98,14 @@ describe('useVegaTransaction', () => {
});
expect(result.current.transaction.status).toEqual(VegaTxStatus.Error);
expect(result.current.transaction.error).toHaveProperty(
'message',
'title',
'Something went wrong'
);
expect(result.current.transaction.error).toHaveProperty('code', 105);
expect(result.current.transaction.error).toHaveProperty(
'message',
'Unknown error occurred'
);
expect(result.current.transaction.error).toHaveProperty(
'data',
'Unknown error occurred'
+3 -10
View File
@@ -6,8 +6,6 @@ import { VegaTransactionDialog } from './vega-transaction-dialog';
import type { Intent } from '@vegaprotocol/ui-toolkit';
import type { Transaction } from './connectors';
import { ClientErrors } from './connectors';
import { WalletError } from './connectors';
import type { WalletClientError } from '@vegaprotocol/wallet-client';
export interface DialogProps {
intent?: Intent;
@@ -26,7 +24,7 @@ export enum VegaTxStatus {
export interface VegaTxState {
status: VegaTxStatus;
error: WalletError | WalletClientError | Error | null;
error: Error | null;
txHash: string | null;
signature: string | null;
dialogOpen: boolean;
@@ -90,14 +88,9 @@ export const useVegaTransaction = () => {
return null;
} catch (err) {
const error =
err instanceof WalletError
? err
: err instanceof Error
? err
: ClientErrors.UNKNOWN;
const error = err instanceof Error ? err : ClientErrors.UNKNOWN;
setTransaction({
error: error,
error,
status: VegaTxStatus.Error,
});
return null;
+3 -3
View File
@@ -10,9 +10,9 @@ export function useVegaWallet() {
}
export function useReconnectVegaWallet() {
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const { disconnect } = useVegaWallet();
const reconnect = useCallback(async () => {
await disconnect();
@@ -2,7 +2,7 @@ import { Networks, useEnvironment } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/react-helpers';
import { Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import { WalletError } from '../connectors';
import { WalletClientError } from '@vegaprotocol/wallet-client';
import type { VegaTxState } from '../use-vega-transaction';
import { VegaTxStatus } from '../use-vega-transaction';
@@ -107,18 +107,13 @@ export const VegaDialog = ({ transaction }: VegaDialogProps) => {
}
if (transaction.status === VegaTxStatus.Error) {
content = (
<div data-testid={transaction.status}>
{transaction.error instanceof WalletError && (
<p>
{transaction.error.message}: {transaction.error.data}
</p>
)}
{transaction.error instanceof Error && (
<p>{transaction.error.message}</p>
)}
</div>
);
let messageText = '';
if (transaction.error instanceof WalletClientError) {
messageText = `${transaction.error.title}: ${transaction.error.message}`;
} else if (transaction.error instanceof Error) {
messageText = transaction.error.message;
}
content = <div data-testid={transaction.status}>{messageText}</div>;
}
if (transaction.status === VegaTxStatus.Pending) {
+1 -1
View File
@@ -8,6 +8,6 @@ export * from './lib/withdrawal-feedback';
export * from './lib/use-complete-withdraw';
export * from './lib/use-create-withdraw';
export * from './lib/use-verify-withdrawal';
export * from './lib/use-withdrawals';
export * from './lib/withdrawals-provider';
export * from './lib/__generated__/Withdrawal';
export * from './lib/__generated__/Erc20Approval';
@@ -1,170 +0,0 @@
import * as Schema from '@vegaprotocol/types';
import { generateWithdrawal } from './test-helpers';
import { updateQuery } from './use-withdrawals';
import type {
WithdrawalsQuery,
WithdrawalEventSubscription,
WithdrawalFieldsFragment,
} from './__generated__/Withdrawal';
describe('updateQuery', () => {
it('updates existing withdrawals', () => {
const withdrawal = generateWithdrawal({
id: '1',
status: Schema.WithdrawalStatus.STATUS_OPEN,
});
const withdrawalUpdate = generateWithdrawal({
id: '1',
status: Schema.WithdrawalStatus.STATUS_FINALIZED,
});
const prev = mockQuery([withdrawal]);
const incoming = mockSub([withdrawalUpdate]);
expect(updateQuery(prev, incoming)).toEqual({
party: {
__typename: 'Party',
id: 'party-id',
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges: [
{
node: withdrawalUpdate,
},
],
},
},
});
});
it('Adds new withdrawals', () => {
const withdrawal = generateWithdrawal({
id: '1',
amount: '100',
});
const withdrawalUpdate = generateWithdrawal({
id: '2',
amount: '200',
});
const prev = mockQuery([withdrawal]);
const incoming = mockSub([withdrawalUpdate]);
expect(updateQuery(prev, incoming)).toEqual({
party: {
__typename: 'Party',
id: 'party-id',
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges: [
{
node: withdrawalUpdate,
},
{
node: withdrawal,
},
],
},
},
});
});
it('creates new party if not present', () => {
const partyId = 'party-id';
const withdrawalUpdate = generateWithdrawal({
id: '2',
});
const incoming = mockSub([withdrawalUpdate]);
expect(
updateQuery({ party: null }, { ...incoming, variables: { partyId } })
).toEqual({
party: {
__typename: 'Party',
id: partyId,
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges: [
{
node: withdrawalUpdate,
},
],
},
},
});
});
it('Handles updates and inserts simultaneously', () => {
const withdrawal1 = generateWithdrawal({
id: '1',
status: Schema.WithdrawalStatus.STATUS_OPEN,
});
const withdrawal2 = generateWithdrawal({
id: '2',
});
const withdrawalUpdate = generateWithdrawal({
id: '1',
status: Schema.WithdrawalStatus.STATUS_FINALIZED,
});
const withdrawalNew = generateWithdrawal({
id: '3',
});
const prev = mockQuery([withdrawal1, withdrawal2]);
const incoming = mockSub([withdrawalUpdate, withdrawalNew]);
expect(updateQuery(prev, incoming)).toEqual({
party: {
__typename: 'Party',
id: 'party-id',
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges: [
{
node: withdrawalUpdate,
},
{
node: withdrawalNew,
},
{
node: withdrawal2,
},
],
},
},
});
});
});
const mockQuery = (
withdrawals: WithdrawalFieldsFragment[]
): WithdrawalsQuery => {
return {
party: {
__typename: 'Party',
id: 'party-id',
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges: withdrawals.map((w) => ({
node: w,
})),
},
},
};
};
const mockSub = (
withdrawals: WithdrawalFieldsFragment[]
): {
subscriptionData: {
data: WithdrawalEventSubscription;
};
} => {
return {
subscriptionData: {
data: {
busEvents: withdrawals.map((w) => ({
__typename: 'BusEvent',
event: w,
})),
},
},
};
};
-111
View File
@@ -1,111 +0,0 @@
import type { UpdateQueryFn } from '@apollo/client/core/watchQueryOptions';
import { useVegaWallet } from '@vegaprotocol/wallet';
import uniqBy from 'lodash/uniqBy';
import { useEffect } from 'react';
import {
useWithdrawalsQuery,
WithdrawalEventDocument,
} from './__generated__/Withdrawal';
import type {
WithdrawalsQuery,
WithdrawalFieldsFragment,
WithdrawalEventSubscription,
WithdrawalEventSubscriptionVariables,
} from './__generated__/Withdrawal';
import { removePaginationWrapper } from '@vegaprotocol/react-helpers';
type WithdrawalEdges = { node: WithdrawalFieldsFragment }[];
export const useWithdrawals = () => {
const { pubKey } = useVegaWallet();
const { data, loading, error, subscribeToMore } = useWithdrawalsQuery({
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
useEffect(() => {
if (!pubKey) return;
const unsubscribe = subscribeToMore<
WithdrawalEventSubscription,
WithdrawalEventSubscriptionVariables
>({
document: WithdrawalEventDocument,
variables: { partyId: pubKey },
updateQuery,
});
return () => {
unsubscribe();
};
}, [pubKey, subscribeToMore]);
return {
data: removePaginationWrapper(
data?.party?.withdrawalsConnection?.edges ?? []
).sort((a, b) => {
if (!b.txHash !== !a.txHash) {
return b.txHash ? -1 : 1;
}
return (
b.txHash ? b.withdrawnTimestamp : b.createdTimestamp
).localeCompare(a.txHash ? a.withdrawnTimestamp : a.createdTimestamp);
}),
loading,
error,
};
};
export const updateQuery: UpdateQueryFn<
WithdrawalsQuery,
WithdrawalEventSubscriptionVariables,
WithdrawalEventSubscription
> = (prev, { subscriptionData, variables }) => {
if (!subscriptionData.data.busEvents?.length) {
return prev;
}
const curr = prev.party?.withdrawalsConnection?.edges || [];
const incoming = subscriptionData.data.busEvents.reduce<WithdrawalEdges>(
(acc, event) => {
if (event.event.__typename === 'Withdrawal') {
acc.push({
node: {
...event.event,
pendingOnForeignChain: false,
},
});
}
return acc;
},
[]
);
const edges = uniqBy([...incoming, ...curr], 'node.id');
// Write new party to cache if not present
if (!prev.party) {
return {
...prev,
party: {
id: variables?.partyId,
__typename: 'Party',
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges,
},
},
} as WithdrawalsQuery;
}
return {
...prev,
party: {
...prev.party,
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges,
},
},
};
};
@@ -0,0 +1,56 @@
import uniqBy from 'lodash/uniqBy';
import { makeDataProvider } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import {
WithdrawalsDocument,
WithdrawalEventDocument,
} from './__generated__/Withdrawal';
import type {
WithdrawalsQuery,
WithdrawalFieldsFragment,
WithdrawalEventSubscription,
WithdrawalEventSubscriptionVariables,
} from './__generated__/Withdrawal';
import {
removePaginationWrapper,
getEvents,
} from '@vegaprotocol/react-helpers';
const sortWithdrawals = (data: WithdrawalFieldsFragment[]) =>
data.sort((a, b) => {
if (!b.txHash !== !a.txHash) {
return b.txHash ? -1 : 1;
}
return (b.txHash ? b.withdrawnTimestamp : b.createdTimestamp).localeCompare(
a.txHash ? a.withdrawnTimestamp : a.createdTimestamp
);
});
export const withdrawalProvider = makeDataProvider<
WithdrawalsQuery,
WithdrawalFieldsFragment[],
WithdrawalEventSubscription,
WithdrawalEventSubscription,
WithdrawalEventSubscriptionVariables
>({
query: WithdrawalsDocument,
subscriptionQuery: WithdrawalEventDocument,
getData: (data: WithdrawalsQuery | null) =>
sortWithdrawals(
removePaginationWrapper(data?.party?.withdrawalsConnection?.edges || [])
),
getDelta: (data: WithdrawalEventSubscription) => data,
update: (
data: WithdrawalFieldsFragment[] | null,
delta: WithdrawalEventSubscription
) => {
if (!delta.busEvents?.length) {
return data;
}
const incoming = getEvents<WithdrawalFieldsFragment>(
Schema.BusEventType.Withdrawal,
delta.busEvents
);
return uniqBy([...incoming, ...(data || [])], 'id');
},
});
+1 -1
View File
@@ -36,7 +36,7 @@
"@sentry/nextjs": "^6.19.3",
"@sentry/react": "^6.19.2",
"@sentry/tracing": "^6.19.2",
"@vegaprotocol/wallet-client": "0.1.8",
"@vegaprotocol/wallet-client": "0.1.9",
"@walletconnect/ethereum-provider": "^1.7.5",
"@web3-react/core": "8.0.20-beta.0",
"@web3-react/metamask": "8.0.16-beta.0",
+4 -4
View File
@@ -7321,10 +7321,10 @@
"@typescript-eslint/types" "5.40.0"
eslint-visitor-keys "^3.3.0"
"@vegaprotocol/wallet-client@0.1.8":
version "0.1.8"
resolved "https://registry.yarnpkg.com/@vegaprotocol/wallet-client/-/wallet-client-0.1.8.tgz#38ca8566d78b9f6694b12ad9364bb34d6482935d"
integrity sha512-FVvDvvlccKyXn0ujhivPUCVnkZYQJxtI1q8OgipNnbmAjU1mLyeuRTBw0Isu330yPI1KppNbW6Qicd8OTHBmxw==
"@vegaprotocol/wallet-client@0.1.9":
version "0.1.9"
resolved "https://registry.yarnpkg.com/@vegaprotocol/wallet-client/-/wallet-client-0.1.9.tgz#8c6a71c8b2222b3de5d73cade8fc6db57e332de9"
integrity sha512-oacfJGT0zHM+1If4I/pgIWi7zzU/3uHy4+sjuFEh8pWI8bWBzCF+mHbOGA6iTM+5/5mhayMFc+YZ9GexH1sBnQ==
dependencies:
express "4.18.2"
nanoid "3.3.4"