feat(web): add apps/web Next.js app src, tailwind, tsconfig and pages
This commit is contained in:
@@ -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);
|
||||
Reference in New Issue
Block a user