move transaction history to alchemy lib

This commit is contained in:
2026-05-14 01:50:53 +08:00
parent f47491ad86
commit 90a3086d52
7 changed files with 170 additions and 86 deletions
+16 -46
View File
@@ -10,6 +10,7 @@ import {
import { inspect } from 'util'
import { getTokenMetadata, getTokensBalances } from './lib/alchemy'
import { tryCatch } from '@koins/utils'
import { getTransactionHistory } from './lib/alchemy/get-transaction-history'
const DEV_SERVER_PORT = 5173
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`
@@ -94,7 +95,7 @@ type RPC = {
service: string
name: string
}
response: Promise<string | null>
response: string | null
}
setSecret: {
params: {
@@ -102,27 +103,27 @@ type RPC = {
name: string
value: string
}
response: Promise<void>
response: void
}
fetchTxHistory: {
params: {
address: string
chainid: string
}
response: Promise<TxEntry[]>
response: TxEntry[]
}
fetchTokenBalances: {
params: {
address: string
chainid: string
}
response: Promise<TokenBalanceResult[]>
response: TokenBalanceResult[]
}
openExternal: {
params: {
url: string
}
response: Promise<void>
response: void
}
}
messages: {}
@@ -138,47 +139,16 @@ async function fetchAlchemyTransfers(
chainid: string,
address: string,
): Promise<any[]> {
const network = ALCHEMY_NETWORKS[chainid]
if (!network) return []
const url = `https://${network}.g.alchemy.com/v2/${key}`
const transactions = await getTransactionHistory(
key,
chainid,
address,
{ maxCount: 10 },
)
const body = (fromAddress?: string, toAddress?: string) => ({
jsonrpc: '2.0',
id: 0,
method: 'alchemy_getAssetTransfers',
params: [
{
...(fromAddress ? { fromAddress } : {}),
...(toAddress ? { toAddress } : {}),
category: ['external', 'erc20'],
withMetadata: true,
maxCount: '0x3E8',
},
],
})
if (!transactions) return []
const [outRes, inRes] = await Promise.all([
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body(address, undefined)),
}).then((r) => r.json()),
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body(undefined, address)),
}).then((r) => r.json()),
])
const outgoing: any[] = outRes.result?.transfers ?? []
const incoming: any[] = inRes.result?.transfers ?? []
const seen = new Set<string>()
return [...outgoing, ...incoming].filter((t) => {
if (seen.has(t.hash)) return false
seen.add(t.hash)
return true
})
return transactions
}
function formatUnits(balance: string, decimals: number): string {
@@ -261,7 +231,7 @@ function mapTransfer(t: any): TxEntry {
}
const rpc = BrowserView.defineRPC<RPC>({
maxRequestTime: 200000,
maxRequestTime: 10000,
handlers: {
requests: {
getSecret: async ({ name, service }) => {
@@ -389,4 +359,4 @@ const win = new BrowserWindow({
},
})
console.log('Svelte app started!')
console.log('Koins app started!')
+3 -2
View File
@@ -1,5 +1,7 @@
import { tryCatch } from '@koins/utils'
const TIMEOUT = 15_000
export const post = async <T>(
url: string,
body: any,
@@ -9,6 +11,7 @@ export const post = async <T>(
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
signal: AbortSignal.timeout(TIMEOUT),
}),
)
@@ -17,8 +20,6 @@ export const post = async <T>(
throw error
}
console.log('data', await response.text())
const [data, dataError] = await tryCatch(response.json())
if (dataError) {
@@ -28,7 +28,7 @@ export const getTokensBalances = async (
params: [address, 'erc20'],
})
console.log(inspect(balRes, { depth: null, colors: true }))
// console.log(inspect(balRes, { depth: null, colors: true }))
return balRes
}
@@ -0,0 +1,59 @@
import { post } from './call'
import { getChainID, getURL } from './network'
import { inspect } from 'util'
import type { Transaction } from './types'
import { numberToHex } from 'viem'
export const getTransactionHistory = async (
key: string,
chainid: string,
address: string,
{ maxCount = 20 }: { maxCount: number },
) => {
const chainId = getChainID(chainid)
if (!chainId) return
const url = getURL({
chainId,
key,
})
const body = (fromAddress?: string, toAddress?: string) => ({
jsonrpc: '2.0',
id: 0,
method: 'alchemy_getAssetTransfers',
params: [
{
...(fromAddress ? { fromAddress } : {}),
...(toAddress ? { toAddress } : {}),
category: ['external', 'internal', 'erc20'],
withMetadata: true,
maxCount: numberToHex(maxCount),
},
],
})
const outRequest = post<{
transfers: Transaction[]
}>(url, body(address, undefined))
const inRequest = post<{
transfers: Transaction[]
}>(url, body(address, undefined))
const outRes = await outRequest
const inRes = await inRequest
console.log(inspect(outRes, { colors: true, depth: null }))
const outgoing = outRes.result?.transfers ?? []
const incoming = inRes.result?.transfers ?? []
const seen = new Set<string>()
return [...outgoing, ...incoming].filter((t) => {
if (!t.hash) return false
if (seen.has(t.hash)) return false
seen.add(t.hash)
return true
})
}
+32
View File
@@ -1,3 +1,35 @@
export type Token = {}
export type TokenBalance = {}
export type Transaction = {
category?:
| 'external'
| 'internal'
| 'token'
| 'erc20'
| 'erc721'
| 'erc1155'
| 'specialnft'
blockNum?: string
from?: string
to?: string
value?: number
erc721TokenId?: string
erc1155Metadata?: {
tokenId: string
value: string
}[]
tokenId?: string
asset?: string
uniqueId?: string
hash?: string
rawContract?: {
value?: string
address?: string
decimal?: string
}
metadata?: {
blockTimestamp?: string
}
}
+6 -5
View File
@@ -29,7 +29,7 @@ type RPC = {
service: string
name: string
}
response: Promise<string | null>
response: string | null
}
setSecret: {
params: {
@@ -37,27 +37,27 @@ type RPC = {
name: string
value: string
}
response: Promise<void>
response: void
}
fetchTxHistory: {
params: {
address: string
chainid: string
}
response: Promise<TxEntry[]>
response: TxEntry[]
}
fetchTokenBalances: {
params: {
address: string
chainid: string
}
response: Promise<TokenBalanceResult[]>
response: TokenBalanceResult[]
}
openExternal: {
params: {
url: string
}
response: Promise<void>
response: void
}
}
messages: {}
@@ -69,6 +69,7 @@ type RPC = {
}
const rpc = Electroview.defineRPC<RPC>({
maxRequestTime: 10000,
handlers: {
requests: {},
messages: {},
+53 -32
View File
@@ -1,9 +1,6 @@
import {
JsonRpcProvider,
HDNodeWallet,
formatEther,
} from 'ethers'
import { JsonRpcProvider, HDNodeWallet, formatEther } from 'ethers'
import { electrobun, type TxEntry } from '$lib/electrobun'
import { tryCatch } from '@koins/utils'
export const networks = [
{
@@ -57,21 +54,36 @@ export const Wallet = () => {
const net = () => networks.find((n) => n.id === network)!
const init = async () => {
try {
const raw = await electrobun.rpc?.request.getSecret({
if (!electrobun.rpc) return
const [raw, rawError] = await tryCatch(
electrobun.rpc.request.getSecret({
service: 'koins',
name: 'vault',
})
vaultExists = raw !== null && raw !== undefined
const key = await electrobun.rpc?.request.getSecret({
}),
)
if (rawError) {
console.log('Raw Error:', rawError)
}
vaultExists = raw !== null && raw !== undefined
const [key, keyError] = await tryCatch(
electrobun.rpc?.request.getSecret({
service: 'koins',
name: 'alchemy_key',
})
if (key) apiKey = key
if (raw) {
seed = raw
await refresh()
}
}),
)
if (keyError) {
console.log('key error', keyError)
}
if (key) apiKey = key
if (raw) {
seed = raw
await refresh()
}
try {
} catch (e) {
console.log(e)
error =
@@ -116,30 +128,39 @@ export const Wallet = () => {
}
const refresh = async () => {
if (!seed) return
if (!seed || !electrobun.rpc) return
loading = true
error = ''
tokenBalances = []
try {
const wallet = HDNodeWallet.fromPhrase(seed)
address = wallet.address
const provider = new JsonRpcProvider(net().rpc)
const nativeBal = await provider.getBalance(wallet.address)
balance = formatEther(nativeBal)
const wallet = HDNodeWallet.fromPhrase(seed)
address = wallet.address
const provider = new JsonRpcProvider(net().rpc)
const [nativeBal, nativeBalError] = await tryCatch(
provider.getBalance(wallet.address),
)
const bals = await electrobun.rpc?.request.fetchTokenBalances({
if (nativeBalError) {
loading = false
console.log('Native Balance Error', nativeBalError)
return
}
balance = formatEther(nativeBal)
const [bals, balsError] = await tryCatch(
electrobun.rpc.request.fetchTokenBalances({
address: wallet.address,
chainid: net().chainid,
})
tokenBalances = bals ?? []
}),
)
fetchTxHistory()
} catch (e) {
error =
e instanceof Error ? e.message : 'Failed to fetch balance'
} finally {
loading = false
if (balsError) {
console.log('Bals Error', balsError)
error = balsError.message
}
tokenBalances = bals ?? []
fetchTxHistory()
loading = false
}
const saveApiKey = async (key: string) => {