diff --git a/apps/desktop/src/bun/index.ts b/apps/desktop/src/bun/index.ts index f813789..594b40a 100644 --- a/apps/desktop/src/bun/index.ts +++ b/apps/desktop/src/bun/index.ts @@ -8,19 +8,18 @@ import { defineElectrobunRPC, type RPCSchema, } from 'electrobun/bun' -import { inspect } from 'util' -import { - getTokenMetadata, - getTokensBalances, - type Transaction, -} from './lib/alchemy' +import { getTokenMetadata } from './lib/tokens' +import { getTokensBalances, type Transaction } from './lib/alchemy' import { tryCatch } from '@koins/utils' -import { getTransactionHistory } from './lib/alchemy/get-transaction-history' +import { + getTransactionHistory, + getTransactionDetails, +} from './lib/transactions' import { migrate } from 'drizzle-orm/bun-sqlite/migrator' import { db } from './lib/db' import { join } from 'path' import { getENV } from './lib/get-env' -import { getClient, getTransactionDetails } from './lib/viem' +import { getClient } from './lib/viem' import { isBinaryInstalled, downloadBinary, @@ -168,7 +167,7 @@ type RPC = { hash: string chainid: string } - response: TxDetailsResult | null + response: ReturnType } openExternal: { params: { @@ -206,7 +205,12 @@ type RPC = { response: { mnemonic: string; address: string } } moneroRestoreWallet: { - params: { name: string; password: string; mnemonic: string; restoreHeight?: number } + params: { + name: string + password: string + mnemonic: string + restoreHeight?: number + } response: { address: string } } moneroOpenWallet: { @@ -509,7 +513,11 @@ 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( + client, + hash, + chainid, + ) return details } catch (error) { console.log(error) @@ -517,12 +525,17 @@ const rpc = BrowserView.defineRPC({ } }, moneroBinaryStatus: async () => { - const status = { installed: isBinaryInstalled(), downloading: moneroDownloading } + const status = { + installed: isBinaryInstalled(), + downloading: moneroDownloading, + } console.log('[rpc] moneroBinaryStatus:', status) return status }, moneroDownloadBinary: async () => { - console.log('[rpc] moneroDownloadBinary: starting download...') + console.log( + '[rpc] moneroDownloadBinary: starting download...', + ) moneroDownloading = true try { await downloadBinary() @@ -543,10 +556,17 @@ const rpc = BrowserView.defineRPC({ await moneroManager.start(daemonAddress) } catch (e) { console.log('[rpc] moneroStart error:', e) - return { running: false, walletOpen: false, connected: false } + return { + running: false, + walletOpen: false, + connected: false, + } } const connected = await moneroManager.isConnected() - console.log('[rpc] moneroStart complete, connected:', connected) + console.log( + '[rpc] moneroStart complete, connected:', + connected, + ) return { running: true, walletOpen: false, connected } }, moneroStop: async () => { @@ -558,32 +578,58 @@ const rpc = BrowserView.defineRPC({ }, moneroCreateWallet: async ({ name, password }) => { console.log('[rpc] moneroCreateWallet:', name) - if (!moneroManager) throw new Error('Monero wallet RPC not started') - const result = await moneroManager.createWallet(name, password) + if (!moneroManager) + throw new Error('Monero wallet RPC not started') + const result = await moneroManager.createWallet( + name, + password, + ) console.log('[rpc] moneroCreateWallet complete') return result }, - moneroRestoreWallet: async ({ name, password, mnemonic, restoreHeight }) => { + moneroRestoreWallet: async ({ + name, + password, + mnemonic, + restoreHeight, + }) => { console.log('[rpc] moneroRestoreWallet:', name) - if (!moneroManager) throw new Error('Monero wallet RPC not started') - await moneroManager.restoreWallet(name, password, mnemonic, restoreHeight) + if (!moneroManager) + throw new Error('Monero wallet RPC not started') + await moneroManager.restoreWallet( + name, + password, + mnemonic, + restoreHeight, + ) const address = await moneroManager.getAddress() - console.log('[rpc] moneroRestoreWallet complete, address:', address) + console.log( + '[rpc] moneroRestoreWallet complete, address:', + address, + ) return { address } }, moneroOpenWallet: async ({ name, password }) => { console.log('[rpc] moneroOpenWallet:', name) - if (!moneroManager) throw new Error('Monero wallet RPC not started') + if (!moneroManager) + throw new Error('Monero wallet RPC not started') await moneroManager.openWallet(name, password) }, moneroGetBalance: async () => { console.log('[rpc] moneroGetBalance') - if (!moneroManager) throw new Error('Monero wallet RPC not started') + if (!moneroManager) + throw new Error('Monero wallet RPC not started') const { balance, unlocked } = await moneroManager.getBalance() const address = await moneroManager.getAddress() const height = await moneroManager.getHeight() const daemonHeight = await moneroManager.getDaemonHeight() - console.log('[rpc] balance:', { balance: balance.toString(), unlocked: unlocked.toString(), address, height, daemonHeight }) + console.log('[rpc] balance:', { + balance: balance.toString(), + unlocked: unlocked.toString(), + address, + height, + daemonHeight, + }) return { balance: balance.toString(), unlocked: unlocked.toString(), @@ -594,40 +640,61 @@ const rpc = BrowserView.defineRPC({ }, moneroGetTransactions: async () => { console.log('[rpc] moneroGetTransactions') - if (!moneroManager) throw new Error('Monero wallet RPC not started') + if (!moneroManager) + throw new Error('Monero wallet RPC not started') const txs = await moneroManager.getTransactions() const result = (txs || []).map((tx: any) => ({ hash: tx.hash, amount: tx.amount?.toString() ?? '0', timestamp: String(tx.timestamp ?? 0), - direction: tx.direction === 'in' ? 'in' as const : 'out' as const, + direction: + tx.direction === 'in' + ? ('in' as const) + : ('out' as const), height: tx.height ?? 0, })) - console.log(`[rpc] moneroGetTransactions: ${result.length} txs`) + console.log( + `[rpc] moneroGetTransactions: ${result.length} txs`, + ) return result }, moneroWalletStatus: async () => { if (!moneroManager) { console.log('[rpc] moneroWalletStatus: not running') - return { running: false, walletOpen: false, connected: false } + return { + running: false, + walletOpen: false, + connected: false, + } } const connected = await moneroManager.isConnected() const walletOpen = await moneroManager.isWalletOpen() - console.log('[rpc] moneroWalletStatus: running, walletOpen:', walletOpen, 'connected:', connected) + console.log( + '[rpc] moneroWalletStatus: running, walletOpen:', + walletOpen, + 'connected:', + connected, + ) return { running: true, walletOpen, connected } }, moneroGetAccounts: async () => { console.log('[rpc] moneroGetAccounts') - if (!moneroManager) throw new Error('Monero wallet RPC not started') + if (!moneroManager) + throw new Error('Monero wallet RPC not started') const accounts = await moneroManager.getAccounts() - console.log(`[rpc] moneroGetAccounts: ${accounts.length} accounts`) + console.log( + `[rpc] moneroGetAccounts: ${accounts.length} accounts`, + ) return accounts }, moneroListWallets: async () => { console.log('[rpc] moneroListWallets') - if (!moneroManager) throw new Error('Monero wallet RPC not started') + if (!moneroManager) + throw new Error('Monero wallet RPC not started') const wallets = moneroManager.listWallets() - console.log(`[rpc] moneroListWallets: ${wallets.join(', ') || 'none'}`) + console.log( + `[rpc] moneroListWallets: ${wallets.join(', ') || 'none'}`, + ) return wallets }, }, diff --git a/apps/desktop/src/bun/lib/alchemy/get-token-metadata.ts b/apps/desktop/src/bun/lib/alchemy/get-token-metadata.ts index f39a751..1f04f18 100644 --- a/apps/desktop/src/bun/lib/alchemy/get-token-metadata.ts +++ b/apps/desktop/src/bun/lib/alchemy/get-token-metadata.ts @@ -1,6 +1,3 @@ -import { eq, and } from 'drizzle-orm' -import { db } from '../db' -import { tokenMetadata } from '../db/schema' import { post } from './call' import { getChainID, getURL } from './network' @@ -12,36 +9,9 @@ export const getTokenMetadata = async ( const chainId = getChainID(chainid) if (!chainId) return - const cached = db - .select() - .from(tokenMetadata) - .where( - and( - eq(tokenMetadata.chainId, chainId), - eq(tokenMetadata.contractAddress, tokenAddress.toLowerCase()), - ), - ) - .get() + const url = getURL({ chainId, key }) - if (cached) { - return { - jsonrpc: '2.0' as const, - id: 0, - result: { - name: cached.name ?? null, - symbol: cached.symbol, - decimals: cached.decimals ?? null, - logo: cached.logo ?? null, - }, - } - } - - const url = getURL({ - chainId, - key, - }) - - const result = await post<{ + return await post<{ name: string | null symbol: string | null decimals: number | null @@ -52,24 +22,4 @@ export const getTokenMetadata = async ( method: 'alchemy_getTokenMetadata', params: [tokenAddress], }) - - if (result?.result?.symbol) { - try { - await db - .insert(tokenMetadata) - .values({ - name: result.result.name, - symbol: result.result.symbol, - decimals: result.result.decimals, - logo: result.result.logo, - contractAddress: tokenAddress.toLowerCase(), - chainId, - }) - .onConflictDoNothing() - } catch (e) { - console.log('Failed to cache token metadata', e) - } - } - - return result } diff --git a/apps/desktop/src/bun/lib/db/schema.ts b/apps/desktop/src/bun/lib/db/schema.ts index 75c06cb..ac21d02 100644 --- a/apps/desktop/src/bun/lib/db/schema.ts +++ b/apps/desktop/src/bun/lib/db/schema.ts @@ -1,4 +1,4 @@ -import { int, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core' +import { int, sqliteTable, text, uniqueIndex, index } from 'drizzle-orm/sqlite-core' export const tokenMetadata = sqliteTable( 'token_metadata', @@ -71,3 +71,28 @@ export const transactionReceipts = sqliteTable( ), }), ) + +export const txHistory = sqliteTable( + 'tx_history', + { + chainId: text().notNull(), + hash: text().notNull(), + blockNum: text(), + from: text(), + to: text(), + value: text(), + asset: text(), + category: text(), + rawContractAddress: text(), + rawContractDecimal: text(), + metadataBlockTimestamp: text(), + raw: text().notNull(), + syncedAt: text().notNull(), + }, + (table) => ({ + historyUnique: uniqueIndex('tx_history_unique').on(table.chainId, table.hash, table.category, table.rawContractAddress), + blockIdx: index('tx_history_block_idx').on(table.chainId, table.blockNum), + fromIdx: index('tx_history_from_idx').on(table.from), + toIdx: index('tx_history_to_idx').on(table.to), + }), +) diff --git a/apps/desktop/src/bun/lib/tokens/get-metadata.ts b/apps/desktop/src/bun/lib/tokens/get-metadata.ts new file mode 100644 index 0000000..cd7f310 --- /dev/null +++ b/apps/desktop/src/bun/lib/tokens/get-metadata.ts @@ -0,0 +1,64 @@ +import { eq, and } from 'drizzle-orm' +import { db } from '../db' +import { tokenMetadata } from '../db/schema' +import { getChainID } from '../alchemy/network' +import { getTokenMetadata as getTokenMetadataFromAlchemy } from '../alchemy/get-token-metadata' + +export const getTokenMetadata = async ( + key: string, + chainid: string, + tokenAddress: string, +) => { + const chainId = getChainID(chainid) + if (!chainId) return + + const cached = db + .select() + .from(tokenMetadata) + .where( + and( + eq(tokenMetadata.chainId, chainId), + eq(tokenMetadata.contractAddress, tokenAddress.toLowerCase()), + ), + ) + .get() + + if (cached) { + return { + jsonrpc: '2.0' as const, + id: 0, + result: { + name: cached.name ?? null, + symbol: cached.symbol, + decimals: cached.decimals ?? null, + logo: cached.logo ?? null, + }, + } + } + + const result = await getTokenMetadataFromAlchemy( + key, + chainid, + tokenAddress, + ) + + if (result?.result?.symbol) { + try { + await db + .insert(tokenMetadata) + .values({ + name: result.result.name, + symbol: result.result.symbol, + decimals: result.result.decimals, + logo: result.result.logo, + contractAddress: tokenAddress.toLowerCase(), + chainId, + }) + .onConflictDoNothing() + } catch (e) { + console.log('Failed to cache token metadata', e) + } + } + + return result +} diff --git a/apps/desktop/src/bun/lib/tokens/index.ts b/apps/desktop/src/bun/lib/tokens/index.ts new file mode 100644 index 0000000..778f742 --- /dev/null +++ b/apps/desktop/src/bun/lib/tokens/index.ts @@ -0,0 +1 @@ +export { getTokenMetadata } from './get-metadata' diff --git a/apps/desktop/src/bun/lib/transactions/get-details.ts b/apps/desktop/src/bun/lib/transactions/get-details.ts new file mode 100644 index 0000000..153ed97 --- /dev/null +++ b/apps/desktop/src/bun/lib/transactions/get-details.ts @@ -0,0 +1,142 @@ +import { eq, and } from 'drizzle-orm' +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, +) => { + if (!hash.startsWith('0x')) 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 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, + } +} diff --git a/apps/desktop/src/bun/lib/transactions/get-history.ts b/apps/desktop/src/bun/lib/transactions/get-history.ts new file mode 100644 index 0000000..aa07827 --- /dev/null +++ b/apps/desktop/src/bun/lib/transactions/get-history.ts @@ -0,0 +1,92 @@ +import { eq, and, or, desc } from 'drizzle-orm' +import { db } from '../db' +import { txHistory } from '../db/schema' +import { getTransactionHistory as fetchFromAlchemy } from '../alchemy/get-transaction-history' +import type { Transaction } from '../alchemy/types' + +export const getTransactionHistory = async ( + key: string, + chainid: string, + address: string, + opts: { maxCount: number }, +) => { + const result = await fetchFromAlchemy(key, chainid, address, opts) + + if (result) { + for (const t of result) { + if (!t.hash) continue + const existing = await db.query.txHistory.findFirst({ + where: { + hash: t.hash, + chainId: chainid, + }, + }) + + if (!existing) { + await db.insert(txHistory).values({ + chainId: chainid, + hash: t.hash, + blockNum: t.blockNum + ? String(parseInt(t.blockNum, 16)) + : null, + from: t.from?.toLowerCase() ?? null, + to: t.to?.toLowerCase() ?? null, + value: t.value?.toString() ?? null, + asset: t.asset ?? null, + category: t.category ?? 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(), + }) + } + } + } + + const addrLower = address.toLowerCase() + const rows = db + .select() + .from(txHistory) + .where( + and( + eq(txHistory.chainId, chainid), + or( + eq(txHistory.from, addrLower), + eq(txHistory.to, addrLower), + ), + ), + ) + .orderBy(desc(txHistory.blockNum)) + .all() + + return rows.map(toTransfer) +} + +function toTransfer(row: typeof txHistory.$inferSelect): Transaction { + if (row.raw) { + return JSON.parse(row.raw) as Transaction + } + return { + hash: row.hash, + blockNum: row.blockNum + ? `0x${parseInt(row.blockNum).toString(16)}` + : undefined, + from: row.from ?? undefined, + to: row.to ?? undefined, + value: row.value ? Number(row.value) : undefined, + asset: row.asset ?? undefined, + category: row.category as Transaction['category'], + rawContract: + row.rawContractAddress || row.rawContractDecimal + ? { + address: row.rawContractAddress ?? undefined, + decimal: row.rawContractDecimal ?? undefined, + } + : undefined, + metadata: row.metadataBlockTimestamp + ? { blockTimestamp: row.metadataBlockTimestamp } + : undefined, + } +} diff --git a/apps/desktop/src/bun/lib/transactions/index.ts b/apps/desktop/src/bun/lib/transactions/index.ts new file mode 100644 index 0000000..27d4057 --- /dev/null +++ b/apps/desktop/src/bun/lib/transactions/index.ts @@ -0,0 +1,2 @@ +export { getTransactionHistory } from './get-history' +export { getTransactionDetails } from './get-details' diff --git a/apps/desktop/src/bun/lib/viem/get-transaction-details.ts b/apps/desktop/src/bun/lib/viem/get-transaction-details.ts index edc9dc6..78812f6 100644 --- a/apps/desktop/src/bun/lib/viem/get-transaction-details.ts +++ b/apps/desktop/src/bun/lib/viem/get-transaction-details.ts @@ -1,162 +1,50 @@ -import { eq, and } from 'drizzle-orm' -import { - isHex, - formatEther, - type PublicClient, - formatGwei, -} from 'viem' -import { db } from '../db' -import { transactions, transactionReceipts } from '../db/schema' -import { getChainID } from '../alchemy/network' - -export type TransactionDetails = { - hash: string - from: string - to: string | null - value: string - blockNumber: string | null - fee: string - gasPrice: string - status: 'success' | 'reverted' - type: string - nonce: number - input: string -} +import { isHex, type PublicClient } from 'viem' export const getTransactionDetails = async ( client: PublicClient, hash: string, - chainId: string, -): Promise => { +) => { 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, - to: tx.to, - value: formatEther(tx.value), - blockNumber: receipt.blockNumber?.toString() ?? null, - fee: ( - parseFloat(formatGwei(receipt.gasUsed)) * - parseFloat(formatGwei(tx.gasPrice || BigInt(0))) - ).toString(), - gasPrice: formatGwei(tx.gasPrice || BigInt(0)), - status: receipt.status === 'success' ? 'success' : 'reverted', - type: tx.type, - nonce: tx.nonce, - input: tx.input, + tx: { + hash: tx.hash, + from: tx.from, + to: tx.to ?? null, + value: tx.value, + blockNumber: tx.blockNumber ?? null, + gasPrice: tx.gasPrice ?? null, + gas: tx.gas, + input: tx.input, + nonce: tx.nonce, + type: tx.type ?? 'legacy', + blockHash: tx.blockHash ?? null, + maxFeePerGas: tx.maxFeePerGas ?? null, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas ?? null, + transactionIndex: tx.transactionIndex ?? null, + v: tx.v ?? null, + r: tx.r?.toString() ?? null, + s: tx.s?.toString() ?? null, + yParity: tx.yParity ?? null, + }, + receipt: { + blockHash: receipt.blockHash, + blockNumber: receipt.blockNumber, + from: receipt.from, + to: receipt.to ?? null, + cumulativeGasUsed: receipt.cumulativeGasUsed, + gasUsed: receipt.gasUsed, + status: receipt.status, + effectiveGasPrice: receipt.effectiveGasPrice ?? null, + contractAddress: receipt.contractAddress ?? null, + type: receipt.type, + transactionIndex: receipt.transactionIndex, + logsBloom: receipt.logsBloom?.toString() ?? null, + }, } } diff --git a/apps/desktop/src/bun/lib/viem/index.ts b/apps/desktop/src/bun/lib/viem/index.ts index 6730f46..202fb9a 100644 --- a/apps/desktop/src/bun/lib/viem/index.ts +++ b/apps/desktop/src/bun/lib/viem/index.ts @@ -1,2 +1,2 @@ export * from './getClient' -export * from './get-transaction-details' +export { getTransactionDetails } from './get-transaction-details'