feat: view apps as any pub key (#2548)

This commit is contained in:
Dexter Edwards 2023-01-19 11:16:21 +00:00 committed by GitHub
parent c533c584da
commit f5b345636c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 392 additions and 30 deletions

View File

@ -17,6 +17,18 @@ import {
import { useContracts } from './contexts/contracts/contracts-context';
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
import { Connectors } from './lib/vega-connectors';
import { useSearchParams } from 'react-router-dom';
const useVegaWalletEagerConnect = () => {
const vegaConnecting = useEagerConnect(Connectors);
const { pubKey, connect } = useVegaWallet();
const [searchParams] = useSearchParams();
const [query] = React.useState(searchParams.get('address'));
if (query && !pubKey) {
connect(Connectors['view']);
}
return vegaConnecting;
};
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
const { t } = useTranslation();
@ -27,7 +39,7 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
const { token, staking, vesting } = useContracts();
const setAssociatedBalances = useRefreshAssociatedBalances();
const [balancesLoaded, setBalancesLoaded] = React.useState(false);
const vegaConnecting = useEagerConnect(Connectors);
const vegaConnecting = useVegaWalletEagerConnect();
const loaded = balancesLoaded && !vegaConnecting;

View File

@ -88,7 +88,7 @@ const Web3Container = ({
<AppLoader>
<BalanceManager>
<>
<div className="app w-full max-w-[1500px] mx-auto grid grid-rows-[min-content_1fr_min-content] min-h-full border-neutral-700 lg:border-l lg:border-r lg:text-body-large">
<div className="app w-full max-w-[1500px] mx-auto grid grid-rows-[min-content_min-content_1fr_min-content] min-h-full border-neutral-700 lg:border-l lg:border-r lg:text-body-large">
<TemplateSidebar sidebar={sideBar}>
<AppRouter />
</TemplateSidebar>

View File

@ -1,4 +1,6 @@
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import React from 'react';
import { Nav } from '../nav';
@ -10,9 +12,15 @@ export interface TemplateSidebarProps {
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
const { VEGA_ENV } = useEnvironment();
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
return (
<>
<Nav navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
{isReadOnly ? (
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
) : (
<div />
)}
<div className="w-full border-b border-neutral-700 lg:grid lg:grid-rows-[min-content_1fr] lg:grid-cols-[1fr_450px]">
<main className="col-start-1 p-4">{children}</main>
<aside className="col-start-2 row-start-1 row-span-2 hidden lg:block p-4 bg-banner bg-contain border-l border-neutral-700">

View File

@ -1,9 +1,17 @@
import { RestConnector, JsonRpcConnector } from '@vegaprotocol/wallet';
import {
RestConnector,
JsonRpcConnector,
ViewConnector,
} from '@vegaprotocol/wallet';
const urlParams = new URLSearchParams(window.location.search);
export const rest = new RestConnector();
export const jsonRpc = new JsonRpcConnector();
export const view = new ViewConnector(urlParams.get('address'));
export const Connectors = {
rest,
jsonRpc,
view,
};

View File

@ -55,6 +55,7 @@ describe('Vote buttons', () => {
const mockWalletNoPubKeyContext = {
pubKey: null,
pubKeys: [],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),

View File

@ -43,7 +43,6 @@ export const ProposeFreeform = () => {
NetworkParams.governance_proposal_freeform_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens,
]);
const { VEGA_DOCS_URL, VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation();
const {

View File

@ -85,7 +85,6 @@ export const ProposeNetworkParameter = () => {
loading: networkParamsLoading,
error: networkParamsError,
} = useNetworkParams();
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation();
const {

View File

@ -55,7 +55,6 @@ export const ProposeNewAsset = () => {
NetworkParams.governance_proposal_asset_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens,
]);
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation();
const {

View File

@ -53,7 +53,6 @@ export const ProposeNewMarket = () => {
NetworkParams.governance_proposal_market_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens,
]);
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation();
const {

View File

@ -42,7 +42,6 @@ export const ProposeRaw = () => {
NetworkParams.governance_proposal_freeform_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens,
]);
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation();
const {

View File

@ -53,7 +53,6 @@ export const ProposeUpdateAsset = () => {
NetworkParams.governance_proposal_updateAsset_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens,
]);
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation();
const {

View File

@ -94,7 +94,6 @@ export const ProposeUpdateMarket = () => {
const [selectedMarket, setSelectedMarket] = useState<string | undefined>(
undefined
);
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation();
const {

View File

@ -11,6 +11,7 @@ export const mockPubkey: PubKey = {
export const mockWalletContext = {
pubKey: mockPubkey.publicKey,
pubKeys: [mockPubkey],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),

View File

@ -0,0 +1,11 @@
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
export const ViewingBanner = () => {
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
return isReadOnly ? (
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
) : (
<div />
);
};

View File

@ -1,9 +1,22 @@
import { RestConnector, JsonRpcConnector } from '@vegaprotocol/wallet';
import {
RestConnector,
JsonRpcConnector,
ViewConnector,
} from '@vegaprotocol/wallet';
export const rest = new RestConnector();
export const jsonRpc = new JsonRpcConnector();
let view: ViewConnector;
if (typeof window !== 'undefined') {
const urlParams = new URLSearchParams(window.location.hash.split('?')[1]);
view = new ViewConnector(urlParams.get('address'));
} else {
view = new ViewConnector();
}
export const Connectors = {
rest,
jsonRpc,
view,
};

View File

@ -8,6 +8,7 @@ import {
VegaWalletProvider,
useVegaTransactionManager,
useVegaTransactionUpdater,
useVegaWallet,
} from '@vegaprotocol/wallet';
import {
useEagerConnect as useEthereumEagerConnect,
@ -29,8 +30,9 @@ import { Footer } from '../components/footer';
import { useEffect, useMemo, useState } from 'react';
import DialogsContainer from './dialogs-container';
import ToastsManager from './toasts-manager';
import { HashRouter, useLocation } from 'react-router-dom';
import { HashRouter, useLocation, useSearchParams } from 'react-router-dom';
import { Connectors } from '../lib/vega-connectors';
import { ViewingBanner } from '../components/viewing-banner';
const DEFAULT_TITLE = t('Welcome to Vega trading!');
@ -78,10 +80,11 @@ function AppBody({ Component }: AppProps) {
<VegaWalletProvider>
<AppLoader>
<Web3Provider>
<div className="h-full relative z-0 grid grid-rows-[min-content,1fr,min-content]">
<div className="h-full relative z-0 grid grid-rows-[min-content,min-content,1fr,min-content]">
<Navbar
navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'}
/>
<ViewingBanner />
<main data-testid={location.pathname}>
<Component />
</main>
@ -133,5 +136,12 @@ export default VegaTradingApp;
const MaybeConnectEagerly = () => {
useVegaEagerConnect(Connectors);
useEthereumEagerConnect();
const { pubKey, connect } = useVegaWallet();
const [searchParams] = useSearchParams();
const [query] = useState(searchParams.get('address'));
if (query && !pubKey) {
connect(Connectors['view']);
}
return null;
};

View File

@ -18,6 +18,7 @@ import {
isWithdrawTransaction,
useVegaTransactionStore,
VegaTxStatus,
WalletError,
} from '@vegaprotocol/wallet';
import { VegaTransaction } from '../components/vega-transaction';
import { VerificationStatus } from '@vegaprotocol/withdraws';
@ -284,13 +285,17 @@ export const ToastsManager = () => {
if (tx.status === VegaTxStatus.Error) {
toast = {
render: () => {
const errorMessage = `${tx.error?.message} ${
tx.error?.data ? `: ${tx.error?.data}` : ''
const error = `${tx.error?.message} ${
tx.error instanceof WalletError
? tx.error?.data
? `: ${tx.error?.data}`
: ''
: ''
}`;
return (
<div>
<h3 className="font-bold">{t('Error occurred')}</h3>
<p>{errorMessage}</p>
<p>{error}</p>
<VegaTransactionDetails tx={tx} />
</div>
);

View File

@ -54,7 +54,8 @@ const ErrorContent = ({ transaction, reset }: ErrorContentProps) => {
}
return (
<p data-testid={transaction.status}>
{error.message}: {error.data}
{error.message}{' '}
{error instanceof WalletError ? `: ${error.data}` : null}
</p>
);
}

View File

@ -12,6 +12,7 @@ import * as Schema from '@vegaprotocol/types';
const defaultWalletContext = {
pubKey: null,
pubKeys: [],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),

View File

@ -14,6 +14,7 @@ import * as Schema from '@vegaprotocol/types';
const defaultWalletContext = {
pubKey: null,
pubKeys: [],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),

View File

@ -40,6 +40,7 @@ const defaultMarket = {
const defaultWalletContext = {
pubKey: null,
pubKeys: [],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),

View File

@ -17,6 +17,7 @@ const pubKey = 'test-pubkey';
const defaultWalletContext = {
pubKey,
pubKeys: [{ publicKey: pubKey, name: 'test pubkey' }],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),

View File

@ -39,6 +39,7 @@ export * from './toggle';
export * from './tooltip';
export * from './vega-icons';
export * from './vega-logo';
export * from './viewing-as-user';
export * from './traffic-light';
export * from './toast';
export * from './notification';

View File

@ -0,0 +1,31 @@
import { Button } from '../button';
import React from 'react';
import { t } from '@vegaprotocol/react-helpers';
export function truncateMiddle(address: string) {
if (address.length < 11) return address;
return (
address.slice(0, 6) +
'\u2026' +
address.slice(address.length - 4, address.length)
);
}
export interface ViewingAsBannerProps {
pubKey: string | null;
disconnect: () => Promise<void>;
}
export const ViewingAsBanner = ({
pubKey,
disconnect,
}: ViewingAsBannerProps) => {
return (
<div className="w-full p-2 bg-neutral-800 flex justify-between text-neutral-400">
<div className="text-base flex items-center justify-center">
{t('Viewing as Vega user:')} {pubKey && truncateMiddle(pubKey)}
</div>
<Button onClick={disconnect}>{t('Exit view as')}</Button>
</div>
);
};

View File

@ -14,6 +14,7 @@ import {
ClientErrors,
JsonRpcConnector,
RestConnector,
ViewConnector,
WalletError,
} from '../connectors';
import { EnvironmentProvider } from '@vegaprotocol/environment';
@ -30,11 +31,15 @@ jest.mock('zustand', () => () => () => ({
let defaultProps: VegaConnectDialogProps;
const INITIAL_KEY = 'some-key';
const rest = new RestConnector();
const jsonRpc = new JsonRpcConnector();
const view = new ViewConnector(INITIAL_KEY);
const connectors = {
rest,
jsonRpc,
view,
};
beforeEach(() => {
jest.clearAllMocks();
@ -89,13 +94,16 @@ describe('VegaConnectDialog', () => {
rerender(generateJSX());
const list = await screen.findByTestId('connectors-list');
expect(list).toBeInTheDocument();
expect(list.children).toHaveLength(2);
expect(list.children).toHaveLength(3);
expect(screen.getByTestId('connector-jsonRpc')).toHaveTextContent(
'Connect Vega wallet'
);
expect(screen.getByTestId('connector-hosted')).toHaveTextContent(
'Hosted Fairground wallet'
);
expect(screen.getByTestId('connector-view')).toHaveTextContent(
'View as vega user'
);
});
describe('RestConnector', () => {
@ -346,4 +354,75 @@ describe('VegaConnectDialog', () => {
fireEvent.click(await screen.findByTestId('connector-jsonRpc'));
}
});
describe('ViewOnlyConnector', () => {
const fillInForm = (address = '0'.repeat(64)) => {
fireEvent.change(screen.getByTestId('address'), {
target: { value: address },
});
return { address };
};
it('connects', async () => {
const spy = jest.spyOn(connectors.view, 'connect');
render(generateJSX());
// Switches to view form
fireEvent.click(await screen.findByText('View as vega user'));
// Client side validation
fireEvent.submit(screen.getByTestId('view-connector-form'));
expect(spy).not.toHaveBeenCalled();
await waitFor(() => {
expect(screen.getAllByText('Required')).toHaveLength(1);
});
fillInForm();
// Wait for auth method to be called
await act(async () => {
fireEvent.submit(screen.getByTestId('view-connector-form'));
});
expect(spy).toHaveBeenCalled();
expect(mockCloseVegaDialog).toHaveBeenCalled();
});
it('ensures pubkey is of correct length', async () => {
render(generateJSX());
// Switches to view form
fireEvent.click(await screen.findByText('View as vega user'));
fillInForm('123');
// Wait for auth method to be called
await act(async () => {
fireEvent.submit(screen.getByTestId('view-connector-form'));
});
await waitFor(() => {
expect(
screen.getAllByText('Pubkey must be 64 characters in length')
).toHaveLength(1);
});
});
it('ensures pubkey is of valid hex', async () => {
render(generateJSX());
// Switches to view form
fireEvent.click(await screen.findByText('View as vega user'));
fillInForm('q'.repeat(64));
// Wait for auth method to be called
await act(async () => {
fireEvent.submit(screen.getByTestId('view-connector-form'));
});
await waitFor(() => {
expect(screen.getAllByText('Pubkey must be be valid hex')).toHaveLength(
1
);
});
});
});
});

View File

@ -11,6 +11,7 @@ import {
import { useCallback, useState } from 'react';
import { ExternalLinks, t, useChainIdQuery } from '@vegaprotocol/react-helpers';
import type { VegaConnector, WalletError } from '../connectors';
import { ViewConnector } from '../connectors';
import { JsonRpcConnector, RestConnector } from '../connectors';
import { RestConnectorForm } from './rest-connector-form';
import { JsonRpcConnectorForm } from './json-rpc-connector-form';
@ -22,10 +23,11 @@ import {
} from './connect-dialog-elements';
import type { Status } from '../use-json-rpc-connect';
import { useJsonRpcConnect } from '../use-json-rpc-connect';
import { ViewConnectorForm } from './view-connector-form';
export const CLOSE_DELAY = 1700;
type Connectors = { [key: string]: VegaConnector };
type WalletType = 'jsonRpc' | 'hosted';
type WalletType = 'jsonRpc' | 'hosted' | 'view';
export interface VegaConnectDialogProps {
connectors: Connectors;
@ -217,7 +219,7 @@ const ConnectorList = ({
/>
</li>
{!isMainnet && (
<li className="mb-0 border-t pt-4">
<li className="mb-4 last:mb-0">
<ConnectionOption
type="hosted"
text={t('Hosted Fairground wallet')}
@ -225,6 +227,13 @@ const ConnectorList = ({
/>
</li>
)}
<li className="mb-4 last:mb-0">
<ConnectionOption
type="view"
text={t('View as vega user')}
onClick={() => onSelect('view')}
/>
</li>
</ul>
</ConnectDialogContent>
<ConnectDialogFooter />
@ -301,6 +310,17 @@ const SelectedForm = ({
);
}
if (connector instanceof ViewConnector) {
return (
<>
<ConnectDialogContent>
<ViewConnectorForm connector={connector} onConnect={onConnect} />
</ConnectDialogContent>
<ConnectDialogFooter />
</>
);
}
throw new Error('No connector selected');
};
@ -318,7 +338,7 @@ const ConnectionOption = ({
onClick={onClick}
size="lg"
fill={true}
variant={type === 'hosted' ? 'default' : 'primary'}
variant={['hosted', 'view'].includes(type) ? 'default' : 'primary'}
data-testid={`connector-${type}`}
>
<span className="-mx-6 flex text-left justify-between items-center">

View File

@ -0,0 +1,64 @@
import { t } from '@vegaprotocol/react-helpers';
import { Button, FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
import { useForm } from 'react-hook-form';
import type { ViewConnector } from '../connectors';
import { useVegaWallet } from '../use-vega-wallet';
interface FormFields {
address: string;
}
interface RestConnectorFormProps {
connector: ViewConnector;
onConnect: (connector: ViewConnector) => void;
}
export function ViewConnectorForm({
connector,
onConnect,
}: RestConnectorFormProps) {
const { connect } = useVegaWallet();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormFields>();
const validatePubkey = (value: string) => {
const number = +`0x${value}`;
if (value.length !== 64) {
return t('Pubkey must be 64 characters in length');
} else if (Number.isNaN(number)) {
return t('Pubkey must be be valid hex');
}
return true;
};
async function onSubmit(fields: FormFields) {
await connector.setPubkey(fields.address);
await connect(connector);
onConnect(connector);
}
return (
<form onSubmit={handleSubmit(onSubmit)} data-testid="view-connector-form">
<FormGroup label={t('Vega Pubkey')} labelFor="address">
<Input
{...register('address', {
required: t('Required'),
validate: validatePubkey,
})}
id="address"
data-testid="address"
type="text"
/>
{errors.address?.message && (
<InputError intent="danger">{errors.address.message}</InputError>
)}
</FormGroup>
<Button variant="primary" type="submit" fill={true}>
{t('Connect')}
</Button>
</form>
);
}

View File

@ -2,3 +2,4 @@ export * from './vega-connector';
export * from './rest-connector';
export * from './injected-connector';
export * from './json-rpc-connector';
export * from './view-connector';

View File

@ -0,0 +1,52 @@
import type {
PubKey,
TransactionResponse,
VegaConnector,
} from './vega-connector';
import { clearConfig, getConfig, setConfig } from '../storage';
export class ViewConnector implements VegaConnector {
url: string | null;
pubkey: string | null | undefined = null;
/**
*
*/
constructor(pubkey?: string | null) {
this.url = 'view-only';
const cfg = getConfig();
if (pubkey || cfg?.token) {
this.pubkey = pubkey || cfg?.token;
}
}
setPubkey(pubkey: string) {
this.pubkey = pubkey;
}
connect(): Promise<PubKey[] | null> {
if (!this.pubkey) {
throw new Error('Cannot connect until address is set first');
}
setConfig({
token: this.pubkey,
connector: 'view',
url: this.url,
});
return Promise.resolve([
{
name: 'View only pubkey',
publicKey: this.pubkey,
},
]);
}
disconnect(): Promise<void> {
clearConfig();
this.pubkey = null;
return Promise.resolve();
}
sendTx(): Promise<TransactionResponse | null> {
throw new Error(
`You are connected in a view only state for public key: ${this.pubkey}. In order to send transactions you must connect to a real wallet.`
);
}
}

View File

@ -7,6 +7,8 @@ import type {
} from './connectors';
export interface VegaWalletContextShape {
/** If the current connector does not support signing transactions */
isReadOnly: boolean;
/** The current select public key */
pubKey: string | null;

View File

@ -1,5 +1,6 @@
import { act, renderHook } from '@testing-library/react';
import type { Transaction } from './connectors';
import { ViewConnector } from './connectors';
import { RestConnector } from './connectors';
import { useVegaWallet } from './use-vega-wallet';
import { VegaWalletProvider } from './provider';
@ -8,6 +9,7 @@ import type { ReactNode } from 'react';
import { WALLET_KEY } from './storage';
const restConnector = new RestConnector();
const viewConnector = new ViewConnector();
const setup = () => {
const wrapper = ({ children }: { children: ReactNode }) => (
@ -43,6 +45,7 @@ describe('VegaWalletProvider', () => {
expect(result.current).toEqual({
pubKey: null,
pubKeys: null,
isReadOnly: false,
selectPubKey: expect.any(Function),
connect: expect.any(Function),
disconnect: expect.any(Function),
@ -92,4 +95,17 @@ describe('VegaWalletProvider', () => {
expect(spyOnDisconnect).toHaveBeenCalled();
expect(localStorage.getItem(WALLET_KEY)).toBe(null);
});
it('sets isReadOnly to true if using view connector', async () => {
jest
.spyOn(viewConnector, 'connect')
.mockImplementation(() => Promise.resolve(mockPubKeys));
const { result } = setup();
expect(result.current.pubKey).toBe(null);
await act(async () => {
result.current.connect(viewConnector);
});
expect(result.current.isReadOnly).toBe(true);
});
});

View File

@ -10,6 +10,7 @@ import type {
import { VegaWalletContext } from './context';
import { WALLET_KEY } from './storage';
import { WalletError } from './connectors/vega-connector';
import { ViewConnector } from './connectors';
interface VegaWalletProviderProps {
children: ReactNode;
@ -18,6 +19,7 @@ interface VegaWalletProviderProps {
export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
// Current selected pubKey
const [pubKey, setPubKey] = useState<string | null>(null);
const [isReadOnly, setIsReadOnly] = useState<boolean>(false);
// Arary of pubkeys retrieved from the connector
const [pubKeys, setPubKeys] = useState<PubKey[] | null>(null);
@ -37,7 +39,7 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
if (keys?.length) {
setPubKeys(keys);
setIsReadOnly(connector.current instanceof ViewConnector);
const lastUsedPubKey = LocalStorage.getItem(WALLET_KEY);
const foundKey = keys.find((key) => key.publicKey === lastUsedPubKey);
if (foundKey) {
@ -65,6 +67,7 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
// again as expected
setPubKeys(null);
setPubKey(null);
setIsReadOnly(false);
LocalStorage.removeItem(WALLET_KEY);
try {
await connector.current?.disconnect();
@ -85,6 +88,7 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
const contextValue = useMemo<VegaWalletContextShape>(() => {
return {
isReadOnly,
pubKey,
pubKeys,
selectPubKey,
@ -92,7 +96,7 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
disconnect,
sendTx,
};
}, [pubKey, pubKeys, selectPubKey, connect, disconnect, sendTx]);
}, [isReadOnly, pubKey, pubKeys, selectPubKey, connect, disconnect, sendTx]);
return (
<VegaWalletContext.Provider value={contextValue}>

View File

@ -2,7 +2,7 @@ import { LocalStorage } from '@vegaprotocol/react-helpers';
interface ConnectorConfig {
token: string | null;
connector: 'rest' | 'jsonRpc';
connector: 'rest' | 'jsonRpc' | 'view';
url: string | null;
}

View File

@ -12,7 +12,6 @@ export function useEagerConnect(Connectors: {
useEffect(() => {
const attemptConnect = async () => {
const cfg = getConfig();
// No stored config, or config was malformed
if (!cfg || !cfg.connector) {
setConnecting(false);
@ -31,7 +30,6 @@ export function useEagerConnect(Connectors: {
);
return;
}
try {
await connect(Connectors[cfg.connector]);
} catch {

View File

@ -14,6 +14,7 @@ const mockPubKey = '0x123';
const defaultWalletContext = {
pubKey: null,
pubKeys: [],
isReadOnly: false,
sendTx: jest.fn(),
connect: jest.fn(),
disconnect: jest.fn(),
@ -52,7 +53,7 @@ describe('useVegaTransaction', () => {
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
});
it('handles a single error', () => {
it('handles a single wallet error', () => {
const error = new WalletError('test error', 1, 'test data');
const mockSendTx = jest.fn(() => {
throw error;
@ -70,6 +71,22 @@ describe('useVegaTransaction', () => {
expect(result.current.transaction.error).toHaveProperty('data', error.data);
});
it('handles a single error', () => {
const error = new Error('test error');
const mockSendTx = jest.fn(() => {
throw error;
});
const { result } = setup({ sendTx: mockSendTx });
act(() => {
result.current.send(mockPubKey, {} as Transaction);
});
expect(result.current.transaction.status).toEqual(VegaTxStatus.Error);
expect(result.current.transaction.error).toHaveProperty(
'message',
error.message
);
});
it('handles an unkwown error', () => {
const unknownThrow = { foo: 'bar' };
const mockSendTx = jest.fn(() => {

View File

@ -25,7 +25,7 @@ export enum VegaTxStatus {
export interface VegaTxState {
status: VegaTxStatus;
error: WalletError | null;
error: WalletError | Error | null;
txHash: string | null;
signature: string | null;
dialogOpen: boolean;
@ -89,8 +89,14 @@ export const useVegaTransaction = () => {
return null;
} catch (err) {
const error =
err instanceof WalletError
? err
: err instanceof Error
? err
: ClientErrors.UNKNOWN;
setTransaction({
error: err instanceof WalletError ? err : ClientErrors.UNKNOWN,
error: error,
status: VegaTxStatus.Error,
});
return null;

View File

@ -2,6 +2,7 @@ import { Networks, useEnvironment } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/react-helpers';
import { Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import { WalletError } from '../connectors';
import type { VegaTxState } from '../use-vega-transaction';
import { VegaTxStatus } from '../use-vega-transaction';
@ -108,11 +109,14 @@ export const VegaDialog = ({ transaction }: VegaDialogProps) => {
if (transaction.status === VegaTxStatus.Error) {
content = (
<div data-testid={transaction.status}>
{transaction.error && (
{transaction.error instanceof WalletError && (
<p>
{transaction.error.message}: {transaction.error.data}
</p>
)}
{transaction.error instanceof Error && (
<p>{transaction.error.message}</p>
)}
</div>
);
}