Skip to content

feat: show approximate EUR/USD value next to reward amounts - #183

Merged
zaibon merged 2 commits into
mainfrom
feat/eur-value
Oct 4, 2026
Merged

zaibon merged 2 commits into
mainfrom
feat/eur-value

Conversation

@zaibon

@zaibon zaibon commented Oct 4, 2026

Copy link
Copy Markdown
Owner

What

The reward amount input (Balance.svelte, used in simple mode, the expert-mode single reward and each multi-reward contributor card) now shows a small line under it:

≈ 22.23 €                                   Balance ≈ 106.70 €

It updates live as the amount or token changes. A tooltip says the value is approximate and names the price source.

Price source

  • CoinGecko's keyless API wasn't an option. Since its recent limits, /simple/token_price without a key returns error 10012: "exceeds the allowed limit of 1 contract address". That means one request per token. Mapping contracts to IDs instead needs /coins/list?include_platform=true, which is about 3.9 MB.
  • DefiLlama coins.llama.fi/prices/current/... needs no key, accepts many coins in one request (contract addresses per chain plus coingecko:<id> for native coins) and returns confidence. Prices are in USD, so EURC (coingecko:euro-coin) is added to the same request and used as the USD→EUR rate. That's close enough for "≈". Cross-check: ETH came out at 2,398 € via DefiLlama/EURC and 2,394 € from CoinGecko's simple/price?vs_currencies=eur.

Browser vs server route

The app calls the API directly from the browser:

  • DefiLlama sends Access-Control-Allow-Origin: *.
  • Rate limits then apply to each user's IP instead of shared Vercel IPs. Keyless public APIs often return 429 to shared serverless IPs, so this is the more robust choice.
  • It adds no server hop or cold start, which suits a client-only (ssr = false) app.
  • The shared runes store already prevents duplicate requests within a tab.

Caching / degradation

  • src/lib/services/prices.svelte.ts: a shared store. While a wallet is connected, one refresh loop fetches all Peanut-listed tokens of the current chain in one request, cached for 5 min and deduplicated while a request is in flight. Many contributor cards only read the store.
  • No € value when: the chain is a testnet (Sepolia and anything not mapped), the token is unpriced or DefiLlama's confidence is below 0.9, or the request fails or hits a rate limit. After a failure, prices up to 15 min old are kept, then dropped.
  • Nothing in the reward flow depends on prices.

Testing

  • pnpm run check, pnpm run lint and pnpm run build pass.
  • In headless Chrome (dev server), /, /dashboard and /badges render with no console errors.
  • Live lookup through the service module: Base, Optimism and mainnet ETH, USDC and WETH return € values. Sepolia returns nothing.
  • With a Base balance injected into the store: the € line updates on amount and token changes and is hidden for an unpriced token (Ed).
  • With the chain set to Base while 20 readers read the store: 1 request. Switching to Sepolia sends no request, and switching back within the TTL uses the cache.
  • With a simulated 429, the € value is hidden and there are no page errors.
  • Not tested: a real wallet connection, and a visual check of the multi-reward cards.

🤖 Generated with Claude Code

Prices come from DefiLlama's keyless coins API (one batched request per
chain, cached for 5 minutes in a shared runes store). CoinGecko's keyless
API only accepts one contract address per request, so it can't batch a
token list. USD prices are converted with EURC as the EUR rate.

The value is hidden on testnets, for unpriced tokens, and when the API
fails, so the reward flow is never blocked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
commitkudos Ready Ready Preview Oct 4, 2026 3:09pm UTC

A €/$ button in the header toggles the currency for every reward input.
The choice is saved in localStorage. The store now keeps DefiLlama's USD
prices plus the EURC rate, so switching needs no new request, and USD
values still show if the EUR rate can't be priced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zaibon zaibon changed the title feat: show approximate EUR value next to reward amounts feat: show approximate EUR/USD value next to reward amounts Oct 4, 2026
@zaibon

zaibon commented Oct 4, 2026

Copy link
Copy Markdown
Owner Author

Added a €/$ toggle in the header (next to the light/dark switch). It switches every reward input between euros and US dollars, and the choice is saved in localStorage. The store now keeps DefiLlama's USD prices plus the EURC rate, so switching sends no new request, and USD values still show if the EUR rate is missing. Tested in headless Chrome: 0.002 ETH on Base shows ≈ 4.80 € and ≈ $5.40, the choice persists across reloads, there are no console errors, and check/lint/build pass.

@zaibon
zaibon marked this pull request as ready for review October 4, 2026 15:09
@zaibon
zaibon merged commit 8012f63 into main Oct 4, 2026
6 checks passed
@zaibon
zaibon deleted the feat/eur-value branch October 4, 2026 15:10
zaibon added a commit that referenced this pull request Oct 4, 2026
## What

- **Brand theme**: a custom Skeleton theme based on the logo (blue,
mint, navy) replaces the stock `legacy` theme. Inter and JetBrains Mono
are bundled with the app, and a soft brand-colored glow sits behind the
page.
- **Header and footer**: logo, navigation (Reward / Advanced / Badge),
wallet button, €/$ switch and light/dark switch. The floating "Expert
mode" / "Simple mode" buttons are removed. The footer links to Peanut
Protocol, the license and GitHub. Each page now has a `<title>`, a meta
description and Open Graph tags.
- **Home page**: a short intro, then four steps: Repository →
Contributors → Reward → Send.
  - Repository: a card with avatar, description, stars and forks.
- Contributors: a Top 3 / 5 / 10 / custom picker, and rows with rank and
commit count.
- Reward: a summary (count × amount = total, network, ≈ fiat value) and
a warning when the balance is too low.
  - Send: a reward links table with per-link and "Copy all" buttons.
- **Advanced page**: a summary panel that stays visible while scrolling
(mode switch, avatars of selected contributors, total per token with
fiat values), filter and sort controls, and compact contributor cards.
- **Badge page**: a live preview on light and dark README backgrounds,
previews of each badge style, color pickers, and defaults so a badge
appears right away.
- **Polish**: placeholder content while loading instead of loading
toasts, labelled inputs, visible keyboard focus,
`prefers-reduced-motion` support, no sideways scrolling on narrow
screens.
- **Fiat values (#183)**: the amount field shows "≈ 1.85 €" on the left
and "Balance: 120 USDC (≈ 111.11 €)" on the right, on one line. The
reward totals show approximate EUR/USD values too.

### Behavior changes and fixes

- **Links are protected**: on the home page, the form locks once reward
links are created. Before, editing the repository silently erased the
links even though the funds were already deposited. "Start a new reward"
asks for confirmation first.
- **Max splits the balance**: "Max" now divides the balance between the
selected contributors. Before, it filled in the whole balance as the
per-person amount, so the total came to several times what the wallet
held.
- **Private-email warning**: step 4 flags contributors with a
`@users.noreply.github.com` address, since the reward email won't reach
them.
- **Badge snippet**: it no longer shows `src="undefined"` before a
repository is entered.
- **Page background**: `data-theme` moved from `<body>` to `<html>`.
Skeleton paints the background on `<html>`, which never received the
theme's colors.
- **New endpoint**: `/api/github/repo` returns repository information
for the card.
- **Prefill links**: `/dashboard` and `/badges` accept
`?repository=owner/name`, and pasting a full GitHub URL works
everywhere.
- **SvelteKit 3**: the page snapshots use the `snapshot` helper from
`$app/navigation`, since `export const snapshot` is deprecated.

## Why

The UI was the stock Skeleton look with bare forms. A first-time visitor
couldn't tell what CommitKudos does, the badge page wasn't linked from
anywhere, and some flows could lose money-related state. This PR gives
the app an identity that matches the logo, and makes the reward flow
explain itself step by step.

## Design decisions

- **Fonts are bundled**, not loaded from Google Fonts, so the app makes
no third-party font request (simpler GDPR-wise).
- **Icons come from `@lucide/svelte`**, imported one per file so only
the icons used are bundled.
- **The contributor count re-slices the loaded list** instead of
fetching again. Only the latest repository search can update the page,
so out-of-order responses are dropped.
- **The fiat total on the Advanced page** only appears when every token
in the reward has a price, so the sum is never partial.
- **"Expert mode" is renamed "Advanced"**: both modes reward several
people at once. What sets the dashboard apart is choosing an amount per
person.

## Testing

- `pnpm check`, `pnpm lint` and `pnpm build` pass.
- Checked every page with headless Chrome screenshots: dark and light
mode, 1280–1440px wide and 500px (the narrowest headless Chrome
renders).
- I checked the step 4 layout and the fiat values with injected test
data, which is not part of this PR.
- **Not tested**:
- Anything that needs a connected wallet: link creation, emails,
per-contributor amounts, real price loading.
  - Clicks and keyboard use.
  - True 390px phone width.

A click-through with a test wallet on Sepolia before merging would cover
these.


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — 56ce4b67 Deployed Oct 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant