diff --git a/.github/workflows/capsule-cypress-nightly.yml b/.github/workflows/capsule-cypress-night-run.yml similarity index 99% rename from .github/workflows/capsule-cypress-nightly.yml rename to .github/workflows/capsule-cypress-night-run.yml index c13468c35..b77e23636 100644 --- a/.github/workflows/capsule-cypress-nightly.yml +++ b/.github/workflows/capsule-cypress-night-run.yml @@ -1,4 +1,4 @@ -name: Capsule tests -- nightly +name: Capsule tests -- night run # This workflow runs the frontend tests against latest develop of the core to preempt breaking changes diff --git a/apps/explorer-e2e/.env b/apps/explorer-e2e/.env index 5577c7a3e..cda5965c4 100644 --- a/apps/explorer-e2e/.env +++ b/apps/explorer-e2e/.env @@ -12,6 +12,7 @@ NX_EXPLORER_ASSETS=1 NX_EXPLORER_GENESIS=1 NX_EXPLORER_GOVERNANCE=1 NX_EXPLORER_MARKETS=1 +NX_EXPLORER_ORACLES=1 NX_EXPLORER_TXS_LIST=0 NX_EXPLORER_NETWORK_PARAMETERS=1 NX_EXPLORER_PARTIES=1 diff --git a/apps/explorer-e2e/.env.devnet b/apps/explorer-e2e/.env.devnet index 3065f3c3a..0956ee9f3 100644 --- a/apps/explorer-e2e/.env.devnet +++ b/apps/explorer-e2e/.env.devnet @@ -11,6 +11,7 @@ NX_EXPLORER_ASSETS=1 NX_EXPLORER_GENESIS=1 NX_EXPLORER_GOVERNANCE=1 NX_EXPLORER_MARKETS=1 +NX_EXPLORER_ORACLES=1 NX_EXPLORER_TXS_LIST=1 NX_EXPLORER_NETWORK_PARAMETERS=1 NX_EXPLORER_PARTIES=1 diff --git a/apps/explorer-e2e/.env.stagnet1 b/apps/explorer-e2e/.env.stagnet1 index 1d833d3c9..19d6fd050 100644 --- a/apps/explorer-e2e/.env.stagnet1 +++ b/apps/explorer-e2e/.env.stagnet1 @@ -11,6 +11,7 @@ NX_EXPLORER_ASSETS=1 NX_EXPLORER_GENESIS=1 NX_EXPLORER_GOVERNANCE=1 NX_EXPLORER_MARKETS=1 +NX_EXPLORER_ORACLES=1 NX_EXPLORER_TXS_LIST=1 NX_EXPLORER_NETWORK_PARAMETERS=1 NX_EXPLORER_PARTIES=1 diff --git a/apps/explorer-e2e/.env.stagnet2 b/apps/explorer-e2e/.env.stagnet2 index 4352d0666..c55daeed5 100644 --- a/apps/explorer-e2e/.env.stagnet2 +++ b/apps/explorer-e2e/.env.stagnet2 @@ -11,6 +11,7 @@ NX_EXPLORER_ASSETS=1 NX_EXPLORER_GENESIS=1 NX_EXPLORER_GOVERNANCE=1 NX_EXPLORER_MARKETS=1 +NX_EXPLORER_ORACLES=1 NX_EXPLORER_TXS_LIST=1 NX_EXPLORER_NETWORK_PARAMETERS=1 NX_EXPLORER_PARTIES=1 diff --git a/apps/explorer-e2e/.env.testnet b/apps/explorer-e2e/.env.testnet index add164e7a..3b5c6f537 100644 --- a/apps/explorer-e2e/.env.testnet +++ b/apps/explorer-e2e/.env.testnet @@ -11,6 +11,7 @@ NX_EXPLORER_ASSETS=1 NX_EXPLORER_GENESIS=1 NX_EXPLORER_GOVERNANCE=1 NX_EXPLORER_MARKETS=1 +NX_EXPLORER_ORACLES=1 NX_EXPLORER_TXS_LIST=1 NX_EXPLORER_NETWORK_PARAMETERS=1 NX_EXPLORER_PARTIES=1 diff --git a/apps/explorer/.env b/apps/explorer/.env index b6b59b054..844001e0f 100644 --- a/apps/explorer/.env +++ b/apps/explorer/.env @@ -23,4 +23,5 @@ NX_EXPLORER_NETWORK_PARAMETERS=1 NX_EXPLORER_PARTIES=1 NX_EXPLORER_VALIDATORS=1 NX_EXPLORER_MARKETS=1 +NX_EXPLORER_ORACLES=1 NX_EXPLORER_TXS_LIST=1 diff --git a/apps/explorer/.env.capsule b/apps/explorer/.env.capsule index e603825a6..302407622 100644 --- a/apps/explorer/.env.capsule +++ b/apps/explorer/.env.capsule @@ -13,6 +13,7 @@ NX_EXPLORER_ASSETS=1 NX_EXPLORER_GENESIS=1 NX_EXPLORER_GOVERNANCE=1 NX_EXPLORER_MARKETS=1 +NX_EXPLORER_ORACLES=1 NX_EXPLORER_TXS_LIST=1 NX_EXPLORER_NETWORK_PARAMETERS=1 NX_EXPLORER_PARTIES=1 diff --git a/apps/explorer/README.md b/apps/explorer/README.md index 0a8e8cc7c..f16ef2536 100644 --- a/apps/explorer/README.md +++ b/apps/explorer/README.md @@ -58,6 +58,7 @@ There are a few different configuration options offered for this app: | `NX_EXPLORER_GENESIS` | Enable the genesis page for the explorer | | `NX_EXPLORER_GOVERNANCE` | Enable the governance page for the explorer | | `NX_EXPLORER_MARKETS` | Enable the markets page for the explorer | +| `NX_EXPLORER_ORACLES` | Enable the oracles page for the explorer | | `NX_EXPLORER_TXS_LIST` | Enable the transactions list page for the explorer | | `NX_EXPLORER_NETWORK_PARAMETERS` | Enable the network parameters page for the explorer | | `NX_EXPLORER_PARTIES` | Enable the parties page for the explorer | diff --git a/apps/explorer/src/app/components/blocks/block-data.tsx b/apps/explorer/src/app/components/blocks/block-data.tsx index b392892e3..8092f8275 100644 --- a/apps/explorer/src/app/components/blocks/block-data.tsx +++ b/apps/explorer/src/app/components/blocks/block-data.tsx @@ -2,7 +2,9 @@ import React from 'react'; import type { BlockMeta } from '../../routes/blocks/tendermint-blockchain-response'; import { Routes } from '../../routes/route-names'; import { Link } from 'react-router-dom'; -import { SecondsAgo } from '../seconds-ago'; +import { getDateTimeFormat } from '@vegaprotocol/react-helpers'; +import { Tooltip } from '@vegaprotocol/ui-toolkit'; +import { TimeAgo } from '../time-ago'; import { TableWithTbody, TableRow, TableCell } from '../table'; import { t } from '@vegaprotocol/react-helpers'; @@ -53,7 +55,16 @@ export const BlockData = ({ block, className }: BlockProps) => { className="text-center pr-28 text-neutral-300 w-[170px]" aria-label={t('Block genesis')} > - + + {/*For some reason we get forwardRef errors if we pass in the TimeAgo component directly*/} + + + + diff --git a/apps/explorer/src/app/components/jump-to-block/index.tsx b/apps/explorer/src/app/components/jump-to-block/index.tsx index f6c6e5cfc..41396344d 100644 --- a/apps/explorer/src/app/components/jump-to-block/index.tsx +++ b/apps/explorer/src/app/components/jump-to-block/index.tsx @@ -29,6 +29,7 @@ export const JumpToBlock = () => { inputType="number" inputName="blockNumber" submitHandler={handleSubmit} + inputMin={1} /> ); }; diff --git a/apps/explorer/src/app/components/jump-to/index.tsx b/apps/explorer/src/app/components/jump-to/index.tsx index c00f47de8..79d1ac21b 100644 --- a/apps/explorer/src/app/components/jump-to/index.tsx +++ b/apps/explorer/src/app/components/jump-to/index.tsx @@ -9,6 +9,8 @@ interface JumpToProps { inputType: HTMLInputTypeAttribute; inputName: string; submitHandler: (arg0: SyntheticEvent) => void; + inputMin?: string | number; + inputMax?: string | number; } export const JumpTo = ({ @@ -18,6 +20,8 @@ export const JumpTo = ({ inputType, inputName, submitHandler, + inputMin, + inputMax, }: JumpToProps) => { return (
@@ -35,6 +39,8 @@ export const JumpTo = ({ name={inputName} placeholder={placeholder} className="max-w-[200px]" + min={inputMin} + max={inputMax} /> - - - Mined by - - - - - - Time - - - - - - {blockData && blockData.result.block.data.txs.length > 0 ? ( - - ) : null} + {blockData && ( + <> + + + Mined by + + + + + + Time + + {' '} + -{' '} + {getDateTimeFormat().format( + new Date(blockData.result.block.header.time) + )} + + + + {blockData.result.block.data.txs.length > 0 ? ( + + ) : null} + + )} diff --git a/apps/explorer/src/app/routes/governance/index.tsx b/apps/explorer/src/app/routes/governance/index.tsx index 659cdcd82..b64bed16f 100644 --- a/apps/explorer/src/app/routes/governance/index.tsx +++ b/apps/explorer/src/app/routes/governance/index.tsx @@ -108,7 +108,9 @@ const Governance = () => { if (!data) return null; return (
- {t('Governance')} + + {t('Governance Proposals')} + {data.proposals?.map((p) => ( {getProposalName(p.terms.change)} diff --git a/apps/explorer/src/app/routes/network-parameters/network-parameters.spec.tsx b/apps/explorer/src/app/routes/network-parameters/network-parameters.spec.tsx index 7d9f3445e..db966e9dc 100644 --- a/apps/explorer/src/app/routes/network-parameters/network-parameters.spec.tsx +++ b/apps/explorer/src/app/routes/network-parameters/network-parameters.spec.tsx @@ -24,10 +24,10 @@ describe('NetworkParametersTable', () => { ); const rows = screen.getAllByTestId('key-value-table-row'); expect(rows[0].children[0]).toHaveTextContent( - 'Market Fee Factors Infrastructure Fee' + 'market.fee.factors.infrastructureFee' ); expect(rows[1].children[0]).toHaveTextContent( - 'Market Liquidity Provision Min Lp Stake Quantum Multiple' + 'market.liquidityProvision.minLpStakeQuantumMultiple' ); expect(rows[0].children[1]).toHaveTextContent('0.0005'); expect(rows[1].children[1]).toHaveTextContent('1'); @@ -54,10 +54,10 @@ describe('NetworkParametersTable', () => { ); const rows = screen.getAllByTestId('key-value-table-row'); expect(rows[0].children[0]).toHaveTextContent( - 'Market Fee Factors Infrastructure Fee' + 'market.fee.factors.infrastructureFee' ); expect(rows[1].children[0]).toHaveTextContent( - 'Market Liquidity Provision Min Lp Stake Quantum Multiple' + 'market.liquidityProvision.minLpStakeQuantumMultiple' ); expect(rows[0].children[1]).toHaveTextContent('0.0005'); expect(rows[1].children[1]).toHaveTextContent('1'); diff --git a/apps/explorer/src/app/routes/network-parameters/network-parameters.tsx b/apps/explorer/src/app/routes/network-parameters/network-parameters.tsx index 47c859ebb..f6466b805 100644 --- a/apps/explorer/src/app/routes/network-parameters/network-parameters.tsx +++ b/apps/explorer/src/app/routes/network-parameters/network-parameters.tsx @@ -16,7 +16,6 @@ import type { NetworkParametersQuery_networkParameters, } from './__generated__/NetworkParametersQuery'; import orderBy from 'lodash/orderBy'; -import startCase from 'lodash/startCase'; const BIG_NUMBER_PARAMS = [ 'spam.protection.delegation.min.tokens', @@ -42,8 +41,15 @@ export const renderRow = ({ }: NetworkParametersQuery_networkParameters) => { const isSyntaxRow = isJsonObject(value); return ( - - {startCase(key)} + + {key} {isSyntaxRow ? ( ) : isNaN(Number(value)) ? ( diff --git a/apps/explorer/src/app/routes/oracles/__generated__/OracleSpecs.ts b/apps/explorer/src/app/routes/oracles/__generated__/OracleSpecs.ts new file mode 100644 index 000000000..676ebde9b --- /dev/null +++ b/apps/explorer/src/app/routes/oracles/__generated__/OracleSpecs.ts @@ -0,0 +1,97 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { OracleSpecStatus, PropertyKeyType, ConditionOperator } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: OracleSpecs +// ==================================================== + +export interface OracleSpecs_oracleSpecs_filters_key { + __typename: "PropertyKey"; + /** + * name is the name of the property. + */ + name: string | null; + /** + * type is the type of the property. + */ + type: PropertyKeyType; +} + +export interface OracleSpecs_oracleSpecs_filters_conditions { + __typename: "Condition"; + /** + * value is used by the comparator. + */ + value: string | null; + /** + * comparator is the type of comparison to make on the value. + */ + operator: ConditionOperator; +} + +export interface OracleSpecs_oracleSpecs_filters { + __typename: "Filter"; + /** + * key is the oracle data property key targeted by the filter. + */ + key: OracleSpecs_oracleSpecs_filters_key; + /** + * conditions are the conditions that should be matched by the data to be + * considered of interest. + */ + conditions: OracleSpecs_oracleSpecs_filters_conditions[] | null; +} + +export interface OracleSpecs_oracleSpecs_data { + __typename: "OracleData"; + /** + * pubKeys is the list of public keys that signed the data + */ + pubKeys: string[] | null; +} + +export interface OracleSpecs_oracleSpecs { + __typename: "OracleSpec"; + /** + * status describes the status of the oracle spec + */ + status: OracleSpecStatus; + /** + * id is a hash generated from the OracleSpec data. + */ + id: string; + /** + * RFC3339Nano creation date time + */ + createdAt: string; + /** + * RFC3339Nano last updated timestamp + */ + updatedAt: string | null; + /** + * pubKeys is the list of authorized public keys that signed the data for this + * oracle. All the public keys in the oracle data should be contained in these + * public keys. + */ + pubKeys: string[] | null; + /** + * filters describes which oracle data are considered of interest or not for + * the product (or the risk model). + */ + filters: OracleSpecs_oracleSpecs_filters[] | null; + /** + * data list all the oracle data broadcast to this spec + */ + data: OracleSpecs_oracleSpecs_data[]; +} + +export interface OracleSpecs { + /** + * All registered oracle specs + */ + oracleSpecs: OracleSpecs_oracleSpecs[] | null; +} diff --git a/apps/explorer/src/app/routes/oracles/index.tsx b/apps/explorer/src/app/routes/oracles/index.tsx new file mode 100644 index 000000000..a9a78ee0f --- /dev/null +++ b/apps/explorer/src/app/routes/oracles/index.tsx @@ -0,0 +1,53 @@ +import { gql, useQuery } from '@apollo/client'; +import type { OracleSpecs as OracleSpecsQuery } from './__generated__/OracleSpecs'; + +import React from 'react'; +import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; +import { RouteTitle } from '../../components/route-title'; +import { t } from '@vegaprotocol/react-helpers'; +import { SubHeading } from '../../components/sub-heading'; + +const ORACLE_SPECS_QUERY = gql` + query OracleSpecs { + oracleSpecs { + status + id + createdAt + updatedAt + pubKeys + filters { + key { + name + type + } + conditions { + value + operator + } + } + data { + pubKeys + } + } + } +`; + +const Oracles = () => { + const { data } = useQuery(ORACLE_SPECS_QUERY); + + return ( +
+ {t('Oracles')} + {data?.oracleSpecs + ? data.oracleSpecs.map((o) => ( + + {o.id} + + + )) + : null} +
+ ); +}; + +export default Oracles; diff --git a/apps/explorer/src/app/routes/route-names.tsx b/apps/explorer/src/app/routes/route-names.tsx index 0cf20aa2a..3abbc1572 100644 --- a/apps/explorer/src/app/routes/route-names.tsx +++ b/apps/explorer/src/app/routes/route-names.tsx @@ -8,5 +8,6 @@ export const Routes = { GENESIS: 'genesis', GOVERNANCE: 'governance', MARKETS: 'markets', + ORACLES: 'oracles', NETWORK_PARAMETERS: 'network-parameters', }; diff --git a/apps/explorer/src/app/routes/router-config.tsx b/apps/explorer/src/app/routes/router-config.tsx index 7117c326f..b8602ff31 100644 --- a/apps/explorer/src/app/routes/router-config.tsx +++ b/apps/explorer/src/app/routes/router-config.tsx @@ -3,6 +3,7 @@ import BlockPage from './blocks'; import Governance from './governance'; import Home from './home'; import Markets from './markets'; +import Oracles from './oracles'; import Party from './parties'; import { Parties } from './parties/home'; import { Party as PartySingle } from './parties/id'; @@ -66,8 +67,8 @@ const governanceRoutes = flags.governance ? [ { path: Routes.GOVERNANCE, - name: 'Governance', - text: t('Proposals'), + name: 'Governance proposals', + text: t('Governance Proposals'), element: , }, ] @@ -84,6 +85,17 @@ const marketsRoutes = flags.markets ] : []; +const oraclesRoutes = flags.oracles + ? [ + { + path: Routes.ORACLES, + name: 'Oracles', + text: t('Oracles'), + element: , + }, + ] + : []; + const networkParametersRoutes = flags.networkParameters ? [ { @@ -154,6 +166,7 @@ const routerConfig = [ ...genesisRoutes, ...governanceRoutes, ...marketsRoutes, + ...oraclesRoutes, ...networkParametersRoutes, ...validators, ]; diff --git a/apps/simple-trading-app-e2e/.env b/apps/simple-trading-app-e2e/.env index 16bb3ce7c..979b577d6 100644 --- a/apps/simple-trading-app-e2e/.env +++ b/apps/simple-trading-app-e2e/.env @@ -24,3 +24,4 @@ NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL NX_VEGA_URL=https://lb.testnet.vega.xyz/query NX_VEGA_ENV=TESTNET NX_VEGA_REST=https://lb.testnet.vega.xyz/datanode/rest +NX_VEGA_WALLET_URL=http://localhost:1789/api/v1 diff --git a/apps/simple-trading-app-e2e/cypress.config.js b/apps/simple-trading-app-e2e/cypress.config.js index 1436e587c..889458a22 100644 --- a/apps/simple-trading-app-e2e/cypress.config.js +++ b/apps/simple-trading-app-e2e/cypress.config.js @@ -18,4 +18,22 @@ module.exports = defineConfig({ viewportWidth: 1440, viewportHeight: 900, }, + env: { + TRADING_TEST_VEGA_WALLET_NAME: 'UI_Trading_Test', + ETHEREUM_PROVIDER_URL: + 'https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8', + VEGA_PUBLIC_KEY: + '47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278', + VEGA_PUBLIC_KEY2: + '1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4', + TRUNCATED_VEGA_PUBLIC_KEY: '47836c…c7d278', + TRUNCATED_VEGA_PUBLIC_KEY2: '1a18cd…0cf2e4', + ETHEREUM_WALLET_ADDRESS: '0x265Cc6d39a1B53d0d92068443009eE7410807158', + ETHERSCAN_URL: 'https://ropsten.etherscan.io', + tsConfig: 'tsconfig.json', + TAGS: 'not @todo and not @ignore and not @manual', + TRADING_TEST_VEGA_WALLET_PASSPHRASE: '123', + ETH_WALLET_MNEMONIC: + 'ugly gallery notice network true range brave clarify flat logic someone chunk', + }, }); diff --git a/apps/simple-trading-app-e2e/src/integration/market-trade.test.ts b/apps/simple-trading-app-e2e/src/integration/market-trade.test.ts index e88b7d90d..b28503e00 100644 --- a/apps/simple-trading-app-e2e/src/integration/market-trade.test.ts +++ b/apps/simple-trading-app-e2e/src/integration/market-trade.test.ts @@ -1,6 +1,22 @@ import { aliasQuery } from '@vegaprotocol/cypress'; import { generateSimpleMarkets } from '../support/mocks/generate-markets'; import { generateDealTicket } from '../support/mocks/generate-deal-ticket'; +import { generateMarketTags } from '../support/mocks/generate-market-tags'; +import { generateMarketPositions } from '../support/mocks/generate-market-positions'; +import { generateEstimateOrder } from '../support/mocks/generate-estimate-order'; +import { generatePartyBalance } from '../support/mocks/generate-party-balance'; + +const connectVegaWallet = () => { + const form = 'rest-connector-form'; + const walletName = Cypress.env('TRADING_TEST_VEGA_WALLET_NAME'); + const walletPassphrase = Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE'); + + cy.getByTestId('connect-vega-wallet').click(); + cy.getByTestId('connectors-list').find('button').click(); + cy.getByTestId(form).find('#wallet').click().type(walletName); + cy.getByTestId(form).find('#passphrase').click().type(walletPassphrase); + cy.getByTestId('rest-connector-form').find('button[type=submit]').click(); +}; describe('Market trade', () => { let markets; @@ -8,6 +24,10 @@ describe('Market trade', () => { cy.mockGQL((req) => { aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets()); aliasQuery(req, 'DealTicketQuery', generateDealTicket()); + aliasQuery(req, 'MarketTags', generateMarketTags()); + aliasQuery(req, 'MarketPositions', generateMarketPositions()); + aliasQuery(req, 'EstimateOrder', generateEstimateOrder()); + aliasQuery(req, 'PartyBalanceQuery', generatePartyBalance()); }); cy.visit('/markets'); cy.wait('@SimpleMarkets').then((response) => { @@ -64,4 +84,32 @@ describe('Market trade', () => { ); } }); + + it('order review should display proper calculations', () => { + if (markets?.length) { + cy.visit(`/trading/${markets[0].id}`); + connectVegaWallet(); + cy.get('h3').contains('Review Trade').click(); + cy.getByTestId('key-value-table') + .find('dl') + .eq(1) + .find('dd div') + .should('have.text', '25.78726'); + cy.getByTestId('key-value-table') + .find('dl') + .eq(2) + .find('dd div') + .should('have.text', '1.00000'); + cy.getByTestId('key-value-table') + .find('dl') + .eq(3) + .find('dd div') + .should('have.text', '-785.81045'); + cy.getByTestId('place-order').click(); + cy.getByTestId('dialog-title').should( + 'have.text', + 'Confirm transaction in wallet' + ); + } + }); }); diff --git a/apps/simple-trading-app-e2e/src/support/mocks/generate-estimate-order.ts b/apps/simple-trading-app-e2e/src/support/mocks/generate-estimate-order.ts new file mode 100644 index 000000000..f7ee51bc1 --- /dev/null +++ b/apps/simple-trading-app-e2e/src/support/mocks/generate-estimate-order.ts @@ -0,0 +1,12 @@ +export const generateEstimateOrder = () => { + return { + estimateOrder: { + totalFeeAmount: '16085.09240212.7380425.46', + marginLevels: { + initialLevel: '2844054.80937741220203', + __typename: 'MarginLevels', + }, + __typename: 'OrderEstimate', + }, + }; +}; diff --git a/apps/simple-trading-app-e2e/src/support/mocks/generate-market-positions.ts b/apps/simple-trading-app-e2e/src/support/mocks/generate-market-positions.ts new file mode 100644 index 000000000..95648719a --- /dev/null +++ b/apps/simple-trading-app-e2e/src/support/mocks/generate-market-positions.ts @@ -0,0 +1,57 @@ +export const generateMarketPositions = () => { + return { + party: { + id: '2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1', + accounts: [ + { + asset: { + decimals: 5, + }, + balance: '400000000000000000000', + market: { + id: '2751c508f9759761f912890f37fb3f97a00300bf7685c02a56a86e05facfe221', + __typename: 'Market', + }, + }, + { + asset: { + decimals: 5, + }, + balance: '265329', + market: { + id: 'first-btcusd-id', + __typename: 'Market', + }, + }, + ], + positionsConnection: { + edges: [ + { + node: { + openVolume: '3', + market: { + id: '2751c508f9759761f912890f37fb3f97a00300bf7685c02a56a86e05facfe221', + __typename: 'Market', + }, + __typename: 'Position', + }, + __typename: 'PositionEdge', + }, + { + node: { + openVolume: '12', + market: { + id: 'first-btcusd-id', + __typename: 'Market', + }, + __typename: 'Position', + }, + __typename: 'PositionEdge', + }, + ], + __typename: 'PositionConnection', + }, + __typename: 'Party', + }, + }; +}; diff --git a/apps/simple-trading-app-e2e/src/support/mocks/generate-market-tags.ts b/apps/simple-trading-app-e2e/src/support/mocks/generate-market-tags.ts new file mode 100644 index 000000000..abf5477e3 --- /dev/null +++ b/apps/simple-trading-app-e2e/src/support/mocks/generate-market-tags.ts @@ -0,0 +1,25 @@ +export const generateMarketTags = () => { + return { + market: { + tradableInstrument: { + instrument: { + metadata: { + tags: [ + 'formerly:2839D9B2329C9E70', + 'base:AAVE', + 'quote:DAI', + 'class:fx/crypto', + 'monthly', + 'sector:defi', + 'settlement:2022-08-01', + ], + __typename: 'InstrumentMetadata', + }, + __typename: 'Instrument', + }, + __typename: 'TradableInstrument', + }, + __typename: 'Market', + }, + }; +}; diff --git a/apps/simple-trading-app-e2e/src/support/mocks/generate-party-balance.ts b/apps/simple-trading-app-e2e/src/support/mocks/generate-party-balance.ts new file mode 100644 index 000000000..c38801314 --- /dev/null +++ b/apps/simple-trading-app-e2e/src/support/mocks/generate-party-balance.ts @@ -0,0 +1,53 @@ +export const generatePartyBalance = () => { + return { + party: { + accounts: [ + { + balance: '88474051', + asset: { + id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', + symbol: 'tDAI', + name: 'tDAI TEST', + decimals: 5, + __typename: 'Asset', + }, + __typename: 'Account', + }, + { + balance: '100000000', + asset: { + id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4', + symbol: 'tEURO', + name: 'tEURO TEST', + decimals: 5, + __typename: 'Asset', + }, + __typename: 'Account', + }, + { + balance: '3412867', + asset: { + id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', + symbol: 'tDAI', + name: 'tDAI TEST', + decimals: 5, + __typename: 'Asset', + }, + __typename: 'Account', + }, + { + balance: '70007', + asset: { + id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', + symbol: 'tDAI', + name: 'tDAI TEST', + decimals: 5, + __typename: 'Asset', + }, + __typename: 'Account', + }, + ], + __typename: 'Party', + }, + }; +}; diff --git a/apps/simple-trading-app/.env b/apps/simple-trading-app/.env index 72bdc67a8..f572c2eeb 100644 --- a/apps/simple-trading-app/.env +++ b/apps/simple-trading-app/.env @@ -21,3 +21,4 @@ NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL NX_VEGA_CONFIG_URL="https://static.vega.xyz/assets/testnet-network.json" NX_VEGA_ENV = 'TESTNET' NX_VEGA_URL="https://lb.testnet.vega.xyz/query" +NX_VEGA_WALLET_URL=http://localhost:1789/api/v1 diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/__generated__/MarketTags.ts b/apps/simple-trading-app/src/app/components/deal-ticket/__generated__/MarketTags.ts new file mode 100644 index 000000000..36694839a --- /dev/null +++ b/apps/simple-trading-app/src/app/components/deal-ticket/__generated__/MarketTags.ts @@ -0,0 +1,51 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: MarketTags +// ==================================================== + +export interface MarketTags_market_tradableInstrument_instrument_metadata { + __typename: "InstrumentMetadata"; + /** + * An arbitrary list of tags to associated to associate to the Instrument (string list) + */ + tags: string[] | null; +} + +export interface MarketTags_market_tradableInstrument_instrument { + __typename: "Instrument"; + /** + * Metadata for this instrument + */ + metadata: MarketTags_market_tradableInstrument_instrument_metadata; +} + +export interface MarketTags_market_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of or reference to a fully specified instrument. + */ + instrument: MarketTags_market_tradableInstrument_instrument; +} + +export interface MarketTags_market { + __typename: "Market"; + /** + * An instance of or reference to a tradable instrument. + */ + tradableInstrument: MarketTags_market_tradableInstrument; +} + +export interface MarketTags { + /** + * An instrument that is trading on the VEGA network + */ + market: MarketTags_market | null; +} + +export interface MarketTagsVariables { + marketId: string; +} diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx index 57c974f16..f645feb52 100644 --- a/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx +++ b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx @@ -61,7 +61,7 @@ export const DealTicketContainer = () => { isWalletConnected={!!keypair?.pub} /> )} - + )} diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-steps.tsx b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-steps.tsx index 137bd6c11..4be517c03 100644 --- a/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-steps.tsx +++ b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-steps.tsx @@ -1,9 +1,16 @@ -import * as React from 'react'; +import React, { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; import { useForm, Controller } from 'react-hook-form'; import { Stepper } from '../stepper'; import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket'; -import { Button, InputError } from '@vegaprotocol/ui-toolkit'; -import { DealTicketAmount, MarketSelector } from '@vegaprotocol/deal-ticket'; +import { InputError } from '@vegaprotocol/ui-toolkit'; +import { + DealTicketAmount, + getDialogTitle, + getDialogIntent, + getDialogIcon, + MarketSelector, +} from '@vegaprotocol/deal-ticket'; import type { Order } from '@vegaprotocol/orders'; import { VegaTxStatus } from '@vegaprotocol/wallet'; import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers'; @@ -11,17 +18,22 @@ import { getDefaultOrder, useOrderValidation, useOrderSubmit, + OrderFeedback, } from '@vegaprotocol/orders'; -import { useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; import MarketNameRenderer from '../simple-market-list/simple-market-renderer'; import SideSelector, { SIDE_NAMES } from './side-selector'; +import ReviewTrade from './review-trade'; +import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery'; interface DealTicketMarketProps { market: DealTicketQuery_market; + partyData?: PartyBalanceQuery; } -export const DealTicketSteps = ({ market }: DealTicketMarketProps) => { +export const DealTicketSteps = ({ + market, + partyData, +}: DealTicketMarketProps) => { const navigate = useNavigate(); const setMarket = useCallback( (marketId) => { @@ -45,6 +57,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => { const orderType = watch('type'); const orderTimeInForce = watch('timeInForce'); const orderSide = watch('side'); + const order = watch(); const { message: invalidText, isDisabled } = useOrderValidation({ step, @@ -54,7 +67,8 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => { fieldErrors: errors, }); - const { submit, transaction } = useOrderSubmit(market); + const { submit, transaction, finalizedOrder, TransactionDialog } = + useOrderSubmit(market); const transactionStatus = transaction.status === VegaTxStatus.Requested || @@ -101,7 +115,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => { component: ( { {invalidText} )} - + + ), disabled: true, diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/review-trade.tsx b/apps/simple-trading-app/src/app/components/deal-ticket/review-trade.tsx new file mode 100644 index 000000000..81527e229 --- /dev/null +++ b/apps/simple-trading-app/src/app/components/deal-ticket/review-trade.tsx @@ -0,0 +1,146 @@ +import { addDecimal, formatNumber, t } from '@vegaprotocol/react-helpers'; +import { + Button, + Icon, + KeyValueTable, + KeyValueTableRow, +} from '@vegaprotocol/ui-toolkit'; +import * as React from 'react'; +import classNames from 'classnames'; +import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket'; +import type { Order } from '@vegaprotocol/orders'; +import { SIDE_NAMES } from './side-selector'; +import { useVegaWallet, VegaWalletOrderSide } from '@vegaprotocol/wallet'; +import SimpleMarketExpires from '../simple-market-list/simple-market-expires'; +import { gql, useQuery } from '@apollo/client'; +import type { + MarketTags, + MarketTagsVariables, +} from './__generated__/MarketTags'; +import useOrderMargin from '../../hooks/use-order-margin'; +import useOrderCloseOut from '../../hooks/use-order-closeout'; +import { IconNames } from '@blueprintjs/icons'; +import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery'; + +export const MARKET_TAGS_QUERY = gql` + query MarketTags($marketId: ID!) { + market(id: $marketId) { + tradableInstrument { + instrument { + metadata { + tags + } + } + } + } + } +`; + +interface Props { + market: DealTicketQuery_market; + isDisabled: boolean; + transactionStatus?: string; + order: Order; + partyData?: PartyBalanceQuery; +} + +export default ({ + isDisabled, + market, + order, + transactionStatus, + partyData, +}: Props) => { + const { keypair } = useVegaWallet(); + const { data: tagsData } = useQuery( + MARKET_TAGS_QUERY, + { + variables: { marketId: market.id }, + } + ); + const estMargin = useOrderMargin({ + order, + market, + partyId: keypair?.pub || '', + }); + const estCloseOut = useOrderCloseOut({ order, market, partyData }); + return ( +
+ + +
+
+ {SIDE_NAMES[order.side]} +
+
{market.tradableInstrument.instrument.product.quoteName}
+
+ {tagsData?.market?.tradableInstrument.instrument.metadata + .tags && ( + + )} +
+
+
+ @{' '} + {market.depth.lastTrade + ? addDecimal(market.depth.lastTrade.price, market.decimalPlaces) + : ' - '}{' '} + (EST) +
+
+ + <>{t('Est. margin')} +
+ {estMargin} + +
+
+ + <> + {t('Size')}{' '} +
+ ({market.tradableInstrument.instrument.product.quoteName}) +
+ +
+ {formatNumber(order.size, market.decimalPlaces)} + +
+
+ + <>{t('Est. close out')} +
+ {estCloseOut} + +
+
+
+ + +
+ ); +}; diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/side-selector.tsx b/apps/simple-trading-app/src/app/components/deal-ticket/side-selector.tsx index 1c03d045d..292975695 100644 --- a/apps/simple-trading-app/src/app/components/deal-ticket/side-selector.tsx +++ b/apps/simple-trading-app/src/app/components/deal-ticket/side-selector.tsx @@ -29,6 +29,7 @@ export default ({ value, onSelect }: SideSelectorProps) => { variant="inline-link" aria-label={t('Open long position')} className={classNames( + 'py-8', 'buyButton hover:buyButton dark:buyButtonDark dark:hover:buyButtonDark', { selected: value === VegaWalletOrderSide.Buy } )} @@ -40,6 +41,7 @@ export default ({ value, onSelect }: SideSelectorProps) => { variant="inline-link" aria-label={t('Open short position')} className={classNames( + 'py-8', 'sellButton hover:sellButton dark:sellButtonDark dark:hover:sellButtonDark', { selected: value === VegaWalletOrderSide.Sell } )} diff --git a/apps/simple-trading-app/src/app/components/simple-market-list/simple-market-expires.spec.tsx b/apps/simple-trading-app/src/app/components/simple-market-list/simple-market-expires.spec.tsx index a3da535f6..ed08fcbda 100644 --- a/apps/simple-trading-app/src/app/components/simple-market-list/simple-market-expires.spec.tsx +++ b/apps/simple-trading-app/src/app/components/simple-market-list/simple-market-expires.spec.tsx @@ -23,7 +23,7 @@ describe('SimpleMarketExpires', () => { 'settlement-date:2022-04-25T1200', ]; render(); - expect(screen.getByText('April 25')).toBeInTheDocument(); + expect(screen.getByText('Apr 25')).toBeInTheDocument(); }); it('last one proper tag should matter', () => { @@ -33,7 +33,7 @@ describe('SimpleMarketExpires', () => { 'settlement-expiry-date:2022-03-25T12:00:00', ]; render(); - expect(screen.getByText('March 25')).toBeInTheDocument(); + expect(screen.getByText('Mar 25')).toBeInTheDocument(); }); it('when no proper tag nor date should be null', () => { diff --git a/apps/simple-trading-app/src/app/constants/index.ts b/apps/simple-trading-app/src/app/constants/index.ts index 7de82e7f5..30885f110 100644 --- a/apps/simple-trading-app/src/app/constants/index.ts +++ b/apps/simple-trading-app/src/app/constants/index.ts @@ -1,7 +1,7 @@ import type { SimpleMarkets_markets } from '../components/simple-market-list/__generated__/SimpleMarkets'; export const DATE_FORMAT = 'dd MMMM yyyy HH:mm'; -export const EXPIRE_DATE_FORMAT = 'MMMM dd'; +export const EXPIRE_DATE_FORMAT = 'MMM dd'; export const TRADABLE_STATES = { Active: true, diff --git a/apps/simple-trading-app/src/app/hooks/__generated__/estimateOrder.ts b/apps/simple-trading-app/src/app/hooks/__generated__/estimateOrder.ts new file mode 100644 index 000000000..108368d2e --- /dev/null +++ b/apps/simple-trading-app/src/app/hooks/__generated__/estimateOrder.ts @@ -0,0 +1,48 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { Side, OrderTimeInForce, OrderType } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: EstimateOrder +// ==================================================== + +export interface EstimateOrder_estimateOrder_marginLevels { + __typename: "MarginLevels"; + /** + * this is the minimal margin required for a party to place a new order on the network (unsigned int actually) + */ + initialLevel: string; +} + +export interface EstimateOrder_estimateOrder { + __typename: "OrderEstimate"; + /** + * The total estimated amount of fee if the order was to trade + */ + totalFeeAmount: string; + /** + * The margin requirement for this order + */ + marginLevels: EstimateOrder_estimateOrder_marginLevels; +} + +export interface EstimateOrder { + /** + * return an estimation of the potential cost for a new order + */ + estimateOrder: EstimateOrder_estimateOrder; +} + +export interface EstimateOrderVariables { + marketId: string; + partyId: string; + price?: string | null; + size: string; + side: Side; + timeInForce: OrderTimeInForce; + expiration?: string | null; + type: OrderType; +} diff --git a/apps/simple-trading-app/src/app/hooks/__generated__/marketPositions.ts b/apps/simple-trading-app/src/app/hooks/__generated__/marketPositions.ts new file mode 100644 index 000000000..55f2be60a --- /dev/null +++ b/apps/simple-trading-app/src/app/hooks/__generated__/marketPositions.ts @@ -0,0 +1,106 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { AccountType } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: MarketPositions +// ==================================================== + +export interface MarketPositions_party_accounts_asset { + __typename: "Asset"; + /** + * The precision of the asset + */ + decimals: number; +} + +export interface MarketPositions_party_accounts_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; +} + +export interface MarketPositions_party_accounts { + __typename: "Account"; + /** + * Account type (General, Margin, etc) + */ + type: AccountType; + /** + * Balance as string - current account balance (approx. as balances can be updated several times per second) + */ + balance: string; + /** + * Asset, the 'currency' + */ + asset: MarketPositions_party_accounts_asset; + /** + * Market (only relevant to margin accounts) + */ + market: MarketPositions_party_accounts_market | null; +} + +export interface MarketPositions_party_positionsConnection_edges_node_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; +} + +export interface MarketPositions_party_positionsConnection_edges_node { + __typename: "Position"; + /** + * Open volume (uint64) + */ + openVolume: string; + /** + * Market relating to this position + */ + market: MarketPositions_party_positionsConnection_edges_node_market; +} + +export interface MarketPositions_party_positionsConnection_edges { + __typename: "PositionEdge"; + node: MarketPositions_party_positionsConnection_edges_node; +} + +export interface MarketPositions_party_positionsConnection { + __typename: "PositionConnection"; + /** + * The positions in this connection + */ + edges: MarketPositions_party_positionsConnection_edges[] | null; +} + +export interface MarketPositions_party { + __typename: "Party"; + /** + * Party identifier + */ + id: string; + /** + * Collateral accounts relating to a party + */ + accounts: MarketPositions_party_accounts[] | null; + /** + * Trading positions relating to a party + */ + positionsConnection: MarketPositions_party_positionsConnection; +} + +export interface MarketPositions { + /** + * An entity that is trading on the VEGA network + */ + party: MarketPositions_party | null; +} + +export interface MarketPositionsVariables { + partyId: string; +} diff --git a/apps/simple-trading-app/src/app/hooks/use-market-positions.spec.ts b/apps/simple-trading-app/src/app/hooks/use-market-positions.spec.ts new file mode 100644 index 000000000..5074c9521 --- /dev/null +++ b/apps/simple-trading-app/src/app/hooks/use-market-positions.spec.ts @@ -0,0 +1,133 @@ +import { renderHook } from '@testing-library/react-hooks'; +import useMarketPositions from './use-market-positions'; + +let mockNotEmptyData = { + party: { + accounts: [ + { + balance: '50001000000', + asset: { + decimals: 5, + }, + market: { + id: 'marketId', + }, + }, + { + balance: '700000000000000000000000000000', + asset: { + decimals: 5, + }, + market: { + id: 'someOtherMarketId', + }, + }, + ], + positionsConnection: { + edges: [ + { + node: { + openVolume: '100002', + market: { + id: 'marketId', + }, + }, + }, + { + node: { + openVolume: '3', + market: { + id: 'someOtherMarketId', + }, + }, + }, + ], + }, + }, +}; + +jest.mock('@apollo/client', () => ({ + ...jest.requireActual('@apollo/client'), + useQuery: jest.fn(() => ({ data: mockNotEmptyData })), +})); + +describe('useOrderPosition Hook', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('should return proper positive value', () => { + const { result } = renderHook(() => + useMarketPositions({ marketId: 'marketId', partyId: 'partyId' }) + ); + expect(result.current?.openVolume.toNumber()).toEqual(100002); + expect(result.current?.balance.toString()).toEqual('50001000000'); + }); + + it('if balance equal 0 return null', () => { + mockNotEmptyData = { + party: { + accounts: [ + { + balance: '0', + asset: { + decimals: 5, + }, + market: { + id: 'marketId', + }, + }, + ], + positionsConnection: { + edges: [ + { + node: { + openVolume: '2', + market: { + id: 'marketId', + }, + }, + }, + ], + }, + }, + }; + const { result } = renderHook(() => + useMarketPositions({ marketId: 'marketId', partyId: 'partyId' }) + ); + expect(result.current).toBeNull(); + }); + + it('if no markets return null', () => { + mockNotEmptyData = { + party: { + accounts: [ + { + balance: '33330', + asset: { + decimals: 5, + }, + market: { + id: 'otherMarketId', + }, + }, + ], + positionsConnection: { + edges: [ + { + node: { + openVolume: '2', + market: { + id: 'otherMarketId', + }, + }, + }, + ], + }, + }, + }; + const { result } = renderHook(() => + useMarketPositions({ marketId: 'marketId', partyId: 'partyId' }) + ); + expect(result.current).toBeNull(); + }); +}); diff --git a/apps/simple-trading-app/src/app/hooks/use-market-positions.ts b/apps/simple-trading-app/src/app/hooks/use-market-positions.ts new file mode 100644 index 000000000..1465fbfe9 --- /dev/null +++ b/apps/simple-trading-app/src/app/hooks/use-market-positions.ts @@ -0,0 +1,77 @@ +import { gql, useQuery } from '@apollo/client'; +import { BigNumber } from 'bignumber.js'; +import type { + MarketPositions, + MarketPositionsVariables, +} from './__generated__/marketPositions'; + +const MARKET_POSITIONS_QUERY = gql` + query MarketPositions($partyId: ID!) { + party(id: $partyId) { + id + accounts { + type + balance + asset { + decimals + } + market { + id + } + } + positionsConnection { + edges { + node { + openVolume + market { + id + } + } + } + } + } + } +`; + +interface Props { + marketId: string; + partyId: string; +} + +export type PositionMargin = { + openVolume: BigNumber; + balance: BigNumber; + balanceDecimals?: number; +} | null; + +export default ({ marketId, partyId }: Props): PositionMargin => { + const { data } = useQuery( + MARKET_POSITIONS_QUERY, + { + pollInterval: 5000, + variables: { partyId }, + skip: !partyId, + } + ); + + const account = data?.party?.accounts?.find( + (nodes) => nodes.market?.id === marketId + ); + + if (account) { + const balance = new BigNumber(account.balance || 0); + const openVolume = new BigNumber( + data?.party?.positionsConnection?.edges?.find( + (nodes) => nodes.node.market.id === marketId + )?.node.openVolume || 0 + ); + if (!balance.isZero() && !openVolume.isZero()) { + return { + balance, + balanceDecimals: account?.asset.decimals, + openVolume, + }; + } + } + return null; +}; diff --git a/apps/simple-trading-app/src/app/hooks/use-order-closeout.spec.ts b/apps/simple-trading-app/src/app/hooks/use-order-closeout.spec.ts new file mode 100644 index 000000000..8633cb879 --- /dev/null +++ b/apps/simple-trading-app/src/app/hooks/use-order-closeout.spec.ts @@ -0,0 +1,71 @@ +import { renderHook } from '@testing-library/react-hooks'; +import useOrderCloseOut from './use-order-closeout'; +import type { Order } from '@vegaprotocol/orders'; +import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket'; +import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery'; + +describe('useOrderCloseOut Hook', () => { + const order = { size: '2', side: 'SIDE_BUY' }; + const market = { + decimalPlaces: 5, + depth: { + lastTrade: { + price: '1000000', + }, + }, + tradableInstrument: { + instrument: { + product: { + settlementAsset: { + id: 'assetId', + }, + }, + }, + }, + }; + const partyData = { + party: { + accounts: [ + { + balance: '200000', + asset: { + id: 'assetId', + decimals: 5, + }, + }, + ], + }, + }; + + it('return proper buy value', () => { + const { result } = renderHook(() => + useOrderCloseOut({ + order: order as Order, + market: market as DealTicketQuery_market, + partyData: partyData as PartyBalanceQuery, + }) + ); + expect(result.current).toEqual('9.00000'); + }); + + it('return proper sell value', () => { + const { result } = renderHook(() => + useOrderCloseOut({ + order: { ...order, side: 'SIDE_SELL' } as Order, + market: market as DealTicketQuery_market, + partyData: partyData as PartyBalanceQuery, + }) + ); + expect(result.current).toEqual('11.00000'); + }); + + it('return proper empty value', () => { + const { result } = renderHook(() => + useOrderCloseOut({ + order: { ...order, side: 'SIDE_SELL' } as Order, + market: market as DealTicketQuery_market, + }) + ); + expect(result.current).toEqual(' - '); + }); +}); diff --git a/apps/simple-trading-app/src/app/hooks/use-order-closeout.ts b/apps/simple-trading-app/src/app/hooks/use-order-closeout.ts new file mode 100644 index 000000000..aade7e728 --- /dev/null +++ b/apps/simple-trading-app/src/app/hooks/use-order-closeout.ts @@ -0,0 +1,39 @@ +import { BigNumber } from 'bignumber.js'; +import type { Order } from '@vegaprotocol/orders'; +import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket'; +import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery'; +import { useSettlementAccount } from './use-settlement-account'; +import { VegaWalletOrderSide } from '@vegaprotocol/wallet'; +import { addDecimal, formatNumber } from '@vegaprotocol/react-helpers'; + +interface Props { + order: Order; + market: DealTicketQuery_market; + partyData?: PartyBalanceQuery; +} + +const useOrderCloseOut = ({ order, market, partyData }: Props): string => { + const account = useSettlementAccount( + market.tradableInstrument.instrument.product.settlementAsset.id, + partyData?.party?.accounts || [] + ); + if (account?.balance && market.depth.lastTrade) { + const price = new BigNumber( + addDecimal(market.depth.lastTrade.price, market.decimalPlaces) + ); + const balance = new BigNumber( + addDecimal(account.balance, account.asset.decimals) + ); + const { size, side } = order; + const bigOne = new BigNumber(1); + return formatNumber( + side === VegaWalletOrderSide.Buy + ? bigOne.minus(balance.div(price.times(size))).times(price) + : bigOne.plus(balance.div(price.times(size))).times(price), + market.decimalPlaces + ); + } + return ' - '; +}; + +export default useOrderCloseOut; diff --git a/apps/simple-trading-app/src/app/hooks/use-order-margin.spec.ts b/apps/simple-trading-app/src/app/hooks/use-order-margin.spec.ts new file mode 100644 index 000000000..28c89ef22 --- /dev/null +++ b/apps/simple-trading-app/src/app/hooks/use-order-margin.spec.ts @@ -0,0 +1,106 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useQuery } from '@apollo/client'; +import { BigNumber } from 'bignumber.js'; +import type { Order } from '@vegaprotocol/orders'; +import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket'; +import type { PositionMargin } from './use-market-positions'; +import useOrderMargin from './use-order-margin'; + +let mockEstimateData = { + estimateOrder: { + marginLevels: { + initialLevel: '200000', + }, + }, +}; +jest.mock('@apollo/client', () => ({ + ...jest.requireActual('@apollo/client'), + useQuery: jest.fn(() => ({ data: mockEstimateData })), +})); + +let mockMarketPositions: PositionMargin = { + openVolume: new BigNumber(1), + balance: new BigNumber(100000), +}; +jest.mock('./use-market-positions', () => jest.fn(() => mockMarketPositions)); + +describe('useOrderMargin Hook', () => { + const order = { + size: '2', + side: 'SIDE_BUY', + timeInForce: 'TIME_IN_FORCE_IOC', + type: 'TYPE_MARKET', + }; + const market = { + id: 'marketId', + depth: { + lastTrade: { + price: '1000000', + }, + }, + }; + const partyId = 'partyId'; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('margin should be properly calculated', () => { + const { result } = renderHook(() => + useOrderMargin({ + order: order as Order, + market: market as DealTicketQuery_market, + partyId, + }) + ); + expect(result.current).toEqual('100000'); + + const calledSize = new BigNumber(mockMarketPositions?.openVolume || 0) + .plus(order.size) + .toString(); + expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual( + calledSize + ); + }); + + it('if there is no positions initialMargin should not be subtracted', () => { + mockMarketPositions = null; + const { result } = renderHook(() => + useOrderMargin({ + order: order as Order, + market: market as DealTicketQuery_market, + partyId, + }) + ); + expect(result.current).toEqual('200000'); + + expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual( + order.size + ); + }); + + it('if api fails, should return empty value', () => { + mockEstimateData = { + estimateOrder: { + marginLevels: { + initialLevel: '', + }, + }, + }; + const { result } = renderHook(() => + useOrderMargin({ + order: order as Order, + market: market as DealTicketQuery_market, + partyId, + }) + ); + expect(result.current).toEqual(' - '); + + const calledSize = new BigNumber(mockMarketPositions?.openVolume || 0) + .plus(order.size) + .toString(); + expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual( + calledSize + ); + }); +}); diff --git a/apps/simple-trading-app/src/app/hooks/use-order-margin.ts b/apps/simple-trading-app/src/app/hooks/use-order-margin.ts new file mode 100644 index 000000000..2e97d9cdf --- /dev/null +++ b/apps/simple-trading-app/src/app/hooks/use-order-margin.ts @@ -0,0 +1,103 @@ +import { BigNumber } from 'bignumber.js'; +import type { Order } from '@vegaprotocol/orders'; +import { gql, useQuery } from '@apollo/client'; +import type { + EstimateOrder, + EstimateOrderVariables, +} from './__generated__/estimateOrder'; +import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket'; +import { OrderTimeInForce, OrderType, Side } from '@vegaprotocol/types'; +import { + VegaWalletOrderSide, + VegaWalletOrderTimeInForce, + VegaWalletOrderType, +} from '@vegaprotocol/wallet'; +import { addDecimal } from '@vegaprotocol/react-helpers'; +import useMarketPositions from './use-market-positions'; + +export const ESTIMATE_ORDER_QUERY = gql` + query EstimateOrder( + $marketId: ID! + $partyId: ID! + $price: String + $size: String! + $side: Side! + $timeInForce: OrderTimeInForce! + $expiration: String + $type: OrderType! + ) { + estimateOrder( + marketId: $marketId + partyId: $partyId + price: $price + size: $size + side: $side + timeInForce: $timeInForce + expiration: $expiration + type: $type + ) { + totalFeeAmount + marginLevels { + initialLevel + } + } + } +`; + +interface Props { + order: Order; + market: DealTicketQuery_market; + partyId: string; +} + +const times: Record = { + [VegaWalletOrderTimeInForce.GTC]: OrderTimeInForce.GTC, + [VegaWalletOrderTimeInForce.GTT]: OrderTimeInForce.GTT, + [VegaWalletOrderTimeInForce.IOC]: OrderTimeInForce.IOC, + [VegaWalletOrderTimeInForce.FOK]: OrderTimeInForce.FOK, + [VegaWalletOrderTimeInForce.GFN]: OrderTimeInForce.GFN, + [VegaWalletOrderTimeInForce.GFA]: OrderTimeInForce.GFA, +}; + +const types: Record = { + [VegaWalletOrderType.Market]: OrderType.Market, + [VegaWalletOrderType.Limit]: OrderType.Limit, +}; + +const useOrderMargin = ({ order, market, partyId }: Props) => { + const marketPositions = useMarketPositions({ marketId: market.id, partyId }); + const { data } = useQuery( + ESTIMATE_ORDER_QUERY, + { + variables: { + marketId: market.id, + partyId, + price: market.depth.lastTrade?.price, + size: new BigNumber(marketPositions?.openVolume || 0) + [order.side === VegaWalletOrderSide.Buy ? 'plus' : 'minus']( + order.size + ) + .toString(), + side: order.side === VegaWalletOrderSide.Buy ? Side.Buy : Side.Sell, + timeInForce: times[order.timeInForce], + type: types[order.type], + }, + skip: + !partyId || !market.id || !order.size || !market.depth.lastTrade?.price, + } + ); + if (data?.estimateOrder.marginLevels.initialLevel) { + return addDecimal( + BigNumber.maximum( + 0, + new BigNumber(data.estimateOrder.marginLevels.initialLevel).minus( + marketPositions?.balance || 0 + ) + ).toString(), + market.decimalPlaces + ); + } + return ' - '; +}; + +export default useOrderMargin; diff --git a/apps/static/src/assets/mainnet-tranches.json b/apps/static/src/assets/mainnet-tranches.json index 63f612fb5..9c17a5e55 100644 --- a/apps/static/src/assets/mainnet-tranches.json +++ b/apps/static/src/assets/mainnet-tranches.json @@ -71,7 +71,7 @@ "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "129999.45", "total_removed": "0", - "locked_amount": "117722.18818369513317219", + "locked_amount": "116061.65519824298138415", "deposits": [ { "amount": "129999.45", @@ -521,7 +521,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "97499.58", "total_removed": "0", - "locked_amount": "58696.701913247477199432", + "locked_amount": "57067.87104692389040607", "deposits": [ { "amount": "97499.58", @@ -554,7 +554,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "135173.4239508", "total_removed": "0", - "locked_amount": "80228.25795257262490728767616", + "locked_amount": "78001.92736422610737413692656", "deposits": [ { "amount": "135173.4239508", @@ -587,7 +587,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "32499.86", "total_removed": "0", - "locked_amount": "24692.688857079010352968", + "locked_amount": "24007.467840021196513942", "deposits": [ { "amount": "32499.86", @@ -620,7 +620,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "10833.29", "total_removed": "0", - "locked_amount": "8037.23061079827060276", + "locked_amount": "7814.197818973311608206", "deposits": [ { "amount": "10833.29", @@ -728,7 +728,7 @@ "tranche_end": "2022-11-01T00:00:00.000Z", "total_added": "22500", "total_removed": "0", - "locked_amount": "11891.54353487318775", + "locked_amount": "11035.58650362318825", "deposits": [ { "amount": "15000", @@ -814,7 +814,7 @@ "tranche_end": "2023-06-02T00:00:00.000Z", "total_added": "1939928.38", "total_removed": "179856.049568108351", - "locked_amount": "1648919.99192040261888734", + "locked_amount": "1611716.80872347542469696", "deposits": [ { "amount": "1852091.69", @@ -1045,7 +1045,7 @@ "tranche_end": "2023-02-01T00:00:00.000Z", "total_added": "42500", "total_removed": "0", - "locked_amount": "42500", + "locked_amount": "42094.99672906602225", "deposits": [ { "amount": "12500", @@ -1889,7 +1889,7 @@ "tranche_end": "2022-09-30T00:00:00.000Z", "total_added": "60916.66666633337", "total_removed": "19238.601152747179372649", - "locked_amount": "10191.2646876420868592017845866885", + "locked_amount": "9097.9157367340121736957898191851", "deposits": [ { "amount": "2833.333333", @@ -3373,8 +3373,8 @@ "tranche_id": 10, "tranche_start": "2021-07-15T23:37:11.000Z", "tranche_end": "2021-07-15T23:37:11.000Z", - "total_added": "4620969.150000000000000001", - "total_removed": "4618402.640000000000000001", + "total_added": "4630969.150000000000000001", + "total_removed": "4628402.640000000000000001", "locked_amount": "0", "deposits": [ { @@ -3387,6 +3387,11 @@ "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", "tx": "0x0cd7dab19227ea994201ea2c7e5456e83568c5985b86376bf7e1223df351491f" }, + { + "amount": "10000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tx": "0x2b30ce4ea28386740738bf02913088eca12adc5138fd81f3390e7589136b7d11" + }, { "amount": "114000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -3849,6 +3854,11 @@ "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", "tx": "0x5445fefd6548404e650f9c5065b5462548ed5ed206cd0f2782d4f72a1b23a56d" }, + { + "amount": "10000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tx": "0x4c2a70e8bc8f56171e4666d65fafca445006ee53e9593777d6fe297579217713" + }, { "amount": "114000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -4265,6 +4275,12 @@ "tranche_id": 10, "tx": "0x0cd7dab19227ea994201ea2c7e5456e83568c5985b86376bf7e1223df351491f" }, + { + "amount": "10000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tranche_id": 10, + "tx": "0x2b30ce4ea28386740738bf02913088eca12adc5138fd81f3390e7589136b7d11" + }, { "amount": "114000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -4381,6 +4397,12 @@ "tranche_id": 10, "tx": "0x5445fefd6548404e650f9c5065b5462548ed5ed206cd0f2782d4f72a1b23a56d" }, + { + "amount": "10000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tranche_id": 10, + "tx": "0x4c2a70e8bc8f56171e4666d65fafca445006ee53e9593777d6fe297579217713" + }, { "amount": "114000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -4484,8 +4506,8 @@ "tx": "0xac16a4ce688d40a482a59914d68c3a676592f8804ee8f0781b66a4ba5ccfbdfc" } ], - "total_tokens": "1461651", - "withdrawn_tokens": "1461651", + "total_tokens": "1471651", + "withdrawn_tokens": "1471651", "remaining_tokens": "0" }, { @@ -5512,9 +5534,9 @@ "tranche_id": 11, "tranche_start": "2021-09-03T00:00:00.000Z", "tranche_end": "2022-09-03T00:00:00.000Z", - "total_added": "24994.000000000000000003", - "total_removed": "8288.51201225622", - "locked_amount": "2618.98776211313942074031435397640791463", + "total_added": "25026.000000000000000003", + "total_removed": "10713.64234595729", + "locked_amount": "2142.40208980213216824025682115677321172", "deposits": [ { "amount": "2", @@ -5556,6 +5578,36 @@ "user": "0xfeDf4b4406B3126744047be05A013570b09DC0cC", "tx": "0xd8fb387a63f4c66e0745787c5468f08bb8321b284ee8275ab961a52b51726381" }, + { + "amount": "12", + "user": "0x54e6627177165Aa7b6F31752c744c306FaC993dB", + "tx": "0x846ec1e97e805281e019842be187b58f63237c0ea04cc2abd002899e86762b39" + }, + { + "amount": "2", + "user": "0xcd6f7FBa16933469bd99F714CC802849216C532A", + "tx": "0x05bbc77056939645ce69b9dba25d17697f4aa348ffbdb88e55abe467bd7d2839" + }, + { + "amount": "2", + "user": "0x7cad86eC6007Ad16B0D50366Ffc5C5182636EB1d", + "tx": "0x432102400ff0ac55b2028d5cac2e5d50155c469d03318f50beca8f704494b37a" + }, + { + "amount": "12", + "user": "0xeeb7c600722630Dd0b7698a23236E8465c1d2D79", + "tx": "0xce7584fd21075fed120b7dc297a1c72ce213b4735246138f6bd0efbf5e8aa8f9" + }, + { + "amount": "2", + "user": "0x611BCA8adcA69b03adBfe733EF18b9bB88A5A518", + "tx": "0xb726e71dcfdfbf40bd054c692e119b8927a5b3f9e5894bd7f307464e54cdb2eb" + }, + { + "amount": "2", + "user": "0xFE06543Fa924915E15EC30B4F363DB146a4d8516", + "tx": "0x6882a0d3d033503d4892050310563cd36b1888b861ddb6f9931752fa70dd4159" + }, { "amount": "25", "user": "0x2c51Dc5A225a9d8542FBf28BA9dC501f026CD5e8", @@ -9273,6 +9325,46 @@ "user": "0x57AF825ff979Bd64B841ab525A5C743912abBC59", "tx": "0x623596de3b43952854ab08fcbac3044c500bbe8dcd54f4ffb8f1b2c898fbe71d" }, + { + "amount": "3.44219305", + "user": "0xc5467213593778E528f0eB8117cc7AFBC5b7491b", + "tx": "0xf1be3a0908eb819cafd136a09720acbde12b65cdd5d2034ba0da7eb13908ab66" + }, + { + "amount": "131.5360248618", + "user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c", + "tx": "0xed9bda4dedb117203e96ff4677d4b9ab3365ea1aa23036e459734613d576fac3" + }, + { + "amount": "280.4488489344", + "user": "0xf144fcebdb84fD3Bc070108673D6290940AAD756", + "tx": "0x6140499fa91ae792569441c6721af4cf153635337c5defb33f34f6d94414a537" + }, + { + "amount": "452.842386475", + "user": "0xEb01fF124D71b6C7E6613fd6E0A86c28C733F008", + "tx": "0x61c0a4966bbd17963420f02d0eb42444174371ef7d8fae1f2c6fc3e87e26c7c5" + }, + { + "amount": "575.1660296123", + "user": "0x2c51Dc5A225a9d8542FBf28BA9dC501f026CD5e8", + "tx": "0xee910cb53ef7198cb66bf74b5f56c6f7bf93b691ac44cb0c935c4fd5884b5604" + }, + { + "amount": "525.3730156012", + "user": "0xf450648448be1117A854ab39FA30B398bAC88AEf", + "tx": "0x773f30247e67eb4e00091dd33a86a62ae16a8a4958c829cf92f4a6942c86e389" + }, + { + "amount": "448.48974600045", + "user": "0xd996efF75E43357F04E3f37D61EF6A6851A0b8f1", + "tx": "0x40e95412fafb1365d30cc16fec155c7d5d2af99994784618440c0eaafe15a9b0" + }, + { + "amount": "7.83208916592", + "user": "0xa0479A52EEe756B49cbdB3Fd07F8292E4Bd61c39", + "tx": "0xde6380a15a7f1084900a531bec6b0587fa283a24ed1cadde171509469dd22c4e" + }, { "amount": "842.29689345648", "user": "0xab6dE081Af6C78433AFDaC5B756804bcE17e9eC1", @@ -9719,6 +9811,102 @@ "withdrawn_tokens": "0", "remaining_tokens": "157" }, + { + "address": "0x54e6627177165Aa7b6F31752c744c306FaC993dB", + "deposits": [ + { + "amount": "12", + "user": "0x54e6627177165Aa7b6F31752c744c306FaC993dB", + "tranche_id": 11, + "tx": "0x846ec1e97e805281e019842be187b58f63237c0ea04cc2abd002899e86762b39" + } + ], + "withdrawals": [], + "total_tokens": "12", + "withdrawn_tokens": "0", + "remaining_tokens": "12" + }, + { + "address": "0xcd6f7FBa16933469bd99F714CC802849216C532A", + "deposits": [ + { + "amount": "2", + "user": "0xcd6f7FBa16933469bd99F714CC802849216C532A", + "tranche_id": 11, + "tx": "0x05bbc77056939645ce69b9dba25d17697f4aa348ffbdb88e55abe467bd7d2839" + } + ], + "withdrawals": [], + "total_tokens": "2", + "withdrawn_tokens": "0", + "remaining_tokens": "2" + }, + { + "address": "0x7cad86eC6007Ad16B0D50366Ffc5C5182636EB1d", + "deposits": [ + { + "amount": "2", + "user": "0x7cad86eC6007Ad16B0D50366Ffc5C5182636EB1d", + "tranche_id": 11, + "tx": "0x432102400ff0ac55b2028d5cac2e5d50155c469d03318f50beca8f704494b37a" + } + ], + "withdrawals": [], + "total_tokens": "2", + "withdrawn_tokens": "0", + "remaining_tokens": "2" + }, + { + "address": "0xeeb7c600722630Dd0b7698a23236E8465c1d2D79", + "deposits": [ + { + "amount": "12", + "user": "0xeeb7c600722630Dd0b7698a23236E8465c1d2D79", + "tranche_id": 11, + "tx": "0xce7584fd21075fed120b7dc297a1c72ce213b4735246138f6bd0efbf5e8aa8f9" + } + ], + "withdrawals": [], + "total_tokens": "12", + "withdrawn_tokens": "0", + "remaining_tokens": "12" + }, + { + "address": "0x611BCA8adcA69b03adBfe733EF18b9bB88A5A518", + "deposits": [ + { + "amount": "2", + "user": "0x611BCA8adcA69b03adBfe733EF18b9bB88A5A518", + "tranche_id": 11, + "tx": "0xb726e71dcfdfbf40bd054c692e119b8927a5b3f9e5894bd7f307464e54cdb2eb" + } + ], + "withdrawals": [], + "total_tokens": "2", + "withdrawn_tokens": "0", + "remaining_tokens": "2" + }, + { + "address": "0xFE06543Fa924915E15EC30B4F363DB146a4d8516", + "deposits": [ + { + "amount": "2", + "user": "0xFE06543Fa924915E15EC30B4F363DB146a4d8516", + "tranche_id": 11, + "tx": "0x6882a0d3d033503d4892050310563cd36b1888b861ddb6f9931752fa70dd4159" + }, + { + "amount": "12", + "user": "0xFE06543Fa924915E15EC30B4F363DB146a4d8516", + "tranche_id": 11, + "tx": "0x54794f4feda35faa5eb26f41281935ac41855663c28259ce58da8a45e22b7a44" + } + ], + "withdrawals": [], + "total_tokens": "14", + "withdrawn_tokens": "0", + "remaining_tokens": "14" + }, { "address": "0x2c51Dc5A225a9d8542FBf28BA9dC501f026CD5e8", "deposits": [ @@ -9789,10 +9977,17 @@ "tx": "0x0684f1765543cce5bf5c79fe51963ec4efc121a7778c151e0f8f940a406d0a6e" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "575.1660296123", + "user": "0x2c51Dc5A225a9d8542FBf28BA9dC501f026CD5e8", + "tranche_id": 11, + "tx": "0xee910cb53ef7198cb66bf74b5f56c6f7bf93b691ac44cb0c935c4fd5884b5604" + } + ], "total_tokens": "635", - "withdrawn_tokens": "0", - "remaining_tokens": "635" + "withdrawn_tokens": "575.1660296123", + "remaining_tokens": "59.8339703877" }, { "address": "0xd996efF75E43357F04E3f37D61EF6A6851A0b8f1", @@ -9882,10 +10077,17 @@ "tx": "0x1538f80b41c11cff6c307fc69901cf7b8b2ab863f62f683175b298885ddb855e" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "448.48974600045", + "user": "0xd996efF75E43357F04E3f37D61EF6A6851A0b8f1", + "tranche_id": 11, + "tx": "0x40e95412fafb1365d30cc16fec155c7d5d2af99994784618440c0eaafe15a9b0" + } + ], "total_tokens": "495", - "withdrawn_tokens": "0", - "remaining_tokens": "495" + "withdrawn_tokens": "448.48974600045", + "remaining_tokens": "46.51025399955" }, { "address": "0x89d0dfb58149fB1FF7471983191F6e0eF5c06dCd", @@ -10273,10 +10475,17 @@ "tx": "0x5e26adc0060f0cc3a5a8507be33b703e9d00ab6a794fe2621b2d512bcec109a6" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "525.3730156012", + "user": "0xf450648448be1117A854ab39FA30B398bAC88AEf", + "tranche_id": 11, + "tx": "0x773f30247e67eb4e00091dd33a86a62ae16a8a4958c829cf92f4a6942c86e389" + } + ], "total_tokens": "580", - "withdrawn_tokens": "0", - "remaining_tokens": "580" + "withdrawn_tokens": "525.3730156012", + "remaining_tokens": "54.6269843988" }, { "address": "0xf144fcebdb84fD3Bc070108673D6290940AAD756", @@ -10354,10 +10563,17 @@ "tx": "0x6a812385fe8e104cedcb81615bcf2f65aa4ca4400a94524774878047b505d5cc" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "280.4488489344", + "user": "0xf144fcebdb84fD3Bc070108673D6290940AAD756", + "tranche_id": 11, + "tx": "0x6140499fa91ae792569441c6721af4cf153635337c5defb33f34f6d94414a537" + } + ], "total_tokens": "310", - "withdrawn_tokens": "0", - "remaining_tokens": "310" + "withdrawn_tokens": "280.4488489344", + "remaining_tokens": "29.5511510656" }, { "address": "0xEeFF517294d2aa759274703a1B1C5ac81f9a754B", @@ -10816,10 +11032,17 @@ "tx": "0x1dbbc8c55e6b64081b2336251d1ab30886e0446a885ee7b2fa981897e94a0f6c" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "452.842386475", + "user": "0xEb01fF124D71b6C7E6613fd6E0A86c28C733F008", + "tranche_id": 11, + "tx": "0x61c0a4966bbd17963420f02d0eb42444174371ef7d8fae1f2c6fc3e87e26c7c5" + } + ], "total_tokens": "500", - "withdrawn_tokens": "0", - "remaining_tokens": "500" + "withdrawn_tokens": "452.842386475", + "remaining_tokens": "47.157613525" }, { "address": "0x9D375719442C8D57CA9658eDe45336787648381F", @@ -11314,6 +11537,12 @@ } ], "withdrawals": [ + { + "amount": "131.5360248618", + "user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c", + "tranche_id": 11, + "tx": "0xed9bda4dedb117203e96ff4677d4b9ab3365ea1aa23036e459734613d576fac3" + }, { "amount": "934.2413521038", "user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c", @@ -11322,8 +11551,8 @@ } ], "total_tokens": "1180", - "withdrawn_tokens": "934.2413521038", - "remaining_tokens": "245.7586478962" + "withdrawn_tokens": "1065.7773769656", + "remaining_tokens": "114.2226230344" }, { "address": "0xBF8C00ee066421c2c6daE50835c2a7AD939db752", @@ -14737,21 +14966,6 @@ "withdrawn_tokens": "0", "remaining_tokens": "10" }, - { - "address": "0xFE06543Fa924915E15EC30B4F363DB146a4d8516", - "deposits": [ - { - "amount": "12", - "user": "0xFE06543Fa924915E15EC30B4F363DB146a4d8516", - "tranche_id": 11, - "tx": "0x54794f4feda35faa5eb26f41281935ac41855663c28259ce58da8a45e22b7a44" - } - ], - "withdrawals": [], - "total_tokens": "12", - "withdrawn_tokens": "0", - "remaining_tokens": "12" - }, { "address": "0x5e1623A9F268278B9Fd2115AA7b3ec34dc5d98A8", "deposits": [ @@ -15892,6 +16106,12 @@ "tranche_id": 11, "tx": "0x147603b63143769e9407bfe6f69756a1eebda129344a5342f044758112484541" }, + { + "amount": "7.83208916592", + "user": "0xa0479A52EEe756B49cbdB3Fd07F8292E4Bd61c39", + "tranche_id": 11, + "tx": "0xde6380a15a7f1084900a531bec6b0587fa283a24ed1cadde171509469dd22c4e" + }, { "amount": "54.26009690396", "user": "0xa0479A52EEe756B49cbdB3Fd07F8292E4Bd61c39", @@ -15954,8 +16174,8 @@ } ], "total_tokens": "412", - "withdrawn_tokens": "365.92193619984", - "remaining_tokens": "46.07806380016" + "withdrawn_tokens": "373.75402536576", + "remaining_tokens": "38.24597463424" }, { "address": "0x70dE1021E0dC0876CE3Df2889ADbf1aeD381Fc69", @@ -16276,6 +16496,12 @@ } ], "withdrawals": [ + { + "amount": "3.44219305", + "user": "0xc5467213593778E528f0eB8117cc7AFBC5b7491b", + "tranche_id": 11, + "tx": "0xf1be3a0908eb819cafd136a09720acbde12b65cdd5d2034ba0da7eb13908ab66" + }, { "amount": "5.695719178", "user": "0xc5467213593778E528f0eB8117cc7AFBC5b7491b", @@ -16356,8 +16582,8 @@ } ], "total_tokens": "200", - "withdrawn_tokens": "176.472355402", - "remaining_tokens": "23.527644598" + "withdrawn_tokens": "179.914548452", + "remaining_tokens": "20.085451548" }, { "address": "0x1775cc97E5c05Fde8b571ef75CA52d0A9ff19025", @@ -16381,8 +16607,8 @@ "tranche_start": "2022-03-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z", "total_added": "3732368.4671", - "total_removed": "144843.5352821052414", - "locked_amount": "2558317.1648180147802561215", + "total_removed": "148381.2401909658774", + "locked_amount": "2501148.91954371656585585758", "deposits": [ { "amount": "1998.95815", @@ -16551,6 +16777,11 @@ } ], "withdrawals": [ + { + "amount": "3537.704908860636", + "user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0", + "tx": "0x94e1cf9e13f0d65808fb9a27364952be89155eab13f6b4259acced06c18694fc" + }, { "amount": "20676.42232", "user": "0x93b478148FF792B00076B7EdC89Db1FdE7772079", @@ -16650,6 +16881,12 @@ } ], "withdrawals": [ + { + "amount": "3537.704908860636", + "user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0", + "tranche_id": 1, + "tx": "0x94e1cf9e13f0d65808fb9a27364952be89155eab13f6b4259acced06c18694fc" + }, { "amount": "38390.8952661194945", "user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0", @@ -16664,8 +16901,8 @@ } ], "total_tokens": "187637.95", - "withdrawn_tokens": "55736.5564673891205", - "remaining_tokens": "131901.3935326108795" + "withdrawn_tokens": "59274.2613762497565", + "remaining_tokens": "128363.6886237502435" }, { "address": "0x1A71e3ED1996CAbB91bB043f880CE963D601707e", @@ -17127,8 +17364,8 @@ "tranche_start": "2022-06-05T00:00:00.000Z", "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "15788853.065470999700000001", - "total_removed": "67542.001079865509505", - "locked_amount": "14297739.632184757022370311873006803811841", + "total_removed": "86199.494349116919035", + "locked_amount": "14096062.5380303455040661406657035491132739", "deposits": [ { "amount": "16249.93", @@ -17632,6 +17869,51 @@ "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", "tx": "0x4303f2bca5e794329a9dd8b34e0bf4f29234f1fa5a920c48a8203b62529aba13" }, + { + "amount": "1181.264644302466", + "user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0", + "tx": "0xa67914a8bbc426c6faa561af9417fafae696f08823d534b37a93adc0801e66a7" + }, + { + "amount": "400.56886157179005", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0x36922428802b59ffa5372882e41946441a94a2adeeb9d85a8b701880d50e7a5e" + }, + { + "amount": "394.49820208894695", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0xe41aea73669c53b7a3687a3ee635cff1926833a692679899e01e837d69223351" + }, + { + "amount": "272.141858008646025", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0xb06fd9b057c2b92266a06f4d02a291882bb263f10ff5b4d35874aa9a5143bb54" + }, + { + "amount": "14997.49643037387568", + "user": "0xe3eB4CF43C072658401996b71D43eE26E573562D", + "tx": "0x489b9996f152de920bd4704ff05cda064d45a3ac756fe09dde56dbdde5ede241" + }, + { + "amount": "430.061399510802075", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0xd0ca6b4d7a1b8fe195a1401b975c9d38d844299a783ca402c9f440f9b3746abb" + }, + { + "amount": "261.063109006584075", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0x6db1b0712fca06f7030e4d49e4477178ae8a037e99a82095c7e2837b4982e6cb" + }, + { + "amount": "364.980953855429325", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0x838a453d9bd95a8826ec480e23d0b9f352550ecfee1e9fd2a6b95e9c1ae394ec" + }, + { + "amount": "355.41781053286935", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0xa19e2f313b5a01721ecd982b38a21384f907157339a373adcf63c7b811d5f1db" + }, { "amount": "325.1056930428465", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", @@ -17905,6 +18187,48 @@ "tranche_id": 2, "tx": "0x4303f2bca5e794329a9dd8b34e0bf4f29234f1fa5a920c48a8203b62529aba13" }, + { + "amount": "400.56886157179005", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0x36922428802b59ffa5372882e41946441a94a2adeeb9d85a8b701880d50e7a5e" + }, + { + "amount": "394.49820208894695", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0xe41aea73669c53b7a3687a3ee635cff1926833a692679899e01e837d69223351" + }, + { + "amount": "272.141858008646025", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0xb06fd9b057c2b92266a06f4d02a291882bb263f10ff5b4d35874aa9a5143bb54" + }, + { + "amount": "430.061399510802075", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0xd0ca6b4d7a1b8fe195a1401b975c9d38d844299a783ca402c9f440f9b3746abb" + }, + { + "amount": "261.063109006584075", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0x6db1b0712fca06f7030e4d49e4477178ae8a037e99a82095c7e2837b4982e6cb" + }, + { + "amount": "364.980953855429325", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0x838a453d9bd95a8826ec480e23d0b9f352550ecfee1e9fd2a6b95e9c1ae394ec" + }, + { + "amount": "355.41781053286935", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0xa19e2f313b5a01721ecd982b38a21384f907157339a373adcf63c7b811d5f1db" + }, { "amount": "325.1056930428465", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", @@ -18129,8 +18453,8 @@ } ], "total_tokens": "194999.1675", - "withdrawn_tokens": "18304.412614954827825", - "remaining_tokens": "176694.754885045172175" + "withdrawn_tokens": "20783.144809529895675", + "remaining_tokens": "174216.022690470104325" }, { "address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c", @@ -18337,10 +18661,17 @@ "tx": "0x17e1133669078791baf79b47369bc1e0c7146c2645d572f0797f4d5bf9d4a200" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "14997.49643037387568", + "user": "0xe3eB4CF43C072658401996b71D43eE26E573562D", + "tranche_id": 2, + "tx": "0x489b9996f152de920bd4704ff05cda064d45a3ac756fe09dde56dbdde5ede241" + } + ], "total_tokens": "150551.801", - "withdrawn_tokens": "0", - "remaining_tokens": "150551.801" + "withdrawn_tokens": "14997.49643037387568", + "remaining_tokens": "135554.30456962612432" }, { "address": "0x4d982Ab0823fD2f48e934a7be2bb0a5374a26148", @@ -18942,10 +19273,17 @@ "tx": "0x2fbd183386974d45954eb28b4def917a8c9dfb1cf6538d754378ba4ce9756126" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "1181.264644302466", + "user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0", + "tranche_id": 2, + "tx": "0xa67914a8bbc426c6faa561af9417fafae696f08823d534b37a93adc0801e66a7" + } + ], "total_tokens": "12362.05", - "withdrawn_tokens": "0", - "remaining_tokens": "12362.05" + "withdrawn_tokens": "1181.264644302466", + "remaining_tokens": "11180.785355697534" }, { "address": "0xb091D456d0dFCB94dcba6f355379056C5bb995fC", @@ -19569,8 +19907,8 @@ "tranche_start": "2021-11-05T00:00:00.000Z", "tranche_end": "2023-05-05T00:00:00.000Z", "total_added": "14597706.0446472999", - "total_removed": "2421946.596295493754033047", - "locked_amount": "7546061.93619634790916496991038218", + "total_removed": "2553288.680626734507145402", + "locked_amount": "7358916.80976112071634476419463288", "deposits": [ { "amount": "129284.449", @@ -19789,6 +20127,51 @@ "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", "tx": "0x20171309054732628e1b86149642280cde77c3318a2ef37ec6f4a40ff8126228" }, + { + "amount": "809.98883257047654925", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0x9531a4b8f6db038e5a1889be447cd1573c12c0c9e203038984dcc45de810ea6b" + }, + { + "amount": "727.95462472139826575", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0x8ae703a4a5f720f8f0e5e7f10e90b35ab67c3eef6448346c1e4526a9f8fe52ac" + }, + { + "amount": "490.3477527727869285", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0x61bf5aeef6deab3f2fcb49b0608e32af09bdbd9fdf4af90db69625c5e0a063d0" + }, + { + "amount": "811.06221670868926675", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0x61452de3a3ad51635b87a628675c86d4e431331bb450bd035b4c61ec2004a892" + }, + { + "amount": "480.4588858197533795", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0xaf1d33c608027752e7d1849706ccecf79c8f2d083b0a2027f2da8a5ee9af91e7" + }, + { + "amount": "675.7773352141219005", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0x1e24fe0b5acde09b05ef50ad8646de34c54689b9a357a3f57433d9dd6b6ec01d" + }, + { + "amount": "278.52501221863969625", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0x600792fee26b87f2584ddc24504058260ad922cc3e64b06508126e15e1c8ecae" + }, + { + "amount": "126690.906167169859826355", + "user": "0xa8679b60612Fb2e19d68964326CA02dCe6a08D08", + "tx": "0x9df3a9858514900037a6318f277e5bb3f6507daf6efa36ed0baa5aba877179b6" + }, + { + "amount": "377.0635040450272995", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0x4e19528b1c582b289b6f9e74a7bd3b832d303f4cc19478d847aacc7e2d03868a" + }, { "amount": "600.79246609513746875", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", @@ -21627,6 +22010,54 @@ "tranche_id": 3, "tx": "0x20171309054732628e1b86149642280cde77c3318a2ef37ec6f4a40ff8126228" }, + { + "amount": "809.98883257047654925", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0x9531a4b8f6db038e5a1889be447cd1573c12c0c9e203038984dcc45de810ea6b" + }, + { + "amount": "727.95462472139826575", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0x8ae703a4a5f720f8f0e5e7f10e90b35ab67c3eef6448346c1e4526a9f8fe52ac" + }, + { + "amount": "490.3477527727869285", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0x61bf5aeef6deab3f2fcb49b0608e32af09bdbd9fdf4af90db69625c5e0a063d0" + }, + { + "amount": "811.06221670868926675", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0x61452de3a3ad51635b87a628675c86d4e431331bb450bd035b4c61ec2004a892" + }, + { + "amount": "480.4588858197533795", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0xaf1d33c608027752e7d1849706ccecf79c8f2d083b0a2027f2da8a5ee9af91e7" + }, + { + "amount": "675.7773352141219005", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0x1e24fe0b5acde09b05ef50ad8646de34c54689b9a357a3f57433d9dd6b6ec01d" + }, + { + "amount": "278.52501221863969625", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0x600792fee26b87f2584ddc24504058260ad922cc3e64b06508126e15e1c8ecae" + }, + { + "amount": "377.0635040450272995", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0x4e19528b1c582b289b6f9e74a7bd3b832d303f4cc19478d847aacc7e2d03868a" + }, { "amount": "600.79246609513746875", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", @@ -23093,8 +23524,8 @@ } ], "total_tokens": "359123.469575", - "withdrawn_tokens": "173203.61934691521133675", - "remaining_tokens": "185919.85022808478866325" + "withdrawn_tokens": "177854.79751098610462275", + "remaining_tokens": "181268.67206401389537725" }, { "address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB", @@ -23856,6 +24287,12 @@ } ], "withdrawals": [ + { + "amount": "126690.906167169859826355", + "user": "0xa8679b60612Fb2e19d68964326CA02dCe6a08D08", + "tranche_id": 3, + "tx": "0x9df3a9858514900037a6318f277e5bb3f6507daf6efa36ed0baa5aba877179b6" + }, { "amount": "442758.921918885747690339", "user": "0xa8679b60612Fb2e19d68964326CA02dCe6a08D08", @@ -23864,8 +24301,8 @@ } ], "total_tokens": "1151073.5946347", - "withdrawn_tokens": "442758.921918885747690339", - "remaining_tokens": "708314.672715814252309661" + "withdrawn_tokens": "569449.828086055607516694", + "remaining_tokens": "581623.766548644392483306" }, { "address": "0x97E5985117F47c8d110Be1c422DdCB9bE9b46e62", @@ -24157,8 +24594,8 @@ "tranche_start": "2021-10-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "5778205.3912159303", - "total_removed": "1580907.252363731354286516", - "locked_amount": "2664591.30565361576883153539717868", + "total_removed": "1604355.105699796690650516", + "locked_amount": "2590649.13435475633919175491355577", "deposits": [ { "amount": "552496.6455", @@ -24307,6 +24744,21 @@ "user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90", "tx": "0x5bc74ee513c4bd0e56365efdb933004f2b259efc04e9e21ec59e286e8d0b7014" }, + { + "amount": "4156.884040508288931", + "user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90", + "tx": "0x5e0cb1c6af7cb8b1e90268e4e4fe4bf36dd15234d1cf3b2f26d0330c576aefe5" + }, + { + "amount": "12138.958071607841803", + "user": "0xa8679b60612Fb2e19d68964326CA02dCe6a08D08", + "tx": "0x3a9aec237b3192d7cd7a5bbedf622aed04cefbd4dd694b6ced84c96b4c9c7dd0" + }, + { + "amount": "7152.01122394920563", + "user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75", + "tx": "0xa638b3d7303ec1e1f00caed22c2a4dce6e1f3b029fa42bfbae73702bd8bc0542" + }, { "amount": "515.054756304992408", "user": "0x1b4B2372462b391006FAc69f09066Ae8504D75eC", @@ -24680,6 +25132,12 @@ } ], "withdrawals": [ + { + "amount": "12138.958071607841803", + "user": "0xa8679b60612Fb2e19d68964326CA02dCe6a08D08", + "tranche_id": 4, + "tx": "0x3a9aec237b3192d7cd7a5bbedf622aed04cefbd4dd694b6ced84c96b4c9c7dd0" + }, { "amount": "48688.022653199634303", "user": "0xa8679b60612Fb2e19d68964326CA02dCe6a08D08", @@ -24688,8 +25146,8 @@ } ], "total_tokens": "110499.5291", - "withdrawn_tokens": "48688.022653199634303", - "remaining_tokens": "61811.506446800365697" + "withdrawn_tokens": "60826.980724807476106", + "remaining_tokens": "49672.548375192523894" }, { "address": "0x8767d65677CabaD2050b764AEf40610f2f9796F5", @@ -24850,6 +25308,12 @@ } ], "withdrawals": [ + { + "amount": "7152.01122394920563", + "user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75", + "tranche_id": 4, + "tx": "0xa638b3d7303ec1e1f00caed22c2a4dce6e1f3b029fa42bfbae73702bd8bc0542" + }, { "amount": "10109.350358551032104", "user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75", @@ -24912,8 +25376,8 @@ } ], "total_tokens": "110499.5291", - "withdrawn_tokens": "53764.286493438849258", - "remaining_tokens": "56735.242606561150742" + "withdrawn_tokens": "60916.297717388054888", + "remaining_tokens": "49583.231382611945112" }, { "address": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3", @@ -25226,6 +25690,12 @@ "tranche_id": 4, "tx": "0x5bc74ee513c4bd0e56365efdb933004f2b259efc04e9e21ec59e286e8d0b7014" }, + { + "amount": "4156.884040508288931", + "user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90", + "tranche_id": 4, + "tx": "0x5e0cb1c6af7cb8b1e90268e4e4fe4bf36dd15234d1cf3b2f26d0330c576aefe5" + }, { "amount": "3615.167281564930431", "user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90", @@ -25318,8 +25788,8 @@ } ], "total_tokens": "331498.5873", - "withdrawn_tokens": "177945.043682780683572", - "remaining_tokens": "153553.543617219316428" + "withdrawn_tokens": "182101.927723288972503", + "remaining_tokens": "149396.659576711027497" }, { "address": "0x16da609341ed67750A8BCC5AAa2005471006Cd77", @@ -25401,8 +25871,8 @@ "tranche_start": "2022-06-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z", "total_added": "472355.6199999996", - "total_removed": "580.0445766491", - "locked_amount": "405379.97872978654505782214916288", + "total_removed": "923.07230685326", + "locked_amount": "396321.34347845219603071008320652", "deposits": [ { "amount": "3000", @@ -32031,6 +32501,41 @@ "user": "0x7708B79e2bc3b7ab816E66aF33A8c9385f0FcA4b", "tx": "0x3ce37e2dab3f254ec65285b01ded923f8acc03d4e0ed0fdae383bcc46c94800e" }, + { + "amount": "42.936872145", + "user": "0xdc040917f80aC350460e55467e4B6c52Fc5e220E", + "tx": "0x1177079901d6f5b6a6b2e8597f7c7b774e5fefa0f820ffe47a0fcf1dd274172e" + }, + { + "amount": "57.78402967656", + "user": "0xE0961E5C38D733bE59369BB62D6b3c3B5FADf3d2", + "tx": "0x247b8b961532a75d6863a4c613e57a85122c0e2bfa988e24ef00bb44b270d329" + }, + { + "amount": "58.368150684", + "user": "0xD1549131c3f33B69f31f3Fc6D581d49d6c8dc6f0", + "tx": "0x4d37aa485a412a9dd32f724de7e6c65ba69a95be216593918ef63419b3e4f6c7" + }, + { + "amount": "25.312227296", + "user": "0x4eD2b3c68BB4fda084ce1591a210F4aC8b71234A", + "tx": "0x2e5744f69f760d73bfa7b5b2d956794857464ae53ee175884ec74f34ef650264" + }, + { + "amount": "30.893512176", + "user": "0x175BEB5A0b07C9FBb640CF8b97352B3F1534E7b3", + "tx": "0x57e1f815f47d06c8723f664685128ae574ee654101d69739d71c51874084eaad" + }, + { + "amount": "49.4479147616", + "user": "0x74Da54F44975a1C224ABb5CAB8e5fc38a268B425", + "tx": "0x5bb475b5a470590db1e4b81821c7ef846964899474f840a64761cb22c1fe2797" + }, + { + "amount": "78.285023465", + "user": "0xa9a677b0a3Be231C0654CabB0Eaa0A72E91B3E8d", + "tx": "0xed231b251719fd7039ef78305ac3e734042ef222d9c8c4eeb0bb69daa298ffbc" + }, { "amount": "18.521226534", "user": "0xfeF4b56A370135A17Eef5ba8f5c308451a64Cf86", @@ -43512,10 +44017,17 @@ "tx": "0x716a7be06da5a7a3d8038907974887a31805ea8eeab5d130cba528e4d2094d9f" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "30.893512176", + "user": "0x175BEB5A0b07C9FBb640CF8b97352B3F1534E7b3", + "tranche_id": 5, + "tx": "0x57e1f815f47d06c8723f664685128ae574ee654101d69739d71c51874084eaad" + } + ], "total_tokens": "200", - "withdrawn_tokens": "0", - "remaining_tokens": "200" + "withdrawn_tokens": "30.893512176", + "remaining_tokens": "169.106487824" }, { "address": "0xB95C140fB5c6c881eDc32be1e220D2bD5D8f9c36", @@ -43855,6 +44367,12 @@ } ], "withdrawals": [ + { + "amount": "25.312227296", + "user": "0x4eD2b3c68BB4fda084ce1591a210F4aC8b71234A", + "tranche_id": 5, + "tx": "0x2e5744f69f760d73bfa7b5b2d956794857464ae53ee175884ec74f34ef650264" + }, { "amount": "34.722323692", "user": "0x4eD2b3c68BB4fda084ce1591a210F4aC8b71234A", @@ -43863,8 +44381,8 @@ } ], "total_tokens": "400", - "withdrawn_tokens": "34.722323692", - "remaining_tokens": "365.277676308" + "withdrawn_tokens": "60.034550988", + "remaining_tokens": "339.965449012" }, { "address": "0x5c90765F50629570738fEe7b7FA82ae118f81Ed1", @@ -45803,10 +46321,17 @@ "tx": "0xb1425d9b0d5f10c5b06236d6f9d61ebddd21c4027e0f41deb5ec227a1753c715" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "57.78402967656", + "user": "0xE0961E5C38D733bE59369BB62D6b3c3B5FADf3d2", + "tranche_id": 5, + "tx": "0x247b8b961532a75d6863a4c613e57a85122c0e2bfa988e24ef00bb44b270d329" + } + ], "total_tokens": "396", - "withdrawn_tokens": "0", - "remaining_tokens": "396" + "withdrawn_tokens": "57.78402967656", + "remaining_tokens": "338.21597032344" }, { "address": "0xbd6aeC2cd1Cc9fE80b4154E1cCD29bBe15854E8d", @@ -47255,10 +47780,17 @@ "tx": "0xd23813c30e93f3867eaa257b7aef7052a050b1ee1c1a90102a3f40c5d989fe82" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "49.4479147616", + "user": "0x74Da54F44975a1C224ABb5CAB8e5fc38a268B425", + "tranche_id": 5, + "tx": "0x5bb475b5a470590db1e4b81821c7ef846964899474f840a64761cb22c1fe2797" + } + ], "total_tokens": "320", - "withdrawn_tokens": "0", - "remaining_tokens": "320" + "withdrawn_tokens": "49.4479147616", + "remaining_tokens": "270.5520852384" }, { "address": "0xB7D725753a300FeD6D13f3951D890856EF0C6e30", @@ -49712,10 +50244,17 @@ "tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "58.368150684", + "user": "0xD1549131c3f33B69f31f3Fc6D581d49d6c8dc6f0", + "tranche_id": 5, + "tx": "0x4d37aa485a412a9dd32f724de7e6c65ba69a95be216593918ef63419b3e4f6c7" + } + ], "total_tokens": "400", - "withdrawn_tokens": "0", - "remaining_tokens": "400" + "withdrawn_tokens": "58.368150684", + "remaining_tokens": "341.631849316" }, { "address": "0xaCc993013812e5524102474Ba78f3a278c6d2eFc", @@ -49974,10 +50513,17 @@ "tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "78.285023465", + "user": "0xa9a677b0a3Be231C0654CabB0Eaa0A72E91B3E8d", + "tranche_id": 5, + "tx": "0xed231b251719fd7039ef78305ac3e734042ef222d9c8c4eeb0bb69daa298ffbc" + } + ], "total_tokens": "500", - "withdrawn_tokens": "0", - "remaining_tokens": "500" + "withdrawn_tokens": "78.285023465", + "remaining_tokens": "421.714976535" }, { "address": "0x3aDDC4bF402118c9453a51B23fD3Ea3707028273", @@ -50004,10 +50550,17 @@ "tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "42.936872145", + "user": "0xdc040917f80aC350460e55467e4B6c52Fc5e220E", + "tranche_id": 5, + "tx": "0x1177079901d6f5b6a6b2e8597f7c7b774e5fefa0f820ffe47a0fcf1dd274172e" + } + ], "total_tokens": "300", - "withdrawn_tokens": "0", - "remaining_tokens": "300" + "withdrawn_tokens": "42.936872145", + "remaining_tokens": "257.063127855" }, { "address": "0x8D01053B4f4857Ff453CAeDBf8387Cd57a5a2A2b", @@ -51282,7 +51835,7 @@ "tranche_start": "2021-12-05T00:00:00.000Z", "tranche_end": "2022-06-05T00:00:00.000Z", "total_added": "171288.42", - "total_removed": "40866.0912801057989", + "total_removed": "43101.6840385532989", "locked_amount": "0", "deposits": [ { @@ -55527,6 +56080,76 @@ "user": "0x72bB3eB15F285F8e2893a29287186d5210590C22", "tx": "0xc47354b308b7512768605c9e908e87620df22c197d7d2c769728f1d64dc164d3" }, + { + "amount": "150", + "user": "0x6EE016F4512C910b401B18087BBAC15412dD97cA", + "tx": "0xbc09d1b5e70ce11912142519f7372859423f6475ce3c0f38c098b6bdf8be4480" + }, + { + "amount": "250", + "user": "0xb21715Fe20319202d7AcC12320A8121760EF83dD", + "tx": "0x409e2ea40dba97134578e1b0fd560c7652aac5939bb830f0fad1180cb3321ae1" + }, + { + "amount": "108.1327584475", + "user": "0x207EeAb5F72e3F9e831428eF54dCaDc8B8727c85", + "tx": "0x73eb6423003425d47a97876737e6c1fe31f492c7cd196b472f5565c8f2f2ce22" + }, + { + "amount": "250", + "user": "0x823bedF06B12FB80cf60f0c3979E12b4Cd980E55", + "tx": "0x42311b1f41b9a5589ffaf83c0406aa6af812038b8990a294715d710afab57a91" + }, + { + "amount": "0", + "user": "0x823bedF06B12FB80cf60f0c3979E12b4Cd980E55", + "tx": "0x342315d292800d3c0da2696468b90c9bb14ff7734daadde2a73b1adeab9f170a" + }, + { + "amount": "250", + "user": "0xD53c2eaE5f37009afF895AA2c7EFbBF757d96F2e", + "tx": "0xf43ae78aedbdba7880b34b4721f5d3e2776d3144fda27c694adcf9c11e0f64a9" + }, + { + "amount": "250", + "user": "0x027306C886Da27fa29Ce9B4DB489C6E44d2ECc55", + "tx": "0xd179cb428095eb8fb78a8e5cce7f4845a75756d817cbadbeba05fcf555d72c12" + }, + { + "amount": "0", + "user": "0x823bedF06B12FB80cf60f0c3979E12b4Cd980E55", + "tx": "0xd2c0a9a3339417736df0a526a2d393774e86c785c9f99e93dd0cee5f107f7ffd" + }, + { + "amount": "250", + "user": "0x9776C82c8A4E9A388829cF4bd78D3Cc7B25E09f3", + "tx": "0xbd04c7ab0380cef2a23f265303bf1174d650f00931c71bb72e65f3200a5f0587" + }, + { + "amount": "250", + "user": "0xee87e4d25BFe3a1Cb8E642D8e73F1E2C7940c4fA", + "tx": "0xf87619c8e6acf02d86f4535722006fef3fa8ca3ac837999d23934ac6f7b3467b" + }, + { + "amount": "100", + "user": "0x6b98423409A8842Dd6b9c5D3D4f29a3d2Db88D51", + "tx": "0x0003343973183b55a80328c63d51f168fb32060273471c267771bcef4f49bcea" + }, + { + "amount": "30", + "user": "0xe05Bf1470cE307B50edd9f6847D71Ef2Ed701388", + "tx": "0x8e8a105cbb6e0b52df10879ac5c985d8449edcc54d17fddf1481288bf03469c1" + }, + { + "amount": "247.46", + "user": "0x8fa0C224289292F13a4D719FE3957B8cDE1c687B", + "tx": "0x7e83accbb0f2c9f633578838ef5e928b8f35ee7563d2981e64aac69e6eba344d" + }, + { + "amount": "100", + "user": "0x099B26208FFe4CF7DE5d0c12bF5fcCf6aA90A7Ca", + "tx": "0xef953ed5449b70426b128c591e38931bb8d8f64bd0d0016607ad3aafe3454e85" + }, { "amount": "250", "user": "0x1f7C20Bd98B2f346A58d2c5faaFc940E0C36450E", @@ -57879,10 +58502,17 @@ "tx": "0xee02e72ef8e0755982704385da512cc12c8ff0ea7b58056fa3d5a5932394e9ca" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "30", + "user": "0xe05Bf1470cE307B50edd9f6847D71Ef2Ed701388", + "tranche_id": 6, + "tx": "0x8e8a105cbb6e0b52df10879ac5c985d8449edcc54d17fddf1481288bf03469c1" + } + ], "total_tokens": "30", - "withdrawn_tokens": "0", - "remaining_tokens": "30" + "withdrawn_tokens": "30", + "remaining_tokens": "0" }, { "address": "0x389f68402c3355Af05566faC84914f8E7caabc52", @@ -59505,10 +60135,17 @@ "tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "150", + "user": "0x6EE016F4512C910b401B18087BBAC15412dD97cA", + "tranche_id": 6, + "tx": "0xbc09d1b5e70ce11912142519f7372859423f6475ce3c0f38c098b6bdf8be4480" + } + ], "total_tokens": "150", - "withdrawn_tokens": "0", - "remaining_tokens": "150" + "withdrawn_tokens": "150", + "remaining_tokens": "0" }, { "address": "0xaA6522d3c033Adf9C149408526C539aaa91d5b67", @@ -60140,10 +60777,29 @@ "tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "250", + "user": "0x823bedF06B12FB80cf60f0c3979E12b4Cd980E55", + "tranche_id": 6, + "tx": "0x42311b1f41b9a5589ffaf83c0406aa6af812038b8990a294715d710afab57a91" + }, + { + "amount": "0", + "user": "0x823bedF06B12FB80cf60f0c3979E12b4Cd980E55", + "tranche_id": 6, + "tx": "0x342315d292800d3c0da2696468b90c9bb14ff7734daadde2a73b1adeab9f170a" + }, + { + "amount": "0", + "user": "0x823bedF06B12FB80cf60f0c3979E12b4Cd980E55", + "tranche_id": 6, + "tx": "0xd2c0a9a3339417736df0a526a2d393774e86c785c9f99e93dd0cee5f107f7ffd" + } + ], "total_tokens": "250", - "withdrawn_tokens": "0", - "remaining_tokens": "250" + "withdrawn_tokens": "250", + "remaining_tokens": "0" }, { "address": "0x251ff8E9C055B19f76aDe0890b1Abc8b2aA91aE8", @@ -62578,10 +63234,17 @@ "tx": "0xc7dd4c2b995cc486fcd8b7892cd79f8fb393ada004dc68cf66ec82d99b35763c" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "250", + "user": "0xD53c2eaE5f37009afF895AA2c7EFbBF757d96F2e", + "tranche_id": 6, + "tx": "0xf43ae78aedbdba7880b34b4721f5d3e2776d3144fda27c694adcf9c11e0f64a9" + } + ], "total_tokens": "250", - "withdrawn_tokens": "0", - "remaining_tokens": "250" + "withdrawn_tokens": "250", + "remaining_tokens": "0" }, { "address": "0x99492aF57325BA3971542B9ea4036186BE50fa07", @@ -63274,10 +63937,17 @@ "tx": "0x057f65938b1360b6d2c4ebf5e789c67897cf60cb6a13f947004def03891afbd8" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "250", + "user": "0x027306C886Da27fa29Ce9B4DB489C6E44d2ECc55", + "tranche_id": 6, + "tx": "0xd179cb428095eb8fb78a8e5cce7f4845a75756d817cbadbeba05fcf555d72c12" + } + ], "total_tokens": "250", - "withdrawn_tokens": "0", - "remaining_tokens": "250" + "withdrawn_tokens": "250", + "remaining_tokens": "0" }, { "address": "0x4C916514eb2721A2d74387DAc3b925568f488177", @@ -64531,10 +65201,17 @@ "tx": "0xadc25e73736cd85e84fee16f2d7ee41f16f7deb83a52ceb82b327974063964f7" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "250", + "user": "0x9776C82c8A4E9A388829cF4bd78D3Cc7B25E09f3", + "tranche_id": 6, + "tx": "0xbd04c7ab0380cef2a23f265303bf1174d650f00931c71bb72e65f3200a5f0587" + } + ], "total_tokens": "250", - "withdrawn_tokens": "0", - "remaining_tokens": "250" + "withdrawn_tokens": "250", + "remaining_tokens": "0" }, { "address": "0xaf8E293e016E0d7cdb187373020ae2510acCd503", @@ -66844,10 +67521,17 @@ "tx": "0x9f916cf09e8a3c4ade0ffce5190db464d0a2b1dadba78e1ee7ba5d6e751d6148" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "250", + "user": "0xb21715Fe20319202d7AcC12320A8121760EF83dD", + "tranche_id": 6, + "tx": "0x409e2ea40dba97134578e1b0fd560c7652aac5939bb830f0fad1180cb3321ae1" + } + ], "total_tokens": "250", - "withdrawn_tokens": "0", - "remaining_tokens": "250" + "withdrawn_tokens": "250", + "remaining_tokens": "0" }, { "address": "0x8433BF349d4f71FB37c8CacF5D6D4863D43EaE23", @@ -66959,6 +67643,12 @@ } ], "withdrawals": [ + { + "amount": "108.1327584475", + "user": "0x207EeAb5F72e3F9e831428eF54dCaDc8B8727c85", + "tranche_id": 6, + "tx": "0x73eb6423003425d47a97876737e6c1fe31f492c7cd196b472f5565c8f2f2ce22" + }, { "amount": "98.96596459", "user": "0x207EeAb5F72e3F9e831428eF54dCaDc8B8727c85", @@ -66973,8 +67663,8 @@ } ], "total_tokens": "250", - "withdrawn_tokens": "141.8672415525", - "remaining_tokens": "108.1327584475" + "withdrawn_tokens": "250", + "remaining_tokens": "0" }, { "address": "0xc9f98A16690fa9D19c5A475Cd818d030fb035b27", @@ -69303,10 +69993,17 @@ "tx": "0x3a0390dffa4280c286016566b29c62e4cec36c6504f7ac3d1de80d22b2903008" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "247.46", + "user": "0x8fa0C224289292F13a4D719FE3957B8cDE1c687B", + "tranche_id": 6, + "tx": "0x7e83accbb0f2c9f633578838ef5e928b8f35ee7563d2981e64aac69e6eba344d" + } + ], "total_tokens": "247.46", - "withdrawn_tokens": "0", - "remaining_tokens": "247.46" + "withdrawn_tokens": "247.46", + "remaining_tokens": "0" }, { "address": "0xcDeB8494F9738605036B4Bb2AfdF192Ca8AB076B", @@ -69891,10 +70588,17 @@ "tx": "0xc8541da6a57f410b6faba47a5e5184bae700193b7bd042914fffc562114d92f5" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "100", + "user": "0x6b98423409A8842Dd6b9c5D3D4f29a3d2Db88D51", + "tranche_id": 6, + "tx": "0x0003343973183b55a80328c63d51f168fb32060273471c267771bcef4f49bcea" + } + ], "total_tokens": "100", - "withdrawn_tokens": "0", - "remaining_tokens": "100" + "withdrawn_tokens": "100", + "remaining_tokens": "0" }, { "address": "0xbCc28D073f7a96c0568D36FdB21A98105ee60Ca3", @@ -70166,10 +70870,17 @@ "tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "250", + "user": "0xee87e4d25BFe3a1Cb8E642D8e73F1E2C7940c4fA", + "tranche_id": 6, + "tx": "0xf87619c8e6acf02d86f4535722006fef3fa8ca3ac837999d23934ac6f7b3467b" + } + ], "total_tokens": "250", - "withdrawn_tokens": "0", - "remaining_tokens": "250" + "withdrawn_tokens": "250", + "remaining_tokens": "0" }, { "address": "0x83dF74E7114079b98e69089EDcDc8DE377fB7cB0", @@ -70663,10 +71374,17 @@ "tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "100", + "user": "0x099B26208FFe4CF7DE5d0c12bF5fcCf6aA90A7Ca", + "tranche_id": 6, + "tx": "0xef953ed5449b70426b128c591e38931bb8d8f64bd0d0016607ad3aafe3454e85" + } + ], "total_tokens": "100", - "withdrawn_tokens": "0", - "remaining_tokens": "100" + "withdrawn_tokens": "100", + "remaining_tokens": "0" }, { "address": "0x68091918EaDB98f54E8fC179870aB6F755736174", diff --git a/apps/static/src/assets/stagnet1-tranches.json b/apps/static/src/assets/stagnet1-tranches.json index e30651e0b..5506d7234 100644 --- a/apps/static/src/assets/stagnet1-tranches.json +++ b/apps/static/src/assets/stagnet1-tranches.json @@ -38,7 +38,7 @@ "tranche_end": "2022-11-26T13:48:10.000Z", "total_added": "100", "total_removed": "0", - "locked_amount": "33.64974949264333", + "locked_amount": "31.73197932521563", "deposits": [ { "amount": "100", @@ -242,7 +242,7 @@ "tranche_end": "2022-10-12T00:53:20.000Z", "total_added": "1100", "total_removed": "673.04388635", - "locked_amount": "232.909199010654485", + "locked_amount": "211.813727168949774", "deposits": [ { "amount": "1000", diff --git a/apps/static/src/assets/testnet-tranches.json b/apps/static/src/assets/testnet-tranches.json index c540ff2e6..31bd14d7b 100644 --- a/apps/static/src/assets/testnet-tranches.json +++ b/apps/static/src/assets/testnet-tranches.json @@ -69,7 +69,7 @@ "tranche_end": "2022-10-12T00:53:20.000Z", "total_added": "1010.000000000000000001", "total_removed": "668.4622323651", - "locked_amount": "213.85302384576360370021173566717402337", + "locked_amount": "194.48351312785388340019255793378995434", "deposits": [ { "amount": "1000", diff --git a/apps/token-e2e/.env b/apps/token-e2e/.env index 6d3d52c1f..253cd8dda 100644 --- a/apps/token-e2e/.env +++ b/apps/token-e2e/.env @@ -12,7 +12,8 @@ NX_ETHEREUM_CHAIN_ID=1440 NX_ETH_URL_CONNECT=1 NX_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session NX_LOCAL_PROVIDER_URL=http://localhost:8545/ +NX_VEGA_WALLET_URL=http://localhost:1789/api/v1 #Test configuration variables CYPRESS_FAIRGROUND=false -CYPRESS_INCLUDE_FLOWS=true \ No newline at end of file +CYPRESS_INCLUDE_FLOWS=true diff --git a/apps/token-e2e/src/integration/flow/staking-flow.cy.js b/apps/token-e2e/src/integration/flow/staking-flow.cy.js index 3621305d4..0d7d826bd 100644 --- a/apps/token-e2e/src/integration/flow/staking-flow.cy.js +++ b/apps/token-e2e/src/integration/flow/staking-flow.cy.js @@ -25,7 +25,8 @@ const ethWalletContainer = '[data-testid="ethereum-wallet"]'; const txTimeout = { timeout: 40000 }; const epochTimeout = { timeout: 10000 }; -context('Staking Tab - with eth and vega wallets connected', function () { +// Tests skipped because of change of the UI of data nodes +context.skip('Staking Tab - with eth and vega wallets connected', function () { before('visit staking tab and connect vega wallet', function () { cy.vega_wallet_import(); cy.visit('/'); diff --git a/apps/token-e2e/src/integration/flow/token-association-flow.cy.js b/apps/token-e2e/src/integration/flow/token-association-flow.cy.js index 12074d615..394023492 100644 --- a/apps/token-e2e/src/integration/flow/token-association-flow.cy.js +++ b/apps/token-e2e/src/integration/flow/token-association-flow.cy.js @@ -1,4 +1,4 @@ -const validatorList = '[data-testid="node-list-item-name"]'; +const validatorsGrid = '[data-testid="validators-grid"]'; const ethWalletContainer = '[data-testid="ethereum-wallet"]'; const ethWalletAssociatedBalances = '[data-testid="eth-wallet-associated-balances"]'; @@ -23,7 +23,7 @@ context( cy.ethereum_wallet_connect(); cy.navigate_to('staking'); cy.wait_for_spinner(); - cy.get(validatorList).first().invoke('text').as('validatorName'); + cy.get(validatorsGrid).should('be.visible'); }); describe('Eth wallet - contains VEGA tokens', function () { diff --git a/apps/token-e2e/src/integration/view/wallet-vega.cy.js b/apps/token-e2e/src/integration/view/wallet-vega.cy.js new file mode 100644 index 000000000..1ab4d69db --- /dev/null +++ b/apps/token-e2e/src/integration/view/wallet-vega.cy.js @@ -0,0 +1,287 @@ +const walletContainer = '[data-testid="vega-wallet"]'; +const walletHeader = '[data-testid="wallet-header"] h1'; +const connectButton = '[data-testid="connect-vega"]'; +const getVegaLink = '[data-testid="link"]'; +const dialog = '[role="dialog"]'; +const dialogHeader = '[data-testid="dialog-title"]'; +const connectorsList = '[data-testid="connectors-list"]'; +const dialogCloseBtn = '[data-testid="dialog-close"]'; +const restConnectorForm = '[data-testid="rest-connector-form"]'; +const restUrl = '#url'; +const restWallet = '#wallet'; +const restPassphrase = '#passphrase'; +const restConnectBtn = '[type="submit"]'; +const accountNo = '[data-testid="vega-account-truncated"]'; +const walletName = '[data-testid="wallet-name"]'; +const currencyTitle = '[data-testid="currency-title"]'; +const currencyValue = '[data-testid="currency-value"]'; +const vegaUnstaked = '[data-testid="vega-wallet-balance-unstaked"] .text-right'; +const governanceBtn = '[href="/governance"]'; +const stakingBtn = '[href="/staking"]'; +const manageLink = '[data-testid="manage-vega-wallet"]'; +const dialogWalletName = `[data-testid="key-${Cypress.env( + 'vegaWalletPublicKey' +)}"] h2`; +const dialogVegaKey = '[data-testid="vega-public-key-full"]'; +const dialogDisconnectBtn = '[data-testid="disconnect"]'; +const copyPublicKeyBtn = '[data-testid="copy-vega-public-key"]'; + +context('Vega Wallet - verify elements on widget', function () { + before('visit token home page', function () { + cy.visit('/'); + }); + + describe('with wallets disconnected', function () { + before('wait for widget to load', function () { + cy.get(walletContainer, { timeout: 10000 }).should('be.visible'); + }); + + it('should have VEGA WALLET header visible', function () { + cy.get(walletContainer).within(() => { + cy.get(walletHeader) + .should('be.visible') + .and('have.text', 'Vega Wallet'); + }); + }); + + it('should have Connect Vega button visible', function () { + cy.get(walletContainer).within(() => { + cy.get(connectButton) + .should('be.visible') + .and('have.text', 'Connect Vega wallet to use associated $VEGA'); + }); + }); + + it('should have Get a Vega wallet link visible', function () { + cy.get(walletContainer).within(() => { + cy.get(getVegaLink) + .should('be.visible') + .and('have.text', 'Get a Vega wallet') + .and('have.attr', 'href', 'https://vega.xyz/wallet'); + }); + }); + }); + + describe('when connect button clicked', function () { + before('click connect vega wallet button', function () { + cy.get(walletContainer).within(() => { + cy.get(connectButton).click(); + }); + }); + + it('should have Connect Vega header visible', function () { + cy.get(dialog).within(() => { + cy.get(dialogHeader) + .should('be.visible') + .and('have.text', 'Connect to your Vega Wallet'); + }); + }); + + it('should have REST connector visible on list', function () { + cy.get(connectorsList).within(() => { + cy.get('button').should('be.visible').and('have.text', 'rest provider'); + }); + }); + + it('should have close button visible', function () { + cy.get(dialog).within(() => { + cy.get(dialogCloseBtn).should('be.visible'); + }); + }); + }); + + describe('when rest connector form opened', function () { + before('click rest provider link', function () { + cy.get(connectorsList).within(() => { + cy.get('button').click(); + }); + }); + + it('should have url field visible', function () { + cy.get(restConnectorForm).within(() => { + cy.get(restUrl) + .should('be.visible') + .and('have.value', 'http://localhost:1789/api/v1'); + }); + }); + + it('should have wallet field visible', function () { + cy.get(restConnectorForm).within(() => { + cy.get(restWallet).should('be.visible'); + }); + }); + + it('should have password field visible', function () { + cy.get(restConnectorForm).within(() => { + cy.get(restPassphrase).should('be.visible'); + }); + }); + + it('should have connect button visible', function () { + cy.get(restConnectorForm).within(() => { + cy.get(restConnectBtn).should('be.visible').and('have.text', 'Connect'); + }); + }); + + it('should have Connect Vega header visible', function () { + cy.get(dialog).within(() => { + cy.get(dialogHeader) + .should('be.visible') + .and('have.text', 'Connect to your Vega Wallet'); + }); + }); + + it('should have close button visible', function () { + cy.get(dialog).within(() => { + cy.get(dialogCloseBtn).should('be.visible'); + }); + }); + + // after('close dialog', function () { + // cy.get(dialogCloseBtn).click().should('not.exist'); + // }); - to be changed when dialog state is fixed - https://github.com/vegaprotocol/frontend-monorepo/issues/838 + }); + + describe('when vega wallet connected', function () { + before('connect vega wallet', function () { + cy.vega_wallet_import(); + + // cy.vega_wallet_connect(); - to be changed when dialog state is fixed - https://github.com/vegaprotocol/frontend-monorepo/issues/838 + // then code below can be removed + cy.get(restConnectorForm).within(() => { + cy.get('#wallet').click().type(Cypress.env('vegaWalletName')); + cy.get('#passphrase').click().type(Cypress.env('vegaWalletPassphrase')); + cy.get('button').contains('Connect').click(); + }); + }); + + it('should have VEGA WALLET header visible', function () { + cy.get(walletContainer).within(() => { + cy.get(walletHeader) + .should('be.visible') + .and('have.text', 'Vega Wallet'); + }); + }); + + it('should have truncated account number visible', function () { + cy.get(walletContainer).within(() => { + cy.get(accountNo) + .should('be.visible') + .and('have.text', Cypress.env('vegaWalletPublicKeyShort')); + }); + }); + + it('should have wallet name visible', function () { + cy.get(walletContainer).within(() => { + cy.get(walletName) + .should('be.visible') + .and('have.text', `${Cypress.env('vegaWalletName')} key 1`); + }); + }); + + it('should have Vega Associated currency title visible', function () { + cy.get(walletContainer).within(() => { + cy.get(currencyTitle) + .should('be.visible') + .and('have.text', `VEGAAssociated`); + }); + }); + + it('should have Vega Associated currency value visible', function () { + cy.get(walletContainer).within(() => { + cy.get(currencyValue) + .should('be.visible') + .and('have.text', `0.000000000000000000`); + }); + }); + + it('should have Unstaked value visible', function () { + cy.get(walletContainer).within(() => { + cy.get(vegaUnstaked) + .should('be.visible') + .and('have.text', `0.000000000000000000`); + }); + }); + + it('should have Governance button visible', function () { + cy.get(walletContainer).within(() => { + cy.get(governanceBtn) + .should('be.visible') + .and('have.text', 'Governance'); + }); + }); + + it('should have Staking button visible', function () { + cy.get(walletContainer).within(() => { + cy.get(stakingBtn).should('be.visible').and('have.text', 'Staking'); + }); + }); + + it('should have Manage link visible', function () { + cy.get(walletContainer).within(() => { + cy.get(manageLink).should('be.visible').and('have.text', 'Manage'); + }); + }); + }); + + describe('when Manage dialog opened', function () { + before('click Manage link', function () { + cy.get(walletContainer).within(() => { + cy.get(manageLink).click(); + }); + }); + + it('should have SELECT A VEGA KEY dialog title visible', function () { + cy.get(dialog).within(() => { + cy.get(dialogHeader) + .should('be.visible') + .and('have.text', 'SELECT A VEGA KEY'); + }); + }); + + it('should have wallet name visible', function () { + cy.get(dialog).within(() => { + cy.get(dialogWalletName) + .should('be.visible') + .and('have.text', `${Cypress.env('vegaWalletName')} key 1`); + }); + }); + + it('should have vega wallet public key visible', function () { + cy.get(dialog).within(() => { + cy.get(dialogVegaKey) + .should('be.visible') + .and('have.text', `${Cypress.env('vegaWalletPublicKey')}`); + }); + }); + + it('should have copy public key button visible', function () { + cy.get(dialog).within(() => { + cy.get(copyPublicKeyBtn).should('be.visible').and('have.text', 'Copy'); + }); + }); + + it('should have close button visible', function () { + cy.get(dialog).within(() => { + cy.get(dialogCloseBtn).should('be.visible'); + }); + }); + + it('should have vega Disconnect all keys button visible', function () { + cy.get(dialog).within(() => { + cy.get(dialogDisconnectBtn) + .should('be.visible') + .and('have.text', 'Disconnect all keys'); + }); + }); + + it('should be able to disconnect all keys', function () { + cy.get(dialog).within(() => { + cy.get(dialogDisconnectBtn).click(); + }); + cy.get(walletContainer).within(() => { + cy.get(connectButton).should('be.visible'); + }); + }); + }); +}); diff --git a/apps/token-e2e/src/support/wallet-teardown.functions.js b/apps/token-e2e/src/support/wallet-teardown.functions.js index 46eabf2f6..b34700bfe 100644 --- a/apps/token-e2e/src/support/wallet-teardown.functions.js +++ b/apps/token-e2e/src/support/wallet-teardown.functions.js @@ -75,7 +75,7 @@ Cypress.Commands.add( (stakingBridgeContract) => { cy.highlight('Tearing down staking tokens from vega wallet if present'); cy.wrap( - stakingBridgeContract.stakeBalance(ethWalletPubKey, vegaWalletPubKey), + stakingBridgeContract.stake_balance(ethWalletPubKey, vegaWalletPubKey), { timeout: transactionTimeout, log: false, @@ -83,7 +83,7 @@ Cypress.Commands.add( ).then((stake_amount) => { if (String(stake_amount) != '0') { cy.wrap( - stakingBridgeContract.removeStake(stake_amount, vegaWalletPubKey), + stakingBridgeContract.remove_stake(stake_amount, vegaWalletPubKey), { timeout: transactionTimeout, log: false } ).then((tx) => { cy.wait_for_transaction(tx); @@ -95,12 +95,12 @@ Cypress.Commands.add( Cypress.Commands.add('vega_wallet_teardown_vesting', (vestingContract) => { cy.highlight('Tearing down vesting tokens from vega wallet if present'); - cy.wrap(vestingContract.stakeBalance(ethWalletPubKey, vegaWalletPubKey), { + cy.wrap(vestingContract.stake_balance(ethWalletPubKey, vegaWalletPubKey), { timeout: transactionTimeout, log: false, }).then((vesting_amount) => { if (String(vesting_amount) != '0') { - cy.wrap(vestingContract.removeStake(vesting_amount, vegaWalletPubKey), { + cy.wrap(vestingContract.remove_stake(vesting_amount, vegaWalletPubKey), { timeout: transactionTimeout, log: false, }).then((tx) => { diff --git a/apps/token/.env b/apps/token/.env index c5982dfb4..b254c7be6 100644 --- a/apps/token/.env +++ b/apps/token/.env @@ -8,6 +8,7 @@ NX_FAIRGROUND=false NX_IS_NEW_BRIDGE_CONTRACT=true NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}' NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions +NX_VEGA_WALLET_URL=http://localhost:1789/api/v1 #Test configuration variables CYPRESS_FAIRGROUND=false diff --git a/apps/token/src/app.tsx b/apps/token/src/app.tsx index 4c9e2edcf..15c1f6e73 100644 --- a/apps/token/src/app.tsx +++ b/apps/token/src/app.tsx @@ -49,7 +49,7 @@ const AppContainer = () => { <> -
+
diff --git a/apps/token/src/components/splash-loader/splash-loader.tsx b/apps/token/src/components/splash-loader/splash-loader.tsx index 5ea68a36c..34c3366bb 100644 --- a/apps/token/src/components/splash-loader/splash-loader.tsx +++ b/apps/token/src/components/splash-loader/splash-loader.tsx @@ -25,7 +25,7 @@ export const SplashLoader = ({ text = 'Loading' }: { text?: string }) => { ); })}
-
{text}
+
{text}
); }; diff --git a/apps/token/src/components/vega-wallet/hooks.ts b/apps/token/src/components/vega-wallet/hooks.ts index da7df7337..54a3b3a59 100644 --- a/apps/token/src/components/vega-wallet/hooks.ts +++ b/apps/token/src/components/vega-wallet/hooks.ts @@ -18,6 +18,7 @@ import type { } from './__generated__/Delegations'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { useContracts } from '../../contexts/contracts/contracts-context'; +import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers'; const DELEGATIONS_QUERY = gql` query Delegations($partyId: ID!) { @@ -117,7 +118,7 @@ export const usePollForDelegations = () => { .filter((a) => a.type === AccountType.General) .map((a) => { const isVega = - a.asset.source.__typename === 'ERC20' && + isAssetTypeERC20(a.asset) && a.asset.source.contractAddress === vegaToken.address; return { @@ -131,10 +132,9 @@ export const usePollForDelegations = () => { ), image: isVega ? vegaBlack : noIcon, border: isVega, - address: - a.asset.source.__typename === 'ERC20' - ? a.asset.source.contractAddress - : undefined, + address: isAssetTypeERC20(a.asset) + ? a.asset.source.contractAddress + : undefined, }; }) .sort((a, b) => { diff --git a/apps/token/src/components/vega-wallet/vega-wallet.tsx b/apps/token/src/components/vega-wallet/vega-wallet.tsx index ff81d2a17..a73e6f5a6 100644 --- a/apps/token/src/components/vega-wallet/vega-wallet.tsx +++ b/apps/token/src/components/vega-wallet/vega-wallet.tsx @@ -43,10 +43,16 @@ export const VegaWallet = () => {

{t('vegaWallet')}

{keypair && ( <> -
+
{keypair.name}
- + {truncateMiddle(keypair.pub)} @@ -129,6 +135,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => { const footer = (
)} diff --git a/apps/token/src/routes/governance/components/vote-details/index.tsx b/apps/token/src/routes/governance/components/vote-details/index.tsx index ffda2d552..b8d4db866 100644 --- a/apps/token/src/routes/governance/components/vote-details/index.tsx +++ b/apps/token/src/routes/governance/components/vote-details/index.tsx @@ -1,2 +1 @@ export { VoteDetails } from './vote-details'; -export { VOTE_VALUE_MAP } from './vote-types'; diff --git a/apps/token/src/routes/governance/components/vote-details/use-user-vote.tsx b/apps/token/src/routes/governance/components/vote-details/use-user-vote.tsx index cced62554..bb1a095c6 100644 --- a/apps/token/src/routes/governance/components/vote-details/use-user-vote.tsx +++ b/apps/token/src/routes/governance/components/vote-details/use-user-vote.tsx @@ -2,8 +2,7 @@ import { captureException, captureMessage } from '@sentry/minimal'; import * as React from 'react'; import { VoteValue } from '../../../../__generated__/globalTypes'; -import { VOTE_VALUE_MAP } from './vote-types'; -import { useVegaWallet } from '@vegaprotocol/wallet'; +import { useVegaWallet, VegaWalletVoteValue } from '@vegaprotocol/wallet'; export type Vote = { value: VoteValue; @@ -101,7 +100,7 @@ export function useUserVote( pubKey: keypair.pub, propagate: true, voteSubmission: { - value: VOTE_VALUE_MAP[value], + value: VegaWalletVoteValue[value], proposalId, }, }; diff --git a/apps/token/src/routes/governance/components/vote-details/vote-types.ts b/apps/token/src/routes/governance/components/vote-details/vote-types.ts deleted file mode 100644 index 48b920a80..000000000 --- a/apps/token/src/routes/governance/components/vote-details/vote-types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { VoteValue } from '../../../../__generated__/globalTypes'; - -export const VOTE_VALUE_MAP = { - [VoteValue.Yes]: 'VALUE_YES', - [VoteValue.No]: 'VALUE_NO', -} as const; diff --git a/apps/token/src/routes/redemption/home/vesting-table.tsx b/apps/token/src/routes/redemption/home/vesting-table.tsx index 72b0f0194..0b5a07187 100644 --- a/apps/token/src/routes/redemption/home/vesting-table.tsx +++ b/apps/token/src/routes/redemption/home/vesting-table.tsx @@ -63,7 +63,7 @@ export const VestingTable = ({ {formatNumber(associated)}
-
+
{ {t('rewardType')} - {DEFAULT_REWARD_TYPE} + {reward.rewardType} {t('yourStake')} diff --git a/apps/token/src/routes/staking/node-list.tsx b/apps/token/src/routes/staking/node-list.tsx index 0130112dc..d345c974b 100644 --- a/apps/token/src/routes/staking/node-list.tsx +++ b/apps/token/src/routes/staking/node-list.tsx @@ -57,6 +57,8 @@ interface ValidatorRendererProps { data: { validator: { avatarUrl: string; name: string } }; } +const stripNonDigits = (string: string) => string.replace(/\D/g, ''); + const ValidatorRenderer = ({ data }: ValidatorRendererProps) => { const { avatarUrl, name } = data.validator; return ( @@ -66,6 +68,7 @@ const ValidatorRenderer = ({ data }: ValidatorRendererProps) => { className="h-24 w-24 rounded-full mr-8" src={avatarUrl} alt={`Avatar icon for ${name}`} + onError={(e) => (e.currentTarget.style.display = 'none')} /> )} {name} @@ -142,7 +145,7 @@ export const NodeList = ({ epoch }: NodeListProps) => { [TOTAL_STAKE_THIS_EPOCH]: formatNumber(stakedTotal, 2), [SHARE]: stakedTotalPercentage, [VALIDATOR_STAKE]: formatNumber(stakedOnNode, 2), - [PENDING_STAKE]: pendingStake, + [PENDING_STAKE]: formatNumber(pendingStake, 2), [RANKING_SCORE]: formatNumber(new BigNumber(rankingScore), 5), [STAKE_SCORE]: formatNumber(new BigNumber(stakeScore), 5), [PERFORMANCE_SCORE]: formatNumber(new BigNumber(performanceScore), 5), @@ -167,16 +170,34 @@ export const NodeList = ({ epoch }: NodeListProps) => { field: TOTAL_STAKE_THIS_EPOCH, headerName: t('totalStakeThisEpoch').toString(), }, - { field: SHARE, headerName: t('share').toString() }, - { field: VALIDATOR_STAKE, headerName: t('validatorStake').toString() }, - { field: PENDING_STAKE, headerName: t('nextEpoch').toString() }, - { field: RANKING_SCORE, headerName: t('rankingScore').toString() }, - { field: STAKE_SCORE, headerName: t('stakeScore').toString() }, + { + field: SHARE, + headerName: t('share').toString(), + }, + { + field: VALIDATOR_STAKE, + headerName: t('validatorStake').toString(), + }, + { + field: PENDING_STAKE, + headerName: t('nextEpoch').toString(), + }, + { + field: RANKING_SCORE, + headerName: t('rankingScore').toString(), + }, + { + field: STAKE_SCORE, + headerName: t('stakeScore').toString(), + }, { field: PERFORMANCE_SCORE, headerName: t('performanceScore').toString(), }, - { field: VOTING_POWER, headerName: t('votingPower').toString() }, + { + field: VOTING_POWER, + headerName: t('votingPower').toString(), + }, ], [] ); @@ -184,6 +205,8 @@ export const NodeList = ({ epoch }: NodeListProps) => { const defaultColDef = useMemo( () => ({ sortable: true, + comparator: (a: string, b: string) => + parseFloat(stripNonDigits(a)) - parseFloat(stripNonDigits(b)), }), [] ); @@ -206,7 +229,7 @@ export const NodeList = ({ epoch }: NodeListProps) => { event.columnApi.applyColumnState({ state: [ { - colId: t('rankingScore'), + colId: RANKING_SCORE, sort: 'desc', }, ], diff --git a/apps/token/src/routes/staking/pending-stake.tsx b/apps/token/src/routes/staking/pending-stake.tsx index 42098df3c..b1fb172ba 100644 --- a/apps/token/src/routes/staking/pending-stake.tsx +++ b/apps/token/src/routes/staking/pending-stake.tsx @@ -5,8 +5,8 @@ import { useTranslation } from 'react-i18next'; import { useAppState } from '../../contexts/app-state/app-state-context'; import { BigNumber } from '../../lib/bignumber'; import { removeDecimal } from '../../lib/decimals'; +import type { UndelegateSubmissionBody } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet'; -import type { UndelegateSubmissionBody } from '@vegaprotocol/vegawallet-service-api-client'; interface PendingStakeProps { pendingAmount: BigNumber; diff --git a/apps/token/src/routes/staking/staking-form.tsx b/apps/token/src/routes/staking/staking-form.tsx index 40d66a563..1d4501406 100644 --- a/apps/token/src/routes/staking/staking-form.tsx +++ b/apps/token/src/routes/staking/staking-form.tsx @@ -26,11 +26,11 @@ import { Radio, RadioGroup, } from '@vegaprotocol/ui-toolkit'; -import { useVegaWallet } from '@vegaprotocol/wallet'; import type { DelegateSubmissionBody, UndelegateSubmissionBody, -} from '@vegaprotocol/vegawallet-service-api-client'; +} from '@vegaprotocol/wallet'; +import { useVegaWallet } from '@vegaprotocol/wallet'; export const PARTY_DELEGATIONS_QUERY = gql` query PartyDelegations($partyId: ID!) { diff --git a/apps/trading-e2e/.env b/apps/trading-e2e/.env index 92559423b..fab1c558d 100644 --- a/apps/trading-e2e/.env +++ b/apps/trading-e2e/.env @@ -1 +1,2 @@ NX_USE_ENV_OVERRIDES=0 +NX_VEGA_WALLET_URL=http://localhost:1789/api/v1 diff --git a/apps/trading-e2e/src/integration/global.cy.ts b/apps/trading-e2e/src/integration/global.cy.ts index c4c468351..5e44b7a9a 100644 --- a/apps/trading-e2e/src/integration/global.cy.ts +++ b/apps/trading-e2e/src/integration/global.cy.ts @@ -29,7 +29,7 @@ describe('vega wallet', () => { cy.getByTestId(form).find('#wallet').click().type('invalid name'); cy.getByTestId(form).find('#passphrase').click().type('invalid password'); cy.getByTestId('rest-connector-form').find('button[type=submit]').click(); - cy.getByTestId('form-error').should('have.text', 'Authentication failed'); + cy.getByTestId('form-error').should('have.text', 'Invalid credentials'); }); it('doesnt connect with invalid fields', () => { diff --git a/apps/trading/.env b/apps/trading/.env index 1e7125719..cf3e5ef66 100644 --- a/apps/trading/.env +++ b/apps/trading/.env @@ -6,3 +6,4 @@ NX_ETHERSCAN_URL=https://ropsten.etherscan.io NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"} NX_USE_ENV_OVERRIDES=1 NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf +NX_VEGA_WALLET_URL=http://localhost:1789/api/v1 diff --git a/apps/trading/components/web3-container/web3-container.tsx b/apps/trading/components/web3-container/web3-container.tsx index e0da31b5a..f1b06315e 100644 --- a/apps/trading/components/web3-container/web3-container.tsx +++ b/apps/trading/components/web3-container/web3-container.tsx @@ -63,7 +63,10 @@ export const Web3Content = ({ if (connector?.connectEagerly && !('Cypress' in window)) { connector.connectEagerly(); } - }, [connector]); + // wallet connect doesnt handle connectEagerly being called when connector is also in the + // deps array. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); if (error) { return ( diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index dc16cabfc..9d4ccc4d0 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -28,7 +28,7 @@ function AppBody({ Component, pageProps }: AppProps) { return ( -
+
@@ -41,7 +41,11 @@ function AppBody({ Component, pageProps }: AppProps) { store.setVegaWalletManageDialog(open); }} /> - +
diff --git a/apps/trading/pages/portfolio/deposit/deposit-container.tsx b/apps/trading/pages/portfolio/deposit/deposit-container.tsx index 98f34c2e9..8cd07878e 100644 --- a/apps/trading/pages/portfolio/deposit/deposit-container.tsx +++ b/apps/trading/pages/portfolio/deposit/deposit-container.tsx @@ -16,14 +16,10 @@ const DEPOSIT_PAGE_QUERY = gql` } `; -interface DepositContainerProps { - assetId?: string; -} - /** * Fetches data required for the Deposit page */ -export const DepositContainer = ({ assetId }: DepositContainerProps) => { +export const DepositContainer = () => { const { VEGA_ENV } = useEnvironment(); return ( @@ -41,7 +37,6 @@ export const DepositContainer = ({ assetId }: DepositContainerProps) => { return ( ); diff --git a/apps/trading/pages/portfolio/deposit/index.page.tsx b/apps/trading/pages/portfolio/deposit/index.page.tsx index d9d7b2ea7..a7c6feb87 100644 --- a/apps/trading/pages/portfolio/deposit/index.page.tsx +++ b/apps/trading/pages/portfolio/deposit/index.page.tsx @@ -1,29 +1,12 @@ -import { useRouter } from 'next/router'; -import { useMemo } from 'react'; import { Web3Container } from '../../../components/web3-container'; import { DepositContainer } from './deposit-container'; const Deposit = () => { - const { query } = useRouter(); - - // AssetId can be specified in the query string to allow link to deposit a particular asset - const assetId = useMemo(() => { - if (query.assetId && Array.isArray(query.assetId)) { - return undefined; - } - - if (Array.isArray(query.assetId)) { - return undefined; - } - - return query.assetId; - }, [query]); - return (

Deposit

- +
); diff --git a/libs/cypress/src/lib/commands/mock-vega-wallet.ts b/libs/cypress/src/lib/commands/mock-vega-wallet.ts index f3b674b23..7a33ff8e7 100644 --- a/libs/cypress/src/lib/commands/mock-vega-wallet.ts +++ b/libs/cypress/src/lib/commands/mock-vega-wallet.ts @@ -1,5 +1,5 @@ import merge from 'lodash/merge'; -import type { TransactionResponse } from '@vegaprotocol/vegawallet-service-api-client'; +import type { TransactionResponse } from '@vegaprotocol/wallet'; import type { PartialDeep } from 'type-fest'; declare global { @@ -17,7 +17,10 @@ export function addMockVegaWalletCommands() { 'mockVegaCommandSync', (override?: PartialDeep) => { const defaultTransactionResponse = { + txId: 'tx-id', txHash: 'tx-hash', + sentAt: new Date().toISOString(), + receivedAt: new Date().toISOString(), tx: { input_data: 'CPe6vpiqsPqxDBDC1w7KPkoKQGE4Y2M0NjUwMjhiMGY4OTM4YTYzZTEzNDViYzM2ODc3ZWRmODg4MjNmOWU0ZmI4ZDRlN2VkMmFlMzAwNzA3ZTMYASABKAM4Ag==', @@ -28,7 +31,7 @@ export function addMockVegaWalletCommands() { version: 1, }, From: { - PubKey: Cypress.env('vegaPublicKey'), + PubKey: Cypress.env('VEGA_PUBLIC_KEY'), }, version: 2, pow: { diff --git a/libs/deal-ticket/src/components/deal-ticket-manager.tsx b/libs/deal-ticket/src/components/deal-ticket-manager.tsx index 76dbd130d..01d0d2e86 100644 --- a/libs/deal-ticket/src/components/deal-ticket-manager.tsx +++ b/libs/deal-ticket/src/components/deal-ticket-manager.tsx @@ -44,7 +44,7 @@ export const DealTicketManager = ({ ); }; -const getDialogTitle = (status?: OrderStatus): string | undefined => { +export const getDialogTitle = (status?: OrderStatus): string | undefined => { if (!status) { return; } @@ -63,7 +63,7 @@ const getDialogTitle = (status?: OrderStatus): string | undefined => { } }; -const getDialogIntent = (status?: OrderStatus): Intent | undefined => { +export const getDialogIntent = (status?: OrderStatus): Intent | undefined => { if (!status) { return; } @@ -81,7 +81,7 @@ const getDialogIntent = (status?: OrderStatus): Intent | undefined => { } }; -const getDialogIcon = (status?: OrderStatus): ReactNode | undefined => { +export const getDialogIcon = (status?: OrderStatus): ReactNode | undefined => { if (!status) { return; } diff --git a/libs/deal-ticket/src/components/deal-ticket.spec.tsx b/libs/deal-ticket/src/components/deal-ticket.spec.tsx index 2b06c0c6e..59431d73c 100644 --- a/libs/deal-ticket/src/components/deal-ticket.spec.tsx +++ b/libs/deal-ticket/src/components/deal-ticket.spec.tsx @@ -7,8 +7,8 @@ import { addDecimal } from '@vegaprotocol/react-helpers'; import { fireEvent, render, screen, act } from '@testing-library/react'; import { DealTicket } from './deal-ticket'; import type { DealTicketQuery_market } from './__generated__/DealTicketQuery'; -import type { Order } from '../utils/get-default-order'; import { MarketState, MarketTradingMode } from '@vegaprotocol/types'; +import type { Order } from '@vegaprotocol/orders'; const market: DealTicketQuery_market = { __typename: 'Market', @@ -59,109 +59,111 @@ function generateJsx(order?: Order) { ); } -it('Displays ticket defaults', () => { - render(generateJsx()); +describe('DealTicket', () => { + it('Displays ticket defaults', () => { + render(generateJsx()); - // Assert defaults are used - expect( - screen.getByTestId(`order-type-${VegaWalletOrderType.Market}-selected`) - ).toBeInTheDocument(); - expect( - screen.queryByTestId('order-side-SIDE_BUY-selected') - ).toBeInTheDocument(); - expect( - screen.queryByTestId('order-side-SIDE_SELL-selected') - ).not.toBeInTheDocument(); - expect(screen.getByTestId('order-size')).toHaveDisplayValue( - String(1 / Math.pow(10, market.positionDecimalPlaces)) - ); - expect(screen.getByTestId('order-tif')).toHaveValue( - VegaWalletOrderTimeInForce.IOC - ); + // Assert defaults are used + expect( + screen.getByTestId(`order-type-${VegaWalletOrderType.Market}-selected`) + ).toBeInTheDocument(); + expect( + screen.queryByTestId('order-side-SIDE_BUY-selected') + ).toBeInTheDocument(); + expect( + screen.queryByTestId('order-side-SIDE_SELL-selected') + ).not.toBeInTheDocument(); + expect(screen.getByTestId('order-size')).toHaveDisplayValue( + String(1 / Math.pow(10, market.positionDecimalPlaces)) + ); + expect(screen.getByTestId('order-tif')).toHaveValue( + VegaWalletOrderTimeInForce.IOC + ); - // Assert last price is shown - expect(screen.getByTestId('last-price')).toHaveTextContent( - // eslint-disable-next-line - `~${addDecimal(market.depth.lastTrade!.price, market.decimalPlaces)} ${ - market.tradableInstrument.instrument.product.quoteName - }` - ); -}); + // Assert last price is shown + expect(screen.getByTestId('last-price')).toHaveTextContent( + // eslint-disable-next-line + `~${addDecimal(market.depth.lastTrade!.price, market.decimalPlaces)} ${ + market.tradableInstrument.instrument.product.quoteName + }` + ); + }); -it('Can edit deal ticket', async () => { - render(generateJsx()); + it('Can edit deal ticket', async () => { + render(generateJsx()); - // BUY is selected by default - screen.getByTestId('order-side-SIDE_BUY-selected'); + // BUY is selected by default + screen.getByTestId('order-side-SIDE_BUY-selected'); - await act(async () => { - fireEvent.change(screen.getByTestId('order-size'), { - target: { value: '200' }, + await act(async () => { + fireEvent.change(screen.getByTestId('order-size'), { + target: { value: '200' }, + }); }); + + expect(screen.getByTestId('order-size')).toHaveDisplayValue('200'); + + fireEvent.change(screen.getByTestId('order-tif'), { + target: { value: VegaWalletOrderTimeInForce.IOC }, + }); + expect(screen.getByTestId('order-tif')).toHaveValue( + VegaWalletOrderTimeInForce.IOC + ); + + // Switch to limit order + fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT')); + + // Assert price input shown with default value + expect(screen.getByTestId('order-price')).toHaveDisplayValue('0'); + + // Check all TIF options shown + expect(screen.getByTestId('order-tif').children).toHaveLength( + Object.keys(VegaWalletOrderTimeInForce).length + ); }); - expect(screen.getByTestId('order-size')).toHaveDisplayValue('200'); + it('Handles TIF select box dependent on order type', () => { + render(generateJsx()); - fireEvent.change(screen.getByTestId('order-tif'), { - target: { value: VegaWalletOrderTimeInForce.IOC }, + // Check only IOC and + expect( + Array.from(screen.getByTestId('order-tif').children).map( + (o) => o.textContent + ) + ).toEqual(['Immediate or Cancel (IOC)', 'Fill or Kill (FOK)']); + + // Switch to limit order and check all TIF options shown + fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT')); + expect(screen.getByTestId('order-tif').children).toHaveLength( + Object.keys(VegaWalletOrderTimeInForce).length + ); + + // Change to GTC + fireEvent.change(screen.getByTestId('order-tif'), { + target: { value: VegaWalletOrderTimeInForce.GTC }, + }); + expect(screen.getByTestId('order-tif')).toHaveValue( + VegaWalletOrderTimeInForce.GTC + ); + + // Switch back to market order and TIF should now be IOC + fireEvent.click(screen.getByTestId('order-type-TYPE_MARKET')); + expect(screen.getByTestId('order-tif')).toHaveValue( + VegaWalletOrderTimeInForce.IOC + ); + + // Switch tif to FOK + fireEvent.change(screen.getByTestId('order-tif'), { + target: { value: VegaWalletOrderTimeInForce.FOK }, + }); + expect(screen.getByTestId('order-tif')).toHaveValue( + VegaWalletOrderTimeInForce.FOK + ); + + // Change back to limit and check we are still on FOK + fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT')); + expect(screen.getByTestId('order-tif')).toHaveValue( + VegaWalletOrderTimeInForce.FOK + ); }); - expect(screen.getByTestId('order-tif')).toHaveValue( - VegaWalletOrderTimeInForce.IOC - ); - - // Switch to limit order - fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT')); - - // Assert price input shown with default value - expect(screen.getByTestId('order-price')).toHaveDisplayValue('0'); - - // Check all TIF options shown - expect(screen.getByTestId('order-tif').children).toHaveLength( - Object.keys(VegaWalletOrderTimeInForce).length - ); -}); - -it('Handles TIF select box dependent on order type', () => { - render(generateJsx()); - - // Check only IOC and - expect( - Array.from(screen.getByTestId('order-tif').children).map( - (o) => o.textContent - ) - ).toEqual(['IOC', 'FOK']); - - // Switch to limit order and check all TIF options shown - fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT')); - expect(screen.getByTestId('order-tif').children).toHaveLength( - Object.keys(VegaWalletOrderTimeInForce).length - ); - - // Change to GTC - fireEvent.change(screen.getByTestId('order-tif'), { - target: { value: VegaWalletOrderTimeInForce.GTC }, - }); - expect(screen.getByTestId('order-tif')).toHaveValue( - VegaWalletOrderTimeInForce.GTC - ); - - // Switch back to market order and TIF should now be IOC - fireEvent.click(screen.getByTestId('order-type-TYPE_MARKET')); - expect(screen.getByTestId('order-tif')).toHaveValue( - VegaWalletOrderTimeInForce.IOC - ); - - // Switch tif to FOK - fireEvent.change(screen.getByTestId('order-tif'), { - target: { value: VegaWalletOrderTimeInForce.FOK }, - }); - expect(screen.getByTestId('order-tif')).toHaveValue( - VegaWalletOrderTimeInForce.FOK - ); - - // Change back to limit and check we are still on FOK - fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT')); - expect(screen.getByTestId('order-tif')).toHaveValue( - VegaWalletOrderTimeInForce.FOK - ); }); diff --git a/libs/deal-ticket/src/components/time-in-force-selector.tsx b/libs/deal-ticket/src/components/time-in-force-selector.tsx index 6f7dd17b7..5852aca45 100644 --- a/libs/deal-ticket/src/components/time-in-force-selector.tsx +++ b/libs/deal-ticket/src/components/time-in-force-selector.tsx @@ -11,6 +11,26 @@ interface TimeInForceSelectorProps { onSelect: (tif: VegaWalletOrderTimeInForce) => void; } +// More detail in https://docs.vega.xyz/docs/mainnet/graphql/enums/order-time-in-force +export const timeInForceLabel = (tif: string) => { + switch (tif) { + case VegaWalletOrderTimeInForce.GTC: + return t(`Good 'til Cancelled`); + case VegaWalletOrderTimeInForce.IOC: + return t('Immediate or Cancel'); + case VegaWalletOrderTimeInForce.FOK: + return t('Fill or Kill'); + case VegaWalletOrderTimeInForce.GTT: + return t(`Good 'til Time`); + case VegaWalletOrderTimeInForce.GFN: + return t('Good for Normal'); + case VegaWalletOrderTimeInForce.GFA: + return t('Good for Auction'); + default: + return t(tif); + } +}; + export const TimeInForceSelector = ({ value, orderType, @@ -37,7 +57,7 @@ export const TimeInForceSelector = ({ {options.map(([key, value]) => { return ( ); })} diff --git a/libs/deposits/src/lib/deposit-form.spec.tsx b/libs/deposits/src/lib/deposit-form.spec.tsx index 0fae80b5b..4adeda9b4 100644 --- a/libs/deposits/src/lib/deposit-form.spec.tsx +++ b/libs/deposits/src/lib/deposit-form.spec.tsx @@ -37,10 +37,8 @@ beforeEach(() => { submitApprove: jest.fn(), submitDeposit: jest.fn(), requestFaucet: jest.fn(), - limits: { - max: new BigNumber(20), - deposited: new BigNumber(10), - }, + max: new BigNumber(20), + deposited: new BigNumber(10), allowance: new BigNumber(30), isFaucetable: true, }; @@ -134,7 +132,8 @@ describe('Deposit form', () => { ); @@ -213,18 +212,16 @@ describe('Deposit form', () => { const mockUseWeb3React = useWeb3React as jest.Mock; mockUseWeb3React.mockReturnValue({ account }); - const limits = { - max: new BigNumber(20), - deposited: new BigNumber(10), - }; const balance = new BigNumber(50); - + const max = new BigNumber(20); + const deposited = new BigNumber(10); render( ); @@ -237,13 +234,13 @@ describe('Deposit form', () => { expect( screen.getByText('Maximum total deposit amount', { selector: 'th' }) .nextElementSibling - ).toHaveTextContent(limits.max.toString()); + ).toHaveTextContent(max.toString()); expect( screen.getByText('Deposited', { selector: 'th' }).nextElementSibling - ).toHaveTextContent(limits.deposited.toString()); + ).toHaveTextContent(deposited.toString()); expect( screen.getByText('Remaining', { selector: 'th' }).nextElementSibling - ).toHaveTextContent(limits.max.minus(limits.deposited).toString()); + ).toHaveTextContent(max.minus(deposited).toString()); fireEvent.change(screen.getByLabelText('Amount'), { target: { value: '8' }, @@ -257,7 +254,7 @@ describe('Deposit form', () => { expect(props.submitDeposit).toHaveBeenCalledWith({ // @ts-ignore contract address definitely defined assetSource: asset.source.contractAddress, - amount: '800', + amount: '8', vegaPublicKey: vegaKey, }); }); diff --git a/libs/deposits/src/lib/deposit-form.tsx b/libs/deposits/src/lib/deposit-form.tsx index fb2fbf9b9..acff042da 100644 --- a/libs/deposits/src/lib/deposit-form.tsx +++ b/libs/deposits/src/lib/deposit-form.tsx @@ -1,5 +1,5 @@ +import type { Asset } from '@vegaprotocol/react-helpers'; import { - removeDecimal, ethereumAddress, t, required, @@ -7,6 +7,7 @@ import { minSafe, maxSafe, addDecimal, + isAssetTypeERC20, } from '@vegaprotocol/react-helpers'; import { Button, @@ -21,10 +22,9 @@ import { useWeb3React } from '@web3-react/core'; import { Web3WalletInput } from '@vegaprotocol/web3'; import BigNumber from 'bignumber.js'; import type { ReactNode } from 'react'; -import { useMemo, useEffect } from 'react'; -import { useForm, useWatch } from 'react-hook-form'; +import { useMemo } from 'react'; +import { Controller, useForm, useWatch } from 'react-hook-form'; import { DepositLimits } from './deposit-limits'; -import type { Asset } from './deposit-manager'; interface FormFields { asset: string; @@ -45,10 +45,8 @@ export interface DepositFormProps { vegaPublicKey: string; }) => void; requestFaucet: () => void; - limits: { - max: BigNumber; - deposited: BigNumber; - } | null; + max: BigNumber | undefined; + deposited: BigNumber | undefined; allowance: BigNumber | undefined; isFaucetable?: boolean; } @@ -58,10 +56,11 @@ export const DepositForm = ({ selectedAsset, onSelectAsset, balance, + max, + deposited, submitApprove, submitDeposit, requestFaucet, - limits, allowance, isFaucetable, }: DepositFormProps) => { @@ -89,15 +88,14 @@ export const DepositForm = ({ submitDeposit({ assetSource: selectedAsset.source.contractAddress, - amount: removeDecimal(fields.amount, selectedAsset.decimals), + amount: fields.amount, vegaPublicKey: fields.to, }); }; - const assetId = useWatch({ name: 'asset', control }); const amount = useWatch({ name: 'amount', control }); - const max = useMemo(() => { + const maxAmount = useMemo(() => { const maxApproved = allowance ? allowance : new BigNumber(0); const maxAvailable = balance ? balance : new BigNumber(0); @@ -106,8 +104,8 @@ export const DepositForm = ({ let maxLimit = new BigNumber(Infinity); // A max limit of zero indicates that there is no limit - if (limits && limits.max.isGreaterThan(0)) { - maxLimit = limits.max.minus(limits.deposited); + if (max && deposited && max.isGreaterThan(0)) { + maxLimit = max.minus(deposited); } return { @@ -116,7 +114,7 @@ export const DepositForm = ({ limit: maxLimit, amount: BigNumber.minimum(maxLimit, maxApproved, maxAvailable), }; - }, [limits, allowance, balance]); + }, [max, deposited, allowance, balance]); const min = useMemo(() => { // Min viable amount given asset decimals EG for WEI 0.000000000000000001 @@ -127,10 +125,6 @@ export const DepositForm = ({ return minViableAmount; }, [selectedAsset]); - useEffect(() => { - onSelectAsset(assetId); - }, [assetId, onSelectAsset]); - return ( - + ( + + )} + /> {errors.asset?.message && ( {errors.asset.message} @@ -196,9 +202,9 @@ export const DepositForm = ({ )} - {selectedAsset && limits && ( + {selectedAsset && max && deposited && (
- +
)} @@ -212,14 +218,14 @@ export const DepositForm = ({ minSafe: (value) => minSafe(new BigNumber(min))(value), maxSafe: (v) => { const value = new BigNumber(v); - if (value.isGreaterThan(max.available)) { + if (value.isGreaterThan(maxAmount.available)) { return t('Insufficient amount in Ethereum wallet'); - } else if (value.isGreaterThan(max.limit)) { + } else if (value.isGreaterThan(maxAmount.limit)) { return t('Amount is above temporary deposit limit'); - } else if (value.isGreaterThan(max.approved)) { + } else if (value.isGreaterThan(maxAmount.approved)) { return t('Amount is above approved amount'); } - return maxSafe(max.amount)(v); + return maxSafe(maxAmount.amount)(v); }, }, })} diff --git a/libs/deposits/src/lib/deposit-limits.tsx b/libs/deposits/src/lib/deposit-limits.tsx index 8599a2d14..2068ddb32 100644 --- a/libs/deposits/src/lib/deposit-limits.tsx +++ b/libs/deposits/src/lib/deposit-limits.tsx @@ -2,28 +2,30 @@ import { t } from '@vegaprotocol/react-helpers'; import type BigNumber from 'bignumber.js'; interface DepositLimitsProps { - limits: { - max: BigNumber; - deposited: BigNumber; - }; + max: BigNumber; + deposited: BigNumber; balance?: BigNumber; } -export const DepositLimits = ({ limits, balance }: DepositLimitsProps) => { +export const DepositLimits = ({ + max, + deposited, + balance, +}: DepositLimitsProps) => { let maxLimit = ''; - if (limits.max.isEqualTo(Infinity)) { + if (max.isEqualTo(Infinity)) { maxLimit = t('No limit'); - } else if (limits.max.isGreaterThan(1_000_000)) { + } else if (max.isGreaterThan(1_000_000)) { maxLimit = t('1m+'); } else { - maxLimit = limits.max.toString(); + maxLimit = max.toString(); } let remaining = ''; - if (limits.deposited.isEqualTo(0)) { + if (deposited.isEqualTo(0)) { remaining = maxLimit; } else { - const amountRemaining = limits.max.minus(limits.deposited); + const amountRemaining = max.minus(deposited); remaining = amountRemaining.isGreaterThan(1_000_000) ? t('1m+') : amountRemaining.toString(); @@ -44,7 +46,7 @@ export const DepositLimits = ({ limits, balance }: DepositLimitsProps) => { {t('Deposited')} - {limits.deposited.toString()} + {deposited.toString()} {t('Remaining')} diff --git a/libs/deposits/src/lib/deposit-manager.tsx b/libs/deposits/src/lib/deposit-manager.tsx index 4d6187028..9c52800d6 100644 --- a/libs/deposits/src/lib/deposit-manager.tsx +++ b/libs/deposits/src/lib/deposit-manager.tsx @@ -1,121 +1,56 @@ -import { useEffect, useMemo, useState } from 'react'; import { DepositForm } from './deposit-form'; -import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token'; import { useSubmitDeposit } from './use-submit-deposit'; import sortBy from 'lodash/sortBy'; import { useSubmitApproval } from './use-submit-approval'; -import { useGetDepositLimits } from './use-get-deposit-limits'; -import { useGetAllowance } from './use-get-allowance'; import { useSubmitFaucet } from './use-submit-faucet'; -import { EthTxStatus, useEthereumConfig } from '@vegaprotocol/web3'; -import { useTokenContract } from '@vegaprotocol/web3'; -import { removeDecimal } from '@vegaprotocol/react-helpers'; - -interface ERC20AssetSource { - __typename: 'ERC20'; - contractAddress: string; -} - -interface BuiltinAssetSource { - __typename: 'BuiltinAsset'; -} - -type AssetSource = ERC20AssetSource | BuiltinAssetSource; -export interface Asset { - __typename: 'Asset'; - id: string; - symbol: string; - name: string; - decimals: number; - source: AssetSource; -} +import { useDepositStore } from './deposit-store'; +import { useCallback } from 'react'; +import { useDepositBalances } from './use-deposit-balances'; +import type { Asset } from '@vegaprotocol/react-helpers'; interface DepositManagerProps { assets: Asset[]; - initialAssetId?: string; - isFaucetable?: boolean; + isFaucetable: boolean; } export const DepositManager = ({ assets, - initialAssetId, isFaucetable, }: DepositManagerProps) => { - const [assetId, setAssetId] = useState(initialAssetId); - - // Find the asset object from the select box - const asset = useMemo(() => { - const asset = assets?.find((a) => a.id === assetId); - return asset; - }, [assets, assetId]); - - const { config } = useEthereumConfig(); - - const tokenContract = useTokenContract( - asset?.source.__typename === 'ERC20' - ? asset.source.contractAddress - : undefined, - isFaucetable - ); - - // Get users balance of the erc20 token selected - const { balance, refetch: refetchBalance } = useGetBalanceOfERC20Token( - tokenContract, - asset?.decimals - ); - - // Get temporary deposit limits - const limits = useGetDepositLimits(asset); - - // Get allowance (approved spending limit of brdige contract) for the selected asset - const { allowance, refetch: refetchAllowance } = useGetAllowance( - tokenContract, - asset?.decimals - ); + const { asset, balance, allowance, deposited, max, update } = + useDepositStore(); + useDepositBalances(isFaucetable); // Set up approve transaction - const approve = useSubmitApproval(tokenContract); + const approve = useSubmitApproval(); // Set up deposit transaction const deposit = useSubmitDeposit(); // Set up faucet transaction - const faucet = useSubmitFaucet(tokenContract); + const faucet = useSubmitFaucet(); - // Update balance after confirmation event has been received - useEffect(() => { - if ( - faucet.transaction.status === EthTxStatus.Confirmed || - deposit.transaction.status === EthTxStatus.Confirmed - ) { - refetchBalance(); - } - }, [deposit.transaction.status, faucet.transaction.status, refetchBalance]); - - // After an approval transaction refetch allowance - useEffect(() => { - if (approve.transaction.status === EthTxStatus.Confirmed) { - refetchAllowance(); - } - }, [approve.transaction.status, refetchAllowance]); + const handleSelectAsset = useCallback( + (id) => { + const asset = assets.find((a) => a.id === id); + if (!asset) return; + update({ asset }); + }, + [assets, update] + ); return ( <> setAssetId(id)} + onSelectAsset={handleSelectAsset} assets={sortBy(assets, 'name')} - submitApprove={() => { - if (!asset || !config) return; - const amount = removeDecimal('1000000', asset.decimals); - approve.perform(config.collateral_bridge_contract.address, amount); - }} - submitDeposit={(args) => { - deposit.perform(args.assetSource, args.amount, args.vegaPublicKey); - }} + submitApprove={() => approve.perform()} + submitDeposit={(args) => deposit.perform(args)} requestFaucet={() => faucet.perform()} - limits={limits} + deposited={deposited} + max={max} allowance={allowance} isFaucetable={isFaucetable} /> diff --git a/libs/deposits/src/lib/deposit-store.ts b/libs/deposits/src/lib/deposit-store.ts new file mode 100644 index 000000000..17a04b3c9 --- /dev/null +++ b/libs/deposits/src/lib/deposit-store.ts @@ -0,0 +1,24 @@ +import type { Asset } from '@vegaprotocol/react-helpers'; +import BigNumber from 'bignumber.js'; +import type { SetState } from 'zustand'; +import create from 'zustand'; + +interface DepositStore { + balance: BigNumber; + allowance: BigNumber; + asset: Asset | undefined; + deposited: BigNumber; + max: BigNumber; + update: (state: Partial) => void; +} + +export const useDepositStore = create((set: SetState) => ({ + balance: new BigNumber(0), + allowance: new BigNumber(0), + deposited: new BigNumber(0), + max: new BigNumber(0), + asset: undefined, + update: (updatedState) => { + set(updatedState); + }, +})); diff --git a/libs/deposits/src/lib/use-deposit-balances.ts b/libs/deposits/src/lib/use-deposit-balances.ts new file mode 100644 index 000000000..67b2c20df --- /dev/null +++ b/libs/deposits/src/lib/use-deposit-balances.ts @@ -0,0 +1,59 @@ +import { useBridgeContract, useTokenContract } from '@vegaprotocol/web3'; +import { useEffect } from 'react'; +import * as Sentry from '@sentry/react'; +import { useDepositStore } from './deposit-store'; +import { useGetAllowance } from './use-get-allowance'; +import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token'; +import { useGetDepositMaximum } from './use-get-deposit-maximum'; +import { useGetDepositedAmount } from './use-get-deposited-amount'; +import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers'; + +/** + * Hook which fetches all the balances required for despoiting + * whenever the asset changes in the form + */ +export const useDepositBalances = (isFaucetable: boolean) => { + const { asset, update } = useDepositStore(); + const tokenContract = useTokenContract( + isAssetTypeERC20(asset) ? asset : undefined, + isFaucetable + ); + const bridgeContract = useBridgeContract(true); + const getAllowance = useGetAllowance(tokenContract, asset); + const getBalance = useGetBalanceOfERC20Token(tokenContract, asset); + const getDepositMaximum = useGetDepositMaximum(bridgeContract, asset); + const getDepositedAmount = useGetDepositedAmount(asset); + + useEffect(() => { + const getBalances = async () => { + try { + const [max, deposited, balance, allowance] = await Promise.all([ + getDepositMaximum(), + getDepositedAmount(), + getBalance(), + getAllowance(), + ]); + + update({ + max, + deposited, + balance, + allowance, + }); + } catch (err) { + Sentry.captureException(err); + } + }; + + if (asset) { + getBalances(); + } + }, [ + asset, + update, + getDepositMaximum, + getDepositedAmount, + getAllowance, + getBalance, + ]); +}; diff --git a/libs/deposits/src/lib/use-get-allowance.ts b/libs/deposits/src/lib/use-get-allowance.ts index c20f450ab..42468e69e 100644 --- a/libs/deposits/src/lib/use-get-allowance.ts +++ b/libs/deposits/src/lib/use-get-allowance.ts @@ -1,30 +1,35 @@ import type { Token } from '@vegaprotocol/smart-contracts'; +import * as Sentry from '@sentry/react'; import { useWeb3React } from '@web3-react/core'; import { useCallback } from 'react'; -import { useEthereumConfig, useEthereumReadContract } from '@vegaprotocol/web3'; +import { useEthereumConfig } from '@vegaprotocol/web3'; import BigNumber from 'bignumber.js'; +import type { Asset } from '@vegaprotocol/react-helpers'; import { addDecimal } from '@vegaprotocol/react-helpers'; -export const useGetAllowance = (contract: Token | null, decimals?: number) => { +export const useGetAllowance = ( + contract: Token | null, + asset: Asset | undefined +) => { const { account } = useWeb3React(); const { config } = useEthereumConfig(); - const getAllowance = useCallback(() => { - if (!contract || !account || !config) { + const getAllowance = useCallback(async () => { + if (!contract || !account || !config || !asset) { return; } - return contract.allowance( - account, - config.collateral_bridge_contract.address - ); - }, [contract, account, config]); + try { + const res = await contract.allowance( + account, + config.collateral_bridge_contract.address + ); - const { state, refetch } = useEthereumReadContract(getAllowance); + return new BigNumber(addDecimal(res.toString(), asset.decimals)); + } catch (err) { + Sentry.captureException(err); + return; + } + }, [contract, account, config, asset]); - const allowance = - state.data && decimals - ? new BigNumber(addDecimal(state.data.toString(), decimals)) - : undefined; - - return { allowance, refetch }; + return getAllowance; }; diff --git a/libs/deposits/src/lib/use-get-balance-of-erc20-token.ts b/libs/deposits/src/lib/use-get-balance-of-erc20-token.ts index 0d507c33d..7ffb0b311 100644 --- a/libs/deposits/src/lib/use-get-balance-of-erc20-token.ts +++ b/libs/deposits/src/lib/use-get-balance-of-erc20-token.ts @@ -1,30 +1,29 @@ -import { useEthereumReadContract } from '@vegaprotocol/web3'; import type { Token } from '@vegaprotocol/smart-contracts'; +import * as Sentry from '@sentry/react'; import { useWeb3React } from '@web3-react/core'; import { useCallback } from 'react'; import BigNumber from 'bignumber.js'; +import type { Asset } from '@vegaprotocol/react-helpers'; import { addDecimal } from '@vegaprotocol/react-helpers'; export const useGetBalanceOfERC20Token = ( contract: Token | null, - decimals: number | undefined + asset: Asset | undefined ) => { const { account } = useWeb3React(); - - const getBalance = useCallback(() => { - if (!contract || !account) { + const getBalance = useCallback(async () => { + if (!contract || !asset || !account) { return; } - return contract.balanceOf(account); - }, [contract, account]); + try { + const res = await contract.balanceOf(account); + return new BigNumber(addDecimal(res.toString(), asset.decimals)); + } catch (err) { + Sentry.captureException(err); + return; + } + }, [contract, asset, account]); - const { state, refetch } = useEthereumReadContract(getBalance); - - const balance = - state.data && decimals - ? new BigNumber(addDecimal(state.data?.toString(), decimals)) - : undefined; - - return { balance, refetch }; + return getBalance; }; diff --git a/libs/deposits/src/lib/use-get-deposit-limits.ts b/libs/deposits/src/lib/use-get-deposit-limits.ts deleted file mode 100644 index 9173ea2af..000000000 --- a/libs/deposits/src/lib/use-get-deposit-limits.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { ethers } from 'ethers'; -import type { Asset } from './deposit-manager'; -import { - useBridgeContract, - useEthereumConfig, - useEthereumReadContract, -} from '@vegaprotocol/web3'; -import BigNumber from 'bignumber.js'; -import { addDecimal } from '@vegaprotocol/react-helpers'; -import { useWeb3React } from '@web3-react/core'; - -export const useGetDepositLimits = (asset?: Asset) => { - const { account, provider } = useWeb3React(); - const { config } = useEthereumConfig(); - const contract = useBridgeContract(true); - const [userTotal, setUserTotal] = useState(null); - const getLimits = useCallback(async () => { - if (!contract || !asset || asset.source.__typename !== 'ERC20') { - return; - } - - return contract.get_deposit_maximum(asset.source.contractAddress); - }, [asset, contract]); - - useEffect(() => { - if ( - !provider || - !config || - !account || - !asset || - asset.source.__typename !== 'ERC20' - ) { - return; - } - - const abicoder = new ethers.utils.AbiCoder(); - const innerHash = ethers.utils.keccak256( - abicoder.encode(['address', 'uint256'], [account, 4]) - ); - const storageLocation = ethers.utils.keccak256( - abicoder.encode( - ['address', 'bytes32'], - [asset.source.contractAddress, innerHash] - ) - ); - (async () => { - const res = await provider.getStorageAt( - config.collateral_bridge_contract.address, - storageLocation - ); - const value = new BigNumber(res, 16).toString(); - setUserTotal(new BigNumber(addDecimal(value, asset.decimals))); - })(); - }, [provider, config, account, asset]); - - const { - state: { data }, - } = useEthereumReadContract(getLimits); - - if (!data || !userTotal || !asset) return null; - - const max = new BigNumber(addDecimal(data.toString(), asset.decimals)); - - return { - max: max.isEqualTo(0) ? new BigNumber(Infinity) : max, - deposited: userTotal, - }; -}; diff --git a/libs/deposits/src/lib/use-get-deposit-maximum.ts b/libs/deposits/src/lib/use-get-deposit-maximum.ts new file mode 100644 index 000000000..beec6d081 --- /dev/null +++ b/libs/deposits/src/lib/use-get-deposit-maximum.ts @@ -0,0 +1,32 @@ +import { useCallback } from 'react'; +import * as Sentry from '@sentry/react'; +import BigNumber from 'bignumber.js'; +import type { Asset } from '@vegaprotocol/react-helpers'; +import { addDecimal } from '@vegaprotocol/react-helpers'; +import type { + CollateralBridge, + CollateralBridgeNew, +} from '@vegaprotocol/smart-contracts'; + +export const useGetDepositMaximum = ( + contract: CollateralBridge | CollateralBridgeNew | null, + asset: Asset | undefined +) => { + const getDepositMaximum = useCallback(async () => { + if (!contract || !asset || asset.source.__typename !== 'ERC20') { + return; + } + try { + const res = await contract.get_deposit_maximum( + asset.source.contractAddress + ); + const max = new BigNumber(addDecimal(res.toString(), asset.decimals)); + return max.isEqualTo(0) ? new BigNumber(Infinity) : max; + } catch (err) { + Sentry.captureException(err); + return; + } + }, [contract, asset]); + + return getDepositMaximum; +}; diff --git a/libs/deposits/src/lib/use-get-deposited-amount.ts b/libs/deposits/src/lib/use-get-deposited-amount.ts new file mode 100644 index 000000000..ad85f4050 --- /dev/null +++ b/libs/deposits/src/lib/use-get-deposited-amount.ts @@ -0,0 +1,50 @@ +import { useCallback } from 'react'; +import * as Sentry from '@sentry/react'; +import { ethers } from 'ethers'; +import { useEthereumConfig } from '@vegaprotocol/web3'; +import BigNumber from 'bignumber.js'; +import type { Asset } from '@vegaprotocol/react-helpers'; +import { addDecimal } from '@vegaprotocol/react-helpers'; +import { useWeb3React } from '@web3-react/core'; + +export const useGetDepositedAmount = (asset: Asset | undefined) => { + const { account, provider } = useWeb3React(); + const { config } = useEthereumConfig(); + + // For an explaination of how this code works see here: https://gist.github.com/emilbayes/44a36f59b06b1f3edb9cf914041544ed + const getDepositedAmount = useCallback(async () => { + if ( + !provider || + !config || + !account || + !asset || + asset.source.__typename !== 'ERC20' + ) { + return; + } + + try { + const abicoder = new ethers.utils.AbiCoder(); + const innerHash = ethers.utils.keccak256( + abicoder.encode(['address', 'uint256'], [account, 4]) + ); + const storageLocation = ethers.utils.keccak256( + abicoder.encode( + ['address', 'bytes32'], + [asset.source.contractAddress, innerHash] + ) + ); + const res = await provider.getStorageAt( + config.collateral_bridge_contract.address, + storageLocation + ); + const value = new BigNumber(res, 16).toString(); + return new BigNumber(addDecimal(value, asset.decimals)); + } catch (err) { + Sentry.captureException(err); + return; + } + }, [provider, asset, config, account]); + + return getDepositedAmount; +}; diff --git a/libs/deposits/src/lib/use-submit-approval.ts b/libs/deposits/src/lib/use-submit-approval.ts index 3ee9f9c5f..55255be38 100644 --- a/libs/deposits/src/lib/use-submit-approval.ts +++ b/libs/deposits/src/lib/use-submit-approval.ts @@ -1,10 +1,41 @@ +import { isAssetTypeERC20, removeDecimal } from '@vegaprotocol/react-helpers'; +import * as Sentry from '@sentry/react'; import type { Token } from '@vegaprotocol/smart-contracts'; -import { useEthereumTransaction } from '@vegaprotocol/web3'; +import { + useEthereumConfig, + useEthereumTransaction, + useTokenContract, +} from '@vegaprotocol/web3'; +import { useDepositStore } from './deposit-store'; +import { useGetAllowance } from './use-get-allowance'; -export const useSubmitApproval = (contract: Token | null) => { +export const useSubmitApproval = () => { + const { config } = useEthereumConfig(); + const { asset, update } = useDepositStore(); + const contract = useTokenContract( + isAssetTypeERC20(asset) ? asset : undefined, + true + ); + const getAllowance = useGetAllowance(contract, asset); const transaction = useEthereumTransaction( contract, 'approve' ); - return transaction; + return { + ...transaction, + perform: async () => { + if (!asset || !config) return; + try { + const amount = removeDecimal('1000000', asset.decimals); + await transaction.perform( + config.collateral_bridge_contract.address, + amount + ); + const allowance = await getAllowance(); + update({ allowance }); + } catch (err) { + Sentry.captureException(err); + } + }, + }; }; diff --git a/libs/deposits/src/lib/use-submit-deposit.tsx b/libs/deposits/src/lib/use-submit-deposit.tsx index 9ed43f514..ca9630913 100644 --- a/libs/deposits/src/lib/use-submit-deposit.tsx +++ b/libs/deposits/src/lib/use-submit-deposit.tsx @@ -1,21 +1,29 @@ import { gql, useSubscription } from '@apollo/client'; +import * as Sentry from '@sentry/react'; import type { DepositEvent, DepositEventVariables, } from './__generated__/DepositEvent'; import { DepositStatus } from '@vegaprotocol/types'; import { useState } from 'react'; -import { remove0x } from '@vegaprotocol/react-helpers'; +import { + isAssetTypeERC20, + remove0x, + removeDecimal, +} from '@vegaprotocol/react-helpers'; import { useBridgeContract, useEthereumConfig, useEthereumTransaction, + useTokenContract, } from '@vegaprotocol/web3'; import type { CollateralBridge, CollateralBridgeNew, } from '@vegaprotocol/smart-contracts'; import { prepend0x } from '@vegaprotocol/smart-contracts'; +import { useDepositStore } from './deposit-store'; +import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token'; const DEPOSIT_EVENT_SUB = gql` subscription DepositEvent($partyId: ID!) { @@ -32,17 +40,24 @@ const DEPOSIT_EVENT_SUB = gql` `; export const useSubmitDeposit = () => { + const { asset, update } = useDepositStore(); const { config } = useEthereumConfig(); - const contract = useBridgeContract(true); + const bridgeContract = useBridgeContract(true); + const tokenContract = useTokenContract( + isAssetTypeERC20(asset) ? asset : undefined, + true + ); // Store public key from contract arguments for use in the subscription, // NOTE: it may be different from the users connected key const [partyId, setPartyId] = useState(null); + const getBalance = useGetBalanceOfERC20Token(tokenContract, asset); + const transaction = useEthereumTransaction< CollateralBridgeNew | CollateralBridge, 'deposit_asset' - >(contract, 'deposit_asset', config?.confirmations, true); + >(bridgeContract, 'deposit_asset', config?.confirmations, true); useSubscription(DEPOSIT_EVENT_SUB, { variables: { partyId: partyId ? remove0x(partyId) : '' }, @@ -78,10 +93,22 @@ export const useSubmitDeposit = () => { return { ...transaction, - perform: (...args: Parameters) => { - setPartyId(args[2]); - const publicKey = prepend0x(args[2]); - transaction.perform(args[0], args[1], publicKey); + perform: async (args: { + assetSource: string; + amount: string; + vegaPublicKey: string; + }) => { + if (!asset) return; + try { + setPartyId(args.vegaPublicKey); + const publicKey = prepend0x(args.vegaPublicKey); + const amount = removeDecimal(args.amount, asset.decimals); + await transaction.perform(args.assetSource, amount, publicKey); + const balance = await getBalance(); + update({ balance }); + } catch (err) { + Sentry.captureException(err); + } }, }; }; diff --git a/libs/deposits/src/lib/use-submit-faucet.ts b/libs/deposits/src/lib/use-submit-faucet.ts index a0eb4ab7a..4bc28e7e8 100644 --- a/libs/deposits/src/lib/use-submit-faucet.ts +++ b/libs/deposits/src/lib/use-submit-faucet.ts @@ -1,10 +1,31 @@ -import type { Token, TokenFaucetable } from '@vegaprotocol/smart-contracts'; -import { useEthereumTransaction } from '@vegaprotocol/web3'; +import type { TokenFaucetable } from '@vegaprotocol/smart-contracts'; +import * as Sentry from '@sentry/react'; +import { useEthereumTransaction, useTokenContract } from '@vegaprotocol/web3'; +import { useDepositStore } from './deposit-store'; +import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token'; +import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers'; -export const useSubmitFaucet = (contract: Token | TokenFaucetable | null) => { +export const useSubmitFaucet = () => { + const { asset, update } = useDepositStore(); + const contract = useTokenContract( + isAssetTypeERC20(asset) ? asset : undefined, + true + ); + const getBalance = useGetBalanceOfERC20Token(contract, asset); const transaction = useEthereumTransaction( contract, 'faucet' ); - return transaction; + return { + ...transaction, + perform: async () => { + try { + await transaction.perform(); + const balance = await getBalance(); + update({ balance }); + } catch (err) { + Sentry.captureException(err); + } + }, + }; }; diff --git a/libs/environment/README.md b/libs/environment/README.md index 32ad9c42f..0666759b5 100644 --- a/libs/environment/README.md +++ b/libs/environment/README.md @@ -12,6 +12,8 @@ The environment variables needed to be present for any app consuming this librar `NX_VEGA_URL` OR `NX_VEGA_CONFIG_URL` - either the network configuration url or a url to a node to directly connect to +`NX_VEGA_WALLET_URL` the default vega wallet URL + For examples, see Block Explorer's .env files [here](../../apps/explorer) ## Running unit tests diff --git a/libs/environment/src/components/node-switcher/node-switcher.tsx b/libs/environment/src/components/node-switcher/node-switcher.tsx index 95a9f4a43..18f8d63e3 100644 --- a/libs/environment/src/components/node-switcher/node-switcher.tsx +++ b/libs/environment/src/components/node-switcher/node-switcher.tsx @@ -169,7 +169,10 @@ export const NodeSwitcher = ({ onChange={(e) => setCustomNodeText(e.target.value)} /> { setNetworkError(null); updateNodeUrl(CUSTOM_NODE_KEY, customNodeText); diff --git a/libs/environment/src/utils/compile-environment.ts b/libs/environment/src/utils/compile-environment.ts index 445c4f786..c733e6434 100644 --- a/libs/environment/src/utils/compile-environment.ts +++ b/libs/environment/src/utils/compile-environment.ts @@ -68,6 +68,8 @@ const getBundledEnvironmentValue = (key: EnvKey) => { return process.env['NX_GITHUB_FEEDBACK_URL']; case 'VEGA_EXPLORER_URL': return process.env['NX_VEGA_EXPLORER_URL']; + case 'VEGA_WALLET_URL': + return process.env['NX_VEGA_WALLET_URL']; } }; diff --git a/libs/environment/src/utils/validate-environment.ts b/libs/environment/src/utils/validate-environment.ts index 2289ab6a2..d3a659c6d 100644 --- a/libs/environment/src/utils/validate-environment.ts +++ b/libs/environment/src/utils/validate-environment.ts @@ -14,6 +14,7 @@ export enum Networks { const schemaObject = { VEGA_URL: z.optional(z.string()), + VEGA_WALLET_URL: z.optional(z.string()), VEGA_CONFIG_URL: z.optional(z.string()), GIT_BRANCH: z.optional(z.string()), GIT_COMMIT_HASH: z.optional(z.string()), diff --git a/libs/environment/src/utils/validate-node.tsx b/libs/environment/src/utils/validate-node.tsx index 95b1c110f..43ef8113f 100644 --- a/libs/environment/src/utils/validate-node.tsx +++ b/libs/environment/src/utils/validate-node.tsx @@ -2,22 +2,18 @@ import { t } from '@vegaprotocol/react-helpers'; import { CUSTOM_NODE_KEY, ErrorType } from '../types'; import type { Networks, NodeData } from '../types'; -export const getIsNodeLoading = ({ - chain, - responseTime, - block, - ssl, -}: NodeData) => { +export const getIsNodeLoading = (node?: NodeData): boolean => { + if (!node) return false; return ( - chain.isLoading || - responseTime.isLoading || - block.isLoading || - ssl.isLoading + node.chain.isLoading || + node.responseTime.isLoading || + node.block.isLoading || + node.ssl.isLoading ); }; export const getHasInvalidChain = (env: Networks, chain = '') => { - return !(chain.split('-')[0] === env.toLowerCase() ?? false); + return !chain.split('-').includes(env.toLowerCase()); }; export const getIsInvalidUrl = (url: string) => { diff --git a/libs/market-depth/src/lib/orderbook.tsx b/libs/market-depth/src/lib/orderbook.tsx index 01d8f0de6..5fb8adbb0 100644 --- a/libs/market-depth/src/lib/orderbook.tsx +++ b/libs/market-depth/src/lib/orderbook.tsx @@ -282,13 +282,13 @@ export const Orderbook = ({ const hasData = renderedRows.data && renderedRows.data.length !== 0; return (
{t('Bid Vol')}
diff --git a/libs/network-info/src/network-info.tsx b/libs/network-info/src/network-info.tsx index 35967de16..3db53eb69 100644 --- a/libs/network-info/src/network-info.tsx +++ b/libs/network-info/src/network-info.tsx @@ -31,7 +31,7 @@ export const NetworkInfo = () => { . setNodeSwitcherOpen()}>{t('Edit')}

-

+

{t('Reading Ethereum data from')}{' '} {ETHEREUM_PROVIDER_URL} diff --git a/libs/orders/src/lib/order-hooks/use-order-edit.tsx b/libs/orders/src/lib/order-hooks/use-order-edit.tsx index e3853d335..36d466e17 100644 --- a/libs/orders/src/lib/order-hooks/use-order-edit.tsx +++ b/libs/orders/src/lib/order-hooks/use-order-edit.tsx @@ -51,14 +51,11 @@ export const useOrderEdit = (order: OrderFields | null) => { orderAmendment: { orderId: order.id, marketId: order.market.id, - // @ts-ignore fix me please! price: { value: removeDecimal(args.price, order.market.decimalPlaces), }, timeInForce: VegaWalletOrderTimeInForce[order.timeInForce], - // @ts-ignore fix me please! sizeDelta: 0, - // @ts-ignore fix me please! expiresAt: order.expiresAt ? { value: toNanoSeconds(new Date(order.expiresAt)), // Wallet expects timestamp in nanoseconds diff --git a/libs/react-helpers/src/index.ts b/libs/react-helpers/src/index.ts index 75f83254e..9611b2cd4 100644 --- a/libs/react-helpers/src/index.ts +++ b/libs/react-helpers/src/index.ts @@ -1,4 +1,5 @@ export * from './hooks'; +export * from './lib/assets'; export * from './lib/context'; export * from './lib/determine-id'; export * from './lib/format'; diff --git a/libs/react-helpers/src/lib/assets.ts b/libs/react-helpers/src/lib/assets.ts new file mode 100644 index 000000000..df799b3ae --- /dev/null +++ b/libs/react-helpers/src/lib/assets.ts @@ -0,0 +1,30 @@ +export interface ERC20AssetSource { + __typename: 'ERC20'; + contractAddress: string; +} + +export interface BuiltinAssetSource { + __typename: 'BuiltinAsset'; +} + +export interface Asset { + __typename: 'Asset'; + id: string; + symbol: string; + name: string; + decimals: number; + source: ERC20AssetSource | BuiltinAssetSource; +} + +export type ERC20Asset = Omit & { + source: ERC20AssetSource; +}; + +export type BuiltinAsset = Omit & { + source: BuiltinAssetSource; +}; + +export const isAssetTypeERC20 = (asset?: Asset): asset is ERC20Asset => { + if (!asset?.source) return false; + return asset.source.__typename === 'ERC20'; +}; diff --git a/libs/tailwindcss-config/src/theme.js b/libs/tailwindcss-config/src/theme.js index 6001a1b68..95c321fff 100644 --- a/libs/tailwindcss-config/src/theme.js +++ b/libs/tailwindcss-config/src/theme.js @@ -204,10 +204,10 @@ module.exports = { 'inset-danger': `${boxShadowPosition.insetUnderline} ${colours.danger}`, input: `${boxShadowPosition.insetShading} ${colours.white['80']}`, 'input-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}`, - 'input-focus': `${boxShadowPosition.insetShading} ${colours.white['80']}, ${boxShadowPosition.insetUnderline} ${colours.vega.pink}`, - 'input-focus-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}, ${boxShadowPosition.insetUnderline} ${colours.vega.yellow}`, - 'input-focus-error': `${boxShadowPosition.insetShading} ${colours.white['80']}, ${boxShadowPosition.insetUnderline} ${colours.danger}`, - 'input-focus-error-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}, ${boxShadowPosition.insetUnderline} ${colours.danger}`, + 'input-focus': `${boxShadowPosition.insetUnderline} ${colours.vega.pink}, ${boxShadowPosition.insetShading} ${colours.white['80']}`, + 'input-focus-dark': `${boxShadowPosition.insetUnderline} ${colours.vega.yellow}, ${boxShadowPosition.insetShading} ${colours.black['80']}`, + 'input-focus-error': `${boxShadowPosition.insetUnderline} ${colours.danger}, ${boxShadowPosition.insetShading} ${colours.white['80']}`, + 'input-focus-error-dark': `${boxShadowPosition.insetUnderline} ${colours.danger}, ${boxShadowPosition.insetShading} ${colours.black['80']}`, 'checkbox-focus': `${boxShadowPosition.insetShading} ${colours.white['80']}, ${boxShadowPosition.outer} ${colours.vega.pink}`, 'checkbox-focus-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}, ${boxShadowPosition.outer} ${colours.vega.yellow}`, }, diff --git a/libs/tailwindcss-config/src/vega-custom-classes-lite.js b/libs/tailwindcss-config/src/vega-custom-classes-lite.js index 470774b1b..0e8a38a82 100644 --- a/libs/tailwindcss-config/src/vega-custom-classes-lite.js +++ b/libs/tailwindcss-config/src/vega-custom-classes-lite.js @@ -45,8 +45,6 @@ const vegaCustomClassesLite = plugin(function ({ addUtilities }) { backgroundColor: 'rgba(0, 143, 74, 0.1)', border: `1px solid ${theme.colors.darkerGreen}`, color: theme.colors.darkerGreen, - paddingTop: '0.5rem', - paddingBottom: '0.5rem', '&:hover': { backgroundColor: theme.colors.darkerGreen, color: theme.colors.white.DEFAULT, @@ -68,8 +66,6 @@ const vegaCustomClassesLite = plugin(function ({ addUtilities }) { '.sellButton': { textTransform: 'uppercase', textDecoration: 'none', - paddingTop: '0.5rem', - paddingBottom: '0.5rem', backgroundColor: 'rgba(255, 8, 126, 0.1)', border: `1px solid ${theme.colors.pink}`, color: theme.colors.pink, diff --git a/libs/trades/src/lib/trades-table.tsx b/libs/trades/src/lib/trades-table.tsx index af9dbda1f..94e165572 100644 --- a/libs/trades/src/lib/trades-table.tsx +++ b/libs/trades/src/lib/trades-table.tsx @@ -24,7 +24,7 @@ const changeCellClass = if (typeof rowIndex === 'number') { const prevRowNode = api.getModel().getRow(rowIndex + 1); - const prevValue = prevRowNode?.data[dataKey]; + const prevValue = prevRowNode?.data && prevRowNode.data[dataKey]; const valueNum = new BigNumber(value); if (valueNum.isGreaterThan(prevValue)) { diff --git a/libs/types/src/__generated__/globalTypes.ts b/libs/types/src/__generated__/globalTypes.ts index aaf291df8..edf55e031 100644 --- a/libs/types/src/__generated__/globalTypes.ts +++ b/libs/types/src/__generated__/globalTypes.ts @@ -67,6 +67,17 @@ export enum BusEventType { Withdrawal = "Withdrawal", } +/** + * Comparator describes the type of comparison. + */ +export enum ConditionOperator { + OperatorEquals = "OperatorEquals", + OperatorGreaterThan = "OperatorGreaterThan", + OperatorGreaterThanOrEqual = "OperatorGreaterThanOrEqual", + OperatorLessThan = "OperatorLessThan", + OperatorLessThanOrEqual = "OperatorLessThanOrEqual", +} + /** * The status of a deposit */ @@ -119,6 +130,14 @@ export enum NodeStatus { Validator = "Validator", } +/** + * Status describe the status of the oracle spec + */ +export enum OracleSpecStatus { + StatusActive = "StatusActive", + StatusUnused = "StatusUnused", +} + /** * Reason for the order being rejected by the core node */ @@ -202,6 +221,19 @@ export enum OrderType { Network = "Network", } +/** + * Type describes the type of properties that are supported by the oracle + * engine. + */ +export enum PropertyKeyType { + TypeBoolean = "TypeBoolean", + TypeDecimal = "TypeDecimal", + TypeEmpty = "TypeEmpty", + TypeInteger = "TypeInteger", + TypeString = "TypeString", + TypeTimestamp = "TypeTimestamp", +} + /** * Reason for the proposal being rejected by the core node */ diff --git a/libs/ui-toolkit/src/components/key-value-table/key-value-table.spec.tsx b/libs/ui-toolkit/src/components/key-value-table/key-value-table.spec.tsx index 78ae0ac6d..8e891f4b5 100644 --- a/libs/ui-toolkit/src/components/key-value-table/key-value-table.spec.tsx +++ b/libs/ui-toolkit/src/components/key-value-table/key-value-table.spec.tsx @@ -90,3 +90,19 @@ it('Applies muted class if prop is passed', () => { 'w-full border-collapse mb-8 [border-spacing:0] break-all' ); }); + +it('Applies id to row if passed', () => { + render( + + + My value + My value + + + ); + + expect(screen.getByTestId('key-value-table-row')).toHaveAttribute( + 'id', + 'thisistheid' + ); +}); diff --git a/libs/ui-toolkit/src/components/key-value-table/key-value-table.tsx b/libs/ui-toolkit/src/components/key-value-table/key-value-table.tsx index ae71f9ad7..4b5b8d4f1 100644 --- a/libs/ui-toolkit/src/components/key-value-table/key-value-table.tsx +++ b/libs/ui-toolkit/src/components/key-value-table/key-value-table.tsx @@ -77,6 +77,7 @@ export const KeyValueTableRow = ({ noBorder = false, dtClassName, ddClassName, + id, }: KeyValueTableRowProps) => { const dlClassName = classNames( 'flex gap-1 flex-wrap justify-between ', @@ -92,14 +93,24 @@ export const KeyValueTableRow = ({ const dtClassNames = `break-words font-medium uppercase align-top p-4 capitalize ${dtClassName}`; const ddClassNames = classNames( 'align-top p-4 text-black/60 dark:text-white/60 break-words', + 'group-target:text-white dark:group-target:text-black', { 'font-mono': numerical, }, ddClassName ); + const attributes = {} as Partial>; + if (id) { + attributes.id = id; + } + return ( -

+
{children[0]}
{children[1]}
diff --git a/libs/ui-toolkit/src/components/splash/splash.tsx b/libs/ui-toolkit/src/components/splash/splash.tsx index e1c4b3fb3..f86751d43 100644 --- a/libs/ui-toolkit/src/components/splash/splash.tsx +++ b/libs/ui-toolkit/src/components/splash/splash.tsx @@ -8,8 +8,7 @@ export interface SplashProps { export const Splash = ({ children }: SplashProps) => { const splashClasses = classNames( 'w-full h-full', - 'flex items-center justify-center', - 'text-white' + 'flex items-center justify-center' ); return
{children}
; }; diff --git a/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx b/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx index 3edbd9fbf..e64076eaa 100644 --- a/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx +++ b/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx @@ -13,7 +13,7 @@ export const ThemeSwitcher = ({ sunClassName?: string; moonClassName?: string; }) => { - const sunClasses = classNames('dark:hidden text-white', sunClassName); + const sunClasses = classNames('dark:hidden text-black', sunClassName); const moonClasses = classNames( 'hidden dark:inline text-white', moonClassName diff --git a/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx b/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx index 10bcc271d..6db0d0c25 100644 --- a/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx +++ b/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx @@ -10,6 +10,7 @@ import { VegaWalletContext } from '../context'; import { VegaConnectDialog } from './connect-dialog'; import type { VegaConnectDialogProps } from '..'; import { RestConnector } from '../connectors'; +import { EnvironmentProvider } from '@vegaprotocol/environment'; let defaultProps: VegaConnectDialogProps; let defaultContextValue: VegaWalletContextShape; @@ -35,16 +36,28 @@ beforeEach(() => { const DEFAULT_URL = 'http://localhost:1789/api/v1'; +const mockEnvironment = { + VEGA_ENV: 'TESTNET', + VEGA_URL: 'https://vega-node.url', + VEGA_NETWORKS: JSON.stringify({}), + GIT_BRANCH: 'test', + GIT_COMMIT_HASH: 'abcdef', + GIT_ORIGIN_URL: 'https://github.com/test/repo', + VEGA_WALLET_URL: DEFAULT_URL, +}; + function generateJSX( props?: Partial, contextValue?: Partial ) { return ( - - - + + + + + ); } @@ -133,11 +146,12 @@ it('Successful connection using custom url', async () => { }); it('Unsuccessful connection using rest auth form', async () => { + const errMessage = 'Error message'; // Error from service let spy = jest .spyOn(defaultProps.connectors['rest'] as RestConnector, 'authenticate') .mockImplementation(() => - Promise.resolve({ success: false, error: 'Error message' }) + Promise.resolve({ success: false, error: errMessage }) ); render(generateJSX({ dialogOpen: true })); @@ -154,9 +168,7 @@ it('Unsuccessful connection using rest auth form', async () => { expect(spy).toHaveBeenCalledWith(DEFAULT_URL, fields); - expect(screen.getByTestId('form-error')).toHaveTextContent( - 'Something went wrong' - ); + expect(screen.getByTestId('form-error')).toHaveTextContent(errMessage); expect(defaultProps.setDialogOpen).not.toHaveBeenCalled(); // Fetch failed due to wallet not running diff --git a/libs/wallet/src/connectors/rest-connector.ts b/libs/wallet/src/connectors/rest-connector.ts index 9da0c21ed..065ba7180 100644 --- a/libs/wallet/src/connectors/rest-connector.ts +++ b/libs/wallet/src/connectors/rest-connector.ts @@ -1,43 +1,85 @@ -import type { Configuration } from '@vegaprotocol/vegawallet-service-api-client'; -import { - createConfiguration, - ServerConfiguration, - DefaultApi, -} from '@vegaprotocol/vegawallet-service-api-client'; -import { LocalStorage } from '@vegaprotocol/react-helpers'; +import * as Sentry from '@sentry/react'; +import { LocalStorage, t } from '@vegaprotocol/react-helpers'; import { WALLET_CONFIG } from '../storage-keys'; import type { VegaConnector } from './vega-connector'; -import type { TransactionSubmission } from '../wallet-types'; +import type { TransactionError, TransactionSubmission } from '../wallet-types'; +import { z } from 'zod'; // Perhaps there should be a default ConnectorConfig that others can extend off. Do all connectors // need to use local storage, I don't think so... interface RestConnectorConfig { token: string | null; connector: 'rest'; + url: string | null; } +enum Endpoints { + Auth = 'auth/token', + Command = 'command/sync', + Keys = 'keys', +} + +export const AuthTokenSchema = z.object({ + token: z.string(), +}); + +export const TransactionResponseSchema = z.object({ + txId: z.string(), + txHash: z.string(), + tx: z.object({ + From: z.object({ + PubKey: z.string(), + }), + input_data: z.string(), + pow: z.object({ + tid: z.string(), + nonce: z.number(), + }), + signature: z.object({ + algo: z.string(), + value: z.string(), + version: z.number(), + }), + }), + sentAt: z.string(), + receivedAt: z.string(), +}); + +export const GetKeysSchema = z.object({ + keys: z.array( + z.object({ + algorithm: z.object({ + name: z.string(), + version: z.number(), + }), + index: z.number(), + meta: z.array( + z.object({ + key: z.string(), + value: z.string(), + }) + ), + pub: z.string(), + tainted: z.boolean(), + }) + ), +}); + /** * Connector for using the Vega Wallet Service rest api, requires authentication to get a session token */ export class RestConnector implements VegaConnector { configKey = WALLET_CONFIG; - apiConfig: Configuration; - service: DefaultApi; description = 'Connects using REST to a running Vega wallet service'; + url: string | null = null; + token: string | null = null; constructor() { const cfg = this.getConfig(); - - // If theres a stored auth token create api config with bearer authMethod - this.apiConfig = cfg?.token - ? createConfiguration({ - authMethods: { - bearer: `Bearer ${cfg.token}`, - }, - }) - : createConfiguration(); - - this.service = new DefaultApi(this.apiConfig); + if (cfg) { + this.token = cfg.token; + this.url = cfg.url; + } } async authenticate( @@ -48,39 +90,54 @@ export class RestConnector implements VegaConnector { } ) { try { - const service = new DefaultApi( - createConfiguration({ - baseServer: new ServerConfiguration>(url, {}), - }) - ); + this.url = url; - const res = await service.authTokenPost(params); + const res = await this.request(Endpoints.Auth, { + method: 'post', + body: JSON.stringify(params), + }); - // Renew service instance with default bearer authMethod now that we have the token - this.service = new DefaultApi( - createConfiguration({ - baseServer: new ServerConfiguration>(url, {}), - authMethods: { - bearer: `Bearer ${res.token}`, - }, - }) - ); + if (res.status === 403) { + return { success: false, error: t('Invalid credentials') }; + } + + if (res.error) { + return { success: false, error: res.error }; + } + + const data = AuthTokenSchema.parse(res.data); // Store the token, and other things for later - this.setConfig({ connector: 'rest', token: res.token }); + this.setConfig({ + connector: 'rest', + token: data.token, + url: this.url, + }); + this.token = data.token; return { success: true, error: null }; } catch (err) { - return { success: false, error: err }; + return { success: false, error: 'Authentication failed' }; } } async connect() { try { - const res = await this.service.keysGet(); - return res.keys; + const res = await this.request(Endpoints.Keys, { + method: 'get', + headers: { + authorization: `Bearer ${this.token}`, + }, + }); + + if (res.error) { + return null; + } + + const data = GetKeysSchema.parse(res.data); + + return data.keys; } catch (err) { - console.error(err); // keysGet failed, its likely that the session has expired so remove the token from storage this.clearConfig(); return null; @@ -89,9 +146,14 @@ export class RestConnector implements VegaConnector { async disconnect() { try { - await this.service.authTokenDelete(); + await this.request(Endpoints.Auth, { + method: 'delete', + headers: { + authorization: `Bearer ${this.token}`, + }, + }); } catch (err) { - console.error(err); + Sentry.captureException(err); } finally { // Always clear config, if authTokenDelete fails the user still tried to // connect so clear the config (and containing token) from storage @@ -101,28 +163,31 @@ export class RestConnector implements VegaConnector { async sendTx(body: TransactionSubmission) { try { - const res = await this.service.commandSyncPost(body); - return res; + const res = await this.request(Endpoints.Command, { + method: 'post', + body: JSON.stringify(body), + headers: { + authorization: `Bearer ${this.token}`, + }, + }); + + // User rejected + if (res.status === 401) { + return null; + } + + if (res.error) { + return { + error: res.error, + }; + } + + const data = TransactionResponseSchema.parse(res.data); + + return data; } catch (err) { - return this.handleSendTxError(err); - } - } - - private handleSendTxError(err: unknown) { - const unexpectedError = { error: 'Something went wrong' }; - - if (isServiceError(err)) { - if (err.code === 401) { - return { error: 'User rejected' }; - } - - try { - return JSON.parse(err.body ?? ''); - } catch { - return unexpectedError; - } - } else { - return unexpectedError; + Sentry.captureException(err); + return null; } } @@ -146,18 +211,59 @@ export class RestConnector implements VegaConnector { private clearConfig() { LocalStorage.removeItem(this.configKey); } -} -interface ServiceError { - code: number; - body: string | undefined; - headers: object; -} + /** Parse more complex error object into a single string */ + private parseError(err: TransactionError): string { + if ('error' in err) { + return err.error; + } -export const isServiceError = (err: unknown): err is ServiceError => { - // Some responses don't contain body object - if (typeof err === 'object' && err !== null && 'code' in err) { - return true; + if ('errors' in err) { + return err.errors['*'].join(', '); + } + + return t("Something wen't wrong"); } - return false; -}; + + private async request( + endpoint: Endpoints, + options: RequestInit + ): Promise<{ status?: number; data?: unknown; error?: string }> { + try { + const fetchResult = await fetch(`${this.url}/${endpoint}`, { + ...options, + headers: { + ...options.headers, + 'Content-Type': 'application/json', + }, + }); + + if (!fetchResult.ok) { + const errorData = await fetchResult.json(); + return { + status: fetchResult.status, + error: this.parseError(errorData), + }; + } + + // auth/token delete doesnt return json + if (endpoint === 'auth/token' && options.method === 'delete') { + const textResult = await fetchResult.text(); + return { + status: fetchResult.status, + data: textResult, + }; + } else { + const jsonResult = await fetchResult.json(); + return { + status: fetchResult.status, + data: jsonResult, + }; + } + } catch (err) { + return { + error: 'Failed to fetch', + }; + } + } +} diff --git a/libs/wallet/src/connectors/vega-connector.ts b/libs/wallet/src/connectors/vega-connector.ts index 213988294..a8fcd4ad4 100644 --- a/libs/wallet/src/connectors/vega-connector.ts +++ b/libs/wallet/src/connectors/vega-connector.ts @@ -1,16 +1,8 @@ +import type { VegaKey } from '../wallet-types'; import type { - VegaKey, + TransactionSubmission, TransactionResponse, -} from '@vegaprotocol/vegawallet-service-api-client'; -import type { TransactionSubmission } from '../wallet-types'; - -type ErrorResponse = - | { - error: string; - } - | { - errors: object; - }; +} from '../wallet-types'; export interface VegaConnector { /** Description of how to use this connector */ @@ -25,5 +17,5 @@ export interface VegaConnector { /** Send a TX to the network. Only support order submission for now */ sendTx: ( body: TransactionSubmission - ) => Promise; + ) => Promise; } diff --git a/libs/wallet/src/context.ts b/libs/wallet/src/context.ts index 3f0b6c238..00fa7568d 100644 --- a/libs/wallet/src/context.ts +++ b/libs/wallet/src/context.ts @@ -1,18 +1,10 @@ -import type { - VegaKey, - TransactionResponse, -} from '@vegaprotocol/vegawallet-service-api-client'; +import type { VegaKey } from './wallet-types'; import { createContext } from 'react'; import type { VegaConnector } from './connectors'; -import type { TransactionSubmission } from './wallet-types'; - -export type SendTxError = - | { - error: string; - } - | { - errors: object; - }; +import type { + TransactionSubmission, + TransactionResponse, +} from './wallet-types'; export interface VegaKeyExtended extends VegaKey { name: string; @@ -40,7 +32,7 @@ export interface VegaWalletContextShape { /** Send a transaction to the network, only order submissions for now */ sendTx: ( tx: TransactionSubmission - ) => Promise | null; + ) => Promise | null; } export const VegaWalletContext = createContext< diff --git a/libs/wallet/src/manage-dialog/manage-dialog.tsx b/libs/wallet/src/manage-dialog/manage-dialog.tsx index 47309264e..0357b7a72 100644 --- a/libs/wallet/src/manage-dialog/manage-dialog.tsx +++ b/libs/wallet/src/manage-dialog/manage-dialog.tsx @@ -57,12 +57,23 @@ export const VegaManageDialog = ({ )}
-

+

{truncateByChars(kp.pub, 23, 23)}

-

{kp.pub}

+

+ {kp.pub} +

- diff --git a/libs/wallet/src/provider.spec.tsx b/libs/wallet/src/provider.spec.tsx index b60c2b7b2..e2bfe0260 100644 --- a/libs/wallet/src/provider.spec.tsx +++ b/libs/wallet/src/provider.spec.tsx @@ -1,5 +1,5 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; -import type { VegaKey } from '@vegaprotocol/vegawallet-service-api-client'; +import type { VegaKey } from './wallet-types'; import { RestConnector } from './connectors'; import { useVegaWallet } from './use-vega-wallet'; import { VegaWalletProvider } from './provider'; diff --git a/libs/wallet/src/rest-connector-form.tsx b/libs/wallet/src/rest-connector-form.tsx index 84666c582..c8dcc075f 100644 --- a/libs/wallet/src/rest-connector-form.tsx +++ b/libs/wallet/src/rest-connector-form.tsx @@ -1,3 +1,4 @@ +import { useEnvironment } from '@vegaprotocol/environment'; import { t } from '@vegaprotocol/react-helpers'; import { Button, FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit'; import { useState } from 'react'; @@ -15,25 +16,24 @@ interface RestConnectorFormProps { onAuthenticate: () => void; } -const VEGA_DEFAULT_URL = 'http://localhost:1789/api/v1'; - export function RestConnectorForm({ connector, onAuthenticate, }: RestConnectorFormProps) { const [error, setError] = useState(''); - + const { VEGA_WALLET_URL } = useEnvironment(); const { register, handleSubmit, formState: { errors }, } = useForm({ defaultValues: { - url: VEGA_DEFAULT_URL, + url: VEGA_WALLET_URL, }, }); async function onSubmit(fields: FormFields) { + const authFailedMessage = t('Authentication failed'); try { setError(''); const res = await connector.authenticate(fields.url, { @@ -44,13 +44,13 @@ export function RestConnectorForm({ if (res.success) { onAuthenticate(); } else { - throw res.error; + setError(res.error || authFailedMessage); } } catch (err) { if (err instanceof TypeError) { setError(t(`Wallet not running at ${fields.url}`)); } else if (err instanceof Error) { - setError(t('Authentication failed')); + setError(authFailedMessage); } else { setError(t('Something went wrong')); } diff --git a/libs/wallet/src/use-vega-transaction.spec.tsx b/libs/wallet/src/use-vega-transaction.spec.tsx index 93532ddc9..ca145bf39 100644 --- a/libs/wallet/src/use-vega-transaction.spec.tsx +++ b/libs/wallet/src/use-vega-transaction.spec.tsx @@ -3,7 +3,7 @@ import type { VegaWalletContextShape } from './context'; import { VegaWalletContext } from './context'; import type { ReactNode } from 'react'; import { useVegaTransaction, VegaTxStatus } from './use-vega-transaction'; -import type { OrderSubmissionBody } from '@vegaprotocol/vegawallet-service-api-client'; +import type { OrderSubmissionBody } from './wallet-types'; const defaultWalletContext = { keypair: null, @@ -53,14 +53,12 @@ it('Handles a single error', async () => { result.current.send({} as OrderSubmissionBody); }); expect(result.current.transaction.status).toEqual(VegaTxStatus.Error); - expect(result.current.transaction.error).toEqual({ error: errorMessage }); + expect(result.current.transaction.error).toEqual(errorMessage); }); it('Handles multiple errors', async () => { const errorObj = { - errors: { - something: 'Went wrong!', - }, + error: 'Went wrong!', }; const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(errorObj)); const { result } = setup({ sendTx: mockSendTx }); @@ -68,7 +66,7 @@ it('Handles multiple errors', async () => { result.current.send({} as OrderSubmissionBody); }); expect(result.current.transaction.status).toEqual(VegaTxStatus.Error); - expect(result.current.transaction.error).toEqual(errorObj); + expect(result.current.transaction.error).toEqual(errorObj.error); }); it('Returns the signature if successful', async () => { diff --git a/libs/wallet/src/use-vega-transaction.tsx b/libs/wallet/src/use-vega-transaction.tsx index 2a79bb97b..8df695734 100644 --- a/libs/wallet/src/use-vega-transaction.tsx +++ b/libs/wallet/src/use-vega-transaction.tsx @@ -2,7 +2,6 @@ import type { ReactNode } from 'react'; import { useCallback, useMemo, useState } from 'react'; import type { TransactionSubmission } from './wallet-types'; import { useVegaWallet } from './use-vega-wallet'; -import type { SendTxError } from './context'; import { VegaTransactionDialog } from './vega-transaction-dialog'; import type { Intent } from '@vegaprotocol/ui-toolkit'; @@ -23,7 +22,7 @@ export enum VegaTxStatus { export interface VegaTxState { status: VegaTxStatus; - error: object | null; + error: string | null; txHash: string | null; signature: string | null; dialogOpen: boolean; @@ -48,13 +47,6 @@ export const useVegaTransaction = () => { })); }, []); - const handleError = useCallback( - (error: SendTxError) => { - setTransaction({ error, status: VegaTxStatus.Error }); - }, - [setTransaction] - ); - const reset = useCallback(() => { setTransaction(initialState); }, [setTransaction]); @@ -76,23 +68,23 @@ export const useVegaTransaction = () => { const res = await sendTx(tx); if (res === null) { - setTransaction({ status: VegaTxStatus.Default }); - return null; + // User rejected + reset(); + return; } - if ('errors' in res) { - handleError(res); - } else if ('error' in res) { - if (res.error === 'User rejected') { - reset(); - } else { - handleError(res); - } - } else if (res.tx?.signature?.value && res.txHash) { + + if (isError(res)) { + setTransaction({ error: res.error, status: VegaTxStatus.Error }); + return; + } + + if (res.tx?.signature?.value && res.txHash) { setTransaction({ status: VegaTxStatus.Pending, txHash: res.txHash, signature: res.tx.signature.value, }); + return { signature: res.tx.signature?.value, }; @@ -100,7 +92,7 @@ export const useVegaTransaction = () => { return null; }, - [sendTx, handleError, setTransaction, reset] + [sendTx, setTransaction, reset] ); const TransactionDialog = useMemo(() => { @@ -125,3 +117,10 @@ export const useVegaTransaction = () => { TransactionDialog, }; }; + +const isError = (error: unknown): error is { error: string } => { + if (error !== null && typeof error === 'object' && 'error' in error) { + return true; + } + return false; +}; diff --git a/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.spec.tsx b/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.spec.tsx index 01c3a4b90..6e92b8b02 100644 --- a/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.spec.tsx +++ b/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.spec.tsx @@ -57,7 +57,7 @@ describe('VegaTransactionDialog', () => { {...props} transaction={{ ...props.transaction, - error: { message: 'rejected' }, + error: 'rejected', status: VegaTxStatus.Error, }} /> diff --git a/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.tsx b/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.tsx index cbc1bebca..463df6290 100644 --- a/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.tsx +++ b/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.tsx @@ -1,5 +1,4 @@ import { useEnvironment } from '@vegaprotocol/environment'; -import get from 'lodash/get'; import { t } from '@vegaprotocol/react-helpers'; import { Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit'; import type { ReactNode } from 'react'; @@ -73,12 +72,7 @@ export const VegaDialog = ({ transaction }: VegaDialogProps) => { if (transaction.status === VegaTxStatus.Error) { return (
- {transaction.error && ( -
-            {get(transaction.error, 'error') ??
-              JSON.stringify(transaction.error, null, 2)}
-          
- )} +

{transaction.error}

); } diff --git a/libs/wallet/src/wallet-types.ts b/libs/wallet/src/wallet-types.ts index a09fab938..651aadef9 100644 --- a/libs/wallet/src/wallet-types.ts +++ b/libs/wallet/src/wallet-types.ts @@ -1,12 +1,88 @@ -import type { - DelegateSubmissionBody, - OrderCancellationBody, - OrderSubmissionBody, - UndelegateSubmissionBody, - VoteSubmissionBody, - WithdrawSubmissionBody, - OrderAmendmentBody, -} from '@vegaprotocol/vegawallet-service-api-client'; +import type { z } from 'zod'; +import type { GetKeysSchema, TransactionResponseSchema } from './connectors'; +import type { IterableElement } from 'type-fest'; + +interface BaseTransaction { + pubKey: string; + propagate: boolean; +} + +export interface DelegateSubmissionBody extends BaseTransaction { + delegateSubmission: { + nodeId: string; + amount: string; + }; +} + +export interface UndelegateSubmissionBody extends BaseTransaction { + undelegateSubmission: { + nodeId: string; + amount: string; + method: 'METHOD_NOW' | 'METHOD_AT_END_OF_EPOCH'; + }; +} + +export interface OrderSubmissionBody extends BaseTransaction { + orderSubmission: { + marketId: string; + reference?: string; + type: VegaWalletOrderType; + side: VegaWalletOrderSide; + timeInForce: VegaWalletOrderTimeInForce; + size: string; + price?: string; + expiresAt?: string; + }; +} + +export interface OrderCancellationBody extends BaseTransaction { + orderCancellation: { + orderId: string; + marketId: string; + }; +} + +export interface OrderAmendmentBody extends BaseTransaction { + orderAmendment: { + marketId: string; + orderId: string; + reference?: string; + timeInForce: VegaWalletOrderTimeInForce; + sizeDelta?: number; + // Note this is soon changing to price?: string + price?: { + value: string; + }; + // Note this is soon changing to expiresAt?: number + expiresAt?: { + value: string; + }; + }; +} + +export interface VoteSubmissionBody extends BaseTransaction { + voteSubmission: { + value: VegaWalletVoteValue; + proposalId: string; + }; +} + +export interface WithdrawSubmissionBody extends BaseTransaction { + withdrawSubmission: { + amount: string; + asset: string; + ext: { + erc20: { + receiverAddress: string; + }; + }; + }; +} + +export enum VegaWalletVoteValue { + Yes = 'VALUE_YES', + No = 'VALUE_NO', +} export enum VegaWalletOrderType { Market = 'TYPE_MARKET', @@ -36,3 +112,17 @@ export type TransactionSubmission = | DelegateSubmissionBody | UndelegateSubmissionBody | OrderAmendmentBody; + +export type TransactionResponse = z.infer; +export type GetKeysResponse = z.infer; +export type VegaKey = IterableElement; + +export type TransactionError = + | { + errors: { + '*': string[]; + }; + } + | { + error: string; + }; diff --git a/libs/web3/src/lib/use-ethereum-read-contract.ts b/libs/web3/src/lib/use-ethereum-read-contract.ts index 982fd35ee..dfe0ee2f4 100644 --- a/libs/web3/src/lib/use-ethereum-read-contract.ts +++ b/libs/web3/src/lib/use-ethereum-read-contract.ts @@ -51,9 +51,11 @@ export const useEthereumReadContract = ( const response = await result; if (cancelRequest.current) return; dispatch({ type: ActionType.FETCHED, payload: response }); + return response; } catch (error) { if (cancelRequest.current) return; dispatch({ type: ActionType.ERROR, error: error as Error }); + return; } }, [contractFunc]); diff --git a/libs/web3/src/lib/use-ethereum-transaction.tsx b/libs/web3/src/lib/use-ethereum-transaction.tsx index 9d48a4956..cbd043642 100644 --- a/libs/web3/src/lib/use-ethereum-transaction.tsx +++ b/libs/web3/src/lib/use-ethereum-transaction.tsx @@ -130,6 +130,7 @@ export const useEthereumTransaction = < error: new Error('Something went wrong'), }); } + return; } }, [ diff --git a/libs/web3/src/lib/use-token-contract.ts b/libs/web3/src/lib/use-token-contract.ts index 49cbae48f..1cefd7030 100644 --- a/libs/web3/src/lib/use-token-contract.ts +++ b/libs/web3/src/lib/use-token-contract.ts @@ -1,26 +1,25 @@ +import type { ERC20Asset } from '@vegaprotocol/react-helpers'; import { Token, TokenFaucetable } from '@vegaprotocol/smart-contracts'; import { useWeb3React } from '@web3-react/core'; import { useMemo } from 'react'; -export const useTokenContract = ( - contractAddress?: string, - faucetable = false -) => { +export const useTokenContract = (asset?: ERC20Asset, faucetable = false) => { const { provider } = useWeb3React(); const contract = useMemo(() => { - if (!provider || !contractAddress) { + if (!provider || !asset) { return null; } const signer = provider.getSigner(); + const address = asset.source.contractAddress; if (faucetable) { - return new TokenFaucetable(contractAddress, signer || provider); + return new TokenFaucetable(address, signer || provider); } else { - return new Token(contractAddress, signer || provider); + return new Token(address, signer || provider); } - }, [provider, contractAddress, faucetable]); + }, [provider, asset, faucetable]); return contract; }; diff --git a/libs/withdraws/src/lib/test-helpers.ts b/libs/withdraws/src/lib/test-helpers.ts index afd650b86..28168f00a 100644 --- a/libs/withdraws/src/lib/test-helpers.ts +++ b/libs/withdraws/src/lib/test-helpers.ts @@ -1,11 +1,13 @@ +import type { Asset } from '@vegaprotocol/react-helpers'; import { AccountType, WithdrawalStatus } from '@vegaprotocol/types'; import merge from 'lodash/merge'; import type { PartialDeep } from 'type-fest'; -import type { Asset, Account } from './types'; +import type { Account } from './types'; import type { Withdrawals_party_withdrawals } from './__generated__/Withdrawals'; export const generateAsset = (override?: PartialDeep) => { const defaultAsset: Asset = { + __typename: 'Asset', id: 'asset-id', symbol: 'asset-symbol', name: 'asset-name', diff --git a/libs/withdraws/src/lib/types.ts b/libs/withdraws/src/lib/types.ts index 27a95ab91..16b6cab16 100644 --- a/libs/withdraws/src/lib/types.ts +++ b/libs/withdraws/src/lib/types.ts @@ -1,24 +1,5 @@ import type { AccountType } from '@vegaprotocol/types'; -interface ERC20AssetSource { - __typename: 'ERC20'; - contractAddress: string; -} - -interface BuiltinAssetSource { - __typename: 'BuiltinAsset'; -} - -type AssetSource = ERC20AssetSource | BuiltinAssetSource; - -export interface Asset { - id: string; - symbol: string; - name: string; - decimals: number; - source: AssetSource; -} - export interface Account { type: AccountType; balance: string; diff --git a/libs/withdraws/src/lib/use-get-withdraw-limits.tsx b/libs/withdraws/src/lib/use-get-withdraw-limits.tsx index 12d6eec65..44556b04a 100644 --- a/libs/withdraws/src/lib/use-get-withdraw-limits.tsx +++ b/libs/withdraws/src/lib/use-get-withdraw-limits.tsx @@ -1,7 +1,7 @@ import { useCallback } from 'react'; -import type { Asset } from './types'; import { useBridgeContract, useEthereumReadContract } from '@vegaprotocol/web3'; import BigNumber from 'bignumber.js'; +import type { Asset } from '@vegaprotocol/react-helpers'; import { addDecimal } from '@vegaprotocol/react-helpers'; export const useGetWithdrawLimits = (asset?: Asset) => { diff --git a/libs/withdraws/src/lib/withdraw-dialog.tsx b/libs/withdraws/src/lib/withdraw-dialog.tsx index 4c4845732..85bdd9de8 100644 --- a/libs/withdraws/src/lib/withdraw-dialog.tsx +++ b/libs/withdraws/src/lib/withdraw-dialog.tsx @@ -82,11 +82,7 @@ const getProps = ( intent: Intent.Danger, children: ( - {vegaTx.error && ( -
-              {JSON.stringify(vegaTx.error, null, 2)}
-            
- )} +

{vegaTx.error}

), }, diff --git a/libs/withdraws/src/lib/withdraw-form.spec.tsx b/libs/withdraws/src/lib/withdraw-form.spec.tsx index 31cda4804..fe9883ae7 100644 --- a/libs/withdraws/src/lib/withdraw-form.spec.tsx +++ b/libs/withdraws/src/lib/withdraw-form.spec.tsx @@ -3,8 +3,8 @@ import BigNumber from 'bignumber.js'; import { useWeb3React } from '@web3-react/core'; import { WithdrawForm } from './withdraw-form'; import { generateAsset } from './test-helpers'; -import type { Asset } from './types'; import type { WithdrawFormProps } from './withdraw-form'; +import type { Asset } from '@vegaprotocol/react-helpers'; jest.mock('@web3-react/core'); diff --git a/libs/withdraws/src/lib/withdraw-form.tsx b/libs/withdraws/src/lib/withdraw-form.tsx index ae0d6db5d..49969649c 100644 --- a/libs/withdraws/src/lib/withdraw-form.tsx +++ b/libs/withdraws/src/lib/withdraw-form.tsx @@ -1,3 +1,4 @@ +import type { Asset } from '@vegaprotocol/react-helpers'; import { ethereumAddress, minSafe, @@ -5,6 +6,7 @@ import { removeDecimal, required, maxSafe, + isAssetTypeERC20, } from '@vegaprotocol/react-helpers'; import { Button, @@ -19,7 +21,6 @@ import BigNumber from 'bignumber.js'; import type { ButtonHTMLAttributes, ReactNode } from 'react'; import { useForm, Controller } from 'react-hook-form'; import type { WithdrawalFields } from './use-withdraw'; -import type { Asset } from './types'; import { WithdrawLimits } from './withdraw-limits'; interface FormFields { @@ -99,13 +100,11 @@ export const WithdrawForm = ({ id="asset" > - {assets - .filter((a) => a.source.__typename === 'ERC20') - .map((a) => ( - - ))} + {assets.filter(isAssetTypeERC20).map((a) => ( + + ))} )} /> diff --git a/libs/withdraws/src/lib/withdraw-manager.tsx b/libs/withdraws/src/lib/withdraw-manager.tsx index 056bbe09a..27fc4319d 100644 --- a/libs/withdraws/src/lib/withdraw-manager.tsx +++ b/libs/withdraws/src/lib/withdraw-manager.tsx @@ -5,10 +5,11 @@ import type { WithdrawalFields } from './use-withdraw'; import { useWithdraw } from './use-withdraw'; import { WithdrawDialog } from './withdraw-dialog'; import { isExpectedEthereumError, EthTxStatus } from '@vegaprotocol/web3'; +import type { Asset } from '@vegaprotocol/react-helpers'; import { addDecimal } from '@vegaprotocol/react-helpers'; import { AccountType } from '@vegaprotocol/types'; import BigNumber from 'bignumber.js'; -import type { Account, Asset } from './types'; +import type { Account } from './types'; import { useGetWithdrawLimits } from './use-get-withdraw-limits'; export interface WithdrawManagerProps { diff --git a/package.json b/package.json index 1fac35868..e89898982 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@sentry/react": "^6.19.2", "@sentry/tracing": "^6.19.2", "@testing-library/user-event": "^14.2.1", - "@vegaprotocol/vegawallet-service-api-client": "0.4.15", "@walletconnect/ethereum-provider": "^1.7.5", "@web3-react/core": "8.0.20-beta.0", "@web3-react/metamask": "8.0.16-beta.0", diff --git a/yarn.lock b/yarn.lock index f612475e3..0abe5e032 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6693,15 +6693,6 @@ "@typescript-eslint/types" "5.22.0" eslint-visitor-keys "^3.0.0" -"@vegaprotocol/vegawallet-service-api-client@0.4.15": - version "0.4.15" - resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.15.tgz#b303fec121b9b334a678161a6f66b360aeed5f0d" - integrity sha512-YwJkUgFvFqpA1xPYQ30ILGddgzjwD9lclsu1GvwK2AUX/8e3iUcXyr37wLd/t8mDZ7P3Zb2AsuLJP8uZ6E1GHQ== - dependencies: - es6-promise "^4.2.4" - url-parse "^1.4.3" - whatwg-fetch "^3.0.0" - "@walletconnect/browser-utils@^1.7.7": version "1.7.7" resolved "https://registry.yarnpkg.com/@walletconnect/browser-utils/-/browser-utils-1.7.7.tgz#4ae0db1ddf49be179ea556af842db3b7afce973d" @@ -11543,11 +11534,6 @@ es6-object-assign@^1.1.0: resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= -es6-promise@^4.2.4: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - es6-shim@^0.35.5: version "0.35.6" resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.6.tgz#d10578301a83af2de58b9eadb7c2c9945f7388a0" @@ -18378,11 +18364,6 @@ querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -21666,14 +21647,6 @@ url-loader@^4.1.1: mime-types "^2.1.27" schema-utils "^3.0.0" -url-parse@^1.4.3: - version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - url@^0.11.0, url@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -22293,11 +22266,6 @@ whatwg-encoding@^2.0.0: dependencies: iconv-lite "0.6.3" -whatwg-fetch@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" - integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== - whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"