Skip to content

Commit c8c6f0b

Browse files
committed
time range for transactions
1 parent 8d15fa9 commit c8c6f0b

File tree

9 files changed

+188
-7
lines changed

9 files changed

+188
-7
lines changed

README.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,4 +461,63 @@ storage:
461461
host: localhost
462462
username: admin
463463
password: password
464-
```
464+
```
465+
466+
## API Documentation
467+
468+
### Transaction Queries with Time Range Filtering
469+
470+
The transaction endpoints support time-based filtering using Unix timestamps. This allows you to query transactions within a specific time range.
471+
472+
#### Time Range Parameters
473+
474+
- `from_time` (optional): Start time for filtering (Unix timestamp)
475+
- `to_time` (optional): End time for filtering (Unix timestamp, defaults to current time)
476+
477+
#### Configuration
478+
479+
The maximum allowed time range is configurable via the `api.transactionMaxTimeRangeSeconds` setting:
480+
481+
```yaml
482+
api:
483+
host: ":8080"
484+
transactionMaxTimeRangeSeconds: 604800 # 1 week in seconds (default)
485+
```
486+
487+
#### Examples
488+
489+
**Query transactions from a specific time to now:**
490+
```
491+
GET /{chainId}/transactions?from_time=1640995200
492+
```
493+
494+
**Query transactions within a specific time range:**
495+
```
496+
GET /{chainId}/transactions?from_time=1640995200&to_time=1641081600
497+
```
498+
499+
**Query wallet transactions with time filtering:**
500+
```
501+
GET /{chainId}/wallet-transactions/{wallet_address}?from_time=1640995200&to_time=1641081600
502+
```
503+
504+
#### Validation Rules
505+
506+
1. If `to_time` is not provided, it defaults to the current time
507+
2. The time range (`to_time - from_time`) cannot exceed the configured `maxTimeRangeSeconds`
508+
3. `from_time` must be less than `to_time`
509+
4. Both parameters are optional, but if provided, they must be valid Unix timestamps
510+
511+
#### Error Responses
512+
513+
- `400 Bad Request`: If the time range exceeds the maximum allowed duration
514+
- `400 Bad Request`: If `from_time` is greater than or equal to `to_time`
515+
- `400 Bad Request`: If timestamps are invalid
516+
517+
#### Supported Endpoints
518+
519+
Time range filtering is available on all transaction endpoints:
520+
- `GET /{chainId}/transactions`
521+
- `GET /{chainId}/wallet-transactions/{wallet_address}`
522+
- `GET /{chainId}/transactions/{to}`
523+
- `GET /{chainId}/transactions/{to}/{signature}`

api/api.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ type QueryParams struct {
4646
Decode bool `schema:"decode"`
4747
// @Description Flag to force consistent data at the expense of query speed
4848
ForceConsistentData bool `schema:"force_consistent_data"`
49+
// @Description Start time for filtering (Unix timestamp)
50+
FromTime int64 `schema:"from_time"`
51+
// @Description End time for filtering (Unix timestamp)
52+
ToTime int64 `schema:"to_time"`
4953
}
5054

5155
// Meta represents metadata for a query response

configs/config.example.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ reorgHandler:
4141
validation:
4242
mode: strict # "disabled", "minimal", or "strict"
4343

44+
api:
45+
host: ":8080"
46+
transactionMaxTimeRangeSeconds: 604800 # 1 week in seconds
47+
4448
storage:
4549
main:
4650
clickhouse:

configs/config.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,13 @@ type ContractApiRequestConfig struct {
124124
}
125125

126126
type APIConfig struct {
127-
Host string `mapstructure:"host"`
128-
BasicAuth BasicAuthConfig `mapstructure:"basicAuth"`
129-
ThirdwebContractApi string `mapstructure:"thirdwebContractApi"`
130-
ContractApiRequest ContractApiRequestConfig `mapstructure:"contractApiRequest"`
131-
AbiDecodingEnabled bool `mapstructure:"abiDecodingEnabled"`
132-
Thirdweb ThirdwebConfig `mapstructure:"thirdweb"`
127+
Host string `mapstructure:"host"`
128+
BasicAuth BasicAuthConfig `mapstructure:"basicAuth"`
129+
ThirdwebContractApi string `mapstructure:"thirdwebContractApi"`
130+
ContractApiRequest ContractApiRequestConfig `mapstructure:"contractApiRequest"`
131+
AbiDecodingEnabled bool `mapstructure:"abiDecodingEnabled"`
132+
Thirdweb ThirdwebConfig `mapstructure:"thirdweb"`
133+
TransactionMaxTimeRangeSeconds int `mapstructure:"transactionMaxTimeRangeSeconds"`
133134
}
134135

135136
type BlockPublisherConfig struct {

internal/handlers/transactions_handlers.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package handlers
22

33
import (
4+
"fmt"
45
"net/http"
6+
"time"
57

68
"github.com/ethereum/go-ethereum/accounts/abi"
79
gethCommon "github.com/ethereum/go-ethereum/common"
@@ -28,6 +30,8 @@ import (
2830
// @Param limit query int false "Number of items per page" default(5)
2931
// @Param aggregate query []string false "List of aggregate functions to apply"
3032
// @Param force_consistent_data query bool false "Force consistent data at the expense of query speed"
33+
// @Param from_time query int false "Start time for filtering (Unix timestamp)"
34+
// @Param to_time query int false "End time for filtering (Unix timestamp, defaults to current time)"
3135
// @Success 200 {object} api.QueryResponse{data=[]common.TransactionModel}
3236
// @Failure 400 {object} api.Error
3337
// @Failure 401 {object} api.Error
@@ -53,6 +57,8 @@ func GetTransactions(c *gin.Context) {
5357
// @Param limit query int false "Number of items per page" default(5)
5458
// @Param force_consistent_data query bool false "Force consistent data at the expense of query speed"
5559
// @Param decode query bool false "Decode transaction data"
60+
// @Param from_time query int false "Start time for filtering (Unix timestamp)"
61+
// @Param to_time query int false "End time for filtering (Unix timestamp, defaults to current time)"
5662
// @Success 200 {object} api.QueryResponse{data=[]common.DecodedTransactionModel}
5763
// @Failure 400 {object} api.Error
5864
// @Failure 401 {object} api.Error
@@ -78,6 +84,8 @@ func GetWalletTransactions(c *gin.Context) {
7884
// @Param limit query int false "Number of items per page" default(5)
7985
// @Param aggregate query []string false "List of aggregate functions to apply"
8086
// @Param force_consistent_data query bool false "Force consistent data at the expense of query speed"
87+
// @Param from_time query int false "Start time for filtering (Unix timestamp)"
88+
// @Param to_time query int false "End time for filtering (Unix timestamp, defaults to current time)"
8189
// @Success 200 {object} api.QueryResponse{data=[]common.TransactionModel}
8290
// @Failure 400 {object} api.Error
8391
// @Failure 401 {object} api.Error
@@ -104,6 +112,8 @@ func GetTransactionsByContract(c *gin.Context) {
104112
// @Param limit query int false "Number of items per page" default(5)
105113
// @Param aggregate query []string false "List of aggregate functions to apply"
106114
// @Param force_consistent_data query bool false "Force consistent data at the expense of query speed"
115+
// @Param from_time query int false "Start time for filtering (Unix timestamp)"
116+
// @Param to_time query int false "End time for filtering (Unix timestamp, defaults to current time)"
107117
// @Success 200 {object} api.QueryResponse{data=[]common.DecodedTransactionModel}
108118
// @Failure 400 {object} api.Error
109119
// @Failure 401 {object} api.Error
@@ -129,6 +139,35 @@ func handleTransactionsRequest(c *gin.Context) {
129139
return
130140
}
131141

142+
// Parse and validate time parameters
143+
fromTime := queryParams.FromTime
144+
toTime := queryParams.ToTime
145+
146+
// If toTime is not provided, default to current time
147+
if toTime == 0 {
148+
toTime = time.Now().Unix()
149+
}
150+
151+
// Validate time range
152+
if fromTime > 0 && toTime > 0 {
153+
timeRange := toTime - fromTime
154+
maxTimeRange := int64(config.Cfg.API.TransactionMaxTimeRangeSeconds)
155+
if maxTimeRange == 0 {
156+
// Default to 1 week if not configured
157+
maxTimeRange = 7 * 24 * 60 * 60 // 1 week in seconds
158+
}
159+
160+
if timeRange > maxTimeRange {
161+
api.BadRequestErrorHandler(c, fmt.Errorf("time range cannot exceed %d seconds (approximately %d days)", maxTimeRange, maxTimeRange/(24*60*60)))
162+
return
163+
}
164+
165+
if fromTime >= toTime {
166+
api.BadRequestErrorHandler(c, fmt.Errorf("from_time must be less than to_time"))
167+
return
168+
}
169+
}
170+
132171
var functionABI *abi.Method
133172
signatureHash := ""
134173
if signature != "" {
@@ -158,6 +197,8 @@ func handleTransactionsRequest(c *gin.Context) {
158197
Page: queryParams.Page,
159198
Limit: queryParams.Limit,
160199
ForceConsistentData: queryParams.ForceConsistentData,
200+
FromTime: fromTime,
201+
ToTime: toTime,
161202
}
162203

163204
// Initialize the QueryResult

internal/storage/clickhouse.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,10 @@ func (c *ClickHouseConnector) GetAggregations(table string, qf QueryFilter) (Que
500500
if signatureClause != "" {
501501
whereClauses = append(whereClauses, signatureClause)
502502
}
503+
timeRangeClause := createTimeRangeClause(qf.FromTime, qf.ToTime)
504+
if timeRangeClause != "" {
505+
whereClauses = append(whereClauses, timeRangeClause)
506+
}
503507
for key, value := range qf.FilterParams {
504508
whereClauses = append(whereClauses, createFilterClause(key, strings.ToLower(value)))
505509
}
@@ -641,6 +645,10 @@ func (c *ClickHouseConnector) buildQuery(table, columns string, qf QueryFilter)
641645
if signatureClause != "" {
642646
whereClauses = append(whereClauses, signatureClause)
643647
}
648+
timeRangeClause := createTimeRangeClause(qf.FromTime, qf.ToTime)
649+
if timeRangeClause != "" {
650+
whereClauses = append(whereClauses, timeRangeClause)
651+
}
644652
// Add filter params
645653
for key, value := range qf.FilterParams {
646654
whereClauses = append(whereClauses, createFilterClause(key, strings.ToLower(value)))
@@ -749,6 +757,24 @@ func createSignatureClause(table, signature string) string {
749757
return ""
750758
}
751759

760+
func createTimeRangeClause(fromTime, toTime int64) string {
761+
clauses := []string{}
762+
763+
if fromTime > 0 {
764+
clauses = append(clauses, fmt.Sprintf("block_timestamp >= toDateTime(%d)", fromTime))
765+
}
766+
767+
if toTime > 0 {
768+
clauses = append(clauses, fmt.Sprintf("block_timestamp <= toDateTime(%d)", toTime))
769+
}
770+
771+
if len(clauses) == 0 {
772+
return ""
773+
}
774+
775+
return strings.Join(clauses, " AND ")
776+
}
777+
752778
func getTopicValueFormat(topic string) string {
753779
if topic == "" {
754780
// if there is no indexed topic, indexer stores an empty string

internal/storage/clickhouse_connector_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,47 @@ func TestMapClickHouseTypeToGoType(t *testing.T) {
132132
})
133133
}
134134
}
135+
136+
// TestCreateTimeRangeClause tests the createTimeRangeClause function
137+
func TestCreateTimeRangeClause(t *testing.T) {
138+
testCases := []struct {
139+
name string
140+
fromTime int64
141+
toTime int64
142+
expected string
143+
}{
144+
{
145+
name: "both times provided",
146+
fromTime: 1640995200, // 2022-01-01 00:00:00 UTC
147+
toTime: 1641081600, // 2022-01-02 00:00:00 UTC
148+
expected: "block_timestamp >= toDateTime(1640995200) AND block_timestamp <= toDateTime(1641081600)",
149+
},
150+
{
151+
name: "only from time",
152+
fromTime: 1640995200,
153+
toTime: 0,
154+
expected: "block_timestamp >= toDateTime(1640995200)",
155+
},
156+
{
157+
name: "only to time",
158+
fromTime: 0,
159+
toTime: 1641081600,
160+
expected: "block_timestamp <= toDateTime(1641081600)",
161+
},
162+
{
163+
name: "no times provided",
164+
fromTime: 0,
165+
toTime: 0,
166+
expected: "",
167+
},
168+
}
169+
170+
for _, tc := range testCases {
171+
t.Run(tc.name, func(t *testing.T) {
172+
result := createTimeRangeClause(tc.fromTime, tc.toTime)
173+
if result != tc.expected {
174+
t.Errorf("createTimeRangeClause(%d, %d) = %s, want %s", tc.fromTime, tc.toTime, result, tc.expected)
175+
}
176+
})
177+
}
178+
}

internal/storage/connector.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ type QueryFilter struct {
2424
WalletAddress string
2525
Signature string
2626
ForceConsistentData bool
27+
FromTime int64
28+
ToTime int64
2729
}
2830

2931
type TransfersQueryFilter struct {

main

50.3 MB
Binary file not shown.

0 commit comments

Comments
 (0)