Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a0727ec4b |
@@ -79,14 +79,14 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should have information on active nodes', function () {
|
||||
it('should have information on active nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.first()
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'active nodes');
|
||||
});
|
||||
|
||||
it.skip('should have information on consensus nodes', function () {
|
||||
it('should have information on consensus nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.last()
|
||||
.should('contain.text', '2')
|
||||
|
||||
@@ -26,9 +26,9 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
const { token, staking, vesting } = useContracts();
|
||||
const setAssociatedBalances = useRefreshAssociatedBalances();
|
||||
const [balancesLoaded, setBalancesLoaded] = React.useState(false);
|
||||
const vegaWalletStatus = useEagerConnect();
|
||||
const vegaConnecting = useEagerConnect();
|
||||
|
||||
const loaded = balancesLoaded && vegaWalletStatus !== 'connecting';
|
||||
const loaded = balancesLoaded && !vegaConnecting;
|
||||
|
||||
React.useEffect(() => {
|
||||
const run = async () => {
|
||||
@@ -169,5 +169,3 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
}
|
||||
return <Suspense fallback={loading}>{children}</Suspense>;
|
||||
};
|
||||
|
||||
AppLoader.displayName = 'AppLoader';
|
||||
|
||||
@@ -111,4 +111,3 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
</ContractsContext.Provider>
|
||||
);
|
||||
};
|
||||
ContractsProvider.displayName = 'ContractsProvider';
|
||||
|
||||
@@ -50,6 +50,8 @@ def truncate_middle(market_id, start=6, end=4):
|
||||
def change_keys(page: Page, vega: VegaServiceNull, key_name):
|
||||
page.get_by_test_id("manage-vega-wallet").click()
|
||||
page.get_by_test_id("key-" + vega.wallet.public_key(key_name)).click()
|
||||
page.click(
|
||||
f'data-testid=key-{vega.wallet.public_key(key_name)} >> .inline-flex')
|
||||
page.reload()
|
||||
|
||||
|
||||
|
||||
@@ -299,10 +299,10 @@ def test_leaderboard(competitions_page: Page, setup_teams_and_games):
|
||||
|
||||
|
||||
def test_game_card(competitions_page: Page):
|
||||
expect(competitions_page.get_by_test_id("active-rewards-card")).to_have_count(2)
|
||||
expect(competitions_page.get_by_test_id("active-rewards-card")).to_have_count(1)
|
||||
game_1 = competitions_page.get_by_test_id("active-rewards-card").first
|
||||
expect(game_1).to_be_visible()
|
||||
expect(game_1.get_by_test_id("entity-scope")).to_have_text("Individual")
|
||||
expect(game_1.get_by_test_id("entity-scope")).to_have_text("Team")
|
||||
expect(game_1.get_by_test_id("locked-for")).to_have_text("1 epoch")
|
||||
expect(game_1.get_by_test_id("reward-value")).to_have_text("100.00")
|
||||
expect(game_1.get_by_test_id("reward-asset")).to_have_text("VEGA")
|
||||
@@ -311,7 +311,7 @@ def test_game_card(competitions_page: Page):
|
||||
"Price maker fees paid • tDAI"
|
||||
)
|
||||
expect(game_1.get_by_test_id("assessed-over")).to_have_text("15 epochs")
|
||||
expect(game_1.get_by_test_id("scope")).to_have_text("In team")
|
||||
expect(game_1.get_by_test_id("scope")).to_have_text("All teams")
|
||||
expect(game_1.get_by_test_id("staking-requirement")).to_have_text("0.00")
|
||||
expect(game_1.get_by_test_id("average-position")).to_have_text("0.00")
|
||||
|
||||
|
||||
@@ -194,11 +194,11 @@ describe('isScopedToTeams', () => {
|
||||
undefined,
|
||||
makeDispatchStrategy(
|
||||
EntityScope.ENTITY_SCOPE_INDIVIDUALS,
|
||||
IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM // individual in teams
|
||||
IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM // individual in teams but not a team game
|
||||
),
|
||||
'RecurringTransfer'
|
||||
),
|
||||
true,
|
||||
false,
|
||||
],
|
||||
[
|
||||
makeReward(
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
TransferStatus,
|
||||
type DispatchStrategy,
|
||||
EntityScope,
|
||||
IndividualScope,
|
||||
MarketState,
|
||||
AccountType,
|
||||
} from '@vegaprotocol/types';
|
||||
@@ -75,20 +74,11 @@ export const isActiveReward = (node: RewardTransfer, currentEpoch: number) => {
|
||||
|
||||
/**
|
||||
* Checks if given reward (transfer) is scoped to teams.
|
||||
*
|
||||
* A reward is scoped to teams if it's entity scope is set to teams or
|
||||
* if the scope is set to individuals but the individuals are in a team.
|
||||
*/
|
||||
export const isScopedToTeams = (node: EnrichedRewardTransfer) =>
|
||||
// scoped to teams
|
||||
node.transfer.kind.dispatchStrategy?.entityScope ===
|
||||
EntityScope.ENTITY_SCOPE_TEAMS ||
|
||||
// or to individuals
|
||||
(node.transfer.kind.dispatchStrategy?.entityScope ===
|
||||
EntityScope.ENTITY_SCOPE_INDIVIDUALS &&
|
||||
// but they have to be in a team
|
||||
node.transfer.kind.dispatchStrategy?.individualScope ===
|
||||
IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM);
|
||||
EntityScope.ENTITY_SCOPE_TEAMS;
|
||||
|
||||
/** Retrieves rewards (transfers) */
|
||||
export const useRewards = ({
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"Get MetaMask": "Get MetaMask",
|
||||
"Get the Vega Wallet": "Get the Vega Wallet",
|
||||
"I agree": "I agree",
|
||||
"Once you have the added the extension, <0>refresh</0> you browser.": "Once you have the added the extension, <0>refresh</0> you browser.",
|
||||
"Successfully connected": "Successfully connected",
|
||||
"Transaction was not successful": "Transaction was not successful",
|
||||
"Wallet rejected transaction": "Wallet rejected transaction"
|
||||
|
||||
@@ -17,8 +17,8 @@ export const InputError = ({
|
||||
...props
|
||||
}: InputErrorProps) => {
|
||||
const effectiveClassName = classNames(
|
||||
'text-sm block items-center first-letter:capitalize',
|
||||
'mt-2 min-w-0 break-words',
|
||||
'text-sm flex items-center first-letter:uppercase',
|
||||
'mt-2',
|
||||
{
|
||||
'border-danger': intent === 'danger',
|
||||
'border-warning': intent === 'warning',
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
type ReactNode,
|
||||
type FunctionComponent,
|
||||
forwardRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { type ReactNode, type FunctionComponent, forwardRef } from 'react';
|
||||
import {
|
||||
ConnectorErrors,
|
||||
isBrowserWalletInstalled,
|
||||
@@ -17,7 +12,6 @@ import { useConnect } from '../../hooks/use-connect';
|
||||
import { Links } from '../../constants';
|
||||
import { ConnectorIcon } from './connector-icon';
|
||||
import { useUserAgent } from '@vegaprotocol/react-helpers';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
const vegaExtensionsLinks = {
|
||||
chrome: Links.chromeExtension,
|
||||
@@ -35,67 +29,48 @@ export const ConnectionOptions = ({
|
||||
onConnect: (id: ConnectorType) => void;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const { connectors } = useConnect();
|
||||
const error = useWallet((store) => store.error);
|
||||
const [isInstalling, setIsInstalling] = useState(false);
|
||||
const { connectors } = useConnect();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<h2 className="text-xl">{t('Connect to Vega')}</h2>
|
||||
{isInstalling ? (
|
||||
<p className="text-warning">
|
||||
<Trans
|
||||
i18nKey="Once you have the added the extension, <0>refresh</0> you browser."
|
||||
components={[
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="underline underline-offset-4"
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
<ul
|
||||
className="grid grid-cols-1 sm:grid-cols-2 gap-1 -mx-2"
|
||||
data-testid="connectors-list"
|
||||
>
|
||||
{connectors.map((c) => {
|
||||
const ConnectionOption = ConnectionOptionRecord[c.id];
|
||||
const props = {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
description: c.description,
|
||||
showDescription: false,
|
||||
onClick: () => onConnect(c.id),
|
||||
};
|
||||
|
||||
if (ConnectionOption) {
|
||||
return (
|
||||
<li key={c.id}>
|
||||
<ConnectionOption {...props} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={c.id}>
|
||||
<ConnectionOptionDefault {...props} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{error && error.code !== ConnectorErrors.userRejected.code && (
|
||||
<p
|
||||
className="text-danger text-sm first-letter:uppercase"
|
||||
data-testid="connection-error"
|
||||
>
|
||||
{error.message}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<ul
|
||||
className="grid grid-cols-1 sm:grid-cols-2 gap-1 -mx-2"
|
||||
data-testid="connectors-list"
|
||||
>
|
||||
{connectors.map((c) => {
|
||||
const ConnectionOption = ConnectionOptionRecord[c.id];
|
||||
const props = {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
description: c.description,
|
||||
showDescription: false,
|
||||
onClick: () => onConnect(c.id),
|
||||
onInstall: () => setIsInstalling(true),
|
||||
};
|
||||
|
||||
if (ConnectionOption) {
|
||||
return (
|
||||
<li key={c.id}>
|
||||
<ConnectionOption {...props} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={c.id}>
|
||||
<ConnectionOptionDefault {...props} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{error && error.code !== ConnectorErrors.userRejected.code && (
|
||||
<p
|
||||
className="text-danger text-sm first-letter:uppercase"
|
||||
data-testid="connection-error"
|
||||
>
|
||||
{error.message}
|
||||
{error.data ? `: ${error.data}` : ''}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<a
|
||||
href={Links.walletOverview}
|
||||
@@ -115,7 +90,6 @@ interface ConnectionOptionProps {
|
||||
description: string;
|
||||
showDescription?: boolean;
|
||||
onClick: () => void;
|
||||
onInstall?: () => void;
|
||||
}
|
||||
|
||||
const CONNECTION_OPTION_CLASSES =
|
||||
@@ -168,7 +142,6 @@ export const ConnectionOptionInjected = ({
|
||||
description,
|
||||
showDescription = false,
|
||||
onClick,
|
||||
onInstall,
|
||||
}: ConnectionOptionProps) => {
|
||||
const t = useT();
|
||||
const userAgent = useUserAgent();
|
||||
@@ -185,11 +158,7 @@ export const ConnectionOptionInjected = ({
|
||||
</span>
|
||||
</ConnectionOptionButtonWithDescription>
|
||||
) : (
|
||||
<ConnectionOptionLinkWithDescription
|
||||
id={id}
|
||||
href={link}
|
||||
onClick={onInstall}
|
||||
>
|
||||
<ConnectionOptionLinkWithDescription id={id} href={link}>
|
||||
<span className="flex flex-col justify-start text-left">
|
||||
<span className="capitalize leading-5">
|
||||
{t('Get the Vega Wallet')}
|
||||
@@ -214,7 +183,7 @@ export const ConnectionOptionInjected = ({
|
||||
{name}
|
||||
</ConnectionOptionButton>
|
||||
) : (
|
||||
<ConnectionOptionLink id={id} href={link} onClick={onInstall}>
|
||||
<ConnectionOptionLink id={id} href={link}>
|
||||
{t('Get the Vega Wallet')}
|
||||
</ConnectionOptionLink>
|
||||
)}
|
||||
@@ -306,9 +275,8 @@ const ConnectionOptionLink = forwardRef<
|
||||
children: ReactNode;
|
||||
id: ConnectorType;
|
||||
href: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
>(({ children, id, href, onClick }, ref) => {
|
||||
>(({ children, id, href }, ref) => {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
@@ -317,7 +285,6 @@ const ConnectionOptionLink = forwardRef<
|
||||
className={CONNECTION_OPTION_CLASSES}
|
||||
data-testid={`connector-${id}`}
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
>
|
||||
<ConnectorIcon id={id} />
|
||||
{children}
|
||||
@@ -353,10 +320,8 @@ const ConnectionOptionLinkWithDescription = forwardRef<
|
||||
children: ReactNode;
|
||||
id: ConnectorType;
|
||||
href: string;
|
||||
|
||||
onClick?: () => void;
|
||||
}
|
||||
>(({ children, id, href, onClick }, ref) => {
|
||||
>(({ children, id, href }, ref) => {
|
||||
return (
|
||||
<a
|
||||
ref={ref}
|
||||
@@ -364,7 +329,6 @@ const ConnectionOptionLinkWithDescription = forwardRef<
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={onClick}
|
||||
>
|
||||
<span>
|
||||
<ConnectorIcon id={id} />
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useWallet } from './use-wallet';
|
||||
import { useConnect } from './use-connect';
|
||||
|
||||
export function useEagerConnect() {
|
||||
const current = useWallet((store) => store.current);
|
||||
const status = useWallet((store) => store.status);
|
||||
const { connect } = useConnect();
|
||||
const [connecting, setConnecting] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const attemptConnect = async () => {
|
||||
// No stored config, or config was malformed or no risk accepted
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status !== 'disconnected') {
|
||||
setConnecting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -22,13 +19,15 @@ export function useEagerConnect() {
|
||||
await connect(current);
|
||||
} catch {
|
||||
console.warn(`Failed to connect with connector: ${current}`);
|
||||
} finally {
|
||||
setConnecting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
attemptConnect();
|
||||
}
|
||||
}, [status, connect, current]);
|
||||
}, [connect, current, connecting]);
|
||||
|
||||
return status;
|
||||
return connecting;
|
||||
}
|
||||
|
||||
@@ -65,12 +65,12 @@ export const useSimpleTransaction = (opts?: Options) => {
|
||||
if (err.code === ConnectorErrors.userRejected.code) {
|
||||
setStatus('idle');
|
||||
} else {
|
||||
setError(`${err.message}${err.data ? `: ${err.data}` : ''}`);
|
||||
setError(err.message);
|
||||
setStatus('idle');
|
||||
opts?.onError?.(err.message);
|
||||
}
|
||||
} else {
|
||||
const msg = t('Something went wrong');
|
||||
const msg = t('Wallet rejected transaction');
|
||||
setError(msg);
|
||||
setStatus('idle');
|
||||
opts?.onError?.(msg);
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
listKeysError,
|
||||
noWalletError,
|
||||
sendTransactionError,
|
||||
userRejectedError,
|
||||
} from '../errors';
|
||||
import {
|
||||
type TransactionParams,
|
||||
@@ -15,19 +14,6 @@ import {
|
||||
type VegaWalletEvent,
|
||||
} from '../types';
|
||||
|
||||
interface InjectedError {
|
||||
message: string;
|
||||
code: number;
|
||||
data:
|
||||
| {
|
||||
message: string;
|
||||
code: number;
|
||||
}
|
||||
| string;
|
||||
}
|
||||
|
||||
const USER_REJECTED_CODE = -4;
|
||||
|
||||
export class InjectedConnector implements Connector {
|
||||
readonly id = 'injected';
|
||||
readonly name = 'Vega Wallet';
|
||||
@@ -99,55 +85,15 @@ export class InjectedConnector implements Connector {
|
||||
sentAt: res.sentAt,
|
||||
};
|
||||
} catch (err) {
|
||||
if (this.isInjectedError(err)) {
|
||||
if (err.code === USER_REJECTED_CODE) {
|
||||
throw userRejectedError();
|
||||
}
|
||||
|
||||
if (typeof err.data === 'string') {
|
||||
throw sendTransactionError(err.data);
|
||||
} else {
|
||||
throw sendTransactionError(err.data.message);
|
||||
}
|
||||
}
|
||||
|
||||
throw sendTransactionError();
|
||||
}
|
||||
}
|
||||
|
||||
on(event: VegaWalletEvent, callback: () => void) {
|
||||
// Check for on/off in case user is on older versions which don't support it
|
||||
// We can remove this check once FF is at the latest version
|
||||
if (
|
||||
typeof window.vega !== 'undefined' &&
|
||||
typeof window.vega.on === 'function'
|
||||
) {
|
||||
window.vega.on(event, callback);
|
||||
}
|
||||
window.vega.on(event, callback);
|
||||
}
|
||||
|
||||
off(event: VegaWalletEvent, callback: () => void) {
|
||||
// Check for on/off in case user is on older versions which don't support it
|
||||
// We can remove this check once FF is at the latest version
|
||||
if (
|
||||
typeof window.vega !== 'undefined' &&
|
||||
typeof window.vega.off === 'function'
|
||||
) {
|
||||
window.vega.off(event, callback);
|
||||
}
|
||||
}
|
||||
|
||||
private isInjectedError(obj: unknown): obj is InjectedError {
|
||||
if (
|
||||
obj !== undefined &&
|
||||
obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
'code' in obj &&
|
||||
'message' in obj &&
|
||||
'data' in obj
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
window.vega.off(event, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ import {
|
||||
|
||||
type JsonRpcConnectorConfig = { url: string; token?: string };
|
||||
|
||||
const USER_REJECTED_CODE = 3001;
|
||||
|
||||
export class JsonRpcConnector implements Connector {
|
||||
readonly id = 'jsonRpc';
|
||||
readonly name = 'Command Line Wallet';
|
||||
@@ -29,7 +27,7 @@ export class JsonRpcConnector implements Connector {
|
||||
requestId: number = 0;
|
||||
store: StoreApi<Store> | undefined;
|
||||
pollRef: NodeJS.Timer | undefined;
|
||||
ee: InstanceType<typeof EventEmitter>;
|
||||
ee: EventEmitter;
|
||||
|
||||
constructor(config: JsonRpcConnectorConfig) {
|
||||
this.url = config.url;
|
||||
@@ -65,7 +63,7 @@ export class JsonRpcConnector implements Connector {
|
||||
const token = response.headers.get('Authorization');
|
||||
|
||||
if (!response.ok) {
|
||||
if ('error' in data && data.error.code === USER_REJECTED_CODE) {
|
||||
if ('error' in data && data.error.code === 3001) {
|
||||
throw userRejectedError();
|
||||
}
|
||||
throw connectError('response not ok');
|
||||
@@ -139,7 +137,7 @@ export class JsonRpcConnector implements Connector {
|
||||
|
||||
if (!response.ok) {
|
||||
if ('error' in data) {
|
||||
if (data.error.code === USER_REJECTED_CODE) {
|
||||
if (data.error.code === 3001) {
|
||||
throw userRejectedError();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import {
|
||||
ConnectorError,
|
||||
chainIdError,
|
||||
@@ -5,13 +6,13 @@ import {
|
||||
listKeysError,
|
||||
noWalletError,
|
||||
sendTransactionError,
|
||||
userRejectedError,
|
||||
} from '../errors';
|
||||
import { type Transaction } from '../transaction-types';
|
||||
import {
|
||||
JsonRpcMethod,
|
||||
type Connector,
|
||||
type TransactionParams,
|
||||
type VegaWalletEvent,
|
||||
} from '../types';
|
||||
|
||||
enum EthereumMethod {
|
||||
@@ -42,6 +43,7 @@ declare global {
|
||||
type WindowEthereumProvider = {
|
||||
isMetaMask: boolean;
|
||||
request<T = unknown>(args: RequestArguments): Promise<T>;
|
||||
selectedAddress: string | null;
|
||||
};
|
||||
|
||||
interface Window {
|
||||
@@ -50,16 +52,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
interface SnapRPCError {
|
||||
code: number;
|
||||
message: string;
|
||||
data?: {
|
||||
originalError: { code: number };
|
||||
};
|
||||
}
|
||||
|
||||
const USER_REJECTED_CODE = -4;
|
||||
|
||||
export class SnapConnector implements Connector {
|
||||
readonly id = 'snap';
|
||||
readonly name = 'MetaMask Snap';
|
||||
@@ -69,6 +61,8 @@ export class SnapConnector implements Connector {
|
||||
node: string;
|
||||
version: string;
|
||||
snapId: string;
|
||||
pollRef: NodeJS.Timer | undefined;
|
||||
ee: EventEmitter;
|
||||
|
||||
// Note: apps may not know which node is selected on start up so its up
|
||||
// to the app to make sure class intances are renewed if the node changes
|
||||
@@ -76,21 +70,14 @@ export class SnapConnector implements Connector {
|
||||
this.node = config.node;
|
||||
this.version = config.version;
|
||||
this.snapId = config.snapId;
|
||||
this.ee = new EventEmitter();
|
||||
}
|
||||
|
||||
bindStore() {}
|
||||
|
||||
async connectWallet(desiredChainId: string) {
|
||||
try {
|
||||
const res = await this.requestSnap();
|
||||
|
||||
if (res[this.snapId].blocked) {
|
||||
throw connectError('snap is blocked');
|
||||
}
|
||||
|
||||
if (!res[this.snapId].enabled) {
|
||||
throw connectError('snap is not enabled');
|
||||
}
|
||||
await this.requestSnap();
|
||||
|
||||
const { chainId } = await this.getChainId();
|
||||
|
||||
@@ -100,6 +87,7 @@ export class SnapConnector implements Connector {
|
||||
);
|
||||
}
|
||||
|
||||
this.startPoll();
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
if (err instanceof ConnectorError) {
|
||||
@@ -110,66 +98,57 @@ export class SnapConnector implements Connector {
|
||||
}
|
||||
}
|
||||
|
||||
async disconnectWallet() {}
|
||||
async disconnectWallet() {
|
||||
this.stopPoll();
|
||||
}
|
||||
|
||||
// deprecated, pass chain on connect
|
||||
async getChainId() {
|
||||
try {
|
||||
const data = await this.invokeSnap<{ chainID: string }>(
|
||||
const res = await this.invokeSnap<{ chainID: string }>(
|
||||
JsonRpcMethod.GetChainId,
|
||||
{
|
||||
networkEndpoints: [this.node],
|
||||
}
|
||||
);
|
||||
|
||||
if ('error' in data) {
|
||||
throw chainIdError(data.error.message);
|
||||
}
|
||||
|
||||
return { chainId: data.chainID };
|
||||
return { chainId: res.chainID };
|
||||
} catch (err) {
|
||||
if (err instanceof ConnectorError) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
this.stopPoll();
|
||||
throw chainIdError();
|
||||
}
|
||||
}
|
||||
|
||||
async listKeys() {
|
||||
try {
|
||||
const data = await this.invokeSnap<{
|
||||
const res = await this.invokeSnap<{
|
||||
keys: Array<{ publicKey: string; name: string }>;
|
||||
}>(JsonRpcMethod.ListKeys);
|
||||
|
||||
if ('error' in data) {
|
||||
throw listKeysError(data.error.message);
|
||||
}
|
||||
|
||||
return data.keys;
|
||||
return res.keys;
|
||||
} catch (err) {
|
||||
if (err instanceof ConnectorError) {
|
||||
throw err;
|
||||
}
|
||||
this.stopPoll();
|
||||
throw listKeysError();
|
||||
}
|
||||
}
|
||||
|
||||
async isConnected() {
|
||||
try {
|
||||
// Check if metamask is unlocked
|
||||
if (!window.ethereum.selectedAddress) {
|
||||
throw noWalletError();
|
||||
}
|
||||
|
||||
// If this throws its likely the snap is disabled or has been uninstalled
|
||||
await this.listKeys();
|
||||
return { connected: true };
|
||||
} catch (err) {
|
||||
this.stopPoll();
|
||||
return { connected: false };
|
||||
}
|
||||
}
|
||||
|
||||
async sendTransaction(params: TransactionParams) {
|
||||
try {
|
||||
// If the transaction is invalid this will throw with SnapRPCError
|
||||
// but if its rejected it will resolve with 'error' in data
|
||||
const data = await this.invokeSnap<{
|
||||
const res = await this.invokeSnap<{
|
||||
transactionHash: string;
|
||||
transaction: { signature: { value: string } };
|
||||
receivedAt: string;
|
||||
@@ -181,99 +160,115 @@ export class SnapConnector implements Connector {
|
||||
networkEndpoints: [this.node],
|
||||
});
|
||||
|
||||
if ('error' in data) {
|
||||
if (data.error.code === USER_REJECTED_CODE) {
|
||||
throw userRejectedError();
|
||||
}
|
||||
|
||||
throw sendTransactionError(`${data.error.message}: ${data.error.data}`);
|
||||
}
|
||||
|
||||
return {
|
||||
transactionHash: data.transactionHash,
|
||||
signature: data.transaction.signature.value,
|
||||
receivedAt: data.receivedAt,
|
||||
sentAt: data.sentAt,
|
||||
transactionHash: res.transactionHash,
|
||||
signature: res.transaction.signature.value,
|
||||
receivedAt: res.receivedAt,
|
||||
sentAt: res.sentAt,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof ConnectorError) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (this.isSnapRPCError(err)) {
|
||||
throw sendTransactionError(err.message);
|
||||
}
|
||||
|
||||
throw sendTransactionError();
|
||||
}
|
||||
}
|
||||
|
||||
on() {}
|
||||
|
||||
off() {}
|
||||
on(event: VegaWalletEvent, callback: () => void) {
|
||||
this.ee.on(event, callback);
|
||||
}
|
||||
|
||||
off(event: VegaWalletEvent, callback?: () => void) {
|
||||
this.ee.off(event, callback);
|
||||
}
|
||||
////////////////////////////////////
|
||||
// Snap methods
|
||||
////////////////////////////////////
|
||||
|
||||
private startPoll() {
|
||||
// This only event we need to poll for right now is client.disconnect,
|
||||
// if more events get added we will need more logic here
|
||||
this.pollRef = setInterval(async () => {
|
||||
const result = await this.isConnected();
|
||||
if (result.connected) return;
|
||||
this.ee.emit('client.disconnected');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
private stopPoll() {
|
||||
if (this.pollRef) {
|
||||
clearInterval(this.pollRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests permission for a website to communicate with the specified snaps
|
||||
* and attempts to install them if they're not already installed.
|
||||
* If the installation of any snap fails, returns the error that caused the failure.
|
||||
* More informations here: https://docs.metamask.io/snaps/reference/rpc-api/#wallet_requestsnaps
|
||||
*/
|
||||
private async requestSnap(): Promise<{
|
||||
[snapId: string]: {
|
||||
blocked: boolean;
|
||||
enabled: boolean;
|
||||
id: string;
|
||||
version: string;
|
||||
};
|
||||
}> {
|
||||
return window.ethereum.request({
|
||||
method: EthereumMethod.RequestSnaps,
|
||||
params: {
|
||||
[this.snapId]: {
|
||||
version: this.version,
|
||||
},
|
||||
private async requestSnap() {
|
||||
await this.request(EthereumMethod.RequestSnaps, {
|
||||
[this.snapId]: {
|
||||
version: this.version,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: check if this is needed, its used in use-snap-status
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Gets the list of all installed snaps.
|
||||
// * More information here: https://docs.metamask.io/snaps/reference/rpc-api/#wallet_getsnaps
|
||||
// */
|
||||
// async getSnap() {
|
||||
// const snaps = await this.request(EthereumMethod.GetSnaps);
|
||||
// return Object.values(snaps).find(
|
||||
// (s) => s.id === this.snapId && s.version === this.version
|
||||
// );
|
||||
// }
|
||||
|
||||
/**
|
||||
* Calls a method on the specified snap, always vega in this case
|
||||
* should always be npm:@vegaprotocol/snap
|
||||
*/
|
||||
private async invokeSnap<TResult>(
|
||||
method: JsonRpcMethod,
|
||||
params: SnapInvocationParams = {}
|
||||
): Promise<TResult | { error: SnapRPCError }> {
|
||||
// MetaMask in Firefox doesn't like undefined properties or some properties
|
||||
// on __proto__ so we need to strip them out with JSON.strinfify
|
||||
params = JSON.parse(JSON.stringify(params));
|
||||
|
||||
return window.ethereum.request({
|
||||
method: EthereumMethod.InvokeSnap,
|
||||
params: {
|
||||
snapId: this.snapId,
|
||||
request: {
|
||||
method,
|
||||
params,
|
||||
},
|
||||
params?: SnapInvocationParams
|
||||
): Promise<TResult> {
|
||||
return await this.request(EthereumMethod.InvokeSnap, {
|
||||
snapId: this.snapId,
|
||||
request: {
|
||||
method,
|
||||
params,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private isSnapRPCError(obj: unknown): obj is SnapRPCError {
|
||||
if (
|
||||
obj !== undefined &&
|
||||
obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
'code' in obj &&
|
||||
'message' in obj
|
||||
) {
|
||||
return true;
|
||||
/**
|
||||
* Calls window.ethereum.request with method and params
|
||||
*/
|
||||
private async request<TResult>(
|
||||
method: EthereumMethod,
|
||||
params?: object
|
||||
): Promise<TResult> {
|
||||
if (window.ethereum?.request && window.ethereum?.isMetaMask) {
|
||||
// MetaMask in Firefox doesn't like undefined properties or some properties
|
||||
// on __proto__ so we need to strip them out with JSON.strinfify
|
||||
try {
|
||||
params = JSON.parse(JSON.stringify(params));
|
||||
} catch (err) {
|
||||
throw sendTransactionError();
|
||||
}
|
||||
|
||||
return window.ethereum.request({
|
||||
method,
|
||||
params,
|
||||
});
|
||||
}
|
||||
return false;
|
||||
|
||||
throw noWalletError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export class ConnectorError extends Error {
|
||||
|
||||
export const ConnectorErrors = {
|
||||
userRejected: { message: 'user rejected', code: 0 },
|
||||
noConnector: { message: 'not connected', code: 1 },
|
||||
noConnector: { message: 'no connector', code: 1 },
|
||||
connect: { message: 'failed to connect', code: 2 },
|
||||
disconnect: { message: 'failed to disconnect', code: 3 },
|
||||
chainId: { message: 'incorrect chain id', code: 4 },
|
||||
|
||||
@@ -93,6 +93,7 @@ describe('disconnect', () => {
|
||||
expect(result).toEqual({ status: 'disconnected' });
|
||||
expect(config.store.getState()).toMatchObject({
|
||||
status: 'disconnected',
|
||||
error: noConnectorError(),
|
||||
current: undefined,
|
||||
keys: [],
|
||||
pubKey: undefined,
|
||||
@@ -129,7 +130,7 @@ describe('refresh keys', () => {
|
||||
it('handles invalid connector', async () => {
|
||||
await config.refreshKeys();
|
||||
expect(config.store.getState()).toMatchObject({
|
||||
error: undefined,
|
||||
error: noConnectorError(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -132,20 +132,18 @@ export function createConfig(cfg: Config): Wallet {
|
||||
store.setState(getInitialState(), true);
|
||||
return { status: 'disconnected' as const };
|
||||
} catch (err) {
|
||||
store.setState(getInitialState(), true);
|
||||
store.setState({
|
||||
...getInitialState(),
|
||||
error: err instanceof ConnectorError ? err : unknownError(),
|
||||
});
|
||||
return { status: 'disconnected' as const };
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshKeys() {
|
||||
const state = store.getState();
|
||||
const connector = connectors.getState().find((x) => x.id === state.current);
|
||||
|
||||
// Only refresh keys if connnected. If you aren't connect when you connect
|
||||
// you will get the latest keys
|
||||
if (state.status !== 'connected') {
|
||||
return;
|
||||
}
|
||||
const connector = connectors
|
||||
.getState()
|
||||
.find((x) => x.id === store.getState().current);
|
||||
|
||||
try {
|
||||
if (!connector) {
|
||||
|
||||
Reference in New Issue
Block a user