From 8895d52cef913a7b9c01101eba4c3a8b78603f09 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 Nov 2024 12:42:53 -0800 Subject: [PATCH] Add support for alternative token out vaults like cmeth --- src/contexts/v1/BoringVaultContextV1.tsx | 37 ++- src/examples/v3.tsx | 2 +- src/examples/v4.html | 13 + src/examples/v4.tsx | 289 +++++++++++++++++++++++ webpack.config.js | 44 +++- 5 files changed, 372 insertions(+), 13 deletions(-) create mode 100644 src/examples/v4.html create mode 100644 src/examples/v4.tsx diff --git a/src/contexts/v1/BoringVaultContextV1.tsx b/src/contexts/v1/BoringVaultContextV1.tsx index 1210ef0..12e57d2 100644 --- a/src/contexts/v1/BoringVaultContextV1.tsx +++ b/src/contexts/v1/BoringVaultContextV1.tsx @@ -38,6 +38,7 @@ const SEVEN_SEAS_BASE_API_URL = "https://api.sevenseas.capital"; interface BoringVaultV1ContextProps { chain: string; vaultEthersContract: Contract | null; + outputTokenEthersContract: Contract | null; tellerEthersContract: Contract | null; accountantEthersContract: Contract | null; lensEthersContract: Contract | null; @@ -123,6 +124,7 @@ const BoringVaultV1Context = createContext( export const BoringVaultV1Provider: React.FC<{ chain: string; + outputTokenContract?: string; vaultContract: string; tellerContract: string; accountantContract: string; @@ -139,6 +141,7 @@ export const BoringVaultV1Provider: React.FC<{ }> = ({ children, chain, + outputTokenContract, depositTokens, withdrawTokens, vaultContract, @@ -168,6 +171,8 @@ export const BoringVaultV1Provider: React.FC<{ useState(null); const [boringQueueEthersContract, setBoringQueueEthersContract] = useState(null); + const [outputTokenEthersContract, setOutputTokenEthersContract] = + useState(null); const [baseToken, setBaseToken] = useState(null); @@ -249,6 +254,15 @@ export const BoringVaultV1Provider: React.FC<{ setBoringQueueEthersContract(boringQueueEthersContract); } + if (outputTokenContract) { + const outputTokenEthersContract = new Contract( + outputTokenContract, + erc20Abi, + ethersProvider + ); + setOutputTokenEthersContract(outputTokenEthersContract); + } + setVaultEthersContract(vaultEthersContract); setTellerContract(tellerEthersContract); setAccountantEthersContract(accountantEthersContract); @@ -284,6 +298,7 @@ export const BoringVaultV1Provider: React.FC<{ depositTokens, withdrawTokens, delayWithdrawContract, + outputTokenContract, ]); // Effect to handle updates on acceptedTokens if needed @@ -313,7 +328,9 @@ export const BoringVaultV1Provider: React.FC<{ try { const assets = await lensEthersContract.totalAssets( - vaultContract, + outputTokenEthersContract + ? outputTokenEthersContract + : vaultEthersContract, accountantContract ); console.log("Total assets from contract: ", assets); @@ -353,7 +370,7 @@ export const BoringVaultV1Provider: React.FC<{ try { const balance = await lensEthersContract.balanceOf( userAddress, - vaultContract + outputTokenContract ? outputTokenContract : vaultContract ); console.log("User balance from contract: ", balance); return Number(balance) / Math.pow(10, decimals!); @@ -613,7 +630,7 @@ export const BoringVaultV1Provider: React.FC<{ try { // First check if the delay withdraw is approved for at least the amount const vaultContractWithSigner = new Contract( - vaultContract, + outputTokenContract ? outputTokenContract : vaultContract, BoringVaultABI, signer ); @@ -1031,7 +1048,7 @@ export const BoringVaultV1Provider: React.FC<{ try { // First check if the delay withdraw is approved for at least the amount const vaultContractWithSigner = new Contract( - vaultContract, + outputTokenContract ? outputTokenContract : vaultContract, BoringVaultABI, signer ); @@ -1109,7 +1126,7 @@ export const BoringVaultV1Provider: React.FC<{ const queueTx = await withdrawQueueContractWithSigner.safeUpdateAtomicRequest( - vaultContract, // offer + outputTokenContract ? outputTokenContract : vaultContract, // offer token.address, // want [ deadline.toFixed(0), // Deadline @@ -1208,7 +1225,7 @@ export const BoringVaultV1Provider: React.FC<{ // Update request with same token, but 0 amount const cancelTx = await withdrawQueueContractWithSigner.updateAtomicRequest( - vaultContract, // Offer + outputTokenContract ? outputTokenContract : vaultContract, // Offer token.address, // Want [ 0, // Deadline @@ -1391,7 +1408,7 @@ export const BoringVaultV1Provider: React.FC<{ try { // First check if the delay withdraw is approved for at least the amount const vaultContractWithSigner = new Contract( - vaultContract, + outputTokenContract ? outputTokenContract : vaultContract, BoringVaultABI, signer ); @@ -1452,7 +1469,7 @@ export const BoringVaultV1Provider: React.FC<{ name: name, version: '1', chainId: chainId, - verifyingContract: vaultContract + verifyingContract: outputTokenContract ? outputTokenContract : vaultContract }; const types = { @@ -1557,7 +1574,8 @@ export const BoringVaultV1Provider: React.FC<{ isBoringV1ContextReady, accountantContract, boringQueueContract, - vaultContract + vaultContract, + outputTokenContract, ] ); @@ -1775,6 +1793,7 @@ export const BoringVaultV1Provider: React.FC<{ value={{ chain, vaultEthersContract, + outputTokenEthersContract, tellerEthersContract, accountantEthersContract, lensEthersContract, diff --git a/src/examples/v3.tsx b/src/examples/v3.tsx index cd4fc10..c559d09 100644 --- a/src/examples/v3.tsx +++ b/src/examples/v3.tsx @@ -197,7 +197,7 @@ const VaultWidget = () => { Once you request a withdraw a solver will need to process your request. This can take some time depending on the current queue length and the gas price you are willing to pay. You can check the status of your withdraw request below. " buttonText="Withdraw" - popupText="Welcome to the delay withdraw interface!" + popupText="Welcome to the withdraw interface!" buttonProps={{ colorScheme: "teal", size: "lg", diff --git a/src/examples/v4.html b/src/examples/v4.html new file mode 100644 index 0000000..516364c --- /dev/null +++ b/src/examples/v4.html @@ -0,0 +1,13 @@ + + + + + + + Example Application + + +
+ + + \ No newline at end of file diff --git a/src/examples/v4.tsx b/src/examples/v4.tsx new file mode 100644 index 0000000..5874601 --- /dev/null +++ b/src/examples/v4.tsx @@ -0,0 +1,289 @@ +// src/examples/v2.tsx +import React, { useEffect } from "react"; +import { + ChakraProvider, + extendTheme, + Box, + VStack, + HStack, + Text, +} from "@chakra-ui/react"; +import DepositButton from "../components/v1/DepositButton"; +import PendingDelayedWithdraws from "../components/v1/PendingDelayedWithdraws"; +import DelayWithdrawButton from "../components/v1/DelayWithdrawButton"; +import { createRoot } from "react-dom/client"; +import { + BoringVaultV1Provider, + useBoringVaultV1, +} from "../contexts/v1/BoringVaultContextV1"; +import { WagmiProvider, createConfig, http } from "wagmi"; +import { mainnet } from "wagmi/chains"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + ConnectKitButton, + ConnectKitProvider, + getDefaultConfig, +} from "connectkit"; +import { ethers } from "ethers"; +import { useEthersSigner } from "../hooks/ethers"; + +const config = createConfig( + getDefaultConfig({ + // Your dApps chains + chains: [mainnet], + transports: { + // RPC URL for each chain + [mainnet.id]: http( + `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}` + ), + }, + + // Required API Keys + // ! https://cloud.walletconnect.com/sign-in + walletConnectProjectId: process.env.WALLETCONNECT_PROJECT_ID!, + + // Required App Info + appName: "Boring Vault Alt Token Example App with Direct Withdraws", + + // Optional App Info + appDescription: "An example app for the Boring Vault V1", + appUrl: "http://localhost:9000", // your app's url + }) +); +const ethersInfuraProvider = new ethers.InfuraProvider( + "mainnet", + process.env.INFURA_API_KEY +); + +const queryClient = new QueryClient(); + +// Customize the theme to fit your branding or design needs +const theme = extendTheme({ + colors: { + brand: { + 100: "#f7fafc", + // ... (provide your brand colors) + }, + }, + components: { + Modal: { + baseStyle: (props: any) => ({ + dialog: { + bg: "brand.100", + }, + }), + }, + }, +}); + +const VaultWidget = () => { + const [assets, setAssets] = React.useState(0); + const { + fetchTotalAssets, + isBoringV1ContextReady, + fetchUserShares, + fetchShareValue, + fetchUserUnlockTime, + } = useBoringVaultV1(); + const signer = useEthersSigner(); + + useEffect(() => { + console.warn("ready: ", isBoringV1ContextReady); + if (!isBoringV1ContextReady) return; + fetchTotalAssets().then((assets) => { + console.log("Total assets: ", assets); + setAssets(assets); + }); + }, [isBoringV1ContextReady]); + + const [userShares, setUserShares] = React.useState(0); + useEffect(() => { + if (!isBoringV1ContextReady || !signer) return; + const fetchShares = async () => { + const address = await signer.getAddress(); + fetchUserShares(address) + .then((shares) => { + console.log("User shares: ", shares); + setUserShares(shares); + }) + .catch((error) => console.error("Failed to fetch user shares:", error)); + }; + + fetchShares(); + }, [isBoringV1ContextReady, signer]); + + const [shareValue, setShareValue] = React.useState(0); + useEffect(() => { + if (!isBoringV1ContextReady) return; + fetchShareValue().then((value) => { + console.log("Share value: ", value); + setShareValue(value); + }); + }, [isBoringV1ContextReady]); + + const [userUnlockTime, setUserUnlockTime] = React.useState(0); + useEffect(() => { + if (!isBoringV1ContextReady || !signer) return; + + const fetchUnlockTime = async () => { + const address = await signer.getAddress(); + fetchUserUnlockTime(address) + .then((unlockTime) => { + console.log("User unlock time: ", unlockTime); + setUserUnlockTime(unlockTime); + }) + .catch((error) => + console.error("Failed to fetch user unlock time:", error) + ); + }; + + fetchUnlockTime(); + }, [isBoringV1ContextReady, signer]); + + return ( + <> + + + + Boring Vault Example + + {`TVL (ETH): ${assets}`} + {`Share (1 unit) Value (ETH): ${shareValue}`} + {`User Share Balance: ${userShares}`} + {`User Share Unlock Unix seconds timestamp: ${userUnlockTime}`} + + + + + + + + + ); +}; + +const App = () => { + return ( + + + + + + + + + + + + + ); +}; + +const element = document.getElementById("root"); +const root = createRoot(element!); +root.render(); diff --git a/webpack.config.js b/webpack.config.js index ec8a111..1e810b3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,7 +7,7 @@ const dotenv = require("dotenv"); dotenv.config({ path: path.resolve(__dirname, ".env") }); /* Uncomment for ethereum example with boring queue */ - +/* module.exports = { entry: "./src/examples/v3.tsx", // Entry point for your React app output: { @@ -44,7 +44,7 @@ module.exports = { children: true, // Display information about child compilations }, }; - +*/ /* Uncomment for arbitrum example with direct withdraws */ /* @@ -124,4 +124,42 @@ module.exports = { children: true, // Display information about child compilations }, }; -*/ \ No newline at end of file +*/ + +/* Uncomment for eth example with direct withdraws & an alternative vault token*/ +module.exports = { + entry: "./src/examples/v4.tsx", // Entry point for your React app + output: { + path: path.resolve(__dirname, "dist"), // Output directory + filename: "v4.js", // Output file + }, + plugins: [ + new HtmlWebpackPlugin({ + template: "./src/examples/v4.html", // Path to your HTML template + }), + new webpack.DefinePlugin({ + "process.env": JSON.stringify(process.env), // Defines it on process.env + }), + ], + module: { + rules: [ + { + test: /\.tsx?$/, + use: "babel-loader", + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: [".tsx", ".ts", ".js"], // Resolve these extensions + }, + devServer: { + static: path.resolve(__dirname, "dist"), // Serve files from 'dist' directory + compress: true, + port: 9000, // Port to run the dev server + }, + stats: { + errorDetails: true, // Display the details of errors + children: true, // Display information about child compilations + }, +}; \ No newline at end of file