wormhole-explorer/common/domain/chainid.go

347 lines
11 KiB
Go

package domain
import (
"encoding/base32"
"encoding/hex"
"fmt"
algorand_types "github.com/algorand/go-algorand-sdk/types"
"github.com/cosmos/btcutil/bech32"
"github.com/mr-tron/base58"
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
)
var (
// nearKnownEmitters maps NEAR emitter addresses to NEAR accounts.
nearKnownEmitters = map[string]string{
"148410499d3fcda4dcfd68a1ebfcdddda16ab28326448d4aae4d2f0465cdfcb7": "contract.portalbridge.near",
}
// suiKnownEmitters maps Sui emitter addresses to Sui accounts.
suiKnownEmitters = map[string]string{
"ccceeb29348f71bdd22ffef43a2a19c1f5b5e17c5cca5411529120182672ade5": "0xc57508ee0d4595e5a8728974a4a93a787d38f339757230d441e895422c07aba9",
}
// aptosKnownEmitters maps Aptos emitter addresses to Aptos accounts.
aptosKnownEmitters = map[string]string{
// Token Bridge
"0000000000000000000000000000000000000000000000000000000000000001": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f",
// NFT Bridge
"0000000000000000000000000000000000000000000000000000000000000005": "0x1bdffae984043833ed7fe223f7af7a3f8902d04129b14f801823e64827da7130",
}
)
var allChainIDs = make(map[sdk.ChainID]bool)
func init() {
for _, chainID := range sdk.GetAllNetworkIDs() {
allChainIDs[chainID] = true
}
}
// ChainIdIsValid returns true if and only if the given chain ID exists.
func ChainIdIsValid(id sdk.ChainID) bool {
_, exists := allChainIDs[id]
return exists
}
// GetSupportedChainIDs returns a map of all supported chain IDs to their respective names.
func GetSupportedChainIDs() map[sdk.ChainID]string {
chainIDs := sdk.GetAllNetworkIDs()
supportedChaindIDs := make(map[sdk.ChainID]string, len(chainIDs))
for _, chainID := range chainIDs {
supportedChaindIDs[chainID] = chainID.String()
}
return supportedChaindIDs
}
// TranslateEmitterAddress converts an emitter address into the corresponding native address for the given chain.
func TranslateEmitterAddress(chainID sdk.ChainID, address string) (string, error) {
// Decode the address from hex
addressBytes, err := hex.DecodeString(address)
if err != nil {
return "", fmt.Errorf(`failed to decode emitter address "%s" from hex: %w`, address, err)
}
if len(addressBytes) != 32 {
return "", fmt.Errorf("expected emitter address length to be 32: %s", address)
}
// Translation rules are based on the chain ID
switch chainID {
// Solana emitter addresses use base58 encoding.
case sdk.ChainIDSolana:
return base58.Encode(addressBytes), nil
// EVM chains use the classic hex, 0x-prefixed encoding.
// Also, Karura and Acala support EVM-compatible addresses, so they're handled here as well.
case sdk.ChainIDEthereum,
sdk.ChainIDBase,
sdk.ChainIDBSC,
sdk.ChainIDPolygon,
sdk.ChainIDAvalanche,
sdk.ChainIDOasis,
sdk.ChainIDAurora,
sdk.ChainIDFantom,
sdk.ChainIDKarura,
sdk.ChainIDAcala,
sdk.ChainIDKlaytn,
sdk.ChainIDCelo,
sdk.ChainIDMoonbeam,
sdk.ChainIDArbitrum,
sdk.ChainIDOptimism,
sdk.ChainIDSepolia,
sdk.ChainIDArbitrumSepolia,
sdk.ChainIDBaseSepolia,
sdk.ChainIDOptimismSepolia,
sdk.ChainIDHolesky:
return "0x" + hex.EncodeToString(addressBytes[12:]), nil
// Terra addresses use bench32 encoding
case sdk.ChainIDTerra:
return encodeBech32("terra", addressBytes[12:])
// Terra2 addresses use bench32 encoding
case sdk.ChainIDTerra2:
return encodeBech32("terra", addressBytes)
// Injective addresses use bench32 encoding
case sdk.ChainIDInjective:
return encodeBech32("inj", addressBytes[12:])
// Xpla addresses use bench32 encoding
case sdk.ChainIDXpla:
return encodeBech32("xpla", addressBytes)
// Sei addresses use bench32 encoding
case sdk.ChainIDSei:
return encodeBech32("sei", addressBytes)
// Algorand addresses use base32 encoding with a trailing checksum.
// We're using the SDK to handle the checksum logic.
case sdk.ChainIDAlgorand:
var addr algorand_types.Address
if len(addr) != len(addressBytes) {
return "", fmt.Errorf("expected Algorand address to be %d bytes long, but got: %d", len(addr), len(addressBytes))
}
copy(addr[:], addressBytes[:])
return addr.String(), nil
// Near addresses are arbitrary-length strings. The emitter is the sha256 digest of the program address string.
//
// We're using a hashmap of known emitters to avoid querying external APIs.
case sdk.ChainIDNear:
if nativeAddress, ok := nearKnownEmitters[address]; ok {
return nativeAddress, nil
} else {
return "", fmt.Errorf(`no mapping found for NEAR emitter address "%s"`, address)
}
// For Sui emitters, an emitter capacity is taken from the core bridge. The capability object ID is used.
//
// We're using a hashmap of known emitters to avoid querying the contract's state.
case sdk.ChainIDSui:
if nativeAddress, ok := suiKnownEmitters[address]; ok {
return nativeAddress, nil
} else {
return "", fmt.Errorf(`no mapping found for Sui emitter address "%s"`, address)
}
// For Aptos, an emitter capability is taken from the core bridge. The capability object ID is used.
// The core bridge generates capabilities in a sequence and the capability object ID is its index in the sequence.
//
// We're using a hashmap of known emitters to avoid querying the contract's state.
case sdk.ChainIDAptos:
if nativeAddress, ok := aptosKnownEmitters[address]; ok {
return nativeAddress, nil
} else {
return "", fmt.Errorf(`no mapping found for Aptos emitter address "%s"`, address)
}
default:
return "", fmt.Errorf("can't translate emitter address: ChainID=%d not supported", chainID)
}
}
// EncodeTrxHashByChainID encodes the transaction hash by chain id with different encoding methods.
func EncodeTrxHashByChainID(chainID sdk.ChainID, txHash []byte) (string, error) {
switch chainID {
case sdk.ChainIDSolana:
return base58.Encode(txHash), nil
case sdk.ChainIDEthereum:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDTerra:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDBSC:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDPolygon:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDAvalanche:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDOasis:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDAlgorand:
return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(txHash), nil
case sdk.ChainIDAurora:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDFantom:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDKarura:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDAcala:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDKlaytn:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDCelo:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDNear:
return base58.Encode(txHash), nil
case sdk.ChainIDMoonbeam:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDNeon:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDTerra2:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDInjective:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDSui:
return base58.Encode(txHash), nil
case sdk.ChainIDAptos:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDArbitrum:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDOptimism:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDXpla:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDBtc:
//TODO: check if this is correct
return hex.EncodeToString(txHash), nil
case sdk.ChainIDBase:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDSei:
return hex.EncodeToString(txHash), nil
case sdk.ChainIDWormchain:
//TODO: check if this is correct
return hex.EncodeToString(txHash), nil
case sdk.ChainIDSepolia,
sdk.ChainIDArbitrumSepolia,
sdk.ChainIDBaseSepolia,
sdk.ChainIDOptimismSepolia,
sdk.ChainIDHolesky:
return hex.EncodeToString(txHash), nil
default:
return hex.EncodeToString(txHash), fmt.Errorf("unknown chain id: %d", chainID)
}
}
// DecodeNativeAddressToHex decodes a native address to hex.
func DecodeNativeAddressToHex(chainID sdk.ChainID, address string) (string, error) {
// Translation rules are based on the chain ID
switch chainID {
// Solana emitter addresses use base58 encoding.
case sdk.ChainIDSolana:
addr, err := base58.Decode(address)
if err != nil {
return "", fmt.Errorf("base58 decoding failed: %w", err)
}
return hex.EncodeToString(addr), nil
// EVM chains use the classic hex, 0x-prefixed encoding.
// Also, Karura and Acala support EVM-compatible addresses, so they're handled here as well.
case sdk.ChainIDEthereum,
sdk.ChainIDBase,
sdk.ChainIDBSC,
sdk.ChainIDPolygon,
sdk.ChainIDAvalanche,
sdk.ChainIDOasis,
sdk.ChainIDAurora,
sdk.ChainIDFantom,
sdk.ChainIDKarura,
sdk.ChainIDAcala,
sdk.ChainIDKlaytn,
sdk.ChainIDCelo,
sdk.ChainIDMoonbeam,
sdk.ChainIDArbitrum,
sdk.ChainIDOptimism,
sdk.ChainIDSepolia,
sdk.ChainIDArbitrumSepolia,
sdk.ChainIDBaseSepolia,
sdk.ChainIDOptimismSepolia,
sdk.ChainIDHolesky:
return address, nil
// Terra addresses use bench32 encoding
case sdk.ChainIDTerra:
return decodeBech32("terra", address)
// Terra2 addresses use bench32 encoding
case sdk.ChainIDTerra2:
return decodeBech32("terra", address)
// Injective addresses use bench32 encoding
case sdk.ChainIDInjective:
return decodeBech32("inj", address)
// Sui addresses use hex encoding
case sdk.ChainIDSui:
return address, nil
// Aptos addresses use hex encoding
case sdk.ChainIDAptos:
return address, nil
// Xpla addresses use bench32 encoding
case sdk.ChainIDXpla:
return decodeBech32("xpla", address)
// Sei addresses use bench32 encoding
case sdk.ChainIDSei:
return decodeBech32("sei", address)
// Algorand addresses use base32 encoding with a trailing checksum.
// We're using the SDK to handle the checksum logic.
case sdk.ChainIDAlgorand:
addr, err := algorand_types.DecodeAddress(address)
if err != nil {
return "", fmt.Errorf("algorand decoding failed: %w", err)
}
return hex.EncodeToString(addr[:]), nil
default:
return "", fmt.Errorf("can't translate emitter address: ChainID=%d not supported", chainID)
}
}
// decodeBech32 is a helper function to decode a bech32 addresses.
func decodeBech32(h, address string) (string, error) {
hrp, decoded, err := bech32.Decode(address, bech32.MaxLengthBIP173)
if err != nil {
return "", fmt.Errorf("bech32 decoding failed: %w", err)
}
if hrp != h {
return "", fmt.Errorf("bech32 decoding failed, invalid prefix: %s", hrp)
}
return hex.EncodeToString(decoded), nil
}
// encodeBech32 is a helper function to encode a bech32 addresses.
func encodeBech32(hrp string, data []byte) (string, error) {
aligned, err := bech32.ConvertBits(data, 8, 5, true)
if err != nil {
return "", fmt.Errorf("bech32 encoding failed: %w", err)
}
return bech32.Encode(hrp, aligned)
}