feat: first release

This commit is contained in:
darwincereska
2025-12-19 10:14:05 -05:00
parent 194fd7357c
commit 3f4349c9d2
9 changed files with 59 additions and 20 deletions

View File

@@ -9,6 +9,7 @@ object Tables {
val address = varchar("address", 64) // Format random_word:random_6_digits
val balance = decimal("balance", 20, 8).default(BigDecimal.ZERO)
val label = varchar("label", 255).nullable()
val passwordHash = varchar("password_hash", 64).nullable()
val createdAt = long("created_at")
val lastActivity = long("last_activity").nullable()

View File

@@ -8,7 +8,8 @@ data class SendTransactionRequest(
val toAddress: String, // Format random_word:random_6_digits
val amount: Double,
val fee: Double = 0.0,
val memo: String? = null
val memo: String? = null,
val password: String
)
@Serializable

View File

@@ -4,7 +4,8 @@ import kotlinx.serialization.Serializable
@Serializable
data class CreateWalletRequest(
val label: String? = null
val label: String? = null,
val password: String
)
@Serializable
@@ -12,6 +13,7 @@ data class WalletResponse(
val address: String, // Format random_word:random_6_digits (e.g. "phoenix:123456")
val balance: Double,
val label: String?,
val passwordHash: String,
val createdAt: Long,
val lastActivity: Long?
)

View File

@@ -24,6 +24,7 @@ fun Route.transactionRoutes() {
request.fromAddress,
request.toAddress,
request.amount,
request.password,
request.fee,
request.memo
)
@@ -37,8 +38,9 @@ fun Route.transactionRoutes() {
request.fromAddress,
request.toAddress,
request.amount,
request.password,
request.fee,
request.memo
request.memo,
)
call.respond(HttpStatusCode.Created, transaction)
@@ -204,11 +206,13 @@ fun Route.transactionRoutes() {
val totalCount = TransactionService.getTotalTransactionCount()
val pendingCount = TransactionService.getPendingTransactions().size
call.respond(mapOf(
"totalTransactions" to totalCount,
"pendingTransactions" to pendingCount,
"confirmedTransactions" to (totalCount - pendingCount)
))
call.respond(
TransactionStatsResponse(
totalTransactions = totalCount,
pendingTransactions = pendingCount,
confirmedTransactions = (totalCount - pendingCount)
)
)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get transaction statistics")))
@@ -229,3 +233,10 @@ data class TransactionPendingResponse(
val transactions: List<TransactionResponse>,
val count: Int
)
@Serializable
data class TransactionStatsResponse(
val totalTransactions: Long,
val pendingTransactions: Int,
val confirmedTransactions: Long
)

View File

@@ -18,12 +18,7 @@ fun Route.walletRoutes() {
/** Create a new wallet */
post("/create") {
try {
val request = try {
call.receive<CreateWalletRequest>()
} catch (e: Exception) {
// If no body or invalid JSON, create with null label
CreateWalletRequest(null)
}
val request = call.receive<CreateWalletRequest>()
// Validate input
val validation = ValidationService.validateWalletCreation(request.label)
@@ -32,7 +27,7 @@ fun Route.walletRoutes() {
return@post
}
val wallet = WalletService.createWallet(request.label)
val wallet = WalletService.createWallet(request.label, request.password)
call.respond(HttpStatusCode.Created, wallet)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to create wallet")))

View File

@@ -19,6 +19,7 @@ object TransactionService {
fromAddress: String,
toAddress: String,
amount: Double,
password: String,
fee: Double = 0.0,
memo: String? = null
): TransactionResponse {
@@ -27,11 +28,20 @@ object TransactionService {
val totalAmount = BigDecimal.valueOf(amount + fee)
return transaction {
// Check if sender wallet exists and has sufficient balance
val fromBalance = Tables.Wallets.selectAll()
// Check if sender wallet exists
val wallet = Tables.Wallets.selectAll()
.where { Tables.Wallets.address eq fromAddress }
.map { it[Tables.Wallets.balance] }
.singleOrNull() ?: throw WalletNotFoundException(fromAddress)
val storedHash = wallet[Tables.Wallets.passwordHash]
?: throw InvalidTransactionException("Wallet has no password set")
if (!CryptoUtils.verifyPassword(password, storedHash)) {
throw InvalidTransactionException("Invalid password")
}
// Check if sender wallet exists and has sufficient balance
val fromBalance = wallet[Tables.Wallets.balance]
if (fromBalance < totalAmount) {
throw InsufficientFundsException(fromAddress, amount + fee, fromBalance.toDouble())

View File

@@ -55,6 +55,7 @@ object ValidationService {
fromAddress: String?,
toAddress: String,
amount: Double,
password: String,
fee: Double,
memo: String?
): ValidationResult {

View File

@@ -12,18 +12,27 @@ import java.time.Instant
object WalletService {
/** Creates a new wallet with optional label */
fun createWallet(label: String? = null): WalletResponse {
fun createWallet(label: String? = null, password: String): WalletResponse {
val address = CryptoUtils.generateWalletAddress()
val timestamp = Instant.now().epochSecond
val passwordHash = CryptoUtils.hashPassword(password)
return transaction {
Tables.Wallets.insert {
it[Tables.Wallets.address] = address
it[Tables.Wallets.label] = label
it[Tables.Wallets.passwordHash] = passwordHash
it[createdAt] = timestamp
}
WalletResponse(address, 0.0, label, timestamp, null)
WalletResponse(
address = address,
balance = 0.0,
label = label,
passwordHash = passwordHash,
createdAt = timestamp,
lastActivity = null
)
}
}
@@ -35,6 +44,7 @@ object WalletService {
it[Tables.Wallets.address],
it[Tables.Wallets.balance].toDouble(),
it[Tables.Wallets.label],
it[Tables.Wallets.passwordHash].toString(),
it[Tables.Wallets.createdAt],
it[Tables.Wallets.lastActivity]
)
@@ -95,6 +105,7 @@ object WalletService {
it[Tables.Wallets.address],
it[Tables.Wallets.balance].toDouble(),
it[Tables.Wallets.label],
it[Tables.Wallets.passwordHash].toString(),
it[Tables.Wallets.createdAt],
it[Tables.Wallets.lastActivity]
)
@@ -115,6 +126,7 @@ object WalletService {
it[Tables.Wallets.address],
it[Tables.Wallets.balance].toDouble(),
it[Tables.Wallets.label],
it[Tables.Wallets.passwordHash].toString(),
it[Tables.Wallets.createdAt],
it[Tables.Wallets.lastActivity]
)

View File

@@ -52,6 +52,9 @@ object CryptoUtils {
.joinToString("") { "%02x".format(it) }
}
/** Hashes password */
fun hashPassword(password: String): String = sha256("ccoin_password_$password")
/** Generates a transaction hash */
fun generateTransactionHash(
fromAddress: String?,
@@ -120,4 +123,7 @@ object CryptoUtils {
/** Validates block has format */
fun isValidBlockHash(hash: String): Boolean = hash.length == 64 && hash.all { it.isDigit() || it.lowercaseChar() in 'a'..'f' }
/** Verifies password hash */
fun verifyPassword(password: String, storedHash: String): Boolean = hashPassword(password) == storedHash
}