From 8ae3cef7898346df82a3ca0c8a5607b578935b0b Mon Sep 17 00:00:00 2001 From: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Date: Sat, 20 Jun 2026 00:02:25 +0530 Subject: [PATCH] feat(agent-world): show Solana wallet address chip in Tiny Place sidebar header (#3838) --- .../components/WalletAddressChip.test.tsx | 207 +++++++++++++++ .../components/WalletAddressChip.tsx | 236 ++++++++++++++++++ app/src/agentworld/pages/AgentWorld.tsx | 10 +- app/src/lib/i18n/ar.ts | 5 + app/src/lib/i18n/bn.ts | 5 + app/src/lib/i18n/de.ts | 5 + app/src/lib/i18n/en.ts | 5 + app/src/lib/i18n/es.ts | 5 + app/src/lib/i18n/fr.ts | 5 + app/src/lib/i18n/hi.ts | 5 + app/src/lib/i18n/id.ts | 5 + app/src/lib/i18n/it.ts | 5 + app/src/lib/i18n/ko.ts | 5 + app/src/lib/i18n/pl.ts | 5 + app/src/lib/i18n/pt.ts | 5 + app/src/lib/i18n/ru.ts | 5 + app/src/lib/i18n/zh-CN.ts | 5 + 17 files changed, 520 insertions(+), 3 deletions(-) create mode 100644 app/src/agentworld/components/WalletAddressChip.test.tsx create mode 100644 app/src/agentworld/components/WalletAddressChip.tsx diff --git a/app/src/agentworld/components/WalletAddressChip.test.tsx b/app/src/agentworld/components/WalletAddressChip.test.tsx new file mode 100644 index 000000000..7965e2d7e --- /dev/null +++ b/app/src/agentworld/components/WalletAddressChip.test.tsx @@ -0,0 +1,207 @@ +/** + * Tests for WalletAddressChip — the always-visible wallet address chip in the + * Agent World sidebar header. + * + * All addresses are GENERIC placeholders (never real wallet addresses). + */ +import { act, render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; + +// Pull the mock reference after vi.mock is hoisted. +import { fetchWalletStatus } from '../../services/walletApi'; +import WalletAddressChip from './WalletAddressChip'; + +// --------------------------------------------------------------------------- +// Module mock — walletApi +// --------------------------------------------------------------------------- + +vi.mock('../../services/walletApi', () => ({ fetchWalletStatus: vi.fn() })); + +const mockFetchWalletStatus = vi.mocked(fetchWalletStatus); + +// Generic placeholder wallet status — 44-char base58 Solana address. +const SOLANA_ADDRESS = 'AAAAAA1111112222223333334444445555556789BB'; + +function makeStatus(address: string | null = SOLANA_ADDRESS) { + return { + configured: address !== null, + onboardingCompleted: address !== null, + consentGranted: true, + secretStored: true, + source: 'generated' as const, + mnemonicWordCount: 12, + accounts: + address !== null + ? [ + { + chain: 'evm' as const, + address: '0xGENERICevm0000', + derivationPath: "m/44'/60'/0'/0/0", + }, + { chain: 'solana' as const, address, derivationPath: "m/44'/501'/0'/0'" }, + ] + : [], + updatedAtMs: Date.now(), + }; +} + +// --------------------------------------------------------------------------- +// Clipboard mock (jsdom doesn't implement navigator.clipboard) +// --------------------------------------------------------------------------- + +const clipboardWriteText = vi.fn().mockResolvedValue(undefined); + +beforeEach(() => { + // Reset mock implementation between tests so a never-resolving promise from + // one test doesn't bleed into the next (vitest config has mockReset: false). + mockFetchWalletStatus.mockReset(); + // Clear clipboard call history so per-test assertions don't see stale calls. + clipboardWriteText.mockClear(); + + Object.defineProperty(navigator, 'clipboard', { + value: { writeText: clipboardWriteText }, + writable: true, + configurable: true, + }); +}); + +// --------------------------------------------------------------------------- +// Test suite +// --------------------------------------------------------------------------- + +describe('WalletAddressChip', () => { + test('renders a loading skeleton before wallet resolves', () => { + // Never resolves during this test — promise stays pending. + mockFetchWalletStatus.mockReturnValue(new Promise(() => {})); + + render(); + + const chip = screen.getByTestId('wallet-address-chip'); + expect(chip).toBeInTheDocument(); + // In loading state the chip is the pulse element itself (no address text). + expect(chip).not.toHaveTextContent(SOLANA_ADDRESS); + expect(chip.className).toContain('animate-pulse'); + }); + + test('renders truncated address (6…4) in ready state', async () => { + mockFetchWalletStatus.mockResolvedValue(makeStatus()); + + render(); + + // Truncated: first 6 + last 4 of SOLANA_ADDRESS + const expected = `${SOLANA_ADDRESS.slice(0, 6)}…${SOLANA_ADDRESS.slice(-4)}`; + await screen.findByText(expected); + + const chip = screen.getByTestId('wallet-address-chip'); + expect(chip).toBeInTheDocument(); + }); + + test('full address is in the title attribute of the truncated span', async () => { + mockFetchWalletStatus.mockResolvedValue(makeStatus()); + + render(); + + await waitFor(() => { + const span = screen.getByTitle(SOLANA_ADDRESS); + expect(span).toBeInTheDocument(); + }); + }); + + test('copy button calls clipboard.writeText with the full address', async () => { + mockFetchWalletStatus.mockResolvedValue(makeStatus()); + + render(); + + // Wait for ready state. + await screen.findByTitle(SOLANA_ADDRESS); + + const copyBtn = screen.getByRole('button', { name: /copy address/i }); + await userEvent.click(copyBtn); + + expect(clipboardWriteText).toHaveBeenCalledWith(SOLANA_ADDRESS); + }); + + test('copy button shows "Copied" feedback after click and reverts', async () => { + // Fake timers so we can fast-forward the 2s reset; shouldAdvanceTime keeps + // findBy/waitFor polling alive, and userEvent drives the same clock. + vi.useFakeTimers({ shouldAdvanceTime: true }); + try { + const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); + mockFetchWalletStatus.mockResolvedValue(makeStatus()); + + render(); + await screen.findByTitle(SOLANA_ADDRESS); + + const copyBtn = screen.getByRole('button', { name: /copy address/i }); + await user.click(copyBtn); + + // After click the aria-label should flip to "Copied". + await waitFor(() => + expect(screen.getByRole('button', { name: /copied/i })).toBeInTheDocument() + ); + + // After the 2s reset timer fires, it should revert to "Copy address". + await act(async () => { + vi.advanceTimersByTime(2000); + }); + await waitFor(() => + expect(screen.getByRole('button', { name: /copy address/i })).toBeInTheDocument() + ); + } finally { + vi.useRealTimers(); + } + }); + + test('shows a retryable "Wallet unavailable" state when fetchWalletStatus rejects', async () => { + // A transient RPC/transport failure must NOT be reported as "not set up" — + // a configured wallet would otherwise be mislabelled until the route remounts. + mockFetchWalletStatus.mockRejectedValue(new Error('core rpc unavailable')); + + render(); + + await screen.findByText(/wallet unavailable/i); + + const chip = screen.getByTestId('wallet-address-chip'); + expect(chip).not.toHaveTextContent(SOLANA_ADDRESS); + // The error chip is itself a retry button — and it must NOT claim "not set up". + expect(screen.queryByText(/wallet not set up/i)).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: /retry/i })).toBeInTheDocument(); + }); + + test('error state recovers to ready after a successful retry', async () => { + // First fetch fails (transient), the retry click succeeds. + mockFetchWalletStatus + .mockRejectedValueOnce(new Error('core rpc unavailable')) + .mockResolvedValueOnce(makeStatus()); + + render(); + + const retryBtn = await screen.findByRole('button', { name: /retry/i }); + await userEvent.click(retryBtn); + + // After the retry resolves, the truncated address is shown. + await screen.findByTitle(SOLANA_ADDRESS); + expect(screen.queryByText(/wallet unavailable/i)).not.toBeInTheDocument(); + }); + + test('shows "Wallet not set up" when no solana account exists in accounts list', async () => { + // Wallet configured but only EVM — no Solana entry. + mockFetchWalletStatus.mockResolvedValue(makeStatus(null)); + + render(); + + await screen.findByText(/wallet not set up/i); + expect(screen.queryByRole('button')).not.toBeInTheDocument(); + }); + + test('copy button has descriptive aria-label visible to assistive technology', async () => { + mockFetchWalletStatus.mockResolvedValue(makeStatus()); + + render(); + await screen.findByTitle(SOLANA_ADDRESS); + + // The button must be discoverable by assistive technology. + expect(screen.getByRole('button', { name: /copy address/i })).toBeInTheDocument(); + }); +}); diff --git a/app/src/agentworld/components/WalletAddressChip.tsx b/app/src/agentworld/components/WalletAddressChip.tsx new file mode 100644 index 000000000..be260d1bc --- /dev/null +++ b/app/src/agentworld/components/WalletAddressChip.tsx @@ -0,0 +1,236 @@ +/** + * WalletAddressChip — persistent wallet address display for the Agent World + * sidebar header. + * + * Fetches the wallet status on mount, extracts the Solana account address + * (= tiny.place cryptoId), and renders a compact monospace chip with a + * copy-to-clipboard button. Visible from every Agent World section without + * requiring navigation. + * + * States: + * loading — pulse skeleton while wallet_status is in-flight. + * ready — truncated 6…4 address + copy button. + * locked — muted "Wallet not set up" label (no copy). + * + * Copy pattern mirrors WalletBalancesPanel (2s checkmark feedback). + */ +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { useT } from '../../lib/i18n/I18nContext'; +import { fetchWalletStatus } from '../../services/walletApi'; + +// --------------------------------------------------------------------------- +// Address helpers +// --------------------------------------------------------------------------- + +/** Truncate a crypto address to first 6 + last 4: `AbCdEf…wxyz`. */ +function truncateAddress(address: string): string { + if (address.length <= 12) return address; + return `${address.slice(0, 6)}…${address.slice(-4)}`; +} + +// --------------------------------------------------------------------------- +// Icon primitives (inline SVG, 16×16, no external deps) +// --------------------------------------------------------------------------- + +const WalletIcon = () => ( + +); + +const CopyIcon = () => ( + +); + +const CheckIcon = () => ( + +); + +// --------------------------------------------------------------------------- +// Component state type +// --------------------------------------------------------------------------- + +type ChipState = + | { status: 'loading' } + | { status: 'ready'; address: string } + | { status: 'locked' } + | { status: 'error' }; + +// --------------------------------------------------------------------------- +// WalletAddressChip +// --------------------------------------------------------------------------- + +export default function WalletAddressChip() { + const { t } = useT(); + const [state, setState] = useState({ status: 'loading' }); + const [copied, setCopied] = useState(false); + const copyResetTimerRef = useRef | null>(null); + // Monotonic request id so a slow/aborted fetch can't clobber a newer one + // (e.g. when the user taps "retry" while a prior request is still in flight). + const latestRequestIdRef = useRef(0); + + // Fetch wallet status and resolve the chip state. Distinguishes the genuine + // "not set up" case (a successful wallet_status with no Solana account) from a + // transient RPC/transport failure — the latter must NOT masquerade as a locked + // wallet for a user who already has one configured. + const loadStatus = useCallback(async () => { + const requestId = ++latestRequestIdRef.current; + try { + const status = await fetchWalletStatus(); + const solana = (status.accounts ?? []).find(a => a.chain === 'solana'); + if (requestId !== latestRequestIdRef.current) return; + if (solana?.address) { + setState({ status: 'ready', address: solana.address }); + } else { + // Successful response, but the wallet has no Solana account yet. + setState({ status: 'locked' }); + } + } catch (err) { + if (requestId !== latestRequestIdRef.current) return; + // Core RPC unavailable / timeout / transport error — surface a retryable + // error state rather than mislabelling a configured wallet as "not set up". + const message = err instanceof Error ? err.message : String(err); + console.debug('[walletAddressChip] wallet_status fetch failed:', message); + setState({ status: 'error' }); + } + }, []); + + // Fetch wallet status on mount. + useEffect(() => { + // loadStatus only calls setState after an awaited fetch (never synchronously), + // so it does not cause the cascading renders this rule guards against. + // eslint-disable-next-line react-hooks/set-state-in-effect + void loadStatus(); + return () => { + // Invalidate any in-flight request so its resolution is ignored. + latestRequestIdRef.current += 1; + }; + }, [loadStatus]); + + // Cleanup copy-reset timer on unmount. + useEffect( + () => () => { + if (copyResetTimerRef.current !== null) { + clearTimeout(copyResetTimerRef.current); + copyResetTimerRef.current = null; + } + }, + [] + ); + + const handleCopy = useCallback(async (address: string) => { + try { + await navigator.clipboard.writeText(address); + setCopied(true); + if (copyResetTimerRef.current !== null) { + clearTimeout(copyResetTimerRef.current); + } + copyResetTimerRef.current = setTimeout(() => { + setCopied(false); + copyResetTimerRef.current = null; + }, 2000); + } catch { + // Clipboard unavailable in this webview context; silently skip. + } + }, []); + + // ── Loading skeleton ───────────────────────────────────────────────────── + if (state.status === 'loading') { + return ( +
+
+
+
+ ); + } + + // ── Error — transient RPC/transport failure, offer a retry ──────────────── + if (state.status === 'error') { + return ( + + ); + } + + // ── Locked / not configured ─────────────────────────────────────────────── + if (state.status === 'locked') { + return ( +
+ + {t('agentWorld.walletNotConfigured')} +
+ ); + } + + // ── Ready — show truncated address + copy button ────────────────────────── + const { address } = state; + const truncated = truncateAddress(address); + + return ( +
+ + + {truncated} + + +
+ ); +} diff --git a/app/src/agentworld/pages/AgentWorld.tsx b/app/src/agentworld/pages/AgentWorld.tsx index 9f51d39a1..66a7a302e 100644 --- a/app/src/agentworld/pages/AgentWorld.tsx +++ b/app/src/agentworld/pages/AgentWorld.tsx @@ -14,6 +14,7 @@ import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router- import { SidebarContent } from '../../components/layout/shell/SidebarSlot'; import TwoPaneNav from '../../components/layout/TwoPaneNav'; import { useT } from '../../lib/i18n/I18nContext'; +import WalletAddressChip from '../components/WalletAddressChip'; import BountiesSection from './BountiesSection'; import DirectorySection from './DirectorySection'; import ExploreSection from './ExploreSection'; @@ -131,9 +132,12 @@ export default function AgentWorld() { }, ]} header={ -

- {t('agentWorld.description')} -

+
+

+ {t('agentWorld.description')} +

+ +
} />
diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index 59043b08c..700516307 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'الملفات الشخصية', 'agentWorld.marketplace': 'السوق', 'agentWorld.messaging': 'الرسائل', + 'agentWorld.walletNotConfigured': 'المحفظة غير مُهيَّأة', + 'agentWorld.copyAddress': 'نسخ العنوان', + 'agentWorld.addressCopied': 'تم النسخ', + 'agentWorld.walletUnavailable': 'المحفظة غير متاحة', + 'agentWorld.walletRetry': 'إعادة محاولة تحميل المحفظة', // Agent World — Explore section live data 'explore.networkOverview': 'نظرة عامة على الشبكة', 'explore.trendingCommunities': 'المجتمعات الرائجة', diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index 0c9e5947d..e4b398c00 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'প্রোফাইল', 'agentWorld.marketplace': 'মার্কেটপ্লেস', 'agentWorld.messaging': 'বার্তা', + 'agentWorld.walletNotConfigured': 'ওয়ালেট সেট আপ হয়নি', + 'agentWorld.copyAddress': 'ঠিকানা কপি করুন', + 'agentWorld.addressCopied': 'কপি হয়েছে', + 'agentWorld.walletUnavailable': 'ওয়ালেট অনুপলব্ধ', + 'agentWorld.walletRetry': 'ওয়ালেট পুনরায় লোড করুন', // Agent World — Explore section live data 'explore.networkOverview': 'নেটওয়ার্ক ওভারভিউ', 'explore.trendingCommunities': 'ট্রেন্ডিং কমিউনিটি', diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index 87eaa99bf..7eb73eb01 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'Profile', 'agentWorld.marketplace': 'Marktplatz', 'agentWorld.messaging': 'Nachrichten', + 'agentWorld.walletNotConfigured': 'Wallet nicht eingerichtet', + 'agentWorld.copyAddress': 'Adresse kopieren', + 'agentWorld.addressCopied': 'Kopiert', + 'agentWorld.walletUnavailable': 'Wallet nicht verfügbar', + 'agentWorld.walletRetry': 'Wallet erneut laden', // Agent World — Explore section live data 'explore.networkOverview': 'Netzwerkübersicht', 'explore.trendingCommunities': 'Trending-Communities', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index 92cc8e969..0a32882c0 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -37,6 +37,11 @@ const en: TranslationMap = { 'agentWorld.profiles': 'Profiles', 'agentWorld.marketplace': 'Marketplace', 'agentWorld.messaging': 'Messages', + 'agentWorld.walletNotConfigured': 'Wallet not set up', + 'agentWorld.copyAddress': 'Copy address', + 'agentWorld.addressCopied': 'Copied', + 'agentWorld.walletUnavailable': 'Wallet unavailable', + 'agentWorld.walletRetry': 'Retry loading wallet', // Agent World — Explore section live data 'explore.networkOverview': 'Network Overview', 'explore.trendingCommunities': 'Trending Communities', diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index 075bdb502..85abe6ea9 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'Perfiles', 'agentWorld.marketplace': 'Mercado', 'agentWorld.messaging': 'Mensajes', + 'agentWorld.walletNotConfigured': 'Cartera no configurada', + 'agentWorld.copyAddress': 'Copiar dirección', + 'agentWorld.addressCopied': 'Copiada', + 'agentWorld.walletUnavailable': 'Cartera no disponible', + 'agentWorld.walletRetry': 'Reintentar cargar la cartera', // Agent World — Explore section live data 'explore.networkOverview': 'Resumen de red', 'explore.trendingCommunities': 'Comunidades en tendencia', diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index ca6555005..998636d0f 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'Profils', 'agentWorld.marketplace': 'Marché', 'agentWorld.messaging': 'Messages', + 'agentWorld.walletNotConfigured': 'Portefeuille non configuré', + 'agentWorld.copyAddress': "Copier l'adresse", + 'agentWorld.addressCopied': 'Copié', + 'agentWorld.walletUnavailable': 'Portefeuille indisponible', + 'agentWorld.walletRetry': 'Réessayer de charger le portefeuille', // Agent World — Explore section live data 'explore.networkOverview': 'Aperçu du réseau', 'explore.trendingCommunities': 'Communautés tendance', diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index 7f4755321..a9e9a40b6 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'प्रोफ़ाइल', 'agentWorld.marketplace': 'मार्केटप्लेस', 'agentWorld.messaging': 'संदेश', + 'agentWorld.walletNotConfigured': 'वॉलेट सेट नहीं है', + 'agentWorld.copyAddress': 'पता कॉपी करें', + 'agentWorld.addressCopied': 'कॉपी हो गया', + 'agentWorld.walletUnavailable': 'वॉलेट उपलब्ध नहीं', + 'agentWorld.walletRetry': 'वॉलेट फिर से लोड करें', // Agent World — Explore section live data 'explore.networkOverview': 'नेटवर्क अवलोकन', 'explore.trendingCommunities': 'ट्रेंडिंग समुदाय', diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index d315195fa..35780dcf0 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'Profil', 'agentWorld.marketplace': 'Pasar', 'agentWorld.messaging': 'Pesan', + 'agentWorld.walletNotConfigured': 'Dompet belum diatur', + 'agentWorld.copyAddress': 'Salin alamat', + 'agentWorld.addressCopied': 'Disalin', + 'agentWorld.walletUnavailable': 'Dompet tidak tersedia', + 'agentWorld.walletRetry': 'Coba muat dompet lagi', // Agent World — Explore section live data 'explore.networkOverview': 'Ikhtisar Jaringan', 'explore.trendingCommunities': 'Komunitas Trending', diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index 2b62ac97a..cc2a0c55a 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'Profili', 'agentWorld.marketplace': 'Mercato', 'agentWorld.messaging': 'Messaggi', + 'agentWorld.walletNotConfigured': 'Wallet non configurato', + 'agentWorld.copyAddress': 'Copia indirizzo', + 'agentWorld.addressCopied': 'Copiato', + 'agentWorld.walletUnavailable': 'Wallet non disponibile', + 'agentWorld.walletRetry': 'Riprova a caricare il wallet', // Agent World — Explore section live data 'explore.networkOverview': 'Panoramica della rete', 'explore.trendingCommunities': 'Comunità di tendenza', diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 1f215343e..063c66ba1 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': '프로필', 'agentWorld.marketplace': '마켓플레이스', 'agentWorld.messaging': '메시지', + 'agentWorld.walletNotConfigured': '지갑이 설정되지 않음', + 'agentWorld.copyAddress': '주소 복사', + 'agentWorld.addressCopied': '복사됨', + 'agentWorld.walletUnavailable': '지갑을 사용할 수 없음', + 'agentWorld.walletRetry': '지갑 다시 불러오기', // Agent World — Explore section live data 'explore.networkOverview': '네트워크 개요', 'explore.trendingCommunities': '트렌딩 커뮤니티', diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index 51b7fcf50..0b870ff97 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'Profile', 'agentWorld.marketplace': 'Rynek', 'agentWorld.messaging': 'Wiadomości', + 'agentWorld.walletNotConfigured': 'Portfel nie jest skonfigurowany', + 'agentWorld.copyAddress': 'Kopiuj adres', + 'agentWorld.addressCopied': 'Skopiowano', + 'agentWorld.walletUnavailable': 'Portfel niedostępny', + 'agentWorld.walletRetry': 'Spróbuj ponownie załadować portfel', // Agent World — Explore section live data 'explore.networkOverview': 'Przegląd sieci', 'explore.trendingCommunities': 'Popularne społeczności', diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index 0662f5885..65e87b0df 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'Perfis', 'agentWorld.marketplace': 'Mercado', 'agentWorld.messaging': 'Mensagens', + 'agentWorld.walletNotConfigured': 'Carteira não configurada', + 'agentWorld.copyAddress': 'Copiar endereço', + 'agentWorld.addressCopied': 'Copiado', + 'agentWorld.walletUnavailable': 'Carteira indisponível', + 'agentWorld.walletRetry': 'Tentar carregar a carteira novamente', // Agent World — Explore section live data 'explore.networkOverview': 'Visão geral da rede', 'explore.trendingCommunities': 'Comunidades em alta', diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index a8fe1b3ac..fcf0fb255 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': 'Профили', 'agentWorld.marketplace': 'Маркетплейс', 'agentWorld.messaging': 'Сообщения', + 'agentWorld.walletNotConfigured': 'Кошелёк не настроен', + 'agentWorld.copyAddress': 'Скопировать адрес', + 'agentWorld.addressCopied': 'Скопировано', + 'agentWorld.walletUnavailable': 'Кошелёк недоступен', + 'agentWorld.walletRetry': 'Повторить загрузку кошелька', // Agent World — Explore section live data 'explore.networkOverview': 'Обзор сети', 'explore.trendingCommunities': 'Трендовые сообщества', diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index 3aaf99af3..6ee3304b3 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -52,6 +52,11 @@ const messages: TranslationMap = { 'agentWorld.profiles': '档案', 'agentWorld.marketplace': '市场', 'agentWorld.messaging': '消息', + 'agentWorld.walletNotConfigured': '钱包未设置', + 'agentWorld.copyAddress': '复制地址', + 'agentWorld.addressCopied': '已复制', + 'agentWorld.walletUnavailable': '钱包不可用', + 'agentWorld.walletRetry': '重试加载钱包', // Agent World — Explore section live data 'explore.networkOverview': '网络概览', 'explore.trendingCommunities': '热门社区',