diff --git a/.env.sample b/.env.sample index 2d1d319..524c060 100644 --- a/.env.sample +++ b/.env.sample @@ -8,8 +8,8 @@ CRYPTO_NOTIFY_TELEGRAM_CHAT_ID="" CRYPTO_NOTIFY_LINE_NOTIFY_TOKEN="" CRYPTO_NOTIFY_NOTIFY_CRON="0 0 */3 * * ? *" # optional CRYPTO_NOTIFY_HEALTHCHECK_CRON="0 */1 * * * ? *" # optional -CRYPTO_NOTIFY_BSCSCAN_API_KEY="" # optional -CRYPTO_NOTIFY_BSCSCAN_ADDRESS="" # optional +CRYPTO_NOTIFY_ETHERSCAN_API_KEY="" # optional +CRYPTO_NOTIFY_BSC_ADDRESS="" # optional CRYPTO_NOTIFY_MACKEREL_API_KEY="" # optional CRYPTO_NOTIFY_MACKEREL_SERVICE_NAME="" # optional CRYPTO_NOTIFY_BINANCE_API_KEY="" # optional diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a9ee6d1..8b8698b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -15,12 +15,12 @@ jobs: dockerPublish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 + - uses: actions/checkout@v5 + - name: Set up JDK + uses: actions/setup-java@v5 with: - distribution: 'temurin' - java-version: '21' + distribution: 'zulu' + java-version: '25' - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml index c4f1771..b750812 100644 --- a/.github/workflows/pr_build.yml +++ b/.github/workflows/pr_build.yml @@ -9,15 +9,15 @@ jobs: runs-on: ubuntu-latest if: ${{ !contains(github.actor, 'github-actions[bot]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ github.head_ref }} token: ${{ secrets.ACTION_TOKEN }} - - name: Set up JDK 21 - uses: actions/setup-java@v4 + - name: Set up JDK + uses: actions/setup-java@v5 with: - distribution: 'temurin' - java-version: '21' + distribution: 'zulu' + java-version: '25' - name: Set Git Username run: | git config --global user.name "github-actions[bot]" @@ -44,12 +44,12 @@ jobs: needs: formatCode runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 + - uses: actions/checkout@v5 + - name: Set up JDK + uses: actions/setup-java@v5 with: - distribution: 'temurin' - java-version: '21' + distribution: 'zulu' + java-version: '25' - uses: sbt/setup-sbt@v1 - name: Clean and Compile run: sbt clean compile @@ -58,12 +58,12 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 + - uses: actions/checkout@v5 + - name: Set up JDK + uses: actions/setup-java@v5 with: - distribution: 'temurin' - java-version: '21' + distribution: 'zulu' + java-version: '25' - uses: sbt/setup-sbt@v1 - name: Test run: sbt test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b3ad426..5eaf214 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,12 +14,12 @@ jobs: needs: dockerPublish runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: - distribution: 'temurin' - java-version: '21' + distribution: 'zulu' + java-version: '25' - name: Set GitHub user run: | git config --global user.name 'github-actions[bot]' @@ -34,7 +34,7 @@ jobs: needs: release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set GitHub user run: | git config --global user.name 'oat9002' diff --git a/README.md b/README.md index c786690..66e1fb5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Notify your current cryptocurrency balance from Satang Pro via Line Notify or Te - Orbix `// Required to convert to THB` - Binance `// Required if there is no listed coin in Satang Pro` -- BscScan +- EtherScan - Terra - CakePool - VeCakePool diff --git a/build.sbt b/build.sbt index 6099a0b..9098240 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ scalacOptions ++= Seq("-deprecation") enablePlugins(JavaAppPackaging, DockerPlugin) dockerRepository := Some("oat9002") -dockerBaseImage := "eclipse-temurin:21-jre-jammy" +dockerBaseImage := "azul/zulu-openjdk:25-jre-latest" dockerExposedPorts := Seq(8080, 80, 443) dockerUpdateLatest := true diff --git a/qodana.yaml b/qodana.yaml index eed31eb..b23e747 100644 --- a/qodana.yaml +++ b/qodana.yaml @@ -18,7 +18,7 @@ profile: # paths: # - -projectJDK: temurin-21 #(Applied in CI/CD pipeline) +projectJDK: zulu-25 #(Applied in CI/CD pipeline) #Execute shell command before Qodana execution (Applied in CI/CD pipeline) #bootstrap: sh ./prepare-qodana.sh diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 89f666a..743a736 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -16,9 +16,9 @@ telegram { botToken = ${?CRYPTO_NOTIFY_TELEGRAM_BOT_TOKEN} chatId = ${?CRYPTO_NOTIFY_TELEGRAM_CHAT_ID} } -bscScan { - apiKey = ${?CRYPTO_NOTIFY_BSCSCAN_API_KEY} - address = ${?CRYPTO_NOTIFY_BSCSCAN_ADDRESS} +etherScan { + apiKey = ${?CRYPTO_NOTIFY_ETHERSCAN_API_KEY} + address = ${?CRYPTO_NOTIFY_BSC_ADDRESS} } binance { apiKey = ${?CRYPTO_NOTIFY_BINANCE_API_KEY} diff --git a/src/main/scala/commons/Configuration.scala b/src/main/scala/commons/Configuration.scala index 39e07dc..48b7d4e 100644 --- a/src/main/scala/commons/Configuration.scala +++ b/src/main/scala/commons/Configuration.scala @@ -11,7 +11,7 @@ trait Configuration { lazy val telegramConfig: Option[TelegramConfig] lazy val satangConfig: SatangConfig lazy val akkaConfig: AkkaConfig - lazy val bscScanConfig: Option[BscScanConfig] + lazy val etherScanConfig: Option[EtherScanConfig] lazy val mackerelConfig: Option[MackerelConfig] lazy val binanceConfig: Option[BinanceConfig] lazy val terraConfig: Option[TerraConfig] @@ -28,7 +28,7 @@ class ConfigurationImpl extends Configuration { private val lineSection = conf.getConfig("line") private val satangSection = conf.getConfig("satang") private val akkaSection = conf.getConfig("akka") - private val bscScanSection = conf.getConfig("bscScan") + private val etherScanSection = conf.getConfig("etherScan") private val mackerelSection = conf.getConfig("mackerel") private val binanceSection = conf.getConfig("binance") private val terraSection = conf.getConfig("terra") @@ -53,10 +53,10 @@ class ConfigurationImpl extends Configuration { satangSection.getString("apiSecret"), satangSection.getString("userId") ) - lazy val bscScanConfig: Option[BscScanConfig] = Try( - BscScanConfig( - bscScanSection.getString("apiKey"), - bscScanSection.getString("address") + lazy val etherScanConfig: Option[EtherScanConfig] = Try( + EtherScanConfig( + etherScanSection.getString("apiKey"), + etherScanSection.getString("address") ) ) match { case Success(v) => Some(v) diff --git a/src/main/scala/commons/Constant.scala b/src/main/scala/commons/Constant.scala index 1e51ed1..214bdc6 100644 --- a/src/main/scala/commons/Constant.scala +++ b/src/main/scala/commons/Constant.scala @@ -8,7 +8,7 @@ object Constant { val bscRpcUrl = "https://bsc-dataseed.binance.org" val satangUrl = "https://www.orbixtrade.com/api" - val bscScanUrl = "https://api.bscscan.com/api" + val etherScanUrl = "https://api.etherscan.io/v2/api" val binanceUrl = "https://api.binance.com" val terraUrl = "https://terra-classic-lcd.publicnode.com" val twoPointOTerraUrl = "https://terra-lcd.publicnode.com" @@ -17,6 +17,8 @@ object Constant { val telegramUrl = "https://api.telegram.org" val blockStreamUrl = "https://blockstream.info/api" + val bnbMainNetChainId = 56 + enum EncryptionAlgorithm: case HmacSHA512, HmacSHA256 diff --git a/src/main/scala/di/DependencySetup.scala b/src/main/scala/di/DependencySetup.scala index 52f4b84..36a33a7 100644 --- a/src/main/scala/di/DependencySetup.scala +++ b/src/main/scala/di/DependencySetup.scala @@ -26,8 +26,8 @@ class DependencySetup(using system: ActorSystem[Nothing], context: ExecutionCont given terraHelper: TerraHelper = TerraHelperImpl() given satangService: SatangService = SatangServiceImpl() - given bscScanService: BscScanService = - BscScanServiceImpl() + given etherScanService: EtherScanService = + EtherScanServiceImpl() given binanceService: BinanceService = BinanceServiceImpl() given terraService: TerraService = diff --git a/src/main/scala/models/bscScan/BscScanResponse.scala b/src/main/scala/models/bscScan/BscScanResponse.scala deleted file mode 100644 index 2a7065a..0000000 --- a/src/main/scala/models/bscScan/BscScanResponse.scala +++ /dev/null @@ -1,10 +0,0 @@ -package models.bscScan - -import io.circe.* -import io.circe.generic.semiauto.* - -case class BscScanResponse(status: Int, message: String, result: BigInt) -object BscScanResponse { - given Encoder[BscScanResponse] = deriveEncoder[BscScanResponse] - given Decoder[BscScanResponse] = deriveDecoder[BscScanResponse] -} diff --git a/src/main/scala/models/configuration/BscScanConfig.scala b/src/main/scala/models/configuration/BscScanConfig.scala deleted file mode 100644 index 1449b60..0000000 --- a/src/main/scala/models/configuration/BscScanConfig.scala +++ /dev/null @@ -1,3 +0,0 @@ -package models.configuration - -final case class BscScanConfig(apiKey: String, address: String) diff --git a/src/main/scala/models/configuration/EtherScanConfig.scala b/src/main/scala/models/configuration/EtherScanConfig.scala new file mode 100644 index 0000000..5d0a68e --- /dev/null +++ b/src/main/scala/models/configuration/EtherScanConfig.scala @@ -0,0 +1,3 @@ +package models.configuration + +final case class EtherScanConfig(apiKey: String, address: String) diff --git a/src/main/scala/models/etherScan/EtherScanResponse.scala b/src/main/scala/models/etherScan/EtherScanResponse.scala new file mode 100644 index 0000000..ee7feb1 --- /dev/null +++ b/src/main/scala/models/etherScan/EtherScanResponse.scala @@ -0,0 +1,10 @@ +package models.etherScan + +import io.circe.* +import io.circe.generic.semiauto.* + +case class EtherScanResponse(status: Int, message: String, result: String) +object EtherScanResponse { + given Encoder[EtherScanResponse] = deriveEncoder[EtherScanResponse] + given Decoder[EtherScanResponse] = deriveDecoder[EtherScanResponse] +} diff --git a/src/main/scala/processors/NotifyProcessor.scala b/src/main/scala/processors/NotifyProcessor.scala index b3ec338..29e966f 100644 --- a/src/main/scala/processors/NotifyProcessor.scala +++ b/src/main/scala/processors/NotifyProcessor.scala @@ -22,7 +22,7 @@ class NotifyProcessorImpl(using val now = getFormattedNowDate("E dd MMM YYYY HH:mm:ss", isThai = false) val message = userService.getBalanceMessage( configuration.satangConfig.userId, - configuration.bscScanConfig.map(_.address), + configuration.etherScanConfig.map(_.address), configuration.terraConfig.map(_.address), configuration.bitcoinConfig.map(_.address), notificationService.getProvider diff --git a/src/main/scala/services/crypto/BscScanService.scala b/src/main/scala/services/crypto/EtherScanService.scala similarity index 57% rename from src/main/scala/services/crypto/BscScanService.scala rename to src/main/scala/services/crypto/EtherScanService.scala index e684fa9..9cddf46 100644 --- a/src/main/scala/services/crypto/BscScanService.scala +++ b/src/main/scala/services/crypto/EtherScanService.scala @@ -1,16 +1,14 @@ package services.crypto import akka.actor.typed.ActorSystem -import com.typesafe.scalalogging.LazyLogging import commons.{Configuration, Constant, HttpClient, Logger} -import models.bscScan.BscScanResponse -import services.crypto.BscScanService +import models.etherScan.EtherScanResponse import scala.concurrent.{ExecutionContext, Future} import scala.math.BigDecimal.RoundingMode import scala.math.pow -trait BscScanService { +trait EtherScanService { def getBnbBalance(address: String): Future[Option[BigDecimal]] def getTokenBalance( contractAddress: String, @@ -18,18 +16,18 @@ trait BscScanService { ): Future[Option[BigDecimal]] } -class BscScanServiceImpl(using configuration: Configuration, httpClient: HttpClient)(using +class EtherScanServiceImpl(using configuration: Configuration, httpClient: HttpClient)(using system: ActorSystem[Nothing], context: ExecutionContext, logger: Logger -) extends BscScanService { - val baseUrl: String = Constant.bscScanUrl - val apiKey: String = configuration.bscScanConfig.map(_.apiKey).getOrElse("") +) extends EtherScanService { + val baseUrl: String = Constant.etherScanUrl + val apiKey: String = configuration.etherScanConfig.map(_.apiKey).getOrElse("") override def getBnbBalance(address: String): Future[Option[BigDecimal]] = { val url = - s"$baseUrl?module=account&action=balance&address=$address&apikey=$apiKey" - val response = httpClient.get[BscScanResponse](url) + s"$baseUrl?chainId=${Constant.bnbMainNetChainId}&module=account&action=balance&address=$address&apikey=$apiKey" + val response = httpClient.get[EtherScanResponse](url) response.map { case Left(err) => @@ -37,9 +35,9 @@ class BscScanServiceImpl(using configuration: Configuration, httpClient: HttpCli None case Right(x) => if (x.message == "OK") { - Some(convertFromWei(x.result)) + Some(convertFromWei(BigInt(x.result))) } else { - logger.error(s"getBnbBalance failed: ${x.message}") + logger.error(s"getBnbBalance failed: ${x.message}, ${x.result}") None } } @@ -50,8 +48,8 @@ class BscScanServiceImpl(using configuration: Configuration, httpClient: HttpCli address: String ): Future[Option[BigDecimal]] = { val url = - s"$baseUrl?module=account&action=tokenbalance&contractaddress=$contractAddress&address=$address&tag=latest&apikey=$apiKey" - val response = httpClient.get[BscScanResponse](url) + s"$baseUrl?chainId=${Constant.bnbMainNetChainId}&module=account&action=tokenbalance&contractaddress=$contractAddress&address=$address&tag=latest&apikey=$apiKey" + val response = httpClient.get[EtherScanResponse](url) response.map { case Left(err) => @@ -59,9 +57,9 @@ class BscScanServiceImpl(using configuration: Configuration, httpClient: HttpCli None case Right(x) => if (x.message == "OK") { - Some(convertFromWei(x.result)) + Some(convertFromWei(BigInt(x.result))) } else { - logger.error(s"getTokenBalance failed: ${x.message}") + logger.error(s"getTokenBalance failed: ${x.message}, ${x.result}") None } } diff --git a/src/main/scala/services/crypto/contracts/PancakeService.scala b/src/main/scala/services/crypto/contracts/PancakeService.scala index 1ac1018..426b421 100644 --- a/src/main/scala/services/crypto/contracts/PancakeService.scala +++ b/src/main/scala/services/crypto/contracts/PancakeService.scala @@ -1,7 +1,6 @@ package services.crypto.contracts import akka.actor.typed.ActorSystem -import com.typesafe.scalalogging.LazyLogging import commons.{Configuration, Constant, Logger} import contracts.pancake.{CakePool, VeCakePool} import org.web3j.crypto.Credentials diff --git a/src/main/scala/services/user/UserService.scala b/src/main/scala/services/user/UserService.scala index e6ede31..724b6aa 100644 --- a/src/main/scala/services/user/UserService.scala +++ b/src/main/scala/services/user/UserService.scala @@ -24,7 +24,7 @@ trait UserService { class UserServiceImpl(using satangService: SatangService, - bscScanService: BscScanService, + etherScanService: EtherScanService, binanceService: BinanceService, terraService: TerraService, pancakeService: PancakeService, @@ -44,11 +44,11 @@ class UserServiceImpl(using val satangCurrentPricesF = satangService.getCryptoPrices val binanceCurrentPricesF = binanceService.getLatestPrice val extBnbAmountF = bscAddress - .map(bscScanService.getBnbBalance) + .map(etherScanService.getBnbBalance) .getOrElse(Future.successful(None)) val extCakeAmountF = bscAddress .map(address => - bscScanService.getTokenBalance( + etherScanService.getTokenBalance( Constant.CakeTokenContractAddress, address ) diff --git a/src/test/scala/BscScanServiceSpec.scala b/src/test/scala/BscScanServiceSpec.scala deleted file mode 100644 index 37274f4..0000000 --- a/src/test/scala/BscScanServiceSpec.scala +++ /dev/null @@ -1,31 +0,0 @@ -//import akka.actor.testkit.typed.scaladsl.ActorTestKit -//import commons.{Configuration, HttpClient} -//import models.bscScan.BscScanResponse -//import org.scalamock.scalatest.MockFactory -//import org.scalatest.funspec.AnyFunSpec -//import org.scalatest.matchers.should.Matchers -//import services.crypto.BscScanServiceImpl -// -//import scala.concurrent.duration.Duration -//import scala.concurrent.{Await, Future} -// -//class BscScanServiceSpec extends AnyFunSpec with Matchers with MockFactory { -// val actorTestKit: ActorTestKit = ActorTestKit() -// val httpClientMock: HttpClient = mock[HttpClient] -// val configurationMock: Configuration = mock[Configuration] -// val bscScanService = new BscScanServiceImpl( -// configurationMock, -// httpClientMock -// )(actorTestKit.system, actorTestKit.system.executionContext) -// -// describe("getBnbBalance") { -// it("get balance correctly") { -// -// (httpClientMock.get[Any, BscScanResponse] _).expects(*, *, *).returning(Future.successful(Right(BscScanResponse(200, "", BigInt(1))))) -// -// val result = Await.result(bscScanService.getBnbBalance("address"), Duration.Inf) -// -// result shouldBe Some(BigInt(1)) -// } -// } -//}