github.com/hashgraph/hedera-sdk-go/v2@v2.48.0/account_id.go (about)

     1  package hedera
     2  
     3  /*-
     4   *
     5   * Hedera Go SDK
     6   *
     7   * Copyright (C) 2020 - 2022 Hedera Hashgraph, LLC
     8   *
     9   * Licensed under the Apache License, Version 2.0 (the "License");
    10   * you may not use this file except in compliance with the License.
    11   * You may obtain a copy of the License at
    12   *
    13   *      http://www.apache.org/licenses/LICENSE-2.0
    14   *
    15   * Unless required by applicable law or agreed to in writing, software
    16   * distributed under the License is distributed on an "AS IS" BASIS,
    17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    18   * See the License for the specific language governing permissions and
    19   * limitations under the License.
    20   *
    21   */
    22  
    23  import (
    24  	"encoding/hex"
    25  	"encoding/json"
    26  	"fmt"
    27  	"net/http"
    28  	"strconv"
    29  	"strings"
    30  
    31  	"github.com/pkg/errors"
    32  
    33  	"github.com/hashgraph/hedera-protobufs-go/services"
    34  	protobuf "google.golang.org/protobuf/proto"
    35  )
    36  
    37  // AccountID is the ID for a Hedera account
    38  type AccountID struct {
    39  	Shard           uint64
    40  	Realm           uint64
    41  	Account         uint64
    42  	AliasKey        *PublicKey
    43  	AliasEvmAddress *[]byte
    44  	checksum        *string
    45  }
    46  
    47  type _AccountIDs struct { //nolint
    48  	accountIDs []AccountID
    49  }
    50  
    51  // AccountIDFromString constructs an AccountID from a string formatted as
    52  // `Shard.Realm.Account` (for example "0.0.3")
    53  func AccountIDFromString(data string) (AccountID, error) {
    54  	shard, realm, num, checksum, alias, aliasEvmAddress, err := _AccountIDFromString(data)
    55  	if err != nil {
    56  		return AccountID{}, err
    57  	}
    58  
    59  	if num == -1 {
    60  		if alias != nil {
    61  			return AccountID{
    62  				Shard:           uint64(shard),
    63  				Realm:           uint64(realm),
    64  				Account:         0,
    65  				AliasKey:        alias,
    66  				AliasEvmAddress: nil,
    67  				checksum:        checksum,
    68  			}, nil
    69  		}
    70  
    71  		return AccountID{
    72  			Shard:           uint64(shard),
    73  			Realm:           uint64(realm),
    74  			Account:         0,
    75  			AliasKey:        nil,
    76  			AliasEvmAddress: aliasEvmAddress,
    77  			checksum:        checksum,
    78  		}, nil
    79  	}
    80  
    81  	return AccountID{
    82  		Shard:           uint64(shard),
    83  		Realm:           uint64(realm),
    84  		Account:         uint64(num),
    85  		AliasKey:        nil,
    86  		AliasEvmAddress: nil,
    87  		checksum:        checksum,
    88  	}, nil
    89  }
    90  
    91  // AccountIDFromEvmAddress constructs an AccountID from a string formatted as 0.0.<evm address>
    92  func AccountIDFromEvmAddress(shard uint64, realm uint64, aliasEvmAddress string) (AccountID, error) {
    93  	temp, err := hex.DecodeString(aliasEvmAddress)
    94  	if err != nil {
    95  		return AccountID{}, err
    96  	}
    97  	return AccountID{
    98  		Shard:           shard,
    99  		Realm:           realm,
   100  		Account:         0,
   101  		AliasEvmAddress: &temp,
   102  		checksum:        nil,
   103  	}, nil
   104  }
   105  
   106  // Returns an AccountID with EvmPublic address for the use of HIP-583
   107  func AccountIDFromEvmPublicAddress(s string) (AccountID, error) {
   108  	return AccountIDFromString(s)
   109  }
   110  
   111  // AccountIDFromSolidityAddress constructs an AccountID from a string
   112  // representation of a _Solidity address
   113  func AccountIDFromSolidityAddress(s string) (AccountID, error) {
   114  	shard, realm, account, err := _IdFromSolidityAddress(s)
   115  	if err != nil {
   116  		return AccountID{}, err
   117  	}
   118  
   119  	return AccountID{
   120  		Shard:    shard,
   121  		Realm:    realm,
   122  		Account:  account,
   123  		checksum: nil,
   124  	}, nil
   125  }
   126  
   127  // Verify that the client has a valid checksum.
   128  func (id *AccountID) ValidateChecksum(client *Client) error {
   129  	if id.AliasKey != nil {
   130  		return errors.New("Account ID contains alias key, unable to validate")
   131  	}
   132  	if !id._IsZero() && client != nil {
   133  		var tempChecksum _ParseAddressResult
   134  		var err error
   135  		tempChecksum, err = _ChecksumParseAddress(client.GetLedgerID(), fmt.Sprintf("%d.%d.%d", id.Shard, id.Realm, id.Account))
   136  		if err != nil {
   137  			return err
   138  		}
   139  		err = _ChecksumVerify(tempChecksum.status)
   140  		if err != nil {
   141  			return err
   142  		}
   143  		if id.checksum == nil {
   144  			return errChecksumMissing
   145  		}
   146  		if tempChecksum.correctChecksum != *id.checksum {
   147  			networkName := NetworkNameOther
   148  			if client.network.ledgerID != nil {
   149  				networkName, _ = client.network.ledgerID.ToNetworkName()
   150  			}
   151  			return errors.New(fmt.Sprintf("network mismatch or wrong checksum given, given checksum: %s, correct checksum %s, network: %s",
   152  				*id.checksum,
   153  				tempChecksum.correctChecksum,
   154  				networkName))
   155  		}
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  // Deprecated - use ValidateChecksum instead
   162  func (id *AccountID) Validate(client *Client) error {
   163  	return id.ValidateChecksum(client)
   164  }
   165  
   166  // String returns the string representation of an AccountID in
   167  // `Shard.Realm.Account` (for example "0.0.3")
   168  func (id AccountID) String() string {
   169  	if id.AliasKey != nil {
   170  		return fmt.Sprintf("%d.%d.%s", id.Shard, id.Realm, id.AliasKey.String())
   171  	} else if id.AliasEvmAddress != nil {
   172  		return fmt.Sprintf("%d.%d.%s", id.Shard, id.Realm, hex.EncodeToString(*id.AliasEvmAddress))
   173  	}
   174  
   175  	return fmt.Sprintf("%d.%d.%d", id.Shard, id.Realm, id.Account)
   176  }
   177  
   178  // ToStringWithChecksum returns the string representation of an AccountID in
   179  // `Shard.Realm.Account-checksum` (for example "0.0.3-sdaf")
   180  func (id AccountID) ToStringWithChecksum(client *Client) (string, error) {
   181  	if id.AliasKey != nil {
   182  		return "", errors.New("Account ID contains alias key, unable get checksum")
   183  	}
   184  	if client.GetNetworkName() == nil && client.GetLedgerID() == nil {
   185  		return "", errNetworkNameMissing
   186  	}
   187  	var checksum _ParseAddressResult
   188  	var err error
   189  	if client.network.ledgerID != nil {
   190  		checksum, err = _ChecksumParseAddress(client.GetLedgerID(), fmt.Sprintf("%d.%d.%d", id.Shard, id.Realm, id.Account))
   191  	}
   192  	if err != nil {
   193  		return "", err
   194  	}
   195  
   196  	return fmt.Sprintf("%d.%d.%d-%s", id.Shard, id.Realm, id.Account, checksum.correctChecksum), nil
   197  }
   198  
   199  // GetChecksum Retrieve just the checksum
   200  func (id AccountID) GetChecksum() *string {
   201  	return id.checksum
   202  }
   203  
   204  // ToSolidityAddress returns the string representation of the AccountID as a
   205  // _Solidity address.
   206  func (id AccountID) ToSolidityAddress() string {
   207  	return _IdToSolidityAddress(id.Shard, id.Realm, id.Account)
   208  }
   209  
   210  func (id AccountID) _ToProtobuf() *services.AccountID {
   211  	resultID := &services.AccountID{
   212  		ShardNum: int64(id.Shard),
   213  		RealmNum: int64(id.Realm),
   214  	}
   215  	if id.AliasKey != nil {
   216  		data, _ := protobuf.Marshal(id.AliasKey._ToProtoKey())
   217  		resultID.Account = &services.AccountID_Alias{
   218  			Alias: data,
   219  		}
   220  
   221  		return resultID
   222  	} else if id.AliasEvmAddress != nil {
   223  		resultID.Account = &services.AccountID_Alias{
   224  			Alias: *id.AliasEvmAddress,
   225  		}
   226  
   227  		return resultID
   228  	}
   229  
   230  	resultID.Account = &services.AccountID_AccountNum{
   231  		AccountNum: int64(id.Account),
   232  	}
   233  
   234  	return resultID
   235  }
   236  
   237  // UnmarshalJSON implements the encoding.JSON interface.
   238  func (id *AccountID) UnmarshalJSON(data []byte) error {
   239  	accountID, err := AccountIDFromString(strings.Replace(string(data), "\"", "", 2))
   240  
   241  	if err != nil {
   242  		return err
   243  	}
   244  
   245  	*id = accountID
   246  
   247  	return nil
   248  }
   249  
   250  func _AccountIDFromProtobuf(accountID *services.AccountID) *AccountID {
   251  	if accountID == nil {
   252  		return nil
   253  	}
   254  	resultAccountID := &AccountID{
   255  		Shard: uint64(accountID.ShardNum),
   256  		Realm: uint64(accountID.RealmNum),
   257  	}
   258  
   259  	switch t := accountID.Account.(type) {
   260  	case *services.AccountID_Alias:
   261  		pb := services.Key{}
   262  		_ = protobuf.Unmarshal(t.Alias, &pb)
   263  		initialKey, err := _KeyFromProtobuf(&pb)
   264  		if err != nil && t.Alias != nil {
   265  			resultAccountID.Account = 0
   266  			resultAccountID.AliasEvmAddress = &t.Alias
   267  			return resultAccountID
   268  		}
   269  		if evm, ok := pb.Key.(*services.Key_ECDSASecp256K1); ok && len(evm.ECDSASecp256K1) == 20 {
   270  			resultAccountID.Account = 0
   271  			resultAccountID.AliasEvmAddress = &evm.ECDSASecp256K1
   272  			return resultAccountID
   273  		}
   274  		switch t2 := initialKey.(type) {
   275  		case PublicKey:
   276  			resultAccountID.Account = 0
   277  			resultAccountID.AliasKey = &t2
   278  			return resultAccountID
   279  		default:
   280  			return &AccountID{}
   281  		}
   282  	case *services.AccountID_AccountNum:
   283  		resultAccountID.Account = uint64(t.AccountNum)
   284  		resultAccountID.AliasKey = nil
   285  		return resultAccountID
   286  	default:
   287  		return &AccountID{}
   288  	}
   289  }
   290  
   291  // IsZero returns true if this AccountID is the zero-value
   292  func (id AccountID) IsZero() bool {
   293  	return id._IsZero()
   294  }
   295  
   296  func (id AccountID) _IsZero() bool {
   297  	return id.Shard == 0 && id.Realm == 0 && id.Account == 0 && id.AliasKey == nil
   298  }
   299  
   300  // Equals returns true if this AccountID and the given AccountID are identical
   301  func (id AccountID) Equals(other AccountID) bool {
   302  	return id._Equals(other)
   303  }
   304  
   305  func (id AccountID) _Equals(other AccountID) bool {
   306  	initialAlias := ""
   307  	otherAlias := ""
   308  	if id.AliasKey != nil && other.AliasKey != nil {
   309  		initialAlias = id.AliasKey.String()
   310  		otherAlias = other.AliasKey.String()
   311  	}
   312  
   313  	return id.Shard == other.Shard && id.Realm == other.Realm && id.Account == other.Account && initialAlias == otherAlias
   314  }
   315  
   316  // ToBytes returns the wire-format encoding of AccountID
   317  func (id AccountID) ToBytes() []byte {
   318  	data, err := protobuf.Marshal(id._ToProtobuf())
   319  	if err != nil {
   320  		return make([]byte, 0)
   321  	}
   322  
   323  	return data
   324  }
   325  
   326  // AccountIDFromBytes converts wire-format encoding to Account ID
   327  func AccountIDFromBytes(data []byte) (AccountID, error) {
   328  	if data == nil {
   329  		return AccountID{}, errByteArrayNull
   330  	}
   331  	pb := services.AccountID{}
   332  	err := protobuf.Unmarshal(data, &pb)
   333  	if err != nil {
   334  		return AccountID{}, err
   335  	}
   336  
   337  	return *_AccountIDFromProtobuf(&pb), nil
   338  }
   339  
   340  type PopulateType int
   341  
   342  const (
   343  	Account PopulateType = iota
   344  	EvmAddress
   345  )
   346  
   347  func (id *AccountID) _MirrorNodeRequest(client *Client, populateType string) (map[string]interface{}, error) {
   348  	if client.mirrorNetwork == nil || len(client.GetMirrorNetwork()) == 0 {
   349  		return nil, errors.New("mirror node is not set")
   350  	}
   351  
   352  	mirrorUrl := client.GetMirrorNetwork()[0]
   353  	index := strings.Index(mirrorUrl, ":")
   354  	if index == -1 {
   355  		return nil, errors.New("invalid mirrorUrl format")
   356  	}
   357  	mirrorUrl = mirrorUrl[:index]
   358  
   359  	var url string
   360  	protocol := "https"
   361  	port := ""
   362  
   363  	if client.GetLedgerID().String() == "" {
   364  		protocol = "http"
   365  		port = ":5551"
   366  	}
   367  
   368  	if populateType == "account" {
   369  		url = fmt.Sprintf("%s://%s%s/api/v1/accounts/%s", protocol, mirrorUrl, port, hex.EncodeToString(*id.AliasEvmAddress))
   370  	} else {
   371  		url = fmt.Sprintf("%s://%s%s/api/v1/accounts/%s", protocol, mirrorUrl, port, id.String())
   372  	}
   373  
   374  	resp, err := http.Get(url) // #nosec
   375  	if err != nil {
   376  		return nil, err
   377  	}
   378  	defer resp.Body.Close()
   379  
   380  	var result map[string]interface{}
   381  	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
   382  		return nil, err
   383  	}
   384  
   385  	return result, nil
   386  }
   387  
   388  // PopulateAccount gets the actual `Account` field of the `AccountId` from the Mirror Node.
   389  // Should be used after generating `AccountId.FromEvmAddress()` because it sets the `Account` field to `0`
   390  // automatically since there is no connection between the `Account` and the `evmAddress`
   391  func (id *AccountID) PopulateAccount(client *Client) error {
   392  	result, err := id._MirrorNodeRequest(client, "account")
   393  	if err != nil {
   394  		return err
   395  	}
   396  
   397  	mirrorAccountId, ok := result["account"].(string)
   398  	if !ok {
   399  		return errors.New("unexpected response format")
   400  	}
   401  
   402  	numStr := mirrorAccountId[strings.LastIndex(mirrorAccountId, ".")+1:]
   403  	num, err := strconv.ParseInt(numStr, 10, 64)
   404  	if err != nil {
   405  		return err
   406  	}
   407  	id.Account = uint64(num)
   408  	return nil
   409  }
   410  
   411  // PopulateEvmAddress gets the actual `AliasEvmAddress` field of the `AccountId` from the Mirror Node.
   412  func (id *AccountID) PopulateEvmAddress(client *Client) error {
   413  	result, err := id._MirrorNodeRequest(client, "evmAddress")
   414  	if err != nil {
   415  		return err
   416  	}
   417  
   418  	mirrorEvmAddress, ok := result["evm_address"].(string)
   419  	if !ok {
   420  		return errors.New("unexpected response format")
   421  	}
   422  
   423  	mirrorEvmAddress = strings.TrimPrefix(mirrorEvmAddress, "0x")
   424  	asd, err := hex.DecodeString(mirrorEvmAddress)
   425  	if err != nil {
   426  		return err
   427  	}
   428  	id.AliasEvmAddress = &asd
   429  	return nil
   430  }
   431  
   432  // Compare returns 0 if the two AccountID are identical, -1 if not.
   433  func (id AccountID) Compare(given AccountID) int {
   434  	if id.Shard > given.Shard { //nolint
   435  		return 1
   436  	} else if id.Shard < given.Shard {
   437  		return -1
   438  	}
   439  
   440  	if id.Realm > given.Realm { //nolint
   441  		return 1
   442  	} else if id.Realm < given.Realm {
   443  		return -1
   444  	}
   445  
   446  	if id.AliasKey != nil && given.AliasKey != nil {
   447  		if id.AliasKey.String() > given.AliasKey.String() { //nolint
   448  			return 1
   449  		} else if id.AliasKey.String() < given.AliasKey.String() {
   450  			return -1
   451  		}
   452  	}
   453  
   454  	if id.AliasEvmAddress != nil && given.AliasEvmAddress != nil {
   455  		originalEvmAddress := hex.EncodeToString(*id.AliasEvmAddress)
   456  		givenEvmAddress := hex.EncodeToString(*given.AliasEvmAddress)
   457  		if originalEvmAddress > givenEvmAddress { //nolint
   458  			return 1
   459  		} else if originalEvmAddress < givenEvmAddress {
   460  			return -1
   461  		}
   462  	}
   463  
   464  	if id.Account > given.Account { //nolint
   465  		return 1
   466  	} else if id.Account < given.Account {
   467  		return -1
   468  	} else {
   469  		return 0
   470  	}
   471  }
   472  
   473  // Len returns the number of elements in the collection.
   474  func (accountIDs _AccountIDs) Len() int { //nolint
   475  	return len(accountIDs.accountIDs)
   476  }
   477  
   478  func (accountIDs _AccountIDs) Swap(i, j int) { //nolint
   479  	accountIDs.accountIDs[i], accountIDs.accountIDs[j] = accountIDs.accountIDs[j], accountIDs.accountIDs[i]
   480  }
   481  
   482  func (accountIDs _AccountIDs) Less(i, j int) bool { //nolint
   483  	if accountIDs.accountIDs[i].Compare(accountIDs.accountIDs[j]) < 0 { //nolint
   484  		return true
   485  	}
   486  
   487  	return false
   488  }