diff --git a/.env.example b/.env.example index f5c3dea..e42af6f 100644 --- a/.env.example +++ b/.env.example @@ -63,6 +63,13 @@ ENABLE_DA_TRACKING=false # FAUCET_AMOUNT=0.01 # FAUCET_COOLDOWN_MINUTES=30 +# Optional: force Docker to emulate/build a specific architecture. +# Leave unset for native host builds. +# Common values: +# DOCKER_DEFAULT_PLATFORM=linux/amd64 +# DOCKER_DEFAULT_PLATFORM=linux/arm64 +# DOCKER_DEFAULT_PLATFORM=linux/arm64/v8 + # Optional snapshot feature (daily pg_dump backups) # SNAPSHOT_ENABLED=false # SNAPSHOT_TIME=03:00 # UTC time (HH:MM) to run daily pg_dump diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07a31a6..5c2a749 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,8 +22,9 @@ jobs: uses: actions/checkout@v4 - name: Setup Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@master with: + toolchain: stable components: rustfmt - name: Format @@ -40,8 +41,9 @@ jobs: uses: actions/checkout@v4 - name: Setup Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@master with: + toolchain: stable components: clippy - name: Cache Cargo @@ -63,7 +65,9 @@ jobs: uses: actions/checkout@v4 - name: Setup Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable - name: Cache Cargo uses: Swatinem/rust-cache@v2 @@ -102,7 +106,9 @@ jobs: uses: actions/checkout@v4 - name: Setup Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable - name: Cache Cargo uses: Swatinem/rust-cache@v2 @@ -194,7 +200,9 @@ jobs: fetch-depth: 0 - name: Setup Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable - name: Cache Cargo uses: Swatinem/rust-cache@v2 diff --git a/backend/crates/atlas-server/src/api/handlers/contracts.rs b/backend/crates/atlas-server/src/api/handlers/contracts.rs index 1493791..3da0dd3 100644 --- a/backend/crates/atlas-server/src/api/handlers/contracts.rs +++ b/backend/crates/atlas-server/src/api/handlers/contracts.rs @@ -399,13 +399,14 @@ async fn get_solc_binary(version: &str, cache_dir: &str) -> Result Result<&'static str, AtlasError> { match (os, arch) { ("linux", "x86_64") => Ok("linux-amd64"), + ("linux", "aarch64") => Ok("linux-arm64"), // Solidity's official static macOS binaries are currently published under // macosx-amd64. Apple Silicon can execute them natively via Rosetta. ("macos", "x86_64") | ("macos", "aarch64") => Ok("macosx-amd64"), _ => Err(AtlasError::Verification(format!( "unsupported platform for native solc download: {os}/{arch}. \ - Official Solidity static binaries are currently available for linux/x86_64 \ - and macOS. For Docker on Apple Silicon, run atlas-server as linux/amd64." + Official Solidity static binaries are currently available for \ + linux/x86_64, linux/aarch64, and macOS." ))), } } @@ -1010,17 +1011,19 @@ mod tests { } #[test] - fn solc_binary_target_supports_macos_arm64_via_rosetta() { + fn solc_binary_target_supports_linux_arm64() { assert_eq!( - solc_binary_target("macos", "aarch64").unwrap(), - "macosx-amd64" + solc_binary_target("linux", "aarch64").unwrap(), + "linux-arm64" ); } #[test] - fn solc_binary_target_rejects_linux_arm64() { - let err = solc_binary_target("linux", "aarch64").unwrap_err(); - assert!(matches!(err, AtlasError::Verification(_))); + fn solc_binary_target_supports_macos_arm64_via_rosetta() { + assert_eq!( + solc_binary_target("macos", "aarch64").unwrap(), + "macosx-amd64" + ); } #[test] diff --git a/backend/crates/atlas-server/src/main.rs b/backend/crates/atlas-server/src/main.rs index e120c74..ec84614 100644 --- a/backend/crates/atlas-server/src/main.rs +++ b/backend/crates/atlas-server/src/main.rs @@ -117,11 +117,10 @@ pub(crate) fn postgres_connection_config(db_url: &str) -> Result { - if !value.is_empty() { - database_name = value.into_owned(); - } + "dbname" if !value.is_empty() => { + database_name = value.into_owned(); } + "dbname" => {} "host" => { set_pg_env(&mut env_vars, "PGHOST", value.as_ref()); } diff --git a/docker-compose.yml b/docker-compose.yml index ff7188d..4a15c8e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,9 +16,6 @@ services: retries: 5 atlas-server: - # Contract verification downloads Solidity's official linux-amd64 binary. - # Keep atlas-server on amd64 locally so verification works on Apple Silicon. - platform: linux/amd64 build: context: ./backend dockerfile: Dockerfile @@ -40,6 +37,8 @@ services: FAUCET_ENABLED: ${FAUCET_ENABLED:-false} CHAIN_NAME: ${CHAIN_NAME:-Unknown} CHAIN_LOGO_URL: ${CHAIN_LOGO_URL:-} + CHAIN_LOGO_URL_LIGHT: ${CHAIN_LOGO_URL_LIGHT:-} + CHAIN_LOGO_URL_DARK: ${CHAIN_LOGO_URL_DARK:-} ACCENT_COLOR: ${ACCENT_COLOR:-} BACKGROUND_COLOR_DARK: ${BACKGROUND_COLOR_DARK:-} BACKGROUND_COLOR_LIGHT: ${BACKGROUND_COLOR_LIGHT:-} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e558eed..2283894 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -24,8 +24,8 @@ const StatusPage = lazy(() => import('./pages/StatusPage')); function PageLoader() { return ( -
- Loading... +
+ Loading route
); } diff --git a/frontend/src/assets/defaultLogos.ts b/frontend/src/assets/defaultLogos.ts new file mode 100644 index 0000000..92c5981 --- /dev/null +++ b/frontend/src/assets/defaultLogos.ts @@ -0,0 +1,9 @@ +import darkLogo from './evolve-logo.svg'; +import lightLogo from './evolve-logo-light.svg'; +import type { Theme } from '../context/theme-context'; + +export function getDefaultLogo(theme: Theme): string { + return theme === 'light' ? lightLogo : darkLogo; +} + +export { darkLogo as defaultDarkLogo, lightLogo as defaultLightLogo }; diff --git a/frontend/src/assets/evolve-logo-light.svg b/frontend/src/assets/evolve-logo-light.svg new file mode 100644 index 0000000..b92eab9 --- /dev/null +++ b/frontend/src/assets/evolve-logo-light.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/evolve-logo.svg b/frontend/src/assets/evolve-logo.svg new file mode 100644 index 0000000..16d6bfe --- /dev/null +++ b/frontend/src/assets/evolve-logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/components/BrandPrimitives.tsx b/frontend/src/components/BrandPrimitives.tsx new file mode 100644 index 0000000..e8602b3 --- /dev/null +++ b/frontend/src/components/BrandPrimitives.tsx @@ -0,0 +1,245 @@ +import type { ReactNode } from 'react'; + +type EntityKind = + | 'search' + | 'block' + | 'blocks' + | 'transaction' + | 'transactions' + | 'address' + | 'addresses' + | 'token' + | 'tokens' + | 'nft' + | 'nfts' + | 'status' + | 'faucet' + | 'notfound'; + +interface PageHeroProps { + eyebrow?: string; + title: ReactNode; + description?: ReactNode; + actions?: ReactNode; + meta?: ReactNode; + visual?: ReactNode; + compact?: boolean; + className?: string; +} + +export function PageHero({ + eyebrow, + title, + description, + actions, + meta, + visual, + compact = false, + className = '', +}: PageHeroProps) { + return ( +
+
+ {eyebrow &&

{eyebrow}

} +

{title}

+ {description &&

{description}

} + {actions &&
{actions}
} + {meta &&
{meta}
} +
+
+ {visual ?? } +
+
+ ); +} + +interface StatCardProps { + label: ReactNode; + value: ReactNode; + hint?: ReactNode; + className?: string; +} + +export function StatCard({ label, value, hint, className = '' }: StatCardProps) { + return ( +
+

{label}

+
{value}
+ {hint &&

{hint}

} +
+ ); +} + +interface SectionPanelProps { + eyebrow?: string; + title?: ReactNode; + actions?: ReactNode; + className?: string; + children: ReactNode; +} + +export function SectionPanel({ + eyebrow, + title, + actions, + className = '', + children, +}: SectionPanelProps) { + return ( +
+ {(eyebrow || title || actions) && ( +
+
+ {eyebrow &&

{eyebrow}

} + {title &&

{title}

} +
+ {actions &&
{actions}
} +
+ )} + {children} +
+ ); +} + +interface EmptyStateProps { + title: ReactNode; + description?: ReactNode; + action?: ReactNode; + className?: string; +} + +export function EmptyState({ + title, + description, + action, + className = '', +}: EmptyStateProps) { + return ( +
+
+
+

{title}

+ {description &&

{description}

} +
+ {action &&
{action}
} +
+ ); +} + +export function BrandOrnament({ compact = false }: { compact?: boolean }) { + return ( +
+
+
+
+
+
+ Atlas + Explorer +
+
+ ); +} + +export function EntityHeroVisual({ kind }: { kind: EntityKind }) { + return ( +
+
+
+
+
+
+ +
+
+ ); +} + +function EntityGlyph({ kind }: { kind: EntityKind }) { + const cls = 'h-16 w-16 md:h-20 md:w-20 text-fg'; + + if (kind === 'blocks' || kind === 'block') { + return ( + + + + + + ); + } + + if (kind === 'transactions' || kind === 'transaction') { + return ( + + + + + + + ); + } + + if (kind === 'addresses' || kind === 'address') { + return ( + + + + + + ); + } + + if (kind === 'tokens' || kind === 'token') { + return ( + + + + + ); + } + + if (kind === 'nfts' || kind === 'nft') { + return ( + + + + + + ); + } + + if (kind === 'status') { + return ( + + + + + ); + } + + if (kind === 'faucet') { + return ( + + + + + + ); + } + + if (kind === 'notfound') { + return ( + + + + + ); + } + + return ( + + + + + ); +} diff --git a/frontend/src/components/CopyButton.tsx b/frontend/src/components/CopyButton.tsx index 45b6c45..69bc2a0 100644 --- a/frontend/src/components/CopyButton.tsx +++ b/frontend/src/components/CopyButton.tsx @@ -21,7 +21,7 @@ export default function CopyButton({ text, className = '' }: CopyButtonProps) { return ( +
- {/* Navigation - centered on desktop */} -
- - {/* Mobile navigation */} -
- {/* In-flow search bar under the header (hidden on home hero) */} {!isHome && ( -
-
-
- +
+
+
+
)} - {/* Main content */} -
-
+
+
- {/* Footer */} -