feat: added transaction service

This commit is contained in:
darwincereska
2025-12-17 10:26:56 -05:00
parent abc8ee7553
commit a6021f9523

View File

@@ -0,0 +1,233 @@
package org.ccoin.services
import org.ccoin.database.Tables
import org.ccoin.exceptions.InsufficientFundsException
import org.ccoin.exceptions.InvalidTransactionException
import org.ccoin.exceptions.WalletNotFoundException
import org.ccoin.models.TransactionResponse
import org.ccoin.models.TransactionStatus
import org.ccoin.utils.CryptoUtils
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import java.math.BigDecimal
import java.time.Instant
object TransactionService {
/** Sends a transaction between wallets */
fun sendTransaction(
fromAddress: String,
toAddress: String,
amount: Double,
fee: Double = 0.0,
memo: String? = null
): TransactionResponse {
val hash = CryptoUtils.generateTransactionHash(fromAddress, toAddress, amount, Instant.now().epochSecond)
val timestamp = Instant.now().epochSecond
val totalAmount = BigDecimal.valueOf(amount + fee)
return transaction {
// Check if sender wallet exists and has sufficient balance
val fromBalance = Tables.Wallets.selectAll()
.where { Tables.Wallets.address eq fromAddress }
.map { it[Tables.Wallets.balance] }
.singleOrNull() ?: throw WalletNotFoundException(fromAddress)
if (fromBalance < totalAmount) {
throw InsufficientFundsException(fromAddress, amount + fee, fromBalance.toDouble())
}
// Check if recipient wallet exists
val recipientExists = Tables.Wallets.selectAll()
.where { Tables.Wallets.address eq toAddress }
.count() > 0
if (!recipientExists) {
throw WalletNotFoundException(toAddress)
}
// Create transaction record
Tables.Transactions.insert {
it[Tables.Transactions.hash] = hash
it[Tables.Transactions.fromAddress] = fromAddress
it[Tables.Transactions.toAddress] = toAddress
it[Tables.Transactions.amount] = BigDecimal.valueOf(amount)
it[Tables.Transactions.fee] = BigDecimal.valueOf(fee)
it[Tables.Transactions.memo] = memo
it[Tables.Transactions.timestamp] = timestamp
it[status] = "confirmed"
}
// Update sender balance
val senderCurrentBalance = Tables.Wallets.selectAll()
.where { Tables.Wallets.address eq fromAddress }
.map { it[Tables.Wallets.balance] }
.single()
Tables.Wallets.update({ Tables.Wallets.address eq fromAddress }) {
it[balance] = senderCurrentBalance.subtract(totalAmount)
it[lastActivity] = timestamp
}
// Update recipient balance
val recipientCurrentBalance = Tables.Wallets.selectAll()
.where { Tables.Wallets.address eq toAddress }
.map { it[Tables.Wallets.balance] }
.single()
Tables.Wallets.update({ Tables.Wallets.address eq toAddress }) {
it[balance] = recipientCurrentBalance.add(BigDecimal.valueOf(amount))
it[lastActivity] = timestamp
}
TransactionResponse(
hash, fromAddress, toAddress, amount, fee, memo, null, timestamp, TransactionStatus.CONFIRMED, 0
)
}
}
/** Gets transaction by hash */
fun getTransaction(hash: String): TransactionResponse? = transaction {
Tables.Transactions.selectAll().where { Tables.Transactions.hash eq hash }
.map {
TransactionResponse(
it[Tables.Transactions.hash],
it[Tables.Transactions.fromAddress],
it[Tables.Transactions.toAddress],
it[Tables.Transactions.amount].toDouble(),
it[Tables.Transactions.fee].toDouble(),
it[Tables.Transactions.memo],
it[Tables.Transactions.blockHash],
it[Tables.Transactions.timestamp],
TransactionStatus.valueOf(it[Tables.Transactions.status].uppercase()),
it[Tables.Transactions.confirmations]
)
}.singleOrNull()
}
/** Gets transaction history for an address */
fun getTransactionHistory(
address: String,
limit: Int = 50,
offset: Int = 0
): List<TransactionResponse> = transaction {
Tables.Transactions.selectAll()
.where {
(Tables.Transactions.fromAddress eq address) or (Tables.Transactions.toAddress eq address)
}
.orderBy(Tables.Transactions.timestamp, SortOrder.DESC)
.limit(limit)
.offset(offset.toLong())
.map {
TransactionResponse(
it[Tables.Transactions.hash],
it[Tables.Transactions.fromAddress],
it[Tables.Transactions.toAddress],
it[Tables.Transactions.amount].toDouble(),
it[Tables.Transactions.fee].toDouble(),
it[Tables.Transactions.memo],
it[Tables.Transactions.blockHash],
it[Tables.Transactions.timestamp],
TransactionStatus.valueOf(it[Tables.Transactions.status].uppercase()),
it[Tables.Transactions.confirmations]
)
}
}
/** Gets pending transactions */
fun getPendingTransactions(): List<TransactionResponse> = transaction {
Tables.Transactions.selectAll()
.where { Tables.Transactions.status eq "pending" }
.orderBy(Tables.Transactions.timestamp, SortOrder.ASC)
.map {
TransactionResponse(
it[Tables.Transactions.hash],
it[Tables.Transactions.fromAddress],
it[Tables.Transactions.toAddress],
it[Tables.Transactions.amount].toDouble(),
it[Tables.Transactions.fee].toDouble(),
it[Tables.Transactions.memo],
it[Tables.Transactions.blockHash],
it[Tables.Transactions.timestamp],
TransactionStatus.valueOf(it[Tables.Transactions.status].uppercase()),
it[Tables.Transactions.confirmations]
)
}
}
/** Updates transaction status */
fun updateTransactionStatus(hash: String, status: TransactionStatus): Boolean = transaction {
val updated = Tables.Transactions.update({ Tables.Transactions.hash eq hash }) {
it[Tables.Transactions.status] = status.name.lowercase()
}
updated > 0
}
/** Adds transaction to block */
fun addTransactionToBlock(transactionHash: String, blockHash: String): Boolean = transaction {
val updated = Tables.Transactions.update({ Tables.Transactions.hash eq transactionHash }) {
it[Tables.Transactions.blockHash] = blockHash
it[status] = "confirmed"
}
updated > 0
}
/** Gets total transaction count */
fun getTotalTransactionCount(): Long = transaction {
Tables.Transactions.selectAll().count()
}
/** Gets transaction count for address */
fun getTransactionCountForAddress(address: String): Long = transaction {
Tables.Transactions.selectAll()
.where {
(Tables.Transactions.fromAddress eq address) or (Tables.Transactions.toAddress eq address)
}
.count()
}
/** Creates a genesis transaction (mining reward) */
fun createGenesisTransaction(toAddress: String, amount: Double): TransactionResponse {
val hash = CryptoUtils.generateTransactionHash(null, toAddress, amount, Instant.now().epochSecond)
val timestamp = Instant.now().epochSecond
return transaction {
// Check if recipient wallet exists
val recipientExists = Tables.Wallets.selectAll()
.where { Tables.Wallets.address eq toAddress }
.count() > 0
if (!recipientExists) {
throw WalletNotFoundException(toAddress)
}
// Create genesis transaction
Tables.Transactions.insert {
it[Tables.Transactions.hash] = hash
it[Tables.Transactions.fromAddress] = null
it[Tables.Transactions.toAddress] = toAddress
it[Tables.Transactions.amount] = BigDecimal.valueOf(amount)
it[Tables.Transactions.fee] = BigDecimal.ZERO
it[Tables.Transactions.memo] = "Mining reward"
it[Tables.Transactions.timestamp] = timestamp
it[status] = "confirmed"
}
// Update recipient balance
val recipientCurrentBalance = Tables.Wallets.selectAll()
.where { Tables.Wallets.address eq toAddress }
.map { it[Tables.Wallets.balance] }
.single()
Tables.Wallets.update({ Tables.Wallets.address eq toAddress }) {
it[balance] = recipientCurrentBalance.add(BigDecimal.valueOf(amount))
it[lastActivity] = timestamp
}
TransactionResponse(
hash, null, toAddress, amount, 0.0, "Mining reward", null, timestamp, TransactionStatus.CONFIRMED, 0
)
}
}
}