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
},
},