split data access into API-only (alchemy/viem) and DB-first wrappers (tokens/transactions)

alchemy/get-token-metadata: stripped DB logic, now pure API call
viem/get-transaction-details: stripped DB logic, now pure viem call
tokens/get-metadata: new DB-first wrapper for token metadata
transactions/get-details: new DB-first wrapper for tx details
transactions/get-history: stores individual Alchemy transfers in tx_history
  table with blockNum, from, to columns for searchable ordering
This commit is contained in:
2026-05-18 03:03:34 +08:00
parent 135d651622
commit 9e76bd54ce
10 changed files with 466 additions and 235 deletions
+100 -33
View File
@@ -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<typeof getTransactionDetails>
}
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<RPC>({
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<RPC>({
}
},
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<RPC>({
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<RPC>({
},
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<RPC>({
},
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
},
},
@@ -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
}
+26 -1
View File
@@ -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),
}),
)
@@ -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
}
+1
View File
@@ -0,0 +1 @@
export { getTokenMetadata } from './get-metadata'
@@ -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,
}
}
@@ -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,
}
}
@@ -0,0 +1,2 @@
export { getTransactionHistory } from './get-history'
export { getTransactionDetails } from './get-details'
@@ -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<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,
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,
},
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
export * from './getClient'
export * from './get-transaction-details'
export { getTransactionDetails } from './get-transaction-details'