diff --git a/packages/common/src/components/ActionConfirmation/index.tsx b/packages/common/src/components/ActionConfirmation/index.tsx new file mode 100644 index 0000000..0e8fd50 --- /dev/null +++ b/packages/common/src/components/ActionConfirmation/index.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Button } from 'antd'; +import { LABELS } from '../../constants'; +import { Link } from 'react-router-dom'; +import './style.css'; + +export const ActionConfirmation = (props: { + className?: string; + onClose: () => void; +}) => { + return ( +
+

Congratulations!

+
Your action has been successfully executed
+
+ + + + +
+ ); +}; diff --git a/packages/common/src/components/ActionConfirmation/style.less b/packages/common/src/components/ActionConfirmation/style.less new file mode 100644 index 0000000..64d27b2 --- /dev/null +++ b/packages/common/src/components/ActionConfirmation/style.less @@ -0,0 +1,5 @@ +.success-icon { + background-image: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjNzBjMDQxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDY0IDY0IiB4PSIwcHgiIHk9IjBweCI+PHRpdGxlPmJ1c2luZXNzIGZpbmFuY2UgdXAgcmlzZSBhcnJvdyBkZW1hbmQ8L3RpdGxlPjxwYXRoIGQ9Ik01LDE0YTEsMSwwLDEsMC0xLTFINEExLDEsMCwwLDAsNSwxNFoiPjwvcGF0aD48cGF0aCBkPSJNNyw1Mkg1NWExLDEsMCwwLDAsMC0ySDUwVjI3YTEsMSwwLDAsMC0yLDBWNTBINDRWMzRhMSwxLDAsMCwwLTIsMFY1MEgzOFYzOWExLDEsMCwwLDAtMiwwVjUwSDMyVjQzYTEsMSwwLDAsMC0yLDB2N0gyNlY0NmExLDEsMCwwLDAtMiwwdjRIMjBWNDdhMSwxLDAsMCwwLTIsMHYzSDE0VjQ4YTEsMSwwLDAsMC0yLDB2Mkg3YTEsMSwwLDAsMS0xLTFWMTdhMSwxLDAsMCwwLTIsMFY0OUEzLDMsMCwwLDAsNyw1MloiPjwvcGF0aD48cGF0aCBkPSJNNTksNTBhMSwxLDAsMCwwLTEsMWgwYTEsMSwwLDEsMCwxLTFaIj48L3BhdGg+PHBhdGggZD0iTTExLDQ0aC4wN2E0OS4wNyw0OS4wNywwLDAsMCwyNS41Mi05LjE5QTQ4LjkxLDQ4LjkxLDAsMCwwLDQ5LjcsMjAuNTlMNTIuMzgsMjIsNTIsMTRsLTYuNzEsNC4zMSwyLjYzLDEuMzZBNDYuODEsNDYuODEsMCwwLDEsMzUuNDEsMzMuMTksNDYuOTQsNDYuOTQsMCwwLDEsMTAuOTMsNDIsMSwxLDAsMCwwLDExLDQ0WiI+PC9wYXRoPjwvc3ZnPg=='); + width: 280px; + height: 280px; +} diff --git a/packages/common/src/components/AppBar/index.tsx b/packages/common/src/components/AppBar/index.tsx new file mode 100644 index 0000000..5330169 --- /dev/null +++ b/packages/common/src/components/AppBar/index.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Button, Popover } from 'antd'; +import { CurrentUserBadge } from '../CurrentUserBadge'; +import { SettingOutlined } from '@ant-design/icons'; +import { Settings } from '../Settings'; +import { LABELS } from '../../constants/labels'; +import { ConnectButton } from '..'; +import { useWallet } from '../../contexts/wallet'; +import './style.css'; +export const AppBar = (props: { left?: JSX.Element; right?: JSX.Element }) => { + const { connected, wallet } = useWallet(); + + const TopBar = ( +
+ {connected ? ( + + ) : ( + + )} + } + trigger="click" + > +
+ ); + + return TopBar; +}; diff --git a/packages/common/src/components/AppBar/style.less b/packages/common/src/components/AppBar/style.less new file mode 100644 index 0000000..377c1cc --- /dev/null +++ b/packages/common/src/components/AppBar/style.less @@ -0,0 +1,58 @@ +.App-Bar { + display: grid; + grid-template-columns: 1fr 120px; + -webkit-box-pack: justify; + justify-content: space-between; + -webkit-box-align: center; + align-items: center; + flex-direction: row; + width: 100%; + top: 0px; + position: relative; + padding: 1rem; + z-index: 2; + + .ant-menu-horizontal { + border-bottom-color: transparent; + background-color: transparent; + line-height: inherit; + font-size: 16px; + margin: 0 10px; + + .ant-menu-item { + margin: 0 10px; + color: lightgrey; + height: 35px; + line-height: 35px; + border-width: 0px !important; + } + + .ant-menu-item:hover { + color: white; + border-width: 0px !important; + } + + .ant-menu-item-selected { + font-weight: bold; + } + } +} + +.App-Bar-left { + box-sizing: border-box; + margin: 0px; + min-width: 0px; + display: flex; + padding: 0px; + -webkit-box-align: center; + align-items: center; + width: fit-content; +} + +.App-Bar-right { + display: flex; + flex-direction: row; + -webkit-box-align: center; + align-items: center; + justify-self: flex-end; +} diff --git a/packages/common/src/components/BackButton/index.tsx b/packages/common/src/components/BackButton/index.tsx new file mode 100644 index 0000000..82ce886 --- /dev/null +++ b/packages/common/src/components/BackButton/index.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Button } from 'antd'; +import { LABELS } from '../../constants'; +import { useHistory } from 'react-router-dom'; + +export const BackButton = () => { + const history = useHistory(); + return ( + + ); +}; diff --git a/packages/common/src/components/CurrentUserBadge/index.tsx b/packages/common/src/components/CurrentUserBadge/index.tsx new file mode 100644 index 0000000..41c9617 --- /dev/null +++ b/packages/common/src/components/CurrentUserBadge/index.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import { Identicon } from '../Identicon'; +import { LAMPORTS_PER_SOL } from '@solana/web3.js'; +import { useWallet } from '../../contexts/wallet'; +import { useNativeAccount } from '../../contexts/accounts'; +import { formatNumber, shortenAddress } from '../../utils'; + +export const CurrentUserBadge = (props: {}) => { + const { wallet } = useWallet(); + const { account } = useNativeAccount(); + + if (!wallet || !wallet.publicKey) { + return null; + } + + // should use SOL ◎ ? + + return ( +
+ + {formatNumber.format((account?.lamports || 0) / LAMPORTS_PER_SOL)} SOL + +
+ {shortenAddress(`${wallet.publicKey}`)} + +
+
+ ); +}; diff --git a/packages/common/src/components/Icons/info.tsx b/packages/common/src/components/Icons/info.tsx new file mode 100644 index 0000000..4bb0e3a --- /dev/null +++ b/packages/common/src/components/Icons/info.tsx @@ -0,0 +1,20 @@ +import { Button, Popover } from "antd"; +import React from "react"; + +import { InfoCircleOutlined } from "@ant-design/icons"; + +export const Info = (props: { + text: React.ReactElement; + style?: React.CSSProperties; +}) => { + return ( + {props.text}
} + > + + + ); +}; diff --git a/packages/common/src/components/Identicon/index.tsx b/packages/common/src/components/Identicon/index.tsx new file mode 100644 index 0000000..675b7b7 --- /dev/null +++ b/packages/common/src/components/Identicon/index.tsx @@ -0,0 +1,36 @@ +import React, { useEffect, useRef } from 'react'; + +import Jazzicon from 'jazzicon'; +import bs58 from 'bs58'; +import './style.css'; +import { PublicKey } from '@solana/web3.js'; + +export const Identicon = (props: { + address?: string | PublicKey; + style?: React.CSSProperties; + className?: string; +}) => { + const { style, className } = props; + const address = + typeof props.address === 'string' + ? props.address + : props.address?.toBase58(); + const ref = useRef(); + + useEffect(() => { + if (address && ref.current) { + ref.current.innerHTML = ''; + ref.current.className = className || ''; + ref.current.appendChild( + Jazzicon( + style?.width || 16, + parseInt(bs58.decode(address).toString('hex').slice(5, 15), 16), + ), + ); + } + }, [address, style, className]); + + return ( +
+ ); +}; diff --git a/packages/common/src/components/Identicon/style.less b/packages/common/src/components/Identicon/style.less new file mode 100644 index 0000000..4b04246 --- /dev/null +++ b/packages/common/src/components/Identicon/style.less @@ -0,0 +1,8 @@ +.identicon-wrapper { + display: flex; + height: 1rem; + width: 1rem; + border-radius: 1.125rem; + margin: 0.2rem 0.2rem 0.2rem 0.1rem; + /* background-color: ${({ theme }) => theme.bg4}; */ +} diff --git a/packages/common/src/components/Settings/index.tsx b/packages/common/src/components/Settings/index.tsx new file mode 100644 index 0000000..aed2649 --- /dev/null +++ b/packages/common/src/components/Settings/index.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Button, Select } from 'antd'; +import { useWallet } from '../../contexts/wallet'; +import { ENDPOINTS, useConnectionConfig } from '../../contexts/connection'; + +export const Settings = () => { + const { connected, disconnect } = useWallet(); + const { endpoint, setEndpoint } = useConnectionConfig(); + + return ( + <> +
+ Network:{' '} + + {connected && ( + + )} +
+ + ); +}; diff --git a/packages/common/src/components/index.tsx b/packages/common/src/components/index.tsx index 0038ed5..52c503f 100644 --- a/packages/common/src/components/index.tsx +++ b/packages/common/src/components/index.tsx @@ -1,2 +1,8 @@ export { ExplorerLink } from './ExplorerLink/index'; export { ConnectButton } from './ConnectButton/index'; +export { CurrentUserBadge } from './CurrentUserBadge/index'; +export { Identicon } from './Identicon/index'; +export { Info } from './Icons/info'; +export { NumericInput } from './Input/numeric'; +export { AppBar } from './AppBar/index'; +export { Settings } from './Settings/index'; diff --git a/packages/common/src/constants/index.ts b/packages/common/src/constants/index.ts index 3531606..7d6bf06 100644 --- a/packages/common/src/constants/index.ts +++ b/packages/common/src/constants/index.ts @@ -1 +1,2 @@ export * from './math'; +export * from './labels'; diff --git a/packages/common/src/constants/labels.ts b/packages/common/src/constants/labels.ts new file mode 100644 index 0000000..be5c639 --- /dev/null +++ b/packages/common/src/constants/labels.ts @@ -0,0 +1,15 @@ +export const LABELS = { + CONNECT_LABEL: 'Connect Wallet', + AUDIT_WARNING: + 'Oyster is an unaudited software project used for internal purposes at the Solana Foundation. This app is not for public use.', + FOOTER: + 'This page was produced by the Solana Foundation ("SF") for internal educational and inspiration purposes only. SF does not encourage, induce or sanction the deployment, integration or use of Oyster or any similar application (including its code) in violation of applicable laws or regulations and hereby prohibits any such deployment, integration or use. Anyone using this code or a derivation thereof must comply with applicable laws and regulations when releasing related software.', + MENU_HOME: 'Home', + MENU_DASHBOARD: 'Dashboard', + CONNECT_BUTTON: 'Connect', + WALLET_TOOLTIP: 'Wallet public key', + WALLET_BALANCE: 'Wallet balance', + SETTINGS_TOOLTIP: 'Settings', + DASHBOARD_ACTION: 'Go to dashboard', + GO_BACK_ACTION: 'Go back', +}; diff --git a/packages/common/src/index.tsx b/packages/common/src/index.tsx index c08601d..4c23c1d 100644 --- a/packages/common/src/index.tsx +++ b/packages/common/src/index.tsx @@ -1,5 +1,5 @@ export * as actions from './actions'; -export * from './components'; +export * as components from './components'; export * as config from './config'; export * as constants from './constants'; export * as hooks from './hooks'; diff --git a/packages/common/src/utils/ids.ts b/packages/common/src/utils/ids.ts index f3fe709..2d5b89e 100644 --- a/packages/common/src/utils/ids.ts +++ b/packages/common/src/utils/ids.ts @@ -18,6 +18,11 @@ let WORMHOLE_BRIDGE: { wrappedMaster: string; }; +let TIMELOCK: { + programId: PublicKey; + programAccountId: PublicKey; +}; + let SWAP_PROGRAM_ID: PublicKey; let SWAP_PROGRAM_LEGACY_IDS: PublicKey[]; let SWAP_PROGRAM_LAYOUT: any; @@ -34,6 +39,12 @@ export const ENABLE_FEES_INPUT = false; export const PROGRAM_IDS = [ { name: 'mainnet-beta', + timelock: () => ({ + programAccountId: new PublicKey( + '9gBhDCCKV7KELLFRY8sAJZXqDmvUfmNzFzpB2b4FUVVr', + ), + programId: new PublicKey('9iAeqqppjn7g1Jn8o2cQCqU5aQVV3h4q9bbWdKRbeC2w'), + }), wormhole: () => ({ pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'), bridge: '0xf92cD566Ea4864356C5491c177A430C222d7e678', @@ -52,6 +63,12 @@ export const PROGRAM_IDS = [ }, { name: 'testnet', + timelock: () => ({ + programAccountId: new PublicKey( + '9gBhDCCKV7KELLFRY8sAJZXqDmvUfmNzFzpB2b4FUVVr', + ), + programId: new PublicKey('9iAeqqppjn7g1Jn8o2cQCqU5aQVV3h4q9bbWdKRbeC2w'), + }), wormhole: () => ({ pubkey: new PublicKey('5gQf5AUhAgWYgUCt9ouShm9H7dzzXUsLdssYwe5krKhg'), bridge: '0x251bBCD91E84098509beaeAfF0B9951859af66D3', @@ -67,6 +84,12 @@ export const PROGRAM_IDS = [ }, { name: 'devnet', + timelock: () => ({ + programAccountId: new PublicKey( + '9gBhDCCKV7KELLFRY8sAJZXqDmvUfmNzFzpB2b4FUVVr', + ), + programId: new PublicKey('9iAeqqppjn7g1Jn8o2cQCqU5aQVV3h4q9bbWdKRbeC2w'), + }), wormhole: () => ({ pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'), bridge: '0xf92cD566Ea4864356C5491c177A430C222d7e678', @@ -82,6 +105,12 @@ export const PROGRAM_IDS = [ }, { name: 'localnet', + timelock: () => ({ + programAccountId: new PublicKey( + '9gBhDCCKV7KELLFRY8sAJZXqDmvUfmNzFzpB2b4FUVVr', + ), + programId: new PublicKey('9iAeqqppjn7g1Jn8o2cQCqU5aQVV3h4q9bbWdKRbeC2w'), + }), wormhole: () => ({ pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'), bridge: '0xf92cD566Ea4864356C5491c177A430C222d7e678', @@ -111,6 +140,8 @@ export const setProgramIds = (envName: string) => { SWAP_PROGRAM_LAYOUT = swap.current.layout; SWAP_PROGRAM_LEGACY_IDS = swap.legacy; + TIMELOCK = instance.timelock(); + if (envName === 'mainnet-beta') { LENDING_PROGRAM_ID = new PublicKey( 'LendZqTs7gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi', @@ -125,5 +156,6 @@ export const programIds = () => { swapLayout: SWAP_PROGRAM_LAYOUT, lending: LENDING_PROGRAM_ID, wormhole: WORMHOLE_BRIDGE, + timelock: TIMELOCK, }; };