|
1 | 1 | import { clsx } from 'clsx'; |
2 | | -import React, { useState } from 'react'; |
| 2 | +import React, { useMemo, useState } from 'react'; |
3 | 3 |
|
4 | 4 | import { DEFAULT_GITHUB_REGISTRY } from '@hyperlane-xyz/registry'; |
5 | 5 | import { |
6 | 6 | ChainMetadata, |
7 | 7 | ChainMetadataSchema, |
| 8 | + mergeChainMetadataMap, |
8 | 9 | } from '@hyperlane-xyz/sdk/metadata/chainMetadataTypes'; |
| 10 | +import { |
| 11 | + areChainIdsEqual, |
| 12 | + getEffectiveDomainId, |
| 13 | +} from '@hyperlane-xyz/sdk/metadata/chainIdUtils'; |
9 | 14 | import type { ChainMap } from '@hyperlane-xyz/sdk/types'; |
10 | 15 | import { |
11 | 16 | Result, |
@@ -82,14 +87,18 @@ function Form({ |
82 | 87 | }: ChainAddMenuProps) { |
83 | 88 | const [textInput, setTextInput] = useState(''); |
84 | 89 | const [error, setError] = useState<any>(null); |
| 90 | + const existingChainMetadata = useMemo( |
| 91 | + () => mergeChainMetadataMap(chainMetadata, overrideChainMetadata), |
| 92 | + [chainMetadata, overrideChainMetadata], |
| 93 | + ); |
85 | 94 |
|
86 | 95 | const onChangeInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => { |
87 | 96 | setTextInput(e.target.value); |
88 | 97 | setError(null); |
89 | 98 | }; |
90 | 99 |
|
91 | 100 | const onClickAdd = () => { |
92 | | - const result = tryParseMetadataInput(textInput, chainMetadata); |
| 101 | + const result = tryParseMetadataInput(textInput, existingChainMetadata); |
93 | 102 | if (result.success) { |
94 | 103 | onChangeOverrideMetadata({ |
95 | 104 | ...overrideChainMetadata, |
@@ -149,14 +158,27 @@ function tryParseMetadataInput( |
149 | 158 | } |
150 | 159 |
|
151 | 160 | const newMetadata = result.data as ChainMetadata; |
| 161 | + const chainId = newMetadata.chainId; |
| 162 | + const effectiveDomainId = getEffectiveDomainId(newMetadata); |
152 | 163 |
|
153 | 164 | if (existingChainMetadata[newMetadata.name]) { |
154 | 165 | return failure('name is already in use by another chain'); |
155 | 166 | } |
156 | 167 |
|
| 168 | + // The resolver can tolerate ambiguous duplicate chainId aliases, but local |
| 169 | + // add-chain UX rejects them to avoid persisting ambiguous metadata entries. |
| 170 | + if ( |
| 171 | + Object.entries(existingChainMetadata).some(([, metadata]) => |
| 172 | + areChainIdsEqual(metadata.chainId, chainId), |
| 173 | + ) |
| 174 | + ) { |
| 175 | + return failure('chainId is already in use by another chain'); |
| 176 | + } |
| 177 | + |
157 | 178 | if ( |
158 | | - Object.values(existingChainMetadata).some( |
159 | | - (metadata) => metadata.domainId === newMetadata.domainId, |
| 179 | + effectiveDomainId !== null && |
| 180 | + Object.entries(existingChainMetadata).some( |
| 181 | + ([, metadata]) => getEffectiveDomainId(metadata) === effectiveDomainId, |
160 | 182 | ) |
161 | 183 | ) { |
162 | 184 | return failure('domainId is already in use by another chain'); |
|
0 commit comments