Skip to content

Commit e7c224b

Browse files
authored
feat: tx list timestamp filter options (#2015)
1 parent 542973c commit e7c224b

File tree

4 files changed

+173
-3
lines changed

4 files changed

+173
-3
lines changed

docs/openapi.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,20 @@ paths:
240240
enum: [block_height, burn_block_time, fee]
241241
example: burn_block_time
242242
default: block_height
243+
- name: start_time
244+
in: query
245+
description: Filter by transactions after this timestamp (unix timestamp in seconds)
246+
required: false
247+
schema:
248+
type: integer
249+
example: 1704067200
250+
- name: end_time
251+
in: query
252+
description: Filter by transactions before this timestamp (unix timestamp in seconds)
253+
required: false
254+
schema:
255+
type: integer
256+
example: 1706745599
243257
- name: order
244258
in: query
245259
description: Option to sort results in ascending or descending order

src/api/routes/tx.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,28 @@ export function createTxRouter(db: PgStore): express.Router {
102102
toAddress = req.query.to_address;
103103
}
104104

105+
let startTime: number | undefined;
106+
if (typeof req.query.start_time === 'string') {
107+
if (!/^\d{10}$/.test(req.query.start_time)) {
108+
throw new InvalidRequestError(
109+
`Invalid query parameter for "start_time": "${req.query.start_time}" is not a valid timestamp`,
110+
InvalidRequestErrorType.invalid_param
111+
);
112+
}
113+
startTime = parseInt(req.query.start_time);
114+
}
115+
116+
let endTime: number | undefined;
117+
if (typeof req.query.end_time === 'string') {
118+
if (!/^\d{10}$/.test(req.query.end_time)) {
119+
throw new InvalidRequestError(
120+
`Invalid query parameter for "end_time": "${req.query.end_time}" is not a valid timestamp`,
121+
InvalidRequestErrorType.invalid_param
122+
);
123+
}
124+
endTime = parseInt(req.query.end_time);
125+
}
126+
105127
let sortBy: 'block_height' | 'burn_block_time' | 'fee' | undefined;
106128
if (req.query.sort_by) {
107129
if (
@@ -124,6 +146,8 @@ export function createTxRouter(db: PgStore): express.Router {
124146
includeUnanchored,
125147
fromAddress,
126148
toAddress,
149+
startTime,
150+
endTime,
127151
order,
128152
sortBy,
129153
});

src/datastore/pg-store.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,8 @@ export class PgStore extends BasePgStore {
14181418
includeUnanchored,
14191419
fromAddress,
14201420
toAddress,
1421+
startTime,
1422+
endTime,
14211423
order,
14221424
sortBy,
14231425
}: {
@@ -1427,6 +1429,8 @@ export class PgStore extends BasePgStore {
14271429
includeUnanchored: boolean;
14281430
fromAddress?: string;
14291431
toAddress?: string;
1432+
startTime?: number;
1433+
endTime?: number;
14301434
order?: 'desc' | 'asc';
14311435
sortBy?: 'block_height' | 'burn_block_time' | 'fee';
14321436
}): Promise<{ results: DbTx[]; total: number }> {
@@ -1458,8 +1462,10 @@ export class PgStore extends BasePgStore {
14581462
const toAddressFilterSql = toAddress
14591463
? sql`AND token_transfer_recipient_address = ${toAddress}`
14601464
: sql``;
1461-
1462-
const noFilters = txTypeFilter.length === 0 && !fromAddress && !toAddress;
1465+
const startTimeFilterSql = startTime ? sql`AND burn_block_time >= ${startTime}` : sql``;
1466+
const endTimeFilterSql = endTime ? sql`AND burn_block_time <= ${endTime}` : sql``;
1467+
const noFilters =
1468+
txTypeFilter.length === 0 && !fromAddress && !toAddress && !startTime && !endTime;
14631469

14641470
const totalQuery: { count: number }[] = noFilters
14651471
? await sql<{ count: number }[]>`
@@ -1473,7 +1479,8 @@ export class PgStore extends BasePgStore {
14731479
${txTypeFilterSql}
14741480
${fromAddressFilterSql}
14751481
${toAddressFilterSql}
1476-
1482+
${startTimeFilterSql}
1483+
${endTimeFilterSql}
14771484
`;
14781485

14791486
const resultQuery: ContractTxQueryResult[] = await sql<ContractTxQueryResult[]>`
@@ -1483,6 +1490,8 @@ export class PgStore extends BasePgStore {
14831490
${txTypeFilterSql}
14841491
${fromAddressFilterSql}
14851492
${toAddressFilterSql}
1493+
${startTimeFilterSql}
1494+
${endTimeFilterSql}
14861495
${orderBySql}
14871496
LIMIT ${limit}
14881497
OFFSET ${offset}

src/tests/tx-tests.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2198,6 +2198,129 @@ describe('tx tests', () => {
21982198
);
21992199
});
22002200

2201+
test('tx list - filter by timestamp', async () => {
2202+
const block1 = new TestBlockBuilder({
2203+
block_height: 1,
2204+
index_block_hash: '0x01',
2205+
burn_block_time: 1710000000,
2206+
})
2207+
.addTx({
2208+
tx_id: '0x1234',
2209+
fee_rate: 1n,
2210+
})
2211+
.build();
2212+
2213+
await db.update(block1);
2214+
2215+
const block2 = new TestBlockBuilder({
2216+
block_height: 2,
2217+
index_block_hash: '0x02',
2218+
parent_block_hash: block1.block.block_hash,
2219+
parent_index_block_hash: block1.block.index_block_hash,
2220+
burn_block_time: 1720000000,
2221+
})
2222+
.addTx({
2223+
tx_id: '0x2234',
2224+
fee_rate: 3n,
2225+
})
2226+
.build();
2227+
await db.update(block2);
2228+
2229+
const block3 = new TestBlockBuilder({
2230+
block_height: 3,
2231+
index_block_hash: '0x03',
2232+
parent_block_hash: block2.block.block_hash,
2233+
parent_index_block_hash: block2.block.index_block_hash,
2234+
burn_block_time: 1730000000,
2235+
})
2236+
.addTx({
2237+
tx_id: '0x3234',
2238+
fee_rate: 2n,
2239+
})
2240+
.build();
2241+
await db.update(block3);
2242+
2243+
const txsReq1 = await supertest(api.server).get(
2244+
`/extended/v1/tx?start_time=${block1.block.burn_block_time}&end_time=${block3.block.burn_block_time}`
2245+
);
2246+
expect(txsReq1.status).toBe(200);
2247+
expect(txsReq1.body).toEqual(
2248+
expect.objectContaining({
2249+
results: [
2250+
expect.objectContaining({
2251+
tx_id: block3.txs[0].tx.tx_id,
2252+
}),
2253+
expect.objectContaining({
2254+
tx_id: block2.txs[0].tx.tx_id,
2255+
}),
2256+
expect.objectContaining({
2257+
tx_id: block1.txs[0].tx.tx_id,
2258+
}),
2259+
],
2260+
})
2261+
);
2262+
2263+
const txsReq2 = await supertest(api.server).get(
2264+
`/extended/v1/tx?start_time=${block2.block.burn_block_time}&end_time=${
2265+
block3.block.burn_block_time - 1
2266+
}`
2267+
);
2268+
expect(txsReq2.status).toBe(200);
2269+
expect(txsReq2.body).toEqual(
2270+
expect.objectContaining({
2271+
results: [
2272+
expect.objectContaining({
2273+
tx_id: block2.txs[0].tx.tx_id,
2274+
}),
2275+
],
2276+
})
2277+
);
2278+
2279+
const txsReq3 = await supertest(api.server).get(
2280+
`/extended/v1/tx?start_time=${block1.block.burn_block_time + 1}`
2281+
);
2282+
expect(txsReq3.status).toBe(200);
2283+
expect(txsReq3.body).toEqual(
2284+
expect.objectContaining({
2285+
results: [
2286+
expect.objectContaining({
2287+
tx_id: block3.txs[0].tx.tx_id,
2288+
}),
2289+
expect.objectContaining({
2290+
tx_id: block2.txs[0].tx.tx_id,
2291+
}),
2292+
],
2293+
})
2294+
);
2295+
2296+
const txsReq4 = await supertest(api.server).get(
2297+
`/extended/v1/tx?end_time=${block3.block.burn_block_time - 1}`
2298+
);
2299+
expect(txsReq4.status).toBe(200);
2300+
expect(txsReq4.body).toEqual(
2301+
expect.objectContaining({
2302+
results: [
2303+
expect.objectContaining({
2304+
tx_id: block2.txs[0].tx.tx_id,
2305+
}),
2306+
expect.objectContaining({
2307+
tx_id: block1.txs[0].tx.tx_id,
2308+
}),
2309+
],
2310+
})
2311+
);
2312+
2313+
const txsReq5 = await supertest(api.server).get(
2314+
`/extended/v1/tx?start_time=${block3.block.burn_block_time + 1}`
2315+
);
2316+
expect(txsReq5.status).toBe(200);
2317+
expect(txsReq5.body).toEqual(
2318+
expect.objectContaining({
2319+
results: [],
2320+
})
2321+
);
2322+
});
2323+
22012324
test('fetch raw tx', async () => {
22022325
const block: DbBlock = {
22032326
block_hash: '0x1234',

0 commit comments

Comments
 (0)