add sqlite to cache token metadata

This commit is contained in:
2026-05-14 20:49:54 +08:00
parent 92156f572d
commit cae5d4023f
15 changed files with 296 additions and 8 deletions
+19
View File
@@ -0,0 +1,19 @@
import { defineConfig } from 'drizzle-kit'
import envPaths from 'env-paths'
import { join } from 'path'
import { mkdirSync } from 'fs'
import { getENV } from './src/bun/lib/get-env'
const env = getENV()
const { data } = envPaths(env.appName, { suffix: '' })
mkdirSync(data, { recursive: true })
export default defineConfig({
out: './drizzle',
schema: './src/bun/lib/db/schema.ts',
dialect: 'sqlite',
dbCredentials: {
url: join(data, env.dbFilename),
},
})
@@ -0,0 +1,10 @@
CREATE TABLE `token_metadata` (
`name` text,
`symbol` text NOT NULL,
`decimals` integer,
`logo` text,
`contractAddress` text NOT NULL,
`chainId` text NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `contract_unique` ON `token_metadata` (`chainId`,`contractAddress`);
@@ -0,0 +1,93 @@
{
"version": "7",
"dialect": "sqlite",
"id": "2845d968-33cd-48b6-8944-03e0e46a4a90",
"prevIds": [
"00000000-0000-0000-0000-000000000000"
],
"ddl": [
{
"name": "token_metadata",
"entityType": "tables"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "name",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "symbol",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "decimals",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "logo",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "contractAddress",
"entityType": "columns",
"table": "token_metadata"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "chainId",
"entityType": "columns",
"table": "token_metadata"
},
{
"columns": [
{
"value": "chainId",
"isExpression": false
},
{
"value": "contractAddress",
"isExpression": false
}
],
"isUnique": true,
"where": null,
"origin": "manual",
"name": "contract_unique",
"entityType": "indexes",
"table": "token_metadata"
}
],
"renames": []
}
+1
View File
@@ -11,6 +11,7 @@ export default {
copy: {
'dist/index.html': 'views/mainview/index.html',
'dist/assets': 'views/mainview/assets',
'drizzle': 'drizzle',
},
// Ignore Vite output in watch mode — HMR handles view rebuilds separately
watchIgnore: ['dist/**'],
+10 -2
View File
@@ -9,11 +9,14 @@
"hmr": "vite --port 5173",
"build:dev": "vite build && electrobun build",
"build:canary": "vite build && electrobun build --env=canary",
"build:stable": "vite build && electrobun build --env=stable"
"build:stable": "vite build && electrobun build --env=stable",
"db:push": "bun x --bun drizzle-kit push",
"db:generate": "drizzle-kit generate"
},
"dependencies": {
"@koins/utils": "workspace:*",
"@alchemy/wallet-apis": "^5.0.0-beta.28",
"@koins/utils": "workspace:*",
"drizzle-orm": "^1.0.0-beta.22",
"electrobun": "^1.18.1",
"ethers": "^6.16.0",
"mode-watcher": "^1.1.0",
@@ -27,9 +30,13 @@
"@sveltejs/vite-plugin-svelte": "^7.0.0",
"@tailwindcss/vite": "^4.2.2",
"@tanstack/table-core": "^8.21.3",
"@types/env-paths": "^2.1.2",
"@types/node": "^25.7.0",
"bits-ui": "^2.16.5",
"bun-types": "^1.3.14",
"clsx": "^2.1.1",
"concurrently": "^9.2.1",
"drizzle-kit": "^1.0.0-beta.22",
"embla-carousel-svelte": "^8.6.0",
"formsnap": "^2.0.1",
"layerchart": "2.0.0-next.48",
@@ -46,6 +53,7 @@
"tailwind-merge": "^3.5.0",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.2.2",
"tsx": "^4.21.0",
"tw-animate-css": "^1.4.0",
"typescript": "^6.0.2",
"vaul-svelte": "^1.0.0-next.7",
+16 -1
View File
@@ -2,15 +2,30 @@ import {
ApplicationMenu,
BrowserView,
BrowserWindow,
PATHS,
Updater,
Utils,
defineElectrobunRPC,
type RPCSchema,
Utils,
} from 'electrobun/bun'
import { inspect } from 'util'
import { getTokenMetadata, getTokensBalances } from './lib/alchemy'
import { tryCatch } from '@koins/utils'
import { getTransactionHistory } from './lib/alchemy/get-transaction-history'
import { migrate } from 'drizzle-orm/bun-sqlite/migrator'
import { db } from './lib/db'
import { join } from 'path'
import { getENV } from './lib/get-env'
const env = getENV()
console.log(env)
if (env.env === 'prod') {
migrate(db, {
migrationsFolder: join(PATHS.RESOURCES_FOLDER, 'app', 'drizzle'),
})
}
const DEV_SERVER_PORT = 5173
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`
@@ -1,3 +1,6 @@
import { eq, and } from 'drizzle-orm'
import { db } from '../db'
import { tokenMetadata } from '../db/schema'
import { post } from './call'
import { getChainID, getURL } from './network'
@@ -9,6 +12,30 @@ 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()
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,
@@ -26,5 +53,23 @@ export const getTokenMetadata = async (
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
}
@@ -35,6 +35,7 @@ export const getTransactionHistory = async (
const outRequest = post<{
transfers: Transaction[]
pageKey: string
}>(url, body(address, undefined))
const inRequest = post<{
+5
View File
@@ -0,0 +1,5 @@
import { drizzle } from 'drizzle-orm/bun-sqlite'
import { sqlite } from '../sqlite'
import { relations } from './relations'
export const db = drizzle({ client: sqlite, relations })
+4
View File
@@ -0,0 +1,4 @@
import { defineRelations } from 'drizzle-orm'
import * as schema from './schema'
export const relations = defineRelations(schema, () => ({}))
+19
View File
@@ -0,0 +1,19 @@
import { int, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'
export const tokenMetadata = sqliteTable(
'token_metadata',
{
name: text(),
symbol: text().notNull(),
decimals: int(),
logo: text(),
contractAddress: text().notNull(),
chainId: text().notNull(),
},
(table) => ({
contractUnique: uniqueIndex('contract_unique').on(
table.chainId,
table.contractAddress,
),
}),
)
+19
View File
@@ -0,0 +1,19 @@
const env = Bun.env.ELECTROBUN_ENV as 'dev' | 'prod' | undefined
export const getENV = () => {
let appName
let dbFilename
if (env === 'prod') {
appName = 'koins'
dbFilename = 'koins.db'
} else {
appName = 'koins-dev'
dbFilename = 'koins-dev.db'
}
return {
env: env || 'dev',
appName,
dbFilename,
}
}
+16
View File
@@ -0,0 +1,16 @@
import { Database } from 'bun:sqlite'
import envPaths from 'env-paths'
import { join } from 'path'
import { mkdirSync } from 'fs'
import { getENV } from '../get-env'
const env = getENV()
const { data } = envPaths(env.appName, { suffix: '' })
mkdirSync(data, { recursive: true })
console.log(data)
export const sqlite = new Database(join(data, env.dbFilename), {
create: true,
})
+2 -1
View File
@@ -12,6 +12,7 @@
"paths": {
"$lib": ["./src/lib"],
"$lib/*": ["./src/lib/*"]
}
},
"types": ["bun-types", "node"]
}
}