From 0af328bd4c79318885ad1adf94f305455f2e4cba Mon Sep 17 00:00:00 2001 From: Asen Prodanov Date: Fri, 11 Aug 2023 16:04:44 +0300 Subject: [PATCH] feat(903): associate account with hedera token --- app/clients/hedera/mirror-node/client.go | 31 ++++++++++- .../mirror-node/model/account/account.go | 35 +++++++++++++ app/domain/client/mirror-node.go | 2 + e2e/e2e_test.go | 2 +- scripts/token/native/create/cmd/create.go | 52 +++++++++++++++++++ .../mocks/client/hedera_mirror_client_mock.go | 9 ++++ 6 files changed, 129 insertions(+), 2 deletions(-) diff --git a/app/clients/hedera/mirror-node/client.go b/app/clients/hedera/mirror-node/client.go index b238fcb6b..24bd66e81 100644 --- a/app/clients/hedera/mirror-node/client.go +++ b/app/clients/hedera/mirror-node/client.go @@ -359,7 +359,36 @@ func (c Client) AccountExists(accountID hedera.AccountID) bool { return c.query(accountQuery, accountID.String()) } -// GetAccount retrieves an account entity by its id +// GetAccount retrieves an account entity by its id or public key +func (c Client) GetAccountByPublicKey(publicKey string) (*account.AccountsQueryResponse, error) { + mirrorNodeApiTransactionAddress := fmt.Sprintf("%s%s", c.mirrorAPIAddress, "accounts") + + query := fmt.Sprintf("%s?account.publickey=%s", + mirrorNodeApiTransactionAddress, + publicKey) + + httpResponse, e := c.get(query) + if e != nil { + return nil, e + } + if httpResponse.StatusCode >= 400 { + return nil, fmt.Errorf(`Failed to execute query: [%s]. Error: [%s]`, query, query) + } + + bodyBytes, e := readResponseBody(httpResponse) + if e != nil { + return nil, e + } + + var response *account.AccountsQueryResponse + e = json.Unmarshal(bodyBytes, &response) + if e != nil { + return nil, e + } + + return response, nil +} + func (c Client) GetAccount(accountID string) (*account.AccountsResponse, error) { mirrorNodeApiTransactionAddress := fmt.Sprintf("%s%s", c.mirrorAPIAddress, "accounts") query := fmt.Sprintf("%s/%s", diff --git a/app/clients/hedera/mirror-node/model/account/account.go b/app/clients/hedera/mirror-node/model/account/account.go index 2e0e0de5a..9d1ca3ea1 100644 --- a/app/clients/hedera/mirror-node/model/account/account.go +++ b/app/clients/hedera/mirror-node/model/account/account.go @@ -25,6 +25,41 @@ type ( Account string `json:"account"` Balance Balance `json:"balance"` } + + // AccountsQueryResponse struct used by the Hedera Mirror node REST API to return information regarding a given Account + AccountsQueryResponse struct { + Accounts []struct { + Account string `json:"account"` + Alias string `json:"alias"` + AutoRenewPeriod interface{} `json:"auto_renew_period"` + Balance struct { + Timestamp string `json:"timestamp"` + Balance int `json:"balance"` + Tokens []struct { + TokenId string `json:"token_id"` + Balance int `json:"balance"` + } `json:"tokens"` + } `json:"balance"` + CreatedTimestamp string `json:"created_timestamp"` + DeclineReward bool `json:"decline_reward"` + Deleted bool `json:"deleted"` + EthereumNonce int `json:"ethereum_nonce"` + EvmAddress string `json:"evm_address"` + ExpiryTimestamp interface{} `json:"expiry_timestamp"` + Key interface{} `json:"key"` + MaxAutomaticTokenAssociations int `json:"max_automatic_token_associations"` + Memo string `json:"memo"` + PendingReward int `json:"pending_reward"` + ReceiverSigRequired bool `json:"receiver_sig_required"` + StakedAccountId interface{} `json:"staked_account_id"` + StakedNodeId int `json:"staked_node_id"` + StakePeriodStart string `json:"stake_period_start"` + } `json:"accounts"` + Links struct { + Next string `json:"next"` + } `json:"links"` + } + // Balance struct used by the Hedera Mirror node REST API to return information // regarding a given Account Balance struct { diff --git a/app/domain/client/mirror-node.go b/app/domain/client/mirror-node.go index cf487a7b6..183f2a86d 100644 --- a/app/domain/client/mirror-node.go +++ b/app/domain/client/mirror-node.go @@ -71,6 +71,8 @@ type MirrorNode interface { AccountExists(accountID hedera.AccountID) bool // GetAccount gets the account data by ID. GetAccount(accountID string) (*account.AccountsResponse, error) + // GetAccountByPublicKey gets the account data by public key + GetAccountByPublicKey(publicKey string) (*account.AccountsQueryResponse, error) // GetToken gets the token data by ID. GetToken(tokenID string) (*token.TokenResponse, error) // TopicExists sends a query to check whether a specific topic exists. If the query returns a status != 200, the function returns a false value diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index cbcadf710..75c41568a 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -21,6 +21,7 @@ import ( "database/sql" "encoding/base64" "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "math/big" "strconv" "testing" @@ -43,7 +44,6 @@ import ( "github.com/limechain/hedera-eth-bridge-validator/e2e/helper/verify" "github.com/limechain/hedera-eth-bridge-validator/e2e/setup" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/hashgraph/hedera-sdk-go/v2" ) diff --git a/scripts/token/native/create/cmd/create.go b/scripts/token/native/create/cmd/create.go index b88bda27d..0b66aff04 100644 --- a/scripts/token/native/create/cmd/create.go +++ b/scripts/token/native/create/cmd/create.go @@ -20,12 +20,19 @@ import ( "flag" "fmt" "github.com/hashgraph/hedera-sdk-go/v2" + mirrorNode "github.com/limechain/hedera-eth-bridge-validator/app/clients/hedera/mirror-node" + "github.com/limechain/hedera-eth-bridge-validator/config" "github.com/limechain/hedera-eth-bridge-validator/scripts/client" "github.com/limechain/hedera-eth-bridge-validator/scripts/token/associate" "github.com/limechain/hedera-eth-bridge-validator/scripts/token/native/create" "strings" ) +const ( + HederaMainnetNetworkId = 295 + HederaTestnetNetworkId = 296 +) + func main() { privateKey := flag.String("privateKey", "0x0", "Hedera Private Key") accountID := flag.String("accountID", "0.0", "Hedera Account ID") @@ -51,6 +58,18 @@ func main() { fmt.Println("-----------Start-----------") client := client.Init(*privateKey, *accountID, *network) + mirrorNodeConfigByNetwork := map[uint64]config.MirrorNode{ + HederaMainnetNetworkId: { + ClientAddress: "mainnet-public.mirrornode.hedera.com/:443", + ApiAddress: "https://mainnet-public.mirrornode.hedera.com/api/v1/", + }, + HederaTestnetNetworkId: { + ClientAddress: "hcs.testnet.mirrornode.hedera.com:5600", + ApiAddress: "https://testnet.mirrornode.hedera.com/api/v1/", + }, + } + + var hederaNetworkId uint64 if *network != "testnet" && *setSupplyKey { var confirmation string fmt.Printf("Network is set to [%s] and setSupplyKey is set to [%v]. Are you sure you what to proceed?\n", *network, *setSupplyKey) @@ -59,16 +78,23 @@ func main() { if confirmation != "Y" { panic("Exiting") } + hederaNetworkId = HederaMainnetNetworkId + } else { + hederaNetworkId = HederaTestnetNetworkId } + mirrorNodeClient := mirrorNode.NewClient(mirrorNodeConfigByNetwork[hederaNetworkId]) membersSlice := strings.Split(*memberPrKeys, ",") var custodianKey []hedera.PrivateKey + var membersPublicKey []hedera.PublicKey for i := 0; i < len(membersSlice); i++ { privateKeyFromStr, err := hedera.PrivateKeyFromString(membersSlice[i]) if err != nil { panic(err) } + + membersPublicKey = append(membersPublicKey, privateKeyFromStr.PublicKey()) custodianKey = append(custodianKey, privateKeyFromStr) } @@ -97,4 +123,30 @@ func main() { } fmt.Println("Token ID:", tokenId) fmt.Println("Associate transaction status:", receipt.Status) + + // associate token with members + for _, memberPrKey := range membersPublicKey { + accounts, err := mirrorNodeClient.GetAccountByPublicKey(memberPrKey.String()) + if err != nil { + panic(fmt.Errorf("cannot obtain account by public key: %w", err)) + } + + if len(accounts.Accounts) == 0 { + panic("cannot find account by public key") + } else if len(accounts.Accounts) != 1 { + panic("multiple accounts found for public key - " + memberPrKey.String()) + } + + hAccount, err := hedera.AccountIDFromString(accounts.Accounts[0].Account) + if err != nil { + panic(fmt.Errorf("cannot convert string to hedera account: %w", err)) + } + + receipt, err := associate.TokenToAccount(client, *tokenId, hAccount) + if err != nil { + panic(fmt.Errorf("failed to associate token to account: %w", err)) + } + fmt.Printf("Account[%s] associated with token[%s], tx status: %s\n", + hAccount.String(), tokenId.String(), receipt.Status) + } } diff --git a/test/mocks/client/hedera_mirror_client_mock.go b/test/mocks/client/hedera_mirror_client_mock.go index 7ff4fccd7..35e97f91b 100644 --- a/test/mocks/client/hedera_mirror_client_mock.go +++ b/test/mocks/client/hedera_mirror_client_mock.go @@ -181,6 +181,15 @@ func (m *MockHederaMirror) AccountExists(accountID hedera.AccountID) bool { return args.Get(0).(bool) } +func (m *MockHederaMirror) GetAccountByPublicKey(publicKey string) (*account.AccountsQueryResponse, error) { + args := m.Called(publicKey) + + if args.Get(1) == nil { + return args.Get(0).(*account.AccountsQueryResponse), nil + } + return args.Get(0).(*account.AccountsQueryResponse), args.Get(1).(error) +} + func (m *MockHederaMirror) GetAccount(accountID string) (*account.AccountsResponse, error) { args := m.Called(accountID)