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  }