import React, { createContext, useContext, useState, useEffect, useMemo } from 'react'; import { ethers, Contract } from 'ethers'; import type { SoulStreamContextType, SoulStreamProviderProps, SoulStreamConfig, ReputationData, RewardsData, ChainConfig } from '../types'; // Create context with null as initial value, but with type const SoulStreamContext = createContext(null); /** * Custom hook to use the SoulStream context * @returns {SoulStreamContextType} SoulStream context values */ export const useSoulStream = (): SoulStreamContextType => { const context = useContext(SoulStreamContext); if (!context) { throw new Error('useSoulStream must be used within a SoulStreamProvider'); } return context; }; /** * SoulStream Provider Component */ export const SoulStreamProvider: React.FC = ({ children, config = { rpcUrl: process.env.NEXT_PUBLIC_RPC_URL || 'https://rpc.example.com', contractAddress: process.env.NEXT_PUBLIC_SOULSTREAM_CONTRACT || '0x0000000000000000000000000000000000000000', contractABI: [], // Default empty ABI chainA: { chainId: process.env.NEXT_PUBLIC_CHAIN_A_ID || '31337', rpcUrl: process.env.NEXT_PUBLIC_CHAIN_A_RPC_URL || 'http://localhost:8545', registryAddress: process.env.NEXT_PUBLIC_CHAIN_A_REGISTRY_ADDRESS || '0x0000000000000000000000000000000000000000', bridgeAdapterAddress: process.env.NEXT_PUBLIC_CHAIN_A_BRIDGE_ADAPTER_ADDRESS || '0x0000000000000000000000000000000000000000' }, chainB: { chainId: process.env.NEXT_PUBLIC_CHAIN_B_ID || '31338', rpcUrl: process.env.NEXT_PUBLIC_CHAIN_B_RPC_URL || 'http://localhost:8546', registryAddress: process.env.NEXT_PUBLIC_CHAIN_B_REGISTRY_ADDRESS || '0x0000000000000000000000000000000000000000', bridgeAdapterAddress: process.env.NEXT_PUBLIC_CHAIN_B_BRIDGE_ADAPTER_ADDRESS || '0x0000000000000000000000000000000000000000' }, privateKey: process.env.PRIVATE_KEY } }) => { const [provider, setProvider] = useState(null); const [contract, setContract] = useState(null); const [isInitialized, setIsInitialized] = useState(false); const [error, setError] = useState(null); const [currentChainId, setCurrentChainId] = useState(null); // Store chain configurations const chains = useMemo(() => ({ chainA: config.chainA || null, chainB: config.chainB || null }), [config.chainA, config.chainB]); // Initialize the provider and contract useEffect(() => { const init = async () => { try { // Start with Chain A by default const defaultChain = config.chainA; if (!defaultChain) { throw new Error('No chain configuration provided'); } // Create provider for the default chain const ethersProvider = new ethers.JsonRpcProvider(defaultChain.rpcUrl); setProvider(ethersProvider); setCurrentChainId(defaultChain.chainId); // Create contract instance using the registry address const contractInstance = new ethers.Contract( defaultChain.registryAddress, config.contractABI, ethersProvider ); setContract(contractInstance); setIsInitialized(true); setError(null); } catch (err) { console.error('Failed to initialize SoulStream:', err); setError('Failed to initialize SoulStream'); setIsInitialized(false); } }; init(); }, [config]); /** * Switch between different chains * @param {string} chainId - The chain ID to switch to * @returns {Promise} - Success status */ const switchChain = async (chainId: string): Promise => { try { const targetChain = chains.chainA?.chainId === chainId ? chains.chainA : chains.chainB?.chainId === chainId ? chains.chainB : null; if (!targetChain) { throw new Error(`Chain with ID ${chainId} not configured`); } // Create a new provider for the target chain const ethersProvider = new ethers.JsonRpcProvider(targetChain.rpcUrl); setProvider(ethersProvider); // Create a new contract instance const contractInstance = new ethers.Contract( targetChain.registryAddress, config.contractABI, ethersProvider ); setContract(contractInstance); setCurrentChainId(chainId); return true; } catch (err) { console.error('Failed to switch chain:', err); return false; } }; /** * Get reputation score for a wallet address * @param {string} walletAddress - Ethereum wallet address * @returns {Promise} - Reputation data */ const getReputationScore = async (walletAddress: string): Promise => { if (!isInitialized || !contract) { throw new Error('SoulStream not initialized'); } try { // This is where you would call your contract method // For example: const score = await contract.getReputationScore(walletAddress); // Mock data for now return { score: 85, trust: 90, activity: 80, engagement: 85 }; } catch (err) { console.error('Failed to get reputation score:', err); throw new Error('Failed to get reputation score'); } }; /** * Get rewards for a wallet address * @param {string} walletAddress - Ethereum wallet address * @returns {Promise} - Rewards data */ const getRewards = async (walletAddress: string): Promise => { if (!isInitialized || !contract) { throw new Error('SoulStream not initialized'); } try { // This is where you would call your contract method // For example: const rewards = await contract.getRewards(walletAddress); // Mock data for now return { unclaimed: [ { id: '1', name: 'Community Contribution', amount: '100 SOUL' }, { id: '2', name: 'Active Participation', amount: '50 SOUL' } ], claimed: [ { id: '3', name: 'Early Adopter', amount: '200 SOUL', claimedAt: Date.now() - 86400000 // 1 day ago } ] }; } catch (err) { console.error('Failed to get rewards:', err); throw new Error('Failed to get rewards'); } }; /** * Claim a reward * @param {string} walletAddress - Ethereum wallet address * @param {string} rewardId - ID of the reward to claim * @returns {Promise} - Success status */ const claimReward = async (walletAddress: string, rewardId: string): Promise => { if (!isInitialized || !contract) { throw new Error('SoulStream not initialized'); } try { // This is where you would call your contract method // For example: await contract.claimReward(rewardId, { from: walletAddress }); // Mock success for now return true; } catch (err) { console.error('Failed to claim reward:', err); throw new Error('Failed to claim reward'); } }; // Create the context value const contextValue = useMemo(() => ({ isInitialized, error, getReputationScore, getRewards, claimReward, chains, switchChain, currentChainId }), [isInitialized, error, chains, currentChainId]); return ( {children} ); }; // Export the context export default SoulStreamContext;