cache transaction and receipt data in sqlite

This commit is contained in:
2026-05-16 21:09:22 +08:00
parent 49ada2febd
commit 6b7a773845
6 changed files with 676 additions and 3 deletions
@@ -0,0 +1,41 @@
CREATE TABLE `transaction_receipts` (
`transactionHash` text NOT NULL,
`chainId` text NOT NULL,
`blockHash` text NOT NULL,
`blockNumber` text NOT NULL,
`from` text NOT NULL,
`to` text,
`cumulativeGasUsed` text NOT NULL,
`gasUsed` text NOT NULL,
`status` text NOT NULL,
`effectiveGasPrice` text,
`contractAddress` text,
`type` text NOT NULL,
`transactionIndex` integer NOT NULL,
`logsBloom` text
);
--> statement-breakpoint
CREATE TABLE `transactions` (
`hash` text NOT NULL,
`chainId` text NOT NULL,
`from` text NOT NULL,
`to` text,
`value` text NOT NULL,
`blockNumber` text,
`blockHash` text,
`gas` text NOT NULL,
`gasPrice` text,
`maxFeePerGas` text,
`maxPriorityFeePerGas` text,
`input` text NOT NULL,
`nonce` integer NOT NULL,
`transactionIndex` integer,
`type` text,
`v` text,
`r` text,
`s` text,
`yParity` integer
);
--> statement-breakpoint
CREATE UNIQUE INDEX `receipt_unique` ON `transaction_receipts` (`chainId`,`transactionHash`);--> statement-breakpoint
CREATE UNIQUE INDEX `tx_unique` ON `transactions` (`chainId`,`hash`);
@@ -0,0 +1,467 @@
{
"version": "7",
"dialect": "sqlite",
"id": "3271a353-c528-4d23-a2ff-9be22f763b9d",
"prevIds": [
"2845d968-33cd-48b6-8944-03e0e46a4a90"
],
"ddl": [
{
"name": "token_metadata",
"entityType": "tables"
},
{
"name": "transaction_receipts",
"entityType": "tables"
},
{
"name": "transactions",
"entityType": "tables"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "name",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "symbol",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "decimals",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "logo",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "contractAddress",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "chainId",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "transactionHash",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "chainId",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "blockHash",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "blockNumber",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "from",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "to",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "cumulativeGasUsed",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "gasUsed",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "status",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "effectiveGasPrice",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "contractAddress",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "type",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "transactionIndex",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "logsBloom",
"entityType": "columns",
"table": "transaction_receipts"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "hash",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "chainId",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "from",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "to",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "value",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "blockNumber",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "blockHash",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "gas",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "gasPrice",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "maxFeePerGas",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "maxPriorityFeePerGas",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "input",
"entityType": "columns",
"table": "transactions"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "nonce",
"entityType": "columns",
"table": "transactions"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "transactionIndex",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "type",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "v",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "r",
"entityType": "columns",
"table": "transactions"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "s",
"entityType": "columns",
"table": "transactions"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "yParity",
"entityType": "columns",
"table": "transactions"
},
{
"columns": [
{
"value": "chainId",
"isExpression": false
},
{
"value": "contractAddress",
"isExpression": false
}
],
"isUnique": true,
"where": null,
"origin": "manual",
"name": "contract_unique",
"entityType": "indexes",
"table": "token_metadata"
},
{
"columns": [
{
"value": "chainId",
"isExpression": false
},
{
"value": "transactionHash",
"isExpression": false
}
],
"isUnique": true,
"where": null,
"origin": "manual",
"name": "receipt_unique",
"entityType": "indexes",
"table": "transaction_receipts"
},
{
"columns": [
{
"value": "chainId",
"isExpression": false
},
{
"value": "hash",
"isExpression": false
}
],
"isUnique": true,
"where": null,
"origin": "manual",
"name": "tx_unique",
"entityType": "indexes",
"table": "transactions"
}
],
"renames": []
}
+1 -1
View File
@@ -434,7 +434,7 @@ const rpc = BrowserView.defineRPC<RPC>({
if (!key) return null
try {
const client = getClient(chainid, key)
const details = await getTransactionDetails(client, hash)
const details = await getTransactionDetails(client, hash, chainid)
return details
} catch (error) {
console.log(error)
+54
View File
@@ -17,3 +17,57 @@ export const tokenMetadata = sqliteTable(
),
}),
)
export const transactions = sqliteTable(
'transactions',
{
hash: text().notNull(),
chainId: text().notNull(),
from: text().notNull(),
to: text(),
value: text().notNull(),
blockNumber: text(),
blockHash: text(),
gas: text().notNull(),
gasPrice: text(),
maxFeePerGas: text(),
maxPriorityFeePerGas: text(),
input: text().notNull(),
nonce: int().notNull(),
transactionIndex: int(),
type: text(),
v: text(),
r: text(),
s: text(),
yParity: int(),
},
(table) => ({
txUnique: uniqueIndex('tx_unique').on(table.chainId, table.hash),
}),
)
export const transactionReceipts = sqliteTable(
'transaction_receipts',
{
transactionHash: text().notNull(),
chainId: text().notNull(),
blockHash: text().notNull(),
blockNumber: text().notNull(),
from: text().notNull(),
to: text(),
cumulativeGasUsed: text().notNull(),
gasUsed: text().notNull(),
status: text().notNull(),
effectiveGasPrice: text(),
contractAddress: text(),
type: text().notNull(),
transactionIndex: int().notNull(),
logsBloom: text(),
},
(table) => ({
receiptUnique: uniqueIndex('receipt_unique').on(
table.chainId,
table.transactionHash,
),
}),
)
@@ -1,10 +1,13 @@
import { eq, and } from 'drizzle-orm'
import {
isHex,
formatEther,
type PublicClient,
formatGwei,
decodeFunctionData,
} from 'viem'
import { db } from '../db'
import { transactions, transactionReceipts } from '../db/schema'
import { getChainID } from '../alchemy/network'
export type TransactionDetails = {
hash: string
@@ -23,14 +26,123 @@ export type TransactionDetails = {
export const getTransactionDetails = async (
client: PublicClient,
hash: string,
chainId: string,
): Promise<TransactionDetails | null> => {
if (!isHex(hash)) return null
const id = getChainID(chainId)
if (!id) return null
const hashLower = hash.toLowerCase()
const cachedTx = db
.select()
.from(transactions)
.where(
and(
eq(transactions.hash, hashLower),
eq(transactions.chainId, id),
),
)
.get()
const cachedReceipt = db
.select()
.from(transactionReceipts)
.where(
and(
eq(transactionReceipts.transactionHash, hashLower),
eq(transactionReceipts.chainId, id),
),
)
.get()
if (cachedTx && cachedReceipt) {
const value = BigInt(cachedTx.value)
const gasUsed = BigInt(cachedReceipt.gasUsed)
const gasPrice = cachedTx.gasPrice
? BigInt(cachedTx.gasPrice)
: BigInt(0)
return {
hash: cachedTx.hash,
from: cachedTx.from,
to: cachedTx.to,
value: formatEther(value),
blockNumber: cachedReceipt.blockNumber,
fee: (
parseFloat(formatGwei(gasUsed)) *
parseFloat(formatGwei(gasPrice))
).toString(),
gasPrice: formatGwei(gasPrice),
status: cachedReceipt.status as 'success' | 'reverted',
type: cachedTx.type ?? 'legacy',
nonce: cachedTx.nonce,
input: cachedTx.input,
}
}
const [tx, receipt] = await Promise.all([
client.getTransaction({ hash }),
client.getTransactionReceipt({ hash }),
])
try {
await db
.insert(transactions)
.values({
hash: hashLower,
chainId: id,
from: tx.from.toLowerCase(),
to: tx.to?.toLowerCase() ?? null,
value: tx.value.toString(),
blockNumber: tx.blockNumber?.toString() ?? null,
blockHash: tx.blockHash?.toLowerCase() ?? null,
gas: tx.gas.toString(),
gasPrice: tx.gasPrice?.toString() ?? null,
maxFeePerGas: tx.maxFeePerGas?.toString() ?? null,
maxPriorityFeePerGas:
tx.maxPriorityFeePerGas?.toString() ?? null,
input: tx.input,
nonce: tx.nonce,
transactionIndex: tx.transactionIndex ?? null,
type: tx.type ?? null,
v: tx.v?.toString() ?? null,
r: tx.r?.toLowerCase() ?? null,
s: tx.s?.toLowerCase() ?? null,
yParity: tx.yParity ?? null,
})
.onConflictDoNothing()
} catch (e) {
console.log('Failed to cache transaction', e)
}
try {
await db
.insert(transactionReceipts)
.values({
transactionHash: hashLower,
chainId: id,
blockHash: receipt.blockHash.toLowerCase(),
blockNumber: receipt.blockNumber.toString(),
from: receipt.from.toLowerCase(),
to: receipt.to?.toLowerCase() ?? null,
cumulativeGasUsed: receipt.cumulativeGasUsed.toString(),
gasUsed: receipt.gasUsed.toString(),
status: receipt.status,
effectiveGasPrice:
receipt.effectiveGasPrice?.toString() ?? null,
contractAddress:
receipt.contractAddress?.toLowerCase() ?? null,
type: receipt.type,
transactionIndex: receipt.transactionIndex,
logsBloom: receipt.logsBloom?.toLowerCase() ?? null,
})
.onConflictDoNothing()
} catch (e) {
console.log('Failed to cache transaction receipt', e)
}
return {
hash: tx.hash,
from: tx.from,
@@ -132,7 +132,6 @@
<p>{details.type}</p>
</div>
</div>
</div>
<div class="mt-4">
<Button