Add setting custom cluster endpoint
This commit is contained in:
parent
d332181ae8
commit
2b5e310cdc
|
@ -0,0 +1,62 @@
|
||||||
|
import React, {useState} from "react";
|
||||||
|
import {Col, Input, Modal, Row} from "antd";
|
||||||
|
import {EndpointInfo} from "../utils/types";
|
||||||
|
|
||||||
|
export default function CustomClusterEndpointDialog({
|
||||||
|
visible,
|
||||||
|
onAddCustomEndpoint,
|
||||||
|
onClose,
|
||||||
|
} : {
|
||||||
|
visible: boolean;
|
||||||
|
onAddCustomEndpoint: (info: EndpointInfo) => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
}) {
|
||||||
|
const [ customEndpoint, setCustomEndpoint] = useState('');
|
||||||
|
const [ customEndpointName, setCustomEndpointName] = useState('');
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
const params = {
|
||||||
|
name: customEndpointName,
|
||||||
|
endpoint: customEndpoint,
|
||||||
|
custom: true,
|
||||||
|
}
|
||||||
|
onAddCustomEndpoint(params);
|
||||||
|
onDoClose();
|
||||||
|
};
|
||||||
|
const onDoClose = () => {
|
||||||
|
setCustomEndpoint('')
|
||||||
|
setCustomEndpointName('')
|
||||||
|
onClose && onClose();
|
||||||
|
};
|
||||||
|
const canSubmit = customEndpoint !== '' && customEndpointName !== '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={'Add custom endpoint'}
|
||||||
|
visible={visible}
|
||||||
|
onOk={onSubmit}
|
||||||
|
okText={'Add'}
|
||||||
|
onCancel={onDoClose}
|
||||||
|
okButtonProps={{ disabled: !canSubmit }}
|
||||||
|
>
|
||||||
|
<Row style={{ marginBottom: 8 }}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Input
|
||||||
|
placeholder="Cluster Name"
|
||||||
|
value={customEndpointName}
|
||||||
|
onChange={(e) => setCustomEndpointName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{ marginBottom: 8 }}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Input
|
||||||
|
placeholder="Cluster Endpoint"
|
||||||
|
value={customEndpoint}
|
||||||
|
onChange={(e) => setCustomEndpoint(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { LinkOutlined } from '@ant-design/icons';
|
import { LinkOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export default function LinkAddress({ title, address }) {
|
export default function LinkAddress({ title, address }: {title?: undefined | any; address: string;}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{title && <p style={{ color: 'white' }}>{title}</p>}
|
{title && <p style={{ color: 'white' }}>{title}</p>}
|
|
@ -1,131 +0,0 @@
|
||||||
import {
|
|
||||||
InfoCircleOutlined,
|
|
||||||
SettingOutlined,
|
|
||||||
UserOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { Button, Menu, Popover, Select } from 'antd';
|
|
||||||
import React, { useCallback } from 'react';
|
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
|
||||||
import logo from '../assets/logo.svg';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { useWallet, WALLET_PROVIDERS } from '../utils/wallet';
|
|
||||||
import { ENDPOINTS, useConnectionConfig } from '../utils/connection';
|
|
||||||
import LinkAddress from './LinkAddress';
|
|
||||||
import Settings from './Settings';
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
background-color: #0d1017;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding: 0px 30px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
`;
|
|
||||||
const LogoWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #2abdd2;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
img {
|
|
||||||
height: 30px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function TopBar() {
|
|
||||||
const { connected, wallet, providerUrl, setProvider } = useWallet();
|
|
||||||
const { endpoint, setEndpoint } = useConnectionConfig();
|
|
||||||
const location = useLocation();
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const publicKey = wallet?.publicKey?.toBase58();
|
|
||||||
|
|
||||||
const handleClick = useCallback(
|
|
||||||
(e) => {
|
|
||||||
history.push(e.key);
|
|
||||||
},
|
|
||||||
[history],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<LogoWrapper>
|
|
||||||
<img src={logo} alt="" onClick={() => history.push('/')} />
|
|
||||||
{'SERUM'}
|
|
||||||
</LogoWrapper>
|
|
||||||
<Menu
|
|
||||||
mode="horizontal"
|
|
||||||
onClick={handleClick}
|
|
||||||
selectedKeys={[location.pathname]}
|
|
||||||
style={{
|
|
||||||
borderBottom: 'none',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'flex-end',
|
|
||||||
flex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Menu.Item key="/">TRADE</Menu.Item>
|
|
||||||
</Menu>
|
|
||||||
{connected && (
|
|
||||||
<div>
|
|
||||||
<Popover
|
|
||||||
content={<Settings autoApprove={wallet?.autoApprove} />}
|
|
||||||
placement="bottomRight"
|
|
||||||
title="Settings"
|
|
||||||
trigger="click"
|
|
||||||
>
|
|
||||||
<Button style={{ marginRight: 8 }}>
|
|
||||||
<SettingOutlined />
|
|
||||||
Settings
|
|
||||||
</Button>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
onSelect={setEndpoint}
|
|
||||||
value={endpoint}
|
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
>
|
|
||||||
{ENDPOINTS.map(({ name, endpoint }) => (
|
|
||||||
<Select.Option value={endpoint} key={endpoint}>
|
|
||||||
{name}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Select onSelect={setProvider} value={providerUrl}>
|
|
||||||
{WALLET_PROVIDERS.map(({ name, url }) => (
|
|
||||||
<Select.Option value={url} key={url}>
|
|
||||||
{name}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="large"
|
|
||||||
onClick={connected ? wallet.disconnect : wallet.connect}
|
|
||||||
style={{ color: '#2abdd2' }}
|
|
||||||
>
|
|
||||||
<UserOutlined />
|
|
||||||
{!connected ? 'Connect wallet' : 'Disconnect'}
|
|
||||||
</Button>
|
|
||||||
{connected && (
|
|
||||||
<Popover
|
|
||||||
content={<LinkAddress address={publicKey} />}
|
|
||||||
placement="bottomRight"
|
|
||||||
title="Wallet public key"
|
|
||||||
trigger="click"
|
|
||||||
>
|
|
||||||
<InfoCircleOutlined style={{ color: '#2abdd2' }} />
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
import {InfoCircleOutlined, PlusCircleOutlined, SettingOutlined, UserOutlined,} from '@ant-design/icons';
|
||||||
|
import {Button, Col, Menu, Popover, Row, Select} from 'antd';
|
||||||
|
import React, {useCallback, useState} from 'react';
|
||||||
|
import {useHistory, useLocation} from 'react-router-dom';
|
||||||
|
import logo from '../assets/logo.svg';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {useWallet, WALLET_PROVIDERS} from '../utils/wallet';
|
||||||
|
import {useConnectionConfig} from '../utils/connection';
|
||||||
|
import LinkAddress from './LinkAddress';
|
||||||
|
import Settings from './Settings';
|
||||||
|
import CustomClusterEndpointDialog from "./CustomClusterEndpointDialog";
|
||||||
|
import {EndpointInfo} from "../utils/types";
|
||||||
|
import {notify} from "../utils/notifications";
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
background-color: #0d1017;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 0px 30px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
`;
|
||||||
|
const LogoWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #2abdd2;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
img {
|
||||||
|
height: 30px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function TopBar() {
|
||||||
|
const { connected, wallet, providerUrl, setProvider } = useWallet();
|
||||||
|
const { endpoint, setEndpoint, availableEndpoints, setCustomEndpoints } = useConnectionConfig();
|
||||||
|
const [ addEndpointVisible, setAddEndpointVisible ] = useState(false)
|
||||||
|
const location = useLocation();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const publicKey = wallet?.publicKey?.toBase58();
|
||||||
|
|
||||||
|
const handleClick = useCallback(
|
||||||
|
(e) => {
|
||||||
|
history.push(e.key);
|
||||||
|
},
|
||||||
|
[history],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onAddCustomEndpoint = (info: EndpointInfo) => {
|
||||||
|
const existingEndpoint = availableEndpoints.some(
|
||||||
|
(e) => e.endpoint === info.endpoint,
|
||||||
|
);
|
||||||
|
if (existingEndpoint) {
|
||||||
|
notify({
|
||||||
|
message: `An endpoint with the given url already exists`,
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newCustomEndpoints = [...availableEndpoints.filter(e => e.custom), info];
|
||||||
|
setEndpoint(info.name);
|
||||||
|
setCustomEndpoints(newCustomEndpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CustomClusterEndpointDialog
|
||||||
|
visible={addEndpointVisible}
|
||||||
|
onAddCustomEndpoint={onAddCustomEndpoint}
|
||||||
|
onClose={() => setAddEndpointVisible(false)}
|
||||||
|
/>
|
||||||
|
<Wrapper>
|
||||||
|
<LogoWrapper>
|
||||||
|
<img src={logo} alt="" onClick={() => history.push('/')} />
|
||||||
|
{'SERUM'}
|
||||||
|
</LogoWrapper>
|
||||||
|
<Menu
|
||||||
|
mode="horizontal"
|
||||||
|
onClick={handleClick}
|
||||||
|
selectedKeys={[location.pathname]}
|
||||||
|
style={{
|
||||||
|
borderBottom: 'none',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Menu.Item key="/">TRADE</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
<div>
|
||||||
|
<Row
|
||||||
|
align="middle"
|
||||||
|
style={{ paddingLeft: 5, paddingRight: 5 }}
|
||||||
|
gutter={16}
|
||||||
|
>
|
||||||
|
<Col>
|
||||||
|
<PlusCircleOutlined
|
||||||
|
style={{ color: '#2abdd2' }}
|
||||||
|
onClick={() => setAddEndpointVisible(true)}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Popover
|
||||||
|
content={endpoint}
|
||||||
|
placement="bottomRight"
|
||||||
|
title="URL"
|
||||||
|
trigger="hover"
|
||||||
|
>
|
||||||
|
<InfoCircleOutlined style={{ color: '#2abdd2' }} />
|
||||||
|
</Popover>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Select
|
||||||
|
onSelect={setEndpoint}
|
||||||
|
value={endpoint}
|
||||||
|
style={{ marginRight: 8, width: '150px' }}
|
||||||
|
>
|
||||||
|
{availableEndpoints.map(({ name, endpoint }) => (
|
||||||
|
<Select.Option value={endpoint} key={endpoint}>
|
||||||
|
{name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
{connected && (
|
||||||
|
<div>
|
||||||
|
<Popover
|
||||||
|
content={<Settings autoApprove={wallet?.autoApprove} />}
|
||||||
|
placement="bottomRight"
|
||||||
|
title="Settings"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<Button style={{ marginRight: 8 }}>
|
||||||
|
<SettingOutlined />
|
||||||
|
Settings
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<Select onSelect={setProvider} value={providerUrl}>
|
||||||
|
{WALLET_PROVIDERS.map(({ name, url }) => (
|
||||||
|
<Select.Option value={url} key={url}>
|
||||||
|
{name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="large"
|
||||||
|
onClick={connected ? wallet.disconnect : wallet.connect}
|
||||||
|
style={{ color: '#2abdd2' }}
|
||||||
|
>
|
||||||
|
<UserOutlined />
|
||||||
|
{!connected ? 'Connect wallet' : 'Disconnect'}
|
||||||
|
</Button>
|
||||||
|
{connected && (
|
||||||
|
<Popover
|
||||||
|
content={<LinkAddress address={publicKey} />}
|
||||||
|
placement="bottomRight"
|
||||||
|
title="Wallet public key"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<InfoCircleOutlined style={{ color: '#2abdd2' }} />
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,18 +1,19 @@
|
||||||
import { useLocalStorageState } from './utils';
|
import {useLocalStorageState} from './utils';
|
||||||
import { Account, AccountInfo, clusterApiUrl, Connection, PublicKey } from '@solana/web3.js';
|
import {Account, AccountInfo, clusterApiUrl, Connection, PublicKey} from '@solana/web3.js';
|
||||||
import React, { useContext, useEffect, useMemo } from 'react';
|
import React, {useContext, useEffect, useMemo} from 'react';
|
||||||
import { setCache, useAsyncData } from './fetch-loop';
|
import {setCache, useAsyncData} from './fetch-loop';
|
||||||
import tuple from 'immutable-tuple';
|
import tuple from 'immutable-tuple';
|
||||||
import {ConnectionContextValues} from "./types";
|
import {ConnectionContextValues, EndpointInfo} from "./types";
|
||||||
|
|
||||||
export const ENDPOINTS: {name: string; endpoint: string;}[] = [
|
export const ENDPOINTS: EndpointInfo[] = [
|
||||||
{
|
{
|
||||||
name: 'mainnet-beta',
|
name: 'mainnet-beta',
|
||||||
endpoint: 'https://solana-api.projectserum.com',
|
endpoint: 'https://solana-api.projectserum.com',
|
||||||
|
custom: false
|
||||||
},
|
},
|
||||||
{ name: 'testnet', endpoint: clusterApiUrl('testnet') },
|
{ name: 'testnet', endpoint: clusterApiUrl('testnet'), custom: false },
|
||||||
{ name: 'devnet', endpoint: clusterApiUrl('devnet') },
|
{ name: 'devnet', endpoint: clusterApiUrl('devnet'), custom: false },
|
||||||
{ name: 'localnet', endpoint: 'http://127.0.0.1:8899' },
|
{ name: 'localnet', endpoint: 'http://127.0.0.1:8899', custom: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
const accountListenerCount = new Map();
|
const accountListenerCount = new Map();
|
||||||
|
@ -24,6 +25,11 @@ export function ConnectionProvider({ children }) {
|
||||||
'connectionEndpts',
|
'connectionEndpts',
|
||||||
ENDPOINTS[0].endpoint,
|
ENDPOINTS[0].endpoint,
|
||||||
);
|
);
|
||||||
|
const [customEndpoints, setCustomEndpoints] = useLocalStorageState<EndpointInfo[]>(
|
||||||
|
'customConnectionEndpoints',
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const availableEndpoints = ENDPOINTS.concat(customEndpoints);
|
||||||
|
|
||||||
const connection = useMemo(() => new Connection(endpoint, 'recent'), [
|
const connection = useMemo(() => new Connection(endpoint, 'recent'), [
|
||||||
endpoint,
|
endpoint,
|
||||||
|
@ -60,7 +66,7 @@ export function ConnectionProvider({ children }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConnectionContext.Provider
|
<ConnectionContext.Provider
|
||||||
value={{ endpoint, setEndpoint, connection, sendConnection }}
|
value={{ endpoint, setEndpoint, connection, sendConnection, availableEndpoints, setCustomEndpoints }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ConnectionContext.Provider>
|
</ConnectionContext.Provider>
|
||||||
|
@ -88,7 +94,12 @@ export function useConnectionConfig() {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error('Missing connection context')
|
throw new Error('Missing connection context')
|
||||||
}
|
}
|
||||||
return { endpoint: context.endpoint, setEndpoint: context.setEndpoint };
|
return {
|
||||||
|
endpoint: context.endpoint,
|
||||||
|
setEndpoint: context.setEndpoint,
|
||||||
|
availableEndpoints: context.availableEndpoints,
|
||||||
|
setCustomEndpoints: context.setCustomEndpoints,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAccountInfo(publicKey: PublicKey | undefined | null): [AccountInfo<Buffer> | null | undefined, boolean] {
|
export function useAccountInfo(publicKey: PublicKey | undefined | null): [AccountInfo<Buffer> | null | undefined, boolean] {
|
||||||
|
|
|
@ -9,6 +9,8 @@ export interface ConnectionContextValues {
|
||||||
setEndpoint: (newEndpoint: string) => void;
|
setEndpoint: (newEndpoint: string) => void;
|
||||||
connection: Connection;
|
connection: Connection;
|
||||||
sendConnection: Connection;
|
sendConnection: Connection;
|
||||||
|
availableEndpoints: EndpointInfo[];
|
||||||
|
setCustomEndpoints: (newCustomEndpoints: EndpointInfo[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WalletContextValues {
|
export interface WalletContextValues {
|
||||||
|
@ -106,3 +108,9 @@ export interface PreferencesContextValues {
|
||||||
autoSettleEnabled: boolean;
|
autoSettleEnabled: boolean;
|
||||||
setAutoSettleEnabled: (newAutoSettleEnabled: boolean) => void;
|
setAutoSettleEnabled: (newAutoSettleEnabled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EndpointInfo {
|
||||||
|
name: string;
|
||||||
|
endpoint: string;
|
||||||
|
custom: boolean;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue