Skip to content

Commit fa51beb

Browse files
committed
feat: Added new endpoint GET /historical_fee?date=YYYY-MM-DDTHH:MM:SS
1 parent 048b90a commit fa51beb

3 files changed

Lines changed: 126 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright (c) 2025 Block, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package xyz.block.augurref.api
18+
19+
import io.ktor.http.ContentType
20+
import io.ktor.http.HttpStatusCode
21+
import io.ktor.server.application.call
22+
import io.ktor.server.response.respond
23+
import io.ktor.server.response.respondText
24+
import io.ktor.server.routing.Route
25+
import io.ktor.server.routing.get
26+
import org.slf4j.LoggerFactory
27+
import xyz.block.augurref.service.MempoolCollector
28+
import java.time.LocalDateTime
29+
import java.time.format.DateTimeParseException
30+
31+
/**
32+
* Configure historical fee estimate endpoint
33+
*/
34+
fun Route.configureHistoricalFeesEndpoint(mempoolCollector: MempoolCollector) {
35+
val logger = LoggerFactory.getLogger("xyz.block.augurref.api.HistoricalFeeEstimateEndpoint")
36+
37+
get("/historical_fee") {
38+
logger.info("Received request for historical fee estimates")
39+
40+
// Extract date param from query parameters
41+
val dateParam = call.request.queryParameters["date"]
42+
43+
if (dateParam == null) {
44+
call.respondText(
45+
"Date parameter is required",
46+
status = HttpStatusCode.BadRequest,
47+
contentType = ContentType.Text.Plain,
48+
)
49+
return@get
50+
}
51+
52+
// Validate and parse the date
53+
val date = try {
54+
dateParam?.let { LocalDateTime.parse(it) }
55+
} catch (e: DateTimeParseException) {
56+
logger.warn("Invalid date format: $dateParam")
57+
call.respondText(
58+
"Invalid date format. Use YYYY-MM-DDTHH:MM:SS",
59+
status = HttpStatusCode.BadRequest,
60+
contentType = ContentType.Text.Plain,
61+
)
62+
return@get
63+
}
64+
65+
// Fetch historical fee estimate based on date
66+
val feeEstimate = if (date != null) {
67+
logger.info("Fetching historical fee estimate for date: $date")
68+
mempoolCollector.getFeeEstimateForDate(date)
69+
} else {
70+
logger.warn("date is null")
71+
call.respondText(
72+
"Failed to parse date",
73+
status = HttpStatusCode.InternalServerError,
74+
contentType = ContentType.Text.Plain,
75+
)
76+
return@get
77+
}
78+
79+
if (feeEstimate == null) {
80+
logger.warn("No historical fee estimates available for $date")
81+
call.respondText(
82+
"No historical fee estimates available for $date",
83+
status = HttpStatusCode.ServiceUnavailable,
84+
contentType = ContentType.Text.Plain,
85+
)
86+
} else {
87+
logger.info("Transforming historical fee estimates for response")
88+
val response = transformFeeEstimate(feeEstimate)
89+
logger.debug("Returning historical fee estimates with ${response.estimates.size} targets")
90+
call.respond(response)
91+
}
92+
}
93+
}

app/src/main/kotlin/xyz/block/augurref/server/HttpServer.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
2626
import io.ktor.server.routing.routing
2727
import org.slf4j.LoggerFactory
2828
import xyz.block.augurref.api.configureFeesEndpoint
29+
import xyz.block.augurref.api.configureHistoricalFeesEndpoint
2930
import xyz.block.augurref.config.ServerConfig
3031
import xyz.block.augurref.service.MempoolCollector
3132

@@ -58,6 +59,7 @@ class HttpServer(
5859
// Configure routes
5960
routing {
6061
configureFeesEndpoint(mempoolCollector)
62+
configureHistoricalFeesEndpoint(mempoolCollector)
6163
}
6264
}.start(wait = false)
6365

app/src/main/kotlin/xyz/block/augurref/service/MempoolCollector.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import xyz.block.augurref.bitcoin.BitcoinRpcClient
2424
import xyz.block.augurref.persistence.MempoolPersistence
2525
import java.time.Instant
2626
import java.time.LocalDateTime
27+
import java.time.ZoneId
2728
import java.util.concurrent.atomic.AtomicReference
2829
import kotlin.concurrent.fixedRateTimer
2930

@@ -71,6 +72,36 @@ class MempoolCollector(
7172
return latestFeeEstimate.get()
7273
}
7374

75+
/**
76+
* Get the fee estimate for specific date
77+
*/
78+
fun getFeeEstimateForDate(dateTimeUTC: LocalDateTime): FeeEstimate? {
79+
val dateTime = dateTimeUTC
80+
.atZone(ZoneId.of("UTC")) // Interpret as UTC
81+
.withZoneSameInstant(ZoneId.systemDefault())
82+
.toLocalDateTime()
83+
// Fetch the last day's snapshots
84+
logger.debug("Fetching snapshots from the last day")
85+
val lastDaySnapshots = persistence.getSnapshots(
86+
dateTime.minusDays(1),
87+
dateTime,
88+
)
89+
logger.debug("Retrieved ${lastDaySnapshots.size} snapshots from the last day")
90+
91+
if (lastDaySnapshots.isNotEmpty()) {
92+
// Calculate fee estimate for x blocks
93+
logger.debug("Calculating fee estimates")
94+
val newFeeEstimate = feeEstimator.calculateEstimates(lastDaySnapshots)
95+
return newFeeEstimate
96+
} else {
97+
logger.warn("No snapshots available for fee estimation")
98+
}
99+
return FeeEstimate(
100+
estimates = emptyMap(),
101+
timestamp = dateTime.atZone(ZoneId.of("UTC")).toInstant(),
102+
)
103+
}
104+
74105
/**
75106
* Get the latest fee estimate for block target
76107
*/

0 commit comments

Comments
 (0)