github.com/wormhole-foundation/wormhole-explorer/common@v0.0.0-20240604151348-09585b5b97c5/domain/chainid.go (about) 1 package domain 2 3 import ( 4 "encoding/base32" 5 "encoding/hex" 6 "fmt" 7 "strings" 8 9 algorand_types "github.com/algorand/go-algorand-sdk/types" 10 "github.com/cosmos/btcutil/bech32" 11 "github.com/mr-tron/base58" 12 "github.com/wormhole-foundation/wormhole-explorer/common/utils" 13 sdk "github.com/wormhole-foundation/wormhole/sdk/vaa" 14 ) 15 16 var ( 17 // nearKnownEmitters maps NEAR emitter addresses to NEAR accounts. 18 nearKnownEmitters = map[string]string{ 19 "148410499d3fcda4dcfd68a1ebfcdddda16ab28326448d4aae4d2f0465cdfcb7": "contract.portalbridge.near", 20 } 21 22 // suiKnownEmitters maps Sui emitter addresses to Sui accounts. 23 suiKnownEmitters = map[string]string{ 24 "ccceeb29348f71bdd22ffef43a2a19c1f5b5e17c5cca5411529120182672ade5": "0xc57508ee0d4595e5a8728974a4a93a787d38f339757230d441e895422c07aba9", 25 } 26 27 // aptosKnownEmitters maps Aptos emitter addresses to Aptos accounts. 28 aptosKnownEmitters = map[string]string{ 29 // Token Bridge 30 "0000000000000000000000000000000000000000000000000000000000000001": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", 31 // NFT Bridge 32 "0000000000000000000000000000000000000000000000000000000000000005": "0x1bdffae984043833ed7fe223f7af7a3f8902d04129b14f801823e64827da7130", 33 } 34 ) 35 36 var allChainIDs = make(map[sdk.ChainID]bool) 37 38 func init() { 39 for _, chainID := range sdk.GetAllNetworkIDs() { 40 allChainIDs[chainID] = true 41 } 42 } 43 44 // ChainIdIsValid returns true if and only if the given chain ID exists. 45 func ChainIdIsValid(id sdk.ChainID) bool { 46 _, exists := allChainIDs[id] 47 return exists 48 } 49 50 // GetSupportedChainIDs returns a map of all supported chain IDs to their respective names. 51 func GetSupportedChainIDs() map[sdk.ChainID]string { 52 chainIDs := sdk.GetAllNetworkIDs() 53 supportedChaindIDs := make(map[sdk.ChainID]string, len(chainIDs)) 54 for _, chainID := range chainIDs { 55 supportedChaindIDs[chainID] = chainID.String() 56 } 57 return supportedChaindIDs 58 } 59 60 // TranslateEmitterAddress converts an emitter address into the corresponding native address for the given chain. 61 func TranslateEmitterAddress(chainID sdk.ChainID, address string) (string, error) { 62 63 // Decode the address from hex 64 addressBytes, err := hex.DecodeString(address) 65 if err != nil { 66 return "", fmt.Errorf(`failed to decode emitter address "%s" from hex: %w`, address, err) 67 } 68 if len(addressBytes) != 32 { 69 return "", fmt.Errorf("expected emitter address length to be 32: %s", address) 70 } 71 72 // Translation rules are based on the chain ID 73 switch chainID { 74 75 // Solana emitter addresses use base58 encoding. 76 case sdk.ChainIDSolana: 77 return base58.Encode(addressBytes), nil 78 79 // EVM chains use the classic hex, 0x-prefixed encoding. 80 // Also, Karura and Acala support EVM-compatible addresses, so they're handled here as well. 81 case sdk.ChainIDEthereum, 82 sdk.ChainIDBase, 83 sdk.ChainIDBSC, 84 sdk.ChainIDPolygon, 85 sdk.ChainIDPolygonSepolia, 86 sdk.ChainIDAvalanche, 87 sdk.ChainIDOasis, 88 sdk.ChainIDAurora, 89 sdk.ChainIDFantom, 90 sdk.ChainIDKarura, 91 sdk.ChainIDAcala, 92 sdk.ChainIDKlaytn, 93 sdk.ChainIDCelo, 94 sdk.ChainIDMoonbeam, 95 sdk.ChainIDArbitrum, 96 sdk.ChainIDOptimism, 97 sdk.ChainIDSepolia, 98 sdk.ChainIDArbitrumSepolia, 99 sdk.ChainIDBaseSepolia, 100 sdk.ChainIDOptimismSepolia, 101 sdk.ChainIDHolesky, 102 sdk.ChainIDWormchain, 103 sdk.ChainIDScroll, 104 sdk.ChainIDBlast, 105 sdk.ChainIDXLayer: 106 107 return "0x" + hex.EncodeToString(addressBytes[12:]), nil 108 109 // Terra addresses use bench32 encoding 110 case sdk.ChainIDTerra: 111 return encodeBech32("terra", addressBytes[12:]) 112 113 // Terra2 addresses use bench32 encoding 114 case sdk.ChainIDTerra2: 115 return encodeBech32("terra", addressBytes) 116 117 // Injective addresses use bench32 encoding 118 case sdk.ChainIDInjective: 119 return encodeBech32("inj", addressBytes[12:]) 120 121 // Xpla addresses use bench32 encoding 122 case sdk.ChainIDXpla: 123 return encodeBech32("xpla", addressBytes) 124 125 // Sei addresses use bench32 encoding 126 case sdk.ChainIDSei: 127 return encodeBech32("sei", addressBytes) 128 129 // Algorand addresses use base32 encoding with a trailing checksum. 130 // We're using the SDK to handle the checksum logic. 131 case sdk.ChainIDAlgorand: 132 133 var addr algorand_types.Address 134 if len(addr) != len(addressBytes) { 135 return "", fmt.Errorf("expected Algorand address to be %d bytes long, but got: %d", len(addr), len(addressBytes)) 136 } 137 copy(addr[:], addressBytes[:]) 138 139 return addr.String(), nil 140 141 // Near addresses are arbitrary-length strings. The emitter is the sha256 digest of the program address string. 142 // 143 // We're using a hashmap of known emitters to avoid querying external APIs. 144 case sdk.ChainIDNear: 145 if nativeAddress, ok := nearKnownEmitters[address]; ok { 146 return nativeAddress, nil 147 } else { 148 return "", fmt.Errorf(`no mapping found for NEAR emitter address "%s"`, address) 149 } 150 151 // For Sui emitters, an emitter capacity is taken from the core bridge. The capability object ID is used. 152 // 153 // We're using a hashmap of known emitters to avoid querying the contract's state. 154 case sdk.ChainIDSui: 155 if nativeAddress, ok := suiKnownEmitters[address]; ok { 156 return nativeAddress, nil 157 } else { 158 return "", fmt.Errorf(`no mapping found for Sui emitter address "%s"`, address) 159 } 160 161 // For Aptos, an emitter capability is taken from the core bridge. The capability object ID is used. 162 // The core bridge generates capabilities in a sequence and the capability object ID is its index in the sequence. 163 // 164 // We're using a hashmap of known emitters to avoid querying the contract's state. 165 case sdk.ChainIDAptos: 166 if nativeAddress, ok := aptosKnownEmitters[address]; ok { 167 return nativeAddress, nil 168 } else { 169 return "", fmt.Errorf(`no mapping found for Aptos emitter address "%s"`, address) 170 } 171 172 default: 173 return "", fmt.Errorf("can't translate emitter address: ChainID=%d not supported", chainID) 174 } 175 } 176 177 func NormalizeTxHashByChainId(chainID sdk.ChainID, txHash string) string { 178 switch chainID { 179 case sdk.ChainIDEthereum, 180 sdk.ChainIDBase, 181 sdk.ChainIDBSC, 182 sdk.ChainIDPolygon, 183 sdk.ChainIDPolygonSepolia, 184 sdk.ChainIDAvalanche, 185 sdk.ChainIDOasis, 186 sdk.ChainIDAurora, 187 sdk.ChainIDFantom, 188 sdk.ChainIDKarura, 189 sdk.ChainIDAcala, 190 sdk.ChainIDKlaytn, 191 sdk.ChainIDCelo, 192 sdk.ChainIDMoonbeam, 193 sdk.ChainIDArbitrum, 194 sdk.ChainIDOptimism, 195 sdk.ChainIDSepolia, 196 sdk.ChainIDArbitrumSepolia, 197 sdk.ChainIDBaseSepolia, 198 sdk.ChainIDOptimismSepolia, 199 sdk.ChainIDHolesky, 200 sdk.ChainIDScroll, 201 sdk.ChainIDBlast, 202 sdk.ChainIDXLayer: 203 lowerTxHash := strings.ToLower(txHash) 204 return utils.Remove0x(lowerTxHash) 205 default: 206 return txHash 207 } 208 } 209 210 // EncodeTrxHashByChainID encodes the transaction hash by chain id with different encoding methods. 211 func EncodeTrxHashByChainID(chainID sdk.ChainID, txHash []byte) (string, error) { 212 switch chainID { 213 case sdk.ChainIDSolana: 214 return base58.Encode(txHash), nil 215 case sdk.ChainIDEthereum: 216 return hex.EncodeToString(txHash), nil 217 case sdk.ChainIDTerra: 218 return hex.EncodeToString(txHash), nil 219 case sdk.ChainIDBSC: 220 return hex.EncodeToString(txHash), nil 221 case sdk.ChainIDPolygon: 222 return hex.EncodeToString(txHash), nil 223 case sdk.ChainIDAvalanche: 224 return hex.EncodeToString(txHash), nil 225 case sdk.ChainIDOasis: 226 return hex.EncodeToString(txHash), nil 227 case sdk.ChainIDAlgorand: 228 return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(txHash), nil 229 case sdk.ChainIDAurora: 230 return hex.EncodeToString(txHash), nil 231 case sdk.ChainIDFantom: 232 return hex.EncodeToString(txHash), nil 233 case sdk.ChainIDKarura: 234 return hex.EncodeToString(txHash), nil 235 case sdk.ChainIDAcala: 236 return hex.EncodeToString(txHash), nil 237 case sdk.ChainIDKlaytn: 238 return hex.EncodeToString(txHash), nil 239 case sdk.ChainIDCelo: 240 return hex.EncodeToString(txHash), nil 241 case sdk.ChainIDNear: 242 return base58.Encode(txHash), nil 243 case sdk.ChainIDMoonbeam: 244 return hex.EncodeToString(txHash), nil 245 case sdk.ChainIDTerra2: 246 return hex.EncodeToString(txHash), nil 247 case sdk.ChainIDInjective: 248 return hex.EncodeToString(txHash), nil 249 case sdk.ChainIDSui: 250 return base58.Encode(txHash), nil 251 case sdk.ChainIDAptos: 252 return hex.EncodeToString(txHash), nil 253 case sdk.ChainIDArbitrum: 254 return hex.EncodeToString(txHash), nil 255 case sdk.ChainIDOptimism: 256 return hex.EncodeToString(txHash), nil 257 case sdk.ChainIDXpla: 258 return hex.EncodeToString(txHash), nil 259 case sdk.ChainIDBtc: 260 //TODO: check if this is correct 261 return hex.EncodeToString(txHash), nil 262 case sdk.ChainIDBase: 263 return hex.EncodeToString(txHash), nil 264 case sdk.ChainIDSei: 265 return hex.EncodeToString(txHash), nil 266 case sdk.ChainIDWormchain: 267 //TODO: check if this is correct 268 return hex.EncodeToString(txHash), nil 269 case sdk.ChainIDScroll: 270 return hex.EncodeToString(txHash), nil 271 case sdk.ChainIDBlast: 272 return hex.EncodeToString(txHash), nil 273 case sdk.ChainIDXLayer: 274 return hex.EncodeToString(txHash), nil 275 case sdk.ChainIDSepolia, 276 sdk.ChainIDArbitrumSepolia, 277 sdk.ChainIDBaseSepolia, 278 sdk.ChainIDOptimismSepolia, 279 sdk.ChainIDHolesky, 280 sdk.ChainIDPolygonSepolia: 281 return hex.EncodeToString(txHash), nil 282 default: 283 return hex.EncodeToString(txHash), fmt.Errorf("unknown chain id: %d", chainID) 284 } 285 } 286 287 // DecodeNativeAddressToHex decodes a native address to hex. 288 func DecodeNativeAddressToHex(chainID sdk.ChainID, address string) (string, error) { 289 290 // Translation rules are based on the chain ID 291 switch chainID { 292 293 // Solana emitter addresses use base58 encoding. 294 case sdk.ChainIDSolana: 295 addr, err := base58.Decode(address) 296 if err != nil { 297 return "", fmt.Errorf("base58 decoding failed: %w", err) 298 } 299 return hex.EncodeToString(addr), nil 300 301 // EVM chains use the classic hex, 0x-prefixed encoding. 302 // Also, Karura and Acala support EVM-compatible addresses, so they're handled here as well. 303 case sdk.ChainIDEthereum, 304 sdk.ChainIDBase, 305 sdk.ChainIDBSC, 306 sdk.ChainIDPolygon, 307 sdk.ChainIDPolygonSepolia, 308 sdk.ChainIDAvalanche, 309 sdk.ChainIDOasis, 310 sdk.ChainIDAurora, 311 sdk.ChainIDFantom, 312 sdk.ChainIDKarura, 313 sdk.ChainIDAcala, 314 sdk.ChainIDKlaytn, 315 sdk.ChainIDCelo, 316 sdk.ChainIDMoonbeam, 317 sdk.ChainIDArbitrum, 318 sdk.ChainIDOptimism, 319 sdk.ChainIDSepolia, 320 sdk.ChainIDArbitrumSepolia, 321 sdk.ChainIDBaseSepolia, 322 sdk.ChainIDOptimismSepolia, 323 sdk.ChainIDHolesky, 324 sdk.ChainIDWormchain, 325 sdk.ChainIDScroll, 326 sdk.ChainIDBlast, 327 sdk.ChainIDXLayer: 328 return address, nil 329 330 // Terra addresses use bench32 encoding 331 case sdk.ChainIDTerra: 332 return decodeBech32("terra", address) 333 334 // Terra2 addresses use bench32 encoding 335 case sdk.ChainIDTerra2: 336 return decodeBech32("terra", address) 337 338 // Injective addresses use bench32 encoding 339 case sdk.ChainIDInjective: 340 return decodeBech32("inj", address) 341 342 // Sui addresses use hex encoding 343 case sdk.ChainIDSui: 344 return address, nil 345 346 // Aptos addresses use hex encoding 347 case sdk.ChainIDAptos: 348 return address, nil 349 350 // Xpla addresses use bench32 encoding 351 case sdk.ChainIDXpla: 352 return decodeBech32("xpla", address) 353 354 // Sei addresses use bench32 encoding 355 case sdk.ChainIDSei: 356 return decodeBech32("sei", address) 357 358 // Algorand addresses use base32 encoding with a trailing checksum. 359 // We're using the SDK to handle the checksum logic. 360 case sdk.ChainIDAlgorand: 361 addr, err := algorand_types.DecodeAddress(address) 362 if err != nil { 363 return "", fmt.Errorf("algorand decoding failed: %w", err) 364 } 365 return hex.EncodeToString(addr[:]), nil 366 367 default: 368 return "", fmt.Errorf("can't translate emitter address: ChainID=%d not supported", chainID) 369 } 370 } 371 372 // decodeBech32 is a helper function to decode a bech32 addresses. 373 func decodeBech32(h, address string) (string, error) { 374 375 hrp, decoded, err := bech32.Decode(address, bech32.MaxLengthBIP173) 376 if err != nil { 377 return "", fmt.Errorf("bech32 decoding failed: %w", err) 378 } 379 if hrp != h { 380 return "", fmt.Errorf("bech32 decoding failed, invalid prefix: %s", hrp) 381 } 382 383 return hex.EncodeToString(decoded), nil 384 } 385 386 // encodeBech32 is a helper function to encode a bech32 addresses. 387 func encodeBech32(hrp string, data []byte) (string, error) { 388 389 aligned, err := bech32.ConvertBits(data, 8, 5, true) 390 if err != nil { 391 return "", fmt.Errorf("bech32 encoding failed: %w", err) 392 } 393 394 return bech32.Encode(hrp, aligned) 395 }