move transaction history to alchemy lib
This commit is contained in:
@@ -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!')
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {},
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user