feat: eager connection

This commit is contained in:
Matthew Russell
2024-02-29 16:20:14 +00:00
parent c7560eff7e
commit 30bc99a455
14 changed files with 213 additions and 126 deletions
+13 -5
View File
@@ -2,23 +2,31 @@ import {
InjectedConnector,
JsonRpcConnector,
SnapConnector,
ReadOnlyConnector,
createConfig,
fairground,
stagnet,
getConfig,
} from '@vegaprotocol/wallet';
export const injected = new InjectedConnector();
export const jsonRpc = new JsonRpcConnector({
const walletCfg = getConfig();
const injected = new InjectedConnector();
const jsonRpc = new JsonRpcConnector({
url: 'http://localhost:1789/api/v2/requests',
token: walletCfg?.token,
});
export const snap = new SnapConnector({
const snap = new SnapConnector({
node: 'https://api.n08.testnet.vega.rocks',
id: 'npm:@vegaprotocol/snap',
snapId: 'npm:@vegaprotocol/snap',
version: '0.3.1',
});
const readOnly = new ReadOnlyConnector(walletCfg?.pubKey);
export const config = createConfig({
chains: [fairground, stagnet],
defaultChainId: stagnet.id,
connectors: [injected, jsonRpc, snap],
connectors: [injected, jsonRpc, snap, readOnly],
});
+2 -20
View File
@@ -1,33 +1,15 @@
import { useSearchParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { useEnvironment } from '@vegaprotocol/environment';
import { useEagerConnect as useVegaEagerConnect } from '@vegaprotocol/wallet';
import { useEagerConnect as useEthereumEagerConnect } from '@vegaprotocol/web3';
// import {
// useEagerConnect as useVegaEagerConnect,
// useVegaWallet,
// } from '@vegaprotocol/wallet';
import { useGlobalStore } from '../stores';
// import { useConnectors } from '../lib/vega-connectors';
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
export const MaybeConnectEagerly = () => {
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
const update = useGlobalStore((store) => store.update);
// const connectors = useConnectors();
// const eagerConnecting = useVegaEagerConnect(connectors);
const [isTelemetryApproved] = useTelemetryApproval();
useEthereumEagerConnect(
isTelemetryApproved ? { dsn: SENTRY_DSN, env: VEGA_ENV } : {}
);
useVegaEagerConnect();
// const { pubKey, connect } = useVegaWallet();
const [searchParams] = useSearchParams();
const [query] = useState(searchParams.get('address'));
// if (query && !pubKey) {
// connect(connectors.view);
// }
// useEffect(() => {
// update({ eagerConnecting });
// }, [update, eagerConnecting]);
return null;
};
-2
View File
@@ -4,7 +4,6 @@ import produce from 'immer';
interface GlobalStore {
marketId: string | null;
eagerConnecting: boolean;
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
}
@@ -15,7 +14,6 @@ interface PageTitleStore {
export const useGlobalStore = create<GlobalStore>()((set) => ({
marketId: LocalStorage.getItem('marketId') || null,
eagerConnecting: false,
update: (newState) => {
set(
produce((state: GlobalStore) => {
+4
View File
@@ -0,0 +1,4 @@
export { InjectedConnector } from './injected-connector';
export { SnapConnector } from './snap-connector';
export { JsonRpcConnector } from './json-rpc-connector';
export { ReadOnlyConnector } from './read-only-connector';
@@ -1,11 +1,13 @@
import { clearConfig, setConfig } from '../storage';
import { type TransactionParams, type Connector } from '../types';
export class InjectedConnector implements Connector {
id = 'injected';
readonly id = 'injected';
async connectWallet(chainId: string) {
try {
await window.vega.connectWallet({ chainId });
setConfig({ type: this.id, chainId });
return { success: true };
} catch (err) {
return {
@@ -17,6 +19,7 @@ export class InjectedConnector implements Connector {
async disconnectWallet() {
try {
await window.vega.disconnectWallet();
clearConfig();
return { success: true };
} catch (err) {
return { error: 'failed to disconnect' };
@@ -1,19 +1,22 @@
import { clearConfig, setConfig } from '../storage';
import {
JsonRpcMethod,
type Connector,
type TransactionParams,
} from '../types';
type JsonRpcConnectorConfig = { url: string };
type JsonRpcConnectorConfig = { url: string; token?: string };
export class JsonRpcConnector implements Connector {
id = 'json-rpc';
config: JsonRpcConnectorConfig;
readonly id = 'jsonRpc';
url: string;
token: string | undefined;
requestId: number = 0;
token: string | null = null;
constructor(config: JsonRpcConnectorConfig) {
this.config = config;
this.url = config.url;
this.token = config.token;
}
async connectWallet(desiredChainId: string) {
@@ -24,28 +27,33 @@ export class JsonRpcConnector implements Connector {
throw new Error('incorrect chain id');
}
const { response, data } = await this.request(
JsonRpcMethod.ConnectWallet,
{
hostname: window.location.hostname,
}
);
if (!this.token) {
const { response, data } = await this.request(
JsonRpcMethod.ConnectWallet,
{
hostname: window.location.hostname,
}
);
const token = response.headers.get('Authorization');
const token = response.headers.get('Authorization');
if (!response.ok) {
if ('error' in data) {
return { error: data.error.data };
if (!response.ok) {
if ('error' in data) {
return { error: data.error.data };
}
return { error: 'failed to connect' };
}
return { error: 'failed to connect' };
if (!token) {
return { error: 'failed to connect' };
}
this.token = token;
}
if (!token) {
return { error: 'failed to connect' };
}
setConfig({ type: this.id, token: this.token, chainId, url: this.url });
this.token = token;
return { success: true };
} catch (err) {
return {
@@ -57,6 +65,7 @@ export class JsonRpcConnector implements Connector {
async disconnectWallet() {
try {
await this.request(JsonRpcMethod.DisconnectWallet);
clearConfig();
return { success: true };
} catch (err) {
return { error: 'failed to disconnect' };
@@ -116,7 +125,7 @@ export class JsonRpcConnector implements Connector {
headers.set('Authorization', this.token);
}
const response = await fetch(this.config.url, {
const response = await fetch(this.url, {
method: 'POST',
headers,
body: JSON.stringify({
@@ -126,7 +135,13 @@ export class JsonRpcConnector implements Connector {
params,
}),
});
const data = await response.json();
if (!response.ok) {
this.token = undefined;
}
return {
data,
response,
@@ -0,0 +1,75 @@
import { clearConfig, setConfig } from '../storage';
import { type Connector } from '../types';
export class ReadOnlyConnector implements Connector {
readonly id = 'readOnly';
pubKey: string | undefined;
constructor(pubKey?: string) {
if (pubKey) {
this.pubKey = pubKey;
}
}
async connectWallet() {
if (!this.pubKey) {
const pubKey = window.prompt('Enter pubkey');
if (!pubKey) {
return { error: 'failed to connect' };
}
this.pubKey = pubKey;
setConfig({ type: this.id, pubKey });
}
return { success: true };
}
async disconnectWallet() {
this.pubKey = undefined;
clearConfig();
return { success: true };
}
async getChainId() {
return {
error: `You are connected in a view only state for public key: ${this.pubKey}`,
};
}
async listKeys() {
if (!this.pubKey) {
return { error: 'failed to list keys' };
}
return [
{
name: 'View only',
publicKey: this.pubKey,
},
];
}
async isConnected() {
if (this.pubKey) {
return { connected: true };
}
return { connected: false };
}
// @ts-ignore deliberate fail
async sendTransaction() {
return {
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.`,
};
}
on() {
console.warn('events are not supported using a read only connection');
}
off() {
console.warn('events are not supported using a read only connection');
}
}
+27 -20
View File
@@ -1,3 +1,4 @@
import { clearConfig, setConfig } from '../storage';
import {
JsonRpcMethod,
type Connector,
@@ -11,18 +12,22 @@ enum EthereumMethod {
}
type SnapConfig = {
id: string;
node: string;
version: string;
snapId: string;
};
export class SnapConnector implements Connector {
id = 'snap';
readonly id = 'snap';
config: SnapConfig;
node: string;
version: string;
snapId: string;
constructor(config: SnapConfig) {
this.config = config;
this.node = config.node;
this.version = config.version;
this.snapId = config.snapId;
}
async connectWallet(desiredChainId: string) {
@@ -35,6 +40,7 @@ export class SnapConnector implements Connector {
throw new Error('incorrect chain id');
}
setConfig({ type: this.id, chainId });
return { success: true };
} catch (err) {
return {
@@ -44,15 +50,16 @@ export class SnapConnector implements Connector {
}
async disconnectWallet() {
console.warn('disconnectWallet not implemented');
return { error: 'failed to disconnect' };
clearConfig();
return { success: true };
// return { error: 'failed to disconnect' };
}
// deprecated, pass chain on connect
async getChainId() {
try {
const res = await this.invokeSnap(JsonRpcMethod.GetChainId, {
networkEndpoints: [this.config.node],
networkEndpoints: [this.node],
});
return { chainId: res.chainID };
} catch (err) {
@@ -80,7 +87,7 @@ export class SnapConnector implements Connector {
publicKey: params.publicKey,
sendingMode: params.sendingMode,
transaction: params.transaction,
networkEndpoints: [this.config.node],
networkEndpoints: [this.node],
});
return {
@@ -95,14 +102,22 @@ export class SnapConnector implements Connector {
}
}
on() {
console.warn('events are not supported in json rpc wallet');
}
off() {
console.warn('events are not supported in json rpc wallet');
}
////////////////////////////////////
// Snap methods
////////////////////////////////////
async requestSnap() {
const res = await this.request(EthereumMethod.RequestSnaps, {
[this.config.id]: {
version: this.config.version,
[this.snapId]: {
version: this.version,
},
});
console.log(res);
@@ -111,13 +126,13 @@ export class SnapConnector implements Connector {
async getSnap() {
const snaps = await this.request(EthereumMethod.GetSnaps);
return Object.values(snaps).find(
(s) => s.id === this.config.id && s.version === this.config.version
(s) => s.id === this.snapId && s.version === this.version
);
}
async invokeSnap(method: JsonRpcMethod, params?: any) {
return await this.request(EthereumMethod.InvokeSnap, {
snapId: this.config.id,
snapId: this.snapId,
request: {
method: method,
params,
@@ -136,12 +151,4 @@ export class SnapConnector implements Connector {
params,
});
}
on() {
console.warn('events are not supported in json rpc wallet');
}
off() {
console.warn('events are not supported in json rpc wallet');
}
}
+8 -3
View File
@@ -1,6 +1,9 @@
export { InjectedConnector } from './connectors-v2/injected-connector';
export { SnapConnector } from './connectors-v2/snap-connector';
export { JsonRpcConnector } from './connectors-v2/json-rpc-connector';
export {
InjectedConnector,
SnapConnector,
JsonRpcConnector,
ReadOnlyConnector,
} from './connectors-v2';
export * from './types';
export { createConfig, VegaWalletProvider } from './wallet';
@@ -19,5 +22,7 @@ export { ConnectDialog } from './connect-dialog-v2';
export { useVegaWalletDialogStore } from './connect-dialog/vega-wallet-dialog-store';
export { useVegaWallet } from './use-vega-wallet';
export { isBrowserWalletInstalled } from './utils';
export { useEagerConnect } from './use-eager-connect';
export { getConfig } from './storage';
export { fairground, stagnet, Chain } from './chains';
+5 -3
View File
@@ -1,9 +1,11 @@
import { LocalStorage } from '@vegaprotocol/utils';
interface ConnectorConfig {
token: string | null;
connector: 'injected' | 'jsonRpc' | 'view' | 'snap';
url: string | null;
type: 'injected' | 'jsonRpc' | 'readOnly' | 'snap';
url?: string;
chainId?: string;
token?: string;
pubKey?: string;
}
export const WALLET_CONFIG = 'vega_wallet_config';
+2 -2
View File
@@ -22,9 +22,9 @@ export interface TransactionParams {
}
export interface Connector {
id: string;
readonly id: string;
connectWallet(chainId: string): Promise<{ success: boolean } | IWalletError>;
connectWallet(chainId?: string): Promise<{ success: boolean } | IWalletError>;
disconnectWallet(): Promise<{ success: boolean } | IWalletError>;
+9 -30
View File
@@ -1,48 +1,27 @@
import { useEffect, useState } from 'react';
import { InjectedConnector } from './connectors/injected-connector';
import { SnapConnector } from './connectors/snap-connector';
import { getConfig } from './storage';
import type { Connectors } from './connectors';
import { useConnect } from './wallet';
import { useVegaWallet } from './use-vega-wallet';
export function useEagerConnect(connectors: Connectors) {
export function useEagerConnect() {
const { connect, connectors } = useConnect();
const { onConnect } = useVegaWallet();
const [connecting, setConnecting] = useState(true);
const { vegaUrl, chainId, connect, acknowledgeNeeded } = useVegaWallet();
useEffect(() => {
const attemptConnect = async () => {
const cfg = getConfig();
// No stored config, or config was malformed or no risk accepted
if (!cfg || !cfg.connector || acknowledgeNeeded) {
if (!cfg || !cfg.type) {
setConnecting(false);
return;
}
// Use the connector string in local storage to find the right connector to auto
// connect to
const connector = connectors[cfg.connector];
// Developer hasn't provided this connector
if (!connector) {
setConnecting(false);
console.error(
`Can't eager connect, connector: ${cfg.connector} not found`
);
return;
}
try {
if (connector instanceof InjectedConnector) {
await connector.connectWallet(chainId);
await connect(connector);
} else if (connector instanceof SnapConnector) {
connector.nodeAddress = new URL(vegaUrl).origin;
await connect(connector);
} else {
await connect(connector);
}
await connect(cfg.type);
onConnect();
} catch {
console.warn(`Failed to connect with connector: ${cfg.connector}`);
console.warn(`Failed to connect with connector: ${cfg.type}`);
} finally {
setConnecting(false);
}
@@ -51,7 +30,7 @@ export function useEagerConnect(connectors: Connectors) {
if (typeof window !== 'undefined') {
attemptConnect();
}
}, [connect, connectors, acknowledgeNeeded, vegaUrl, chainId]);
}, [connect, connectors, onConnect]);
return connecting;
}
+26 -19
View File
@@ -1,6 +1,7 @@
import { create } from 'zustand';
import { useConfig } from './wallet';
import { useConfig, useWallet } from './wallet';
import { useVegaWalletDialogStore } from './connect-dialog';
import { useCallback } from 'react';
interface PubKeyStore {
pubKey: string | null;
@@ -14,26 +15,32 @@ export const useVegaWalletStore = create<PubKeyStore>()((set) => ({
// Only for vega apps that expect a single selected key
export const useVegaWallet = () => {
const store = useVegaWalletStore();
const dialog = useVegaWalletDialogStore();
const config = useConfig();
const pubKey = useVegaWalletStore((store) => store.pubKey);
const setPubKey = useVegaWalletStore((store) => store.setPubKey);
const updateDialog = useVegaWalletDialogStore(
(store) => store.updateVegaWalletDialog
);
const pubKeys = useWallet((store) => store.keys);
const onConnect = useCallback(() => {
const state = config.store.getState();
if (state.keys.length) {
setPubKey(state.keys[0].publicKey);
}
if (state.status === 'connected') {
setTimeout(() => {
updateDialog(false);
}, 1000);
}
}, [config.store, setPubKey, updateDialog]);
return {
pubKey: store.pubKey,
selectPubKey: store.setPubKey,
onConnect: () => {
const state = config.store.getState();
if (state.keys.length) {
store.setPubKey(state.keys[0].publicKey);
}
if (state.status === 'connected') {
setTimeout(() => {
dialog.updateVegaWalletDialog(false);
}, 1000);
}
},
pubKeys,
pubKey,
selectPubKey: setPubKey,
onConnect,
};
};
+2
View File
@@ -80,6 +80,8 @@ export function createConfig(cfg: Config): Wallet {
if (!connector) return;
await connector.disconnectWallet();
store.setState({ status: 'disconnected', current: undefined, keys: [] });
}