From e33126f01e012fd9f43cd7e78bc5586a9bf01717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tokin=20=E2=AC=A2=20Cypher?= Date: Fri, 26 Jun 2026 21:40:03 -0700 Subject: [PATCH] Apply mobile compatible transaction signing for window.solana. --- assets/js/webapp/swapUtils.js | 126 ++++++++++++++++------------------ assets/js/webapp/wallet.js | 31 ++++++++- 2 files changed, 87 insertions(+), 70 deletions(-) diff --git a/assets/js/webapp/swapUtils.js b/assets/js/webapp/swapUtils.js index 7a0ab20..85aebd4 100644 --- a/assets/js/webapp/swapUtils.js +++ b/assets/js/webapp/swapUtils.js @@ -1,10 +1,10 @@ // assets/js/webapp/swapUtils.js -// Helper to handle cancellations silently +// Helper to handle user cancellations const handleCancel = (err) => { const msg = err?.message || ""; if (msg.includes("rejected") || msg.includes("cancelled") || msg.includes("user declined")) { - console.log("[Ultra] Swap cancelled by user"); + console.log("[Swap] Cancelled by user"); throw new Error("USER_REJECTED"); } return false; @@ -13,20 +13,25 @@ const handleCancel = (err) => { // ==================== ULTRA SWAP (Primary) ==================== async function performUltraSwap(inputMint, outputMint, rawAmount, provider, connectedWallet) { if (rawAmount < 100000) { - console.warn("[Ultra] Amount too small, skipping Ultra quote"); + console.warn("[Ultra] Amount too small"); throw new Error("Amount too small — try a larger swap"); } try { - console.log(`[Ultra] Requesting quote: ${rawAmount} from ${inputMint} to ${outputMint}`); + console.log(`[Ultra] Quote request: ${rawAmount} ${inputMint} → ${outputMint}`); const ultraRes = await fetch( `https://lite-api.jup.ag/ultra/v1/order?inputMint=${inputMint}&outputMint=${outputMint}&amount=${rawAmount}&taker=${connectedWallet}` ); - if (!ultraRes.ok) throw new Error(`Ultra HTTP ${ultraRes.status}`); + if (!ultraRes.ok) { + const errText = await ultraRes.text().catch(() => ''); + throw new Error(`Ultra Quote Failed ${ultraRes.status}: ${errText}`); + } + const quote = await ultraRes.json(); - if (quote.error) throw new Error(`Ultra Quote Error: ${JSON.stringify(quote.error)}`); - + if (quote.error) throw new Error(`Quote Error: ${JSON.stringify(quote.error)}`); + if (!quote.transaction) throw new Error("No transaction in Ultra response"); + return await executeUltraTransaction(quote, provider); } catch (error) { if (!handleCancel(error)) console.error("[Ultra] Failed:", error.message); @@ -34,89 +39,70 @@ async function performUltraSwap(inputMint, outputMint, rawAmount, provider, conn } } -// ==================== SMART SIGNING WITH CLEVER DETECTION ==================== +// ==================== SMART SIGNING (Mobile-Friendly) ==================== async function executeUltraTransaction(quote, providerParam) { const connectionMethod = window.connectionMethod || localStorage.getItem('connection_method') || 'injected'; - console.log(`[Ultra] Detected connection method: ${connectionMethod}`); + console.log(`[Ultra] Connection: ${connectionMethod} | Wallet: ${window.connectedWallet?.slice(0,8)}...`); let signer = null; - let isEmbedded = false; + // Smart routing based on wallet type if (connectionMethod === 'google' || connectionMethod === 'apple') { - // === EMBEDDED WALLETS (Google / Apple) === - isEmbedded = true; - console.log("[Ultra] Embedded wallet detected — forcing SDK signer only"); + // Google / Apple MUST use SDK + console.log("[Ultra] Embedded wallet detected → using SDK"); const sdk = await window.getPhantomSDK?.(); if (sdk?.solana) { signer = sdk.solana; - console.log("✅ Using Phantom SDK signer (Google/Apple)"); } } else { - // === INJECTED WALLETS (Phantom extension / app) === - console.log("[Ultra] Injected wallet detected — prioritizing window.solana"); - + // Injected (Phantom extension / app) → prefer legacy if (providerParam && typeof providerParam.signTransaction === 'function') { signer = providerParam; - console.log("✅ Using legacy provider (injected)"); - } - else if (window.solana?.isPhantom) { + console.log("✅ Using injected provider"); + } else if (window.solana?.isPhantom) { signer = window.solana; - console.log("✅ Using direct window.solana (injected)"); - } - else { - // Last resort fallback for injected + console.log("✅ Using window.solana"); + } else { const sdk = await window.getPhantomSDK?.(); - if (sdk?.solana) { - signer = sdk.solana; - console.log("✅ Using SDK signer as fallback for injected"); - } + if (sdk?.solana) signer = sdk.solana; } } - if (!signer) { - throw new Error(`No valid signer found for connection type: ${connectionMethod}`); - } + if (!signer) throw new Error("No valid signer found for this wallet type"); - // For embedded wallets we prefer the stable v1 proxy path - if (isEmbedded) { - console.log("[Ultra] Embedded wallet → using v1 proxy for reliable signing"); - return await performV1Swap( - quote.inputMint || "So11111111111111111111111111111111111111112", - quote.outputMint, - quote.inAmount || quote.amount, - providerParam, - window.connectedWallet + try { + console.log("[Ultra] Deserializing transaction..."); + const vtx = solanaWeb3.VersionedTransaction.deserialize( + Buffer.from(quote.transaction, 'base64') ); - } - // Injected path → try direct Jupiter Ultra execute first - try { - console.log("[Ultra] Injected → attempting direct Jupiter Ultra execute"); - const vtx = solanaWeb3.VersionedTransaction.deserialize(Buffer.from(quote.transaction, 'base64')); + console.log("[Ultra] Signing transaction..."); const signed = await signer.signTransaction(vtx); - - const signedTxBase64 = btoa(String.fromCharCode(...new Uint8Array(signed.serialize()))); + + // Safe base64 encoding + const signedTx = btoa(String.fromCharCode(...new Uint8Array(signed.serialize()))); const executeRes = await fetch('https://lite-api.jup.ag/ultra/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - signedTransaction: signedTxBase64, - requestId: quote.requestId + body: JSON.stringify({ + signedTransaction: signedTx, + requestId: quote.requestId }) }); - if (!executeRes.ok) throw new Error(`Ultra Execute HTTP ${executeRes.status}`); - const result = await executeRes.json(); - - if (!result.signature) throw new Error("Ultra execute did not return signature"); + if (!executeRes.ok) { + const errText = await executeRes.text().catch(() => ''); + throw new Error(`Ultra Execute 400: ${executeRes.status} ${errText}`); + } - console.log(`✅ SUCCESS (Ultra direct)! TX: ${result.signature}`); + const result = await executeRes.json(); + console.log(`✅ Ultra Success! TX: ${result.signature}`); return { success: true, txid: result.signature, router: "Ultra" }; - } catch (ultraErr) { - console.warn("[Ultra] Direct Ultra execute failed, falling back to v1 proxy...", ultraErr.message); - if (!handleCancel(ultraErr)) { + } catch (error) { + console.warn("[Ultra] Direct execute failed, falling back to v1 proxy...", error.message); + if (!handleCancel(error)) { return await performV1Swap( quote.inputMint || "So11111111111111111111111111111111111111112", quote.outputMint, @@ -125,14 +111,14 @@ async function executeUltraTransaction(quote, providerParam) { window.connectedWallet ); } - throw ultraErr; + throw error; } } -// ==================== V1 PROXY FALLBACK ==================== +// ==================== V1 PROXY FALLBACK (Most Reliable on Mobile) ==================== async function performV1Swap(inputMint, outputMint, rawAmount, provider, connectedWallet) { try { - console.log("[V1 Proxy] Using fallback proxy"); + console.log("[V1 Proxy] Using backend proxy for reliable signing"); const proxyRes = await fetch('https://giddy-key-swaps-production.up.railway.app/api/swap/v1', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -140,17 +126,19 @@ async function performV1Swap(inputMint, outputMint, rawAmount, provider, connect }); const data = await proxyRes.json(); - if (!data.success) throw new Error(data.error || "v1 proxy failed"); + if (!data.success) throw new Error(data.error || "Proxy failed"); + + const tx = solanaWeb3.VersionedTransaction.deserialize( + Buffer.from(data.swapTransaction, "base64") + ); - const tx = solanaWeb3.VersionedTransaction.deserialize(Buffer.from(data.swapTransaction, "base64")); - - // Re-detect signer for v1 path (respects Google/Apple vs injected) + // Re-resolve signer for v1 path const connectionMethod = window.connectionMethod || localStorage.getItem('connection_method') || 'injected'; let signer = null; if (connectionMethod === 'google' || connectionMethod === 'apple') { const sdk = await window.getPhantomSDK?.(); - signer = sdk?.solana || null; + signer = sdk?.solana; } else { signer = provider || window.solana || (await window.getPhantomSDK?.())?.solana; } @@ -158,15 +146,17 @@ async function performV1Swap(inputMint, outputMint, rawAmount, provider, connect if (!signer) throw new Error("No signer available for v1 fallback"); const result = await signer.signAndSendTransaction(tx); + console.log(`✅ v1 Proxy Success! TX: ${result.signature}`); return { success: true, txid: result.signature, version: "v1" }; + } catch (error) { if (!handleCancel(error)) console.error("[V1 Proxy] Failed:", error.message); throw error; } } -// Global Export +// Global exports window.performUltraSwap = performUltraSwap; window.performV1Swap = performV1Swap; -console.log("✅ swapUtils.js loaded — Smart detection active (Google/Apple → SDK only | Injected → window.solana priority)"); \ No newline at end of file +console.log("✅ swapUtils.js loaded — Mobile-optimized (Smart signer + Ultra + v1 fallback)"); \ No newline at end of file diff --git a/assets/js/webapp/wallet.js b/assets/js/webapp/wallet.js index d671d50..b609929 100644 --- a/assets/js/webapp/wallet.js +++ b/assets/js/webapp/wallet.js @@ -545,6 +545,31 @@ function isMobileDevice() { return /Android|iPhone|iPad|iPod|webOS|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); } +// Get the best available signer for swaps (prioritizes window.solana for injected) +function getBestSigner() { + // 1. Legacy injected (best for Phantom mobile app + extension) + if (window.phantom?.solana) { + console.log("✅ Using window.phantom.solana (injected priority)"); + return window.phantom.solana; + } + if (window.solana?.isPhantom) { + console.log("✅ Using window.solana"); + return window.solana; + } + + // 2. SDK fallback + if (window.getPhantomSDK) { + const sdk = window.getPhantomSDK(); // sync call since it's cached + if (sdk?.solana) { + console.log("✅ Using SDK signer as fallback"); + return sdk.solana; + } + } + + console.warn("⚠️ No signer found"); + return null; +} + // ====================== PHANTOM WALLET INTEGRATION (SDK) ====================== // Main Wallet Dropdown (Add Wallet) @@ -1254,7 +1279,8 @@ async function confirmValueLock() { ? "GsKuLQsKCEnfQxuk4icTEQjc11Av8WiqW31CxZqZpump" : "8kQzvMELBQGSiFmrXqLuDSpYVLKkNoXE4bUQCC14wj3Z"; - const result = await performUltraSwap(inputMint, outputMint, rawAmount, provider, connectedWallet); + // Use smart signer instead of old 'provider' + const result = await performUltraSwap(inputMint, outputMint, rawAmount, getBestSigner(), connectedWallet); showTxSuccess(result); setTimeout(() => { if (connectedWallet) updateWalletBalances(); }, 1500); @@ -1315,7 +1341,8 @@ async function confirmGiddySwap() { outputMint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; } - const result = await performUltraSwap(inputMint, outputMint, rawAmount, provider, connectedWallet); + // Use smart signer + const result = await performUltraSwap(inputMint, outputMint, rawAmount, getBestSigner(), connectedWallet); showTxSuccess(result); setTimeout(() => { if (connectedWallet) updateWalletBalances(); }, 2500);