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 }