Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add form tab switcher to addchaindrawer #2758

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,37 +1,52 @@
<script lang="ts">
import { Button, TextInput } from '@bloomwalletio/ui'
import { AddressType } from '@iota/sdk/out/types'
import { localize } from '@core/i18n'
import {
addNewEvmNetwork,
addNewNetwork,
ChainId,
DEFAULT_BASE_TOKEN,
ETHEREUM_COIN_TYPE,
EvmNetworkId,
IBaseEvmNetworkConfiguration,
IIscChainConfiguration,
IPureEvmNetworkConfiguration,
MAX_NETWORK_NAME_LENGTH,
NetworkNamespace,
NetworkType,
SupportedNetworkId,
} from '@core/network'
import { networks } from '@core/network/stores/networks.store'
import { Router } from '@core/router/classes'
import { isValidHttpsUrl } from '@core/utils'
import { isValidHexAddress, isValidHttpsUrl, validateBech32Address } from '@core/utils'
import { Button, Tabs, TextInput } from '@bloomwalletio/ui'
import { activeProfile } from '@core/profile/stores'
import { NetworkConfigRoute } from '../../network-config-route.enum'
import { Router } from '@core/router/classes'
import { getNetworkHrp } from '@core/profile/actions'

export let drawerRouter: Router<NetworkConfigRoute>

const localeKey = 'views.dashboard.drawers.networkConfig.chain'
const TABS = [
{ key: NetworkType.Evm, value: localize(`${localeKey}.${NetworkType.Evm}`) },
{ key: NetworkType.Isc, value: localize(`${localeKey}.${NetworkType.Isc}`) },
]
let selectedTab = TABS[0]

let chainId = ''
let chainId = '' as ChainId
let name = ''
let explorerUrl = ''
let aliasAddress = ''
let apiEndpoint = ''
let rpcEndpoint = ''
$: type = selectedTab.key

$: submitDisabled = !name || !rpcEndpoint || !chainId

let nameError = ''
let rpcEndpointError = ''
let chainIdError = ''
let explorerUrlError = ''

$: submitDisabled = !name || !chainId || !rpcEndpoint
let apiEndpointError = ''
let aliasAddressError = ''

function validateName(): void {
if (!name) {
Expand All @@ -52,63 +67,123 @@
rpcEndpointError = localize(`${localeKey}.errors.invalidUrl`)
}
}
function validateApiEndpoint(): void {
if (apiEndpoint && !isValidHttpsUrl(apiEndpoint)) {
apiEndpointError = localize(`${localeKey}.errors.invalidUrl`)
}
}

function validateExplorerUrl(): void {
if (explorerUrl && !isValidHttpsUrl(explorerUrl)) {
explorerUrlError = localize(`${localeKey}.errors.invalidUrl`)
}
}

function validateAliasAddress(): void {
const chains = $activeProfile.network.chainConfigurations
let isValidBechAddress = false
try {
validateBech32Address(getNetworkHrp(), aliasAddress, AddressType.Alias)
isValidBechAddress = true
} catch (error) {
isValidBechAddress = false
}

if (!isValidHexAddress(aliasAddress) && !isValidBechAddress) {
aliasAddressError = localize(`${localeKey}.errors.aliasAddressWrongFormat`)
} else if (chains.some(({ aliasAddress }) => aliasAddress === aliasAddress)) {
aliasAddressError = localize(`${localeKey}.errors.aliasAddressAlreadyInUse`)
}
}

function validate(): void {
validateName()
validateRpcEndpoint()
validateChainId()
validateExplorerUrl()
if (selectedTab.key === NetworkType.Isc) {
validateAliasAddress()
validateApiEndpoint()
}
}

function trimInputs(): void {
name = name.trim()
explorerUrl = explorerUrl.trim()
chainId = chainId.trim() as ChainId
rpcEndpoint = rpcEndpoint.trim()
chainId = chainId.trim()
explorerUrl = explorerUrl.trim()
apiEndpoint = apiEndpoint.trim()
}

function resetErrors(): void {
nameError = ''
rpcEndpointError = ''
explorerUrlError = ''
aliasAddressError = ''
apiEndpointError = ''
}

function onSubmitClick(): void {
trimInputs()
resetErrors()
validate()
const hasError = !!nameError || !!rpcEndpointError || !!explorerUrlError || !!chainIdError
if (!hasError) {
const evmNetworkConfiguration: IPureEvmNetworkConfiguration = {
const hasError =
!!nameError || !!rpcEndpointError || !!explorerUrlError || !!apiEndpointError || !!aliasAddressError
if (hasError) {
return
}

const baseNetworkConfig: IBaseEvmNetworkConfiguration = {
id: `${NetworkNamespace.Evm}:${chainId}` as EvmNetworkId,
type,
namespace: NetworkNamespace.Evm,
chainId: chainId,
name,
rpcEndpoint,
explorerUrl,
coinType: ETHEREUM_COIN_TYPE,
baseToken: DEFAULT_BASE_TOKEN[SupportedNetworkId.Ethereum], // TODO
}

if (type === NetworkType.Evm) {
const evmNetworkConfig: IPureEvmNetworkConfiguration = {
...baseNetworkConfig,
type: NetworkType.Evm,
id: `${NetworkNamespace.Evm}:${chainId}`,
namespace: NetworkNamespace.Evm,
chainId: chainId as ChainId,
name,
rpcEndpoint,
explorerUrl,
coinType: ETHEREUM_COIN_TYPE,
baseToken: DEFAULT_BASE_TOKEN[SupportedNetworkId.Ethereum],
}

addNewEvmNetwork(evmNetworkConfiguration)
drawerRouter.previous()
addNewNetwork(evmNetworkConfig)
} else {
const iscNetworkConfig: IIscChainConfiguration = {
...baseNetworkConfig,
type: NetworkType.Isc,
aliasAddress,
apiEndpoint,
}
addNewNetwork(iscNetworkConfig)
}
drawerRouter.previous()
}
</script>

<add-evm-network class="h-full flex flex-col justify-between p-4">
<form id="add-network-form" class="flex flex-col gap-3" on:submit|preventDefault={onSubmitClick}>
<Tabs bind:selectedTab tabs={TABS} />
<TextInput bind:value={name} label={localize('general.name')} error={nameError} />
<TextInput bind:value={rpcEndpoint} label={localize(`${localeKey}.rpcEndpoint`)} error={rpcEndpointError} />
<TextInput bind:value={chainId} label={localize(`${localeKey}.chainId`)} error={nameError} />
<TextInput bind:value={chainId} label={localize(`${localeKey}.chainId`)} error={chainIdError} />

{#if selectedTab.key === NetworkType.Isc}
<TextInput
bind:value={aliasAddress}
label={localize(`${localeKey}.aliasAddress`)}
error={aliasAddressError}
/>
{/if}

<TextInput bind:value={explorerUrl} label={localize(`${localeKey}.explorerUrl`)} error={explorerUrlError} />

{#if selectedTab.key === NetworkType.Isc}
<TextInput bind:value={apiEndpoint} label={localize(`${localeKey}.apiEndpoint`)} error={apiEndpointError} />
{/if}
</form>
<Button
type="submit"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { IMenuItem, IconName, Menu } from '@bloomwalletio/ui'
import { handleError } from '@core/error/handlers'
import { localize } from '@core/i18n'
import { IEvmNetwork, NetworkType, removeExistingEvmNetwork } from '@core/network'
import { IEvmNetwork, NetworkNamespace, removeExistingEvmNetwork } from '@core/network'
import { Router } from '@core/router'
import { closeDrawer } from '@desktop/auxiliary/drawer'
import { PopupId, closePopup, openPopup } from '@desktop/auxiliary/popup'
Expand All @@ -14,7 +14,7 @@
const localeKey = 'views.dashboard.drawers.networkConfig.chain'

$: menuItems = [
...(network.type === NetworkType.Evm
...(network.namespace === NetworkNamespace.Evm
? [
{
icon: IconName.Trash,
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"author": "Bloom Labs Ltd <[email protected]>",
"license": "PolyForm Strict License 1.0.0",
"dependencies": {
"@bloomwalletio/ui": "0.21.8",
"@bloomwalletio/ui": "0.21.10",
"@ethereumjs/common": "4.3.0",
"@ethereumjs/rlp": "5.0.2",
"@ethereumjs/tx": "5.3.0",
Expand Down

This file was deleted.

14 changes: 14 additions & 0 deletions packages/shared/src/lib/core/network/actions/addNewNetwork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { addPersistedEvmNetworkToActiveProfile, addPersistedIscNetworkToActiveProfile } from '@core/profile/stores'
import { IIscChainConfiguration, IPureEvmNetworkConfiguration } from '../interfaces/evm-network-configuration.interface'
import { addEvmNetworkToNetworks, addIscNetworkToNetworks } from '../stores/networks.store'
import { NetworkType } from '../enums'

export function addNewNetwork(networkConfiguration: IPureEvmNetworkConfiguration | IIscChainConfiguration): void {
if (networkConfiguration.type === NetworkType.Evm) {
addPersistedEvmNetworkToActiveProfile(networkConfiguration)
addEvmNetworkToNetworks(networkConfiguration)
} else {
addPersistedIscNetworkToActiveProfile(networkConfiguration)
addIscNetworkToNetworks(networkConfiguration)
}
}
2 changes: 1 addition & 1 deletion packages/shared/src/lib/core/network/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './addNewEvmNetwork'
export * from './addNewNetwork'
export * from './addNodeToClientOptions'
export * from './editNodeInClientOptions'
export * from './getActiveNetworkId'
Expand Down
15 changes: 13 additions & 2 deletions packages/shared/src/lib/core/network/classes/isc-chain.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { NftStandard } from '@core/nfts/enums'
import { ISC_CONFIRMATION_THRESHOLD } from '@core/network/constants'

export class IscChain extends EvmNetwork implements IIscChain {
private readonly _chainApi: string
private readonly _chainApi: string | undefined
private _metadata: IIscChainMetadata | undefined
private WEI_PER_GLOW = BigInt(1_000_000_000_000)

Expand All @@ -36,7 +36,12 @@ export class IscChain extends EvmNetwork implements IIscChain {

this.aliasAddress = aliasAddress
this.apiEndpoint = apiEndpoint
this._chainApi = new URL(`v1/chains/${aliasAddress}`, apiEndpoint).href
try {
const chainUrl = new URL(`v1/chains/${aliasAddress}`, apiEndpoint).href
this._chainApi = chainUrl
} catch (error) {
this._chainApi = undefined
}

void this.setMetadata()
}
Expand Down Expand Up @@ -85,6 +90,9 @@ export class IscChain extends EvmNetwork implements IIscChain {
* node URL). See here for more: https://github.com/iotaledger/wasp/issues/2385
*/
private async fetchChainMetadata(): Promise<IIscChainMetadata> {
if (!this._chainApi) {
throw new Error('Chain API endpoint is not available!')
}
const response = await fetch(this._chainApi)
return (await response.json()) as IIscChainMetadata
}
Expand All @@ -102,6 +110,9 @@ export class IscChain extends EvmNetwork implements IIscChain {
}

async getGasFeeEstimate(outputBytes: string): Promise<bigint> {
if (!this._chainApi) {
throw new Error('Chain API endpoint is not available!')
}
const URL = `${this._chainApi}/estimategas-onledger`
const body = JSON.stringify({ outputBytes })

Expand Down
19 changes: 16 additions & 3 deletions packages/shared/src/lib/core/network/stores/networks.store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Writable, get, writable } from 'svelte/store'
import { activeProfile } from '@core/profile/stores'
import { IscChain, EvmNetwork, StardustNetwork } from '../classes'
import { IEvmNetwork, IIscChain, IStardustNetwork } from '../interfaces'
import {
IBaseEvmNetworkConfiguration,
IEvmNetwork,
IIscChain,
IIscChainConfiguration,
IStardustNetwork,
} from '../interfaces'
import { EvmNetworkId, Network, NetworkId } from '../types'
import { NetworkNamespace, NetworkType } from '../enums'

Expand Down Expand Up @@ -45,9 +51,16 @@ export function addChain(chain: IIscChain): void {
})
}

export function addEvmNetworkToNetworks(evmNetwork: IEvmNetwork): void {
export function addEvmNetworkToNetworks(evmNetworkConfig: IBaseEvmNetworkConfiguration): void {
networks.update((networks) => {
networks.push(new EvmNetwork(evmNetwork))
networks.push(new EvmNetwork(evmNetworkConfig))
return networks
})
}

export function addIscNetworkToNetworks(iscNetworkConfiguration: IIscChainConfiguration): void {
networks.update((networks) => {
networks.push(new IscChain(iscNetworkConfiguration))
return networks
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { get, writable } from 'svelte/store'

import type { IPersistedAccountData } from '@core/account/interfaces'
import type { IEvmAddresses, IPureEvmNetworkConfiguration } from '@core/network/interfaces'
import type { IEvmAddresses, IIscChainConfiguration, IPureEvmNetworkConfiguration } from '@core/network/interfaces'

import { INITIAL_ACTIVE_PROFILE } from '../constants/initial-active-profile.constant'
import type { IProfile, IProfileSettings } from '../interfaces'
Expand Down Expand Up @@ -75,6 +75,20 @@ export function addPersistedEvmNetworkToActiveProfile(persistedNetwork: IPureEvm
})
}

export function addPersistedIscNetworkToActiveProfile(persistedNetwork: IIscChainConfiguration): void {
activeProfile?.update((state) => {
if (!state.network.chainConfigurations) {
state.network.chainConfigurations = []
}
if (state.network.chainConfigurations.some((network) => network.id === persistedNetwork.id)) {
return state
}

state.network.chainConfigurations.push(persistedNetwork)
return state
})
}

export function getActiveProfilePersistedEvmAddressesByAccountIndex(accountIndex: number): IEvmAddresses {
const accountPersistedData = getActiveProfilePersistedAccountData(accountIndex)
return accountPersistedData?.evmAddresses ?? {}
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -434,15 +434,19 @@
"title": "Add chain"
},
"chain": {
"evm": "EVM",
"iscp": "ISC",
"chainId": "Chain ID",
"aliasAddress": "Alias Address",
"rpcEndpoint": "RPC Endpoint",
"apiEndpoint": "API Endpoint (optional)",
"explorerUrl": "Explorer URL (optional)",
"errors": {
"cannotBeEmpty": "Name cannot be empty",
"nameTooLong": "Name is too long",
"aliasAddressWrongFormat": "Must be in either hex or Bech32 format",
"aliasAddressAlreadyInUse": "Alias address already in use",
"chainIdExists": "Chain ID already exists",
"invalidUrl": "Invalid URL"
},
"remove": {
Expand Down
Loading