feat(vault): add user vault factory, non-custodial deposit/withdraw and session key UI

This commit is contained in:
Bot
2026-08-31 01:41:57 +08:00
parent 7e554402b2
commit 4890329f99
6 changed files with 607 additions and 9 deletions
+71 -1
View File
@@ -9,9 +9,14 @@ interface Web3ContextType {
chainId: number | null;
isCorrectNetwork: boolean;
wusdBalance: string;
vaultAddress: string | null;
isSessionActive: boolean;
connectWallet: () => Promise<void>;
switchNetwork: () => Promise<void>;
refreshBalance: () => Promise<void>;
depositToVault: (amount: string) => Promise<string>;
withdrawFromVault: (amount: string) => Promise<string>;
enableSessionKey: () => Promise<void>;
getMarketContract: (address: string) => ethers.Contract | null;
getCollateralContract: () => ethers.Contract | null;
}
@@ -23,9 +28,14 @@ const Web3Context = createContext<Web3ContextType>({
chainId: null,
isCorrectNetwork: false,
wusdBalance: '0',
vaultAddress: null,
isSessionActive: false,
connectWallet: async () => {},
switchNetwork: async () => {},
refreshBalance: async () => {},
depositToVault: async () => '',
withdrawFromVault: async () => '',
enableSessionKey: async () => {},
getMarketContract: () => null,
getCollateralContract: () => null,
});
@@ -36,9 +46,23 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children
const [signer, setSigner] = useState<ethers.Signer | null>(null);
const [chainId, setChainId] = useState<number | null>(null);
const [wusdBalance, setWusdBalance] = useState<string>('0');
const [vaultAddress, setVaultAddress] = useState<string | null>(null);
const [isSessionActive, setIsSessionActive] = useState<boolean>(false);
const isCorrectNetwork = chainId === ROBINHOOD_TESTNET_CHAIN.id;
const predictUserVault = async (userAddr: string, prov: ethers.BrowserProvider) => {
try {
const factoryAddr = DEPLOYMENTS.robinhoodTestnet.contracts.vaultFactory;
if (!factoryAddr) return;
const factoryContract = new ethers.Contract(factoryAddr, ABIS.WTFVaultFactory, prov);
const predicted = await factoryContract.predictVaultAddress(userAddr);
setVaultAddress(predicted);
} catch (e) {
console.error('Failed to predict vault address:', e);
}
};
const refreshBalance = async () => {
if (!account || !provider) return;
try {
@@ -73,10 +97,13 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children
(window as any).ethereum.on('accountsChanged', async (accs: string[]) => {
if (accs.length > 0) {
setAccount(accs[0]);
setSigner(await browserProvider.getSigner());
const currentSigner = await browserProvider.getSigner();
setSigner(currentSigner);
predictUserVault(accs[0], browserProvider);
} else {
setAccount(null);
setSigner(null);
setVaultAddress(null);
}
});
@@ -157,6 +184,44 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children
);
};
const depositToVault = async (amountStr: string): Promise<string> => {
if (!signer || !account || !vaultAddress) throw new Error('Wallet/Vault not ready');
const collateral = new ethers.Contract(
DEPLOYMENTS.robinhoodTestnet.contracts.collateral,
ABIS.MockERC20,
signer
);
const amountWei = ethers.parseUnits(amountStr, 18);
const tx = await collateral.transfer(vaultAddress, amountWei);
await tx.wait();
refreshBalance();
return tx.hash;
};
const withdrawFromVault = async (amountStr: string): Promise<string> => {
if (!signer || !account || !vaultAddress) throw new Error('Wallet/Vault not ready');
const vault = new ethers.Contract(vaultAddress, ABIS.WTFUserVault, signer);
const amountWei = ethers.parseUnits(amountStr, 18);
const tx = await vault.withdraw(
DEPLOYMENTS.robinhoodTestnet.contracts.collateral,
account,
amountWei
);
await tx.wait();
refreshBalance();
return tx.hash;
};
const enableSessionKey = async () => {
if (!signer || !account || !vaultAddress) throw new Error('Wallet/Vault not ready');
const vault = new ethers.Contract(vaultAddress, ABIS.WTFUserVault, signer);
const relayerAdmin = DEPLOYMENTS.robinhoodTestnet.governance.admin;
const duration = 24 * 3600; // 24 hours
const tx = await vault.authorizeSession(relayerAdmin, duration);
await tx.wait();
setIsSessionActive(true);
};
return (
<Web3Context.Provider
value={{
@@ -166,9 +231,14 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children
chainId,
isCorrectNetwork,
wusdBalance,
vaultAddress,
isSessionActive,
connectWallet,
switchNetwork,
refreshBalance,
depositToVault,
withdrawFromVault,
enableSessionKey,
getMarketContract,
getCollateralContract,
}}