diff --git a/apps/desktop/src/bun/index.ts b/apps/desktop/src/bun/index.ts index b604dc2..b9c1917 100644 --- a/apps/desktop/src/bun/index.ts +++ b/apps/desktop/src/bun/index.ts @@ -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 + response: string | null } setSecret: { params: { @@ -102,27 +103,27 @@ type RPC = { name: string value: string } - response: Promise + response: void } fetchTxHistory: { params: { address: string chainid: string } - response: Promise + response: TxEntry[] } fetchTokenBalances: { params: { address: string chainid: string } - response: Promise + response: TokenBalanceResult[] } openExternal: { params: { url: string } - response: Promise + response: void } } messages: {} @@ -138,47 +139,16 @@ async function fetchAlchemyTransfers( chainid: string, address: string, ): Promise { - 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() - 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({ - 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!') diff --git a/apps/desktop/src/bun/lib/alchemy/call.ts b/apps/desktop/src/bun/lib/alchemy/call.ts index 2331329..2751ad3 100644 --- a/apps/desktop/src/bun/lib/alchemy/call.ts +++ b/apps/desktop/src/bun/lib/alchemy/call.ts @@ -1,5 +1,7 @@ import { tryCatch } from '@koins/utils' +const TIMEOUT = 15_000 + export const post = async ( url: string, body: any, @@ -9,6 +11,7 @@ export const post = async ( method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), + signal: AbortSignal.timeout(TIMEOUT), }), ) @@ -17,8 +20,6 @@ export const post = async ( throw error } - console.log('data', await response.text()) - const [data, dataError] = await tryCatch(response.json()) if (dataError) { diff --git a/apps/desktop/src/bun/lib/alchemy/get-token-balances.ts b/apps/desktop/src/bun/lib/alchemy/get-token-balances.ts index cf17018..8c30f33 100644 --- a/apps/desktop/src/bun/lib/alchemy/get-token-balances.ts +++ b/apps/desktop/src/bun/lib/alchemy/get-token-balances.ts @@ -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 } diff --git a/apps/desktop/src/bun/lib/alchemy/get-transaction-history.ts b/apps/desktop/src/bun/lib/alchemy/get-transaction-history.ts new file mode 100644 index 0000000..741b576 --- /dev/null +++ b/apps/desktop/src/bun/lib/alchemy/get-transaction-history.ts @@ -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() + return [...outgoing, ...incoming].filter((t) => { + if (!t.hash) return false + if (seen.has(t.hash)) return false + seen.add(t.hash) + return true + }) +} diff --git a/apps/desktop/src/bun/lib/alchemy/types.ts b/apps/desktop/src/bun/lib/alchemy/types.ts index 2fad86b..9ff9ec8 100644 --- a/apps/desktop/src/bun/lib/alchemy/types.ts +++ b/apps/desktop/src/bun/lib/alchemy/types.ts @@ -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 + } +} diff --git a/apps/desktop/src/lib/electrobun.ts b/apps/desktop/src/lib/electrobun.ts index 9d95225..ffc4888 100644 --- a/apps/desktop/src/lib/electrobun.ts +++ b/apps/desktop/src/lib/electrobun.ts @@ -29,7 +29,7 @@ type RPC = { service: string name: string } - response: Promise + response: string | null } setSecret: { params: { @@ -37,27 +37,27 @@ type RPC = { name: string value: string } - response: Promise + response: void } fetchTxHistory: { params: { address: string chainid: string } - response: Promise + response: TxEntry[] } fetchTokenBalances: { params: { address: string chainid: string } - response: Promise + response: TokenBalanceResult[] } openExternal: { params: { url: string } - response: Promise + response: void } } messages: {} @@ -69,6 +69,7 @@ type RPC = { } const rpc = Electroview.defineRPC({ + maxRequestTime: 10000, handlers: { requests: {}, messages: {}, diff --git a/apps/desktop/src/lib/states/wallet.svelte.ts b/apps/desktop/src/lib/states/wallet.svelte.ts index 10ac106..8227092 100644 --- a/apps/desktop/src/lib/states/wallet.svelte.ts +++ b/apps/desktop/src/lib/states/wallet.svelte.ts @@ -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) => {