move tx detail caching into sync engine, cache during history sync
- new sync-tx-details.ts: extracts tx+receipt caching from get-details.ts - sync-tx-history: calls syncTxDetails per transfer hash during sync - get-details: remove on-the-fly viem fetch + DB write (now handled by sync); returns null placeholder - index: import syncTxDetails; simplify getTransactionDetails handler
This commit is contained in:
@@ -39,6 +39,7 @@ import {
|
|||||||
txHistory,
|
txHistory,
|
||||||
evmWallets,
|
evmWallets,
|
||||||
} from './lib/db/schema'
|
} from './lib/db/schema'
|
||||||
|
import { syncTxDetails } from './lib/sync/sync-tx-details'
|
||||||
|
|
||||||
const env = getENV()
|
const env = getENV()
|
||||||
|
|
||||||
@@ -647,12 +648,7 @@ const rpc = BrowserView.defineRPC<RPC>({
|
|||||||
})
|
})
|
||||||
if (!key) return null
|
if (!key) return null
|
||||||
try {
|
try {
|
||||||
const client = getClient(chainid, key)
|
const details = await getTransactionDetails(hash, chainid)
|
||||||
const details = await getTransactionDetails(
|
|
||||||
client,
|
|
||||||
hash,
|
|
||||||
chainid,
|
|
||||||
)
|
|
||||||
return details
|
return details
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { getClient, getTransactionDetails } from '../viem'
|
||||||
|
import { db } from '../db'
|
||||||
|
import { transactions, transactionReceipts } from '../db/schema'
|
||||||
|
import { getChainID } from '../alchemy/network'
|
||||||
|
|
||||||
|
export const syncTxDetails = async (
|
||||||
|
chainId: string,
|
||||||
|
apiKey: string,
|
||||||
|
hash: string,
|
||||||
|
) => {
|
||||||
|
const client = getClient(chainId, apiKey)
|
||||||
|
|
||||||
|
const id = getChainID(chainId)
|
||||||
|
if (!id) return null
|
||||||
|
|
||||||
|
const raw = await getTransactionDetails(client, hash)
|
||||||
|
if (!raw) return null
|
||||||
|
|
||||||
|
const hashLower = hash.toLowerCase()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.insert(transactions)
|
||||||
|
.values({
|
||||||
|
hash: hashLower,
|
||||||
|
chainId: id,
|
||||||
|
from: raw.tx.from.toLowerCase(),
|
||||||
|
to: raw.tx.to?.toLowerCase() ?? null,
|
||||||
|
value: raw.tx.value.toString(),
|
||||||
|
blockNumber: raw.tx.blockNumber?.toString() ?? null,
|
||||||
|
blockHash: raw.tx.blockHash?.toLowerCase() ?? null,
|
||||||
|
gas: raw.tx.gas.toString(),
|
||||||
|
gasPrice: raw.tx.gasPrice?.toString() ?? null,
|
||||||
|
maxFeePerGas: raw.tx.maxFeePerGas?.toString() ?? null,
|
||||||
|
maxPriorityFeePerGas:
|
||||||
|
raw.tx.maxPriorityFeePerGas?.toString() ?? null,
|
||||||
|
input: raw.tx.input,
|
||||||
|
nonce: raw.tx.nonce,
|
||||||
|
transactionIndex: raw.tx.transactionIndex ?? null,
|
||||||
|
type: raw.tx.type ?? null,
|
||||||
|
v: raw.tx.v?.toString() ?? null,
|
||||||
|
r: raw.tx.r?.toLowerCase() ?? null,
|
||||||
|
s: raw.tx.s?.toLowerCase() ?? null,
|
||||||
|
yParity: raw.tx.yParity ?? null,
|
||||||
|
})
|
||||||
|
.onConflictDoNothing()
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Failed to cache transaction', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.insert(transactionReceipts)
|
||||||
|
.values({
|
||||||
|
transactionHash: hashLower,
|
||||||
|
chainId: id,
|
||||||
|
blockHash: raw.receipt.blockHash.toLowerCase(),
|
||||||
|
blockNumber: raw.receipt.blockNumber.toString(),
|
||||||
|
from: raw.receipt.from.toLowerCase(),
|
||||||
|
to: raw.receipt.to?.toLowerCase() ?? null,
|
||||||
|
cumulativeGasUsed: raw.receipt.cumulativeGasUsed.toString(),
|
||||||
|
gasUsed: raw.receipt.gasUsed.toString(),
|
||||||
|
status: raw.receipt.status,
|
||||||
|
effectiveGasPrice:
|
||||||
|
raw.receipt.effectiveGasPrice?.toString() ?? null,
|
||||||
|
contractAddress:
|
||||||
|
raw.receipt.contractAddress?.toLowerCase() ?? null,
|
||||||
|
type: raw.receipt.type,
|
||||||
|
transactionIndex: raw.receipt.transactionIndex,
|
||||||
|
logsBloom: raw.receipt.logsBloom?.toLowerCase() ?? null,
|
||||||
|
})
|
||||||
|
.onConflictDoNothing()
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Failed to cache transaction receipt', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { numberToHex } from 'viem'
|
|||||||
import { db } from '../db'
|
import { db } from '../db'
|
||||||
import { txHistory, txSyncStatus } from '../db/schema'
|
import { txHistory, txSyncStatus } from '../db/schema'
|
||||||
import { getTransactionHistory } from '../alchemy/get-transaction-history'
|
import { getTransactionHistory } from '../alchemy/get-transaction-history'
|
||||||
|
import { syncTxDetails } from './sync-tx-details'
|
||||||
|
|
||||||
const PAGE_SIZE = 76
|
const PAGE_SIZE = 76
|
||||||
|
|
||||||
@@ -28,10 +29,15 @@ export const syncTransactionHistory = async (
|
|||||||
? numberToHex(Number(existing.lastSyncBlock) + 1)
|
? numberToHex(Number(existing.lastSyncBlock) + 1)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const transfers = await getTransactionHistory(key, chainid, address, {
|
const transfers = await getTransactionHistory(
|
||||||
|
key,
|
||||||
|
chainid,
|
||||||
|
address,
|
||||||
|
{
|
||||||
maxCount: 1000,
|
maxCount: 1000,
|
||||||
fromBlock,
|
fromBlock,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if (!transfers || transfers.length === 0) return
|
if (!transfers || transfers.length === 0) return
|
||||||
|
|
||||||
@@ -54,12 +60,15 @@ export const syncTransactionHistory = async (
|
|||||||
value: t.value?.toString() ?? null,
|
value: t.value?.toString() ?? null,
|
||||||
asset: t.asset ?? null,
|
asset: t.asset ?? null,
|
||||||
category: t.category ?? null,
|
category: t.category ?? null,
|
||||||
rawContractAddress: t.rawContract?.address?.toLowerCase() ?? null,
|
rawContractAddress:
|
||||||
|
t.rawContract?.address?.toLowerCase() ?? null,
|
||||||
rawContractDecimal: t.rawContract?.decimal ?? null,
|
rawContractDecimal: t.rawContract?.decimal ?? null,
|
||||||
metadataBlockTimestamp: t.metadata?.blockTimestamp ?? null,
|
metadataBlockTimestamp: t.metadata?.blockTimestamp ?? null,
|
||||||
raw: JSON.stringify(t),
|
raw: JSON.stringify(t),
|
||||||
syncedAt: new Date().toISOString(),
|
syncedAt: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await syncTxDetails(chainid, key, t.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
flush(prepared)
|
flush(prepared)
|
||||||
@@ -87,10 +96,42 @@ export const syncTransactionHistory = async (
|
|||||||
function flush(prepared: Array<typeof txHistory.$inferInsert>) {
|
function flush(prepared: Array<typeof txHistory.$inferInsert>) {
|
||||||
while (prepared.length > 0) {
|
while (prepared.length > 0) {
|
||||||
const batch = prepared.splice(0, PAGE_SIZE)
|
const batch = prepared.splice(0, PAGE_SIZE)
|
||||||
const cols = ['chainId', 'hash', 'blockNum', '"from"', '"to"', 'value', 'asset', 'category', 'rawContractAddress', 'rawContractDecimal', 'metadataBlockTimestamp', 'raw', 'syncedAt']
|
const cols = [
|
||||||
const colKeys = ['chainId', 'hash', 'blockNum', 'from', 'to', 'value', 'asset', 'category', 'rawContractAddress', 'rawContractDecimal', 'metadataBlockTimestamp', 'raw', 'syncedAt']
|
'chainId',
|
||||||
const placeholders = batch.map(() => `(${colKeys.map(() => '?').join(',')})`).join(',')
|
'hash',
|
||||||
const flat = batch.flatMap((r) => colKeys.map((c) => (r as any)[c] ?? null))
|
'blockNum',
|
||||||
|
'"from"',
|
||||||
|
'"to"',
|
||||||
|
'value',
|
||||||
|
'asset',
|
||||||
|
'category',
|
||||||
|
'rawContractAddress',
|
||||||
|
'rawContractDecimal',
|
||||||
|
'metadataBlockTimestamp',
|
||||||
|
'raw',
|
||||||
|
'syncedAt',
|
||||||
|
]
|
||||||
|
const colKeys = [
|
||||||
|
'chainId',
|
||||||
|
'hash',
|
||||||
|
'blockNum',
|
||||||
|
'from',
|
||||||
|
'to',
|
||||||
|
'value',
|
||||||
|
'asset',
|
||||||
|
'category',
|
||||||
|
'rawContractAddress',
|
||||||
|
'rawContractDecimal',
|
||||||
|
'metadataBlockTimestamp',
|
||||||
|
'raw',
|
||||||
|
'syncedAt',
|
||||||
|
]
|
||||||
|
const placeholders = batch
|
||||||
|
.map(() => `(${colKeys.map(() => '?').join(',')})`)
|
||||||
|
.join(',')
|
||||||
|
const flat = batch.flatMap((r) =>
|
||||||
|
colKeys.map((c) => (r as any)[c] ?? null),
|
||||||
|
)
|
||||||
const sql = `INSERT OR IGNORE INTO tx_history (${cols.join(',')}) VALUES ${placeholders}`
|
const sql = `INSERT OR IGNORE INTO tx_history (${cols.join(',')}) VALUES ${placeholders}`
|
||||||
|
|
||||||
const dbClient = db.$client
|
const dbClient = db.$client
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ import { formatEther, formatGwei, type PublicClient } from 'viem'
|
|||||||
import { db } from '../db'
|
import { db } from '../db'
|
||||||
import { transactions, transactionReceipts } from '../db/schema'
|
import { transactions, transactionReceipts } from '../db/schema'
|
||||||
import { getChainID } from '../alchemy/network'
|
import { getChainID } from '../alchemy/network'
|
||||||
import { getTransactionDetails as fetchFromViem } from '../viem/get-transaction-details'
|
|
||||||
|
|
||||||
export const getTransactionDetails = async (
|
export const getTransactionDetails = async (
|
||||||
client: PublicClient,
|
|
||||||
hash: string,
|
hash: string,
|
||||||
chainId: string,
|
chainId: string,
|
||||||
) => {
|
) => {
|
||||||
@@ -64,79 +62,5 @@ export const getTransactionDetails = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const raw = await fetchFromViem(client, hash)
|
return null
|
||||||
if (!raw) return null
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db
|
|
||||||
.insert(transactions)
|
|
||||||
.values({
|
|
||||||
hash: hashLower,
|
|
||||||
chainId: id,
|
|
||||||
from: raw.tx.from.toLowerCase(),
|
|
||||||
to: raw.tx.to?.toLowerCase() ?? null,
|
|
||||||
value: raw.tx.value.toString(),
|
|
||||||
blockNumber: raw.tx.blockNumber?.toString() ?? null,
|
|
||||||
blockHash: raw.tx.blockHash?.toLowerCase() ?? null,
|
|
||||||
gas: raw.tx.gas.toString(),
|
|
||||||
gasPrice: raw.tx.gasPrice?.toString() ?? null,
|
|
||||||
maxFeePerGas: raw.tx.maxFeePerGas?.toString() ?? null,
|
|
||||||
maxPriorityFeePerGas:
|
|
||||||
raw.tx.maxPriorityFeePerGas?.toString() ?? null,
|
|
||||||
input: raw.tx.input,
|
|
||||||
nonce: raw.tx.nonce,
|
|
||||||
transactionIndex: raw.tx.transactionIndex ?? null,
|
|
||||||
type: raw.tx.type ?? null,
|
|
||||||
v: raw.tx.v?.toString() ?? null,
|
|
||||||
r: raw.tx.r?.toLowerCase() ?? null,
|
|
||||||
s: raw.tx.s?.toLowerCase() ?? null,
|
|
||||||
yParity: raw.tx.yParity ?? null,
|
|
||||||
})
|
|
||||||
.onConflictDoNothing()
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Failed to cache transaction', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db
|
|
||||||
.insert(transactionReceipts)
|
|
||||||
.values({
|
|
||||||
transactionHash: hashLower,
|
|
||||||
chainId: id,
|
|
||||||
blockHash: raw.receipt.blockHash.toLowerCase(),
|
|
||||||
blockNumber: raw.receipt.blockNumber.toString(),
|
|
||||||
from: raw.receipt.from.toLowerCase(),
|
|
||||||
to: raw.receipt.to?.toLowerCase() ?? null,
|
|
||||||
cumulativeGasUsed: raw.receipt.cumulativeGasUsed.toString(),
|
|
||||||
gasUsed: raw.receipt.gasUsed.toString(),
|
|
||||||
status: raw.receipt.status,
|
|
||||||
effectiveGasPrice:
|
|
||||||
raw.receipt.effectiveGasPrice?.toString() ?? null,
|
|
||||||
contractAddress:
|
|
||||||
raw.receipt.contractAddress?.toLowerCase() ?? null,
|
|
||||||
type: raw.receipt.type,
|
|
||||||
transactionIndex: raw.receipt.transactionIndex,
|
|
||||||
logsBloom: raw.receipt.logsBloom?.toLowerCase() ?? null,
|
|
||||||
})
|
|
||||||
.onConflictDoNothing()
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Failed to cache transaction receipt', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
hash: raw.tx.hash,
|
|
||||||
from: raw.tx.from,
|
|
||||||
to: raw.tx.to,
|
|
||||||
value: formatEther(raw.tx.value),
|
|
||||||
blockNumber: raw.receipt.blockNumber?.toString() ?? null,
|
|
||||||
fee: (
|
|
||||||
parseFloat(formatGwei(raw.receipt.gasUsed)) *
|
|
||||||
parseFloat(formatGwei(raw.tx.gasPrice || BigInt(0)))
|
|
||||||
).toString(),
|
|
||||||
gasPrice: formatGwei(raw.tx.gasPrice || BigInt(0)),
|
|
||||||
status: raw.receipt.status === 'success' ? 'success' : 'reverted',
|
|
||||||
type: raw.tx.type,
|
|
||||||
nonce: raw.tx.nonce,
|
|
||||||
input: raw.tx.input,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,7 +236,11 @@ type RPC = {
|
|||||||
response: string[]
|
response: string[]
|
||||||
}
|
}
|
||||||
evmCreateWallet: {
|
evmCreateWallet: {
|
||||||
params: { name: string; phrase: string; passwordHash?: string }
|
params: {
|
||||||
|
name: string
|
||||||
|
phrase: string
|
||||||
|
passwordHash?: string
|
||||||
|
}
|
||||||
response: { id: string; name: string; createdAt: string }
|
response: { id: string; name: string; createdAt: string }
|
||||||
}
|
}
|
||||||
evmListWallets: {
|
evmListWallets: {
|
||||||
|
|||||||
Reference in New Issue
Block a user