diff --git a/src/components/AssetBadge.test.tsx b/src/components/AssetBadge.test.tsx
index 8d3fee8..ae6e04b 100644
--- a/src/components/AssetBadge.test.tsx
+++ b/src/components/AssetBadge.test.tsx
@@ -92,10 +92,10 @@ describe("AssetBadge", () => {
expect(document.querySelector("[data-address]")).not.toBeInTheDocument();
});
- it("falls back to grey/surface-2 for an unknown asset", () => {
+ it("renders deterministic color for unknown assets (not grey)", () => {
const { container } = render();
const icon = container.querySelector(".bg-surface-2");
- expect(icon).toBeInTheDocument();
+ expect(icon).not.toBeInTheDocument();
});
it("renders the asset code for an unknown asset", () => {
@@ -103,6 +103,80 @@ describe("AssetBadge", () => {
expect(screen.getByText("WAVEX")).toBeInTheDocument();
});
+ it("renders yXLM with distinct green color", () => {
+ const yxlmBalance: Balance = {
+ assetType: "credit_alphanum12",
+ assetCode: "yXLM",
+ assetIssuer: "GBUQWP3BOUZX34ULNQG23RQ6F4YUSXHTJGIP5FB4M3US5VM5NGVLYELM",
+ balance: "100",
+ balanceFloat: 100,
+ };
+ const { container } = render();
+ const icon = container.querySelector(".text-green");
+ expect(icon).toBeInTheDocument();
+ });
+
+ it("renders AQUA with distinct blue color", () => {
+ const aquaBalance: Balance = {
+ assetType: "credit_alphanum12",
+ assetCode: "AQUA",
+ assetIssuer: "GBUQWP3BOUZX34ULNQG23RQ6F4YUSXHTJGIP5FB4M3US5VM5NGVLYELM",
+ balance: "50",
+ balanceFloat: 50,
+ };
+ const { container } = render();
+ const icon = container.querySelector(".text-blue");
+ expect(icon).toBeInTheDocument();
+ });
+
+ it("renders SHX with distinct pink color", () => {
+ const shxBalance: Balance = {
+ assetType: "credit_alphanum12",
+ assetCode: "SHX",
+ assetIssuer: "GBUQWP3BOUZX34ULNQG23RQ6F4YUSXHTJGIP5FB4M3US5VM5NGVLYELM",
+ balance: "25",
+ balanceFloat: 25,
+ };
+ const { container } = render();
+ const icon = container.querySelector(".text-pink");
+ expect(icon).toBeInTheDocument();
+ });
+
+ it("renders BLND with distinct yellow color", () => {
+ const blndBalance: Balance = {
+ assetType: "credit_alphanum12",
+ assetCode: "BLND",
+ assetIssuer: "GBUQWP3BOUZX34ULNQG23RQ6F4YUSXHTJGIP5FB4M3US5VM5NGVLYELM",
+ balance: "75",
+ balanceFloat: 75,
+ };
+ const { container } = render();
+ const icon = container.querySelector(".text-yellow");
+ expect(icon).toBeInTheDocument();
+ });
+
+ it("assigns deterministic colors to unknown assets and not grey", () => {
+ const customBalance1: Balance = {
+ assetType: "credit_alphanum12",
+ assetCode: "CUSTOM1",
+ assetIssuer: "GBUQWP3BOUZX34ULNQG23RQ6F4YUSXHTJGIP5FB4M3US5VM5NGVLYELM",
+ balance: "10",
+ balanceFloat: 10,
+ };
+ const customBalance2: Balance = {
+ assetType: "credit_alphanum12",
+ assetCode: "CUSTOM2",
+ assetIssuer: "GBUQWP3BOUZX34ULNQG23RQ6F4YUSXHTJGIP5FB4M3US5VM5NGVLYELM",
+ balance: "20",
+ balanceFloat: 20,
+ };
+ const { container: c1 } = render();
+ const { container: c2 } = render();
+
+ expect(c1.querySelector(".bg-surface-2")).not.toBeInTheDocument();
+ expect(c2.querySelector(".bg-surface-2")).not.toBeInTheDocument();
+ });
+
it("renders 'LP' for liquidity_pool_shares without undefined display", () => {
const { container } = render();
expect(screen.getAllByText("LP").length).toBeGreaterThanOrEqual(1);
@@ -335,11 +409,11 @@ describe("AssetPill", () => {
expect(screen.getByText("USDC")).toHaveClass("text-brand");
});
- it("falls back to grey for an unknown asset code", () => {
+ it("uses deterministic color for unknown asset code (not grey)", () => {
render();
const pill = screen.getByText("WAVEX");
- expect(pill).toHaveClass("bg-surface-2");
- expect(pill).toHaveClass("text-ink-2");
+ expect(pill).not.toHaveClass("bg-surface-2");
+ expect(pill).not.toHaveClass("text-ink-2");
});
it("merges a custom className", () => {
diff --git a/src/components/AssetBadge.tsx b/src/components/AssetBadge.tsx
index 8d56f2b..8baa94c 100644
--- a/src/components/AssetBadge.tsx
+++ b/src/components/AssetBadge.tsx
@@ -3,12 +3,37 @@ import { cn, truncateAddress } from "@/lib/utils";
export const ASSET_COLORS: Record = {
XLM: { bg: "bg-[rgba(20,184,166,0.12)]", text: "text-teal" },
+ yXLM: { bg: "bg-[rgba(34,197,94,0.12)]", text: "text-green" },
USDC: { bg: "bg-[rgba(86,69,212,0.12)]", text: "text-brand" },
USDT: { bg: "bg-success-dim-strong", text: "text-green" },
BTC: { bg: "bg-[rgba(249,115,22,0.12)]", text: "text-orange" },
ETH: { bg: "bg-[rgba(168,85,247,0.12)]", text: "text-purple" },
+ AQUA: { bg: "bg-[rgba(59,130,246,0.12)]", text: "text-blue" },
+ SHX: { bg: "bg-[rgba(236,72,153,0.12)]", text: "text-pink" },
+ BLND: { bg: "bg-[rgba(236,204,41,0.12)]", text: "text-yellow" },
};
+const FALLBACK_COLOR_PALETTE = [
+ { bg: "bg-[rgba(168,85,247,0.12)]", text: "text-purple" },
+ { bg: "bg-[rgba(59,130,246,0.12)]", text: "text-blue" },
+ { bg: "bg-[rgba(236,72,153,0.12)]", text: "text-pink" },
+ { bg: "bg-[rgba(236,204,41,0.12)]", text: "text-yellow" },
+ { bg: "bg-[rgba(34,197,94,0.12)]", text: "text-green" },
+ { bg: "bg-[rgba(249,115,22,0.12)]", text: "text-orange" },
+ { bg: "bg-[rgba(14,165,233,0.12)]", text: "text-cyan" },
+ { bg: "bg-[rgba(229,57,53,0.12)]", text: "text-red" },
+];
+
+function hashCode(str: string): number {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ const char = str.charCodeAt(i);
+ hash = (hash << 5) - hash + char;
+ hash = hash & hash;
+ }
+ return Math.abs(hash);
+}
+
export function getAssetColor(
code: string,
colorMap?: Record,
@@ -21,17 +46,18 @@ export function getAssetColor(
);
if (matchedKey && colorMap[matchedKey]) return colorMap[matchedKey];
}
- return (
- ASSET_COLORS[code] ??
- ASSET_COLORS[code.toUpperCase()] ?? { bg: "bg-surface-2", text: "text-ink-2" }
- );
+ if (ASSET_COLORS[code]) return ASSET_COLORS[code];
+ const upper = code.toUpperCase();
+ if (ASSET_COLORS[upper]) return ASSET_COLORS[upper];
+ const hash = hashCode(code);
+ return FALLBACK_COLOR_PALETTE[hash % FALLBACK_COLOR_PALETTE.length];
}
/**
* Assets whose issuer is well known enough that printing the issuer address
* next to the code is noise rather than information.
*/
-const KNOWN_ASSETS = new Set(["XLM", "USDC", "USDT", "BTC", "ETH"]);
+const KNOWN_ASSETS = new Set(["XLM", "yXLM", "USDC", "USDT", "BTC", "ETH", "AQUA", "SHX", "BLND"]);
/** Whether `code` is in the built-in known-asset registry. */
export function isKnownAsset(code: string): boolean {
diff --git a/src/components/WalletConnectButton.test.tsx b/src/components/WalletConnectButton.test.tsx
index 6c11288..8b23ec1 100644
--- a/src/components/WalletConnectButton.test.tsx
+++ b/src/components/WalletConnectButton.test.tsx
@@ -158,4 +158,22 @@ describe("WalletConnectButton", () => {
expect(screen.queryByText("Previous error")).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: /wallet connected/i })).toBeInTheDocument();
});
+
+ it("calls onOpenModal when connected address pill is clicked", () => {
+ const mockOnOpenModal = vi.fn();
+ const fullAddress = "GABC1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ vi.mocked(useSorokit).mockReturnValue(mockUseSorokit({
+ isConnected: true,
+ address: fullAddress,
+ connectWallet: mockConnect,
+ clearError: mockClearError,
+ }));
+
+ render();
+ const addressPill = screen.getByRole("button", {
+ name: `Wallet connected: ${fullAddress}. Click to manage.`,
+ });
+ fireEvent.click(addressPill);
+ expect(mockOnOpenModal).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/src/components/index.ts b/src/components/index.ts
index d7cc8d1..cdb122f 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -41,6 +41,8 @@ export { Input } from "./ui/Input";
export { LabelledValue } from "./ui/LabelledValue";
export { Separator } from "./ui/Separator";
export { AssetRowSkeleton, Skeleton, SkeletonCard, SkeletonRow } from "./ui/Skeleton";
+export type { InfoCellProps } from "./ui/InfoCell";
+export { InfoCell } from "./ui/InfoCell";
// Error handling
export { ErrorBoundary } from "./ErrorBoundary";