Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

All notable changes to the ar.io Console are documented in this file.

## [Unreleased]

### Changed
- **The Account page leads with your balance.** When other wallets have
shared credits with you, it says how much of the total is yours and how
much is shared with you (spend only).
- **Credit sharing on the Account page matches Share Credits.** The same two
tabs, the same figures to four places, and the same Revoke, which now asks
first and names the amount it returns. A wallet with no shares sees one line
instead of an empty panel. Approval IDs are no longer shown.

### Fixed
- **Revoking on the Account page took effect on one click** and revoked
everything shared with that wallet, though the row showed one share. It now
confirms first and groups shares by wallet.
- **Account showed the amount originally shared** as if it were still
available; it now shows how much has been spent and what is left.

## [4.11.2] - 2026-10-20

### Changed
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Before diving in, these are the most common issues:

10. **Free tier has two axes**: `useFreeUploadLimit()` returns `{ freeUploadLimitBytes, freeTier }`. `freeUploadLimitBytes` (== `freeTier.maxItemBytes`) is the per-item size cap used by `isFileFree()`. `freeTier.lifetimeBytes` / `freeTier.ipBytes` are separate lifetime/IP quotas (0 = uncapped) shown in UI messaging — don't conflate them with the per-item limit.

11. **The store's `creditBalance` is spendable, not shareable**: `useCreditBalance` fills it from the payment service's `effectiveBalance`, which includes credits *other wallets shared with this one*. Those can pay for uploads but cannot be shared onward; only `winc` can. Anything that offers to share or hand credits on reads `toShareableBalance()` (`utils/shareableCredits.ts`), as `ShareCreditsPanel` does. Reading `creditBalance` there offers more than the service will approve.
11. **The store's `creditBalance` is spendable, not shareable**: `useCreditBalance` fills it from the payment service's `effectiveBalance`, which includes credits *other wallets shared with this one*. Those can pay for uploads but cannot be shared onward; only `winc` can. Anything that offers to share or hand credits on reads `useShareBalance()` (`hooks/useShareBalance.ts`, built on `toShareableBalance()`), as Share Credits and the Account page do; both render shares with `ShareCreditsList`, so grouping and Revoke behave the same in both places. Reading `creditBalance` there offers more than the service will approve.

## Architecture Overview

Expand Down
18 changes: 16 additions & 2 deletions src/components/account/BalanceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Coins, Share2, Plus, ShieldCheck } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { useWincForOneGiB } from '../../hooks/useWincForOneGiB';
import { useCreditBalance } from '../../hooks/useCreditBalance';
import { useShareBalance } from '../../hooks/useShareBalance';
import { formatCreditsFloor } from '../../utils/shareableCredits';
import { useFreeUploadLimit, useFreeStatus, freeTierSummary } from '../../hooks/useFreeUploadLimit';
import { wincPerCredit } from '../../constants';
import { buttonClass } from '@/components/button';
Expand All @@ -12,6 +14,10 @@ export default function BalanceCard() {
const { freeUploadLimitBytes } = useFreeUploadLimit();
const { bytesRemaining } = useFreeStatus();
const { credits, isLoading: loading } = useCreditBalance();
// `credits` is what this wallet can spend, which includes credits other
// wallets shared with it. Those can't be shared on, so when there are any,
// say how the total splits: the Share button below acts on the first part.
const { data: shareBalance } = useShareBalance();

// Derive storage from credits (1 credit = 1e12 winc)
const gibStorage = wincForOneGiB ? (credits * wincPerCredit) / Number(wincForOneGiB) : 0;
Expand Down Expand Up @@ -42,8 +48,8 @@ export default function BalanceCard() {
<div className="rounded-2xl border border-border/20 bg-card p-4 sm:p-6">
{/* Header */}
<div className="mb-4 flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg border border-border/20 bg-foreground/20">
<Coins className="h-5 w-5 text-foreground" />
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary/20">
<Coins className="h-5 w-5 text-primary" aria-hidden="true" />
</div>
<div>
<h3 className="text-lg font-extrabold text-foreground">Balance</h3>
Expand All @@ -67,6 +73,14 @@ export default function BalanceCard() {
{formatCredits(credits)}{' '}
<span className="text-base font-semibold text-foreground/60">Credits</span>
</div>
{shareBalance && shareBalance.receivedWinc > 0n && (
<div className="mt-1 text-sm text-foreground/70">
{/* Two places, rounded down in winc, as Share Credits rounds. */}
{formatCreditsFloor(shareBalance.shareableWinc, 2)} yours ·{' '}
{formatCreditsFloor(shareBalance.receivedWinc, 2)} shared with you{' '}
<span className="text-foreground/60">(spend only)</span>
</div>
)}
{showStorage && (
<div className="mt-1 text-sm text-foreground/60">
≈ {gibStorage.toFixed(2)} GiB of permanent storage
Expand Down
Loading
Loading