Skip to content
This repository has been archived by the owner on Dec 6, 2024. It is now read-only.

Commit

Permalink
Custom network setup (#173)
Browse files Browse the repository at this point in the history
* Custom network setup

* Add Custom Network Integration

* feat: add ability to select custom network

* fix: change placeholder

---------

Co-authored-by: Bohdan Ohorodnii <[email protected]>
  • Loading branch information
satyambnsal and varex83 authored Sep 11, 2024
1 parent 7869bd2 commit 6b94ffa
Show file tree
Hide file tree
Showing 15 changed files with 461 additions and 81 deletions.
12 changes: 11 additions & 1 deletion plugin/src/atoms/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,15 @@ const isDevnetAliveAtom = atom<boolean>(true)
const selectedDevnetAccountAtom = atom<null | DevnetAccount>(null)

const availableDevnetAccountsAtom = atom<DevnetAccount[]>([])
const customNetworkAtom = atom('')
const isCustomNetworkAliveAtom = atom<boolean>(false)

export { devnetAtom, envAtom, isDevnetAliveAtom, selectedDevnetAccountAtom, availableDevnetAccountsAtom }
export {
devnetAtom,
envAtom,
isDevnetAliveAtom,
selectedDevnetAccountAtom,
availableDevnetAccountsAtom,
customNetworkAtom,
isCustomNetworkAliveAtom
}
4 changes: 3 additions & 1 deletion plugin/src/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export {
devnetAtom,
envAtom,
isDevnetAliveAtom,
selectedDevnetAccountAtom
selectedDevnetAccountAtom,
customNetworkAtom,
isCustomNetworkAliveAtom
} from './environment'
export { accountsAtom, networkNameAtom, selectedAccountAtom } from './manualAccount'
export { transactionsAtom } from './transaction'
Expand Down
174 changes: 174 additions & 0 deletions plugin/src/components/CustomNetwork/customNetwork.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
.custom-network-container {
display: flex;
flex-direction: column;
}

.input-button-wrapper {
display: flex;
align-items: stretch;
}

.custom-input {
flex-grow: 1;
padding: 5px 8px;
border: 1px solid var(--secondary);
border-right: none;
border-radius: 6px 0 0 6px;
font-size: 1rem;
background: transparent;
color: var(--text);
-webkit-border-radius: 6px 0 0 6px;
-moz-border-radius: 6px 0 0 6px;
-ms-border-radius: 6px 0 0 6px;
-o-border-radius: 6px 0 0 6px;
}

.custom-input:disabled {
background-color: var(--body-bg);
opacity: 0.7;
cursor: not-allowed;
}

.custom-input:disabled:hover {
cursor: not-allowed;
}

.btn-connect {
all: unset;
cursor: pointer;
padding: 0 0.5rem;
border: 1px solid var(--secondary);
border-radius: 0 0.25rem 0.25rem 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 2.5rem;
font-size: 1rem;
-webkit-border-radius: 0 0.25rem 0.25rem 0;
-moz-border-radius: 0 0.25rem 0.25rem 0;
-ms-border-radius: 0 0.25rem 0.25rem 0;
-o-border-radius: 0 0.25rem 0.25rem 0;
transition: all 0.3s ease;
}

.btn-connect:hover {
animation: quick-zoom 0.3s ease;
}

@keyframes quick-zoom {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}

/* For better browser compatibility, include vendor prefixes */
@-webkit-keyframes quick-zoom {
0% {
-webkit-transform: scale(1);
}
50% {
-webkit-transform: scale(1.05);
}
100% {
-webkit-transform: scale(1);
}
}

@-moz-keyframes quick-zoom {
0% {
-moz-transform: scale(1);
}
50% {
-moz-transform: scale(1.05);
}
100% {
-moz-transform: scale(1);
}
}

@-ms-keyframes quick-zoom {
0% {
-ms-transform: scale(1);
}
50% {
-ms-transform: scale(1.05);
}
100% {
-ms-transform: scale(1);
}
}

@-o-keyframes quick-zoom {
0% {
-o-transform: scale(1);
}
50% {
-o-transform: scale(1.05);
}
100% {
-o-transform: scale(1);
}
}

.btn-connect:disabled {
cursor: not-allowed;
opacity: 0.6;
}

.btn-connect-secondary {
background-color: var(--secondary);
color: var(--text);
}

.btn-connect-info {
background-color: var(--info);
color: var(--text);
}

.btn-connect-success {
background-color: var(--primary);
color: var(--text);
}

.btn-connect-danger {
background-color: var(--danger);
color: var(--text);
}

.spinner-border {
width: 1rem;
height: 1rem;
border-width: 0.2em;
}

.btn-connect .connect-icon,
.btn-connect .disconnect-icon {
transition: opacity 0.3s ease;
}

.btn-connect .disconnect-icon {
position: absolute;
opacity: 0;
}

.btn-connect:hover .connect-icon {
opacity: 1;
}

.btn-connect:hover .disconnect-icon {
opacity: 0;
}

.btn-connect-success:hover .connect-icon {
opacity: 0;
}

.btn-connect-success:hover .disconnect-icon {
opacity: 1;
}
158 changes: 158 additions & 0 deletions plugin/src/components/CustomNetwork/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React, {useCallback, useState, useEffect} from 'react'
import {useAtom} from 'jotai'
import {customNetworkAtom, isCustomNetworkAliveAtom} from '@/atoms'
import useInterval from '@/hooks/useInterval'
import {remixClient} from '@/PluginClient'
import {FaCheck, FaPlug, FaTimes} from 'react-icons/fa'
import './customNetwork.css'
import {AccountSelector} from '../DevnetAccountSelector'
import {FaRotate} from "react-icons/fa6"

const CUSTOM_NETWORK_POLL_INTERVAL = 10_000

export const CustomNetwork = () => {
const [customNetwork, setCustomNetwork] = useAtom(customNetworkAtom)
const [customNetworkInput, setCustomNetworkInput] = useState(customNetwork)
const [, setIsCustomNetworkAlive] = useAtom(isCustomNetworkAliveAtom)
const [connectionStatus, setConnectionStatus] = useState('idle') // 'idle', 'connecting', 'connected', 'error'

const fetchCustomNetworkStatus = useCallback(() => {
if (customNetwork) {
setConnectionStatus('connecting')
fetch(`${customNetwork}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'zks_L1BatchNumber',
params: [],
id: 1
})
})
.then(async (res) => await res.json())
.then((res) => {
if (res.result) {
setIsCustomNetworkAlive(true)
setConnectionStatus('connected')
} else {
remixClient.terminal.log({
type: 'error',
value: `Failed to connect with RPC Url ${customNetwork}`
})
setIsCustomNetworkAlive(false)
setConnectionStatus('error')
}
})
.catch((err) => {
console.error(err)
remixClient.terminal.log({
type: 'error',
value: `Failed to connect with RPC Url ${customNetwork}`
})
setIsCustomNetworkAlive(false)
setConnectionStatus('error')
})
}
}, [setIsCustomNetworkAlive, customNetwork])

useInterval(fetchCustomNetworkStatus, customNetwork.length > 0 ? CUSTOM_NETWORK_POLL_INTERVAL : null)

useEffect(() => {
if (customNetwork) {
fetchCustomNetworkStatus()
} else {
setConnectionStatus('idle')
}
}, [customNetwork, fetchCustomNetworkStatus])

const handleConnect = () => {
setCustomNetwork(customNetworkInput)
}

const handleDisconnect = () => {
setCustomNetwork('')
setConnectionStatus('idle')
setIsCustomNetworkAlive(false)
}

const getButtonClass = () => {
switch (connectionStatus) {
case 'not-connected':
return 'btn-connect-secondary'
case 'connecting':
return 'btn-connect-info'
case 'connected':
return 'btn-connect-success'
case 'error':
return 'btn-connect-danger'
default:
return 'btn-connect-secondary'
}
}

const getButtonText = () => {
switch (connectionStatus) {
case 'connecting':
return 'Connecting...'
case 'connected':
return 'Disconnect'
case 'error':
return 'Retry'
default:
return 'Connect'
}
}

return (
<div
className="flex"
style={{
display: 'flex',
flexDirection: 'column',
padding: '1rem 0rem'
}}
>
<div className="custom-network-container">
<label htmlFor="custom-network-input">Custom Network</label>
<div className="input-button-wrapper">
<input
type="text"
id="custom-network-input"
className="custom-input"
placeholder="http://localhost:3050"
value={customNetworkInput}
onChange={(e) => setCustomNetworkInput(e.target.value)}
disabled={connectionStatus === 'connected'}
/>
<button
className={`btn-connect ${getButtonClass()}`}
onClick={connectionStatus === 'connected' ? handleDisconnect : handleConnect}
disabled={connectionStatus === 'connecting'}
title={getButtonText()}
>
<span className="connect-icon">
{connectionStatus === 'connected' ? (
<FaCheck/>
) : connectionStatus === 'connecting' ? (
<span className="spinner-border spinner-border-sm" role="status"
aria-hidden="true"></span>
) : (connectionStatus === 'idle' || connectionStatus == '') ? (
<FaPlug/>
) : (
<FaRotate/>
)}
</span>
{connectionStatus === 'connected' && (
<span className="disconnect-icon">
<FaTimes/>
</span>
)}
</button>
</div>
</div>
<AccountSelector accountsType="customNet"/>
</div>
)
}
Loading

0 comments on commit 6b94ffa

Please sign in to comment.