Skip to content

Commit

Permalink
feat: add ISCP/EVM network support (#6634)
Browse files Browse the repository at this point in the history
* chore: rename interface and other code accordingly

* chore: cleanup some code before review

* fix: remove unnecessary optional chaining

* chore: change name of constant

* chore: add union type

* chore: cleanup code

* chore: change constant name

* feat: add necessary interfaces

* feat: add code up to adding a chain

* chore: disable no-console

* feat: add node connectivity with web3.js

* chore: change var name

* chore: make changes per PR comments

* chore: cleanup code

* chore: put chain creation inside of chain class

* chore: cleanup code for adding / removing chain

* chore: cleanup code and add comments

* refactor: restructure network store

Co-authored-by: Matthew Maxwell <maxwellmattryan@users.noreply.github.com>

* feature: add default chain to profile on profile creation

* chore: separate chain metadata and configuration

* chore: fix types

* chore: prepare code for review

* chore: remove unused store

* chore: revert feature flags

---------

Co-authored-by: Mark Nardi <mark.nardi@iota.org>
Co-authored-by: Matthew Maxwell <maxwellmattryan@users.noreply.github.com>
  • Loading branch information
3 people committed May 1, 2023
1 parent a471528 commit 9232316
Show file tree
Hide file tree
Showing 32 changed files with 1,949 additions and 93 deletions.
4 changes: 2 additions & 2 deletions packages/desktop/components/NetworkCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { ClickableTile, Text, Icon, FontWeight, TextType, NetworkIcon, NetworkStatusPill } from '@ui'
import { Icon as IconEnum } from '@auxiliary/icon'
import { truncateString } from '@core/utils'
import { NetworkHealth, NetworkId, selectedConnectedChainIndex } from '@core/network'
import { NetworkHealth, NetworkId, selectedChainIndex } from '@core/network'
export let name: string
export let address: string
Expand All @@ -16,7 +16,7 @@
}
function onQrCodeIconClick(): void {
$selectedConnectedChainIndex = index
$selectedChainIndex = index
$networkConfigRouter.goTo(NetworkConfigRoute.ChainDepositAddress)
}
</script>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
<script lang="ts">
import { selectedConnectedChain } from '@core/network'
import { selectedChainIndex } from '@core/network'
import { appSettings } from '@core/app'
import { QR, AddressBox, FontWeight } from '@ui'
import { Text } from '@ui'
import { localize } from '@core/i18n'
import { selectedAccount } from '@core/account'
let addressBoxElement: AddressBox
$: chain = $selectedConnectedChain
let address: string = ''
$: $selectedChainIndex, $selectedAccount, (address = getAddress())
function getAddress(): string {
if ($selectedChainIndex === 0) {
return $selectedAccount.depositAddress
} else {
// TODO: Get address from selected chain
// const chain = $network.getChain($selectedChainIndex - 1)
return ''
}
}
function onReceiveClick(): void {
addressBoxElement.copyAddress()
Expand All @@ -20,7 +32,7 @@
on:click={onReceiveClick}
>
<inner-box class="flex flex-col space-y-6 pt-7 pb-6">
<QR data={chain.address} />
<QR data={address} />
<div class="flex flex-col space-y-1">
<Text fontWeight={FontWeight.medium} color="gray-600" darkColor="white"
>{localize('general.myAddress')}</Text
Expand All @@ -29,7 +41,7 @@
bind:this={addressBoxElement}
clearBackground
clearPadding
address={chain.address}
{address}
fontSize="sm"
isCopyable
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
)
chains.push(mainChain)
for (const chain of $activeProfile.network.chains) {
for (const chain of $activeProfile.network.chainConfigurations) {
chains.push({
name: chain.name,
address: chain.name, // TODO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
validate()
const hasError = !!nameError || !!aliasAddressError || !!iscpEndpointError || !!explorerUrlError
if (!hasError) {
// TODO: Fetch chainId from ISCP node before adding it to profile
// TODO: https://github.com/iotaledger/firefly/issues/6375
}
}
</script>
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/lib/core/network/classes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './iscp-chain.class'
export * from './stardust-network.class'
70 changes: 70 additions & 0 deletions packages/shared/lib/core/network/classes/iscp-chain.class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Web3 from 'web3'

import { IBlock, IChain, IChainStatus, IIscpChainConfiguration, IIscpChainMetadata } from '../interfaces'
import { ChainConfiguration, ChainMetadata, Web3Provider } from '../types'

export class IscpChain implements IChain {
private readonly _provider: Web3Provider
private readonly _configuration: IIscpChainConfiguration

private _metadata: IIscpChainMetadata

constructor(payload: IIscpChainConfiguration) {
try {
/**
* NOTE: We can assume that the data inside this payload has already
* been validated.
*/
const { aliasAddress, iscpEndpoint } = payload
const evmJsonRpcPath = this.buildEvmJsonRpcPath(aliasAddress)

this._provider = new Web3(`${iscpEndpoint}/${evmJsonRpcPath}`)
this._configuration = payload
} catch (err) {
console.error(err)
}
}

private buildEvmJsonRpcPath(aliasAddress: string): string {
/**
* NOTE: This is according to the WASP node API specification,
* which can be found here: https://editor.swagger.io/?url=https://raw.githubusercontent.com/iotaledger/wasp/develop/clients/apiclient/api/openapi.yaml.
*/
return `v1/chains/${aliasAddress}/evm`
}

getConfiguration(): ChainConfiguration {
return this._configuration
}

getMetadata(): Promise<ChainMetadata> {
if (this._metadata) {
return Promise.resolve(this._metadata)
} else {
this._metadata = <IIscpChainMetadata>{} // await this.fetchChainMetadata()
return Promise.resolve(this._metadata)
}
}

/**
* CAUTION: The API endpoint used by this method is not available
* with the public ShimmerEVM node URL (b/c it's actually just
* the EVM JSON-RPC endpoint rather than the underlying WASP
* node URL). See here for more: https://github.com/iotaledger/wasp/issues/2385
*/
private async fetchChainMetadata(): Promise<IIscpChainMetadata> {
const { aliasAddress, iscpEndpoint } = this._configuration
const chainMetadataUrl = `${iscpEndpoint}/v1/chains/${aliasAddress}`
const response = await fetch(chainMetadataUrl)
return (await response.json()) as IIscpChainMetadata
}

getStatus(): Promise<IChainStatus> {
return undefined
}

async getLatestBlock(): Promise<IBlock> {
const number = await this._provider.eth.getBlockNumber()
return this._provider.eth.getBlock(number)
}
}
89 changes: 89 additions & 0 deletions packages/shared/lib/core/network/classes/stardust-network.class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* eslint-disable @typescript-eslint/no-unused-vars */

import { get } from 'svelte/store'

import { activeProfile, updateActiveProfile } from '@core/profile/stores'

import { ChainType } from '../enums'
import { IChain, IIscpChainConfiguration, INetwork, INetworkStatus } from '../interfaces'
import { networkStatus } from '../stores'
import { ChainConfiguration, NetworkMetadata } from '../types'

import { IscpChain } from './iscp-chain.class'

export class StardustNetwork implements INetwork {
private readonly _metadata: NetworkMetadata
private readonly _chains: IChain[]

constructor(metadata: NetworkMetadata, chainConfigurations: ChainConfiguration[]) {
this._metadata = metadata
this._chains = this.constructChains(chainConfigurations)
}

private constructChains(chainConfigurations: ChainConfiguration[]): IChain[] {
return chainConfigurations.map((chainConfiguration) => {
switch (chainConfiguration.type) {
case ChainType.Iscp:
return new IscpChain(chainConfiguration)
case ChainType.Evm:
return undefined
default:
return undefined
}
})
}

getMetadata(): NetworkMetadata {
return this._metadata
}

getStatus(): INetworkStatus {
return get(networkStatus) ?? {}
}

getChain(chainId: number): IChain {
return this._chains.find((chain) => chain.getConfiguration().chainId === chainId)
}

getChains(): IChain[] {
return this._chains
}

addChain(chainConfiguration: ChainConfiguration): IChain {
if (this.isChainAlreadyAdded(chainConfiguration)) {
throw new Error('This chain has already been added.')
} else {
const network = get(activeProfile)?.network
network.chainConfigurations.push(chainConfiguration)
/**
* NOTE: Updating the active profile will cause the network store object to be
* re-instantiated, which will also instantiate an object for the newly added
* chain.
*/
updateActiveProfile({ network })

return new IscpChain(<IIscpChainConfiguration>chainConfiguration)
}
}

private isChainAlreadyAdded(chainConfiguration: ChainConfiguration): boolean {
const network = get(activeProfile)?.network
return network.chainConfigurations.some((chain) => {
const hasSameName = chain.name === chainConfiguration.name
const hasSameChainId = chain.chainId === chainConfiguration.chainId
return hasSameName || hasSameChainId
})
}

editChain(chainId: number, payload: Partial<ChainConfiguration>): Promise<void> {
return Promise.resolve()
}

removeChain(chainId: number): void {
const network = get(activeProfile).network
const newChains = network.chainConfigurations.filter(
(chainConfiguration) => chainConfiguration.chainId !== chainId
)
updateActiveProfile({ network: { ...network, chainConfigurations: newChains } })
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ChainType, NetworkId } from '../enums'
import { ChainConfiguration } from '../types'

export const DEFAULT_CHAIN_CONFIGURATIONS: Readonly<{ [id in NetworkId]?: ChainConfiguration }> = {
[NetworkId.Shimmer]: {
type: ChainType.Iscp,
name: 'ShimmerEVM',
chainId: 1071,
aliasAddress: '', // 'rms1prwgvvw472spqusqeufvlmp8xdpyxtrnmvt26jnuk6sxdcq2hk8scku26h7',
iscpEndpoint: '', // 'https://json-rpc.evm.testnet.shimmer.network',
},
[NetworkId.Testnet]: {
type: ChainType.Iscp,
name: 'ShimmerEVM',
chainId: 1071,
aliasAddress: 'rms1prwgvvw472spqusqeufvlmp8xdpyxtrnmvt26jnuk6sxdcq2hk8scku26h7',
iscpEndpoint: 'https://json-rpc.evm.testnet.shimmer.network',
},
}
1 change: 1 addition & 0 deletions packages/shared/lib/core/network/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './coin-type.constant'
export * from './default-base-token.constant'
export * from './default-chain-configurations.constant'
export * from './default-network-metadata.constant'
export * from './empty-node.constant'
export * from './explorer-urls.constant'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { BlockTransactionString } from 'web3-eth'

/* eslint-disable @typescript-eslint/no-empty-interface */
export interface IBlock extends BlockTransactionString {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ChainType } from '../enums'

export interface IIscpChainConfiguration extends IBaseChainConfiguration {
type: ChainType.Iscp
aliasAddress: string
iscpEndpoint: string
}

export interface IEvmChainConfiguration extends IBaseChainConfiguration {
type: ChainType.Evm
rpcEndpoint: string
symbol: string
ticker: string
}

export interface IBaseChainConfiguration {
type: ChainType
chainId: number
name: string
explorerUrl?: string
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { ChainType } from '../enums'
import { IGasFeePolicy } from './gas-fee-policy.interface'
import { IGasLimits } from './gas-limits.interface'

export interface IBaseChainMetadata {
type: ChainType
chainId: number
name: string
explorerUrl?: string
export interface IIscpChainMetadata {
/**
* CAUTION: This field is named incorrectly, as it should be
* "chainId". See here for more: https://github.com/iotaledger/wasp/issues/2411
*/
chainID: string
evmChainId: number
chainOwnerId: string
isActive: boolean
gasLimits: IGasLimits
gasFeePolicy: IGasFeePolicy
customMetadata: unknown
}

export interface IIscpChainMetadata extends IBaseChainMetadata {
type: ChainType.Iscp
aliasAddress: string
iscpEndpoint: string
}

export interface IEvmChainMetadata extends IBaseChainMetadata {
type: ChainType.Evm
symbol: string
ticker: string
rpcEndpoint: string
}
/* eslint-disable @typescript-eslint/no-empty-interface */
export interface IEvmChainMetadata {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NetworkHealth } from '../enums'

export interface IChainStatus {
health?: NetworkHealth
}
11 changes: 11 additions & 0 deletions packages/shared/lib/core/network/interfaces/chain.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ChainConfiguration, ChainMetadata } from '../types'
import { IBlock } from './block.interface'
import { IChainStatus } from './chain-status.interface'

export interface IChain {
getConfiguration(): ChainConfiguration
getMetadata(): Promise<ChainMetadata>
getStatus(): Promise<IChainStatus>

getLatestBlock(): Promise<IBlock>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface IGasFeePolicy {
gasPerToken: {
[name: string]: number
}
validatorFeeShare: number
evmGasRatio: {
[name: string]: number
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface IGasLimits {
maxGasExternalViewCall: number
minGasPerRequest: number
maxGasPerBlock: number
maxGasPerRequest: number
}
7 changes: 7 additions & 0 deletions packages/shared/lib/core/network/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
export * from './auth.interface'
export * from './block.interface'
export * from './chain.interface'
export * from './chain-configuration.interface'
export * from './chain-metadata.interface'
export * from './chain-status.interface'
export * from './connected-chain.interface'
export * from './client-options.interface'
export * from './gas-fee-policy.interface'
export * from './gas-limits.interface'
export * from './connected-chain.interface'
export * from './network.interface'
export * from './network-status.interface'
export * from './node.interface'
export * from './node-info.interface'
Expand Down
Loading

0 comments on commit 9232316

Please sign in to comment.