From e84d1cabd87b15ed104c013dab2be0f99a61f8c5 Mon Sep 17 00:00:00 2001 From: Urko Date: Sat, 31 Jan 2026 10:31:24 +0000 Subject: [PATCH] feat: add phantom integration --- README.md | 4 +- app/globals.css | 46 +++++++++++++++ app/locales/de.json | 17 ++++-- app/locales/en.json | 17 ++++-- app/locales/es.json | 17 ++++-- app/locales/fr.json | 17 ++++-- app/locales/it.json | 17 ++++-- app/locales/sv.json | 17 ++++-- app/page.tsx | 125 ++++++++++++++++++++++++++++++++++++----- package.json | 2 +- public/phantom.svg | 1 + public/trustwallet.svg | Bin 0 -> 16072 bytes 12 files changed, 234 insertions(+), 46 deletions(-) create mode 100644 public/phantom.svg create mode 100644 public/trustwallet.svg diff --git a/README.md b/README.md index d1c7a78..8232742 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # PolyNote · Polygon message pusher -Send hex-encoded messages on Polygon mainnet using the Trust Wallet browser extension or in-app browser. The app builds a zero-value transaction with your message in the data field, so only gas is spent. +Send hex-encoded messages on Polygon mainnet using the Trust Wallet or Phantom browser extensions. The app builds a zero-value transaction with your message in the data field, so only gas is spent. ## What it does -- Connects to an injected EIP-1193 provider (Trust Wallet) and targets Polygon mainnet. +- Connects to injected EIP-1193 providers (Trust Wallet or Phantom) and targets Polygon mainnet. - Hex-encodes up to 280 characters and sends a 0 MATIC transaction carrying the payload. - Internationalization (English, Spanish, French, German, Italian, Swedish) with a locale picker and auto language detection. - Live feedback, copy-to-clipboard for the tx hash, Polygonscan deep links, and a session “recent messages” view. diff --git a/app/globals.css b/app/globals.css index 9fb056d..f00579d 100644 --- a/app/globals.css +++ b/app/globals.css @@ -593,6 +593,52 @@ button:hover:not(:disabled) { font-size: 12px; } +.wallet-selector { + margin-top: 12px; + padding: 14px; + border: 1px solid var(--border); + border-radius: 14px; + background: var(--surface); +} + +.wallet-selector-head { + display: flex; + align-items: baseline; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 10px; +} + +.wallet-toggle { + display: inline-flex; + gap: 10px; +} + +.wallet-chip { + border: 1px solid var(--border); + background: var(--surface-soft); + color: var(--ink); + padding: 10px 14px; + border-radius: 12px; + font-weight: 700; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.wallet-chip.active { + border-color: var(--accent); + background: rgba(14, 165, 233, 0.12); + color: var(--accent-strong); + box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.15); +} + +.wallet-icon { + width: 18px; + height: 18px; + display: inline-block; +} + .footer { margin-top: 8px; padding: 14px 18px; diff --git a/app/locales/de.json b/app/locales/de.json index 221e4c9..8e710fa 100644 --- a/app/locales/de.json +++ b/app/locales/de.json @@ -9,15 +9,16 @@ "light": "Heller Modus" }, "hero": { - "eyebrow": "Eingebettetes Trust Wallet • Polygon Mainnet", - "title": "PolyNote · Trust Wallet zu Polygon", - "lede": "Verbinde dich mit der Trust-Wallet-Erweiterung oder dem In-App-Browser. Wir kodieren deinen Text in Hex und senden eine Null-MATIC-Transaktion auf Polygon – nur Gasgebühren fallen an." + "eyebrow": "Trust Wallet oder Phantom • Polygon Mainnet", + "title": "PolyNote · Eingebettete Wallets zu Polygon", + "lede": "Verbinde dich mit den Erweiterungen von Trust Wallet oder Phantom. Wir kodieren deinen Text in Hex und senden eine 0-MATIC-Transaktion auf Polygon – nur Gasgebühren fallen an." }, "connection": { "title": "Verbindung", - "subtitle": "Eingebettetes Trust Wallet (Browser-Erweiterung oder In-App dApp Browser).", + "subtitle": "Eingebettete Wallet (Trust Wallet- oder Phantom-Erweiterung).", "pill": "Nur Polygon", "connect": "Trust Wallet verbinden", + "connectWallet": "{wallet} verbinden", "disconnect": "Trennen", "account": "Konto", "chain": "Netzwerk", @@ -59,6 +60,12 @@ "footer": { "repo": "Quellcode auf Gitea" }, + "wallet": { + "pick": "Wallet wählen", + "helper": "Nur injizierte Erweiterungen. Wähle Trust Wallet oder Phantom und verbinde dich.", + "trust": "Trust Wallet", + "phantom": "Phantom" + }, "status": { "waiting": "Warten auf Verbindung…", "preparingConnect": "Verbindung wird vorbereitet…", @@ -78,7 +85,7 @@ "disconnected": "Getrennt." }, "errors": { - "noWallet": "Keine eingebettete Wallet gefunden. Verwende die Trust-Wallet-Erweiterung oder den In-App-Browser.", + "noWallet": "Keine injizierte {wallet} gefunden. Installiere die Erweiterung und versuche es erneut.", "noAccounts": "Die Wallet hat keine Konten zurückgegeben.", "sendFailed": "Nachrichten-Transaktion konnte nicht gesendet werden.", "connectionFailed": "Verbindung fehlgeschlagen." diff --git a/app/locales/en.json b/app/locales/en.json index fafb1e1..d9310e1 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -9,15 +9,16 @@ "light": "Light mode" }, "hero": { - "eyebrow": "Injected Trust Wallet • Polygon mainnet", - "title": "PolyNote · Trust Wallet to Polygon", - "lede": "Connect with the Trust Wallet extension or the in-app browser. We hex-encode your text and submit a zero-value Polygon transaction that only costs gas." + "eyebrow": "Trust Wallet or Phantom • Polygon mainnet", + "title": "PolyNote · Injected wallets to Polygon", + "lede": "Connect with the Trust Wallet or Phantom browser extensions. We hex-encode your text and submit a zero-value Polygon transaction that only costs gas." }, "connection": { "title": "Connection", - "subtitle": "Injected Trust Wallet (browser extension or in-app dApp browser).", + "subtitle": "Injected wallet (Trust Wallet or Phantom extension).", "pill": "Polygon only", "connect": "Connect Trust Wallet", + "connectWallet": "Connect {wallet}", "disconnect": "Disconnect", "account": "Account", "chain": "Chain", @@ -59,6 +60,12 @@ "footer": { "repo": "Source on Gitea" }, + "wallet": { + "pick": "Choose a wallet", + "helper": "Injected extensions only. Pick Trust Wallet or Phantom, then connect.", + "trust": "Trust Wallet", + "phantom": "Phantom" + }, "status": { "waiting": "Waiting to connect…", "preparingConnect": "Preparing to connect…", @@ -78,7 +85,7 @@ "disconnected": "Disconnected." }, "errors": { - "noWallet": "No injected wallet found. Try the Trust Wallet extension or app browser.", + "noWallet": "No injected {wallet} found. Install the extension and try again.", "noAccounts": "No accounts returned from injected wallet.", "sendFailed": "Failed to send message transaction.", "connectionFailed": "Connection failed." diff --git a/app/locales/es.json b/app/locales/es.json index 36fc0db..e09cfd7 100644 --- a/app/locales/es.json +++ b/app/locales/es.json @@ -9,15 +9,16 @@ "light": "Modo claro" }, "hero": { - "eyebrow": "Trust Wallet inyectado • Polygon mainnet", - "title": "PolyNote · Trust Wallet a Polygon", - "lede": "Conecta con la extensión de Trust Wallet o el navegador dentro de la app. Codificamos tu texto en hex y enviamos una transacción de valor cero en Polygon; solo pagas gas." + "eyebrow": "Trust Wallet o Phantom • Polygon mainnet", + "title": "PolyNote · Wallets inyectadas a Polygon", + "lede": "Conecta con las extensiones de Trust Wallet o Phantom. Codificamos tu texto en hex y enviamos una transacción de valor cero en Polygon; solo pagas gas." }, "connection": { "title": "Conexión", - "subtitle": "Trust Wallet inyectado (extensión o navegador dentro de la app).", + "subtitle": "Wallet inyectada (extensión Trust Wallet o Phantom).", "pill": "Solo Polygon", "connect": "Conectar Trust Wallet", + "connectWallet": "Conectar {wallet}", "disconnect": "Desconectar", "account": "Cuenta", "chain": "Red", @@ -59,6 +60,12 @@ "footer": { "repo": "Código fuente en Gitea" }, + "wallet": { + "pick": "Elige una wallet", + "helper": "Solo extensiones inyectadas. Elige Trust Wallet o Phantom y conecta.", + "trust": "Trust Wallet", + "phantom": "Phantom" + }, "status": { "waiting": "Esperando conexión…", "preparingConnect": "Preparando conexión…", @@ -78,7 +85,7 @@ "disconnected": "Desconectado." }, "errors": { - "noWallet": "No se encontró una wallet inyectada. Usa la extensión de Trust Wallet o el navegador de la app.", + "noWallet": "No se encontró una {wallet} inyectada. Instala la extensión e inténtalo de nuevo.", "noAccounts": "La wallet no devolvió cuentas.", "sendFailed": "No se pudo enviar la transacción con mensaje.", "connectionFailed": "Fallo de conexión." diff --git a/app/locales/fr.json b/app/locales/fr.json index 6ea903d..5735217 100644 --- a/app/locales/fr.json +++ b/app/locales/fr.json @@ -9,15 +9,16 @@ "light": "Mode clair" }, "hero": { - "eyebrow": "Trust Wallet injecté • Polygon mainnet", - "title": "PolyNote · Trust Wallet vers Polygon", - "lede": "Connecte-toi avec l’extension Trust Wallet ou le navigateur intégré. Nous encodons ton texte en hex et envoyons une transaction à 0 MATIC sur Polygon ; seuls les frais de gas sont dus." + "eyebrow": "Trust Wallet ou Phantom • Polygon mainnet", + "title": "PolyNote · Wallets injectés vers Polygon", + "lede": "Connecte-toi avec les extensions Trust Wallet ou Phantom. Nous encodons ton texte en hex et envoyons une transaction à 0 MATIC sur Polygon ; seuls les frais de gas sont dus." }, "connection": { "title": "Connexion", - "subtitle": "Trust Wallet injecté (extension navigateur ou navigateur dApp intégré).", + "subtitle": "Wallet injectée (extension Trust Wallet ou Phantom).", "pill": "Polygon uniquement", "connect": "Connecter Trust Wallet", + "connectWallet": "Connecter {wallet}", "disconnect": "Déconnecter", "account": "Compte", "chain": "Réseau", @@ -59,6 +60,12 @@ "footer": { "repo": "Code source sur Gitea" }, + "wallet": { + "pick": "Choisis un wallet", + "helper": "Extensions injectées uniquement. Choisis Trust Wallet ou Phantom, puis connecte-toi.", + "trust": "Trust Wallet", + "phantom": "Phantom" + }, "status": { "waiting": "En attente de connexion…", "preparingConnect": "Préparation de la connexion…", @@ -78,7 +85,7 @@ "disconnected": "Déconnecté." }, "errors": { - "noWallet": "Aucune wallet injectée trouvée. Utilise l’extension Trust Wallet ou le navigateur de l’app.", + "noWallet": "Aucun {wallet} injecté trouvé. Installe l’extension et réessaie.", "noAccounts": "La wallet n’a renvoyé aucun compte.", "sendFailed": "Impossible d’envoyer la transaction message.", "connectionFailed": "Échec de connexion." diff --git a/app/locales/it.json b/app/locales/it.json index 870afc5..e197ff8 100644 --- a/app/locales/it.json +++ b/app/locales/it.json @@ -9,15 +9,16 @@ "light": "Modalità chiara" }, "hero": { - "eyebrow": "Trust Wallet iniettato • Polygon mainnet", - "title": "PolyNote · Trust Wallet su Polygon", - "lede": "Connetti con l’estensione di Trust Wallet o il browser integrato. Codifichiamo il testo in hex e inviamo una transazione a 0 MATIC su Polygon; paghi solo il gas." + "eyebrow": "Trust Wallet o Phantom • Polygon mainnet", + "title": "PolyNote · Wallet iniettate su Polygon", + "lede": "Connettiti con le estensioni di Trust Wallet o Phantom. Codifichiamo il testo in hex e inviamo una transazione a 0 MATIC su Polygon; paghi solo il gas." }, "connection": { "title": "Connessione", - "subtitle": "Trust Wallet iniettato (estensione o browser dApp integrato).", + "subtitle": "Wallet iniettata (estensione Trust Wallet o Phantom).", "pill": "Solo Polygon", "connect": "Connetti Trust Wallet", + "connectWallet": "Connetti {wallet}", "disconnect": "Disconnetti", "account": "Account", "chain": "Rete", @@ -59,6 +60,12 @@ "footer": { "repo": "Codice sorgente su Gitea" }, + "wallet": { + "pick": "Scegli un wallet", + "helper": "Solo estensioni iniettate. Scegli Trust Wallet o Phantom, poi connettiti.", + "trust": "Trust Wallet", + "phantom": "Phantom" + }, "status": { "waiting": "In attesa di connessione…", "preparingConnect": "Preparazione della connessione…", @@ -78,7 +85,7 @@ "disconnected": "Disconnesso." }, "errors": { - "noWallet": "Nessun wallet iniettato trovato. Usa l’estensione Trust Wallet o il browser dell’app.", + "noWallet": "Nessuna {wallet} iniettata trovata. Installa l’estensione e riprova.", "noAccounts": "Il wallet non ha restituito account.", "sendFailed": "Impossibile inviare la transazione con messaggio.", "connectionFailed": "Connessione fallita." diff --git a/app/locales/sv.json b/app/locales/sv.json index 446ee40..ff42459 100644 --- a/app/locales/sv.json +++ b/app/locales/sv.json @@ -9,15 +9,16 @@ "light": "Ljust läge" }, "hero": { - "eyebrow": "Injektad Trust Wallet • Polygon mainnet", - "title": "PolyNote · Trust Wallet till Polygon", - "lede": "Anslut via Trust Wallets tillägg eller den inbyggda webbläsaren. Vi kodar din text i hex och skickar en 0 MATIC-transaktion på Polygon; du betalar bara gas." + "eyebrow": "Trust Wallet eller Phantom • Polygon mainnet", + "title": "PolyNote · Injekterade wallets till Polygon", + "lede": "Anslut med Trust Wallet- eller Phantom-tilläggen. Vi kodar din text i hex och skickar en 0 MATIC-transaktion på Polygon; du betalar bara gas." }, "connection": { "title": "Anslutning", - "subtitle": "Injekterad Trust Wallet (tillägg eller in-app dApp-webbläsare).", + "subtitle": "Injekterad wallet (Trust Wallet- eller Phantom-tillägg).", "pill": "Endast Polygon", "connect": "Anslut Trust Wallet", + "connectWallet": "Anslut {wallet}", "disconnect": "Koppla från", "account": "Konto", "chain": "Nätverk", @@ -59,6 +60,12 @@ "footer": { "repo": "Källkod på Gitea" }, + "wallet": { + "pick": "Välj en wallet", + "helper": "Endast injekterade tillägg. Välj Trust Wallet eller Phantom och anslut.", + "trust": "Trust Wallet", + "phantom": "Phantom" + }, "status": { "waiting": "Väntar på anslutning…", "preparingConnect": "Förbereder anslutning…", @@ -78,7 +85,7 @@ "disconnected": "Frånkopplad." }, "errors": { - "noWallet": "Ingen injekterad plånbok hittades. Använd Trust Wallet-tillägget eller appens webbläsare.", + "noWallet": "Ingen injekterad {wallet} hittades. Installera tillägget och försök igen.", "noAccounts": "Plånboken returnerade inga konton.", "sendFailed": "Det gick inte att skicka meddelande-transaktionen.", "connectionFailed": "Anslutningen misslyckades." diff --git a/app/page.tsx b/app/page.tsx index 7a131b7..d612557 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -24,11 +24,13 @@ type Eip1193Provider = { providers?: Eip1193Provider[]; isTrustWallet?: boolean; isTrust?: boolean; + isPhantom?: boolean; }; type EthereumWindow = Window & { ethereum?: Eip1193Provider & { providers?: Eip1193Provider[] }; trustwallet?: Eip1193Provider; + phantom?: { ethereum?: Eip1193Provider }; }; const formatChain = (chainId: number | null) => { @@ -45,6 +47,32 @@ type MessageEntry = { createdAt: number; }; +function TrustIcon() { + return ( + + ); +} + +function PhantomIcon() { + return ( + + ); +} + export default function HomePage() { const { locale, setLocale, t, localeOptions } = useI18n("en"); const [account, setAccount] = useState(""); @@ -55,6 +83,7 @@ export default function HomePage() { key: "status.waiting", tone: "info", }); + const [walletChoice, setWalletChoice] = useState<"trust" | "phantom">("trust"); const [connecting, setConnecting] = useState(false); const [sending, setSending] = useState(false); const [connected, setConnected] = useState(false); @@ -72,6 +101,7 @@ export default function HomePage() { : status.tone === "error" ? "status-line status-error" : "status-line"; + const walletLabel = walletChoice === "trust" ? t("wallet.trust") : t("wallet.phantom"); const chainPillClass = onPolygon ? "pill pill-live" : "pill pill-quiet"; const chainPillText = onPolygon ? "Polygon" : chainLabel || t("live.chainRequired"); const disableSend = !connected || sending; @@ -106,7 +136,7 @@ export default function HomePage() { await disconnect(false); try { - await connectInjected(); + await connectInjected(walletChoice); } catch (err) { console.error("Connection failed", err); setStatusMessage(normalizeError(err, t("errors.connectionFailed"), t), "error"); @@ -116,10 +146,14 @@ export default function HomePage() { } } - async function connectInjected() { - const injected = pickInjectedProvider(); + async function connectInjected(choice: "trust" | "phantom") { + const injected = pickInjectedProvider(choice); if (!injected) { - setStatusMessage(t("errors.noWallet"), "error", "errors.noWallet"); + setStatusMessage( + t("errors.noWallet", { wallet: t(choice === "trust" ? "wallet.trust" : "wallet.phantom") }), + "error", + "errors.noWallet" + ); return; } @@ -174,6 +208,23 @@ export default function HomePage() { setTxHash("—"); } + async function refreshSigner() { + if (!providerRef.current) return; + try { + const browserProvider = new ethers.BrowserProvider( + providerRef.current as unknown as ethers.Eip1193Provider + ); + const signer = await browserProvider.getSigner(); + signerRef.current = signer; + const address = await signer.getAddress(); + const network = await browserProvider.getNetwork(); + setAccount(address); + setChainId(Number(network.chainId)); + } catch (err) { + console.warn("Failed to refresh signer after chain change", err); + } + } + function attachProviderEvents(provider: Eip1193Provider) { if (!provider?.on) return; provider.on("accountsChanged", handleAccountsChanged); @@ -210,6 +261,7 @@ export default function HomePage() { if (typeof newChainId !== "string" && typeof newChainId !== "number") return; const parsed = parseChainId(newChainId); setChainId(parsed); + void refreshSigner(); } async function ensurePolygonChain() { @@ -220,6 +272,7 @@ export default function HomePage() { params: [{ chainId: "0x89" }], }); setChainId(137); + await refreshSigner(); return true; } catch (err) { console.warn("Chain switch rejected", err); @@ -393,6 +446,33 @@ export default function HomePage() { {theme === "dark" ? t("theme.light") : t("theme.dark")} + +
+
+

{t("wallet.pick")}

+

{t("wallet.helper")}

+
+
+ + +
+
@@ -413,7 +493,9 @@ export default function HomePage() { onClick={connectWallet} disabled={disableConnect} > - {connecting ? t("compose.sending") : t("connection.connect")} + {connecting + ? t("compose.sending") + : t("connection.connectWallet", { wallet: walletLabel })}