move transaction history to alchemy lib
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
|||||||
import { inspect } from 'util'
|
import { inspect } from 'util'
|
||||||
import { getTokenMetadata, getTokensBalances } from './lib/alchemy'
|
import { getTokenMetadata, getTokensBalances } from './lib/alchemy'
|
||||||
import { tryCatch } from '@koins/utils'
|
import { tryCatch } from '@koins/utils'
|
||||||
|
import { getTransactionHistory } from './lib/alchemy/get-transaction-history'
|
||||||
|
|
||||||
const DEV_SERVER_PORT = 5173
|
const DEV_SERVER_PORT = 5173
|
||||||
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`
|
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`
|
||||||
@@ -94,7 +95,7 @@ type RPC = {
|
|||||||
service: string
|
service: string
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
response: Promise<string | null>
|
response: string | null
|
||||||
}
|
}
|
||||||
setSecret: {
|
setSecret: {
|
||||||
params: {
|
params: {
|
||||||
@@ -102,27 +103,27 @@ type RPC = {
|
|||||||
name: string
|
name: string
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
response: Promise<void>
|
response: void
|
||||||
}
|
}
|
||||||
fetchTxHistory: {
|
fetchTxHistory: {
|
||||||
params: {
|
params: {
|
||||||
address: string
|
address: string
|
||||||
chainid: string
|
chainid: string
|
||||||
}
|
}
|
||||||
response: Promise<TxEntry[]>
|
response: TxEntry[]
|
||||||
}
|
}
|
||||||
fetchTokenBalances: {
|
fetchTokenBalances: {
|
||||||
params: {
|
params: {
|
||||||
address: string
|
address: string
|
||||||
chainid: string
|
chainid: string
|
||||||
}
|
}
|
||||||
response: Promise<TokenBalanceResult[]>
|
response: TokenBalanceResult[]
|
||||||
}
|
}
|
||||||
openExternal: {
|
openExternal: {
|
||||||
params: {
|
params: {
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
response: Promise<void>
|
response: void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
messages: {}
|
messages: {}
|
||||||
@@ -138,47 +139,16 @@ async function fetchAlchemyTransfers(
|
|||||||
chainid: string,
|
chainid: string,
|
||||||
address: string,
|
address: string,
|
||||||
): Promise<any[]> {
|
): Promise<any[]> {
|
||||||
const network = ALCHEMY_NETWORKS[chainid]
|
const transactions = await getTransactionHistory(
|
||||||
if (!network) return []
|
key,
|
||||||
const url = `https://${network}.g.alchemy.com/v2/${key}`
|
chainid,
|
||||||
|
address,
|
||||||
|
{ maxCount: 10 },
|
||||||
|
)
|
||||||
|
|
||||||
const body = (fromAddress?: string, toAddress?: string) => ({
|
if (!transactions) return []
|
||||||
jsonrpc: '2.0',
|
|
||||||
id: 0,
|
|
||||||
method: 'alchemy_getAssetTransfers',
|
|
||||||
params: [
|
|
||||||
{
|
|
||||||
...(fromAddress ? { fromAddress } : {}),
|
|
||||||
...(toAddress ? { toAddress } : {}),
|
|
||||||
category: ['external', 'erc20'],
|
|
||||||
withMetadata: true,
|
|
||||||
maxCount: '0x3E8',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const [outRes, inRes] = await Promise.all([
|
return transactions
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatUnits(balance: string, decimals: number): string {
|
function formatUnits(balance: string, decimals: number): string {
|
||||||
@@ -261,7 +231,7 @@ function mapTransfer(t: any): TxEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rpc = BrowserView.defineRPC<RPC>({
|
const rpc = BrowserView.defineRPC<RPC>({
|
||||||
maxRequestTime: 200000,
|
maxRequestTime: 10000,
|
||||||
handlers: {
|
handlers: {
|
||||||
requests: {
|
requests: {
|
||||||
getSecret: async ({ name, service }) => {
|
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'
|
import { tryCatch } from '@koins/utils'
|
||||||
|
|
||||||
|
const TIMEOUT = 15_000
|
||||||
|
|
||||||
export const post = async <T>(
|
export const post = async <T>(
|
||||||
url: string,
|
url: string,
|
||||||
body: any,
|
body: any,
|
||||||
@@ -9,6 +11,7 @@ export const post = async <T>(
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
|
signal: AbortSignal.timeout(TIMEOUT),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,8 +20,6 @@ export const post = async <T>(
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('data', await response.text())
|
|
||||||
|
|
||||||
const [data, dataError] = await tryCatch(response.json())
|
const [data, dataError] = await tryCatch(response.json())
|
||||||
|
|
||||||
if (dataError) {
|
if (dataError) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const getTokensBalances = async (
|
|||||||
params: [address, 'erc20'],
|
params: [address, 'erc20'],
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(inspect(balRes, { depth: null, colors: true }))
|
// console.log(inspect(balRes, { depth: null, colors: true }))
|
||||||
|
|
||||||
return balRes
|
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 Token = {}
|
||||||
|
|
||||||
export type TokenBalance = {}
|
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
|
service: string
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
response: Promise<string | null>
|
response: string | null
|
||||||
}
|
}
|
||||||
setSecret: {
|
setSecret: {
|
||||||
params: {
|
params: {
|
||||||
@@ -37,27 +37,27 @@ type RPC = {
|
|||||||
name: string
|
name: string
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
response: Promise<void>
|
response: void
|
||||||
}
|
}
|
||||||
fetchTxHistory: {
|
fetchTxHistory: {
|
||||||
params: {
|
params: {
|
||||||
address: string
|
address: string
|
||||||
chainid: string
|
chainid: string
|
||||||
}
|
}
|
||||||
response: Promise<TxEntry[]>
|
response: TxEntry[]
|
||||||
}
|
}
|
||||||
fetchTokenBalances: {
|
fetchTokenBalances: {
|
||||||
params: {
|
params: {
|
||||||
address: string
|
address: string
|
||||||
chainid: string
|
chainid: string
|
||||||
}
|
}
|
||||||
response: Promise<TokenBalanceResult[]>
|
response: TokenBalanceResult[]
|
||||||
}
|
}
|
||||||
openExternal: {
|
openExternal: {
|
||||||
params: {
|
params: {
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
response: Promise<void>
|
response: void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
messages: {}
|
messages: {}
|
||||||
@@ -69,6 +69,7 @@ type RPC = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rpc = Electroview.defineRPC<RPC>({
|
const rpc = Electroview.defineRPC<RPC>({
|
||||||
|
maxRequestTime: 10000,
|
||||||
handlers: {
|
handlers: {
|
||||||
requests: {},
|
requests: {},
|
||||||
messages: {},
|
messages: {},
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import {
|
import { JsonRpcProvider, HDNodeWallet, formatEther } from 'ethers'
|
||||||
JsonRpcProvider,
|
|
||||||
HDNodeWallet,
|
|
||||||
formatEther,
|
|
||||||
} from 'ethers'
|
|
||||||
import { electrobun, type TxEntry } from '$lib/electrobun'
|
import { electrobun, type TxEntry } from '$lib/electrobun'
|
||||||
|
import { tryCatch } from '@koins/utils'
|
||||||
|
|
||||||
export const networks = [
|
export const networks = [
|
||||||
{
|
{
|
||||||
@@ -57,21 +54,36 @@ export const Wallet = () => {
|
|||||||
const net = () => networks.find((n) => n.id === network)!
|
const net = () => networks.find((n) => n.id === network)!
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
try {
|
if (!electrobun.rpc) return
|
||||||
const raw = await electrobun.rpc?.request.getSecret({
|
const [raw, rawError] = await tryCatch(
|
||||||
|
electrobun.rpc.request.getSecret({
|
||||||
service: 'koins',
|
service: 'koins',
|
||||||
name: 'vault',
|
name: 'vault',
|
||||||
})
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (rawError) {
|
||||||
|
console.log('Raw Error:', rawError)
|
||||||
|
}
|
||||||
|
|
||||||
vaultExists = raw !== null && raw !== undefined
|
vaultExists = raw !== null && raw !== undefined
|
||||||
const key = await electrobun.rpc?.request.getSecret({
|
const [key, keyError] = await tryCatch(
|
||||||
|
electrobun.rpc?.request.getSecret({
|
||||||
service: 'koins',
|
service: 'koins',
|
||||||
name: 'alchemy_key',
|
name: 'alchemy_key',
|
||||||
})
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (keyError) {
|
||||||
|
console.log('key error', keyError)
|
||||||
|
}
|
||||||
|
|
||||||
if (key) apiKey = key
|
if (key) apiKey = key
|
||||||
if (raw) {
|
if (raw) {
|
||||||
seed = raw
|
seed = raw
|
||||||
await refresh()
|
await refresh()
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
error =
|
error =
|
||||||
@@ -116,31 +128,40 @@ export const Wallet = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
if (!seed) return
|
if (!seed || !electrobun.rpc) return
|
||||||
loading = true
|
loading = true
|
||||||
error = ''
|
error = ''
|
||||||
tokenBalances = []
|
tokenBalances = []
|
||||||
try {
|
|
||||||
const wallet = HDNodeWallet.fromPhrase(seed)
|
const wallet = HDNodeWallet.fromPhrase(seed)
|
||||||
address = wallet.address
|
address = wallet.address
|
||||||
const provider = new JsonRpcProvider(net().rpc)
|
const provider = new JsonRpcProvider(net().rpc)
|
||||||
const nativeBal = await provider.getBalance(wallet.address)
|
const [nativeBal, nativeBalError] = await tryCatch(
|
||||||
|
provider.getBalance(wallet.address),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (nativeBalError) {
|
||||||
|
loading = false
|
||||||
|
console.log('Native Balance Error', nativeBalError)
|
||||||
|
return
|
||||||
|
}
|
||||||
balance = formatEther(nativeBal)
|
balance = formatEther(nativeBal)
|
||||||
|
|
||||||
const bals = await electrobun.rpc?.request.fetchTokenBalances({
|
const [bals, balsError] = await tryCatch(
|
||||||
|
electrobun.rpc.request.fetchTokenBalances({
|
||||||
address: wallet.address,
|
address: wallet.address,
|
||||||
chainid: net().chainid,
|
chainid: net().chainid,
|
||||||
})
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (balsError) {
|
||||||
|
console.log('Bals Error', balsError)
|
||||||
|
error = balsError.message
|
||||||
|
}
|
||||||
tokenBalances = bals ?? []
|
tokenBalances = bals ?? []
|
||||||
|
|
||||||
fetchTxHistory()
|
fetchTxHistory()
|
||||||
} catch (e) {
|
|
||||||
error =
|
|
||||||
e instanceof Error ? e.message : 'Failed to fetch balance'
|
|
||||||
} finally {
|
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const saveApiKey = async (key: string) => {
|
const saveApiKey = async (key: string) => {
|
||||||
await electrobun.rpc?.request.setSecret({
|
await electrobun.rpc?.request.setSecret({
|
||||||
|
|||||||
Reference in New Issue
Block a user