Skip to content

Commit 79b0c1a

Browse files
Task/ethereum app (#30)
add ethereum integration
1 parent c890792 commit 79b0c1a

File tree

95 files changed

+659
-24
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+659
-24
lines changed

Diff for: .github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
run: ./gradlew build jacocoTestReport --info
3030

3131
- name: Verify JaCoCo Report Exists for app
32-
run: ls -l app/build/reports/jacoco/test/jacocoTestReport.xml
32+
run: ls -l solana-app/build/reports/jacoco/test/jacocoTestReport.xml
3333

3434
- name: Verify JaCoCo Report Exists for api
3535
run: ls -l api/build/reports/jacoco/test/jacocoTestReport.xml
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.blockchain.api.model;
2+
3+
import java.math.BigDecimal;
4+
import lombok.Builder;
5+
import lombok.Data;
6+
import lombok.extern.jackson.Jacksonized;
7+
8+
@Data
9+
@Builder
10+
@Jacksonized
11+
public class Address {
12+
private String address;
13+
private String tag;
14+
private BigDecimal balance;
15+
private Object state;
16+
}

Diff for: ethereum-app/.gitignore

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.gradle
2+
build/
3+
!gradle/wrapper/gradle-wrapper.jar
4+
!**/src/main/**/build/
5+
!**/src/test/**/build/
6+
7+
### IntelliJ IDEA ###
8+
.idea/modules.xml
9+
.idea/jarRepositories.xml
10+
.idea/compiler.xml
11+
.idea/libraries/
12+
*.iws
13+
*.iml
14+
*.ipr
15+
out/
16+
!**/src/main/**/out/
17+
!**/src/test/**/out/
18+
19+
### Eclipse ###
20+
.apt_generated
21+
.classpath
22+
.factorypath
23+
.project
24+
.settings
25+
.springBeans
26+
.sts4-cache
27+
bin/
28+
!**/src/main/**/bin/
29+
!**/src/test/**/bin/
30+
31+
### NetBeans ###
32+
/nbproject/private/
33+
/nbbuild/
34+
/dist/
35+
/nbdist/
36+
/.nb-gradle/
37+
38+
### VS Code ###
39+
.vscode/
40+
41+
### Mac OS ###
42+
.DS_Store

Diff for: ethereum-app/build.gradle

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
plugins {
2+
id 'org.springframework.boot'
3+
}
4+
5+
springBoot {
6+
buildInfo() {
7+
properties {
8+
group = rootProject.group
9+
artifact = rootProject.name
10+
version = rootProject.version
11+
}
12+
}
13+
}
14+
15+
dependencies {
16+
implementation project(':api')
17+
implementation "org.web3j:core:${web3jVersion}"
18+
implementation 'org.springframework.boot:spring-boot-starter-validation'
19+
implementation 'org.springframework.boot:spring-boot-starter-web'
20+
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'
21+
implementation 'commons-cli:commons-cli:1.9.0'
22+
testImplementation "org.wiremock:wiremock-standalone:${wiremockVersion}"
23+
}
24+
25+
compileJava {
26+
options.compilerArgs += [
27+
'-Amapstruct.defaultComponentModel=spring',
28+
'-Amapstruct.defaultInjectionStrategy=constructor'
29+
]
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.blockchain;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.springframework.boot.SpringApplication;
5+
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
7+
@SpringBootApplication
8+
@Slf4j
9+
public class EthereumApiApplication {
10+
11+
public static void main(String[] args) {
12+
SpringApplication.run(EthereumApiApplication.class, args);
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.blockchain.api.application.controller;
2+
3+
import com.blockchain.api.application.exception.InvalidAddressException;
4+
import com.blockchain.api.application.mapper.BalanceDtoMapper;
5+
import com.blockchain.api.domain.service.balance.BalanceService;
6+
import com.blockchain.api.model.ApiError;
7+
import com.blockchain.api.model.BalanceDto;
8+
import io.swagger.v3.oas.annotations.Operation;
9+
import io.swagger.v3.oas.annotations.media.Content;
10+
import io.swagger.v3.oas.annotations.media.Schema;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
12+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
13+
import io.swagger.v3.oas.annotations.tags.Tag;
14+
import lombok.RequiredArgsConstructor;
15+
import lombok.extern.slf4j.Slf4j;
16+
import org.springframework.web.bind.annotation.GetMapping;
17+
import org.springframework.web.bind.annotation.PathVariable;
18+
import org.springframework.web.bind.annotation.RequestMapping;
19+
import org.springframework.web.bind.annotation.RestController;
20+
import org.web3j.crypto.WalletUtils;
21+
22+
@Slf4j
23+
@RestController
24+
@RequiredArgsConstructor
25+
@RequestMapping("/api/v1/balances")
26+
@Tag(
27+
name = "Ethereum Balance",
28+
description = "Operations related to fetching Ethereum account balances")
29+
public class EthereumBalanceController {
30+
31+
private final BalanceDtoMapper mapper;
32+
private final BalanceService balanceService;
33+
34+
@Operation(
35+
summary = "Get Ethereum Balance",
36+
description = "Fetch the balance of a Ethereum account by its address")
37+
@ApiResponses({
38+
@ApiResponse(
39+
responseCode = "200",
40+
description = "Successfully retrieved balance",
41+
content =
42+
@Content(
43+
mediaType = "application/json",
44+
schema = @Schema(implementation = BalanceDto.class))),
45+
@ApiResponse(
46+
responseCode = "400",
47+
description = "Invalid address format",
48+
content =
49+
@Content(
50+
mediaType = "application/json",
51+
schema = @Schema(implementation = ApiError.class))),
52+
@ApiResponse(
53+
responseCode = "500",
54+
description = "Internal server error",
55+
content =
56+
@Content(
57+
mediaType = "application/json",
58+
schema = @Schema(implementation = ApiError.class)))
59+
})
60+
@GetMapping("/{address}")
61+
public BalanceDto getBalance(@PathVariable("address") String address) {
62+
log.info("Fetching balance for address: {}", address);
63+
if (!WalletUtils.isValidAddress(address)) {
64+
throw InvalidAddressException.withAddress(address);
65+
}
66+
return mapper.mapToDto(address, balanceService.getEthereumBalance(address).toString());
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.blockchain.api.application.controller;
2+
3+
import com.blockchain.api.domain.service.transfer.TransferService;
4+
import com.blockchain.api.model.TransferRequestDto;
5+
import io.swagger.v3.oas.annotations.tags.Tag;
6+
import lombok.RequiredArgsConstructor;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
@Slf4j
12+
@RestController
13+
@RequiredArgsConstructor
14+
@RequestMapping("/api/v1/transfers")
15+
@Tag(
16+
name = "Ethereum balance transfer",
17+
description = "Operations related to Ethereum account balances transfer")
18+
public class TransferController {
19+
20+
private final TransferService transferService;
21+
22+
public void transferEthereumBalance(TransferRequestDto transferRequestDto) {
23+
// transferService.transfer(address);
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.blockchain.api.application.exception;
2+
3+
public class NonceRetrievalException extends RuntimeException {
4+
5+
public NonceRetrievalException(String message) {
6+
super(message);
7+
}
8+
9+
public static NonceRetrievalException withAddress(String address) {
10+
return new NonceRetrievalException("Unable to retrieve nonce for address: " + address);
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.blockchain.api.domain.service.balance;
2+
3+
import java.math.BigInteger;
4+
import java.util.concurrent.CompletableFuture;
5+
6+
public interface BalanceClient {
7+
CompletableFuture<BigInteger> getBalance(String address);
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.blockchain.api.domain.service.balance;
2+
3+
import java.math.BigInteger;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.stereotype.Service;
7+
8+
@Slf4j
9+
@Service
10+
@RequiredArgsConstructor
11+
public class BalanceService {
12+
13+
private final BalanceClient balanceClient;
14+
15+
public BigInteger getEthereumBalance(String address) {
16+
return balanceClient.getBalance(address).join();
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.blockchain.api.domain.service.transfer;
2+
3+
import java.math.BigInteger;
4+
import java.util.concurrent.CompletableFuture;
5+
6+
public interface TransferClient {
7+
void transfer(TransferRequest transferRequest);
8+
9+
CompletableFuture<BigInteger> getNonce(String address);
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.blockchain.api.domain.service.transfer;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.stereotype.Service;
6+
7+
@RequiredArgsConstructor
8+
@Service
9+
@Slf4j
10+
public class TransferService {
11+
12+
public void transfer(TransferRequest transactionRequest) {}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.blockchain.api.infrastructure.client.balance;
2+
3+
import com.blockchain.api.domain.service.balance.BalanceClient;
4+
import java.math.BigInteger;
5+
import java.util.concurrent.CompletableFuture;
6+
import lombok.RequiredArgsConstructor;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.stereotype.Component;
9+
import org.web3j.protocol.Web3j;
10+
import org.web3j.protocol.core.DefaultBlockParameterName;
11+
12+
@Slf4j
13+
@Component
14+
@RequiredArgsConstructor
15+
public class BalanceAdaptor implements BalanceClient {
16+
17+
private final Web3j rpcClient;
18+
19+
@Override
20+
public CompletableFuture<BigInteger> getBalance(String address) {
21+
return rpcClient
22+
.ethGetBalance(address, DefaultBlockParameterName.LATEST)
23+
.sendAsync()
24+
.thenApply(
25+
balance -> {
26+
log.info("Balance request completed successfully for address: {}", address);
27+
return balance.getBalance();
28+
});
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.blockchain.api.infrastructure.client.transfer;
2+
3+
import com.blockchain.api.application.exception.NonceRetrievalException;
4+
import com.blockchain.api.domain.service.transfer.TransferClient;
5+
import com.blockchain.api.domain.service.transfer.TransferRequest;
6+
import java.math.BigInteger;
7+
import java.util.concurrent.CompletableFuture;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.stereotype.Component;
11+
import org.web3j.protocol.Web3j;
12+
import org.web3j.protocol.core.DefaultBlockParameterName;
13+
14+
@RequiredArgsConstructor
15+
@Component
16+
@Slf4j
17+
public class TransferAdaptor implements TransferClient {
18+
19+
private final Web3j rpcClient;
20+
21+
@Override
22+
public void transfer(TransferRequest transferRequest) {
23+
// TODO: Implement transfer logic
24+
}
25+
26+
@Override
27+
public CompletableFuture<BigInteger> getNonce(String address) {
28+
return rpcClient
29+
.ethGetTransactionCount(address, DefaultBlockParameterName.LATEST)
30+
.sendAsync()
31+
.thenApply(
32+
nonce -> {
33+
log.info("Nonce request completed successfully for address: {}", address);
34+
return nonce.getTransactionCount();
35+
})
36+
.exceptionally(
37+
throwable -> {
38+
log.error("Error occurred while fetching nonce for address: {}", address, throwable);
39+
throw NonceRetrievalException.withAddress(address);
40+
});
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.blockchain.api.infrastructure.common;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.web3j.protocol.Web3j;
7+
import org.web3j.protocol.http.HttpService;
8+
9+
@Configuration
10+
public class EthereumClientConfig {
11+
12+
@Bean
13+
public Web3j web3j(@Value("${rpc.client.base.ethereum.url}") String quikNodeUrl) {
14+
return Web3j.build(new HttpService(quikNodeUrl));
15+
}
16+
}

Diff for: ethereum-app/src/main/resources/application.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
spring:
2+
application:
3+
name: ethereum-api
4+
5+
logging:
6+
level:
7+
"com.github.tomakehurst.wiremock": "TRACE"
8+
9+
rpc:
10+
client:
11+
base:
12+
ethereum:
13+
url: "https://patient-divine-aura.ethereum-sepolia.quiknode.pro/c5b3fafdf193c76117612895178bf8c0230e03ed"

0 commit comments

Comments
 (0)