import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'; import { ApiPromise, WsProvider } from '@pezkuwi/api'; import { web3Accounts, web3Enable } from '@pezkuwi/extension-dapp'; import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types'; import { DEFAULT_ENDPOINT } from '../../../shared/blockchain/pezkuwi'; import { isMobileApp, getNativeWalletAddress, getNativeAccountName } from '@/lib/mobile-bridge'; // Teyrchain endpoints (from environment or defaults) const ASSET_HUB_ENDPOINT = import.meta.env.VITE_ASSET_HUB_ENDPOINT || 'wss://asset-hub-rpc.pezkuwichain.io'; const PEOPLE_CHAIN_ENDPOINT = import.meta.env.VITE_PEOPLE_CHAIN_ENDPOINT || 'wss://people-rpc.pezkuwichain.io'; interface PezkuwiContextType { api: ApiPromise | null; assetHubApi: ApiPromise | null; peopleApi: ApiPromise | null; isApiReady: boolean; isAssetHubReady: boolean; isPeopleReady: boolean; isConnected: boolean; accounts: InjectedAccountWithMeta[]; selectedAccount: InjectedAccountWithMeta | null; setSelectedAccount: (account: InjectedAccountWithMeta | null) => void; connectWallet: () => Promise; disconnectWallet: () => void; error: string | null; sudoKey: string | null; } const PezkuwiContext = createContext(undefined); interface PezkuwiProviderProps { children: ReactNode; endpoint?: string; } export const PezkuwiProvider: React.FC = ({ children, endpoint = DEFAULT_ENDPOINT // Beta testnet RPC from shared config }) => { const [api, setApi] = useState(null); const [assetHubApi, setAssetHubApi] = useState(null); const [peopleApi, setPeopleApi] = useState(null); const [isApiReady, setIsApiReady] = useState(false); const [isAssetHubReady, setIsAssetHubReady] = useState(false); const [isPeopleReady, setIsPeopleReady] = useState(false); const [accounts, setAccounts] = useState([]); const [selectedAccount, setSelectedAccount] = useState(null); const [error, setError] = useState(null); const [sudoKey, setSudoKey] = useState(null); // Wrapper to trigger events when wallet changes const handleSetSelectedAccount = (account: InjectedAccountWithMeta | null) => { setSelectedAccount(account); if (account) { localStorage.setItem('selectedWallet', account.address); if (import.meta.env.DEV) { if (import.meta.env.DEV) console.log('💾 Wallet saved:', account.address); } window.dispatchEvent(new Event('walletChanged')); } else { localStorage.removeItem('selectedWallet'); window.dispatchEvent(new Event('walletChanged')); } }; // Initialize Pezkuwi API with fallback endpoints useEffect(() => { // Hardcoded production fallbacks ensure app works even if env vars are missing const PRODUCTION_FALLBACKS = [ 'wss://rpc.pezkuwichain.io', 'wss://mainnet.pezkuwichain.io', ]; const FALLBACK_ENDPOINTS = [ endpoint, import.meta.env.VITE_WS_ENDPOINT_FALLBACK_1, import.meta.env.VITE_WS_ENDPOINT_FALLBACK_2, ...PRODUCTION_FALLBACKS, ].filter((ep, index, arr) => ep && arr.indexOf(ep) === index); // Remove duplicates and empty const initApi = async () => { let lastError: unknown = null; for (const currentEndpoint of FALLBACK_ENDPOINTS) { try { if (import.meta.env.DEV) { if (import.meta.env.DEV) console.log('🔗 Connecting to Pezkuwi node:', currentEndpoint); } const provider = new WsProvider(currentEndpoint); // PezkuwiChain custom signed extensions const apiInstance = await ApiPromise.create({ provider, signedExtensions: { AuthorizeCall: { extrinsic: {}, payload: {}, }, }, }); await apiInstance.isReady; setApi(apiInstance); setIsApiReady(true); setError(null); if (import.meta.env.DEV) { if (import.meta.env.DEV) console.log('✅ Connected to Pezkuwi node'); // Get chain info const [chain, nodeName, nodeVersion] = await Promise.all([ apiInstance.rpc.system.chain(), apiInstance.rpc.system.name(), apiInstance.rpc.system.version(), ]); if (import.meta.env.DEV) console.log(`📡 Chain: ${chain}`); if (import.meta.env.DEV) console.log(`🖥️ Node: ${nodeName} v${nodeVersion}`); // Debug: Check Junction type definition try { const junctionType = apiInstance.createType('XcmV3Junction'); const junctionObj = junctionType as unknown as { defKeys?: string[] }; console.log('🔍 XCM Junction type keys:', junctionObj.defKeys || Object.keys(junctionType.toJSON() || {})); // Expose api for console debugging (window as unknown as { __PEZKUWI_API__: typeof apiInstance }).__PEZKUWI_API__ = apiInstance; console.log('💡 API exposed as window.__PEZKUWI_API__ for debugging'); } catch (e) { console.log('⚠️ Could not check Junction type:', e); } } // Fetch sudo key from blockchain try { const sudoAccount = await apiInstance.query.sudo.key(); const sudoAddress = sudoAccount.toString(); setSudoKey(sudoAddress); if (import.meta.env.DEV) console.log(`🔑 Sudo key: ${sudoAddress}`); } catch (err) { if (import.meta.env.DEV) console.warn('⚠️ Failed to fetch sudo key (sudo pallet may not be available):', err); } return; } catch (err) { lastError = err; if (import.meta.env.DEV) { if (import.meta.env.DEV) console.warn(`⚠️ Failed to connect to ${currentEndpoint}, trying next...`); } continue; } } if (import.meta.env.DEV) { if (import.meta.env.DEV) console.error('❌ Failed to connect to all endpoints:', lastError); } setError('Failed to connect to blockchain network. Please try again later.'); setIsApiReady(false); }; // Initialize Asset Hub API for PEZ token const initAssetHubApi = async () => { try { if (import.meta.env.DEV) { console.log('🔗 Connecting to Asset Hub:', ASSET_HUB_ENDPOINT); } const provider = new WsProvider(ASSET_HUB_ENDPOINT); const assetHubApiInstance = await ApiPromise.create({ provider, signedExtensions: { AuthorizeCall: { extrinsic: {}, payload: {}, }, }, }); await assetHubApiInstance.isReady; setAssetHubApi(assetHubApiInstance); setIsAssetHubReady(true); if (import.meta.env.DEV) { console.log('✅ Connected to Asset Hub for PEZ token'); } } catch (err) { if (import.meta.env.DEV) { console.error('❌ Failed to connect to Asset Hub:', err); } // Don't set error - PEZ features just won't work } }; // Initialize People Chain API for identity/citizenship const initPeopleApi = async () => { try { if (import.meta.env.DEV) { console.log('🔗 Connecting to People Chain:', PEOPLE_CHAIN_ENDPOINT); } const provider = new WsProvider(PEOPLE_CHAIN_ENDPOINT); const peopleApiInstance = await ApiPromise.create({ provider, signedExtensions: { AuthorizeCall: { extrinsic: {}, payload: {}, }, }, }); await peopleApiInstance.isReady; setPeopleApi(peopleApiInstance); setIsPeopleReady(true); if (import.meta.env.DEV) { console.log('✅ Connected to People Chain for identity'); } } catch (err) { if (import.meta.env.DEV) { console.error('❌ Failed to connect to People Chain:', err); } // Don't set error - Identity features just won't work } }; initApi(); initAssetHubApi(); initPeopleApi(); return () => { if (api) { api.disconnect(); } if (assetHubApi) { assetHubApi.disconnect(); } if (peopleApi) { peopleApi.disconnect(); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [endpoint]); // Auto-restore wallet on page load (or setup mobile wallet) useEffect(() => { const restoreWallet = async () => { // Check if running in mobile app if (isMobileApp()) { const nativeAddress = getNativeWalletAddress(); const nativeAccountName = getNativeAccountName(); if (nativeAddress) { // Create a virtual account for the mobile wallet const mobileAccount: InjectedAccountWithMeta = { address: nativeAddress, meta: { name: nativeAccountName || 'Mobile Wallet', source: 'pezkuwi-mobile', }, type: 'sr25519', }; setAccounts([mobileAccount]); handleSetSelectedAccount(mobileAccount); if (import.meta.env.DEV) { console.log('[Mobile] Native wallet connected:', nativeAddress.slice(0, 8) + '...'); } return; } } // Desktop: Try to restore from localStorage const savedAddress = localStorage.getItem('selectedWallet'); if (!savedAddress) return; try { // Enable extension const extensions = await web3Enable('PezkuwiChain'); if (extensions.length === 0) return; // Get accounts const allAccounts = await web3Accounts(); if (allAccounts.length === 0) return; // Find saved account const savedAccount = allAccounts.find(acc => acc.address === savedAddress); if (savedAccount) { setAccounts(allAccounts); handleSetSelectedAccount(savedAccount); if (import.meta.env.DEV) { console.log('✅ Wallet restored:', savedAddress.slice(0, 8) + '...'); } } } catch (err) { if (import.meta.env.DEV) { console.error('Failed to restore wallet:', err); } } }; restoreWallet(); // Listen for native bridge ready event (mobile) const handleNativeReady = () => { if (import.meta.env.DEV) { console.log('[Mobile] Native bridge ready, restoring wallet'); } restoreWallet(); }; window.addEventListener('pezkuwi-native-ready', handleNativeReady); return () => { window.removeEventListener('pezkuwi-native-ready', handleNativeReady); }; }, []); // Connect wallet (Pezkuwi.js extension or native mobile) const connectWallet = async () => { try { setError(null); // Check if running in mobile app if (isMobileApp()) { const nativeAddress = getNativeWalletAddress(); const nativeAccountName = getNativeAccountName(); if (nativeAddress) { // Create a virtual account for the mobile wallet const mobileAccount: InjectedAccountWithMeta = { address: nativeAddress, meta: { name: nativeAccountName || 'Mobile Wallet', source: 'pezkuwi-mobile', }, type: 'sr25519', }; setAccounts([mobileAccount]); handleSetSelectedAccount(mobileAccount); if (import.meta.env.DEV) { console.log('[Mobile] Native wallet connected:', nativeAddress.slice(0, 8) + '...'); } return; } else { // Request wallet connection from native app setError('Please connect your wallet in the app'); return; } } // Desktop: Check if extension is installed first const hasExtension = !!(window as unknown as { injectedWeb3?: Record }).injectedWeb3; // Enable extension const extensions = await web3Enable('PezkuwiChain'); if (extensions.length === 0) { if (hasExtension) { // Extension is installed but user didn't authorize - don't redirect setError('Please authorize the connection in your Pezkuwi Wallet extension'); } else { // Extension not installed - show install link setError('Pezkuwi Wallet extension not found. Please install from Chrome Web Store.'); window.open('https://chrome.google.com/webstore/detail/pezkuwi-wallet/fbnboicjjeebjhgnapneaeccpgjcdibn', '_blank'); } return; } if (import.meta.env.DEV) { console.log('✅ Pezkuwi.js extension enabled'); } // Get accounts const allAccounts = await web3Accounts(); if (allAccounts.length === 0) { setError('No accounts found. Please create an account in Pezkuwi.js extension'); return; } setAccounts(allAccounts); // Try to restore previously selected account, otherwise use first const savedAddress = localStorage.getItem('selectedWallet'); const accountToSelect = savedAddress ? allAccounts.find(acc => acc.address === savedAddress) || allAccounts[0] : allAccounts[0]; // Use wrapper to trigger events handleSetSelectedAccount(accountToSelect); if (import.meta.env.DEV) { console.log(`✅ Found ${allAccounts.length} account(s)`); } } catch (err) { if (import.meta.env.DEV) { console.error('❌ Wallet connection failed:', err); } setError('Failed to connect wallet'); } }; // Disconnect wallet const disconnectWallet = () => { setAccounts([]); handleSetSelectedAccount(null); if (import.meta.env.DEV) { if (import.meta.env.DEV) console.log('🔌 Wallet disconnected'); } }; const value: PezkuwiContextType = { api, assetHubApi, peopleApi, isApiReady, isAssetHubReady, isPeopleReady, isConnected: isApiReady, // Alias for backward compatibility accounts, selectedAccount, setSelectedAccount: handleSetSelectedAccount, connectWallet, disconnectWallet, error, sudoKey, }; return ( {children} ); }; // Hook to use Pezkuwi context export const usePezkuwi = (): PezkuwiContextType => { const context = useContext(PezkuwiContext); if (!context) { throw new Error('usePezkuwi must be used within PezkuwiProvider'); } return context; };