From 357f7071802c9823acdd981a2e49786eb2cfd243 Mon Sep 17 00:00:00 2001 From: TZGyn Date: Fri, 22 May 2026 10:18:33 +0800 Subject: [PATCH] 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 --- apps/desktop/src/bun/index.ts | 8 +- .../src/bun/lib/sync/sync-tx-details.ts | 76 ++++++++++++++++++ .../src/bun/lib/sync/sync-tx-history.ts | 59 +++++++++++--- .../src/bun/lib/transactions/get-details.ts | 78 +------------------ apps/desktop/src/lib/electrobun.ts | 6 +- 5 files changed, 134 insertions(+), 93 deletions(-) create mode 100644 apps/desktop/src/bun/lib/sync/sync-tx-details.ts diff --git a/apps/desktop/src/bun/index.ts b/apps/desktop/src/bun/index.ts index 4b9647e..bddeec8 100644 --- a/apps/desktop/src/bun/index.ts +++ b/apps/desktop/src/bun/index.ts @@ -39,6 +39,7 @@ import { txHistory, evmWallets, } from './lib/db/schema' +import { syncTxDetails } from './lib/sync/sync-tx-details' const env = getENV() @@ -647,12 +648,7 @@ const rpc = BrowserView.defineRPC({ }) if (!key) return null try { - const client = getClient(chainid, key) - const details = await getTransactionDetails( - client, - hash, - chainid, - ) + const details = await getTransactionDetails(hash, chainid) return details } catch (error) { console.log(error) diff --git a/apps/desktop/src/bun/lib/sync/sync-tx-details.ts b/apps/desktop/src/bun/lib/sync/sync-tx-details.ts new file mode 100644 index 0000000..600c787 --- /dev/null +++ b/apps/desktop/src/bun/lib/sync/sync-tx-details.ts @@ -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) + } +} diff --git a/apps/desktop/src/bun/lib/sync/sync-tx-history.ts b/apps/desktop/src/bun/lib/sync/sync-tx-history.ts index 2e89f0c..45f1294 100644 --- a/apps/desktop/src/bun/lib/sync/sync-tx-history.ts +++ b/apps/desktop/src/bun/lib/sync/sync-tx-history.ts @@ -3,6 +3,7 @@ import { numberToHex } from 'viem' import { db } from '../db' import { txHistory, txSyncStatus } from '../db/schema' import { getTransactionHistory } from '../alchemy/get-transaction-history' +import { syncTxDetails } from './sync-tx-details' const PAGE_SIZE = 76 @@ -28,10 +29,15 @@ export const syncTransactionHistory = async ( ? numberToHex(Number(existing.lastSyncBlock) + 1) : undefined - const transfers = await getTransactionHistory(key, chainid, address, { - maxCount: 1000, - fromBlock, - }) + const transfers = await getTransactionHistory( + key, + chainid, + address, + { + maxCount: 1000, + fromBlock, + }, + ) if (!transfers || transfers.length === 0) return @@ -54,12 +60,15 @@ export const syncTransactionHistory = async ( value: t.value?.toString() ?? null, asset: t.asset ?? null, category: t.category ?? null, - rawContractAddress: t.rawContract?.address?.toLowerCase() ?? null, + rawContractAddress: + t.rawContract?.address?.toLowerCase() ?? null, rawContractDecimal: t.rawContract?.decimal ?? null, metadataBlockTimestamp: t.metadata?.blockTimestamp ?? null, raw: JSON.stringify(t), syncedAt: new Date().toISOString(), }) + + await syncTxDetails(chainid, key, t.hash) } flush(prepared) @@ -87,10 +96,42 @@ export const syncTransactionHistory = async ( function flush(prepared: Array) { while (prepared.length > 0) { const batch = prepared.splice(0, PAGE_SIZE) - const cols = ['chainId', 'hash', '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 cols = [ + 'chainId', + 'hash', + '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 dbClient = db.$client diff --git a/apps/desktop/src/bun/lib/transactions/get-details.ts b/apps/desktop/src/bun/lib/transactions/get-details.ts index 153ed97..29df5c3 100644 --- a/apps/desktop/src/bun/lib/transactions/get-details.ts +++ b/apps/desktop/src/bun/lib/transactions/get-details.ts @@ -3,10 +3,8 @@ import { formatEther, formatGwei, type PublicClient } from 'viem' import { db } from '../db' import { transactions, transactionReceipts } from '../db/schema' import { getChainID } from '../alchemy/network' -import { getTransactionDetails as fetchFromViem } from '../viem/get-transaction-details' export const getTransactionDetails = async ( - client: PublicClient, hash: string, chainId: string, ) => { @@ -64,79 +62,5 @@ export const getTransactionDetails = async ( } } - const raw = await fetchFromViem(client, hash) - 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, - } + return null } diff --git a/apps/desktop/src/lib/electrobun.ts b/apps/desktop/src/lib/electrobun.ts index 1dcf9b6..58aceff 100644 --- a/apps/desktop/src/lib/electrobun.ts +++ b/apps/desktop/src/lib/electrobun.ts @@ -236,7 +236,11 @@ type RPC = { response: string[] } evmCreateWallet: { - params: { name: string; phrase: string; passwordHash?: string } + params: { + name: string + phrase: string + passwordHash?: string + } response: { id: string; name: string; createdAt: string } } evmListWallets: {