feat(web): add apps/web Next.js app src, tailwind, tsconfig and pages

This commit is contained in:
Bot
2026-08-31 01:21:32 +08:00
parent acc5274a1e
commit 7e554402b2
14 changed files with 1163 additions and 2 deletions
+181
View File
@@ -0,0 +1,181 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import { ethers } from 'ethers';
import { DEPLOYMENTS, ABIS, ROBINHOOD_TESTNET_CHAIN } from '@wtfx/contracts';
interface Web3ContextType {
account: string | null;
provider: ethers.BrowserProvider | null;
signer: ethers.Signer | null;
chainId: number | null;
isCorrectNetwork: boolean;
wusdBalance: string;
connectWallet: () => Promise<void>;
switchNetwork: () => Promise<void>;
refreshBalance: () => Promise<void>;
getMarketContract: (address: string) => ethers.Contract | null;
getCollateralContract: () => ethers.Contract | null;
}
const Web3Context = createContext<Web3ContextType>({
account: null,
provider: null,
signer: null,
chainId: null,
isCorrectNetwork: false,
wusdBalance: '0',
connectWallet: async () => {},
switchNetwork: async () => {},
refreshBalance: async () => {},
getMarketContract: () => null,
getCollateralContract: () => null,
});
export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [account, setAccount] = useState<string | null>(null);
const [provider, setProvider] = useState<ethers.BrowserProvider | null>(null);
const [signer, setSigner] = useState<ethers.Signer | null>(null);
const [chainId, setChainId] = useState<number | null>(null);
const [wusdBalance, setWusdBalance] = useState<string>('0');
const isCorrectNetwork = chainId === ROBINHOOD_TESTNET_CHAIN.id;
const refreshBalance = async () => {
if (!account || !provider) return;
try {
const collateral = new ethers.Contract(
DEPLOYMENTS.robinhoodTestnet.contracts.collateral,
ABIS.MockERC20,
provider
);
const bal = await collateral.balanceOf(account);
setWusdBalance(ethers.formatUnits(bal, 18));
} catch (err) {
console.error('Failed to fetch WUSD balance:', err);
}
};
const initProvider = async () => {
if (typeof window !== 'undefined' && (window as any).ethereum) {
const browserProvider = new ethers.BrowserProvider((window as any).ethereum);
setProvider(browserProvider);
try {
const network = await browserProvider.getNetwork();
setChainId(Number(network.chainId));
const accounts = await browserProvider.listAccounts();
if (accounts.length > 0) {
setAccount(accounts[0].address);
setSigner(await browserProvider.getSigner());
}
} catch (err) {
console.error('Init web3 error:', err);
}
(window as any).ethereum.on('accountsChanged', async (accs: string[]) => {
if (accs.length > 0) {
setAccount(accs[0]);
setSigner(await browserProvider.getSigner());
} else {
setAccount(null);
setSigner(null);
}
});
(window as any).ethereum.on('chainChanged', (cId: string) => {
setChainId(parseInt(cId, 16));
window.location.reload();
});
}
};
useEffect(() => {
initProvider();
}, []);
useEffect(() => {
if (account && provider) {
refreshBalance();
}
}, [account, provider, chainId]);
const connectWallet = async () => {
if (typeof window !== 'undefined' && (window as any).ethereum) {
try {
const browserProvider = new ethers.BrowserProvider((window as any).ethereum);
await browserProvider.send('eth_requestAccounts', []);
const currentSigner = await browserProvider.getSigner();
const address = await currentSigner.getAddress();
const network = await browserProvider.getNetwork();
setProvider(browserProvider);
setSigner(currentSigner);
setAccount(address);
setChainId(Number(network.chainId));
} catch (err) {
console.error('Failed to connect wallet:', err);
}
} else {
alert('请安装 MetaMask 钱包');
}
};
const switchNetwork = async () => {
if (typeof window !== 'undefined' && (window as any).ethereum) {
const hexChainId = '0x' + ROBINHOOD_TESTNET_CHAIN.id.toString(16);
try {
await (window as any).ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: hexChainId }],
});
} catch (switchError: any) {
if (switchError.code === 4902) {
await (window as any).ethereum.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: hexChainId,
chainName: ROBINHOOD_TESTNET_CHAIN.name,
rpcUrls: ROBINHOOD_TESTNET_CHAIN.rpcUrls.default.http,
nativeCurrency: ROBINHOOD_TESTNET_CHAIN.nativeCurrency,
},
],
});
}
}
}
};
const getMarketContract = (address: string) => {
if (!provider) return null;
return new ethers.Contract(address, ABIS.WTFMarketV2, signer || provider);
};
const getCollateralContract = () => {
if (!provider) return null;
return new ethers.Contract(
DEPLOYMENTS.robinhoodTestnet.contracts.collateral,
ABIS.MockERC20,
signer || provider
);
};
return (
<Web3Context.Provider
value={{
account,
provider,
signer,
chainId,
isCorrectNetwork,
wusdBalance,
connectWallet,
switchNetwork,
refreshBalance,
getMarketContract,
getCollateralContract,
}}
>
{children}
</Web3Context.Provider>
);
};
export const useWeb3 = () => useContext(Web3Context);