diff --git a/public/icons/icon-192x192.png b/public/icons/icon-192x192.png new file mode 100644 index 0000000..948ad65 Binary files /dev/null and b/public/icons/icon-192x192.png differ diff --git a/public/icons/icon-512x512.png b/public/icons/icon-512x512.png new file mode 100644 index 0000000..ae46725 Binary files /dev/null and b/public/icons/icon-512x512.png differ diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..d3ad559 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,30 @@ +{ + "name": "Fast Protocol", + "short_name": "Fast", + "description": "Sub-second swaps on Ethereum powered by preconfirmations", + "start_url": "/", + "display": "standalone", + "background_color": "#030a14", + "theme_color": "#3b8df8", + "orientation": "portrait-primary", + "icons": [ + { + "src": "/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..a38dea4 --- /dev/null +++ b/public/sw.js @@ -0,0 +1,63 @@ +const CACHE_NAME = "fast-protocol-v1"; + +const PRECACHE_URLS = ["/", "/icons/icon-192x192.png", "/icons/icon-512x512.png"]; + +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)) + ); + self.skipWaiting(); +}); + +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))) + ) + ); + self.clients.claim(); +}); + +self.addEventListener("fetch", (event) => { + const { request } = event; + + // Skip non-GET and cross-origin requests + if (request.method !== "GET" || !request.url.startsWith(self.location.origin)) return; + + // Skip API routes and wallet/RPC calls + if (request.url.includes("/api/") || request.url.includes("rpc")) return; + + // Network-first for navigation (HTML pages) + if (request.mode === "navigate") { + event.respondWith( + fetch(request) + .then((response) => { + const clone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); + return response; + }) + .catch(() => caches.match(request)) + ); + return; + } + + // Cache-first for static assets + if ( + request.destination === "image" || + request.destination === "font" || + request.destination === "style" || + request.destination === "script" + ) { + event.respondWith( + caches.match(request).then( + (cached) => + cached || + fetch(request).then((response) => { + const clone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); + return response; + }) + ) + ); + } +}); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3603da8..bb3123f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,10 +5,13 @@ import { Providers } from "@/components/providers" import { Analytics } from "@vercel/analytics/next" import { SpeedInsights } from "@vercel/speed-insights/next" import { getBaseUrl, SITE_URL } from "@/lib/site-config" +import { ServiceWorkerRegister } from "@/components/pwa/service-worker-register" +import { InstallPrompt } from "@/components/pwa/install-prompt" export const viewport: Viewport = { width: "device-width", initialScale: 1, + themeColor: "#3b8df8", } export const metadata: Metadata = { @@ -19,7 +22,11 @@ export const metadata: Metadata = { }, description: "Swap tokens on Ethereum with sub-second execution powered by preconfirmations. Earn tokenized mev rewards with every trade on Fast Protocol.", - icons: { icon: "/icon.png" }, + icons: { + icon: "/icon.png", + apple: "/icons/icon-192x192.png", + }, + manifest: "/manifest.json", keywords: [ "fast swaps", "ethereum swaps", @@ -81,12 +88,19 @@ const jsonLd = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( + + + + +