From 0a9c0205ea7abe3fe1f03ed510d890f2833d2f0f Mon Sep 17 00:00:00 2001 From: Martin <60740992+aster2333@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:28:55 +0800 Subject: [PATCH 1/6] Tighten wallet provider typing and clean lint errors --- components/solana-provider.tsx | 103 +++++++++++------- src/components/EnhancedWalletButton.tsx | 25 ++++- src/components/ui/textarea.tsx | 2 +- src/hooks/useWallet.ts | 139 ++++++++++++------------ src/pages/SubmitEvidence.tsx | 1 - 5 files changed, 152 insertions(+), 118 deletions(-) diff --git a/components/solana-provider.tsx b/components/solana-provider.tsx index 2cb0b41..8787402 100644 --- a/components/solana-provider.tsx +++ b/components/solana-provider.tsx @@ -25,6 +25,25 @@ import { import { useStore } from "../src/store/useStore"; import type { User } from "../src/store/useStore"; +type LegacySolanaAdapter = { + isPhantom?: boolean; + isSolflare?: boolean; + isSolana?: boolean; + connect?: () => Promise<{ publicKey: { toString(): string } }>; + disconnect?: () => Promise; + publicKey?: { toString(): string }; + signAndSendTransaction?: (input: { + transaction: Uint8Array; + chain?: string; + }) => Promise<{ signature: string }>; +}; + +type LegacyWalletWindow = Window & { + phantom?: { solana?: LegacySolanaAdapter }; + solflare?: (LegacySolanaAdapter & { isSolflare?: boolean }) | undefined; + backpack?: (LegacySolanaAdapter & { isBackpack?: boolean }) | undefined; +}; + type TraditionalWallet = { name: string; icon: string; @@ -55,48 +74,54 @@ const detectTraditionalWallets = () => { const traditionalWallets: TraditionalWallet[] = []; // Check for Phantom - if (typeof window !== 'undefined' && (window as any).phantom?.solana) { - console.log("🔍 Phantom wallet detected via window.phantom"); - traditionalWallets.push({ - name: "Phantom", - icon: "https://phantom.app/img/phantom-logo.svg", - chains: ["solana:mainnet", "solana:devnet", "solana:testnet"], - features: [StandardConnect, "solana:signAndSendTransaction"], - accounts: [], - isTraditional: true, - adapter: (window as any).phantom.solana - }); - } + if (typeof window !== "undefined") { + const legacyWindow = window as LegacyWalletWindow; + + if (legacyWindow.phantom?.solana) { + console.log("🔍 Phantom wallet detected via window.phantom"); + traditionalWallets.push({ + name: "Phantom", + icon: "https://phantom.app/img/phantom-logo.svg", + chains: ["solana:mainnet", "solana:devnet", "solana:testnet"], + features: [StandardConnect, "solana:signAndSendTransaction"], + accounts: [], + isTraditional: true, + adapter: legacyWindow.phantom.solana + }); + } - // Check for Solflare - if (typeof window !== 'undefined' && (window as any).solflare?.isSolflare) { - console.log("🔍 Solflare wallet detected via window.solflare"); - traditionalWallets.push({ - name: "Solflare", - icon: "https://solflare.com/favicon.ico", - chains: ["solana:mainnet", "solana:devnet", "solana:testnet"], - features: [StandardConnect, "solana:signAndSendTransaction"], - accounts: [], - isTraditional: true, - adapter: (window as any).solflare - }); - } + // Check for Solflare + if (legacyWindow?.solflare?.isSolflare) { + console.log("🔍 Solflare wallet detected via window.solflare"); + traditionalWallets.push({ + name: "Solflare", + icon: "https://solflare.com/favicon.ico", + chains: ["solana:mainnet", "solana:devnet", "solana:testnet"], + features: [StandardConnect, "solana:signAndSendTransaction"], + accounts: [], + isTraditional: true, + adapter: legacyWindow.solflare + }); + } - // Check for Backpack - if (typeof window !== 'undefined' && (window as any).backpack?.isSolana) { - console.log("🔍 Backpack wallet detected via window.backpack"); - traditionalWallets.push({ - name: "Backpack", - icon: "https://backpack.app/favicon.ico", - chains: ["solana:mainnet", "solana:devnet", "solana:testnet"], - features: [StandardConnect, "solana:signAndSendTransaction"], - accounts: [], - isTraditional: true, - adapter: (window as any).backpack - }); + // Check for Backpack + if (legacyWindow?.backpack?.isSolana) { + console.log("🔍 Backpack wallet detected via window.backpack"); + traditionalWallets.push({ + name: "Backpack", + icon: "https://backpack.app/favicon.ico", + chains: ["solana:mainnet", "solana:devnet", "solana:testnet"], + features: [StandardConnect, "solana:signAndSendTransaction"], + accounts: [], + isTraditional: true, + adapter: legacyWindow.backpack + }); + } + + console.log("🔍 Traditional wallets detected:", traditionalWallets.length); + return traditionalWallets; } - - console.log("🔍 Traditional wallets detected:", traditionalWallets.length); + return traditionalWallets; }; diff --git a/src/components/EnhancedWalletButton.tsx b/src/components/EnhancedWalletButton.tsx index 51eab57..807b919 100644 --- a/src/components/EnhancedWalletButton.tsx +++ b/src/components/EnhancedWalletButton.tsx @@ -24,7 +24,12 @@ import { } from './ui/dropdown-menu'; import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog'; -import { useConnect, useDisconnect, type UiWallet, type UiWalletAccount } from '@wallet-standard/react'; +import { + useConnect, + useDisconnect, + type UiWallet, + type UiWalletAccount +} from '@wallet-standard/react'; import { useSolana } from '../../components/solana-provider'; import { useWalletConnect } from '../providers/WalletConnectProvider'; import { useLocalWallet } from '../providers/LocalWalletProvider'; @@ -68,9 +73,16 @@ function WalletMenuItem({ wallet, onConnect }: { wallet: UiWallet; onConnect: () } // 检查钱包是否真正可用 + type LegacyWalletWindow = Window & { + phantom?: { solana?: unknown }; + solflare?: unknown; + backpack?: unknown; + coinbaseSolana?: unknown; + }; + const isWalletAvailable = () => { if (!wallet) return false; - + // 检查钱包是否有必要的属性 if (!wallet.name || !wallet.accounts) return false; @@ -79,16 +91,17 @@ function WalletMenuItem({ wallet, onConnect }: { wallet: UiWallet; onConnect: () // 对于浏览器扩展钱包,检查是否真正安装 if (typeof window !== 'undefined') { + const legacyWindow = window as LegacyWalletWindow; // 检查常见的钱包对象 const walletName = wallet.name.toLowerCase(); if (walletName.includes('phantom')) { - return Boolean((window as any).phantom?.solana); + return Boolean(legacyWindow.phantom?.solana); } else if (walletName.includes('solflare')) { - return Boolean((window as any).solflare); + return Boolean(legacyWindow.solflare); } else if (walletName.includes('backpack')) { - return Boolean((window as any).backpack); + return Boolean(legacyWindow.backpack); } else if (walletName.includes('coinbase')) { - return Boolean((window as any).coinbaseSolana); + return Boolean(legacyWindow.coinbaseSolana); } } diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx index f74cb15..a167354 100644 --- a/src/components/ui/textarea.tsx +++ b/src/components/ui/textarea.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { cn } from '../../lib/utils'; -export interface TextareaProps extends React.TextareaHTMLAttributes {} +export type TextareaProps = React.TextareaHTMLAttributes; export const Textarea = React.forwardRef( ({ className, ...props }, ref) => { diff --git a/src/hooks/useWallet.ts b/src/hooks/useWallet.ts index 2337b3c..35835d4 100644 --- a/src/hooks/useWallet.ts +++ b/src/hooks/useWallet.ts @@ -1,45 +1,62 @@ -import { useWallet as useSolanaWallet } from '@solana/wallet-adapter-react' -import { useConnection } from '@solana/wallet-adapter-react' import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useSolana } from '../../components/solana-provider' import { useStore } from '../store/useStore' import { useErrorHandler } from './useErrorHandler' export const useWallet = () => { - const { - wallet, - publicKey, - connected, - connecting, - disconnect, - sendTransaction - } = useSolanaWallet() - - const { connection } = useConnection() + const { + balance: solanaBalance, + refreshBalance, + sendSol, + disconnect: disconnectSolana, + connection, + isConnected, + publicKey: solanaPublicKey, + } = useSolana() const { user, setUser, setIsWalletConnected } = useStore() const { handleError, handleSuccess } = useErrorHandler() - const [balance, setBalance] = useState(0) + const [balance, setBalance] = useState(() => + typeof solanaBalance === 'number' ? solanaBalance : 0 + ) const [loading, setLoading] = useState(false) - // 获取钱包余额 - const getBalance = async () => { + const publicKey = useMemo(() => { + if (!solanaPublicKey) return null + + try { + return new PublicKey(solanaPublicKey) + } catch (error) { + console.error('Failed to parse public key', error) + return null + } + }, [solanaPublicKey]) + + const getBalance = useCallback(async () => { if (!publicKey || !connection) return 0 - + try { - const balance = await connection.getBalance(publicKey) - return balance / LAMPORTS_PER_SOL + const lamports = await connection.getBalance(publicKey) + return lamports / LAMPORTS_PER_SOL } catch (error) { handleError(error, '获取余额失败') return 0 } - } + }, [connection, handleError, publicKey]) - // 更新余额 - const updateBalance = async () => { + const updateBalance = useCallback(async () => { if (!publicKey) return - + setLoading(true) try { + if (refreshBalance) { + const refreshed = await refreshBalance() + if (typeof refreshed === 'number') { + setBalance(refreshed) + return + } + } + const newBalance = await getBalance() setBalance(newBalance) } catch (error) { @@ -47,14 +64,18 @@ export const useWallet = () => { } finally { setLoading(false) } - } + }, [getBalance, handleError, publicKey, refreshBalance]) - // 监听钱包连接状态变化 useEffect(() => { - setIsWalletConnected(connected) - - if (connected && publicKey) { - // 创建或更新用户信息 + if (typeof solanaBalance === 'number') { + setBalance(solanaBalance) + } + }, [solanaBalance]) + + useEffect(() => { + setIsWalletConnected(isConnected) + + if (isConnected && publicKey) { const userData = { address: publicKey.toString(), publicKey: publicKey.toString(), @@ -66,62 +87,44 @@ export const useWallet = () => { balance: balance, nfts: user?.nfts || [] } - + setUser(userData) updateBalance() } else { setUser(null) setBalance(0) } - }, [connected, publicKey, setUser, setIsWalletConnected]) + }, [balance, isConnected, publicKey, setIsWalletConnected, setUser, updateBalance, user]) - // 监听余额变化 useEffect(() => { - if (connected && publicKey) { + if (isConnected && publicKey) { updateBalance() - - // 设置定期更新余额 - const interval = setInterval(updateBalance, 30000) // 每30秒更新一次 + + const interval = setInterval(updateBalance, 30000) return () => clearInterval(interval) } - }, [connected, publicKey]) + }, [isConnected, publicKey, updateBalance]) - // 发送SOL - const sendSOL = async (to: string, amount: number) => { - if (!publicKey || !sendTransaction) { + const sendSOL = useCallback(async (to: string, amount: number) => { + if (!publicKey) { throw new Error('钱包未连接') } try { - const { Transaction, SystemProgram } = await import('@solana/web3.js') - - const transaction = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: publicKey, - toPubkey: new PublicKey(to), - lamports: amount * LAMPORTS_PER_SOL, - }) - ) - - const signature = await sendTransaction(transaction, connection) - - // 等待交易确认 - await connection.confirmTransaction(signature, 'confirmed') - - // 更新余额 + const signature = await sendSol(to, amount) + await updateBalance() - + return signature } catch (error) { handleError(error, '发送SOL失败') throw error } - } + }, [handleError, publicKey, sendSol, updateBalance]) - // 断开钱包连接 - const disconnectWallet = async () => { + const disconnectWallet = useCallback(async () => { try { - await disconnect() + await Promise.resolve(disconnectSolana()) setUser(null) setBalance(0) setIsWalletConnected(false) @@ -129,24 +132,18 @@ export const useWallet = () => { } catch (error) { handleError(error, '断开钱包连接失败') } - } + }, [disconnectSolana, handleError, handleSuccess, setIsWalletConnected, setUser]) return { - // 钱包状态 - wallet, publicKey, - connected, - connecting, + connected: isConnected, + connecting: false, balance, loading, - - // 钱包操作 disconnect: disconnectWallet, sendSOL, updateBalance, getBalance, - - // 用户信息 user, } -} \ No newline at end of file +} diff --git a/src/pages/SubmitEvidence.tsx b/src/pages/SubmitEvidence.tsx index e646f08..e08fe9c 100644 --- a/src/pages/SubmitEvidence.tsx +++ b/src/pages/SubmitEvidence.tsx @@ -2,7 +2,6 @@ import React, { useState, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { ArrowLeft, Upload, FileText, DollarSign, AlertCircle } from 'lucide-react'; import { Button } from '../components/ui/button'; -import { Input } from '../components/ui/input'; import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card'; import { Badge } from '../components/ui/badge'; import { useStore } from '../store/useStore'; From 93aa5a6f1bfcb7b819bf7e5017d70d70bfa752b0 Mon Sep 17 00:00:00 2001 From: Martin <60740992+aster2333@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:39:10 +0800 Subject: [PATCH 2/6] Fix wallet store update loop --- src/hooks/useWallet.ts | 58 ++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/hooks/useWallet.ts b/src/hooks/useWallet.ts index 35835d4..5ce7736 100644 --- a/src/hooks/useWallet.ts +++ b/src/hooks/useWallet.ts @@ -14,7 +14,11 @@ export const useWallet = () => { isConnected, publicKey: solanaPublicKey, } = useSolana() - const { user, setUser, setIsWalletConnected } = useStore() + const user = useStore((state) => state.user) + const setUser = useStore((state) => state.setUser) + const setIsWalletConnected = useStore( + (state) => state.setIsWalletConnected + ) const { handleError, handleSuccess } = useErrorHandler() const [balance, setBalance] = useState(() => typeof solanaBalance === 'number' ? solanaBalance : 0 @@ -73,28 +77,44 @@ export const useWallet = () => { }, [solanaBalance]) useEffect(() => { - setIsWalletConnected(isConnected) + const { isWalletConnected } = useStore.getState() + if (isWalletConnected !== isConnected) { + setIsWalletConnected(isConnected) + } + }, [isConnected, setIsWalletConnected]) - if (isConnected && publicKey) { - const userData = { - address: publicKey.toString(), - publicKey: publicKey.toString(), - totalEarnings: user?.totalEarnings || 0, - totalBets: user?.totalBets || 0, - challengesCreated: user?.challengesCreated || 0, - challengesAccepted: user?.challengesAccepted || 0, - winRate: user?.winRate || 0, - balance: balance, - nfts: user?.nfts || [] - } + useEffect(() => { + const currentUser = useStore.getState().user - setUser(userData) - updateBalance() - } else { - setUser(null) + if (!isConnected || !publicKey) { + if (currentUser !== null) { + setUser(null) + } setBalance(0) + return + } + + const address = publicKey.toString() + const nextBalance = balance + + if ( + !currentUser || + currentUser.publicKey !== address || + currentUser.balance !== nextBalance + ) { + setUser({ + address, + publicKey: address, + totalEarnings: currentUser?.totalEarnings ?? 0, + totalBets: currentUser?.totalBets ?? 0, + challengesCreated: currentUser?.challengesCreated ?? 0, + challengesAccepted: currentUser?.challengesAccepted ?? 0, + winRate: currentUser?.winRate ?? 0, + balance: nextBalance, + nfts: currentUser?.nfts ?? [], + }) } - }, [balance, isConnected, publicKey, setIsWalletConnected, setUser, updateBalance, user]) + }, [balance, isConnected, publicKey, setUser]) useEffect(() => { if (isConnected && publicKey) { From a04f679efe4cef12255213e44bbe5142b36ff4e7 Mon Sep 17 00:00:00 2001 From: Martin <60740992+aster2333@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:48:02 +0800 Subject: [PATCH 3/6] Handle legacy Solana wallets when sending transactions --- components/solana-provider.tsx | 141 +++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 25 deletions(-) diff --git a/components/solana-provider.tsx b/components/solana-provider.tsx index 8787402..a19f900 100644 --- a/components/solana-provider.tsx +++ b/components/solana-provider.tsx @@ -25,6 +25,13 @@ import { import { useStore } from "../src/store/useStore"; import type { User } from "../src/store/useStore"; +type LegacySignAndSendInput = + | Transaction + | Uint8Array + | { transaction: Transaction | Uint8Array; chain?: string }; + +type LegacySignAndSendResult = { signature: string } | string; + type LegacySolanaAdapter = { isPhantom?: boolean; isSolflare?: boolean; @@ -32,10 +39,9 @@ type LegacySolanaAdapter = { connect?: () => Promise<{ publicKey: { toString(): string } }>; disconnect?: () => Promise; publicKey?: { toString(): string }; - signAndSendTransaction?: (input: { - transaction: Uint8Array; - chain?: string; - }) => Promise<{ signature: string }>; + signAndSendTransaction?: ( + input: LegacySignAndSendInput + ) => Promise; }; type LegacyWalletWindow = Window & { @@ -51,7 +57,32 @@ type TraditionalWallet = { features: (string | typeof StandardConnect)[]; accounts: UiWalletAccount[]; isTraditional: boolean; - adapter: unknown; + adapter: LegacySolanaAdapter; +}; + +const isLegacyAdapter = (value: unknown): value is LegacySolanaAdapter => + typeof value === "object" && value !== null; + +const isTraditionalWallet = ( + wallet: UiWallet | TraditionalWallet +): wallet is TraditionalWallet => + Boolean(wallet && typeof wallet === "object" && "isTraditional" in wallet); + +const extractLegacySignature = (result: unknown) => { + if (!result) { + return null; + } + + if (typeof result === "string") { + return result; + } + + if (typeof result === "object" && "signature" in result) { + const signature = (result as { signature?: unknown }).signature; + return typeof signature === "string" ? signature : null; + } + + return null; }; type SignAndSendFeature = { @@ -77,7 +108,9 @@ const detectTraditionalWallets = () => { if (typeof window !== "undefined") { const legacyWindow = window as LegacyWalletWindow; - if (legacyWindow.phantom?.solana) { + const phantomAdapter = legacyWindow.phantom?.solana; + + if (phantomAdapter) { console.log("🔍 Phantom wallet detected via window.phantom"); traditionalWallets.push({ name: "Phantom", @@ -86,12 +119,14 @@ const detectTraditionalWallets = () => { features: [StandardConnect, "solana:signAndSendTransaction"], accounts: [], isTraditional: true, - adapter: legacyWindow.phantom.solana + adapter: phantomAdapter as LegacySolanaAdapter }); } // Check for Solflare - if (legacyWindow?.solflare?.isSolflare) { + const solflareAdapter = legacyWindow?.solflare; + + if (solflareAdapter?.isSolflare) { console.log("🔍 Solflare wallet detected via window.solflare"); traditionalWallets.push({ name: "Solflare", @@ -100,12 +135,14 @@ const detectTraditionalWallets = () => { features: [StandardConnect, "solana:signAndSendTransaction"], accounts: [], isTraditional: true, - adapter: legacyWindow.solflare + adapter: solflareAdapter as LegacySolanaAdapter }); } // Check for Backpack - if (legacyWindow?.backpack?.isSolana) { + const backpackAdapter = legacyWindow?.backpack; + + if (backpackAdapter?.isSolana) { console.log("🔍 Backpack wallet detected via window.backpack"); traditionalWallets.push({ name: "Backpack", @@ -114,7 +151,7 @@ const detectTraditionalWallets = () => { features: [StandardConnect, "solana:signAndSendTransaction"], accounts: [], isTraditional: true, - adapter: legacyWindow.backpack + adapter: backpackAdapter as LegacySolanaAdapter }); } @@ -249,12 +286,20 @@ export function SolanaProvider({ children }: { children: React.ReactNode }) { // Combine standard and traditional wallets const wallets = useMemo(() => { // First try strict filtering on standard wallets - const strictFiltered = allWallets.filter( - (wallet) => - wallet.chains?.some((c) => c.startsWith("solana:")) && - wallet.features.includes(StandardConnect) && - wallet.features.includes("solana:signAndSendTransaction") - ); + const strictFiltered = allWallets.filter((wallet) => { + const supportsSolana = wallet.chains?.some((c) => + c.startsWith("solana:") + ); + + const supportsConnect = wallet.features.includes(StandardConnect); + const supportsSignAndSend = wallet.features.some( + (feature) => + feature === "solana:signAndSendTransaction" || + isSignAndSendFeature(feature) + ); + + return supportsSolana && supportsConnect && supportsSignAndSend; + }); // If no wallets found with strict filtering, try relaxed filtering const relaxedFiltered = allWallets.filter( @@ -403,12 +448,14 @@ export function SolanaProvider({ children }: { children: React.ReactNode }) { throw new Error("Wallet not connected"); } + const maybeTraditional = selectedWallet as UiWallet | TraditionalWallet; const features = selectedWallet.features as unknown[]; const signAndSendFeature = features.find(isSignAndSendFeature); - - if (!signAndSendFeature) { - throw new Error("Wallet does not support sending transactions"); - } + const legacyAdapter = + isTraditionalWallet(maybeTraditional) && + isLegacyAdapter(maybeTraditional.adapter) + ? maybeTraditional.adapter + : null; const fromPubkey = new PublicKey(selectedAccount.address); const toPubkey = new PublicKey(destination); @@ -437,10 +484,54 @@ export function SolanaProvider({ children }: { children: React.ReactNode }) { verifySignatures: false }); - const { signature } = await signAndSendFeature.signAndSendTransaction({ - transaction: new Uint8Array(serialized), - chain - }); + let signature: string | null = null; + + if (signAndSendFeature) { + const response = await signAndSendFeature.signAndSendTransaction({ + transaction: new Uint8Array(serialized), + chain + }); + + signature = response.signature; + } else if (legacyAdapter?.signAndSendTransaction) { + const legacyInputs: LegacySignAndSendInput[] = [ + { transaction }, + transaction, + new Uint8Array(serialized) + ]; + + let lastError: unknown; + + for (const input of legacyInputs) { + try { + const result = await legacyAdapter.signAndSendTransaction(input); + signature = extractLegacySignature(result); + + if (signature) { + break; + } + } catch (error) { + lastError = error; + } + } + + if (!signature) { + if (lastError) { + console.error( + "Legacy wallet signAndSendTransaction failed", + lastError + ); + } + + throw new Error("Wallet does not support sending transactions"); + } + } else { + throw new Error("Wallet does not support sending transactions"); + } + + if (!signature) { + throw new Error("Failed to obtain transaction signature"); + } await connection.confirmTransaction( { From ecc0916031fb544797dd50ce0f66b42ce750c289 Mon Sep 17 00:00:00 2001 From: Martin <60740992+aster2333@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:41:02 +0800 Subject: [PATCH 4/6] Improve legacy Solana transaction fallbacks --- components/solana-provider.tsx | 135 +++++++++++++++++++++++++-------- 1 file changed, 102 insertions(+), 33 deletions(-) diff --git a/components/solana-provider.tsx b/components/solana-provider.tsx index a19f900..b607934 100644 --- a/components/solana-provider.tsx +++ b/components/solana-provider.tsx @@ -42,6 +42,10 @@ type LegacySolanaAdapter = { signAndSendTransaction?: ( input: LegacySignAndSendInput ) => Promise; + signTransaction?: (transaction: Transaction) => Promise; + sendTransaction?: ( + transaction: Transaction + ) => Promise; }; type LegacyWalletWindow = Window & { @@ -442,20 +446,49 @@ export function SolanaProvider({ children }: { children: React.ReactNode }) { [connection, refreshBalance, selectedAccount] ); + const findLegacyAdapter = useCallback( + (wallet: UiWallet | null) => { + if (!wallet) { + return null; + } + + const maybeTraditional = wallet as UiWallet | TraditionalWallet; + + if ( + isTraditionalWallet(maybeTraditional) && + isLegacyAdapter(maybeTraditional.adapter) + ) { + return maybeTraditional.adapter; + } + + const fallback = traditionalWallets.find( + (candidate) => candidate.name === wallet.name + ); + + if (fallback && isLegacyAdapter(fallback.adapter)) { + return fallback.adapter; + } + + return null; + }, + [traditionalWallets] + ); + + const ensureLegacyConnection = useCallback(async (adapter: LegacySolanaAdapter) => { + if (!adapter.publicKey) { + await adapter.connect?.(); + } + }, []); + const sendSol = useCallback( async (destination: string, amount: number) => { if (!selectedAccount || !selectedWallet) { throw new Error("Wallet not connected"); } - const maybeTraditional = selectedWallet as UiWallet | TraditionalWallet; const features = selectedWallet.features as unknown[]; const signAndSendFeature = features.find(isSignAndSendFeature); - const legacyAdapter = - isTraditionalWallet(maybeTraditional) && - isLegacyAdapter(maybeTraditional.adapter) - ? maybeTraditional.adapter - : null; + const legacyAdapter = findLegacyAdapter(selectedWallet); const fromPubkey = new PublicKey(selectedAccount.address); const toPubkey = new PublicKey(destination); @@ -479,50 +512,79 @@ export function SolanaProvider({ children }: { children: React.ReactNode }) { }) ); - const serialized = transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false - }); - let signature: string | null = null; if (signAndSendFeature) { + const serialized = transaction.serialize({ + requireAllSignatures: false, + verifySignatures: false + }); + const response = await signAndSendFeature.signAndSendTransaction({ transaction: new Uint8Array(serialized), chain }); signature = response.signature; - } else if (legacyAdapter?.signAndSendTransaction) { - const legacyInputs: LegacySignAndSendInput[] = [ - { transaction }, - transaction, - new Uint8Array(serialized) - ]; - - let lastError: unknown; - - for (const input of legacyInputs) { - try { - const result = await legacyAdapter.signAndSendTransaction(input); - signature = extractLegacySignature(result); - - if (signature) { - break; + } else if (legacyAdapter) { + await ensureLegacyConnection(legacyAdapter); + + if (legacyAdapter.signAndSendTransaction) { + const serialized = transaction.serialize({ + requireAllSignatures: false, + verifySignatures: false + }); + + const legacyInputs: LegacySignAndSendInput[] = [ + { transaction }, + transaction, + new Uint8Array(serialized) + ]; + + let lastError: unknown; + + for (const input of legacyInputs) { + try { + const result = await legacyAdapter.signAndSendTransaction(input); + signature = extractLegacySignature(result); + + if (signature) { + break; + } + } catch (error) { + lastError = error; } - } catch (error) { - lastError = error; } - } - if (!signature) { - if (lastError) { + if (!signature && lastError) { console.error( "Legacy wallet signAndSendTransaction failed", lastError ); } + } + if (!signature && legacyAdapter.signTransaction) { + const signedTransaction = await legacyAdapter.signTransaction( + transaction + ); + const rawTransaction = + signedTransaction && typeof signedTransaction.serialize === "function" + ? signedTransaction.serialize() + : transaction.serialize(); + signature = await connection.sendRawTransaction(rawTransaction); + } + + if ( + !signature && + legacyAdapter.sendTransaction && + typeof legacyAdapter.sendTransaction === "function" + ) { + const result = await legacyAdapter.sendTransaction(transaction); + signature = extractLegacySignature(result); + } + + if (!signature) { throw new Error("Wallet does not support sending transactions"); } } else { @@ -545,7 +607,14 @@ export function SolanaProvider({ children }: { children: React.ReactNode }) { return signature; }, - [connection, refreshBalance, selectedAccount, selectedWallet] + [ + connection, + ensureLegacyConnection, + findLegacyAdapter, + refreshBalance, + selectedAccount, + selectedWallet + ] ); // Create context value From 103cf69bf1ec0b55da1e7f7d8aaaa168c57e05c4 Mon Sep 17 00:00:00 2001 From: Martin <60740992+aster2333@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:29:14 +0800 Subject: [PATCH 5/6] Ensure Vercel keeps build configs --- .vercelignore | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.vercelignore b/.vercelignore index 008edb8..2bc1d41 100644 --- a/.vercelignore +++ b/.vercelignore @@ -87,4 +87,11 @@ tasks.md CHALLENGE_DETAIL_REDESIGN.md DEPLOYMENT_FIXES.md DEPLOYMENT_GUIDE.md -INTERNATIONALIZATION_FIXES.md \ No newline at end of file +INTERNATIONALIZATION_FIXES.md + +# Build configuration files required during deployment +!vite.config.ts +!tsconfig.json +!tailwind.config.js +!postcss.config.js +!components.json From ed903d48c3a8c915a7ecc335667b5b5ecb4a32f8 Mon Sep 17 00:00:00 2001 From: Martin <60740992+aster2333@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:34:09 +0800 Subject: [PATCH 6/6] README.md --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 365cd51..0ed8df6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,45 @@ -# 挑战市场 - Solana 区块链前端应用 +# ActionFi — Like Pumpfun for Real World UGC prediction market for action. Act, bet, vote and win. +### Problem +In Web3, belief and execution are disconnected: +• Prediction markets price opinions but not real actions. +• Task platforms reward completion but lack transparency and market dynamics. +There’s no native way to monetize credibility, accountability, or proof of action on-chain. +________________________________________ +### Solution +ActionFi creates a market for human execution. +Anyone can launch a public challenge, back outcomes with YES/NO staking, and let the market price belief in real-world actions. +Every challenge is a verifiable on-chain statement — success is rewarded, failure redistributes trust. +________________________________________ +### Mechanism +1. Launch Challenge — Creator deposits ≥0.05 SOL, sets time limit (1 h – 5 d, optional random stop). +2. Stake Belief — Users stake on YES / NO pools via constant-product AMM. +3. Host Bidding — Others may outbid the host (+0.1 SOL) to “take the stage.” +4. Resolution — Chain verifies proof (video / on-chain evidence). +o YES wins → supporters share NO pool. +o NO wins → skeptics share YES pool. +5. Rewards +o Creator earns 1 % of total pool if challenge succeeds. +o 5 % of pot → JP reward pool for top challengers. +o Host (if NO wins) gets 2 % of JP. +6. Second-Round Challenge — Winner may reopen next round with 10 % of prior profit → perpetual loop. +________________________________________ +### Economic Model +Layer Function Incentive +YES/NO Pools Belief pricing Early conviction = higher upside +JP Pool Action reward Execution proof bonus +Host Bidding Ownership game Adds liquidity + social tension +1 % Creator Fee Quality incentive Drives real, verifiable actions +Rolling Rounds Perpetual liquidity Continuous market cycle +Platform 1 % Sustainability Maintenance & auditing +________________________________________ +### Why It Matters +• Turns action into a financial primitive. +• Enables proof-of-credibility for individuals and DAOs. +• Blends DeFi, social identity, and prediction markets into one composable layer. +________________________________________ +### Tagline +ActionFi — Launch Challenges. Prove Yourself. Let the Market Decide. -这是一个基于 Solana 区块链的前端挑战市场应用,用户可以创建挑战、参与投注并获得奖励。 ## 🚀 技术栈