Skip to content

Commit 4038d0b

Browse files
Merge pull request #62 from manishdait/issue-36
feat: Implement AccountsRepository to interact with Mirror Node REST API
2 parents d41fda2 + eeb3aef commit 4038d0b

File tree

7 files changed

+177
-0
lines changed

7 files changed

+177
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.openelements.hedera.base;
2+
3+
import com.hedera.hashgraph.sdk.AccountId;
4+
import com.openelements.hedera.base.mirrornode.AccountInfo;
5+
import org.jspecify.annotations.NonNull;
6+
7+
import java.util.Objects;
8+
import java.util.Optional;
9+
10+
/**
11+
* Interface for interacting with a Hedera network. This interface provides methods for searching for Accounts.
12+
*/
13+
public interface AccountRepository {
14+
/**
15+
* Return the AccountInfo of a given accountId.
16+
*
17+
* @param accountId id of the account
18+
* @return {@link Optional} containing the found AccountInfo or null
19+
* @throws HederaException if the search fails
20+
*/
21+
Optional<AccountInfo> findById(@NonNull AccountId accountId) throws HederaException;
22+
23+
/**
24+
* Return the AccountInfo of a given accountId.
25+
*
26+
* @param accountId id of the account
27+
* @return {@link Optional} containing the found AccountInfo or null
28+
* @throws HederaException if the search fails
29+
*/
30+
default Optional<AccountInfo> findById(@NonNull String accountId) throws HederaException {
31+
Objects.requireNonNull(accountId, "accountId must not be null");
32+
return findById(AccountId.fromString(accountId));
33+
}
34+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.openelements.hedera.base.implementation;
2+
3+
import com.hedera.hashgraph.sdk.AccountId;
4+
import com.openelements.hedera.base.AccountRepository;
5+
import com.openelements.hedera.base.HederaException;
6+
import com.openelements.hedera.base.mirrornode.AccountInfo;
7+
import com.openelements.hedera.base.mirrornode.MirrorNodeClient;
8+
import org.jspecify.annotations.NonNull;
9+
10+
import java.util.Objects;
11+
import java.util.Optional;
12+
13+
public class AccountRepositoryImpl implements AccountRepository {
14+
private final MirrorNodeClient mirrorNodeClient;
15+
16+
public AccountRepositoryImpl(@NonNull final MirrorNodeClient mirrorNodeClient) {
17+
this.mirrorNodeClient = Objects.requireNonNull(mirrorNodeClient, "mirrorNodeClient must not be null");
18+
}
19+
20+
@Override
21+
public Optional<AccountInfo> findById(@NonNull AccountId accountId) throws HederaException {
22+
return mirrorNodeClient.queryAccount(accountId);
23+
}
24+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.openelements.hedera.base.mirrornode;
2+
3+
import com.hedera.hashgraph.sdk.AccountBalance;
4+
import com.hedera.hashgraph.sdk.AccountId;
5+
import org.jspecify.annotations.NonNull;
6+
7+
import java.util.Objects;
8+
9+
public record AccountInfo(@NonNull AccountId accountId, @NonNull String evmAddress, long balance, long ethereumNonce, long pendingReward) {
10+
public AccountInfo {
11+
Objects.requireNonNull(accountId, "accountId must not be null");
12+
Objects.requireNonNull(evmAddress, "evmAddress must not be null");
13+
}
14+
}

hedera-base/src/main/java/com/openelements/hedera/base/mirrornode/MirrorNodeClient.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,27 @@ default Optional<Nft> queryNftsByAccountAndTokenIdAndSerial(@NonNull String acco
153153
*/
154154
@NonNull
155155
Optional<TransactionInfo> queryTransaction(@NonNull String transactionId) throws HederaException;
156+
157+
/**
158+
* Queries the account information for a specific account ID.
159+
*
160+
* @param accountId the account ID
161+
* @return the account information for the account ID
162+
* @throws HederaException if an error occurs
163+
*/
164+
@NonNull
165+
Optional<AccountInfo> queryAccount(@NonNull AccountId accountId) throws HederaException;
166+
167+
/**
168+
* Queries the account information for a specific account ID.
169+
*
170+
* @param accountId the account ID
171+
* @return the account information for the account ID
172+
* @throws HederaException if an error occurs
173+
*/
174+
@NonNull
175+
default Optional<AccountInfo> queryAccount(@NonNull String accountId) throws HederaException {
176+
Objects.requireNonNull(accountId, "accountId must not be null");
177+
return queryAccount(AccountId.fromString(accountId));
178+
}
156179
}

hedera-spring/src/main/java/com/openelements/hedera/spring/implementation/HederaAutoConfiguration.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.openelements.hedera.base.FileClient;
1010
import com.openelements.hedera.base.NftClient;
1111
import com.openelements.hedera.base.NftRepository;
12+
import com.openelements.hedera.base.AccountRepository;
1213
import com.openelements.hedera.base.SmartContractClient;
1314
import com.openelements.hedera.base.implementation.AccountClientImpl;
1415
import com.openelements.hedera.base.implementation.FileClientImpl;
@@ -17,6 +18,7 @@
1718
import com.openelements.hedera.base.implementation.NftRepositoryImpl;
1819
import com.openelements.hedera.base.implementation.ProtocolLayerClientImpl;
1920
import com.openelements.hedera.base.implementation.SmartContractClientImpl;
21+
import com.openelements.hedera.base.implementation.AccountRepositoryImpl;
2022
import com.openelements.hedera.base.mirrornode.MirrorNodeClient;
2123
import com.openelements.hedera.base.protocol.ProtocolLayerClient;
2224
import java.net.URI;
@@ -200,6 +202,13 @@ NftRepository nftRepository(final MirrorNodeClient mirrorNodeClient) {
200202
return new NftRepositoryImpl(mirrorNodeClient);
201203
}
202204

205+
@Bean
206+
@ConditionalOnProperty(prefix = "spring.hedera", name = "mirrorNodeSupported",
207+
havingValue = "true", matchIfMissing = true)
208+
AccountRepository accountRepository(final MirrorNodeClient mirrorNodeClient) {
209+
return new AccountRepositoryImpl(mirrorNodeClient);
210+
}
211+
203212
@Bean
204213
ContractVerificationClient contractVerificationClient(HederaNetwork hederaNetwork) {
205214
return new ContractVerificationClientImplementation(hederaNetwork);

hedera-spring/src/main/java/com/openelements/hedera/spring/implementation/MirrorNodeClientImpl.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.hedera.hashgraph.sdk.TokenId;
88
import com.openelements.hedera.base.HederaException;
99
import com.openelements.hedera.base.Nft;
10+
import com.openelements.hedera.base.mirrornode.AccountInfo;
1011
import com.openelements.hedera.base.mirrornode.MirrorNodeClient;
1112
import com.openelements.hedera.base.mirrornode.Page;
1213
import com.openelements.hedera.base.mirrornode.TransactionInfo;
@@ -98,6 +99,13 @@ public Optional<TransactionInfo> queryTransaction(@NonNull final String transact
9899
return Optional.of(new TransactionInfo(transactionId));
99100
}
100101

102+
@Override
103+
public @NonNull Optional<AccountInfo> queryAccount(@NonNull AccountId accountId) throws HederaException {
104+
Objects.requireNonNull(accountId, "accountId must not be null");
105+
final JsonNode jsonNode = doGetCall("/api/v1/accounts/" + accountId);
106+
return jsonNodeToOptionalAccountINfo(jsonNode);
107+
}
108+
101109
private JsonNode doGetCall(String path, Map<String, ?> params) throws HederaException {
102110
return doGetCall(builder -> {
103111
UriBuilder uriBuilder = builder.path(path);
@@ -172,6 +180,30 @@ private Nft jsonNodeToNft(final JsonNode jsonNode) throws IOException {
172180
}
173181
}
174182

183+
private @NonNull Optional<AccountInfo> jsonNodeToOptionalAccountINfo(JsonNode jsonNode) throws HederaException {
184+
if (jsonNode == null || !jsonNode.fieldNames().hasNext()) {
185+
return Optional.empty();
186+
}
187+
try {
188+
return Optional.of(jsonNodeToAccountInfo(jsonNode));
189+
} catch (final Exception e) {
190+
throw new HederaException("Error parsing AccountInfo from JSON '" + jsonNode + "'", e);
191+
}
192+
}
193+
194+
private AccountInfo jsonNodeToAccountInfo(JsonNode jsonNode) {
195+
try {
196+
final AccountId accountId = AccountId.fromString(jsonNode.get("account").asText());
197+
final String evmAddress = jsonNode.get("evm_address").asText();
198+
final long ethereumNonce = jsonNode.get("ethereum_nonce").asLong();
199+
final long pendingReward = jsonNode.get("pending_reward").asLong();
200+
final long balance = jsonNode.get("balance").get("balance").asLong();
201+
return new AccountInfo(accountId, evmAddress, balance, ethereumNonce, pendingReward);
202+
} catch (final Exception e) {
203+
throw new IllegalArgumentException("Error parsing NFT from JSON '" + jsonNode + "'", e);
204+
}
205+
}
206+
175207
private List<Nft> getNfts(final JsonNode jsonNode) {
176208
if (!jsonNode.has("nfts")) {
177209
return List.of();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.openelements.hedera.spring.test;
2+
3+
import com.hedera.hashgraph.sdk.AccountId;
4+
import com.openelements.hedera.base.Account;
5+
import com.openelements.hedera.base.AccountClient;
6+
import com.openelements.hedera.base.AccountRepository;
7+
import com.openelements.hedera.base.mirrornode.AccountInfo;
8+
import org.junit.jupiter.api.Assertions;
9+
import org.junit.jupiter.api.Test;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.boot.test.context.SpringBootTest;
12+
13+
import java.util.Optional;
14+
15+
@SpringBootTest(classes = TestConfig.class)
16+
public class AccountRepositoryTest {
17+
@Autowired
18+
private AccountRepository accountRepository;
19+
20+
@Autowired
21+
private HederaTestUtils hederaTestUtils;
22+
23+
@Autowired
24+
private AccountClient accountClient;
25+
26+
@Test
27+
void findById() throws Exception {
28+
//given
29+
final Account account = accountClient.createAccount();
30+
final AccountId newOwner = account.accountId();
31+
hederaTestUtils.waitForMirrorNodeRecords();
32+
33+
//when
34+
final Optional<AccountInfo> result = accountRepository.findById(newOwner);
35+
36+
//then
37+
Assertions.assertNotNull(result);
38+
Assertions.assertTrue(result.isPresent());
39+
40+
}
41+
}

0 commit comments

Comments
 (0)