From bdd841b6327e1988a34ceb217f9c55ab75d914cf Mon Sep 17 00:00:00 2001 From: TZGyn Date: Mon, 25 May 2026 21:57:44 +0800 Subject: [PATCH] Add gas price display (Gwei, native, USD) under native balance per network --- apps/desktop/src/bun/index.ts | 45 +++++++++- .../src/lib/components/EvmDashboard.svelte | 87 ++++++++++++++++--- apps/desktop/src/lib/rpc-schema.ts | 1 + 3 files changed, 116 insertions(+), 17 deletions(-) diff --git a/apps/desktop/src/bun/index.ts b/apps/desktop/src/bun/index.ts index 81a8e6a..70bd3ff 100644 --- a/apps/desktop/src/bun/index.ts +++ b/apps/desktop/src/bun/index.ts @@ -21,6 +21,7 @@ import { db } from './lib/db' import { join } from 'path' import { getENV } from './lib/get-env' import { getClient } from './lib/viem' +import { formatGwei } from 'viem' import { isBinaryInstalled, downloadBinary, @@ -502,13 +503,20 @@ const rpc = BrowserView.defineRPC({ if (symbols && symbols.length > 0) { const url = `https://api.g.alchemy.com/prices/v1/${key}/tokens/by-symbol?${symbols.map((s) => `symbols=${encodeURIComponent(s)}`).join('&')}` - const res = await fetch(url, { signal: AbortSignal.timeout(10000) }) + const res = await fetch(url, { + signal: AbortSignal.timeout(10000), + }) if (res.ok) { const body = await res.json() const data = (body.data ?? []) as any[] for (const d of data) { for (const p of d.prices ?? []) { - results.push({ symbol: d.symbol, currency: p.currency, value: p.value, lastUpdatedAt: p.lastUpdatedAt }) + results.push({ + symbol: d.symbol, + currency: p.currency, + value: p.value, + lastUpdatedAt: p.lastUpdatedAt, + }) } } } @@ -527,7 +535,14 @@ const rpc = BrowserView.defineRPC({ const data = (body.data ?? []) as any[] for (const d of data) { for (const p of d.prices ?? []) { - results.push({ symbol: d.symbol, currency: p.currency, value: p.value, lastUpdatedAt: p.lastUpdatedAt, network: d.network, address: d.address }) + results.push({ + symbol: d.symbol, + currency: p.currency, + value: p.value, + lastUpdatedAt: p.lastUpdatedAt, + network: d.network, + address: d.address, + }) } } } @@ -540,6 +555,30 @@ const rpc = BrowserView.defineRPC({ return [] } }, + fetchGasPrice: async ({ chainid }) => { + const key = await Bun.secrets.get({ + service: 'koins', + name: 'alchemy_key', + }) + if (!key) { + console.log('[gas] no key') + return null + } + try { + const client = getClient(chainid, key) + const gasPrice = await client.getGasPrice() + console.log( + '[gas] price for chain', + chainid, + ':', + gasPrice.toString(), + ) + return formatGwei(gasPrice) + } catch (error) { + console.log('[gas] error:', error) + return null + } + }, fetchTransactionDetails: async ({ hash, chainid, address }) => { const key = await Bun.secrets.get({ service: 'koins', diff --git a/apps/desktop/src/lib/components/EvmDashboard.svelte b/apps/desktop/src/lib/components/EvmDashboard.svelte index ba5c253..af734a9 100644 --- a/apps/desktop/src/lib/components/EvmDashboard.svelte +++ b/apps/desktop/src/lib/components/EvmDashboard.svelte @@ -92,7 +92,10 @@ value: string currency: string } | null>(null) - let tokenPrices = $state>({}) + let tokenPrices = $state< + Record + >({}) + let gasPrice = $state(null) $effect(() => { const syms: string[] = w.symbol ? [w.symbol] : [] @@ -107,16 +110,30 @@ } if (syms.length > 0 || addrs.length > 0) { electrobun.rpc?.request - .fetchTokenPrices({ symbols: syms.length > 0 ? syms : undefined, addresses: addrs.length > 0 ? addrs : undefined }) + .fetchTokenPrices({ + symbols: syms.length > 0 ? syms : undefined, + addresses: addrs.length > 0 ? addrs : undefined, + }) .then((prices) => { - const usd = prices.find((p) => p.currency === 'usd' && p.symbol === w.symbol && !p.address) + const usd = prices.find( + (p) => + p.currency === 'usd' && + p.symbol === w.symbol && + !p.address, + ) nativePrice = usd ? { value: usd.value, currency: usd.currency } : null - const map: Record = {} + const map: Record< + string, + { value: string; currency: string } + > = {} for (const p of prices) { if (p.currency === 'usd' && p.address) { - map[p.address.toLowerCase()] = { value: p.value, currency: p.currency } + map[p.address.toLowerCase()] = { + value: p.value, + currency: p.currency, + } } } tokenPrices = map @@ -124,6 +141,14 @@ } }) + $effect(() => { + if (w.chainid) { + electrobun.rpc?.request + .fetchGasPrice({ chainid: w.chainid }) + .then((gp) => (gasPrice = gp)) + } + }) + $effect(() => { if (!initStarted) { initStarted = true @@ -386,16 +411,37 @@ {w.symbol}

{#if nativePrice} - {@const balVal = Number(w.balance) * Number(nativePrice.value)} + {@const balVal = + Number(w.balance) * Number(nativePrice.value)}

- ${balVal.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} @ ${Number(nativePrice.value).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}/{w.symbol} + ${balVal.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} @ ${Number(nativePrice.value).toLocaleString( + undefined, + { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }, + )}/{w.symbol} +

+ {/if} + {#if gasPrice} + {@const gasNative = Number(gasPrice) * 21000 / 1e9} +

+ Gas: {Number(gasPrice).toFixed(1)} Gwei + ({gasNative.toLocaleString(undefined, { minimumFractionDigits: 6, maximumFractionDigits: 6 })} {w.symbol}) + {#if nativePrice} + ${(gasNative * Number(nativePrice.value)).toLocaleString(undefined, { minimumFractionDigits: 4, maximumFractionDigits: 4 })} + {/if}

{/if}

Tokens

{#each w.tokenBalances as t} -
+
{#if t.contractAddress && tokenPrices[t.contractAddress.toLowerCase()]} - {@const val = tokenPrices[t.contractAddress.toLowerCase()]} - {@const balVal = Number(t.balance) * Number(val.value)} -

- ${balVal.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} @ ${Number(val.value).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}/{t.symbol} + {@const val = + tokenPrices[t.contractAddress.toLowerCase()]} + {@const balVal = + Number(t.balance) * Number(val.value)} +

+ ${balVal.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} @ ${Number(val.value).toLocaleString( + undefined, + { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }, + )}/{t.symbol}

{/if} {/each} @@ -485,10 +543,11 @@ })} class="flex w-full cursor-pointer items-start gap-2 rounded-md bg-muted px-3 py-2 text-xs text-left hover:bg-muted/80 transition-colors">
{#if txAction(tx, w.address) === 'Swap'} diff --git a/apps/desktop/src/lib/rpc-schema.ts b/apps/desktop/src/lib/rpc-schema.ts index 9f6a886..668f154 100644 --- a/apps/desktop/src/lib/rpc-schema.ts +++ b/apps/desktop/src/lib/rpc-schema.ts @@ -169,6 +169,7 @@ export type RPC = { evmGetSeed: { params: { vaultKey: string }; response: string } evmDeleteWallet: { params: { id: string }; response: void } fetchTokenPrices: { params: { symbols?: string[]; addresses?: { network: string; address: string }[] }; response: TokenPriceEntry[] } + fetchGasPrice: { params: { chainid: string }; response: string | null } } messages: {} }