Skip to content

Commit 6e54969

Browse files
committed
Use audit API for TIPS transaction data
1 parent 98f3742 commit 6e54969

14 files changed

Lines changed: 340 additions & 941 deletions

File tree

.env.example

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
11
NEXT_PUBLIC_BLOCK_EXPLORER_URL=https://base.blockscout.com
22
TIPS_UI_RPC_URL=http://localhost:8545
3-
TIPS_UI_AWS_REGION=us-east-1
4-
TIPS_UI_S3_BUCKET_NAME=tips
5-
TIPS_UI_S3_CONFIG_TYPE=manual
6-
TIPS_UI_S3_ENDPOINT=http://localhost:7000
7-
TIPS_UI_S3_ACCESS_KEY_ID=minioadmin
8-
TIPS_UI_S3_SECRET_ACCESS_KEY=minioadmin
3+
TIPS_UI_AUDIT_RPC_URL=http://localhost:8080

bun.lock

Lines changed: 125 additions & 332 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
"format": "biome format --write"
1111
},
1212
"dependencies": {
13-
"@aws-sdk/client-s3": "3.940.0",
1413
"next": "16.0.7",
1514
"react": "19.2.1",
1615
"react-dom": "19.2.1",

src/app/api/block/[hash]/route.ts

Lines changed: 12 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,10 @@ import { type Block, createPublicClient, type Hash, http } from "viem";
33
import { mainnet } from "viem/chains";
44
import {
55
getAuditEventsByTransactionHash,
6-
isAuditEventBackend,
76
meterBundleResponseFromAuditEvent,
87
transactionMetadataFromAuditEvents,
98
} from "@/lib/audit-events";
10-
import {
11-
type BlockData,
12-
type BlockTransaction,
13-
cacheBlockData,
14-
getBlockFromCache,
15-
getBundleHistory,
16-
getTransactionMetadataByHash,
17-
} from "@/lib/s3";
9+
import type { BlockData, BlockTransaction } from "@/lib/transaction-data";
1810

1911
function serializeBlockData(block: BlockData) {
2012
return {
@@ -88,53 +80,26 @@ async function enrichTransactionWithBundleData(txHash: string): Promise<{
8880
bundleId: string | null;
8981
meterBundleResponse: Record<string, unknown> | null;
9082
}> {
91-
if (isAuditEventBackend()) {
92-
const events = await getAuditEventsByTransactionHash(txHash);
93-
const metadata = transactionMetadataFromAuditEvents(events);
94-
const accepted = events.find(
95-
(event) => event.event_type === "SIMULATION_ACCEPTED",
96-
);
97-
return {
98-
bundleId: metadata?.bundle_ids[0] ?? null,
99-
meterBundleResponse: accepted
100-
? (meterBundleResponseFromAuditEvent(accepted) as unknown as Record<
101-
string,
102-
unknown
103-
>)
104-
: null,
105-
};
106-
}
107-
108-
const metadata = await getTransactionMetadataByHash(txHash);
109-
if (!metadata || metadata.bundle_ids.length === 0) {
110-
return { bundleId: null, meterBundleResponse: null };
111-
}
112-
113-
const bundleId = metadata.bundle_ids[0];
114-
const bundleHistory = await getBundleHistory(bundleId);
115-
if (!bundleHistory) {
116-
return { bundleId, meterBundleResponse: null };
117-
}
118-
119-
const receivedEvent = bundleHistory.history.find(
120-
(e) => e.event === "Received",
83+
const events = await getAuditEventsByTransactionHash(txHash);
84+
const metadata = transactionMetadataFromAuditEvents(events);
85+
const accepted = events.find(
86+
(event) => event.event_type === "SIMULATION_SUCCEEDED",
12187
);
122-
if (!receivedEvent?.data?.bundle?.meter_bundle_response) {
123-
return { bundleId, meterBundleResponse: null };
124-
}
125-
12688
return {
127-
bundleId,
128-
meterBundleResponse: receivedEvent.data.bundle
129-
.meter_bundle_response as unknown as Record<string, unknown>,
89+
bundleId: metadata?.bundle_ids[0] ?? null,
90+
meterBundleResponse: accepted
91+
? (meterBundleResponseFromAuditEvent(accepted) as unknown as Record<
92+
string,
93+
unknown
94+
>)
95+
: null,
13096
};
13197
}
13298

13399
async function buildAndCacheBlockData(
134100
rpcBlock: Block<bigint, true>,
135101
hash: Hash,
136102
number: bigint,
137-
shouldCache = true,
138103
): Promise<BlockData> {
139104
const transactions: BlockTransaction[] = await Promise.all(
140105
rpcBlock.transactions.map(async (tx, index) => {
@@ -161,69 +126,15 @@ async function buildAndCacheBlockData(
161126
cachedAt: Date.now(),
162127
};
163128

164-
if (shouldCache) {
165-
await cacheBlockData(blockData);
166-
}
167-
168129
return blockData;
169130
}
170131

171-
// On OP Stack, the first transaction (index 0) is the L1 attributes deposit transaction.
172-
// This is not a perfect check (ideally we'd check tx.type === 'deposit' or type 0x7e),
173-
// but sufficient for filtering out system transactions that don't need simulation data.
174-
function isSystemTransaction(tx: BlockTransaction): boolean {
175-
return tx.index === 0;
176-
}
177-
178-
async function refetchMissingTransactionSimulations(
179-
block: BlockData,
180-
): Promise<{ updatedBlock: BlockData; hasUpdates: boolean }> {
181-
const transactionsToRefetch = block.transactions.filter(
182-
(tx) => tx.bundleId === null && !isSystemTransaction(tx),
183-
);
184-
185-
if (transactionsToRefetch.length === 0) {
186-
return { updatedBlock: block, hasUpdates: false };
187-
}
188-
189-
const refetchResults = await Promise.all(
190-
transactionsToRefetch.map(async (tx) => {
191-
const enriched = await enrichTransactionWithBundleData(tx.hash);
192-
return { hash: tx.hash, ...enriched };
193-
}),
194-
);
195-
196-
let hasUpdates = false;
197-
const updatedTransactions = block.transactions.map((tx) => {
198-
const refetchResult = refetchResults.find((r) => r.hash === tx.hash);
199-
if (refetchResult && refetchResult.bundleId !== null) {
200-
hasUpdates = true;
201-
return {
202-
...tx,
203-
bundleId: refetchResult.bundleId,
204-
meterBundleResponse: refetchResult.meterBundleResponse,
205-
};
206-
}
207-
return tx;
208-
});
209-
210-
return {
211-
updatedBlock: {
212-
...block,
213-
transactions: updatedTransactions,
214-
cachedAt: hasUpdates ? Date.now() : block.cachedAt,
215-
},
216-
hasUpdates,
217-
};
218-
}
219-
220132
export async function GET(
221133
_request: NextRequest,
222134
{ params }: { params: Promise<{ hash: string }> },
223135
) {
224136
try {
225137
const { hash: identifier } = await params;
226-
const auditBackend = isAuditEventBackend();
227138

228139
// If the identifier is a block number, resolve it to a hash first
229140
if (isBlockNumber(identifier)) {
@@ -232,42 +143,14 @@ export async function GET(
232143
return NextResponse.json({ error: "Block not found" }, { status: 404 });
233144
}
234145

235-
// Check cache by resolved hash
236-
const cachedBlock = auditBackend
237-
? null
238-
: await getBlockFromCache(rpcBlock.hash);
239-
if (cachedBlock) {
240-
const { updatedBlock, hasUpdates } =
241-
await refetchMissingTransactionSimulations(cachedBlock);
242-
if (hasUpdates) {
243-
await cacheBlockData(updatedBlock);
244-
}
245-
return NextResponse.json(serializeBlockData(updatedBlock));
246-
}
247-
248146
const blockData = await buildAndCacheBlockData(
249147
rpcBlock,
250148
rpcBlock.hash,
251149
rpcBlock.number,
252-
!auditBackend,
253150
);
254151
return NextResponse.json(serializeBlockData(blockData));
255152
}
256153

257-
const cachedBlock = auditBackend
258-
? null
259-
: await getBlockFromCache(identifier);
260-
if (cachedBlock) {
261-
const { updatedBlock, hasUpdates } =
262-
await refetchMissingTransactionSimulations(cachedBlock);
263-
264-
if (hasUpdates) {
265-
await cacheBlockData(updatedBlock);
266-
}
267-
268-
return NextResponse.json(serializeBlockData(updatedBlock));
269-
}
270-
271154
const rpcBlock = await fetchBlockFromRpc(identifier);
272155
if (!rpcBlock || !rpcBlock.hash || !rpcBlock.number) {
273156
return NextResponse.json({ error: "Block not found" }, { status: 404 });
@@ -277,7 +160,6 @@ export async function GET(
277160
rpcBlock,
278161
rpcBlock.hash,
279162
rpcBlock.number,
280-
!auditBackend,
281163
);
282164
return NextResponse.json(serializeBlockData(blockData));
283165
} catch (error) {

src/app/api/bundle/[hash]/route.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import { type NextRequest, NextResponse } from "next/server";
22
import {
33
bundleHistoryFromAuditEvents,
44
getAuditEventsByBundle,
5-
isAuditEventBackend,
65
} from "@/lib/audit-events";
7-
import { type BundleEvent, getBundleHistory } from "@/lib/s3";
6+
import type { BundleEvent } from "@/lib/transaction-data";
87

98
export interface BundleHistoryResponse {
109
hash: string;
@@ -18,9 +17,10 @@ export async function GET(
1817
try {
1918
const { hash } = await params;
2019

21-
const bundle = isAuditEventBackend()
22-
? bundleHistoryFromAuditEvents(hash, await getAuditEventsByBundle(hash))
23-
: await getBundleHistory(hash);
20+
const bundle = bundleHistoryFromAuditEvents(
21+
hash,
22+
await getAuditEventsByBundle(hash),
23+
);
2424
if (!bundle) {
2525
return NextResponse.json({ error: "Bundle not found" }, { status: 404 });
2626
}

src/app/api/rejected/route.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,19 @@
11
import { NextResponse } from "next/server";
22
import {
33
getAuditRejectedTransactionEvents,
4-
isAuditEventBackend,
54
rejectedTransactionFromAuditEvent,
65
} from "@/lib/audit-events";
7-
import {
8-
getRejectedTransaction,
9-
listRejectedTransactions,
10-
type RejectedTransaction,
11-
} from "@/lib/s3";
6+
import type { RejectedTransaction } from "@/lib/transaction-data";
127

138
export interface RejectedTransactionsResponse {
149
transactions: RejectedTransaction[];
1510
}
1611

1712
export async function GET() {
1813
try {
19-
if (isAuditEventBackend()) {
20-
const transactions = (await getAuditRejectedTransactionEvents(100))
21-
.map(rejectedTransactionFromAuditEvent)
22-
.filter((tx): tx is RejectedTransaction => tx !== null);
23-
24-
return NextResponse.json({ transactions });
25-
}
26-
27-
const summaries = await listRejectedTransactions(100);
28-
29-
const transactions = (
30-
await Promise.all(
31-
summaries.map((s) => getRejectedTransaction(s.blockNumber, s.txHash)),
32-
)
33-
).filter((tx): tx is RejectedTransaction => tx !== null);
14+
const transactions = (await getAuditRejectedTransactionEvents(100))
15+
.map(rejectedTransactionFromAuditEvent)
16+
.filter((tx): tx is RejectedTransaction => tx !== null);
3417

3518
const response: RejectedTransactionsResponse = { transactions };
3619
return NextResponse.json(response);

src/app/api/txn/[hash]/route.ts

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,9 @@ import {
33
bundleHistoryFromAuditEvents,
44
getAuditEventsByBundle,
55
getAuditEventsByTransactionHash,
6-
isAuditEventBackend,
76
transactionMetadataFromAuditEvents,
87
} from "@/lib/audit-events";
9-
import {
10-
type BundleEvent,
11-
getBundleHistory,
12-
getTransactionMetadataByHash,
13-
} from "@/lib/s3";
8+
import type { BundleEvent } from "@/lib/transaction-data";
149

1510
export interface TransactionEvent {
1611
type: string;
@@ -48,53 +43,28 @@ export async function GET(
4843
try {
4944
const { hash } = await params;
5045

51-
if (isAuditEventBackend()) {
52-
const events = await getAuditEventsByTransactionHash(hash);
53-
const metadata = transactionMetadataFromAuditEvents(events);
54-
if (!metadata) {
55-
return NextResponse.json(
56-
{ error: "Transaction not found" },
57-
{ status: 404 },
58-
);
59-
}
60-
61-
const bundleId = metadata.bundle_ids[0];
62-
const bundle =
63-
bundleId !== undefined
64-
? bundleHistoryFromAuditEvents(
65-
bundleId,
66-
await getAuditEventsByBundle(bundleId),
67-
)
68-
: { history: [] };
69-
70-
const response: TransactionHistoryResponse = {
71-
hash,
72-
bundle_ids: metadata.bundle_ids,
73-
history: bundle?.history ?? [],
74-
};
75-
76-
return NextResponse.json(response);
77-
}
78-
79-
const metadata = await getTransactionMetadataByHash(hash);
80-
46+
const events = await getAuditEventsByTransactionHash(hash);
47+
const metadata = transactionMetadataFromAuditEvents(events);
8148
if (!metadata) {
8249
return NextResponse.json(
8350
{ error: "Transaction not found" },
8451
{ status: 404 },
8552
);
8653
}
8754

88-
// TODO: Can be in multiple bundles
89-
const bundle = await getBundleHistory(metadata.bundle_ids[0]);
90-
if (!bundle) {
91-
return NextResponse.json({ error: "Bundle not found" }, { status: 404 });
92-
}
55+
const bundleId = metadata.bundle_ids[0];
56+
const bundle =
57+
bundleId !== undefined
58+
? bundleHistoryFromAuditEvents(
59+
bundleId,
60+
await getAuditEventsByBundle(bundleId),
61+
)
62+
: { history: [] };
9363

9464
const response: TransactionHistoryResponse = {
9565
hash,
9666
bundle_ids: metadata.bundle_ids,
97-
history: bundle.history,
67+
history: bundle?.history ?? [],
9868
};
9969

10070
return NextResponse.json(response);

src/app/block/[hash]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import Link from "next/link";
44
import { useEffect, useState } from "react";
5-
import type { BlockData, BlockTransaction } from "@/lib/s3";
5+
import type { BlockData, BlockTransaction } from "@/lib/transaction-data";
66

77
const BLOCK_EXPLORER_URL = process.env.NEXT_PUBLIC_BLOCK_EXPLORER_URL;
88

src/app/bundles/[hash]/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import Link from "next/link";
44
import { useEffect, useState } from "react";
55
import type { BundleHistoryResponse } from "@/app/api/bundle/[hash]/route";
6-
import type { BundleTransaction, MeterBundleResponse } from "@/lib/s3";
6+
import type {
7+
BundleTransaction,
8+
MeterBundleResponse,
9+
} from "@/lib/transaction-data";
710

811
const WEI_PER_GWEI = 10n ** 9n;
912
const WEI_PER_ETH = 10n ** 18n;

0 commit comments

Comments
 (0)