feat: eager connection
This commit is contained in:
@@ -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],
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -80,6 +80,8 @@ export function createConfig(cfg: Config): Wallet {
|
||||
|
||||
if (!connector) return;
|
||||
|
||||
await connector.disconnectWallet();
|
||||
|
||||
store.setState({ status: 'disconnected', current: undefined, keys: [] });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user