diff --git a/.gitignore b/.gitignore
index 98542e40..a6bccda9 100755
--- a/.gitignore
+++ b/.gitignore
@@ -12,7 +12,6 @@
/.nyc_output
/reports
-
/src/static/contracts/compiled/*.ts
/src/static/compiled/*.json
@@ -35,10 +34,13 @@ cypress/screenshots
cypress/videos
cypress.env.json
+tsconfig.tsbuildinfo
+
# vercel
.now
.vercel
.env
+.env.local
.eslintcache
diff --git a/README.md b/README.md
index 266bd655..aab7aea1 100755
--- a/README.md
+++ b/README.md
@@ -6,7 +6,13 @@ Rari Capital's Web3 Portal.
- Run `npm run dev` to start the development server.
- Run `npm run build` to build a production bundle
-- Run `npm run bump-rari-components` to automatically install the latest version (i.e. most recent commit hash) of rari-components and update `package.json`.
+- Run `npm run bump-rari-components` to automatically install the latest version (i.e. most recent commit hash) of [rari-components](https://github.com/Rari-Capital/rari-components) and update `package.json`.
+- Run `npm run typecheck` to typecheck
+
+## Environment
+
+- Set `NEXT_PUBLIC_USE_MOCKS` to `true` to test the dapp with mock objects (e.g. `NEXT_PUBLIC_USE_MOCKS=true npm run dev`).
+ - Currently, this env var is only used in the Turbo code — Ctrl+F for `process.env.NEXT_PUBLIC_USE_MOCKS` to see the specific use sites.
## Requirements
diff --git a/next.config.js b/next.config.js
index 21679762..045944ab 100644
--- a/next.config.js
+++ b/next.config.js
@@ -24,11 +24,11 @@ module.exports = withBundleAnalyzer({
config.module.rules.push({
test: /\.tsx?/,
- // Transpile rari-components, even though it is in node_modules
- include: [/node_modules\/rari-components/],
+ // Transpile rari-components
+ include: [/rari-components/],
use: "next-swc-loader",
});
return config;
},
-});
+});
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 1ca38336..bcd2f141 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
"@0xsequence/multicall": "^0.35.5",
"@aave/protocol-v2": "^1.0.1",
"@chakra-ui/icons": "^1.0.0",
- "@chakra-ui/react": "^1.8.1",
+ "@chakra-ui/react": "1.8.1",
"@emotion/react": "^11.0.0",
"@emotion/styled": "^11.0.0",
"@walletconnect/web3-provider": "^1.4.1",
@@ -41,7 +41,7 @@
"node-fetch": "^2.6.1",
"node-vibrant": "^3.1.6",
"polished": "^4.1.3",
- "rari-components": "github:Rari-Capital/rari-components#1131c95058f26b36048d9cfe507abb15ae5adaa8",
+ "rari-components": "github:Rari-Capital/rari-components#144451807e8673b4586274adfb8e78346ab32fce",
"rari-tokens-generator": "^2.0.0",
"react": "^17.0.2",
"react-apexcharts": "1.3.7",
@@ -51,6 +51,7 @@
"react-error-boundary": "^3.0.2",
"react-fast-marquee": "^1.1.3",
"react-infinite-scroll-hook": "^4.0.1",
+ "react-intersection-observer": "^8.34.0",
"react-jazzicon": "^0.1.3",
"react-query": "^3.13.10",
"react-responsive-carousel": "^3.2.18",
@@ -15788,8 +15789,8 @@
},
"node_modules/rari-components": {
"version": "0.0.1",
- "resolved": "git+https://git@github.com/Rari-Capital/rari-components.git#1131c95058f26b36048d9cfe507abb15ae5adaa8",
- "integrity": "sha512-7i2PwHSGEiRR56oIYF0jsGhSG+Io36C9p32g56KVM0Qzb+bxtoSSXTBJ+otLr1Y8Rs9Dd42Yp7lY3zxX8NBVVQ==",
+ "resolved": "git+ssh://git@github.com/Rari-Capital/rari-components.git#144451807e8673b4586274adfb8e78346ab32fce",
+ "integrity": "sha512-on74amrYNTfdogsVUr55D9xicaOH/lGXkR6jRn6GPHxcsqwo6OZ8ix2AkPY5hbQdn6DW7q08UU1QTKOO8x7KFw==",
"dependencies": {
"lodash": "^4.17.21"
},
@@ -16030,9 +16031,9 @@
}
},
"node_modules/react-intersection-observer": {
- "version": "8.33.1",
- "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.33.1.tgz",
- "integrity": "sha512-3v+qaJvp3D1MlGHyM+KISVg/CMhPiOlO6FgPHcluqHkx4YFCLuyXNlQ/LE6UkbODXlQcLOppfX6UMxCEkUhDLw==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.34.0.tgz",
+ "integrity": "sha512-TYKh52Zc0Uptp5/b4N91XydfSGKubEhgZRtcg1rhTKABXijc4Sdr1uTp5lJ8TN27jwUsdXxjHXtHa0kPj704sw==",
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0 || ^17.0.0|| ^18.0.0"
}
@@ -32595,9 +32596,9 @@
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"rari-components": {
- "version": "git+https://git@github.com/Rari-Capital/rari-components.git#1131c95058f26b36048d9cfe507abb15ae5adaa8",
- "integrity": "sha512-7i2PwHSGEiRR56oIYF0jsGhSG+Io36C9p32g56KVM0Qzb+bxtoSSXTBJ+otLr1Y8Rs9Dd42Yp7lY3zxX8NBVVQ==",
- "from": "rari-components@git+https://github.com/Rari-Capital/rari-components#1131c95058f26b36048d9cfe507abb15ae5adaa8",
+ "version": "git+ssh://git@github.com/Rari-Capital/rari-components.git#144451807e8673b4586274adfb8e78346ab32fce",
+ "integrity": "sha512-on74amrYNTfdogsVUr55D9xicaOH/lGXkR6jRn6GPHxcsqwo6OZ8ix2AkPY5hbQdn6DW7q08UU1QTKOO8x7KFw==",
+ "from": "rari-components@git+https://github.com/Rari-Capital/rari-components#144451807e8673b4586274adfb8e78346ab32fce",
"requires": {
"lodash": "^4.17.21"
}
@@ -32767,9 +32768,9 @@
}
},
"react-intersection-observer": {
- "version": "8.33.1",
- "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.33.1.tgz",
- "integrity": "sha512-3v+qaJvp3D1MlGHyM+KISVg/CMhPiOlO6FgPHcluqHkx4YFCLuyXNlQ/LE6UkbODXlQcLOppfX6UMxCEkUhDLw==",
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.34.0.tgz",
+ "integrity": "sha512-TYKh52Zc0Uptp5/b4N91XydfSGKubEhgZRtcg1rhTKABXijc4Sdr1uTp5lJ8TN27jwUsdXxjHXtHa0kPj704sw==",
"requires": {}
},
"react-intersection-observer-hook": {
diff --git a/package.json b/package.json
index a1dc3a04..ac81e2f6 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"@0xsequence/multicall": "^0.35.5",
"@aave/protocol-v2": "^1.0.1",
"@chakra-ui/icons": "^1.0.0",
- "@chakra-ui/react": "^1.8.1",
+ "@chakra-ui/react": "1.8.1",
"@emotion/react": "^11.0.0",
"@emotion/styled": "^11.0.0",
"@walletconnect/web3-provider": "^1.4.1",
@@ -35,7 +35,7 @@
"node-fetch": "^2.6.1",
"node-vibrant": "^3.1.6",
"polished": "^4.1.3",
- "rari-components": "github:Rari-Capital/rari-components#1131c95058f26b36048d9cfe507abb15ae5adaa8",
+ "rari-components": "github:Rari-Capital/rari-components#144451807e8673b4586274adfb8e78346ab32fce",
"rari-tokens-generator": "^2.0.0",
"react": "^17.0.2",
"react-apexcharts": "1.3.7",
@@ -45,6 +45,7 @@
"react-error-boundary": "^3.0.2",
"react-fast-marquee": "^1.1.3",
"react-infinite-scroll-hook": "^4.0.1",
+ "react-intersection-observer": "^8.34.0",
"react-jazzicon": "^0.1.3",
"react-query": "^3.13.10",
"react-responsive-carousel": "^3.2.18",
@@ -61,7 +62,7 @@
},
"scripts": {
"bump-rari-components": "rm -rf .next && npm i \"git+https://github.com/Rari-Capital/rari-components#$(git ls-remote https://github.com/Rari-Capital/rari-components.git HEAD | awk '{ print $1}')\"",
- "typecheck": "tsc --skipLibCheck -p ./tsconfig.json",
+ "typecheck": "tsc --noEmit",
"postinstall": "rari-tokens-generator ./src/static/compiled/tokens.json",
"start": "next start",
"build": "next build",
diff --git a/public/static/turbo/action-icons/claim-interest.png b/public/static/turbo/action-icons/claim-interest.png
new file mode 100644
index 00000000..6e167ca7
Binary files /dev/null and b/public/static/turbo/action-icons/claim-interest.png differ
diff --git a/public/static/turbo/action-icons/deposit-collateral.png b/public/static/turbo/action-icons/deposit-collateral.png
new file mode 100644
index 00000000..7fd070e7
Binary files /dev/null and b/public/static/turbo/action-icons/deposit-collateral.png differ
diff --git a/public/static/turbo/action-icons/withdraw-collateral.png b/public/static/turbo/action-icons/withdraw-collateral.png
new file mode 100644
index 00000000..87d8b889
Binary files /dev/null and b/public/static/turbo/action-icons/withdraw-collateral.png differ
diff --git a/public/static/turbo/isolated-actions.png b/public/static/turbo/isolated-actions.png
new file mode 100644
index 00000000..431c366d
Binary files /dev/null and b/public/static/turbo/isolated-actions.png differ
diff --git a/public/static/turbo/noseke.png b/public/static/turbo/noseke.png
new file mode 100644
index 00000000..ff0ddc02
Binary files /dev/null and b/public/static/turbo/noseke.png differ
diff --git a/public/static/turbo/one-collateral-type.png b/public/static/turbo/one-collateral-type.png
new file mode 100644
index 00000000..4cdd3889
Binary files /dev/null and b/public/static/turbo/one-collateral-type.png differ
diff --git a/public/static/turbo/treasury-assets.png b/public/static/turbo/treasury-assets.png
new file mode 100644
index 00000000..ca095ea5
Binary files /dev/null and b/public/static/turbo/treasury-assets.png differ
diff --git a/public/static/turbo/turbo-engine-3d-trimmed.png b/public/static/turbo/turbo-engine-3d-trimmed.png
new file mode 100644
index 00000000..eeb855bd
Binary files /dev/null and b/public/static/turbo/turbo-engine-3d-trimmed.png differ
diff --git a/public/static/turbo/turbo-engine-3d.png b/public/static/turbo/turbo-engine-3d.png
new file mode 100644
index 00000000..8a6cbfa1
Binary files /dev/null and b/public/static/turbo/turbo-engine-3d.png differ
diff --git a/public/static/turbo/turbo-engine-green.svg b/public/static/turbo/turbo-engine-green.svg
new file mode 100644
index 00000000..05da9109
--- /dev/null
+++ b/public/static/turbo/turbo-engine-green.svg
@@ -0,0 +1,22 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/static/turbo/turbo-engine-red.svg b/public/static/turbo/turbo-engine-red.svg
new file mode 100644
index 00000000..d80de2ad
--- /dev/null
+++ b/public/static/turbo/turbo-engine-red.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/public/static/turbo/turbo-no-bg.png b/public/static/turbo/turbo-no-bg.png
new file mode 100644
index 00000000..2bda7f39
Binary files /dev/null and b/public/static/turbo/turbo-no-bg.png differ
diff --git a/public/static/turbo/turbo.png b/public/static/turbo/turbo.png
new file mode 100644
index 00000000..cec294a2
Binary files /dev/null and b/public/static/turbo/turbo.png differ
diff --git a/public/static/turbo/turbo2.png b/public/static/turbo/turbo2.png
new file mode 100644
index 00000000..f6dee3e3
Binary files /dev/null and b/public/static/turbo/turbo2.png differ
diff --git a/public/static/turbo/user-icons/daos.png b/public/static/turbo/user-icons/daos.png
new file mode 100644
index 00000000..c4c8cd5f
Binary files /dev/null and b/public/static/turbo/user-icons/daos.png differ
diff --git a/public/static/turbo/user-icons/individuals.png b/public/static/turbo/user-icons/individuals.png
new file mode 100644
index 00000000..52e8fa79
Binary files /dev/null and b/public/static/turbo/user-icons/individuals.png differ
diff --git a/public/static/turbo/user-icons/more.png b/public/static/turbo/user-icons/more.png
new file mode 100644
index 00000000..d115cb5e
Binary files /dev/null and b/public/static/turbo/user-icons/more.png differ
diff --git a/public/static/turbo/user-icons/protocols.png b/public/static/turbo/user-icons/protocols.png
new file mode 100644
index 00000000..6d528ec2
Binary files /dev/null and b/public/static/turbo/user-icons/protocols.png differ
diff --git a/src/components/pages/Fuse/FusePoolsPage/PoolRow.tsx b/src/components/pages/Fuse/FusePoolsPage/PoolRow.tsx
index 0e74cb6d..19a378cd 100644
--- a/src/components/pages/Fuse/FusePoolsPage/PoolRow.tsx
+++ b/src/components/pages/Fuse/FusePoolsPage/PoolRow.tsx
@@ -115,12 +115,12 @@ export const PoolRow = ({
{poolNumber}
-
- {smallUsdFormatter(tvl)}
-
-
- {smallUsdFormatter(borrowed)}
-
+
+ {smallUsdFormatter(tvl)}
+
+
+ {smallUsdFormatter(borrowed)}
+
>
)}
diff --git a/src/components/pages/Fuse/Modals/PluginModal/PluginRewardsModal.tsx b/src/components/pages/Fuse/Modals/PluginModal/PluginRewardsModal.tsx
index 3911d78d..2d02965e 100644
--- a/src/components/pages/Fuse/Modals/PluginModal/PluginRewardsModal.tsx
+++ b/src/components/pages/Fuse/Modals/PluginModal/PluginRewardsModal.tsx
@@ -1,120 +1,150 @@
import {
- Modal,
- ModalOverlay,
- ModalContent,
- ModalHeader,
- ModalFooter,
- ModalBody,
- ModalCloseButton,
- Text,
- Avatar,
- Spinner,
- Flex,
- Link,
- Button,
- Heading,
- VStack,
- HStack,
-} from "@chakra-ui/react"
-import { TokenData } from "hooks/useTokenData"
-import { USDPricedFuseAsset } from "utils/fetchFusePoolData"
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalHeader,
+ ModalFooter,
+ ModalBody,
+ ModalCloseButton,
+ Text,
+ Avatar,
+ Spinner,
+ Flex,
+ Link,
+ Button,
+ Heading,
+ VStack,
+ HStack,
+} from "@chakra-ui/react";
+import { TokenData } from "hooks/useTokenData";
+import { USDPricedFuseAsset } from "utils/fetchFusePoolData";
-import { InfoIcon } from "@chakra-ui/icons"
-import AppLink from "components/shared/AppLink"
-import { CTokenAvatarGroup } from "components/shared/Icons/CTokenIcon"
-import { CONVEX_CTOKEN_INFO, eligibleTokens } from "constants/convex"
+import { InfoIcon } from "@chakra-ui/icons";
+import AppLink from "components/shared/AppLink";
+import { CTokenAvatarGroup } from "components/shared/Icons/CTokenIcon";
+import { CONVEX_CTOKEN_INFO, eligibleTokens } from "constants/convex";
export const PluginRewardsModal = ({
- market,
- isOpen,
- onClose,
- tokenData,
- rewardTokens
+ market,
+ isOpen,
+ onClose,
+ tokenData,
+ rewardTokens,
}: {
- market: USDPricedFuseAsset,
- isOpen: boolean,
- onClose: () => void,
- tokenData: TokenData | undefined,
- rewardTokens: string[]
+ market: USDPricedFuseAsset;
+ isOpen: boolean;
+ onClose: () => void;
+ tokenData: TokenData | undefined;
+ rewardTokens: string[];
}) => {
+ const index = eligibleTokens.indexOf(market.underlyingSymbol);
+ const symbol = tokenData?.symbol ?? market.underlyingSymbol;
- const index = eligibleTokens.indexOf(market.underlyingSymbol)
- const symbol = tokenData?.symbol ?? market.underlyingSymbol
+ const pluginTokenInfo = CONVEX_CTOKEN_INFO?.[market.underlyingSymbol] ?? {};
- const pluginTokenInfo = CONVEX_CTOKEN_INFO?.[market.underlyingSymbol] ?? {}
+ return (
+ <>
+
+
+
+
+ {tokenData ? (
+
+ ) : (
+
+ )}
+
+ {" "}
+ 🔌 {symbol}
+
+
+ Rewards
+
+
+
+
+ {index === -1 ? null : (
+
+ {/* */}
+
+
+
+ This market streams
+ {" "}
+ rewards from the {pluginTokenInfo?.convexPoolName} {" "}
+ Convex pool to suppliers of{" "}
+ {pluginTokenInfo?.curvePoolName} Curve LPs.{" "}
+
+ {/* Deposit your {pluginTokenInfo?.curvePoolName} Curve LP tokens into Fuse to borrow against it while earning all the same rewards from Convex. */}
+ {/* View reward rates for {pluginTokenInfo?.convexPoolName} on Convex */}
+
+ {/* */}
- return (
- <>
-
-
-
-
- {tokenData ? : }
- 🔌 {symbol}
-
- Rewards
-
-
-
-
- {index === -1 ? null :
-
- {/* */}
-
-
-
- This market streams rewards from the
-
- from the {pluginTokenInfo?.convexPoolName} Convex pool
- to suppliers of {pluginTokenInfo?.curvePoolName} Curve LPs.
- {/* Deposit your {pluginTokenInfo?.curvePoolName} Curve LP tokens into Fuse to borrow against it while earning all the same rewards from Convex. */}
- {/* View reward rates for {pluginTokenInfo?.convexPoolName} on Convex */}
-
- {/* */}
+
+ Info
+
+
+
+
+
+ )}
+
+
+
+
+ View rates for{" "}
+
+ {pluginTokenInfo?.convexPoolName}
+ {" "}
+ on Convex
+
+
+
+
+
+ >
+ );
+};
-
- Info
-
-
-
-
-
-
-
- }
-
-
-
-
- View rates for {' '} {pluginTokenInfo?.convexPoolName} {' '} on Convex
-
-
-
-
-
- >
- )
-}
-
-const InfoPairs = ({
- title,
- address,
- link
+export const InfoPairs = ({
+ title,
+ address,
+ link,
}: {
- title: string,
- address: string,
- link?: string,
+ title: string;
+ address: string;
+ link?: string;
}) => {
- return (
-
- {title}
-
- {link ?? address}
-
-
- )
-}
-
+ return (
+
+ {title}
+
+
+ {link ?? address}
+
+
+
+ );
+};
-export default PluginRewardsModal
\ No newline at end of file
+export default PluginRewardsModal;
diff --git a/src/components/pages/Home/Home.tsx b/src/components/pages/Home/Home.tsx
index daf69335..e1449abb 100644
--- a/src/components/pages/Home/Home.tsx
+++ b/src/components/pages/Home/Home.tsx
@@ -276,7 +276,7 @@ const Home = () => {
-
+
Discover infinite possibilities across the Rari Capital
Ecosystem
diff --git a/src/components/pages/Turbo/TurboIndexPage/CreateSafeModal/CreateSafeModal.tsx b/src/components/pages/Turbo/TurboIndexPage/CreateSafeModal/CreateSafeModal.tsx
new file mode 100644
index 00000000..f779b2ea
--- /dev/null
+++ b/src/components/pages/Turbo/TurboIndexPage/CreateSafeModal/CreateSafeModal.tsx
@@ -0,0 +1,258 @@
+import { useRari } from "context/RariContext";
+import { formatEther, parseEther } from "ethers/lib/utils";
+import { useBalanceOf } from "hooks/useBalanceOf";
+import useHasApproval from "hooks/useHasApproval";
+import { createSafeAndDeposit } from "lib/turbo/transactions/createSafeAndDeposit";
+import { createSafe } from "lib/turbo/transactions/safe";
+import { TRIBE, TurboAddresses } from "lib/turbo/utils/constants";
+import { useRouter } from "next/router";
+import { Heading, Modal, Text } from "rari-components";
+import { useState } from "react";
+import { approve } from "utils/erc20Utils";
+import { handleGenericError } from "utils/errorHandling";
+import { Flex, useToast } from "@chakra-ui/react";
+import { CreateSafeCtx, MODAL_STEPS } from "./modalSteps";
+import { MAX_APPROVAL_AMOUNT } from "utils/tokenUtils";
+import { createTurboMaster } from "lib/turbo/utils/turboContracts";
+import { getRecentEventDecoded } from "lib/turbo/utils/decodeEvents";
+import { useQuery } from "react-query";
+import { getEthUsdPriceBN } from "esm/utils/getUSDPriceBN";
+import { getPriceFromOracles } from "hooks/rewards/useRewardAPY";
+import { BigNumber, constants } from "ethers";
+import { calculateMaxBoost } from "lib/turbo/fetchers/safes/getSafeInfo";
+import { getMarketCf } from "lib/turbo/utils/getMarketCF";
+import { USDPricedTurboSafe } from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import useApprovedCollateral from "hooks/turbo/useApprovedCollateral";
+
+type CreateSafeModalProps = Pick<
+ React.ComponentProps,
+ "isOpen" | "onClose"
+>;
+
+export type SimulatedSafe = {
+ collateralUSD: BigNumber;
+ maxBoost: BigNumber;
+};
+
+export const CreateSafeModal: React.FC = ({
+ isOpen,
+ onClose,
+}) => {
+ // Rari and NextJs
+ const router = useRouter();
+ const { address, provider, chainId, fuse, isAuthed } = useRari();
+ const toast = useToast();
+
+ // Modal State
+ const [stepIndex, setStepIndex] = useState(0);
+ function incrementStepIndex() {
+ if (stepIndex + 1 !== MODAL_STEPS.length) {
+ setStepIndex(stepIndex + 1);
+ }
+ }
+ // Modal Buttons
+ const [approving, setApproving] = useState(false);
+ const [creatingSafe, setCreatingSafe] = useState(false);
+ const [navigating, setNavigating] = useState(false);
+
+ // Safe's chosen underlying asset
+ const [createdSafe, setCreatedSafe] = useState(undefined);
+
+ const underlyingTokenAddresses = useApprovedCollateral();
+ const [underlyingTokenAddress, setUnderlyingTokenAddress] = useState(TRIBE);
+ const collateralBalance = useBalanceOf(address, underlyingTokenAddress);
+
+ // Used only if user will deposit after creating.
+ const [depositAmount, setDepositAmount] = useState("");
+
+ // Router State
+ const hasApproval = useHasApproval(
+ underlyingTokenAddress,
+ TurboAddresses[chainId ?? 1]?.ROUTER,
+ depositAmount,
+ address
+ );
+
+ const { data: safeSimulation } = useQuery(
+ "Safe creation and deposit simulation for deposit amount:" + depositAmount,
+ async () => {
+ if (depositAmount === "0" || !depositAmount || !chainId) return;
+
+ // 1. Get eth price and collateral price.
+ // @note - collateral price is denominated in ether. collateralPrice * ethPrice = collateralPriceToUSD
+ const ethUSDBN = (await getEthUsdPriceBN()).div(constants.WeiPerEther);
+ const collateralPriceBN = await getPriceFromOracles(
+ TRIBE,
+ TurboAddresses[1].COMPTROLLER,
+ fuse,
+ isAuthed
+ );
+
+ // Get collateral factor
+ const collateralFactor = await getMarketCf(
+ provider,
+ chainId,
+ underlyingTokenAddress
+ );
+
+ // Calculations
+ const amountBN = BigNumber.from(depositAmount);
+ const collateralUSD = collateralPriceBN.mul(ethUSDBN).mul(amountBN);
+ const maxBoost = calculateMaxBoost(collateralUSD, collateralFactor);
+
+ const safe: SimulatedSafe = {
+ collateralUSD,
+ maxBoost,
+ };
+ return safe;
+ }
+ );
+
+ // Modal Logic
+ const onClickCreateSafe = async () => {
+ if (!address || !provider || !chainId) return;
+
+ setCreatingSafe(true);
+
+ const amountBN = parseEther(depositAmount === "" ? "0" : depositAmount);
+
+ let receipt;
+ if (!amountBN.isZero()) {
+ try {
+ const tx = await createSafe(underlyingTokenAddress, provider, 1);
+
+ receipt = await tx.wait(2);
+ const turboMasterContract = createTurboMaster(provider, chainId);
+ const event = await getRecentEventDecoded(
+ turboMasterContract,
+ turboMasterContract.filters.TurboSafeCreated
+ );
+ setCreatedSafe(event.safe);
+ incrementStepIndex();
+ } catch (err) {
+ handleGenericError(err, toast);
+ console.log({ err });
+ throw err;
+ } finally {
+ setCreatingSafe(false);
+ }
+ } else {
+ try {
+ const tx = await createSafe(underlyingTokenAddress, provider, chainId);
+ const receipt = await tx.wait(1);
+ const turboMasterContract = createTurboMaster(provider, chainId);
+ const event = await getRecentEventDecoded(
+ turboMasterContract,
+ turboMasterContract.filters.TurboSafeCreated
+ );
+ setCreatedSafe(event.safe);
+ incrementStepIndex();
+ } catch (err) {
+ handleGenericError(err, toast);
+ console.log({ err });
+ throw err;
+ } finally {
+ setCreatingSafe(false);
+ }
+ }
+
+ return receipt;
+ };
+
+ async function onClickApprove() {
+ if (!depositAmount || !chainId) return;
+
+ setApproving(true);
+
+ try {
+ await approve(
+ provider.getSigner(),
+ TurboAddresses[chainId].ROUTER,
+ underlyingTokenAddress,
+ MAX_APPROVAL_AMOUNT
+ );
+ } finally {
+ setApproving(false);
+ }
+ }
+
+ const balance = formatEther(collateralBalance);
+
+ const onClickMax = async () => {
+ if (!chainId) return;
+ try {
+ setDepositAmount(balance.slice(0, balance.indexOf(".")));
+ } catch (err) {
+ handleGenericError(err, toast);
+ }
+ };
+
+ // Modal Context
+ const createSafeCtx: CreateSafeCtx = {
+ incrementStepIndex,
+ underlyingTokenAddresses,
+ underlyingTokenAddress,
+ setUnderlyingTokenAddress,
+ depositAmount,
+ setDepositAmount,
+ hasApproval,
+ approving,
+ onClickApprove,
+ onClickCreateSafe,
+ creatingSafe,
+ safeSimulation,
+ navigating,
+ collateralBalance: balance,
+ onClickMax,
+ createdSafe,
+ onClose() {
+ // Only allow close if a transaction isn't in progress.
+ if (!approving && !creatingSafe) {
+ setStepIndex(0);
+ onClose();
+ }
+ },
+ async navigateToCreatedSafe() {
+ if (!createdSafe) return;
+ setNavigating(true);
+ try {
+ await router.push(`/turbo/safe/${createdSafe}`);
+ } finally {
+ setNavigating(false);
+ }
+ },
+ };
+
+ return (
+ {
+ if (!approving && !creatingSafe) {
+ setStepIndex(0);
+ onClose();
+ }
+ }}
+ progressValue={((stepIndex + 1) / MODAL_STEPS.length) * 100}
+ {...MODAL_STEPS[stepIndex]}
+ footerChildren={
+ <>
+ Common questions:
+
+
+ What are safes?
+
+
+ What is boosting?
+
+
+ How do liquidations work?
+
+
+ >
+ }
+ />
+ );
+};
+
+export default CreateSafeModal;
diff --git a/src/components/pages/Turbo/TurboIndexPage/CreateSafeModal/index.ts b/src/components/pages/Turbo/TurboIndexPage/CreateSafeModal/index.ts
new file mode 100644
index 00000000..049b9239
--- /dev/null
+++ b/src/components/pages/Turbo/TurboIndexPage/CreateSafeModal/index.ts
@@ -0,0 +1 @@
+export { default } from "./CreateSafeModal";
diff --git a/src/components/pages/Turbo/TurboIndexPage/CreateSafeModal/modalSteps.tsx b/src/components/pages/Turbo/TurboIndexPage/CreateSafeModal/modalSteps.tsx
new file mode 100644
index 00000000..ba3a3998
--- /dev/null
+++ b/src/components/pages/Turbo/TurboIndexPage/CreateSafeModal/modalSteps.tsx
@@ -0,0 +1,448 @@
+import { BigNumber, constants, utils } from "ethers";
+import { commify, formatEther } from "ethers/lib/utils";
+import {
+ Card,
+ Heading,
+ HoverableCard,
+ ModalProps,
+ StatisticsTable,
+ Text,
+ TokenAmountInput,
+ TokenIcon,
+ TokenSymbol,
+} from "rari-components";
+import { CheckCircleIcon, ChevronRightIcon } from "@chakra-ui/icons";
+import {
+ Box,
+ Flex,
+ HStack,
+ Image,
+ Spacer,
+ Spinner,
+ Stack,
+ VStack,
+} from "@chakra-ui/react";
+import { SimulatedSafe } from "./CreateSafeModal";
+
+type CreateSafeCtx = {
+ /**
+ * Function to increment the current step by 1 (i.e. go to the next step in
+ * the safe creation process).
+ */
+ incrementStepIndex(): void;
+ /** List of addresses of possible underlying tokens. */
+ underlyingTokenAddresses: string[];
+ /** The address of the currently selected underlying token for the safe. */
+ underlyingTokenAddress: string;
+ /** Function to set the address of the underlying token of the safe. */
+ setUnderlyingTokenAddress(underlyingTokenAddress: string): void;
+ /**
+ * Amount to deposit into the safe. Stored as a string that we'll convert to
+ * a `BigNumber` later.
+ */
+ depositAmount: string;
+ /** Set a new amount to deposit into the safe. */
+ setDepositAmount(newDepositAmount: string): void;
+ /**
+ * Whether the currently selected `underlyingTokenAddress` has approved
+ * the router.
+ */
+ hasApproval: boolean;
+ /** Function to approve the underlying token of the safe. */
+ onClickApprove: () => Promise;
+ /** Whether the approval is currently pending. */
+ approving: boolean;
+ /** Function which creates a safe. */
+ onClickCreateSafe: () => Promise;
+ /** Whether the safe is currently being created. */
+ creatingSafe: boolean;
+ collateralBalance: string;
+ safeSimulation: SimulatedSafe | undefined;
+ onClickMax(): Promise;
+ /** Function which closes the modal. */
+ onClose(): void;
+ /** Whether the navigation to the created safe is pending. */
+ navigating: boolean;
+ /**
+ * Function which navigates to the safe that was just created in this modal.
+ */
+ navigateToCreatedSafe(): void;
+ createdSafe: string | undefined;
+};
+
+type ModalStep = Omit, "ctx" | "isOpen" | "onClose">;
+
+const MODAL_STEP_1: ModalStep = {
+ title: "Create a Safe",
+ subtitle: "Safes isolate manage and your collateral.",
+ children: ({
+ underlyingTokenAddresses,
+ setUnderlyingTokenAddress,
+ incrementStepIndex,
+ }) => (
+
+ {!underlyingTokenAddresses.length && }
+ {underlyingTokenAddresses.map((tokenAddress) => (
+ {
+ setUnderlyingTokenAddress(tokenAddress);
+ incrementStepIndex();
+ }}
+ key={tokenAddress}
+ p={4}
+ >
+ {(hovered) => (
+
+
+
+
+
+
+
+ {" "}
+ Safe
+
+
+
+
+ Boost FEI against{" "}
+ {" "}
+ collateral
+
+
+
+
+
+
+ )}
+
+ ))}
+
+ ),
+};
+
+const MODAL_STEP_2: ModalStep = {
+ children: ({ underlyingTokenAddress }) => (
+
+
+
+ {" "}
+ You are creating a
+
+
+
+
+ Safe
+
+
+
+
+
+
+
+
+
+ Collateral
+
+
+ Boost FEI against{" "}
+ {" "}
+ Collateral.
+
+
+
+
+
+
+ Isolated
+
+ Safes isolate collateral, FEI boosting, and liquidations.
+
+
+
+
+
+
+ ),
+ buttons: ({
+ hasApproval,
+ approving,
+ depositAmount,
+ creatingSafe,
+ onClickCreateSafe,
+ onClickApprove,
+ incrementStepIndex,
+ }) => [
+ {
+ children: approving
+ ? "Approving..."
+ : creatingSafe
+ ? "Creating Safe..."
+ : !hasApproval &&
+ BigNumber.from(!!depositAmount ? depositAmount : "0").gt(0)
+ ? "Approve Router"
+ : BigNumber.from(!!depositAmount ? depositAmount : "0").gt(0)
+ ? "Create Safe & Deposit"
+ : "Create Safe",
+ variant: "success",
+ loading: approving || creatingSafe,
+ async onClick() {
+ try {
+ if (!hasApproval) {
+ await onClickApprove();
+ }
+ await onClickCreateSafe();
+ } catch (err) {
+ throw err;
+ }
+ },
+ },
+ ],
+};
+
+// const MODAL_STEP_3: ModalStep = {
+// title: "Deposit collateral",
+// subtitle:
+// "Collateralizing is required before boosting pools. This step is optional.",
+// children: ({
+// underlyingTokenAddress,
+// onClickMax,
+// depositAmount,
+// collateralBalance,
+// setDepositAmount,
+// safeSimulation,
+// }) => (
+//
+//
+//
+//
+//
+// Balance: {commify(collateralBalance)}{" "}
+//
+//
+//
+//
+//
+//
+// ),
+// buttons: ({
+// incrementStepIndex,
+// setDepositAmount,
+// depositAmount,
+// collateralBalance,
+// }) => [
+// {
+// children: "Skip",
+// variant: "cardmatte",
+// onClick() {
+// setDepositAmount("0");
+// incrementStepIndex();
+// },
+// },
+// {
+// disabled:
+// parseInt(depositAmount) > parseInt(collateralBalance) ? true : false,
+// children:
+// parseInt(depositAmount) > parseInt(collateralBalance)
+// ? "Invalid amount"
+// : "Review",
+// variant: "neutral",
+// onClick() {
+// incrementStepIndex();
+// },
+// },
+// ],
+// };
+
+// const MODAL_STEP_4: ModalStep = {
+// children: ({ underlyingTokenAddress, depositAmount, safeSimulation }) => (
+//
+//
+// You are creating
+//
+// {" "}
+// Safe
+//
+//
+// {!depositAmount || depositAmount === "0" ? null : (
+//
+// )}
+//
+// ),
+// stepBubbles: ({ approving, creatingSafe, hasApproval }) => ({
+// steps: hasApproval ? 1 : 2,
+// loading: approving || creatingSafe,
+// activeIndex: creatingSafe ? 1 : 0,
+// background: "neutral",
+// }),
+// buttons: ({
+// hasApproval,
+// approving,
+// depositAmount,
+// creatingSafe,
+// onClickCreateSafe,
+// onClickApprove,
+// incrementStepIndex,
+// }) => [
+// {
+// children: approving
+// ? "Approving..."
+// : creatingSafe
+// ? "Creating Safe..."
+// : !hasApproval &&
+// BigNumber.from(!!depositAmount ? depositAmount : "0").gt(0)
+// ? "Approve Router"
+// : BigNumber.from(!!depositAmount ? depositAmount : "0").gt(0)
+// ? "Create Safe & Deposit"
+// : "Create Safe",
+// variant: "neutral",
+// loading: approving || creatingSafe,
+// async onClick() {
+// try {
+// if (!hasApproval) {
+// await onClickApprove();
+// }
+// await onClickCreateSafe();
+// incrementStepIndex();
+// } catch (err) {
+// throw err;
+// }
+// },
+// },
+// ],
+// };
+
+const MODAL_STEP_5: ModalStep = {
+ children: ({ underlyingTokenAddress }) => (
+
+
+
+
+ {" "}
+ Safe
+
+
+ Successfully created
+
+
+
+ ),
+ buttons: ({ onClose, navigateToCreatedSafe, createdSafe, navigating }) => [
+ {
+ children: navigating ? "Loading..." : "View Safe",
+ loading: navigating,
+ disabled: !createdSafe,
+ variant: "success",
+ async onClick() {
+ await navigateToCreatedSafe();
+ onClose();
+ },
+ },
+ ],
+};
+
+const MODAL_STEPS: ModalStep[] = [
+ MODAL_STEP_1,
+ MODAL_STEP_2,
+ // MODAL_STEP_3,
+ // MODAL_STEP_4,
+ MODAL_STEP_5,
+];
+
+export { MODAL_STEPS };
+export type { CreateSafeCtx };
diff --git a/src/components/pages/Turbo/TurboIndexPage/SafeCard.tsx b/src/components/pages/Turbo/TurboIndexPage/SafeCard.tsx
new file mode 100644
index 00000000..faddf6ac
--- /dev/null
+++ b/src/components/pages/Turbo/TurboIndexPage/SafeCard.tsx
@@ -0,0 +1,138 @@
+import { commify, formatEther, formatUnits } from "ethers/lib/utils";
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+import {
+ Badge,
+ Heading,
+ HoverableCard,
+ Link,
+ Progress,
+ Text,
+ TokenIcon,
+ TokenSymbol,
+ Tooltip,
+} from "rari-components";
+import { abbreviateAmount } from "utils/bigUtils";
+import { Box, Flex, HStack, Spacer, Stack } from "@chakra-ui/react";
+import {
+ StrategyInfosMap,
+ useERC4626StrategiesDataAsMap,
+} from "hooks/turbo/useStrategyInfo";
+import useSafeAvgAPY from "hooks/turbo/useSafeAvgAPY";
+import { filterUsedStrategies } from "lib/turbo/fetchers/strategies/formatStrategyInfo";
+import { useSafeInfo } from "hooks/turbo/useSafeInfo";
+import { useTokenData } from "hooks/useTokenData";
+import useShouldBoostSafe from "hooks/turbo/useShouldBoostSafe";
+import { getSafeColor } from "context/TurboSafeContext";
+import { motion } from "framer-motion";
+import { USDPricedTurboSafe } from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+
+type SafeCardProps = {
+ safe: USDPricedTurboSafe | SafeInfo;
+ previewMode: boolean;
+};
+
+const SafeCard: React.FC = ({ safe, previewMode = false }) => {
+ const usdPricedSafeInfo = useSafeInfo(safe.safeAddress);
+
+ const getERC4626StrategyData = useERC4626StrategiesDataAsMap(
+ safe.strategies.map((s) => s.strategy)
+ );
+
+ const avgAPY = useSafeAvgAPY(
+ filterUsedStrategies(safe.strategies),
+ getERC4626StrategyData,
+ parseFloat(formatEther(safe?.tribeDAOFee ?? 0))
+ );
+
+ const tokenData = useTokenData(safe.collateralAsset);
+
+ const boostMe = useShouldBoostSafe(safe);
+ const isAtLiquidationRisk =
+ usdPricedSafeInfo?.safeUtilization.gt(80) ?? false;
+
+ const color = getSafeColor(safe?.safeUtilization);
+
+ return (
+
+
+
+ {(hovered) => (
+
+
+
+
+ Safe
+
+
+
+ {!previewMode ? (
+ <>
+ {isAtLiquidationRisk && (
+ At Risk
+ )}
+ {boostMe && Boost Me }
+ >
+ ) : null}
+
+
+
+
+ <>
+
+ {abbreviateAmount(
+ usdPricedSafeInfo?.collateralValueUSD
+ )}
+
+
+ deposited
+
+ >
+
+
+
+ {avgAPY.toFixed(2)}%
+
+ APY
+
+
+
+
+
+ Active boost
+
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default SafeCard;
diff --git a/src/components/pages/Turbo/TurboIndexPage/SafeGrid.tsx b/src/components/pages/Turbo/TurboIndexPage/SafeGrid.tsx
new file mode 100644
index 00000000..91fb8d4f
--- /dev/null
+++ b/src/components/pages/Turbo/TurboIndexPage/SafeGrid.tsx
@@ -0,0 +1,57 @@
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+import { Heading, HoverableCard, Text } from "rari-components";
+import { Box, Flex, SimpleGrid } from "@chakra-ui/react";
+import SafeCard from "./SafeCard";
+import { useIsUserAuthorizedToCreateSafes } from "hooks/turbo/useIsUserAuthorizedToCreateSafes";
+
+type SafeGridProps = {
+ safes: SafeInfo[];
+ onClickCreateSafe?: () => void;
+};
+
+const SafeGrid: React.FC = ({ safes, onClickCreateSafe }) => {
+ let isAuthed = useIsUserAuthorizedToCreateSafes();
+ const previewMode = !onClickCreateSafe || !isAuthed;
+ console.log({ previewMode });
+ return (
+
+
+ {!previewMode && (
+
+ {(hovered) => (
+
+
+ Add safe{" "}
+
+
+ +
+
+
+
+
+ Every safe isolates and manages your collateral.
+
+
+ )}
+
+ )}
+ {safes.map((safe) => (
+
+ ))}
+
+
+ );
+};
+
+export default SafeGrid;
diff --git a/src/components/pages/Turbo/TurboIndexPage/TurboFAQ.tsx b/src/components/pages/Turbo/TurboIndexPage/TurboFAQ.tsx
new file mode 100644
index 00000000..c69181a3
--- /dev/null
+++ b/src/components/pages/Turbo/TurboIndexPage/TurboFAQ.tsx
@@ -0,0 +1,351 @@
+import { Variants, motion, useAnimation } from "framer-motion";
+import usePreviewSafes from "hooks/turbo/usePreviewSafes";
+import { Button, Heading, Text } from "rari-components";
+import React, { useEffect, useRef } from "react";
+import { useInView } from "react-intersection-observer";
+import {
+ Box,
+ BoxProps,
+ Center,
+ Flex,
+ HStack,
+ Image,
+ SimpleGrid,
+ Stack,
+} from "@chakra-ui/react";
+import AppLink from "components/shared/AppLink";
+
+type TurboFAQProps = BoxProps;
+
+const TurboFAQ: React.FC = (props) => {
+ const safes = usePreviewSafes();
+ return (
+
+
+ {/* Section 1 */}
+
+
+ {/* Section 2 */}
+
+
+ {/* Section 3 */}
+
+
+ {/* Section 4*/}
+
+
+
+ );
+};
+
+const Section1 = () => (
+
+
+ How does Turbo work?
+ {/* `Stack` adds a little bit of space between each child, even if
+ * `spacing` is set to 0, which we don't want because we want the bottom
+ * borders of each child to flow together, giving the impression of a
+ * single, connected arrow. `Flex` does not add this space, so we use
+ * `Flex` instead. */}
+
+
+
+
+ 1
+
+ Create Safe
+
+
+
+ Take your first step by creating a Turbo Safe. Manage your safe to
+ boost against a collateral type, earn yield, bootstrap Fuse Pools,
+ and more.
+
+
+
+
+
+ 2
+
+ Deposit Collateral
+
+
+
+ Deposit whitelisted collateral (gOHM, TRIBE, BAL) into your safe to
+ enable boosting FEI.
+
+
+
+
+
+ 3
+
+ Boost FEI
+
+
+
+ {/* Arrow tip */}
+ {/* https://css-tricks.com/snippets/css/css-triangle/ */}
+
+
+
+ Take a 0% interest FEI loan, and boost it into the yield bearing
+ strategy of your choice. Claim interest whenever you like.
+
+
+
+
+
+);
+
+const Section2 = () => (
+
+
+
+
+
+ Turbo is for everyone
+
+ Turbo can be used by individuals, treasuries, DAOs, protocols, or any
+ on-chain entity.
+
+
+
+
+
+
+ Individuals
+
+
+
+
+
+
+
+ DAOs
+
+
+
+
+
+
+
+ Protocols
+
+
+
+
+
+
+
+ And more...
+
+
+
+
+
+
+
+);
+
+const Section3 = () => (
+
+
+
+ Utilize your treasury assets
+
+ Deposit BAL, TRIBE, and gOHM today. Support for more protocol tokens
+ coming soon.
+
+
+
+
+
+);
+
+const Section4 = () => (
+
+
+
+ A completely new financial mechanism
+
+ Turbo changes the relationship between users in an ecosystem and the
+ stablecoin issuer. It’s a powerful transformation in the thinking of
+ stablecoins and their provision throughout the ecosystem.
+
+
+
+
+ Get Involved
+
+
+ View Blog Post
+
+
+
+
+);
+
+/** Animations */
+type AnimateInViewDirection = "left" | "right" | "top" | "bottom";
+type AnimateInViewProps = {
+ children: any;
+ from: AnimateInViewDirection;
+};
+
+// TODO (@sharad-s) Move into Component Library and fix the map key type
+const VARIANT_DEFAULTS = {
+ transition: {
+ duration: 0.6,
+ staggerChildren: 0.3,
+ delayChildren: 0.3,
+ },
+};
+
+const variants: { [from: string]: Variants } = {
+ top: {
+ visible: { opacity: 1, y: 0, ...VARIANT_DEFAULTS },
+ hidden: { opacity: 0, y: -40 },
+ },
+ bottom: {
+ visible: { opacity: 1, y: 0, ...VARIANT_DEFAULTS },
+ hidden: { opacity: 0, y: 40 },
+ },
+ left: {
+ visible: { opacity: 1, x: 0, ...VARIANT_DEFAULTS },
+ hidden: { opacity: 0, x: -40 },
+ },
+ right: {
+ visible: { opacity: 1, x: 0, ...VARIANT_DEFAULTS },
+ hidden: { opacity: 0, x: 40 },
+ },
+};
+
+const AnimateInView: React.FC = ({
+ children,
+ from = "top",
+}) => {
+ const controls = useAnimation();
+ const [ref, inView] = useInView();
+ useEffect(() => {
+ if (inView) {
+ controls.start("visible");
+ }
+ }, [controls, inView]);
+ return (
+
+ {children}
+
+ );
+};
+
+export default TurboFAQ;
diff --git a/src/components/pages/Turbo/TurboIndexPage/TurboIndexPage.tsx b/src/components/pages/Turbo/TurboIndexPage/TurboIndexPage.tsx
new file mode 100644
index 00000000..9ad82c7d
--- /dev/null
+++ b/src/components/pages/Turbo/TurboIndexPage/TurboIndexPage.tsx
@@ -0,0 +1,131 @@
+import { useIsUserAuthorizedToCreateSafes } from "hooks/turbo/useIsUserAuthorizedToCreateSafes";
+import { useAllUserSafes } from "hooks/turbo/useUserSafes";
+import { TRIBE } from "lib/turbo/utils/constants";
+import { Button, Divider, Heading, Text } from "rari-components";
+import { useRariTokenData } from "rari-components/hooks";
+import { WarningIcon } from "@chakra-ui/icons";
+import {
+ Box,
+ HStack,
+ Image,
+ Stack,
+ VStack,
+ useDisclosure,
+ SlideFade,
+ Slide,
+} from "@chakra-ui/react";
+import TurboLayout from "../TurboLayout";
+import CreateSafeModal from "./CreateSafeModal/";
+import TurboFAQ from "./TurboFAQ";
+import UserSafes from "./UserSafes";
+import useApprovedCollateral from "hooks/turbo/useApprovedCollateral";
+import { useTokensDataAsMap } from "hooks/useTokenData";
+import { useRef } from "react";
+import TurboEngineIcon from "components/shared/Icons/TurboEngineIcon";
+
+const TurboIndexPage: React.FC = () => {
+ const scrollRef = useRef(null);
+ const { isOpen, onOpen, onClose } = useDisclosure();
+
+ const safes = useAllUserSafes() ?? [];
+ const hasSafes = safes.length > 0;
+
+ const isAuthorized = useIsUserAuthorizedToCreateSafes();
+
+ const approvedCollateral = useApprovedCollateral();
+
+ // // Prefetch Tribe data so it's in the `TokenIcon`/`TokenSymbol` cache.
+ // // This allows collateral types to load instantly when the "Create Safe"
+ // // modal is initially opened.
+ // const _ = useRariTokenData(TRIBE);
+ // const tokenData = useTokensDataAsMap(approvedCollateral);
+
+ const handleClick = () => {
+ // @ts-ignore
+ isAuthorized && !hasSafes ? onOpen() : scrollRef.current.scrollIntoView();
+ };
+
+ return (
+ // Hide overflow of main Turbo screenshot image
+
+
+
+
+ {/* Copy */}
+
+
+
+
+ Introducing Turbo
+
+
+
+ Boost FEI liquidity and earn interest against any protocol
+ token.
+
+ {/* Buttons */}
+
+
+ {!!isAuthorized && (
+
+ {hasSafes ? "Manage Your Safes" : "Create a safe"}
+
+ )}
+ {hasSafes ? (
+
+ Learn more
+
+ ) : (
+
+ Learn more
+
+ )}
+
+
+
+
+
+ {/* Image */}
+
+
+
+
+
+
+
+
+
+ {hasSafes ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+};
+
+export default TurboIndexPage;
diff --git a/src/components/pages/Turbo/TurboIndexPage/UserSafes.tsx b/src/components/pages/Turbo/TurboIndexPage/UserSafes.tsx
new file mode 100644
index 00000000..f4af3a87
--- /dev/null
+++ b/src/components/pages/Turbo/TurboIndexPage/UserSafes.tsx
@@ -0,0 +1,76 @@
+import { Box } from "@chakra-ui/react";
+import { commify, formatEther } from "ethers/lib/utils";
+import { useERC4626StrategiesDataAsMap } from "hooks/turbo/useStrategyInfo";
+import { useTrustedStrategies } from "hooks/turbo/useTrustedStrategies";
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+import { Heading, Statistic } from "rari-components";
+import { smallUsdFormatter } from "utils/bigUtils";
+import { TriangleDownIcon, TriangleUpIcon } from "@chakra-ui/icons";
+import { Flex, HStack } from "@chakra-ui/react";
+import useAggregateSafeData from "hooks/turbo/useAggregateSafeData";
+import { useState } from "react";
+import SafeGrid from "./SafeGrid";
+
+type SafeGridProps = {
+ safes: SafeInfo[];
+ onClickCreateSafe(): void;
+};
+const UserSafes: React.FC = ({ safes, onClickCreateSafe }) => {
+ const allStrategies = useTrustedStrategies();
+ const getERC4626StrategyData = useERC4626StrategiesDataAsMap(allStrategies);
+
+ const { totalBoosted, totalClaimableUSD, netAPY } = useAggregateSafeData(
+ safes,
+ getERC4626StrategyData
+ );
+
+ // TODO(sharad-s) write APY triangle implementation
+ const [apyIncreasing, setApyIncreasing] = useState(true);
+
+ return (
+
+
+ Your Safes
+
+
+
+
+ setApyIncreasing(!apyIncreasing)}
+ >
+
+ {netAPY.toFixed(2)}%
+
+ {/* {apyIncreasing ? (
+
+ ) : (
+
+ )} */}
+
+ }
+ />
+
+
+
+ );
+};
+
+export default UserSafes;
diff --git a/src/components/pages/Turbo/TurboIndexPage/index.ts b/src/components/pages/Turbo/TurboIndexPage/index.ts
new file mode 100644
index 00000000..bf3d099f
--- /dev/null
+++ b/src/components/pages/Turbo/TurboIndexPage/index.ts
@@ -0,0 +1 @@
+export { default } from "./TurboIndexPage";
diff --git a/src/components/pages/Turbo/TurboLayout.tsx b/src/components/pages/Turbo/TurboLayout.tsx
new file mode 100644
index 00000000..cf67a719
--- /dev/null
+++ b/src/components/pages/Turbo/TurboLayout.tsx
@@ -0,0 +1,37 @@
+import Head from "next/head";
+import theme from "rari-components/theme";
+import { Box, BoxProps, ChakraProvider, Fade } from "@chakra-ui/react";
+import { useRouter } from "next/router";
+
+type TurboLayoutProps = BoxProps;
+
+const TurboLayout: React.FC = (props) => {
+ const router = useRouter()
+ return (
+ <>
+
+ {/* Default title (can be overridden with another `title` tag). */}
+ Tribe Turbo
+
+ {/**
+ * Provide rari-components theme on all Turbo pages so we can import from
+ * the main `rari-components` entry point within Turbo pages (Turbo is the
+ * first project to use rari-components from the start — elsewhere on the
+ * app, we are replacing components incrementally and import from
+ * `rari-components/standalone` instead, which is less performant).
+ */}
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default TurboLayout;
diff --git a/src/components/pages/Turbo/TurboSafePage/BoostBar.tsx b/src/components/pages/Turbo/TurboSafePage/BoostBar.tsx
new file mode 100644
index 00000000..87a45ae4
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/BoostBar.tsx
@@ -0,0 +1,113 @@
+// Components
+import {
+ Heading,
+ Progress,
+ Text,
+ TokenIcon,
+ TokenSymbol,
+} from "rari-components";
+import { Avatar, Box, Flex, HStack, Image } from "@chakra-ui/react";
+import { useTurboSafe } from "context/TurboSafeContext";
+import { shortUsdFormatter } from "utils/bigUtils";
+import { toInt } from "utils/ethersUtils";
+import TurboEngineIcon from "components/shared/Icons/TurboEngineIcon";
+import { SimpleTooltip } from "components/shared/SimpleTooltip";
+import AppLink from "components/shared/AppLink";
+
+export const BoostBar: React.FC = () => {
+ const {
+ usdPricedSafe,
+ collateralTokenData: tokenData,
+ colorScheme,
+ isAtLiquidationRisk,
+ } = useTurboSafe();
+ const { boostedUSD, safeUtilization, maxBoostUSD, liquidationPriceUSD } =
+ usdPricedSafe ?? {};
+
+ return (
+
+
+
+ 0}
+ />
+ {/* */}
+
+ {shortUsdFormatter(boostedUSD ?? 0)} boost
+
+
+ / {shortUsdFormatter(maxBoostUSD ?? 0)} ({toInt(safeUtilization)}
+ %)
+
+
+
+ Liquidated when
+
+
+ e.stopPropagation()}
+ isExternal
+ >
+
+
+
+ = ${liquidationPriceUSD?.toFixed(2)}
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/pages/Turbo/TurboSafePage/OnboardingCard.tsx b/src/components/pages/Turbo/TurboSafePage/OnboardingCard.tsx
new file mode 100644
index 00000000..e24d44b9
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/OnboardingCard.tsx
@@ -0,0 +1,91 @@
+import { motion } from "framer-motion";
+import { Badge, Card, Heading, Text } from "rari-components";
+import { Box, HStack, ScaleFade, SlideFade, Stack } from "@chakra-ui/react";
+import { useTurboSafe } from "context/TurboSafeContext";
+import { ArrowLeftIcon, CheckIcon } from "@chakra-ui/icons";
+
+type OnboardingCardProps = {
+ openDepositModal: () => void;
+ onClickBoost: (strategyAddress: string) => void;
+};
+
+export const OnboardingCard: React.FC = ({
+ openDepositModal,
+ onClickBoost,
+}) => {
+ const { safe, collateralTokenData } = useTurboSafe();
+ const { collateralAmount, boostedAmount } = safe ?? {};
+
+ const hasCollateral = collateralAmount?.isZero() ? false : true;
+ const hasBoosted = boostedAmount?.isZero() ? false : true;
+
+ const _handleClickBoost = () => {
+ const pool8Fei = safe?.strategies[0].strategy ?? "";
+ if (!pool8Fei) {
+ return;
+ }
+ onClickBoost(pool8Fei);
+ };
+
+ return (
+
+
+ Getting Started
+
+ (hasCollateral ? null : openDepositModal())}
+ opacity={hasCollateral ? 0.25 : 1}
+ >
+
+ {hasCollateral ? : 1 }
+
+
+
+ Deposit {collateralTokenData?.symbol} collateral{hasCollateral ? "" : " →"}
+
+
+ Deposit collateral into your safe to enable boosting FEI
+
+
+
+
+
+ {hasBoosted ? : 2 }
+
+
+ Boost a pool {hasCollateral ? " →" : ""}
+
+ Click to boost your first pool, or scroll below for options
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/pages/Turbo/TurboSafePage/SafeStats.tsx b/src/components/pages/Turbo/TurboSafePage/SafeStats.tsx
new file mode 100644
index 00000000..f15248ee
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/SafeStats.tsx
@@ -0,0 +1,68 @@
+import { commify, formatEther, formatUnits } from "ethers/lib/utils";
+import { Statistic } from "rari-components";
+// Hooks
+import { smallStringUsdFormatter, smallUsdFormatter } from "utils/bigUtils";
+import { Stack } from "@chakra-ui/react";
+import { useUserFeiOwed } from "hooks/turbo/useUserFeiOwed";
+import { useTurboSafe } from "context/TurboSafeContext";
+
+export const SafeStats: React.FC = () => {
+ const { usdPricedSafe, netAPY, collateralTokenData, loading } =
+ useTurboSafe();
+
+ const [userFeiOwed] = useUserFeiOwed(usdPricedSafe);
+
+ return (
+
+
+
+
+
+
+
+ {/*
+ */}
+
+ );
+};
diff --git a/src/components/pages/Turbo/TurboSafePage/SafeStrategies.tsx b/src/components/pages/Turbo/TurboSafePage/SafeStrategies.tsx
new file mode 100644
index 00000000..112b429c
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/SafeStrategies.tsx
@@ -0,0 +1,174 @@
+import { Box, Button, Flex, HStack, Image } from "@chakra-ui/react";
+import {
+ Heading,
+ Link,
+ Table,
+ Text,
+ TokenIcon,
+ Tooltip,
+} from "rari-components";
+
+// Turbo
+import { USDPricedStrategy } from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+
+// Utils
+import { smallUsdFormatter } from "utils/bigUtils";
+import { formatEther, formatUnits } from "ethers/lib/utils";
+import { convertMantissaToAPY } from "utils/apyUtils";
+import { FEI } from "lib/turbo/utils/constants";
+import { useTurboSafe } from "context/TurboSafeContext";
+import { getStrategyFusePoolId } from "lib/turbo/fetchers/strategies/formatStrategyInfo";
+import TurboEngineIcon from "components/shared/Icons/TurboEngineIcon";
+
+export const SafeStrategies: React.FC<{
+ onClickBoost: (strategyAddress: string) => void;
+ onClickLess: (strategyAddress: string) => void;
+}> = ({ onClickBoost, onClickLess }) => {
+ const {
+ usdPricedSafe,
+ getERC4626StrategyData,
+ isAtLiquidationRisk,
+ colorScheme,
+ isUserAdmin
+ } = useTurboSafe();
+
+ const safeStrategies: USDPricedStrategy[] =
+ usdPricedSafe?.usdPricedStrategies ?? [];
+
+ const userPercent = usdPricedSafe?.tribeDAOFee
+ ? 1 - parseFloat(formatEther(usdPricedSafe?.tribeDAOFee))
+ : 1;
+
+ console.log({ safeStrategies });
+
+ // TODO (@sharad-s) Need to find a way to merge "active" and "inactive" strategies elegantly. Inactive Strategies have no strat address
+ return (
+ <>
+ {
+ const strategyData = getERC4626StrategyData[strat.strategy];
+ const poolId: string | undefined = getStrategyFusePoolId(
+ strategyData?.symbol
+ );
+ const grossApy = convertMantissaToAPY(
+ strategyData?.supplyRatePerBlock ?? 0,
+ 365
+ );
+ const netAPY = grossApy * userPercent;
+
+ const canLess = strat.boostAmountUSD > 0;
+
+ return {
+ key: strat.strategy,
+ items: [
+
+
+
+ {/* */}
+ {strategyData?.name}
+
+ ,
+
+
+
+ {strat.feiClaimableUSD > 0 || strat.boostAmountUSD > 0
+ ? smallUsdFormatter(strat.feiClaimableUSD)
+ : "-"}
+
+
+ ,
+
+
+ {netAPY.toFixed(2) + "%"}
+
+ ,
+
+
+
+
+ {strat.boostAmountUSD > 0
+ ? smallUsdFormatter(strat.boostAmountUSD)
+ : "-"}
+
+
+ ,
+
+
+ onClickBoost(strat.strategy)}
+ >
+
+ +
+
+
+
+
+
+ canLess ? onClickLess(strat.strategy) : null
+ }
+ >
+
+ —
+
+
+
+ ,
+ ],
+ };
+ })}
+ />
+ >
+ );
+};
diff --git a/src/components/pages/Turbo/TurboSafePage/TurboSafePage.tsx b/src/components/pages/Turbo/TurboSafePage/TurboSafePage.tsx
new file mode 100644
index 00000000..e5148e06
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/TurboSafePage.tsx
@@ -0,0 +1,292 @@
+import Head from "next/head";
+
+// Components
+import {
+ Button,
+ Divider,
+ Heading,
+ Link,
+ Text,
+ TokenIcon,
+ TokenSymbol,
+} from "rari-components";
+import {
+ Box,
+ Flex,
+ HStack,
+ Image,
+ Skeleton,
+ Stack,
+ StackProps,
+ useDisclosure,
+ VStack,
+} from "@chakra-ui/react";
+import { ChevronLeftIcon, InfoIcon, WarningTwoIcon } from "@chakra-ui/icons";
+import TurboLayout from "../TurboLayout";
+import { SafeStats } from "./SafeStats";
+import { SafeStrategies } from "./SafeStrategies";
+import ClaimInterestModal from "./modals/ClaimInterestModal";
+import DepositSafeCollateralModal from "./modals/DepositSafeCollateralModal/DepositSafeCollateralModal";
+import SafeInfoModal from "./modals/TurboInfoModal";
+import WithdrawSafeCollateralModal from "./modals/WithdrawSafeCollateralModal";
+import AtRiskOfLiquidationAlert from "../alerts/AtRiskOfLiquidationAlert";
+import { OnboardingCard } from "./OnboardingCard";
+import { BoostBar } from "./BoostBar";
+
+// Hooks
+import { useRouter } from "next/router";
+import { useState } from "react";
+
+// Context
+import { TurboSafeProvider, useTurboSafe } from "context/TurboSafeContext";
+import BoostMeAlert from "../alerts/BoostMeAlert";
+import { AdminAlert } from "components/shared/AdminAlert";
+import { SafeInteractionMode } from "hooks/turbo/useUpdatedSafeInfo";
+import BoostModal from "./modals/BoostModal";
+
+const TurboSafePage: React.FC = () => {
+ const {
+ safe,
+ usdPricedSafe,
+ collateralTokenData,
+ loading,
+ isAtLiquidationRisk,
+ shouldBoost,
+ isUserAdmin,
+ } = useTurboSafe();
+
+ const safeHealth = safe?.safeUtilization;
+
+ const {
+ isOpen: isDepositModalOpen,
+ onOpen: openDepositModal,
+ onClose: closeDepositModal,
+ } = useDisclosure();
+
+ const {
+ isOpen: isSafeModalOpen,
+ onOpen: openSafeModal,
+ onClose: closeSafeModal,
+ } = useDisclosure();
+
+ const {
+ isOpen: isWithdrawModalOpen,
+ onOpen: openWithdrawModal,
+ onClose: closeWithdrawModal,
+ } = useDisclosure();
+
+ const {
+ isOpen: isClaimInterestModalOpen,
+ onOpen: openClaimInterestModal,
+ onClose: closeClaimInterestModal,
+ } = useDisclosure();
+
+ const [hovered, setHovered] = useState(false);
+
+ /* Strategy + Boost Modal State */
+ const [activeStrategyAddress, setActiveStrategyAddress] = useState();
+
+ const {
+ isOpen: isBoostModalOpen,
+ onOpen: onBoostModalOpen,
+ onClose: onBoostModalClose,
+ } = useDisclosure();
+
+ const [boostMode, setBoostMode] = useState<
+ SafeInteractionMode.BOOST | SafeInteractionMode.LESS | undefined
+ >();
+
+ const onClickBoost = (strategyAddress: string) => {
+ setActiveStrategyAddress(strategyAddress);
+ setBoostMode(SafeInteractionMode.BOOST);
+ onBoostModalOpen();
+ };
+
+ const onClickLess = (strategyAddress: string) => {
+ setActiveStrategyAddress(strategyAddress);
+ setBoostMode(SafeInteractionMode.LESS);
+ onBoostModalOpen();
+ };
+
+ return (
+ <>
+
+ {collateralTokenData?.symbol} Safe | Tribe Turbo
+
+
+ {/* Modals */}
+
+
+
+
+ {!!activeStrategyAddress && !!boostMode && (
+
+ )}
+
+ setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ mb={4}
+ >
+
+
+ {" "}
+
+ All Safes
+
+
+
+
+
+ {/* Alerts */}
+ {isAtLiquidationRisk && (
+
+ )}
+
+
+
+
+
+
+
+ Safe
+
+
+
+ {!isUserAdmin && (
+
+
+ You are not the admin of this safe
+
+ )}
+
+
+
+
+
+ {safe?.boostedAmount.isZero() && !loading && (
+
+ )}
+
+
+
+
+
+
+
+ >
+ );
+};
+
+type ButtonsProps = StackProps & {
+ openDepositModal: any;
+ openWithdrawModal: any;
+ openClaimInterestModal: any;
+};
+
+export const Buttons: React.FC = ({
+ openDepositModal,
+ openWithdrawModal,
+ openClaimInterestModal,
+ ...restProps
+}) => {
+ const { loading, safe, totalFeiOwed, isUserAdmin } = useTurboSafe();
+ const hasDeposits = !!safe?.collateralAmount.gt(0);
+ const hasClaimable = totalFeiOwed.gt(0);
+
+ return (
+
+
+
+ Deposit Collateral
+
+
+
+ Withdraw Collateral
+
+
+
+ Claim Interest
+
+
+ );
+};
+
+export default () => {
+ const router = useRouter();
+ const { id } = router.query;
+ const safeAddress = id as string;
+
+ return (
+
+
+
+
+
+ );
+};
diff --git a/src/components/pages/Turbo/TurboSafePage/UpdatingStatisticsTable.tsx b/src/components/pages/Turbo/TurboSafePage/UpdatingStatisticsTable.tsx
new file mode 100644
index 00000000..b51dcfca
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/UpdatingStatisticsTable.tsx
@@ -0,0 +1,86 @@
+import { commify } from "ethers/lib/utils";
+import {
+ StatisticsTable,
+ StatisticsTableProps,
+ Tooltip,
+} from "rari-components";
+import { abbreviateAmount } from "utils/bigUtils";
+import { Box, Text } from "@chakra-ui/react";
+
+/**
+ * An `UpdatingStatistic` can either be a statistic with an initial value and
+ * a new value, or a regular statistic (i.e. one that would be passed to a bare
+ * ` `).
+ */
+type UpdatingStatistic =
+ | {
+ title: string;
+ tooltip: string;
+ initialValue: number;
+ newValue?: number | undefined;
+ }
+ | StatisticsTableProps["statistics"][number];
+
+type UpdatingStatisticsTableProps = Omit & {
+ statistics: UpdatingStatistic[];
+ colorScheme: string;
+};
+
+/**
+ * Composition of ` ` specifically for displaying large
+ * numeric statistics that update (use cases include displaying data related to
+ * depositing, withdrawing, etc.).
+ *
+ * The component handles abbreviating long values and setting up tooltips which
+ * display the full value on hover.
+ *
+ * Based on https://reactjs.org/docs/composition-vs-inheritance.html
+ */
+const UpdatingStatisticsTable: React.FC = ({
+ statistics,
+ colorScheme,
+ ...restProps
+}) => {
+ const updatingStatistics = statistics.map((it) => {
+ // If this item is a regular statistic, skip processing.
+ if (Array.isArray(it) || it === StatisticsTable.DIVIDER) {
+ return it;
+ }
+
+ const { title, tooltip, initialValue, newValue } = it;
+
+ const value = (
+
+ {!newValue ? (
+
+ {abbreviateAmount(initialValue)}
+
+ ) : (
+ <>
+
+ {abbreviateAmount(initialValue)}
+ {" "}
+
+ →{" "}
+
+ {abbreviateAmount(newValue)}
+
+
+ >
+ )}
+
+ );
+
+ const statistic: StatisticsTableProps["statistics"][number] = [
+ it.title,
+ value,
+ it.tooltip,
+ ];
+ return statistic;
+ });
+
+ return ;
+};
+
+export default UpdatingStatisticsTable;
+export type { UpdatingStatisticsTableProps };
diff --git a/src/components/pages/Turbo/TurboSafePage/index.tsx b/src/components/pages/Turbo/TurboSafePage/index.tsx
new file mode 100644
index 00000000..be31c1f1
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/index.tsx
@@ -0,0 +1 @@
+export { default } from "./TurboSafePage";
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/BoostModal/BoostModal.tsx b/src/components/pages/Turbo/TurboSafePage/modals/BoostModal/BoostModal.tsx
new file mode 100644
index 00000000..38a2dbdb
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/BoostModal/BoostModal.tsx
@@ -0,0 +1,261 @@
+// Hooks
+import { useRari } from "context/RariContext";
+// Utils
+import { formatEther, parseEther, parseUnits } from "ethers/lib/utils";
+import useSafeMaxAmount from "hooks/turbo/useSafeMaxAmount";
+import { FuseERC4626Strategy } from "hooks/turbo/useStrategyInfo";
+import {
+ SafeInteractionMode,
+ useUpdatedSafeInfo,
+} from "hooks/turbo/useUpdatedSafeInfo";
+import {
+ USDPricedStrategy,
+ USDPricedTurboSafe,
+} from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import { safeBoost, safeLess } from "lib/turbo/transactions/safe";
+// Turbo
+import { Modal } from "rari-components";
+import { useMemo, useState } from "react";
+import { handleGenericError } from "utils/errorHandling";
+import { useToast } from "@chakra-ui/react";
+import { MODAL_STEPS } from "./modalSteps";
+import { useQuery, useQueryClient } from "react-query";
+import { useBoostCapForStrategy } from "hooks/turbo/useBoostCapsForStrategies";
+import { useTurboSafe } from "context/TurboSafeContext";
+
+// Utils
+import { keyBy } from "lodash";
+import useUpdatedStrategyRate from "hooks/turbo/useUpdatedStrategyRate";
+import { getEthUsdPriceBN } from "esm/utils/getUSDPriceBN";
+import { constants } from "ethers";
+
+type BoostStrategyModalProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ activeStrategyAddress: string;
+ mode: SafeInteractionMode.BOOST | SafeInteractionMode.LESS;
+};
+
+export const BoostStrategyModal: React.FC = ({
+ isOpen,
+ onClose,
+ activeStrategyAddress,
+ mode,
+}) => {
+ const { provider, chainId } = useRari();
+ const toast = useToast();
+ const queryClient = useQueryClient();
+
+ /** Strategy ? **/
+ const { usdPricedSafe, getERC4626StrategyData } = useTurboSafe();
+ const safeStrategies: USDPricedStrategy[] =
+ usdPricedSafe?.usdPricedStrategies ?? [];
+
+ const { data: ethUSDPriceBN } = useQuery("ETH USD", async () =>
+ getEthUsdPriceBN()
+ );
+
+ // Construct a new object where safe strategies are indexed by address
+ // for O(1) access by address (order in the table is not necessarily
+ // stable).
+ const safeStrategiesByAddress = useMemo(
+ () => keyBy(safeStrategies, (strategy) => strategy.strategy),
+ [safeStrategies]
+ );
+
+ const strategy = !!activeStrategyAddress
+ ? safeStrategiesByAddress[activeStrategyAddress]
+ : undefined;
+
+ // For now, a bunch of other components rely on the assumption that the
+ // order of the strategies *stored in the source `safe.usdPricedStrategies`
+ // array* is stable. Since the order of items in the table is not
+ // necessarily stable, we need to translate from a table index to a source
+ // array index using `Array.prototype.findIndex`.
+ const activeStrategyIndex = useMemo(
+ () =>
+ safeStrategies.findIndex(
+ (strategy) => strategy.strategy === activeStrategyAddress
+ ),
+ [safeStrategies, activeStrategyAddress]
+ );
+
+ const erc4626Strategy = getERC4626StrategyData[activeStrategyAddress];
+
+ const [stepIndex, setStepIndex] = useState(0);
+ function incrementStepIndex() {
+ if (stepIndex + 1 !== MODAL_STEPS.length) {
+ setStepIndex(stepIndex + 1);
+ }
+ }
+ function resetStepIndex() {
+ setStepIndex(0);
+ }
+
+ const [amount, setAmount] = useState("0");
+ const [transacting, setTransacting] = useState(false);
+
+ const updatedSafe = useUpdatedSafeInfo({
+ mode,
+ safe: usdPricedSafe,
+ amount: parseUnits(!!amount ? amount : "0", 18),
+ strategyIndex: activeStrategyIndex,
+ });
+
+ const updatedRate = useUpdatedStrategyRate(
+ mode,
+ usdPricedSafe,
+ activeStrategyIndex,
+ parseUnits(!!amount ? amount : "0", 18)
+ );
+
+ const maxAmount = useSafeMaxAmount(usdPricedSafe, mode, activeStrategyIndex);
+
+ const isRiskyBoost =
+ mode === SafeInteractionMode.BOOST &&
+ !!updatedSafe?.safeUtilization?.gt(75);
+
+ // Boost cap for a vault
+ const [boostCap, totalBoosted] =
+ useBoostCapForStrategy(strategy?.strategy) ?? [];
+ const percentTotalBoosted =
+ boostCap && totalBoosted
+ ? (parseFloat(formatEther(totalBoosted)) /
+ parseFloat(formatEther(boostCap))) *
+ 100
+ : undefined;
+
+ // Form validation
+ const inputError: string | undefined = useMemo(() => {
+ const _amount = amount ? amount : "0";
+
+ switch (mode) {
+ case SafeInteractionMode.BOOST:
+ if (parseEther(_amount).gt(maxAmount)) {
+ return "You can't boost this much!";
+ }
+ if (!!boostCap && !!totalBoosted) {
+ if (
+ parseFloat(formatEther(totalBoosted)) + parseFloat(_amount) >
+ parseFloat(formatEther(boostCap))
+ ) {
+ return "Boost amount exceeds Cap for Vault";
+ }
+ }
+ if (
+ amount !== "0" &&
+ ethUSDPriceBN?.div(constants.WeiPerEther).gt(parseInt(_amount))
+ ) {
+ return "Minimum Boost must be > 1 ETH";
+ }
+
+ break;
+ case SafeInteractionMode.LESS:
+ if (parseEther(_amount).gt(maxAmount)) {
+ return "You can't less this much!";
+ }
+ break;
+ default:
+ return undefined;
+ }
+ }, [amount, maxAmount, strategy, mode, updatedSafe, ethUSDPriceBN]);
+
+ // Boost a strategy
+ const onClickBoost = async () => {
+ if (!usdPricedSafe?.safeAddress || !provider || !amount) return;
+ const amountBN = parseEther(amount);
+ const strategyAddress = strategy!.strategy;
+
+ try {
+ setTransacting(true);
+ const tx = await safeBoost(
+ usdPricedSafe.safeAddress,
+ strategyAddress,
+ amountBN,
+ //@ts-ignore
+ await provider.getSigner()
+ );
+ await tx.wait(1);
+ incrementStepIndex();
+ } catch (err) {
+ handleGenericError(err, toast);
+ } finally {
+ setTransacting(false);
+
+
+ setTimeout(() => {
+ onClose();
+ resetStepIndex();
+ }, 1500);
+ await queryClient.refetchQueries();
+ }
+ };
+
+ // Less a strategy
+ const onClickLess = async () => {
+ if (!usdPricedSafe?.safeAddress || !provider || !amount || !strategy)
+ return;
+
+ let amountBN = parseEther(amount);
+ amountBN = amountBN.gte(strategy.boostedAmount)
+ ? strategy.boostedAmount
+ : amountBN;
+
+ const lessingMax = strategy.boostedAmount.eq(amountBN);
+
+ try {
+ setTransacting(true);
+ const tx = await safeLess(
+ usdPricedSafe.safeAddress,
+ strategy.strategy,
+ amountBN,
+ lessingMax,
+ await provider.getSigner(),
+ chainId ?? 1
+ );
+ await tx.wait(1);
+ incrementStepIndex();
+ } catch (err) {
+ handleGenericError(err, toast);
+ } finally {
+ setTransacting(false);
+ await queryClient.refetchQueries();
+ }
+ };
+
+ const onClickMax = () => setAmount(formatEther(maxAmount));
+
+ return (
+
+ );
+};
+
+export default BoostStrategyModal;
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/BoostModal/index.ts b/src/components/pages/Turbo/TurboSafePage/modals/BoostModal/index.ts
new file mode 100644
index 00000000..957c3c8a
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/BoostModal/index.ts
@@ -0,0 +1 @@
+export { default } from "./BoostModal";
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/BoostModal/modalSteps.tsx b/src/components/pages/Turbo/TurboSafePage/modals/BoostModal/modalSteps.tsx
new file mode 100644
index 00000000..068d15b1
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/BoostModal/modalSteps.tsx
@@ -0,0 +1,306 @@
+import TurboEngineIcon from "components/shared/Icons/TurboEngineIcon";
+import { BigNumber, constants } from "ethers";
+import { commify, formatEther, parseEther } from "ethers/lib/utils";
+import { FuseERC4626Strategy } from "hooks/turbo/useStrategyInfo";
+import { SafeInteractionMode } from "hooks/turbo/useUpdatedSafeInfo";
+import {
+ USDPricedStrategy,
+ USDPricedTurboSafe,
+} from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import { FEI } from "lib/turbo/utils/constants";
+import {
+ Heading,
+ ModalProps,
+ StatisticsTable,
+ StatisticsTableProps,
+ Text,
+ TokenAmountInput,
+} from "rari-components";
+import { abbreviateAmount } from "utils/bigUtils";
+import { CheckCircleIcon } from "@chakra-ui/icons";
+import { Box, HStack, Image, Spinner, Stack, VStack } from "@chakra-ui/react";
+import { getSafeColor } from "context/TurboSafeContext";
+import { SimpleTooltip } from "components/shared/SimpleTooltip";
+
+type BoostModalCtx = {
+ incrementStepIndex(): void;
+ resetStepIndex(): void;
+ safe?: USDPricedTurboSafe;
+ updatedSafe?: USDPricedTurboSafe;
+ amount: string;
+ setAmount(newAmount: string): void;
+ transacting: boolean;
+ onClickBoost(): Promise;
+ onClickLess(): Promise;
+ onClose(): void;
+ onClickMax(snip?: boolean): void;
+ maxAmount: BigNumber;
+ mode: SafeInteractionMode.BOOST | SafeInteractionMode.LESS;
+ strategy: USDPricedStrategy | undefined;
+ erc4626Strategy: FuseERC4626Strategy | undefined;
+ inputError: string | undefined;
+ // Boost caps
+ boostCap: BigNumber | undefined;
+ totalBoosted: BigNumber | undefined;
+ percentTotalBoosted: number | undefined;
+ // Risk
+ isRiskyBoost: boolean;
+};
+
+type ModalStep = Omit, "ctx" | "isOpen" | "onClose">;
+
+const MODAL_STEP_1: ModalStep = {
+ title: ({ mode, erc4626Strategy }) => `${mode} strategy`,
+ children: ({
+ onClickMax,
+ setAmount,
+ amount,
+ safe,
+ updatedSafe,
+ mode,
+ maxAmount,
+ strategy,
+ boostCap,
+ totalBoosted,
+ erc4626Strategy,
+ }) => {
+ if (!safe) {
+ return null;
+ }
+
+ const statisticsLoading = !updatedSafe;
+
+ const totalBoostBalanceStatistic = statisticsLoading ? (
+
+ ) : (
+
+ {abbreviateAmount(safe?.boostedUSD)}{" "}
+
+ → {abbreviateAmount(updatedSafe?.boostedUSD)}
+
+
+ );
+
+ const safeUtilizationStatistic = statisticsLoading ? (
+
+ ) : (
+
+ {parseFloat(safe?.safeUtilization.toString() ?? "0").toFixed(2)}%{" "}
+
+ →{" "}
+ {parseFloat(updatedSafe?.safeUtilization.toString() ?? "0").toFixed(
+ 2
+ )}
+ %
+
+
+ );
+
+ const liquidationPriceStatistic = statisticsLoading ? (
+
+ ) : (
+
+ {abbreviateAmount(safe?.liquidationPriceUSD)}{" "}
+
+ → {abbreviateAmount(updatedSafe?.liquidationPriceUSD)}
+
+
+ );
+
+ const newTotalBoost =
+ totalBoosted?.add(parseEther(!!amount ? amount : "0")) ?? constants.Zero;
+
+ const boostCapStatistic = statisticsLoading ? (
+
+ ) : (
+
+
+
+ {abbreviateAmount(formatEther(newTotalBoost), false)}
+
+
+ /
+
+ {abbreviateAmount(formatEther(boostCap ?? constants.Zero), false)}
+
+
+ );
+
+ const statistics: StatisticsTableProps["statistics"] = [
+ [
+ "Total Boost Balance",
+ totalBoostBalanceStatistic,
+ "The maximum amount you can boost.",
+ ],
+ [
+ "Safe Utilization",
+ safeUtilizationStatistic,
+ "The maximum amount you can boost.",
+ ],
+ [
+ "Liquidation Price",
+ liquidationPriceStatistic,
+ "The liquidation price of your collateral",
+ ],
+ "DIVIDER",
+ [
+ "Strategy",
+ {erc4626Strategy?.name} ,
+ "The strategy you are boosting",
+ ],
+ ];
+
+ if (mode === SafeInteractionMode.BOOST) {
+ statistics.push([
+ "Boost Cap",
+ boostCapStatistic,
+ "Every strategy has its boost cap to as a safety measure. This the amount of FEI in total that the strategy can be boosted with.",
+ ]);
+ }
+
+ return (
+ <>
+
+ setAmount(amount ?? "0")}
+ tokenAddress={FEI}
+ onClickMax={() => onClickMax(true)}
+ />
+ onClickMax()}
+ // _hover={{ cursor: "pointer", opacity: 0.9 }}
+ // transition="opacity 0.2s ease"
+ >
+ {mode === SafeInteractionMode.BOOST
+ ? `You can boost ${commify(
+ parseFloat(formatEther(maxAmount)).toFixed(2)
+ )} FEI`
+ : `You can less ${commify(
+ parseFloat(formatEther(strategy!.boostedAmount)).toFixed(2)
+ )} FEI`}
+
+
+
+ {/* Boost cap for Strategy - show if amount is 50% over boost */}
+ {mode === SafeInteractionMode.BOOST &&
+ !!boostCap
+ ?.div(2)
+ ?.lt(
+ totalBoosted?.add(parseEther(amount || "0")) ?? constants.Zero
+ ) && (
+
+ {/* */}
+ {/*
+ Total Boost{" "}
+ {abbreviateAmount(
+ formatEther(
+ totalBoosted?.add(parseEther(amount || "0")) ??
+ constants.Zero
+ )
+ )}
+
+ ·
+ Approaching {abbreviateAmount(formatEther(boostCap))} */}
+
+ )}
+ {/* Claim Fees on Less All */}
+ {mode === SafeInteractionMode.LESS &&
+ strategy?.boostedAmount?.eq(parseEther(amount ? amount : "0")) && (
+
+
+
+ Lessing the entire strategy will also accrue{" "}
+
+ {parseFloat(formatEther(strategy.feiClaimable)) < 0.01
+ ? "<.01"
+ : parseFloat(formatEther(strategy.feiClaimable)).toFixed(
+ 2
+ )}{" "}
+ FEI
+ {" "}
+ to the Safe.
+
+
+ )}
+ >
+ );
+ },
+ buttons: ({
+ mode,
+ amount,
+ strategy,
+ transacting,
+ onClickBoost,
+ onClickLess,
+ incrementStepIndex,
+ inputError,
+ isRiskyBoost,
+ }) => [
+ {
+ children: !!inputError
+ ? inputError
+ : mode === SafeInteractionMode.LESS
+ ? strategy?.boostedAmount?.eq(parseEther(amount || "0"))
+ ? "Less and Accrue Rewards"
+ : "Less"
+ : isRiskyBoost
+ ? "Risky Boost!"
+ : "Boost",
+ variant: isRiskyBoost ? "danger" : "neutral",
+ loading: transacting,
+ disabled: !amount || !!inputError || transacting,
+ async onClick() {
+ try {
+ if (mode === "Boost") {
+ await onClickBoost();
+ } else {
+ await onClickLess();
+ }
+ } catch (err) {
+ throw err;
+ }
+ },
+ },
+ ],
+};
+
+const MODAL_STEP_2: ModalStep = {
+ children: ({ amount, mode }) => (
+
+
+
+ {commify(parseFloat(amount).toFixed(2))} FEI
+
+ Successfully {mode}ed
+
+
+
+ ),
+ buttons: ({ onClose, resetStepIndex }) => [
+ {
+ children: "Back to Safe",
+ variant: "neutral",
+ async onClick() {
+ resetStepIndex();
+ onClose();
+ },
+ },
+ ],
+};
+
+const MODAL_STEPS: ModalStep[] = [MODAL_STEP_1, MODAL_STEP_2];
+
+export { MODAL_STEPS };
+export type { BoostModalCtx };
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/ClaimInterestModal/ClaimInterestModal.tsx b/src/components/pages/Turbo/TurboSafePage/modals/ClaimInterestModal/ClaimInterestModal.tsx
new file mode 100644
index 00000000..0fee59b1
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/ClaimInterestModal/ClaimInterestModal.tsx
@@ -0,0 +1,115 @@
+// Hooks
+import { useRari } from "context/RariContext";
+import { useERC4626StrategiesDataAsMap } from "hooks/turbo/useStrategyInfo";
+import { useUserFeiOwed } from "hooks/turbo/useUserFeiOwed";
+import {
+ USDPricedStrategy,
+ USDPricedTurboSafe,
+} from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import { filterUsedStrategies } from "lib/turbo/fetchers/strategies/formatStrategyInfo";
+import { safeClaimAll } from "lib/turbo/transactions/claim";
+import { safeSweep } from "lib/turbo/transactions/safe";
+import { FEI } from "lib/turbo/utils/constants";
+// Turbo
+import { Modal } from "rari-components";
+import { useState } from "react";
+import { handleGenericError } from "utils/errorHandling";
+import { useToast } from "@chakra-ui/react";
+import { MODAL_STEPS } from "./modalSteps";
+
+// Todo - reuse Modal Prop Types
+type ClaimInterestModalProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ safe?: USDPricedTurboSafe;
+};
+
+export const ClaimInterestModal: React.FC = ({
+ isOpen,
+ onClose,
+ safe,
+}) => {
+ const { address, chainId, provider } = useRari();
+
+ const toast = useToast();
+
+ const [claiming, setClaiming] = useState(false);
+
+ const [stepIndex, setStepIndex] = useState(0);
+ function incrementStepIndex() {
+ if (stepIndex + 1 !== MODAL_STEPS.length) {
+ setStepIndex(stepIndex + 1);
+ }
+ }
+ function resetStepIndex() {
+ setStepIndex(0);
+ }
+
+ const activeStrategies: USDPricedStrategy[] = filterUsedStrategies(
+ safe?.usdPricedStrategies ?? []
+ ) as USDPricedStrategy[];
+ const strategyData = useERC4626StrategiesDataAsMap(
+ activeStrategies.map((strat) => strat.strategy)
+ );
+
+ const [totalClaimable, claimableFromStrategies, safeFeiBalance] =
+ useUserFeiOwed(safe);
+
+ const onClickClaimInterest = async () => {
+ if (!safe || !chainId || !activeStrategies) return;
+ try {
+ setClaiming(true);
+
+ // If There is nothing claimable from strats, just sweep the safe. Else slurp all + sweep
+ if (claimableFromStrategies.isZero()) {
+ const tx = await safeSweep(
+ safe.safeAddress,
+ address,
+ FEI,
+ safeFeiBalance,
+ chainId,
+ await provider.getSigner()
+ );
+ await tx.wait(1);
+ incrementStepIndex();
+ } else {
+ const tx = await safeClaimAll({
+ safeAddress: safe.safeAddress,
+ strategies: activeStrategies.map((s) => s.strategy),
+ recipient: address,
+ signer: await provider.getSigner(),
+ chainID: chainId,
+ });
+ await tx.wait(1);
+ incrementStepIndex();
+ }
+ } catch (err) {
+ handleGenericError(err, toast);
+ } finally {
+ setClaiming(false);
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default ClaimInterestModal;
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/ClaimInterestModal/index.ts b/src/components/pages/Turbo/TurboSafePage/modals/ClaimInterestModal/index.ts
new file mode 100644
index 00000000..5d555dc2
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/ClaimInterestModal/index.ts
@@ -0,0 +1 @@
+export { default } from "./ClaimInterestModal";
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/ClaimInterestModal/modalSteps.tsx b/src/components/pages/Turbo/TurboSafePage/modals/ClaimInterestModal/modalSteps.tsx
new file mode 100644
index 00000000..e999b608
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/ClaimInterestModal/modalSteps.tsx
@@ -0,0 +1,115 @@
+import { BigNumber } from "ethers";
+import { commify, formatEther } from "ethers/lib/utils";
+import { StrategyInfosMap } from "hooks/turbo/useStrategyInfo";
+import {
+ USDPricedStrategy,
+ USDPricedTurboSafe,
+} from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import { FEI } from "lib/turbo/utils/constants";
+import {
+ Heading,
+ ModalProps,
+ StatisticsTable,
+ Text,
+ TokenIcon,
+} from "rari-components";
+import { CheckCircleIcon } from "@chakra-ui/icons";
+import { Box, HStack, Stack, VStack } from "@chakra-ui/react";
+
+type ClaimInterestCtx = {
+ incrementStepIndex(): void;
+ resetStepIndex(): void;
+ claiming: boolean;
+ onClickClaimInterest(): void;
+ onClose(): void;
+ totalClaimable: BigNumber;
+ claimableFromStrategies: BigNumber;
+ safeFeiBalance: BigNumber;
+ activeStrategies: USDPricedStrategy[];
+ strategyData: StrategyInfosMap;
+ safe?: USDPricedTurboSafe;
+};
+
+type ModalStep = Omit<
+ ModalProps,
+ "ctx" | "isOpen" | "onClose"
+>;
+
+const MODAL_STEP_1: ModalStep = {
+ children: ({
+ totalClaimable,
+ claimableFromStrategies,
+ safeFeiBalance,
+ activeStrategies,
+ strategyData,
+ safe,
+ }) => (
+
+ Claim Interest
+
+
+
+ {commify(parseFloat(formatEther(totalClaimable)).toFixed(2))} FEI
+
+
+
+
+ ),
+ buttons: ({ claiming, onClickClaimInterest }) => [
+ {
+ children: claiming ? "Claiming..." : "Claim rewards",
+ variant: "neutral",
+ loading: claiming,
+ async onClick() {
+ await onClickClaimInterest();
+ },
+ },
+ ],
+};
+
+const MODAL_STEP_2: ModalStep = {
+ children: ({ totalClaimable }) => (
+
+
+
+
+ {commify(parseFloat(formatEther(totalClaimable)).toFixed(2))} FEI
+
+
+ Successfully claimed
+
+
+
+ ),
+ buttons: ({ resetStepIndex, onClose }) => [
+ {
+ children: "Back to Safe",
+ variant: "neutral",
+ async onClick() {
+ resetStepIndex();
+ onClose();
+ },
+ },
+ ],
+};
+
+const MODAL_STEPS: ModalStep[] = [MODAL_STEP_1, MODAL_STEP_2];
+
+export { MODAL_STEPS };
+export type { ClaimInterestCtx };
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/DepositSafeCollateralModal/DepositSafeCollateralModal.tsx b/src/components/pages/Turbo/TurboSafePage/modals/DepositSafeCollateralModal/DepositSafeCollateralModal.tsx
new file mode 100644
index 00000000..1680d259
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/DepositSafeCollateralModal/DepositSafeCollateralModal.tsx
@@ -0,0 +1,168 @@
+// Hooks
+import { useRari } from "context/RariContext";
+// Utils
+import { formatEther, parseEther, parseUnits } from "ethers/lib/utils";
+import { useBalanceOf } from "hooks/useBalanceOf";
+// Turbo
+import { Modal } from "rari-components";
+import { useMemo, useState } from "react";
+import { handleGenericError } from "utils/errorHandling";
+import { useToast } from "@chakra-ui/react";
+import { MODAL_STEPS } from "./modalSteps";
+import { safeDeposit } from "lib/turbo/transactions/safe";
+import { checkAllowanceAndApprove } from "utils/erc20Utils";
+
+// import useHasApproval from "hooks/useHasApproval";
+import {
+ SafeInteractionMode,
+ useUpdatedSafeInfo,
+} from "hooks/turbo/useUpdatedSafeInfo";
+import { USDPricedTurboSafe } from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import { fetchMaxSafeAmount } from "lib/turbo/utils/fetchMaxSafeAmount";
+import { MAX_APPROVAL_AMOUNT } from "utils/tokenUtils";
+import useHasApproval from "hooks/useHasApproval";
+import { useQueryClient } from "react-query";
+import useSafeMaxAmount from "hooks/turbo/useSafeMaxAmount";
+import { constants } from "ethers";
+import { useTurboSafe } from "context/TurboSafeContext";
+
+// Todo - reuse Modal Prop Types
+type DepositSafeCollateralModalProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ safe: USDPricedTurboSafe | undefined;
+};
+
+export const DepositSafeCollateralModal: React.FC<
+ DepositSafeCollateralModalProps
+> = ({ isOpen, onClose, safe }) => {
+ const { address, provider, chainId } = useRari();
+ const { collateralTokenData } = useTurboSafe();
+ const toast = useToast();
+ const queryClient = useQueryClient();
+
+ const [stepIndex, setStepIndex] = useState(0);
+ function incrementStepIndex() {
+ if (stepIndex + 1 !== MODAL_STEPS.length) {
+ setStepIndex(stepIndex + 1);
+ }
+ }
+ function resetStepIndex() {
+ setStepIndex(0);
+ }
+
+ const [depositAmount, setDepositAmount] = useState("");
+ const [depositing, setDepositing] = useState(false);
+
+ const collateralBalance = useBalanceOf(address, safe?.collateralAsset);
+ const [approving, setApproving] = useState(false);
+
+ const hasApproval = useHasApproval(
+ safe?.collateralAsset,
+ safe?.safeAddress,
+ depositAmount
+ );
+
+ const updatedSafe = useUpdatedSafeInfo({
+ mode: SafeInteractionMode.DEPOSIT,
+ safe,
+ amount: parseUnits(!!depositAmount ? depositAmount : "0", 18),
+ });
+
+ const handleApproveAndDeposit = async () => {
+ if (!hasApproval) {
+ await approve();
+ }
+ await onClickDeposit();
+ };
+
+ const approve = async () => {
+ setApproving(true);
+ const safeAddress = safe?.safeAddress;
+ if (!safeAddress) return;
+ try {
+ setApproving(true);
+ const tx = await checkAllowanceAndApprove(
+ provider.getSigner(),
+ address,
+ safeAddress,
+ safe.collateralAsset,
+ MAX_APPROVAL_AMOUNT
+ );
+ await tx.wait(1);
+ } catch (err) {
+ throw err;
+ } finally {
+ setApproving(false);
+ }
+ };
+
+ const onClickDeposit = async () => {
+ if (!depositAmount || !safe) return;
+ const depositAmountBN = parseEther(depositAmount);
+ const { safeAddress, collateralAsset } = safe;
+
+ try {
+ setDepositing(true);
+ const tx = await safeDeposit(
+ safeAddress,
+ address,
+ depositAmountBN,
+ provider.getSigner()
+ );
+ await tx.wait(1);
+ incrementStepIndex();
+ } catch (err) {
+ throw err;
+ } finally {
+ setDepositing(false);
+ await queryClient.refetchQueries();
+ }
+ };
+
+ const maxAmount = useSafeMaxAmount(safe, SafeInteractionMode.DEPOSIT);
+
+ const onClickMax = async () => {
+ try {
+ setDepositAmount(formatEther(maxAmount));
+ } catch (err) {
+ handleGenericError(err, toast);
+ }
+ };
+
+ // Form validation
+ const inputError: string | undefined = useMemo(() => {
+ const _amount = !!depositAmount ? depositAmount : "0";
+ const _amountBN = parseUnits(_amount, collateralTokenData?.decimals ?? 18);
+ //
+ if (_amountBN.gt(maxAmount)) {
+ return "Can't deposit this much!";
+ }
+ }, [depositAmount, maxAmount]);
+
+ return (
+
+ );
+};
+
+export default DepositSafeCollateralModal;
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/DepositSafeCollateralModal/index.ts b/src/components/pages/Turbo/TurboSafePage/modals/DepositSafeCollateralModal/index.ts
new file mode 100644
index 00000000..1ae3e984
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/DepositSafeCollateralModal/index.ts
@@ -0,0 +1 @@
+export { default } from "./DepositSafeCollateralModal";
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/DepositSafeCollateralModal/modalSteps.tsx b/src/components/pages/Turbo/TurboSafePage/modals/DepositSafeCollateralModal/modalSteps.tsx
new file mode 100644
index 00000000..42f3fe6c
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/DepositSafeCollateralModal/modalSteps.tsx
@@ -0,0 +1,181 @@
+import { BigNumber } from "ethers";
+import { commify, formatEther } from "ethers/lib/utils";
+import { USDPricedTurboSafe } from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import {
+ Heading,
+ ModalProps,
+ Text,
+ TokenAmountInput,
+ TokenSymbol,
+} from "rari-components";
+import { Box, Stack, VStack } from "@chakra-ui/react";
+import { CheckCircleIcon } from "@chakra-ui/icons";
+import UpdatingStatisticsTable from "../../UpdatingStatisticsTable";
+import { getSafeColor } from "context/TurboSafeContext";
+
+type DepositSafeCollateralCtx = {
+ incrementStepIndex(): void;
+ resetStepIndex(): void;
+ safe?: USDPricedTurboSafe;
+ updatedSafe?: USDPricedTurboSafe;
+ hasApproval: boolean;
+ approving: boolean;
+ depositAmount: string;
+ setDepositAmount(newDepositAmount: string): void;
+ depositing: boolean;
+ collateralBalance: BigNumber;
+ onClickMax(): Promise;
+ onClose(): void;
+ inputError: string | undefined;
+ handleApproveAndDeposit(): Promise;
+};
+
+type ModalStep = Omit<
+ ModalProps,
+ "ctx" | "isOpen" | "onClose"
+>;
+
+const MODAL_STEP_1: ModalStep = {
+ title: "Deposit Collateral",
+ subtitle: "Collateralizing is required before boosting pools.",
+ children: ({
+ onClickMax,
+ setDepositAmount,
+ safe,
+ updatedSafe,
+ collateralBalance,
+ depositAmount,
+ }) => {
+ if (!safe) {
+ return null;
+ }
+
+ const safeUtilizationValue = (colorScheme: string) =>
+ depositAmount === "" || parseInt(depositAmount) == 0 ? (
+ parseFloat(safe?.safeUtilization.toString() ?? "0").toFixed(2) + "%"
+ ) : (
+
+ {parseFloat(safe?.safeUtilization.toString() ?? "0").toFixed(2) + "%"}{" "}
+
+ →{" "}
+
+ {parseFloat(
+ updatedSafe?.safeUtilization.toString() ?? "0"
+ ).toFixed(2) + "%"}
+
+
+
+ );
+
+ return (
+ <>
+
+ setDepositAmount(amount ?? "0")}
+ tokenAddress={safe.collateralAsset}
+ onClickMax={onClickMax}
+ />
+
+ Balance: {commify(formatEther(collateralBalance))}{" "}
+
+
+
+
+ >
+ );
+ },
+ buttons: ({
+ approving,
+ depositing,
+ hasApproval,
+ depositAmount,
+ inputError,
+ handleApproveAndDeposit,
+ }) => [
+ {
+ children: !!inputError
+ ? inputError
+ : approving
+ ? "Approving..."
+ : depositing
+ ? "Depositing..."
+ : !hasApproval
+ ? "Approve Safe"
+ : "Deposit",
+ variant: "neutral",
+ loading: approving || depositing,
+ disabled:
+ approving ||
+ depositing ||
+ !depositAmount ||
+ parseFloat(depositAmount) == 0 ||
+ !!inputError,
+ async onClick() {
+ await handleApproveAndDeposit();
+ },
+ },
+ ],
+};
+
+const MODAL_STEP_2: ModalStep = {
+ children: ({ depositAmount, updatedSafe }) => (
+
+
+
+
+ {commify(depositAmount)}{" "}
+ {updatedSafe ? (
+
+ ) : null}
+
+
+ Successfully deposited
+
+
+
+ ),
+ buttons: ({ onClose, resetStepIndex }) => [
+ {
+ children: "Back to Safe",
+ variant: "neutral",
+ async onClick() {
+ resetStepIndex();
+ onClose();
+ },
+ },
+ ],
+};
+
+const MODAL_STEPS: ModalStep[] = [MODAL_STEP_1, MODAL_STEP_2];
+
+export { MODAL_STEPS };
+export type { DepositSafeCollateralCtx };
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/TurboInfoModal/TurboInfoModal.tsx b/src/components/pages/Turbo/TurboSafePage/modals/TurboInfoModal/TurboInfoModal.tsx
new file mode 100644
index 00000000..729e3067
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/TurboInfoModal/TurboInfoModal.tsx
@@ -0,0 +1,66 @@
+import { SimpleGrid } from "@chakra-ui/react";
+import { InfoPairs } from "components/pages/Fuse/Modals/PluginModal/PluginRewardsModal";
+import { useRari } from "context/RariContext";
+import { constants } from "ethers";
+import { commify, formatEther } from "ethers/lib/utils";
+import useCollateralBoostCap from "hooks/turbo/useCollateralBoostCap";
+import { useSafeOwner } from "hooks/turbo/useSafeOwner";
+import { useBalanceOf } from "hooks/useBalanceOf";
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+import { FEI } from "lib/turbo/utils/constants";
+import { Modal, Statistic } from "rari-components";
+
+export const SafeInfoModal: React.FC<{
+ isOpen: any;
+ onClose: any;
+ safe: SafeInfo | undefined;
+}> = ({ isOpen, onClose, safe }) => {
+ const { address } = useRari();
+ const safeBalanceOfFei = useBalanceOf(safe?.safeAddress, FEI);
+ const userBalanceOfFei = useBalanceOf(address, FEI);
+
+ const collateralBoostCap = useCollateralBoostCap(safe?.collateralAsset);
+ const owner = useSafeOwner(safe?.safeAddress);
+
+ return (
+
+
+
+ {/* */}
+
+
+
+
+
+
+
+ );
+};
+
+export default SafeInfoModal;
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/TurboInfoModal/index.ts b/src/components/pages/Turbo/TurboSafePage/modals/TurboInfoModal/index.ts
new file mode 100644
index 00000000..004b1cfb
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/TurboInfoModal/index.ts
@@ -0,0 +1,2 @@
+import TurboInfoModal from './TurboInfoModal'
+export default TurboInfoModal
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/WithdrawSafeCollateralModal/WithdrawSafeCollateralModal.tsx b/src/components/pages/Turbo/TurboSafePage/modals/WithdrawSafeCollateralModal/WithdrawSafeCollateralModal.tsx
new file mode 100644
index 00000000..ca4cfb32
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/WithdrawSafeCollateralModal/WithdrawSafeCollateralModal.tsx
@@ -0,0 +1,112 @@
+// Hooks
+import { useRari } from "context/RariContext";
+// Turbo
+import { Modal } from "rari-components";
+import { useMemo, useState } from "react";
+import { handleGenericError } from "utils/errorHandling";
+import { useToast } from "@chakra-ui/react";
+import { MODAL_STEPS } from "./modalSteps";
+import { fetchMaxSafeAmount } from "lib/turbo/utils/fetchMaxSafeAmount";
+import { SafeInteractionMode, useUpdatedSafeInfo } from "hooks/turbo/useUpdatedSafeInfo";
+import { formatEther, parseEther, parseUnits } from "ethers/lib/utils";
+import { USDPricedTurboSafe } from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import { constants } from "ethers";
+import useSafeMaxAmount from "hooks/turbo/useSafeMaxAmount";
+import { safeWithdraw } from "lib/turbo/transactions/safe";
+
+// Todo - reuse Modal Prop Types
+type WithdrawSafeCollateralModalProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ safe: USDPricedTurboSafe | undefined;
+};
+
+export const WithdrawSafeCollateralModal: React.FC<
+ WithdrawSafeCollateralModalProps
+> = ({ isOpen, onClose, safe }) => {
+ const { address, provider, chainId } = useRari();
+ const toast = useToast();
+
+ const [withdrawalAmount, setWithdrawalAmount] = useState("10");
+ const [withdrawing, setWithdrawing] = useState(false);
+
+ const [stepIndex, setStepIndex] = useState(0);
+ function incrementStepIndex() {
+ if (stepIndex + 1 !== MODAL_STEPS.length) {
+ setStepIndex(stepIndex + 1);
+ }
+ }
+
+ const updatedSafe = useUpdatedSafeInfo(
+ {
+ mode: SafeInteractionMode.WITHDRAW,
+ safe,
+ amount: parseUnits(!!withdrawalAmount ? withdrawalAmount : "0", 18)
+ }
+ )
+
+ const maxAmount = useSafeMaxAmount(safe, SafeInteractionMode.WITHDRAW)
+
+ const onClickWithdraw = async () => {
+ if (!safe) return
+ try {
+ setWithdrawing(true);
+ const tx = await safeWithdraw(safe.safeAddress, address, parseEther(withdrawalAmount), await provider.getSigner())
+ await tx.wait(1)
+ } catch (err) {
+ handleGenericError(err, toast);
+ } finally {
+ setWithdrawing(false);
+ }
+ };
+
+ const onClickMax = async () => {
+ try {
+ const maxAmount = await fetchMaxSafeAmount(
+ provider,
+ SafeInteractionMode.WITHDRAW,
+ address,
+ safe,
+ chainId ?? 1
+ )
+ setWithdrawalAmount(formatEther(maxAmount ?? 0))
+ } catch (err) {
+ handleGenericError(err, toast)
+ }
+ }
+
+ // Form validation
+ const inputError: string | undefined = useMemo(() => {
+ const amount = withdrawalAmount ? withdrawalAmount : '0'
+ if (parseUnits(amount, 18).gt(maxAmount)) {
+ return "Cannot withdraw this much!"
+ }
+ if (updatedSafe && updatedSafe.collateralAmount.lt(0)) {
+ return "Cannot withdraw this much!"
+ }
+ }, [maxAmount, updatedSafe, withdrawalAmount])
+
+
+ return (
+
+ );
+};
+
+export default WithdrawSafeCollateralModal;
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/WithdrawSafeCollateralModal/index.ts b/src/components/pages/Turbo/TurboSafePage/modals/WithdrawSafeCollateralModal/index.ts
new file mode 100644
index 00000000..e1b71841
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/WithdrawSafeCollateralModal/index.ts
@@ -0,0 +1 @@
+export { default } from "./WithdrawSafeCollateralModal";
diff --git a/src/components/pages/Turbo/TurboSafePage/modals/WithdrawSafeCollateralModal/modalSteps.tsx b/src/components/pages/Turbo/TurboSafePage/modals/WithdrawSafeCollateralModal/modalSteps.tsx
new file mode 100644
index 00000000..2e4230cf
--- /dev/null
+++ b/src/components/pages/Turbo/TurboSafePage/modals/WithdrawSafeCollateralModal/modalSteps.tsx
@@ -0,0 +1,138 @@
+import { Box, VStack } from "@chakra-ui/react";
+import { getSafeColor } from "context/TurboSafeContext";
+import { BigNumber } from "ethers";
+import { formatEther } from "ethers/lib/utils";
+import { USDPricedTurboSafe } from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import {
+ ModalProps,
+ Text,
+ TokenAmountInput,
+ TokenSymbol,
+} from "rari-components";
+import UpdatingStatisticsTable from "../../UpdatingStatisticsTable";
+
+type WithdrawSafeCollateralCtx = {
+ incrementStepIndex(): void;
+ withdrawalAmount: string;
+ setWithdrawalAmount(newWithdrawalAmount: string): void;
+ withdrawing: boolean;
+ onClickWithdraw(): Promise;
+ onClose(): void;
+ onClickMax(): Promise;
+ maxAmount: BigNumber;
+ safe?: USDPricedTurboSafe;
+ updatedSafe?: USDPricedTurboSafe;
+ inputError?: string | undefined;
+};
+
+type ModalStep = Omit<
+ ModalProps,
+ "ctx" | "isOpen" | "onClose"
+>;
+
+const MODAL_STEP_1: ModalStep = {
+ title: "Withdraw Collateral",
+ subtitle: "Withdraw collateral from this safe.",
+ children: ({
+ setWithdrawalAmount,
+ withdrawalAmount,
+ onClickMax,
+ safe,
+ updatedSafe,
+ maxAmount,
+ }) => {
+ if (!safe) {
+ return null;
+ }
+
+ const safeUtilizationValue = (colorScheme: string) =>
+ withdrawalAmount === "" || parseInt(withdrawalAmount) == 0 ? (
+ parseFloat(safe?.safeUtilization.toString() ?? "0").toFixed(2) + "%"
+ ) : (
+
+ {parseFloat(safe?.safeUtilization.toString() ?? "0").toFixed(2) + "%"}{" "}
+
+ →{" "}
+
+ {parseFloat(
+ updatedSafe?.safeUtilization.toString() ?? "0"
+ ).toFixed(2) + "%"}
+
+
+
+ );
+
+ return (
+ <>
+
+ setWithdrawalAmount(amount || "0")}
+ tokenAddress={safe.collateralAsset}
+ onClickMax={onClickMax}
+ />
+
+ You can withdraw {formatEther(maxAmount)} {" "}
+
+
+
+
+ >
+ );
+ },
+ buttons: ({
+ withdrawing,
+ onClickWithdraw,
+ incrementStepIndex,
+ onClose,
+ inputError,
+ }) => [
+ {
+ children: inputError
+ ? inputError
+ : withdrawing
+ ? "Withdrawing..."
+ : "Withdraw",
+ variant: "neutral",
+ loading: withdrawing,
+ disabled: !!inputError || withdrawing,
+ async onClick() {
+ await onClickWithdraw();
+ onClose();
+ },
+ },
+ ],
+};
+
+const MODAL_STEPS: ModalStep[] = [MODAL_STEP_1];
+
+export { MODAL_STEPS };
+export type { WithdrawSafeCollateralCtx };
diff --git a/src/components/pages/Turbo/alerts/AtRiskOfLiquidationAlert.tsx b/src/components/pages/Turbo/alerts/AtRiskOfLiquidationAlert.tsx
new file mode 100644
index 00000000..ead40eb5
--- /dev/null
+++ b/src/components/pages/Turbo/alerts/AtRiskOfLiquidationAlert.tsx
@@ -0,0 +1,35 @@
+import { BigNumber } from 'ethers';
+import { motion } from "framer-motion";
+import React from 'react'
+import {
+ Alert,
+ AlertIcon,
+ Box,
+} from "@chakra-ui/react"
+import { Text } from 'rari-components';
+import theme from 'rari-components/theme';
+
+const AtRiskOfLiquidationAlert: React.FC<{ safeHealth: BigNumber | undefined }> = ({
+ safeHealth,
+}) => {
+ return (
+
+
+
+
+ With a {safeHealth?.toNumber()}% utilization, you are at
+ liquidation risk. Please deposit more collateral , or unboost .
+
+
+
+
+ );
+};
+
+
+export default AtRiskOfLiquidationAlert
+
diff --git a/src/components/pages/Turbo/alerts/BoostMeAlert.tsx b/src/components/pages/Turbo/alerts/BoostMeAlert.tsx
new file mode 100644
index 00000000..1a4b20ff
--- /dev/null
+++ b/src/components/pages/Turbo/alerts/BoostMeAlert.tsx
@@ -0,0 +1,34 @@
+import { BigNumber } from 'ethers';
+import { motion } from "framer-motion";
+import React from 'react'
+import {
+ Alert,
+ AlertIcon,
+ Box,
+} from "@chakra-ui/react"
+import { Text } from 'rari-components';
+import theme from 'rari-components/theme';
+
+const BoostMeAlert: React.FC<{ safeHealth: BigNumber | undefined }> = ({
+ safeHealth,
+}) => {
+ return (
+
+
+
+
+ Looks like you have collateral deposited but a {safeHealth?.toNumber()}% utilization. Boost some strategies!
+
+
+
+
+ );
+};
+
+
+export default BoostMeAlert
+
diff --git a/src/components/shared/AdminAlert.tsx b/src/components/shared/AdminAlert.tsx
index 0f7c536c..02f6461e 100644
--- a/src/components/shared/AdminAlert.tsx
+++ b/src/components/shared/AdminAlert.tsx
@@ -29,7 +29,7 @@ export const AdminAlert = ({
return (
{name}
@@ -84,7 +85,6 @@ export const HeaderLink = ({
href={route}
whiteSpace="nowrap"
className={noUnderline ? "no-underline" : ""}
- borderRadius="sm"
{...getHeaderLinkStyleProps(isOnThisRoute)}
>
{name}
@@ -95,7 +95,8 @@ export const HeaderLink = ({
const splitHairs = (path: string) =>
path
.replace(/\/+$/, "")
- .split("/")
+ // Split on `/` or `?`
+ .split(/\/|\?/)
.filter((str) => !!str);
// Dropdown Header Link
@@ -134,7 +135,6 @@ export const DropDownLink = ({
as={Button}
rightIcon={ }
{...getHeaderLinkStyleProps(isOnThisRoute)}
- borderRadius="sm"
>
{name}
@@ -159,7 +159,7 @@ const DropdownItem = ({ link }: { link: DropDownLinkInterface }) => {
return (
- {name}
+ {name}
);
@@ -169,7 +169,13 @@ const DropdownMenuGroup = ({ menuItem }: { menuItem: MenuItemInterface }) => {
return (
<>
-
+
{menuItem.links?.map((link, i) => (
))}
diff --git a/src/components/shared/Header2/NewHeader.tsx b/src/components/shared/Header2/NewHeader.tsx
index 94b01973..e94c9dfb 100644
--- a/src/components/shared/Header2/NewHeader.tsx
+++ b/src/components/shared/Header2/NewHeader.tsx
@@ -1,5 +1,6 @@
import { PixelSize, Row } from "lib/chakraUtils";
import { Box, Flex, Icon, Spacer, useDisclosure } from "@chakra-ui/react";
+import { Link } from "rari-components/standalone";
// Components
import DashboardBox, { DASHBOARD_BOX_SPACING } from "../DashboardBox";
@@ -54,9 +55,9 @@ export const NewHeader = () => {
mb={5}
// bg="pink"
>
-
+
-
+
{isMobile ? null : (
{
+ const prefersReducedMotion = usePrefersReducedMotion();
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default TurboEngineIcon;
diff --git a/src/components/shared/Layout/Layout.tsx b/src/components/shared/Layout/Layout.tsx
index 9d2be4b8..eb58f033 100644
--- a/src/components/shared/Layout/Layout.tsx
+++ b/src/components/shared/Layout/Layout.tsx
@@ -3,23 +3,34 @@
import { useRari } from "context/RariContext";
import { ChainID } from "esm/utils/networks";
import { Column } from "lib/chakraUtils";
+import { useRouter } from "next/router";
+import theme from "rari-components/theme";
import { useMemo, useState, useEffect } from "react";
import NewHeader from "../Header2/NewHeader";
import Footer from "./Footer";
+const { darkmatte } = theme.colors;
const Layout = ({ children }) => {
const { chainId } = useRari()
+ const { pathname } = useRouter();
const bg = useMemo(() => {
+ // Use the rari-components theme background color on `/turbo` pages to match
+ // with the other rari-components colors (Turbo is the first project to use
+ // rari-components from the start — elsewhere on the app, we are replacing
+ // components incrementally and the original `black` background is more
+ // appropriate).
+ const baseBg = pathname.startsWith("/turbo") ? darkmatte : "black";
+
switch (chainId) {
case ChainID.ARBITRUM:
- return "linear-gradient(45deg, hsla(0, 0%, 0%, 1) 76%, hsla(220, 47%, 36%, 0.9) 100%);"
+ return `linear-gradient(45deg, ${baseBg} 76%, hsla(220, 47%, 36%, 0.9) 100%)`;
default:
- return 'black'
+ return baseBg
}
- }, [chainId])
+ }, [chainId, pathname])
const [showShader, setShowShader] = useState(false);
diff --git a/src/constants/homepage.ts b/src/constants/homepage.ts
index 19c82f76..0ed0a328 100644
--- a/src/constants/homepage.ts
+++ b/src/constants/homepage.ts
@@ -95,6 +95,7 @@ export enum HomepageOpportunityType {
Arbitrum,
Connext,
PegExchanger,
+ Turbo,
}
export interface HomepageOpportunity {
@@ -127,12 +128,13 @@ export const HOMEPAGE_OPPORTUNIES: HomepageOpportunity[] = [
icon: "/static/icons/arbitrum_icon_glow.png",
},
{
- type: HomepageOpportunityType.PegExchanger,
- title: "Swap RGT for TRIBE",
- subtitle: "Swap your RGT for TRIBE post-merge.",
- bgColor: "#DD6829",
- icon: "/static/icons/tribe_swap.svg",
+ type: HomepageOpportunityType.Turbo,
+ title: "Tribe Turbo",
+ subtitle: "Earn costless yield on unutilized assets.",
+ bgColor: "#EFEFEF",
+ icon: "/static/turbo/turbo-engine-green.svg",
vaultType: Pool.USDC,
+ textColor: "black"
},
{
type: HomepageOpportunityType.FusePool,
diff --git a/src/constants/nav.ts b/src/constants/nav.ts
index 6b8f61f6..fd58babe 100644
--- a/src/constants/nav.ts
+++ b/src/constants/nav.ts
@@ -6,6 +6,10 @@ import { ChainID } from "esm/utils/networks";
export const PRODUCTS_DROPDOWN_ITEMS: MenuItemInterface[] = [
{ type: MenuItemType.LINK, link: { name: "Fuse", route: "/fuse" } },
+ {
+ type: MenuItemType.LINK,
+ link: { name: "Turbo", route: "/turbo" },
+ },
// { type: MenuItemType.LINK, link: { name: "Vaults", route: "/vaults" } },
{
type: MenuItemType.LINK,
@@ -43,11 +47,18 @@ export const UTILS_DROPDOWN_ITEMS: MenuItemInterface[] = [
},
{
type: MenuItemType.LINK,
- link: { name: "Metrics (Mainnet)", route: "https://rari.grafana.net/goto/61kctV_Gk" },
+ link: {
+ name: "Metrics (Mainnet)",
+ route: "https://rari.grafana.net/goto/61kctV_Gk",
+ },
},
{
type: MenuItemType.LINK,
- link: { name: "Metrics (Arbitrum)", route: "https://metrics.rari.capital/d/BOdF7Hbnk/fuse-overview-arbitrum?orgId=1&refresh=5m&from=now-5m&to=now" },
+ link: {
+ name: "Metrics (Arbitrum)",
+ route:
+ "https://metrics.rari.capital/d/BOdF7Hbnk/fuse-overview-arbitrum?orgId=1&refresh=5m&from=now-5m&to=now",
+ },
},
{
type: MenuItemType.LINK,
@@ -69,7 +80,11 @@ export const UTILS_DROPDOWN_ITEMS_ARBITRUM: MenuItemInterface[] = [
},
{
type: MenuItemType.LINK,
- link: { name: "Metrics", route: "https://metrics.rari.capital/d/BOdF7Hbnk/fuse-overview-arbitrum?orgId=1&refresh=5m&from=now-5m&to=now" },
+ link: {
+ name: "Metrics",
+ route:
+ "https://metrics.rari.capital/d/BOdF7Hbnk/fuse-overview-arbitrum?orgId=1&refresh=5m&from=now-5m&to=now",
+ },
},
{
type: MenuItemType.LINK,
@@ -80,7 +95,8 @@ export const UTILS_DROPDOWN_ITEMS_ARBITRUM: MenuItemInterface[] = [
},
];
-
export const UtilLinks = (chainId: number) => {
- return chainId === ChainID.ETHEREUM ? UTILS_DROPDOWN_ITEMS : UTILS_DROPDOWN_ITEMS_ARBITRUM
-}
\ No newline at end of file
+ return chainId === ChainID.ETHEREUM
+ ? UTILS_DROPDOWN_ITEMS
+ : UTILS_DROPDOWN_ITEMS_ARBITRUM;
+};
diff --git a/src/context/BalancesContext.tsx b/src/context/BalancesContext.tsx
index 1d06711c..42a176ab 100644
--- a/src/context/BalancesContext.tsx
+++ b/src/context/BalancesContext.tsx
@@ -28,19 +28,22 @@ export const BalancesContextProvider = ({
revalidateOnReconnect: false,
});
- const tokenAddresses = useMemo(
- () =>
- underlyingAssets?.length ? underlyingAssets.map((asset) => asset.id) : [],
- [underlyingAssets]
- );
+ const tokenAddresses = !!underlyingAssets
+ ? underlyingAssets.map((asset) => asset.id)
+ : [];
const tokenBalances = useTokenBalances(tokenAddresses);
- const balances: {
+ type BalancesMap = {
[address: string]: number;
- } = useMemo(() => {
- let ret: { [address: string]: number } = {};
- if (!isAuthed) return ret;
+ };
+
+ const balances: BalancesMap = useMemo(() => {
+ let ret: BalancesMap = {};
+
+ if (!isAuthed) {
+ return ret;
+ }
for (let i = 0; i < tokenBalances.length; i++) {
const asset = underlyingAssets![i];
diff --git a/src/context/RariContext.tsx b/src/context/RariContext.tsx
index f4716c65..aa221974 100644
--- a/src/context/RariContext.tsx
+++ b/src/context/RariContext.tsx
@@ -25,7 +25,7 @@ import {
initFuseWithProviders,
} from "../utils/web3Providers";
-import { Web3Provider } from "@ethersproject/providers";
+import { Web3Provider, JsonRpcProvider } from "@ethersproject/providers";
import {
ChainID,
isSupportedChainId,
@@ -96,6 +96,7 @@ export interface RariContextData {
login: (cacheProvider?: boolean) => Promise;
logout: () => any;
address: string;
+ provider: JsonRpcProvider | Web3Provider
isAttemptingLogin: boolean;
chainId: number | undefined;
switchNetwork: (newChainId: number, router: any) => void;
@@ -251,11 +252,32 @@ export const RariProvider = ({ children }: { children: ReactNode }) => {
[setWeb3ModalProvider, setRariAndAddressFromModal, setIsAttemptingLogin, t]
);
- const refetchAccountData = useCallback(() => {
+ const refetchAccountData = useCallback(async () => {
+ setSwitchingNetwork(true);
console.log("New account, clearing the queryClient! ChainId: ", chainId);
+
+ // Get Web3 Provider
const provider = chooseBestWeb3Provider();
- setProvider(provider);
+
+ // Initiate fuse and set global account/address form provider
const fuseInstance = initFuseWithProviders(provider, chainId);
+ fuseInstance.provider.listAccounts().then((addresses: string[]) => {
+ if (addresses.length === 0) {
+ console.log("Address array was empty. Reloading!");
+ router.reload();
+ }
+
+ const address = addresses[0];
+ const requestedAddress = router.query.address as string;
+
+ console.log("Setting Logrocket user to new address: " + address);
+ LogRocket.identify(address);
+
+ console.log("Requested address: ", requestedAddress);
+ setAddress(requestedAddress ?? address);
+ });
+
+ // Set Fuse
setFuse(fuseInstance);
setSwitchingNetwork(false);
}, [setRariAndAddressFromModal, web3ModalProvider, queryClient, chainId]);
@@ -359,6 +381,7 @@ export const RariProvider = ({ children }: { children: ReactNode }) => {
isAuthed: address !== EmptyAddress,
login,
logout,
+ provider,
address,
isAttemptingLogin,
chainId,
diff --git a/src/context/TurboSafeContext.tsx b/src/context/TurboSafeContext.tsx
new file mode 100644
index 00000000..728dbe86
--- /dev/null
+++ b/src/context/TurboSafeContext.tsx
@@ -0,0 +1,155 @@
+import { BigNumber } from "ethers";
+import { formatEther, formatUnits } from "ethers/lib/utils";
+import useSafeAvgAPY from "hooks/turbo/useSafeAvgAPY";
+import { useSafeInfo } from "hooks/turbo/useSafeInfo";
+import { useUserIsSafeOwner } from "hooks/turbo/useSafeOwner";
+import useShouldBoostSafe from "hooks/turbo/useShouldBoostSafe";
+import {
+ StrategyInfosMap,
+ useERC4626StrategiesDataAsMap,
+} from "hooks/turbo/useStrategyInfo";
+import { useUserFeiOwed } from "hooks/turbo/useUserFeiOwed";
+import { TokenData, useTokenData } from "hooks/useTokenData";
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+import { USDPricedTurboSafe } from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import {
+ filterUsedStrategies,
+ StrategyInfo,
+} from "lib/turbo/fetchers/strategies/formatStrategyInfo";
+import { createContext, useContext, ReactNode, useMemo } from "react";
+
+export const getSafeColor = (safeHealth: BigNumber | undefined) => {
+ console.log({safeHealth})
+ return safeHealth?.lte(40)
+ ? "#4DD691"
+ : safeHealth?.lte(60)
+ ? "#4DD691"
+ : safeHealth?.lte(80)
+ ? "orange"
+ : safeHealth?.isZero()
+ ? "grey"
+ : "#DB6464";
+};
+
+type TurboSafeContextData = {
+ // Raw safe data from Lens
+ safe: SafeInfo | undefined;
+
+ // USD Priced safe Data from lens
+ usdPricedSafe: USDPricedTurboSafe | undefined;
+
+ // Fetched token data about collateral asset
+ collateralTokenData: TokenData | undefined;
+
+ // Loading state
+ loading: boolean;
+
+ // List of Active Strats
+ activeStrategies: StrategyInfo[];
+
+ // Fuse ERC 4626 Data about strategies
+ getERC4626StrategyData: StrategyInfosMap;
+
+ // Net APY of safe
+ netAPY: number;
+
+ // bools
+ isAtLiquidationRisk: boolean;
+ shouldBoost: boolean;
+ isUserAdmin: boolean;
+
+ // Colors to display based on safe Health
+ colorScheme: string;
+
+ totalFeiOwed: BigNumber;
+};
+
+export const TurboSafeContext = createContext(
+ undefined
+);
+
+export const TurboSafeProvider = ({
+ safeAddress,
+ children,
+}: {
+ safeAddress: string;
+ children: ReactNode;
+}) => {
+ /** General Safe Data **/
+ const safe = useSafeInfo(safeAddress);
+ const collateralTokenData = useTokenData(safe?.collateralAsset);
+ const isUserAdmin = useUserIsSafeOwner(safeAddress);
+ const loading = !collateralTokenData || !safe;
+
+ // Strategies
+ const activeStrategies = filterUsedStrategies(safe?.strategies);
+ const getERC4626StrategyData = useERC4626StrategiesDataAsMap(
+ safe?.strategies.map((strat) => strat.strategy) ?? []
+ );
+
+ // Average APY across all active Fuse strategies
+ const netAPY = useSafeAvgAPY(
+ activeStrategies,
+ getERC4626StrategyData,
+ parseFloat(formatEther(safe?.tribeDAOFee ?? 0))
+ );
+
+ /** Safe metadata **/
+ const isAtLiquidationRisk = safe?.safeUtilization?.gt(80) ?? false;
+ const shouldBoost = useShouldBoostSafe(safe);
+ const safeHealth = safe?.safeUtilization;
+ const colorScheme = useMemo(
+ () => getSafeColor(safe?.safeUtilization),
+ [safeHealth]
+ );
+
+ const [totalFeiOwed] = useUserFeiOwed(safe);
+
+ /** Boost / Less **/
+
+ const value = useMemo(
+ () => ({
+ safe,
+ usdPricedSafe: safe,
+ collateralTokenData,
+ loading,
+ activeStrategies,
+ getERC4626StrategyData,
+ netAPY,
+ isAtLiquidationRisk,
+ colorScheme,
+ shouldBoost,
+ isUserAdmin,
+ totalFeiOwed,
+ }),
+ [
+ safe,
+ collateralTokenData,
+ loading,
+ activeStrategies,
+ getERC4626StrategyData,
+ netAPY,
+ isAtLiquidationRisk,
+ colorScheme,
+ shouldBoost,
+ isUserAdmin,
+ totalFeiOwed,
+ ]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useTurboSafe = () => {
+ const turboSafeData = useContext(TurboSafeContext);
+
+ if (turboSafeData === undefined) {
+ throw new Error(`useTurboSafe must be used within a TurboSafeProvider`);
+ }
+
+ return turboSafeData;
+};
diff --git a/src/esm/utils/networks.js b/src/esm/utils/networks.js
index 4929a457..bfe6ead8 100644
--- a/src/esm/utils/networks.js
+++ b/src/esm/utils/networks.js
@@ -81,15 +81,15 @@ export const chainMetadata = {
blockExplorerURL: "https://optimistic.etherscan.io",
color: "#FE0521",
},
- // [ChainID.HARDHAT]: {
- // chainId: ChainID.HARDHAT,
- // name: "Hardhat",
- // imageUrl: "/static/networks/optimism.svg", // no logo
- // supported: true,
- // rpcUrl: "http://localhost:8545",
- // blockExplorerURL: "",
- // color: "#BC6C6C"
- // }
+ [ChainID.HARDHAT]: {
+ chainId: ChainID.HARDHAT,
+ name: "Hardhat",
+ imageUrl: "/static/networks/optimism.svg", // no logo
+ supported: true,
+ rpcUrl: "http://localhost:8545",
+ blockExplorerURL: "",
+ color: "#BC6C6C"
+ }
};
export const isSupportedChainId = (chainId) => Object.values(ChainID).includes(chainId);
export function getSupportedChains() {
diff --git a/src/hooks/homepage/useOpportunitySubtitle.ts b/src/hooks/homepage/useOpportunitySubtitle.ts
index 914d99be..88436825 100644
--- a/src/hooks/homepage/useOpportunitySubtitle.ts
+++ b/src/hooks/homepage/useOpportunitySubtitle.ts
@@ -15,7 +15,7 @@ import { useRari } from "context/RariContext";
export const useOpportunitySubtitle = (
opportunity: HomepageOpportunity
): string | null => {
- const { fuse, chainId } = useRari()
+ const { fuse, chainId } = useRari();
// // Earn
// const earnPoolAPY = usePoolAPY(opportunity.vaultType);
// const poolInfos = usePoolInfos();
@@ -24,63 +24,68 @@ export const useOpportunitySubtitle = (
// // Fuse
// const fusePoolData = useFusePoolData(opportunity.fusePoolId?.toString());
// const { data: fuseTVL } = useFuseTVL();
- switch (opportunity.type) {
- case HomepageOpportunityType.EarnVault:
- {
- const earnPoolAPY = usePoolAPY(opportunity.vaultType);
- return earnPoolAPY ? `${earnPoolAPY}% APY` : null;
- }
- case HomepageOpportunityType.FusePool: {
- const fusePoolData = useFusePoolData(opportunity.fusePoolId?.toString());
-
- switch (opportunity.fuseMetric) {
- case FusePoolMetric.TotalBorrowedUSD:
- return fusePoolData?.totalBorrowedUSD
- ? `${shortUsdFormatter(
- fusePoolData.totalBorrowedUSD.toNumber()
- )} borrowed`
- : null;
- case FusePoolMetric.TotalSuppliedUSD:
- return fusePoolData?.totalSuppliedUSD
- ? `${shortUsdFormatter(
- fusePoolData.totalSuppliedUSD.toNumber()
- )} supplied`
- : null;
- case FusePoolMetric.TotalLiquidityUSD:
- return fusePoolData?.totalLiquidityUSD
- ? `${shortUsdFormatter(
- fusePoolData.totalLiquidityUSD.toNumber()
- )} liquidity`
- : null;
- default:
- return fusePoolData?.totalSuppliedUSD
- ? `${shortUsdFormatter(
- fusePoolData.totalSuppliedUSD.toNumber()
- )} supplied`
- : null;
- }
- }
- case HomepageOpportunityType.EarnPage: {
- const poolInfos = usePoolInfos();
- const poolsAPY = usePoolsAPY(poolInfos);
- // @ts-ignore
- const apys = poolsAPY.filter((obj) => obj).map(parseFloat);
- const maxAPY = !!apys.length ? Math.max.apply(null, apys) : null;
- return maxAPY ? `${maxAPY}% APY` : null;
- }
- case HomepageOpportunityType.FusePage: {
- const { data: fuseTVL } = useFuseTVL();
- return (fuseTVL !== undefined || fuseTVL !== null) ? `${shortUsdFormatter(fuseTVL!)} TVL` : null;
+ switch (opportunity.type) {
+ case HomepageOpportunityType.EarnVault: {
+ const earnPoolAPY = usePoolAPY(opportunity.vaultType);
+ return earnPoolAPY ? `${earnPoolAPY}% APY` : null;
+ }
+ case HomepageOpportunityType.FusePool: {
+ const fusePoolData = useFusePoolData(opportunity.fusePoolId?.toString());
+
+ switch (opportunity.fuseMetric) {
+ case FusePoolMetric.TotalBorrowedUSD:
+ return fusePoolData?.totalBorrowedUSD
+ ? `${shortUsdFormatter(
+ fusePoolData.totalBorrowedUSD.toNumber()
+ )} borrowed`
+ : null;
+ case FusePoolMetric.TotalSuppliedUSD:
+ return fusePoolData?.totalSuppliedUSD
+ ? `${shortUsdFormatter(
+ fusePoolData.totalSuppliedUSD.toNumber()
+ )} supplied`
+ : null;
+ case FusePoolMetric.TotalLiquidityUSD:
+ return fusePoolData?.totalLiquidityUSD
+ ? `${shortUsdFormatter(
+ fusePoolData.totalLiquidityUSD.toNumber()
+ )} liquidity`
+ : null;
+ default:
+ return fusePoolData?.totalSuppliedUSD
+ ? `${shortUsdFormatter(
+ fusePoolData.totalSuppliedUSD.toNumber()
+ )} supplied`
+ : null;
}
- case HomepageOpportunityType.Arbitrum:
- return "Now live!";
+ }
+ case HomepageOpportunityType.EarnPage: {
+ const poolInfos = usePoolInfos();
+ const poolsAPY = usePoolsAPY(poolInfos);
+ // @ts-ignore
+ const apys = poolsAPY.filter((obj) => obj).map(parseFloat);
+ const maxAPY = !!apys.length ? Math.max.apply(null, apys) : null;
+ return maxAPY ? `${maxAPY}% APY` : null;
+ }
+ case HomepageOpportunityType.FusePage: {
+ const { data: fuseTVL } = useFuseTVL();
+ return fuseTVL !== undefined || fuseTVL !== null
+ ? `${shortUsdFormatter(fuseTVL!)} TVL`
+ : null;
+ }
+ case HomepageOpportunityType.Arbitrum:
+ return "Now live!";
+
+ case HomepageOpportunityType.Connext:
+ return "via Connext";
- case HomepageOpportunityType.Connext:
- return "via Connext";
+ case HomepageOpportunityType.PegExchanger:
+ return "Join the Tribe";
- case HomepageOpportunityType.PegExchanger:
- return "Join the Tribe";
+ case HomepageOpportunityType.Turbo:
+ return 'Tribe x Rari'
- default:
- return null;
-}};
+ default:
+ return null;
+ }
+};
diff --git a/src/hooks/rewards/useRewardAPY.ts b/src/hooks/rewards/useRewardAPY.ts
index dc170400..196106a5 100644
--- a/src/hooks/rewards/useRewardAPY.ts
+++ b/src/hooks/rewards/useRewardAPY.ts
@@ -25,6 +25,7 @@ import {
import { convertMantissaToAPR, convertMantissaToAPY } from "utils/apyUtils";
import { constants, BigNumber } from "ethers";
import { getEthUsdPriceBN } from "esm/utils/getUSDPriceBN";
+import { formatEther } from "ethers/lib/utils";
// ( ( rewardSupplySpeed * rewardEthPrice ) / ( underlyingTotalSupply * underlyingEthPrice / 1e18 / 1e18 ) )
// (
@@ -315,14 +316,13 @@ export const getPriceFromOracles = async (
comptroller: string,
fuse: Fuse,
isAuthed: boolean
-) => {
+): Promise => {
// Rari MPO
const masterPriceOracle = createMasterPriceOracle(fuse, true);
// Pool's MPO
const comptrollerInstance = useCreateComptroller(comptroller, fuse, isAuthed);
const oracleAddress: string = await comptrollerInstance.callStatic.oracle();
- // const oracleModel: string | undefined = await fuse.getPriceOracle(oracle);
const oracleContract = createOracle(
oracleAddress,
fuse,
@@ -377,7 +377,7 @@ export const useAssetPricesInEth = (
// Calc usd prices
for (let i = 0; i < tokenAddresses.length; i++) {
- const priceInEth = parseFloat(tokenPricesInEth[i]);
+ const priceInEth = parseFloat(tokenPricesInEth[i].toString());
const tokenData = tokensData[tokenAddresses[i]];
const decimals = tokenData?.decimals ?? 18;
@@ -397,7 +397,7 @@ export const useAssetPricesInEth = (
for (let i = 0; i < tokenAddresses.length; i++) {
const tokenAddress = tokenAddresses[i];
const usdPrice = tokenUSDPrices[i];
- const ethPrice = parseFloat(tokenPricesInEth[i]);
+ const ethPrice = parseFloat(tokenPricesInEth[i].toString());
tokenPrices[tokenAddress] = {
ethPrice,
usdPrice,
diff --git a/src/hooks/turbo/test/mocks.ts b/src/hooks/turbo/test/mocks.ts
new file mode 100644
index 00000000..566e825a
--- /dev/null
+++ b/src/hooks/turbo/test/mocks.ts
@@ -0,0 +1,123 @@
+import { BigNumber } from "ethers";
+import {
+ USDPricedStrategy,
+ USDPricedTurboSafe,
+} from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import { StrategyInfo } from "lib/turbo/fetchers/strategies/formatStrategyInfo";
+import { FuseERC4626Strategy } from "../useStrategyInfo";
+
+const MOCK_STRATEGY_1: StrategyInfo = {
+ strategy: "0xaC4c093c777581DC9c4DC935394Ff11e6c58CD45",
+ boostedAmount: BigNumber.from("0x01a784379d99db42000000"),
+ feiAmount: BigNumber.from("0x01a784384483c3a3ebd483"),
+ feiEarned: BigNumber.from("0x0001a784384483c3a3ebd4"),
+ feiClaimable: BigNumber.from("0x0001a784384483c3a3ebd4"),
+};
+
+const MOCK_STRATEGY_2: StrategyInfo = {
+ // Randomly generated address
+ strategy: "0x3E556610757D238c7c806bBE04536a05828f474b",
+ boostedAmount: BigNumber.from("0x01a784379d99db42000000"),
+ feiAmount: BigNumber.from("0x01a784384483c3a3ebd483"),
+ feiEarned: BigNumber.from("0x0001a784384483c3a3ebd4"),
+ feiClaimable: BigNumber.from("0x0001a784384483c3a3ebd4"),
+};
+
+const MOCK_USD_PRICED_STRATEGY_1: USDPricedStrategy = {
+ ...MOCK_STRATEGY_1,
+ boostAmountUSD: 50,
+ feiAmountUSD: 50,
+ feiEarnedUSD: 10,
+ feiClaimableUSD: 10,
+
+};
+
+const MOCK_USD_PRICED_STRATEGY_2: USDPricedStrategy = {
+ ...MOCK_STRATEGY_2,
+ boostAmountUSD: 50,
+ feiAmountUSD: 50,
+ feiEarnedUSD: 10,
+ feiClaimableUSD: 10,
+};
+
+const MOCK_ERC4626_STRATEGY_1: FuseERC4626Strategy = {
+ underlying: "0xc7283b66Eb1EB5FB86327f08e1B5816b0720212B",
+ name: "Fei USD",
+ symbol: "FEI",
+ // Randomly generated address
+ fToken: "0xc7283b66Eb1EB5FB86327f08e1B5816b0720212B",
+ // Randomly generated address
+ comptroller: "0x517F73a1329330c469BA1446BA248aEAa3b3a883",
+ supplyRatePerBlock: 100,
+};
+
+const MOCK_SAFE_1: USDPricedTurboSafe = {
+ safeAddress: "0xCd6442eB75f676671FBFe003A6A6F022CbbB8d38",
+ collateralAsset: "0xc7283b66Eb1EB5FB86327f08e1B5816b0720212B",
+ collateralAmount: BigNumber.from("5000001000000000000000000"),
+ collateralValue: BigNumber.from("1158926336594045961765"),
+ collateralPrice: BigNumber.from("231785220961765"),
+ debtAmount: BigNumber.from("2000000000000000000000000"),
+ debtValue: BigNumber.from("651700000000000000000"),
+ boostedAmount: BigNumber.from("2000000000000000000000000"),
+ feiPrice: BigNumber.from("325850000000000"),
+ feiAmount: BigNumber.from("2000000046982030418498691"),
+ tribeDAOFee: BigNumber.from("750000000000000000"),
+ strategies: [MOCK_STRATEGY_1, MOCK_STRATEGY_2],
+ safeUtilization: BigNumber.from(50),
+ collateralValueUSD: 100,
+ collateralPriceUSD: 1,
+ debtUSD: 50,
+ boostedUSD: 50,
+ feiAmountUSD: 50,
+ feiPriceUSD: 1,
+ usdPricedStrategies: [MOCK_USD_PRICED_STRATEGY_1, MOCK_USD_PRICED_STRATEGY_2],
+ maxBoost: BigNumber.from("20000000000000000000000000"),
+ maxBoostUSD: 20000000000000000000000000,
+ collateralFactor: BigNumber.from("100"),
+ liquidationPrice: .01,
+ liquidationPriceUSD: .32
+};
+
+const MOCK_SAFE_2: USDPricedTurboSafe = {
+ // Randomly generated address
+ safeAddress: "0x87F5A53A5FBB4085AA4111B531044B4350788E2F",
+ collateralAsset: "0xc7283b66Eb1EB5FB86327f08e1B5816b0720212B",
+ collateralAmount: BigNumber.from("5000001000000000000000000"),
+ collateralValue: BigNumber.from("1158926336594045961765"),
+ collateralPrice: BigNumber.from("231785220961765"),
+ debtAmount: BigNumber.from("2000000000000000000000000"),
+ debtValue: BigNumber.from("651700000000000000000"),
+ boostedAmount: BigNumber.from("2000000000000000000000000"),
+ feiPrice: BigNumber.from("325850000000000"),
+ feiAmount: BigNumber.from("2000000046982030418498691"),
+ tribeDAOFee: BigNumber.from("750000000000000000"),
+ strategies: [MOCK_STRATEGY_1],
+ safeUtilization: BigNumber.from(50),
+ collateralValueUSD: 100,
+ collateralPriceUSD: 1,
+ debtUSD: 50,
+ boostedUSD: 50,
+ feiAmountUSD: 50,
+ feiPriceUSD: 1,
+ usdPricedStrategies: [
+ {
+ strategy: "0xaC4c093c777581DC9c4DC935394Ff11e6c58CD45",
+ boostedAmount: BigNumber.from("0x01a784379d99db42000000"),
+ feiAmount: BigNumber.from("0x01a784384483c3a3ebd483"),
+ feiEarned: BigNumber.from("0x0001a784384483c3a3ebd4"),
+ feiClaimable: BigNumber.from("0x0001a784384483c3a3ebd4"),
+ boostAmountUSD: 50,
+ feiAmountUSD: 50,
+ feiEarnedUSD: 10,
+ feiClaimableUSD: 1
+ },
+ ],
+ maxBoost: BigNumber.from("20000000000000000000000000"),
+ maxBoostUSD: 20000000000000000000000000,
+ collateralFactor: BigNumber.from("100"),
+ liquidationPrice: .01,
+ liquidationPriceUSD: .32
+};
+
+export { MOCK_SAFE_1, MOCK_SAFE_2, MOCK_ERC4626_STRATEGY_1 };
diff --git a/src/hooks/turbo/useAggregateSafeData.ts b/src/hooks/turbo/useAggregateSafeData.ts
new file mode 100644
index 00000000..805b84a9
--- /dev/null
+++ b/src/hooks/turbo/useAggregateSafeData.ts
@@ -0,0 +1,82 @@
+import { getEthUsdPriceBN } from 'esm/utils/getUSDPriceBN';
+import { BigNumber, constants } from 'ethers'
+import { formatEther } from 'ethers/lib/utils';
+import { useBalancesOfMultipleAddresses } from 'hooks/useBalanceOf';
+import { SafeInfo } from 'lib/turbo/fetchers/safes/getSafeInfo';
+import { filterUsedStrategies } from 'lib/turbo/fetchers/strategies/formatStrategyInfo';
+import { FEI } from 'lib/turbo/utils/constants';
+import { getUserFeiOwed, getUserFeiOwedWithBalance } from 'lib/turbo/utils/getUserFeiOwed';
+import { calculateETHValueUSD, calculateFEIValueUSD } from 'lib/turbo/utils/usdUtils';
+import { useQuery } from 'react-query';
+import { convertMantissaToAPY } from 'utils/apyUtils';
+import { getStrategiesAvgAPY } from './useSafeAvgAPY';
+import { StrategyInfosMap } from './useStrategyInfo';
+
+type AggregateSafeData = {
+ totalBoosted: BigNumber;
+ totalClaimable: BigNumber;
+ totalClaimableUSD: number;
+ netAPY: number;
+}
+
+const EMPTY_SAFE_DATA = {
+ totalBoosted: constants.Zero,
+ totalClaimable: constants.Zero,
+ totalClaimableUSD: 0,
+ netAPY: 0
+}
+
+const useAggregateSafeData = (
+ safes: SafeInfo[],
+ getERC4626StrategyData: StrategyInfosMap
+): AggregateSafeData => {
+
+ const balancesFEI = useBalancesOfMultipleAddresses(safes.map(safe => safe.safeAddress), FEI)
+ console.log({ balancesFEI })
+
+ const { data } = useQuery(
+ `user aggregate safes ${safes.map(safe => safe.safeAddress).join(', ')}`,
+ async () => {
+ const ethUSD = await getEthUsdPriceBN()
+ const feiPrice = await safes[0]?.feiPrice ?? constants.WeiPerEther
+
+ const numActiveSafes = safes.reduce((acc, safe) => {
+ return safe.boostedAmount.isZero() ? acc : acc + 1
+ }, 0)
+
+ const totalBoosted = safes.reduce((acc, safe) => {
+ return acc.add(safe.boostedAmount);
+ }, constants.Zero)
+
+ let totalClaimable = safes.reduce((acc, safe) => {
+ return acc.add(getUserFeiOwedWithBalance(safe, balancesFEI[safe.safeAddress]));
+ }, constants.Zero)
+
+ let totalClaimableUSD = calculateFEIValueUSD(totalClaimable, feiPrice, ethUSD)
+
+ let netAPY = !numActiveSafes ? 0 : safes.reduce((acc, safe) => {
+ const apy = getStrategiesAvgAPY(
+ filterUsedStrategies(safe.strategies),
+ getERC4626StrategyData,
+ parseFloat(formatEther(safe?.tribeDAOFee ?? 0))
+ )
+ return (acc + apy) / numActiveSafes;
+ }, 0)
+
+ const obj: AggregateSafeData = {
+ totalBoosted,
+ totalClaimable,
+ totalClaimableUSD,
+ netAPY
+ }
+
+ console.log({ obj })
+
+ return obj
+
+ })
+
+ return data ?? EMPTY_SAFE_DATA
+}
+
+export default useAggregateSafeData
\ No newline at end of file
diff --git a/src/hooks/turbo/useAllSafes.ts b/src/hooks/turbo/useAllSafes.ts
new file mode 100644
index 00000000..3ed787d6
--- /dev/null
+++ b/src/hooks/turbo/useAllSafes.ts
@@ -0,0 +1,21 @@
+import { useQuery } from "react-query";
+import { useRari } from "context/RariContext";
+import { createTurboSafe } from "lib/turbo/utils/turboContracts";
+import { getAllSafes } from "lib/turbo/fetchers/safes/getAllSafes";
+
+// Trusted Strategies will be independent of any Safe and whitelisted by TRIBE Governance
+export const useAllSafes = (): any => {
+ const { provider, chainId } = useRari();
+
+ const { data: safeOwner } = useQuery(
+ `All safes`,
+ async () => {
+ if (!provider || !chainId) return;
+ const answer = await getAllSafes(provider, chainId)
+ console.log(answer)
+ return answer
+ }
+ );
+
+ return safeOwner
+};
\ No newline at end of file
diff --git a/src/hooks/turbo/useApprovedCollateral.ts b/src/hooks/turbo/useApprovedCollateral.ts
new file mode 100644
index 00000000..8fc47ce6
--- /dev/null
+++ b/src/hooks/turbo/useApprovedCollateral.ts
@@ -0,0 +1,13 @@
+import { useRari } from "context/RariContext";
+import { getTurboApprovedCollateral } from "lib/turbo/fetchers/getApprovedCollaterals";
+import { useQuery } from "react-query";
+
+const useApprovedCollateral = () => {
+ const { provider } = useRari();
+ const { data } = useQuery("Approved Turbo Collateral", async () => {
+ return await getTurboApprovedCollateral(provider);
+ });
+ return data ?? [];
+};
+
+export default useApprovedCollateral;
diff --git a/src/hooks/turbo/useBoostCapsForStrategies.ts b/src/hooks/turbo/useBoostCapsForStrategies.ts
new file mode 100644
index 00000000..9e6156b0
--- /dev/null
+++ b/src/hooks/turbo/useBoostCapsForStrategies.ts
@@ -0,0 +1,31 @@
+import { useRari } from "context/RariContext"
+import { getBoostCapForStrategy, getBoostCapsForStrategies } from "lib/turbo/fetchers/strategies/getBoostCapsForStrategies"
+import { useQuery } from "react-query"
+
+const useBoostCapsForStrategies = (strategies: string[]) => {
+ const { provider } = useRari();
+
+ const { data } = useQuery(
+ `Boost caps for strategies ${strategies.join(', ')}`,
+ async () => await getBoostCapsForStrategies(provider, strategies)
+
+ )
+
+ return data
+}
+
+export const useBoostCapForStrategy = (strategy: string | undefined) => {
+ const { provider } = useRari();
+
+ const { data } = useQuery(
+ `Boost caps for strategy ${strategy}`,
+ async () => {
+ if (!strategy) return
+ return await getBoostCapForStrategy(provider, strategy)
+ }
+ )
+
+ return data
+}
+
+export default useBoostCapsForStrategies
\ No newline at end of file
diff --git a/src/hooks/turbo/useCollateralBoostCap.ts b/src/hooks/turbo/useCollateralBoostCap.ts
new file mode 100644
index 00000000..504106d2
--- /dev/null
+++ b/src/hooks/turbo/useCollateralBoostCap.ts
@@ -0,0 +1,26 @@
+import { useRari } from "context/RariContext";
+import { BigNumber } from "ethers";
+import { createTurboBooster } from "lib/turbo/utils/turboContracts";
+import { useQuery } from "react-query";
+
+const useCollateralBoostCap = (token: string | undefined) => {
+ const { provider } = useRari();
+ const { data: boostCap } = useQuery(
+ "Turbo collateral boost cap for asset " + token,
+ async () => {
+ if (!token) return;
+ return await getBoostCapForCollateral(provider, token);
+ }
+ );
+ return boostCap
+};
+
+const getBoostCapForCollateral = async (
+ provider: any,
+ token: string
+): Promise => {
+ const Booster = createTurboBooster(provider, 1);
+ return await Booster.callStatic.getBoostCapForCollateral(token);
+};
+
+export default useCollateralBoostCap;
diff --git a/src/hooks/turbo/useIsUserAuthorizedToCreateSafes.ts b/src/hooks/turbo/useIsUserAuthorizedToCreateSafes.ts
new file mode 100644
index 00000000..7ae4e5d2
--- /dev/null
+++ b/src/hooks/turbo/useIsUserAuthorizedToCreateSafes.ts
@@ -0,0 +1,30 @@
+import { useRari } from "context/RariContext";
+import { isUserAuthorizedToCreateSafes } from "lib/turbo/fetchers/getIsUserAuthorizedToCreateSafes";
+import { TurboAddresses } from "lib/turbo/utils/constants";
+import { useQuery } from "react-query";
+
+export const useIsUserAuthorizedToCreateSafes = () => {
+ if (process.env.NEXT_PUBLIC_USE_MOCKS === "true") {
+ return true;
+ }
+
+ const { address, provider, chainId } = useRari();
+
+ const { data: isAuthorized } = useQuery(
+ `Is ${address} authorized to create safes`,
+ async () => {
+ if (!address || !chainId || !provider) return;
+
+ const isAuthorized = await isUserAuthorizedToCreateSafes(
+ provider,
+ TurboAddresses[chainId].TURBO_AUTHORITY,
+ address,
+ TurboAddresses[chainId].MASTER
+ );
+
+ return isAuthorized;
+ }
+ );
+
+ return isAuthorized;
+};
diff --git a/src/hooks/turbo/usePreviewSafes.ts b/src/hooks/turbo/usePreviewSafes.ts
new file mode 100644
index 00000000..3603e76a
--- /dev/null
+++ b/src/hooks/turbo/usePreviewSafes.ts
@@ -0,0 +1,36 @@
+import { useQuery } from "react-query";
+import { createTurboMaster } from "lib/turbo/utils/turboContracts";
+import { useRari } from "context/RariContext";
+import { providers } from "@0xsequence/multicall";
+import { useSafesInfo } from "./useSafeInfo";
+
+const SAFE_IDS = [1, 2, 1];
+
+const usePreviewSafes = () => {
+ const { provider } = useRari();
+
+ const { data: safeAddresses } = useQuery(
+ "Preview safe addresses for indices" + SAFE_IDS.join(","),
+ async () => {
+ const multicallProvider = new providers.MulticallProvider(provider);
+ const TurboMaster = createTurboMaster(multicallProvider);
+
+ const settledPromises = await Promise.allSettled(
+ SAFE_IDS.map((id) => TurboMaster.callStatic.safes(id))
+ );
+
+ const result: string[] = settledPromises.reduce((acc: string[], obj) => {
+ if (obj.status === "fulfilled") return [...acc, obj.value];
+ return [...acc];
+ }, []);
+
+ return result;
+ }
+ );
+ const safes = useSafesInfo(safeAddresses ?? []);
+
+ console.log({ safes });
+ return safes ?? [];
+};
+
+export default usePreviewSafes;
diff --git a/src/hooks/turbo/useSafeAvgAPY.ts b/src/hooks/turbo/useSafeAvgAPY.ts
new file mode 100644
index 00000000..cf2f44b7
--- /dev/null
+++ b/src/hooks/turbo/useSafeAvgAPY.ts
@@ -0,0 +1,49 @@
+import { StrategyInfo } from "lib/turbo/fetchers/strategies/formatStrategyInfo";
+import { useMemo } from "react";
+import { convertMantissaToAPY } from "utils/apyUtils";
+import { StrategyInfosMap } from "./useStrategyInfo";
+
+// Returns avg Apy for a safe
+const useSafeAvgAPY = (
+ activeStrategies: StrategyInfo[],
+ getERC4626StrategyData: StrategyInfosMap,
+ tribeDaoFeeShare: number
+) => {
+ const apy = useMemo(
+ () =>
+ getStrategiesAvgAPY(
+ activeStrategies,
+ getERC4626StrategyData,
+ tribeDaoFeeShare
+ ),
+ [activeStrategies, getERC4626StrategyData, tribeDaoFeeShare]
+ );
+
+ return apy;
+};
+
+export const getStrategiesAvgAPY = (
+ strategies: StrategyInfo[],
+ getERC4626StrategyData: StrategyInfosMap,
+ tribeDaoFeeShare: number
+) => {
+ const numActiveStrategies = strategies.reduce((acc, strategy) => {
+ return strategy.boostedAmount.isZero() ? acc : acc + 1;
+ }, 0);
+
+ console.log({ strategies, getERC4626StrategyData, convertMantissaToAPY, numActiveStrategies, tribeDaoFeeShare });
+
+ return strategies.reduce((num, { strategy }) => {
+ const erc4626Strategy = getERC4626StrategyData[strategy];
+ if (erc4626Strategy && erc4626Strategy.supplyRatePerBlock) {
+ let apy = convertMantissaToAPY(erc4626Strategy.supplyRatePerBlock, 365);
+ let userShare = 1 - tribeDaoFeeShare;
+ let userAPY = apy * userShare;
+ num += userAPY / numActiveStrategies;
+ }
+
+ return num;
+ }, 0);
+};
+
+export default useSafeAvgAPY;
diff --git a/src/hooks/turbo/useSafeInfo.ts b/src/hooks/turbo/useSafeInfo.ts
new file mode 100644
index 00000000..0ca0108a
--- /dev/null
+++ b/src/hooks/turbo/useSafeInfo.ts
@@ -0,0 +1,62 @@
+import { useQueries, useQuery, UseQueryResult } from "react-query";
+import { useRari } from "context/RariContext";
+import {
+ getUSDPricedSafeInfo,
+ USDPricedTurboSafe,
+} from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import { MOCK_SAFE_1, MOCK_SAFE_2 } from "./test/mocks";
+import { isSupportedChainId } from "esm/utils/networks";
+
+export const useSafeInfo = (safe: string): USDPricedTurboSafe | undefined => {
+ if (process.env.NEXT_PUBLIC_USE_MOCKS === "true") {
+ return MOCK_SAFE_1;
+ }
+
+ const { provider, chainId } = useRari();
+
+ const { data: safeInfo } = useQuery(
+ `Safe info for: ${safe} chain ${chainId}`,
+ async () => {
+ if (!safe || !provider || !chainId || !isSupportedChainId(chainId))
+ return;
+ return await getUSDPricedSafeInfo(provider, safe, chainId);
+ }
+ );
+ return safeInfo;
+};
+
+export const useSafesInfo = (
+ safes: string[]
+): USDPricedTurboSafe[] | undefined => {
+ const { provider, chainId } = useRari();
+
+ if (process.env.NEXT_PUBLIC_USE_MOCKS === "true") {
+ return [MOCK_SAFE_1, MOCK_SAFE_2];
+ }
+
+ const safesQueryResult: UseQueryResult[] =
+ useQueries(
+ safes.map((safe) => {
+ return {
+ queryKey: `SafeInfo for safe ${safe}`,
+ queryFn: async () => {
+ if (!safe || !provider || !chainId || !isSupportedChainId(chainId))
+ return;
+ return await getUSDPricedSafeInfo(provider, safe, chainId);
+ },
+ };
+ })
+ );
+
+ const result: USDPricedTurboSafe[] = safesQueryResult.reduce(
+ (obj: USDPricedTurboSafe[], result, i) => {
+ if (result.data) {
+ return [...obj, result.data];
+ }
+ return [...obj];
+ },
+ []
+ );
+
+ return result;
+};
diff --git a/src/hooks/turbo/useSafeMaxAmount.ts b/src/hooks/turbo/useSafeMaxAmount.ts
new file mode 100644
index 00000000..9d629aaa
--- /dev/null
+++ b/src/hooks/turbo/useSafeMaxAmount.ts
@@ -0,0 +1,37 @@
+import { useRari } from "context/RariContext";
+import { BigNumber, constants } from "ethers";
+import { USDPricedTurboSafe } from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import { fetchMaxSafeAmount } from "lib/turbo/utils/fetchMaxSafeAmount";
+import { useQuery } from "react-query";
+import { SafeInteractionMode } from "./useUpdatedSafeInfo";
+
+const useSafeMaxAmount = (
+ safe: USDPricedTurboSafe | undefined,
+ mode: SafeInteractionMode,
+ strategyIndex?: number //only used for LESS
+) => {
+ const { provider, address, chainId } = useRari();
+
+ const { data: maxAmount } = useQuery(
+ `Safe ${safe?.safeAddress} Max ${mode} amount ${
+ !!strategyIndex ? "Strategy " + strategyIndex : null
+ }`,
+ async () => {
+ if (!chainId) return constants.Zero;
+
+ return fetchMaxSafeAmount(
+ provider,
+ mode,
+ address,
+ safe,
+ chainId,
+ strategyIndex,
+ true,
+ );
+ }
+ );
+
+ return maxAmount ?? constants.Zero;
+};
+
+export default useSafeMaxAmount;
diff --git a/src/hooks/turbo/useSafeOwner.ts b/src/hooks/turbo/useSafeOwner.ts
new file mode 100644
index 00000000..6b09440c
--- /dev/null
+++ b/src/hooks/turbo/useSafeOwner.ts
@@ -0,0 +1,25 @@
+import { useQuery } from "react-query";
+import { useRari } from "context/RariContext";
+import { createTurboSafe } from "lib/turbo/utils/turboContracts";
+
+// Trusted Strategies will be independent of any Safe and whitelisted by TRIBE Governance
+export const useSafeOwner = (safeAddress: string | undefined): string => {
+ const { provider, chainId } = useRari();
+
+ const { data: safeOwner } = useQuery(
+ `Safe owner for safe ${safeAddress}`,
+ async () => {
+ if (!provider || !chainId || !safeAddress) return;
+ const safe = createTurboSafe(provider, safeAddress);
+ return await safe.callStatic.owner();
+ }
+ );
+
+ return safeOwner;
+};
+
+export const useUserIsSafeOwner = (safeAddress: string): boolean => {
+ const { address } = useRari();
+ const owner = useSafeOwner(safeAddress);
+ return !!address && address === owner;
+};
diff --git a/src/hooks/turbo/useShouldBoostSafe.ts b/src/hooks/turbo/useShouldBoostSafe.ts
new file mode 100644
index 00000000..e807c6ac
--- /dev/null
+++ b/src/hooks/turbo/useShouldBoostSafe.ts
@@ -0,0 +1,11 @@
+import { SafeInfo } from 'lib/turbo/fetchers/safes/getSafeInfo'
+import { constants } from "ethers";
+
+const useShouldBoostSafe = (safe: SafeInfo | undefined) => {
+ if (!safe) return false
+ const boostMe = safe.maxBoost.gt(constants.WeiPerEther.mul(3)) && safe.safeUtilization.lt(10)
+ return boostMe
+
+}
+
+export default useShouldBoostSafe
\ No newline at end of file
diff --git a/src/hooks/turbo/useStrategyInfo.ts b/src/hooks/turbo/useStrategyInfo.ts
new file mode 100644
index 00000000..06cecbe9
--- /dev/null
+++ b/src/hooks/turbo/useStrategyInfo.ts
@@ -0,0 +1,114 @@
+import { useQueries, useQuery } from "react-query";
+import { useRari } from "context/RariContext";
+import {
+ createFusePoolDirectory,
+ createFusePoolLens,
+ createFusePoolLensSecondary,
+ ICERC20Delegate,
+ IFuseERC4626,
+} from "lib/turbo/utils/turboContracts";
+import { callInterfaceWithMulticall } from "utils/multicall";
+import { MOCK_ERC4626_STRATEGY_1 } from "./test/mocks";
+import { getStrategyFusePoolId } from "lib/turbo/fetchers/strategies/formatStrategyInfo";
+import { filterPoolName } from "utils/fetchFusePoolData";
+
+export interface FuseERC4626Strategy {
+ underlying: string;
+ name: string;
+ symbol: string;
+ fToken: string;
+ comptroller: string;
+ supplyRatePerBlock: number;
+}
+
+// Data Required to render FuseERC4626 strategies
+export type StrategyInfosMap = {
+ [strategyAddress: string]: FuseERC4626Strategy | undefined;
+};
+
+// Gets ERC4626 strategy data
+export const useERC4626StrategyData = (strategy: string) => {
+ const { provider } = useRari();
+
+ const { data: strategyInfo } = useQuery(
+ `Strategy info for: ${strategy}`,
+ async () => {
+ if (!strategy) return;
+ return await fetchStrategyData(provider, strategy);
+ }
+ );
+ return strategyInfo;
+};
+
+// Gets ERC4626 strategy info for multiple strategies
+export const useERC4626StrategiesDataAsMap = (strategies: string[]) => {
+ const { provider } = useRari();
+
+ const strategiesQueryResult = useQueries(
+ strategies.map((strategy: string, i) => {
+ return {
+ queryKey: `Strategy info for: ${strategy}`,
+ queryFn: () => {
+ return fetchStrategyData(provider, strategy);
+ },
+ };
+ })
+ );
+
+ const result: StrategyInfosMap = strategiesQueryResult.reduce(
+ (obj: StrategyInfosMap, result, i) => {
+ obj[strategies[i]] = result.data;
+ return { ...obj };
+ },
+ {}
+ );
+
+ return result ?? {};
+};
+
+export const fetchStrategyData = async (
+ provider: any,
+ strategy: string
+): Promise => {
+ if (process.env.NEXT_PUBLIC_USE_MOCKS === "true") {
+ return MOCK_ERC4626_STRATEGY_1;
+ }
+
+ // Get stuff from FuseERC4626
+ const [[symbol], [fToken], [underlying], [comptroller]] =
+ await callInterfaceWithMulticall(
+ provider,
+ IFuseERC4626,
+ strategy,
+ ["symbol", "cToken", "cTokenUnderlying", "unitroller"],
+ [[], [], [], []]
+ );
+
+ // Get SupplyRate from CERC20
+ const [[supplyRatePerBlock]] = await callInterfaceWithMulticall(
+ provider,
+ ICERC20Delegate,
+ fToken,
+ ["supplyRatePerBlock"],
+ [[]]
+ );
+
+ const poolId = getStrategyFusePoolId(symbol);
+
+ console.log({ poolId , symbol});
+
+ const dir = createFusePoolDirectory(provider);
+ const { name } = await dir.callStatic.pools(poolId);
+
+ // Get pool name
+ let result: FuseERC4626Strategy = {
+ name: filterPoolName(name),
+ symbol,
+ fToken,
+ underlying,
+ comptroller,
+ supplyRatePerBlock: supplyRatePerBlock.toNumber(),
+ };
+
+ return result;
+};
diff --git a/src/hooks/turbo/useTrustedStrategies.ts b/src/hooks/turbo/useTrustedStrategies.ts
new file mode 100644
index 00000000..6991691d
--- /dev/null
+++ b/src/hooks/turbo/useTrustedStrategies.ts
@@ -0,0 +1,18 @@
+import { useQuery } from "react-query";
+import { getBoostableStrategies } from "lib/turbo/fetchers/strategies/getBoostableStrategies";
+import { useRari } from "context/RariContext";
+
+// Trusted Strategies will be independent of any Safe and whitelisted by TRIBE Governance
+export const useTrustedStrategies = (): string[] => {
+ const { provider, chainId } = useRari();
+
+ const { data: trustedStrategies } = useQuery(
+ `Boostable strategies`,
+ async () => {
+ if (!provider || !chainId) return;
+ return await getBoostableStrategies(provider, chainId);
+ }
+ );
+
+ return trustedStrategies ?? [];
+};
diff --git a/src/hooks/turbo/useUpdatedSafeInfo.ts b/src/hooks/turbo/useUpdatedSafeInfo.ts
new file mode 100644
index 00000000..8b59485b
--- /dev/null
+++ b/src/hooks/turbo/useUpdatedSafeInfo.ts
@@ -0,0 +1,302 @@
+import { useQuery } from "react-query";
+import { useRari } from "context/RariContext";
+import { BigNumber, constants } from "ethers";
+import {
+ calculateLiquidationPriceUSD,
+ USDPricedStrategy,
+ USDPricedTurboSafe,
+} from "lib/turbo/fetchers/safes/getUSDPricedSafeInfo";
+import {
+ calculateETHValueUSD,
+ calculateFEIValueUSD,
+} from "lib/turbo/utils/usdUtils";
+import { getEthUsdPriceBN } from "esm/utils/getUSDPriceBN";
+import {
+ calcuateLiquidationPrice,
+ calculateMaxBoost,
+ calculateSafeUtilization,
+} from "lib/turbo/fetchers/safes/getSafeInfo";
+
+export enum SafeInteractionMode {
+ DEPOSIT = "Deposit",
+ WITHDRAW = "Withdraw",
+ BOOST = "Boost",
+ LESS = "Less",
+}
+
+// Preivews a safe position based on action taken and amount
+// TODO(@nathanhleung): Possibly find ways to optimize this query
+export const useUpdatedSafeInfo = ({
+ mode,
+ safe,
+ amount,
+ strategyIndex,
+}: {
+ mode: SafeInteractionMode;
+ safe: USDPricedTurboSafe | undefined;
+ amount: BigNumber;
+ strategyIndex?: number;
+}): USDPricedTurboSafe | undefined => {
+ const { provider, chainId, fuse } = useRari();
+
+ const { data: updatedSafeInfo } = useQuery(
+ `Updated safe info for ${
+ safe?.safeAddress
+ } for mode ${mode} and amount ${amount.toString()}`,
+ async () => {
+ if (!provider || !chainId || !safe) return;
+
+ const ethUSDBN = await getEthUsdPriceBN();
+
+ let updatedSafe: USDPricedTurboSafe;
+
+ /** DEPOSIT **/
+ if (mode === SafeInteractionMode.DEPOSIT) {
+ const collateralAmount = safe.collateralAmount.add(amount);
+ const collateralValue = collateralAmount
+ .mul(safe.collateralPrice)
+ .div(constants.WeiPerEther);
+ const collateralValueUSD = calculateETHValueUSD(
+ collateralValue,
+ ethUSDBN
+ );
+ const safeUtilization = calculateSafeUtilization(
+ safe.debtValue,
+ collateralValue,
+ safe.collateralFactor
+ );
+ const maxBoost = calculateMaxBoost(
+ collateralValue,
+ safe.collateralFactor
+ );
+ const maxBoostUSD = calculateETHValueUSD(maxBoost, ethUSDBN);
+
+ const liquidationPrice = calcuateLiquidationPrice(
+ safe.debtValue,
+ collateralValue,
+ safe.collateralFactor,
+ safe.collateralPrice
+ );
+
+ const liquidationPriceUSD = calculateLiquidationPriceUSD(
+ liquidationPrice,
+ ethUSDBN
+ );
+
+ updatedSafe = {
+ ...safe,
+ collateralAmount,
+ collateralValue,
+ collateralValueUSD,
+ safeUtilization,
+ maxBoost,
+ maxBoostUSD,
+ liquidationPrice,
+ liquidationPriceUSD,
+ };
+
+ return updatedSafe;
+ }
+
+ /** WITHDRAW **/
+ if (mode === SafeInteractionMode.WITHDRAW) {
+ const collateralAmount = safe.collateralAmount.sub(amount);
+ const collateralValue = collateralAmount
+ .mul(safe.collateralPrice)
+ .div(constants.WeiPerEther);
+ const collateralValueUSD = calculateETHValueUSD(
+ collateralValue,
+ ethUSDBN
+ );
+ const safeUtilization = calculateSafeUtilization(
+ safe.debtValue,
+ collateralValue,
+ safe.collateralFactor
+ );
+
+ const liquidationPrice = calcuateLiquidationPrice(
+ safe.debtValue,
+ collateralValue,
+ safe.collateralFactor,
+ safe.collateralPrice
+ );
+
+ const liquidationPriceUSD = calculateLiquidationPriceUSD(
+ liquidationPrice,
+ ethUSDBN
+ );
+
+ const maxBoost = calculateMaxBoost(
+ collateralValue,
+ safe.collateralFactor
+ );
+ const maxBoostUSD = calculateETHValueUSD(maxBoost, ethUSDBN);
+
+ updatedSafe = {
+ ...safe,
+ collateralAmount,
+ collateralValue,
+ collateralValueUSD,
+ safeUtilization,
+ liquidationPrice,
+ liquidationPriceUSD,
+ maxBoost,
+ maxBoostUSD,
+ };
+
+ return updatedSafe;
+ }
+
+ /** BOOST **/
+ if (mode === SafeInteractionMode.BOOST) {
+ console.log({ strategyIndex });
+
+ if (strategyIndex === undefined || strategyIndex < 0) return undefined;
+ const strategyToUpdate = safe.usdPricedStrategies[strategyIndex];
+
+ const boostedAmount = safe.boostedAmount.add(amount); // boosted FEI
+ const boostedUSD = calculateFEIValueUSD(
+ boostedAmount,
+ safe.feiPrice,
+ ethUSDBN
+ );
+ const debtAmount = safe.debtAmount.add(amount);
+ const debtValue = debtAmount
+ .mul(safe.feiPrice)
+ .div(constants.WeiPerEther);
+
+ const safeUtilization = calculateSafeUtilization(
+ debtValue,
+ safe.collateralValue,
+ safe.collateralFactor
+ );
+
+ const stratBoostedAmount = strategyToUpdate.boostedAmount.add(amount);
+ const stratBoostedUSD = calculateFEIValueUSD(
+ stratBoostedAmount,
+ safe.feiPrice,
+ ethUSDBN
+ );
+ const stratFeiAmount = strategyToUpdate.feiAmount.add(amount);
+ const stratFeiUSD = calculateFEIValueUSD(
+ stratFeiAmount,
+ safe.feiPrice,
+ ethUSDBN
+ );
+
+ const updatedStrategy: USDPricedStrategy = {
+ ...strategyToUpdate,
+ boostedAmount: stratBoostedAmount,
+ boostAmountUSD: stratBoostedUSD,
+ feiAmount: stratFeiAmount,
+ feiAmountUSD: stratFeiUSD,
+ };
+
+ const strategies = [...safe.usdPricedStrategies];
+ strategies[strategyIndex] = updatedStrategy;
+
+ const liquidationPrice = calcuateLiquidationPrice(
+ debtValue,
+ safe.collateralValue,
+ safe.collateralFactor,
+ safe.collateralPrice
+ );
+ const liquidationPriceUSD = calculateLiquidationPriceUSD(
+ liquidationPrice,
+ ethUSDBN
+ );
+
+ updatedSafe = {
+ ...safe,
+ boostedAmount,
+ boostedUSD,
+ debtAmount,
+ debtValue,
+ safeUtilization,
+ strategies,
+ liquidationPrice,
+ liquidationPriceUSD,
+ };
+
+ return updatedSafe;
+ }
+
+ /** LESS **/
+ if (mode === SafeInteractionMode.LESS) {
+ if (strategyIndex === undefined || strategyIndex < 0) return undefined;
+
+ const boostedAmount = safe.boostedAmount.sub(amount); // boosted FEI
+ const boostedUSD = calculateFEIValueUSD(
+ boostedAmount,
+ safe.feiPrice,
+ ethUSDBN
+ );
+ const debtAmount = safe.debtAmount.sub(amount);
+ const debtValue = debtAmount
+ .mul(safe.feiPrice)
+ .div(constants.WeiPerEther);
+
+ console.log({ boostedAmount, boostedUSD, debtAmount, debtValue });
+
+ const safeUtilization = calculateSafeUtilization(
+ debtValue,
+ safe.collateralValue,
+ safe.collateralFactor
+ );
+
+ const strategyToUpdate = safe.usdPricedStrategies[strategyIndex];
+
+ const stratBoostedAmount = strategyToUpdate.boostedAmount.sub(amount);
+ const stratBoostedUSD = calculateFEIValueUSD(
+ stratBoostedAmount,
+ safe.feiPrice,
+ ethUSDBN
+ );
+ const stratFeiAmount = strategyToUpdate.feiAmount.sub(amount);
+ const stratFeiUSD = calculateFEIValueUSD(
+ stratFeiAmount,
+ safe.feiPrice,
+ ethUSDBN
+ );
+
+ const updatedStrategy: USDPricedStrategy = {
+ ...strategyToUpdate,
+ boostedAmount: stratBoostedAmount,
+ boostAmountUSD: stratBoostedUSD,
+ feiAmount: stratFeiAmount,
+ feiAmountUSD: stratFeiUSD,
+ };
+
+ const strategies = safe.usdPricedStrategies;
+ strategies[strategyIndex] = updatedStrategy;
+
+ const liquidationPrice = calcuateLiquidationPrice(
+ debtValue,
+ safe.collateralValue,
+ safe.collateralFactor,
+ safe.collateralPrice
+ );
+ const liquidationPriceUSD = calculateLiquidationPriceUSD(
+ liquidationPrice,
+ ethUSDBN
+ );
+
+ updatedSafe = {
+ ...safe,
+ boostedAmount,
+ boostedUSD,
+ debtAmount,
+ debtValue,
+ safeUtilization,
+ strategies,
+ liquidationPrice,
+ liquidationPriceUSD,
+ };
+
+ return updatedSafe;
+ }
+ }
+ );
+
+ return updatedSafeInfo;
+};
diff --git a/src/hooks/turbo/useUpdatedStrategyRate.ts b/src/hooks/turbo/useUpdatedStrategyRate.ts
new file mode 100644
index 00000000..f816f4f1
--- /dev/null
+++ b/src/hooks/turbo/useUpdatedStrategyRate.ts
@@ -0,0 +1,56 @@
+import { useRari } from "context/RariContext";
+import { BigNumber, constants } from "ethers";
+import { parseEther } from "ethers/lib/utils";
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+import { useQuery } from "react-query";
+import { fetchStrategyData } from "./useStrategyInfo";
+import { SafeInteractionMode } from "./useUpdatedSafeInfo";
+
+const useUpdatedStrategyRate = (
+ mode: SafeInteractionMode.BOOST | SafeInteractionMode.LESS,
+ safe: SafeInfo | undefined,
+ strategyIndex: number,
+ amountBN: BigNumber
+) => {
+ const { fuse, provider } = useRari();
+
+ const { data: updatedStrategyRates } = useQuery(
+ `Updated strategy APY for safe ${
+ safe?.safeAddress
+ } strategy ${strategyIndex} mode ${mode} and amount ${amountBN.toString()}`,
+ async () => {
+ const strategy = safe?.strategies[strategyIndex];
+ if (!safe || !strategy) return;
+
+ // Get CToken from strategy
+ const fuseStrategy = await fetchStrategyData(provider, strategy.strategy);
+ const irm = fuse.getInterestRateModel(fuseStrategy.fToken);
+ let supplyRatePerBlock = fuseStrategy.supplyRatePerBlock;
+
+ const collateralValue = safe.collateralValue;
+
+ const debtAmount =
+ mode === SafeInteractionMode.BOOST
+ ? safe.debtAmount.add(amountBN)
+ : safe.debtAmount.sub(amountBN);
+
+ const debtValue = debtAmount
+ .mul(safe.feiPrice)
+ .div(constants.WeiPerEther);
+
+ // console.log({ amountBN, collateralValue, debtAmount, debtValue });
+
+ // let newSupplyRatePerBlock = await irm.getSupplyRate(
+ // totalSupply.gt(0)
+ // ? assetToBeUpdated.totalBorrow
+ // .mul(constants.WeiPerEther)
+ // .div(totalSupply)
+ // : constants.Zero
+ // );
+ }
+ );
+
+ return updatedStrategyRates;
+};
+
+export default useUpdatedStrategyRate;
diff --git a/src/hooks/turbo/useUserFeiOwed.ts b/src/hooks/turbo/useUserFeiOwed.ts
new file mode 100644
index 00000000..76ea5f86
--- /dev/null
+++ b/src/hooks/turbo/useUserFeiOwed.ts
@@ -0,0 +1,14 @@
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+import { useBalanceOf } from "hooks/useBalanceOf";
+import { FEI } from "lib/turbo/utils/constants";
+import { getUserFeiOwed } from "lib/turbo/utils/getUserFeiOwed";
+
+// Safe Balance of FEI is external to SafeInfo right now. Need to use this hook to add the safeFeiBalance
+export const useUserFeiOwed = (safe: SafeInfo | undefined) => {
+ const safeBalance = useBalanceOf(safe?.safeAddress, FEI)
+ const claimableFromStrategies = getUserFeiOwed(safe)
+ const total = claimableFromStrategies.add(safeBalance)
+ return [total, claimableFromStrategies, safeBalance]
+};
+
+
diff --git a/src/hooks/turbo/useUserSafes.ts b/src/hooks/turbo/useUserSafes.ts
new file mode 100644
index 00000000..a14c8687
--- /dev/null
+++ b/src/hooks/turbo/useUserSafes.ts
@@ -0,0 +1,27 @@
+import { useQuery } from "react-query";
+
+import { useRari } from "context/RariContext";
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+import { getAllUserSafes } from "lib/turbo/fetchers/safes/getAllUserSafes";
+import { MOCK_SAFE_1, MOCK_SAFE_2 } from "./test/mocks";
+
+export const useAllUserSafes = (providedAddress?: string): SafeInfo[] | undefined => {
+ if (process.env.NEXT_PUBLIC_USE_MOCKS === "true") {
+ return [MOCK_SAFE_1, MOCK_SAFE_2];
+ }
+
+ const { address, provider, chainId } = useRari()
+
+ let addressToUse = providedAddress ?? address
+
+ const { data: safes } = useQuery(
+ `User: ${addressToUse} safes`,
+ async () => {
+ if(!addressToUse || !chainId) return
+ return await getAllUserSafes(provider, addressToUse, chainId)
+ }
+ );
+ return safes;
+};
+
+
diff --git a/src/hooks/useBalanceOf.ts b/src/hooks/useBalanceOf.ts
new file mode 100644
index 00000000..b8742d1b
--- /dev/null
+++ b/src/hooks/useBalanceOf.ts
@@ -0,0 +1,53 @@
+import { useQueries, useQuery, UseQueryResult } from "react-query";
+import { balanceOf } from "utils/erc20Utils";
+import { BigNumber, constants } from "ethers";
+import { useRari } from "context/RariContext";
+
+
+const fetchBalanceOf = async (provider: any, holder: string | undefined, tokenAddress: string | undefined) => {
+ if (!holder || !tokenAddress) return constants.Zero;
+ const balance = await balanceOf(holder, tokenAddress, provider);
+ return balance;
+}
+
+export const useBalanceOf = (
+ holder: string | undefined,
+ tokenAddress: string | undefined
+) => {
+ const { provider } = useRari();
+ const { data: balance } = useQuery(
+ `${holder} balance of ${tokenAddress}`,
+ async () => await fetchBalanceOf(provider, holder, tokenAddress)
+ );
+
+ return balance ?? constants.Zero;
+};
+
+export type BalancesMap = {
+ [account: string]: BigNumber
+}
+export const useBalancesOfMultipleAddresses = (
+ holders: string[],
+ tokenAddress: string,
+): BalancesMap => {
+ const { provider } = useRari();
+ const result: UseQueryResult[] = useQueries(
+ holders.map(holder => {
+ return {
+ queryKey: `${holder} balance of ${tokenAddress}`,
+ queryFn: async () => await fetchBalanceOf(provider, holder, tokenAddress)
+ }
+
+ })
+ )
+
+ const balancesMap: BalancesMap = result.reduce((acc: BalancesMap, r, i) => {
+ const { data } = r
+ return {
+ ...acc,
+ [holders[i]]: data as BigNumber
+ }
+ }, {})
+
+ return balancesMap
+};
diff --git a/src/hooks/useETHUSDBN.ts b/src/hooks/useETHUSDBN.ts
index 6808319b..beea1397 100644
--- a/src/hooks/useETHUSDBN.ts
+++ b/src/hooks/useETHUSDBN.ts
@@ -1,9 +1,11 @@
import { getEthUsdPriceBN } from "esm/utils/getUSDPriceBN";
+import React from "react";
import { useQuery } from "react-query";
const useETHUSDBN = () => {
- const { data: ethUSDPriceBN } = useQuery("ETHUSD", async () =>
- getEthUsdPriceBN()
+ const { data: ethUSDPriceBN } = useQuery(
+ "ETHUSDBN",
+ async () => await getEthUsdPriceBN()
);
return ethUSDPriceBN;
};
diff --git a/src/hooks/useHasApproval.ts b/src/hooks/useHasApproval.ts
new file mode 100644
index 00000000..7b266408
--- /dev/null
+++ b/src/hooks/useHasApproval.ts
@@ -0,0 +1,41 @@
+import { useQuery } from "react-query";
+import { checkAllowance } from "utils/erc20Utils";
+import { useRari } from "context/RariContext";
+import { parseEther } from "ethers/lib/utils";
+import { isSupportedChainId } from "esm/utils/networks";
+
+const useHasApproval = (
+ underlyingToken: string | undefined,
+ spender: string | undefined,
+ amount: string,
+ userAddress?: string
+) => {
+ const { address, chainId, provider } = useRari();
+ const addressToUse = userAddress ?? address;
+
+ const { data } = useQuery(
+ ` ${spender} has approval to spend ${underlyingToken} ${amount} on behalf of ${addressToUse} on chain ${chainId}`,
+ async () => {
+ if (
+ !addressToUse ||
+ !chainId ||
+ !spender ||
+ !underlyingToken ||
+ !isSupportedChainId(chainId)
+ )
+ return false;
+ if (amount === "" || amount === "0") return true;
+
+ return await checkAllowance(
+ provider,
+ addressToUse,
+ spender,
+ underlyingToken,
+ parseEther(amount)
+ );
+ }
+ );
+ return data ?? false;
+};
+
+export default useHasApproval;
diff --git a/src/hooks/usePriceUSD.ts b/src/hooks/usePriceUSD.ts
new file mode 100644
index 00000000..dd290961
--- /dev/null
+++ b/src/hooks/usePriceUSD.ts
@@ -0,0 +1,18 @@
+import { useQuery } from "react-query";
+import { BigNumber } from "ethers";
+import { getEthUsdPriceBN } from "esm/utils/getUSDPriceBN";
+
+export const usePriceUSD = (
+ priceETH: BigNumber,
+): number | undefined => {
+ const { data: priceUSD } = useQuery(
+ `price ${priceETH} in USD`,
+ async () => {
+ if (!priceETH) return
+ const ethUSD = await getEthUsdPriceBN();
+ const priceUSD = parseFloat(ethUSD.toString()) * parseFloat(priceETH.toString()) / 1e36
+ return priceUSD
+ }
+ );
+ return priceUSD;
+};
diff --git a/src/hooks/useTokenData.ts b/src/hooks/useTokenData.ts
index 35269137..6ad52f26 100644
--- a/src/hooks/useTokenData.ts
+++ b/src/hooks/useTokenData.ts
@@ -65,8 +65,6 @@ export const fetchTokenData = async (
_chainId = 1;
}
- // console.log('fetchTokenData',{address, chainId, _chainid})
-
if (address !== ETH_TOKEN_DATA.address) {
try {
// Since running the vercel functions requires a Vercel account and is super slow,
diff --git a/src/index.css b/src/index.css
index 0e884b6e..6bc858a5 100644
--- a/src/index.css
+++ b/src/index.css
@@ -10,6 +10,7 @@ html {
height: 100% !important;
/* background-color: #000000 !important; */
overflow-y: scroll;
+ scroll-behavior: smooth;
}
* {
diff --git a/src/lib/turbo/abi/Authority.json b/src/lib/turbo/abi/Authority.json
new file mode 100644
index 00000000..2d722898
--- /dev/null
+++ b/src/lib/turbo/abi/Authority.json
@@ -0,0 +1,33 @@
+{
+ "abi": [
+ {
+ "type": "function",
+ "name": "canCall",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ },
+ {
+ "internalType": "bytes4",
+ "name": "functionSig",
+ "type": "bytes4"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/CERC20.json b/src/lib/turbo/abi/CERC20.json
new file mode 100644
index 00000000..4731e475
--- /dev/null
+++ b/src/lib/turbo/abi/CERC20.json
@@ -0,0 +1,446 @@
+{
+ "abi": [
+ {
+ "type": "function",
+ "name": "DOMAIN_SEPARATOR",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "allowance",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "approve",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "balanceOf",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "balanceOfUnderlying",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "borrow",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "underlyingAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "borrowBalanceCurrent",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "decimals",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "exchangeRateStored",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "mint",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "underlyingAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "name",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "nonces",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "permit",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "deadline",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint8",
+ "name": "v",
+ "type": "uint8"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "r",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "s",
+ "type": "bytes32"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "redeemUnderlying",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "underlyingAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "repayBorrow",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "underlyingAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "repayBorrowBehalf",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "underlyingAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "symbol",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "totalSupply",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "transfer",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "transferFrom",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "event",
+ "name": "Approval",
+ "inputs": [
+ {
+ "name": "owner",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "spender",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "Transfer",
+ "inputs": [
+ {
+ "name": "from",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "to",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/CERC20Delegate.json b/src/lib/turbo/abi/CERC20Delegate.json
new file mode 100644
index 00000000..e6b1248b
--- /dev/null
+++ b/src/lib/turbo/abi/CERC20Delegate.json
@@ -0,0 +1,1637 @@
+[
+ {
+ "inputs": [
+
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "cashPrior",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "interestAccumulated",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "borrowIndex",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "totalBorrows",
+ "type": "uint256"
+ }
+ ],
+ "name": "AccrueInterest",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "borrowAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "accountBorrows",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "totalBorrows",
+ "type": "uint256"
+ }
+ ],
+ "name": "Borrow",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "error",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "info",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "detail",
+ "type": "uint256"
+ }
+ ],
+ "name": "Failure",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "liquidator",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "repayAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "cTokenCollateral",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "seizeTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "LiquidateBorrow",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "minter",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "mintAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "mintTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "Mint",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "oldAdminFeeMantissa",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newAdminFeeMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "NewAdminFee",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contractComptrollerInterface",
+ "name": "oldComptroller",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "contractComptrollerInterface",
+ "name": "newComptroller",
+ "type": "address"
+ }
+ ],
+ "name": "NewComptroller",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "oldFuseFeeMantissa",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newFuseFeeMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "NewFuseFee",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "oldImplementation",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "newImplementation",
+ "type": "address"
+ }
+ ],
+ "name": "NewImplementation",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contractInterestRateModel",
+ "name": "oldInterestRateModel",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "contractInterestRateModel",
+ "name": "newInterestRateModel",
+ "type": "address"
+ }
+ ],
+ "name": "NewMarketInterestRateModel",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "oldReserveFactorMantissa",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newReserveFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "NewReserveFactor",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "redeemer",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "redeemAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "redeemTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "Redeem",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "payer",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "repayAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "accountBorrows",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "totalBorrows",
+ "type": "uint256"
+ }
+ ],
+ "name": "RepayBorrow",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "benefactor",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "addAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newTotalReserves",
+ "type": "uint256"
+ }
+ ],
+ "name": "ReservesAdded",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "admin",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "reduceAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newTotalReserves",
+ "type": "uint256"
+ }
+ ],
+ "name": "ReservesReduced",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "_becomeImplementation",
+ "outputs": [
+
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "compLikeDelegatee",
+ "type": "address"
+ }
+ ],
+ "name": "_delegateCompLikeTo",
+ "outputs": [
+
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+
+ ],
+ "name": "_prepare",
+ "outputs": [
+
+ ],
+ "payable": true,
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "reduceAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "_reduceReserves",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "newAdminFeeMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "_setAdminFee",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "implementation_",
+ "type": "address"
+ },
+ {
+ "internalType": "bool",
+ "name": "allowResign",
+ "type": "bool"
+ },
+ {
+ "internalType": "bytes",
+ "name": "becomeImplementationData",
+ "type": "bytes"
+ }
+ ],
+ "name": "_setImplementationSafe",
+ "outputs": [
+
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contractInterestRateModel",
+ "name": "newInterestRateModel",
+ "type": "address"
+ }
+ ],
+ "name": "_setInterestRateModel",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "string",
+ "name": "_name",
+ "type": "string"
+ },
+ {
+ "internalType": "string",
+ "name": "_symbol",
+ "type": "string"
+ }
+ ],
+ "name": "_setNameAndSymbol",
+ "outputs": [
+
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "newReserveFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "_setReserveFactor",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "withdrawAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "_withdrawAdminFees",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "withdrawAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "_withdrawFuseFees",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "accrualBlockNumber",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+
+ ],
+ "name": "accrueInterest",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "adminFeeMantissa",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ }
+ ],
+ "name": "allowance",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "approve",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "name": "balanceOfUnderlying",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "borrowAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "borrow",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "borrowBalanceCurrent",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "borrowBalanceStored",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "borrowIndex",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "borrowRatePerBlock",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "comptroller",
+ "outputs": [
+ {
+ "internalType": "contractComptrollerInterface",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "decimals",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+
+ ],
+ "name": "exchangeRateCurrent",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "exchangeRateStored",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "fuseFeeMantissa",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "getAccountSnapshot",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "getCash",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "implementation",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contractComptrollerInterface",
+ "name": "comptroller_",
+ "type": "address"
+ },
+ {
+ "internalType": "contractInterestRateModel",
+ "name": "interestRateModel_",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "initialExchangeRateMantissa_",
+ "type": "uint256"
+ },
+ {
+ "internalType": "string",
+ "name": "name_",
+ "type": "string"
+ },
+ {
+ "internalType": "string",
+ "name": "symbol_",
+ "type": "string"
+ },
+ {
+ "internalType": "uint8",
+ "name": "decimals_",
+ "type": "uint8"
+ },
+ {
+ "internalType": "uint256",
+ "name": "reserveFactorMantissa_",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "adminFeeMantissa_",
+ "type": "uint256"
+ }
+ ],
+ "name": "initialize",
+ "outputs": [
+
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "underlying_",
+ "type": "address"
+ },
+ {
+ "internalType": "contractComptrollerInterface",
+ "name": "comptroller_",
+ "type": "address"
+ },
+ {
+ "internalType": "contractInterestRateModel",
+ "name": "interestRateModel_",
+ "type": "address"
+ },
+ {
+ "internalType": "string",
+ "name": "name_",
+ "type": "string"
+ },
+ {
+ "internalType": "string",
+ "name": "symbol_",
+ "type": "string"
+ },
+ {
+ "internalType": "uint256",
+ "name": "reserveFactorMantissa_",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "adminFeeMantissa_",
+ "type": "uint256"
+ }
+ ],
+ "name": "initialize",
+ "outputs": [
+
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "interestRateModel",
+ "outputs": [
+ {
+ "internalType": "contractInterestRateModel",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "isCEther",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "isCToken",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "repayAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "contractCTokenInterface",
+ "name": "cTokenCollateral",
+ "type": "address"
+ }
+ ],
+ "name": "liquidateBorrow",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "mintAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "mint",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "name",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "protocolSeizeShareMantissa",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "redeemTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "redeem",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "redeemAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "redeemUnderlying",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "repayAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "repayBorrow",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "repayAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "repayBorrowBehalf",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "reserveFactorMantissa",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "liquidator",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "seizeTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "seize",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "supplyRatePerBlock",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "symbol",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "totalAdminFees",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "totalBorrows",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+
+ ],
+ "name": "totalBorrowsCurrent",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "totalFuseFees",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "totalReserves",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "totalSupply",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "dst",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "transfer",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "src",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "dst",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "transferFrom",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+
+ ],
+ "name": "underlying",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ }
+ ]
\ No newline at end of file
diff --git a/src/lib/turbo/abi/ERC20.json b/src/lib/turbo/abi/ERC20.json
new file mode 100644
index 00000000..e7b0bd7a
--- /dev/null
+++ b/src/lib/turbo/abi/ERC20.json
@@ -0,0 +1,295 @@
+{
+ "abi": [
+ {
+ "type": "function",
+ "name": "DOMAIN_SEPARATOR",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "allowance",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "approve",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "balanceOf",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "decimals",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "name",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "nonces",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "permit",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "deadline",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint8",
+ "name": "v",
+ "type": "uint8"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "r",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "s",
+ "type": "bytes32"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "symbol",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "totalSupply",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "transfer",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "transferFrom",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "event",
+ "name": "Approval",
+ "inputs": [
+ {
+ "name": "owner",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "spender",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "Transfer",
+ "inputs": [
+ {
+ "name": "from",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "to",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/FuseERC4626.json b/src/lib/turbo/abi/FuseERC4626.json
new file mode 100644
index 00000000..8556e713
--- /dev/null
+++ b/src/lib/turbo/abi/FuseERC4626.json
@@ -0,0 +1 @@
+[{"inputs":[{"internalType":"address","name":"_cToken","type":"address"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cToken","outputs":[{"internalType":"contract CToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cTokenUnderlying","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unitroller","outputs":[{"internalType":"contract Unitroller","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]
\ No newline at end of file
diff --git a/src/lib/turbo/abi/TurboAdmin.json b/src/lib/turbo/abi/TurboAdmin.json
new file mode 100644
index 00000000..26e081fb
--- /dev/null
+++ b/src/lib/turbo/abi/TurboAdmin.json
@@ -0,0 +1,762 @@
+{
+ "abi": [
+ {
+ "type": "constructor",
+ "inputs": [
+ {
+ "internalType": "contract Comptroller",
+ "name": "_comptroller",
+ "type": "address"
+ },
+ {
+ "internalType": "contract TimelockController",
+ "name": "_timelock",
+ "type": "address"
+ },
+ {
+ "internalType": "contract Authority",
+ "name": "_authority",
+ "type": "address"
+ }
+ ]
+ },
+ {
+ "type": "function",
+ "name": "CTOKEN_IMPL",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "REVERSE_REGISTRAR",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract IReverseRegistrar",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "ZERO_IRM",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "_acceptAdmin",
+ "inputs": [],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_addRewardsDistributor",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "distributor",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_deployMarket",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "underlying",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "irm",
+ "type": "address"
+ },
+ {
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "internalType": "string",
+ "name": "symbol",
+ "type": "string"
+ },
+ {
+ "internalType": "address",
+ "name": "impl",
+ "type": "address"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "internalType": "uint256",
+ "name": "reserveFactor",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "adminFee",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "collateralFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setBorrowCapGuardian",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newBorrowCapGuardian",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setBorrowPaused",
+ "inputs": [
+ {
+ "internalType": "contract CERC20",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "bool",
+ "name": "state",
+ "type": "bool"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setBorrowPausedByUnderlying",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "underlying",
+ "type": "address"
+ },
+ {
+ "internalType": "bool",
+ "name": "state",
+ "type": "bool"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setCloseFactor",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "newCloseFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setCollateralFactor",
+ "inputs": [
+ {
+ "internalType": "contract CERC20",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "newCollateralFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setLiquidationIncentive",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "newLiquidationIncentiveMantissa",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setMarketBorrowCaps",
+ "inputs": [
+ {
+ "internalType": "contract CERC20[]",
+ "name": "cTokens",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "newBorrowCaps",
+ "type": "uint256[]"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setMarketBorrowCapsByUnderlying",
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "underlyings",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "newBorrowCaps",
+ "type": "uint256[]"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setMarketSupplyCaps",
+ "inputs": [
+ {
+ "internalType": "contract CERC20[]",
+ "name": "cTokens",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "newSupplyCaps",
+ "type": "uint256[]"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setMarketSupplyCapsByUnderlying",
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "underlyings",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "newSupplyCaps",
+ "type": "uint256[]"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setMintPaused",
+ "inputs": [
+ {
+ "internalType": "contract CERC20",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "bool",
+ "name": "state",
+ "type": "bool"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setMintPausedByUnderlying",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "underlying",
+ "type": "address"
+ },
+ {
+ "internalType": "bool",
+ "name": "state",
+ "type": "bool"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setPauseGuardian",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newPauseGuardian",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setPendingAdmin",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newPendingAdmin",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setPriceOracle",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOracle",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setSeizePaused",
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "state",
+ "type": "bool"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setTransferPaused",
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "state",
+ "type": "bool"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setWhitelistEnforcement",
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "enforce",
+ "type": "bool"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_setWhitelistStatuses",
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "suppliers",
+ "type": "address[]"
+ },
+ {
+ "internalType": "bool[]",
+ "name": "statuses",
+ "type": "bool[]"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_toggleAutoImplementations",
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "enabled",
+ "type": "bool"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "_unsupportMarket",
+ "inputs": [
+ {
+ "internalType": "contract CERC20",
+ "name": "cToken",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "addCollateral",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "underlying",
+ "type": "address"
+ },
+ {
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "internalType": "string",
+ "name": "symbol",
+ "type": "string"
+ },
+ {
+ "internalType": "uint256",
+ "name": "collateralFactorMantissa",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "supplyCap",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "authority",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "cancel",
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "id",
+ "type": "bytes32"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "comptroller",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Comptroller",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "execute",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "oracleAdd",
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "underlyings",
+ "type": "address[]"
+ },
+ {
+ "internalType": "address[]",
+ "name": "_oracles",
+ "type": "address[]"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "oracleChangeAdmin",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newAdmin",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "owner",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "schedule",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "scheduleSetPendingAdmin",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newPendingAdmin",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setAuthority",
+ "inputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "newAuthority",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setENSName",
+ "inputs": [
+ {
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setOwner",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "timelock",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract TimelockController",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "event",
+ "name": "AuthorityUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newAuthority",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "OwnerUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newOwner",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "error",
+ "name": "ComptrollerError",
+ "inputs": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/TurboBooster.json b/src/lib/turbo/abi/TurboBooster.json
new file mode 100644
index 00000000..fcb0d457
--- /dev/null
+++ b/src/lib/turbo/abi/TurboBooster.json
@@ -0,0 +1,368 @@
+{
+ "abi": [
+ {
+ "type": "constructor",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "internalType": "contract Authority",
+ "name": "_authority",
+ "type": "address"
+ }
+ ]
+ },
+ {
+ "type": "function",
+ "name": "REVERSE_REGISTRAR",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract IReverseRegistrar",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "authority",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "boostableVaults",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "contract ERC4626",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "canSafeBoostVault",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC20",
+ "name": "collateral",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "feiAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "newTotalBoostedForVault",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "newTotalBoostedAgainstCollateral",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "frozen",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getBoostCapForCollateral",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getBoostCapForVault",
+ "inputs": [
+ {
+ "internalType": "contract ERC4626",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getBoostableVaults",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract ERC4626[]",
+ "name": "",
+ "type": "address[]"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "owner",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "setAuthority",
+ "inputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "newAuthority",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setBoostCapForCollateral",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "collateral",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "newBoostCap",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setBoostCapForVault",
+ "inputs": [
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "newBoostCap",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setENSName",
+ "inputs": [
+ {
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setFreezeStatus",
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "freeze",
+ "type": "bool"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setOwner",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "event",
+ "name": "AuthorityUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newAuthority",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "BoostCapUpdatedForCollateral",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "collateral",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newBoostCap",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "BoostCapUpdatedForVault",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "vault",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newBoostCap",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "FreezeStatusUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "frozen",
+ "type": "bool",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "OwnerUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newOwner",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/TurboClerk.json b/src/lib/turbo/abi/TurboClerk.json
new file mode 100644
index 00000000..9338d898
--- /dev/null
+++ b/src/lib/turbo/abi/TurboClerk.json
@@ -0,0 +1,316 @@
+{
+ "abi": [
+ {
+ "type": "constructor",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "internalType": "contract Authority",
+ "name": "_authority",
+ "type": "address"
+ }
+ ]
+ },
+ {
+ "type": "function",
+ "name": "REVERSE_REGISTRAR",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract IReverseRegistrar",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "authority",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "defaultFeePercentage",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getCustomFeePercentageForCollateral",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getCustomFeePercentageForSafe",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getFeePercentageForSafe",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC20",
+ "name": "collateral",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "owner",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "setAuthority",
+ "inputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "newAuthority",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setCustomFeePercentageForCollateral",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "collateral",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "newFeePercentage",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setCustomFeePercentageForSafe",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "newFeePercentage",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setDefaultFeePercentage",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "newDefaultFeePercentage",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setENSName",
+ "inputs": [
+ {
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setOwner",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "event",
+ "name": "AuthorityUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newAuthority",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "CustomFeePercentageUpdatedForCollateral",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "collateral",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newFeePercentage",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "CustomFeePercentageUpdatedForSafe",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "safe",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newFeePercentage",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "DefaultFeePercentageUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newDefaultFeePercentage",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "OwnerUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newOwner",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/TurboLens.json b/src/lib/turbo/abi/TurboLens.json
new file mode 100644
index 00000000..7fac8817
--- /dev/null
+++ b/src/lib/turbo/abi/TurboLens.json
@@ -0,0 +1,238 @@
+{
+ "abi": [
+ {
+ "type": "constructor",
+ "inputs": [
+ {
+ "internalType": "contract TurboMaster",
+ "name": "_master",
+ "type": "address"
+ }
+ ]
+ },
+ {
+ "type": "function",
+ "name": "getAllUserSafes",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "struct TurboLens.SafeInfo[]",
+ "name": "",
+ "type": "tuple[]",
+ "components": [
+ {
+ "type": "address"
+ },
+ {
+ "type": "address"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "tuple[]",
+ "components": [
+ {
+ "type": "address"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "getSafeInfo",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "struct TurboLens.SafeInfo",
+ "name": "",
+ "type": "tuple",
+ "components": [
+ {
+ "type": "address"
+ },
+ {
+ "type": "address"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "tuple[]",
+ "components": [
+ {
+ "type": "address"
+ },
+ {
+ "type": "uint256"
+ },
+ {
+ "type": "uint256"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "master",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract TurboMaster",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "pool",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Comptroller",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ }
+ ],
+ "bytecode": {
+ "object": "0x60c06040523480156200001157600080fd5b50604051620016de380380620016de8339810160408190526200003491620000d1565b6001600160a01b03811660a0819052604080516316f0115b60e01b815290516316f0115b916004808201926020929091908290030181865afa1580156200007f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620000a59190620000d1565b6001600160a01b031660805250620000f8565b6001600160a01b0381168114620000ce57600080fd5b50565b600060208284031215620000e457600080fd5b8151620000f181620000b8565b9392505050565b60805160a05161158a620001546000396000818160da015281816101020152818161024f015281816102d5015281816105bd01526107320152600081816056015281816103c5015281816106b00152610e60015261158a6000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806316f0115b1461005157806369ff32d814610095578063d027f464146100b5578063ee97f7f3146100d5575b600080fd5b6100787f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100a86100a3366004611104565b6100fc565b60405161008c919061123d565b6100c86100c3366004611104565b6105b1565b60405161008c919061129f565b6100787f000000000000000000000000000000000000000000000000000000000000000081565b606060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316638cbf46ae6040518163ffffffff1660e01b8152600401600060405180830381865afa15801561015e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610186919081019061131d565b9050600060015b825181101561024a57846001600160a01b03168382815181106101b2576101b26113bc565b60200260200101516001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101f7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061021b91906113d2565b6001600160a01b0316141561023857610235600183611405565b91505b806102428161141d565b91505061018d565b5060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663c6def0766040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102cf91906113d2565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b87ee7af6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610331573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061035591906113d2565b90506000826001600160a01b0316630225c09e6040518163ffffffff1660e01b8152600401600060405180830381865afa158015610397573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526103bf9190810190611438565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa158015610421573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061044591906113d2565b905060008567ffffffffffffffff811115610462576104626112b2565b60405190808252806020026020018201604052801561049b57816020015b610488611072565b8152602001906001900390816104805790505b509050600060015b88518110156105a2578a6001600160a01b03168982815181106104c8576104c86113bc565b60200260200101516001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561050d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053191906113d2565b6001600160a01b0316141561059057610565898281518110610555576105556113bc565b60200260200101518686896107bf565b838381518110610577576105776113bc565b602090810291909101015261058d600183611405565b91505b8061059a8161141d565b9150506104a3565b50909998505050505050505050565b6105b9611072565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663c6def0766040518163ffffffff1660e01b8152600401602060405180830381865afa158015610619573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061063d91906113d2565b90506000816001600160a01b0316630225c09e6040518163ffffffff1660e01b8152600401600060405180830381865afa15801561067f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526106a79190810190611438565b90506107b784827f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561070c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061073091906113d2565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b87ee7af6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561078e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107b291906113d2565b6107bf565b949350505050565b6107c7611072565b6000845167ffffffffffffffff8111156107e3576107e36112b2565b60405190808252806020026020018201604052801561084157816020015b61082e604051806060016040528060006001600160a01b0316815260200160008152602001600081525090565b8152602001906001900390816108015790505b5090506000805b8651811015610a1c576000878281518110610865576108656113bc565b6020908102919091010151604051635fd35eb960e01b81526001600160a01b0380831660048301529192506000918b1690635fd35eb990602401602060405180830381865afa1580156108bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108e091906114c7565b6040516370a0823160e01b81526001600160a01b038c81166004830152919250600091841690634cdad5069082906370a0823190602401602060405180830381865afa158015610934573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061095891906114c7565b6040518263ffffffff1660e01b815260040161097691815260200190565b602060405180830381865afa158015610993573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109b791906114c7565b90506109c38186611405565b94506040518060600160405280846001600160a01b03168152602001838152602001828152508685815181106109fb576109fb6113bc565b60200260200101819052505050508080610a149061141d565b915050610848565b506000876001600160a01b03166338d52e0f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a5d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a8191906113d2565b60405162ede82760e41b81526001600160a01b038a811660048301528083166024830152919250600091871690630ede827090604401602060405180830381865afa158015610ad4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610af891906114c7565b90506000896001600160a01b031663a1ea7d6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b3a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b5e91906113d2565b6040516305eff7ef60e21b81526001600160a01b038c8116600483015291909116906317bfdfbc906024016020604051808303816000875af1158015610ba8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bcc91906114c7565b90506000886001600160a01b031663fc57d4df8c6001600160a01b031663a1ea7d6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c1d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c4191906113d2565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401602060405180830381865afa158015610c85573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ca991906114c7565b905060008b6001600160a01b0316634cdad5068d6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cfa573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1e91906114c7565b6040518263ffffffff1660e01b8152600401610d3c91815260200190565b602060405180830381865afa158015610d59573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d7d91906114c7565b905060008a6001600160a01b031663fc57d4df8e6001600160a01b031663ec11e1676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610dce573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610df291906113d2565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401602060405180830381865afa158015610e36573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e5a91906114c7565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316638e8f294b8f6001600160a01b031663ec11e1676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ecb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610eef91906113d2565b6040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016040805180830381865afa158015610f32573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f5691906114e0565b915050604051806101a001604052808f6001600160a01b03168152602001886001600160a01b03168152602001848152602001838152602001828152602001858152602001670de0b6b3a76400008486610fb09190611513565b610fba9190611532565b815260208101879052604001670de0b6b3a7640000610fd98789611513565b610fe39190611532565b81526020018f6001600160a01b03166321fcf7506040518163ffffffff1660e01b8152600401602060405180830381865afa158015611026573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061104a91906114c7565b81526020018981526020018781526020018a8152509950505050505050505050949350505050565b604051806101a0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001606081525090565b6001600160a01b038116811461110157600080fd5b50565b60006020828403121561111657600080fd5b8135611121816110ec565b9392505050565b600081518084526020808501945080840160005b8381101561117757815180516001600160a01b031688528381015184890152604090810151908801526060909601959082019060010161113c565b509495945050505050565b80516001600160a01b0316825260006101a060208301516111ae60208601826001600160a01b03169052565b5060408301516040850152606083015160608501526080830151608085015260a083015160a085015260c083015160c085015260e083015160e085015261010080840151818601525061012080840151818601525061014080840151818601525061016080840151818601525061018080840151828287015261123383870182611128565b9695505050505050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b8281101561129257603f19888603018452611280858351611182565b94509285019290850190600101611264565b5092979650505050505050565b6020815260006111216020830184611182565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156112f1576112f16112b2565b604052919050565b600067ffffffffffffffff821115611313576113136112b2565b5060051b60200190565b6000602080838503121561133057600080fd5b825167ffffffffffffffff81111561134757600080fd5b8301601f8101851361135857600080fd5b805161136b611366826112f9565b6112c8565b81815260059190911b8201830190838101908783111561138a57600080fd5b928401925b828410156113b15783516113a2816110ec565b8252928401929084019061138f565b979650505050505050565b634e487b7160e01b600052603260045260246000fd5b6000602082840312156113e457600080fd5b8151611121816110ec565b634e487b7160e01b600052601160045260246000fd5b60008219821115611418576114186113ef565b500190565b6000600019821415611431576114316113ef565b5060010190565b6000602080838503121561144b57600080fd5b825167ffffffffffffffff81111561146257600080fd5b8301601f8101851361147357600080fd5b8051611481611366826112f9565b81815260059190911b820183019083810190878311156114a057600080fd5b928401925b828410156113b15783516114b8816110ec565b825292840192908401906114a5565b6000602082840312156114d957600080fd5b5051919050565b600080604083850312156114f357600080fd5b8251801515811461150357600080fd5b6020939093015192949293505050565b600081600019048311821515161561152d5761152d6113ef565b500290565b60008261154f57634e487b7160e01b600052601260045260246000fd5b50049056fea264697066735822122000633454327713610bfc1341afc7e508e5d7f441aaf1f3e4e2859aa081c52fa064736f6c634300080a0033",
+ "sourceMap": "415:4873:50:-:0;;;1939:98;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;1983:16:50;;;;;;2016:14;;;-1:-1:-1;;;2016:14:50;;;;:12;;:14;;;;;;;;;;;;;;;1983:16;2016:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;2009:21:50;;;-1:-1:-1;415:4873:50;;14:144:63;-1:-1:-1;;;;;102:31:63;;92:42;;82:70;;148:1;145;138:12;82:70;14:144;:::o;163:284::-;253:6;306:2;294:9;285:7;281:23;277:32;274:52;;;322:1;319;312:12;274:52;354:9;348:16;373:44;411:5;373:44;:::i;:::-;436:5;163:284;-1:-1:-1;;;163:284:63:o;452:285::-;415:4873:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
+ "linkReferences": {}
+ },
+ "deployed_bytecode": {
+ "object": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c806316f0115b1461005157806369ff32d814610095578063d027f464146100b5578063ee97f7f3146100d5575b600080fd5b6100787f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100a86100a3366004611104565b6100fc565b60405161008c919061123d565b6100c86100c3366004611104565b6105b1565b60405161008c919061129f565b6100787f000000000000000000000000000000000000000000000000000000000000000081565b606060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316638cbf46ae6040518163ffffffff1660e01b8152600401600060405180830381865afa15801561015e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610186919081019061131d565b9050600060015b825181101561024a57846001600160a01b03168382815181106101b2576101b26113bc565b60200260200101516001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101f7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061021b91906113d2565b6001600160a01b0316141561023857610235600183611405565b91505b806102428161141d565b91505061018d565b5060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663c6def0766040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102cf91906113d2565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b87ee7af6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610331573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061035591906113d2565b90506000826001600160a01b0316630225c09e6040518163ffffffff1660e01b8152600401600060405180830381865afa158015610397573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526103bf9190810190611438565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa158015610421573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061044591906113d2565b905060008567ffffffffffffffff811115610462576104626112b2565b60405190808252806020026020018201604052801561049b57816020015b610488611072565b8152602001906001900390816104805790505b509050600060015b88518110156105a2578a6001600160a01b03168982815181106104c8576104c86113bc565b60200260200101516001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561050d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053191906113d2565b6001600160a01b0316141561059057610565898281518110610555576105556113bc565b60200260200101518686896107bf565b838381518110610577576105776113bc565b602090810291909101015261058d600183611405565b91505b8061059a8161141d565b9150506104a3565b50909998505050505050505050565b6105b9611072565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663c6def0766040518163ffffffff1660e01b8152600401602060405180830381865afa158015610619573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061063d91906113d2565b90506000816001600160a01b0316630225c09e6040518163ffffffff1660e01b8152600401600060405180830381865afa15801561067f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526106a79190810190611438565b90506107b784827f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561070c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061073091906113d2565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b87ee7af6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561078e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107b291906113d2565b6107bf565b949350505050565b6107c7611072565b6000845167ffffffffffffffff8111156107e3576107e36112b2565b60405190808252806020026020018201604052801561084157816020015b61082e604051806060016040528060006001600160a01b0316815260200160008152602001600081525090565b8152602001906001900390816108015790505b5090506000805b8651811015610a1c576000878281518110610865576108656113bc565b6020908102919091010151604051635fd35eb960e01b81526001600160a01b0380831660048301529192506000918b1690635fd35eb990602401602060405180830381865afa1580156108bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108e091906114c7565b6040516370a0823160e01b81526001600160a01b038c81166004830152919250600091841690634cdad5069082906370a0823190602401602060405180830381865afa158015610934573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061095891906114c7565b6040518263ffffffff1660e01b815260040161097691815260200190565b602060405180830381865afa158015610993573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109b791906114c7565b90506109c38186611405565b94506040518060600160405280846001600160a01b03168152602001838152602001828152508685815181106109fb576109fb6113bc565b60200260200101819052505050508080610a149061141d565b915050610848565b506000876001600160a01b03166338d52e0f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a5d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a8191906113d2565b60405162ede82760e41b81526001600160a01b038a811660048301528083166024830152919250600091871690630ede827090604401602060405180830381865afa158015610ad4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610af891906114c7565b90506000896001600160a01b031663a1ea7d6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b3a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b5e91906113d2565b6040516305eff7ef60e21b81526001600160a01b038c8116600483015291909116906317bfdfbc906024016020604051808303816000875af1158015610ba8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bcc91906114c7565b90506000886001600160a01b031663fc57d4df8c6001600160a01b031663a1ea7d6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c1d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c4191906113d2565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401602060405180830381865afa158015610c85573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ca991906114c7565b905060008b6001600160a01b0316634cdad5068d6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cfa573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1e91906114c7565b6040518263ffffffff1660e01b8152600401610d3c91815260200190565b602060405180830381865afa158015610d59573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d7d91906114c7565b905060008a6001600160a01b031663fc57d4df8e6001600160a01b031663ec11e1676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610dce573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610df291906113d2565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401602060405180830381865afa158015610e36573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e5a91906114c7565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316638e8f294b8f6001600160a01b031663ec11e1676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ecb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610eef91906113d2565b6040516001600160e01b031960e084901b1681526001600160a01b0390911660048201526024016040805180830381865afa158015610f32573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f5691906114e0565b915050604051806101a001604052808f6001600160a01b03168152602001886001600160a01b03168152602001848152602001838152602001828152602001858152602001670de0b6b3a76400008486610fb09190611513565b610fba9190611532565b815260208101879052604001670de0b6b3a7640000610fd98789611513565b610fe39190611532565b81526020018f6001600160a01b03166321fcf7506040518163ffffffff1660e01b8152600401602060405180830381865afa158015611026573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061104a91906114c7565b81526020018981526020018781526020018a8152509950505050505050505050949350505050565b604051806101a0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001606081525090565b6001600160a01b038116811461110157600080fd5b50565b60006020828403121561111657600080fd5b8135611121816110ec565b9392505050565b600081518084526020808501945080840160005b8381101561117757815180516001600160a01b031688528381015184890152604090810151908801526060909601959082019060010161113c565b509495945050505050565b80516001600160a01b0316825260006101a060208301516111ae60208601826001600160a01b03169052565b5060408301516040850152606083015160608501526080830151608085015260a083015160a085015260c083015160c085015260e083015160e085015261010080840151818601525061012080840151818601525061014080840151818601525061016080840151818601525061018080840151828287015261123383870182611128565b9695505050505050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b8281101561129257603f19888603018452611280858351611182565b94509285019290850190600101611264565b5092979650505050505050565b6020815260006111216020830184611182565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156112f1576112f16112b2565b604052919050565b600067ffffffffffffffff821115611313576113136112b2565b5060051b60200190565b6000602080838503121561133057600080fd5b825167ffffffffffffffff81111561134757600080fd5b8301601f8101851361135857600080fd5b805161136b611366826112f9565b6112c8565b81815260059190911b8201830190838101908783111561138a57600080fd5b928401925b828410156113b15783516113a2816110ec565b8252928401929084019061138f565b979650505050505050565b634e487b7160e01b600052603260045260246000fd5b6000602082840312156113e457600080fd5b8151611121816110ec565b634e487b7160e01b600052601160045260246000fd5b60008219821115611418576114186113ef565b500190565b6000600019821415611431576114316113ef565b5060010190565b6000602080838503121561144b57600080fd5b825167ffffffffffffffff81111561146257600080fd5b8301601f8101851361147357600080fd5b8051611481611366826112f9565b81815260059190911b820183019083810190878311156114a057600080fd5b928401925b828410156113b15783516114b8816110ec565b825292840192908401906114a5565b6000602082840312156114d957600080fd5b5051919050565b600080604083850312156114f357600080fd5b8251801515811461150357600080fd5b6020939093015192949293505050565b600081600019048311821515161561152d5761152d6113ef565b500290565b60008261154f57634e487b7160e01b600052601260045260246000fd5b50049056fea264697066735822122000633454327713610bfc1341afc7e508e5d7f441aaf1f3e4e2859aa081c52fa064736f6c634300080a0033",
+ "sourceMap": "415:4873:50:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;440:33;;;;;;;;-1:-1:-1;;;;;321:32:63;;;303:51;;291:2;276:18;440:33:50;;;;;;;;2043:902;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;3156:273::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;480:35::-;;;;;2043:902;2101:17;2130:24;2157:6;-1:-1:-1;;;;;2157:18:50;;:20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;2157:20:50;;;;;;;;;;;;:::i;:::-;2130:47;-1:-1:-1;2187:21:50;2235:1;2218:117;2242:5;:12;2238:1;:16;2218:117;;;2299:5;-1:-1:-1;;;;;2279:25:50;:5;2285:1;2279:8;;;;;;;;:::i;:::-;;;;;;;-1:-1:-1;;;;;2279:14:50;;:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;2279:25:50;;2275:49;;;2306:18;2323:1;2306:18;;:::i;:::-;;;2275:49;2256:3;;;;:::i;:::-;;;;2218:117;;;;2353:20;2376:6;-1:-1:-1;;;;;2376:14:50;;:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2353:39;;2402:16;2421:6;-1:-1:-1;;;;;2421:12:50;;:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2402:33;;2446:27;2476:7;-1:-1:-1;;;;;2476:26:50;;:28;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;2476:28:50;;;;;;;;;;;;:::i;:::-;2446:58;;2514:16;2533:4;-1:-1:-1;;;;;2533:11:50;;:13;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2514:32;;2565:27;2610:13;2595:29;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;-1:-1:-1;2565:59:50;-1:-1:-1;2634:22:50;2683:1;2666:246;2690:5;:12;2686:1;:16;2666:246;;;2747:5;-1:-1:-1;;;;;2727:25:50;:5;2733:1;2727:8;;;;;;;;:::i;:::-;;;;;;;-1:-1:-1;;;;;2727:14:50;;:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;2727:25:50;;2723:179;;;2800:49;2813:5;2819:1;2813:8;;;;;;;;:::i;:::-;;;;;;;2823:10;2835:6;2843:5;2800:12;:49::i;:::-;2772:9;2782:14;2772:25;;;;;;;;:::i;:::-;;;;;;;;;;:77;2867:19;2885:1;2867:19;;:::i;:::-;;;2723:179;2704:3;;;;:::i;:::-;;;;2666:246;;;-1:-1:-1;2929:9:50;;2043:902;-1:-1:-1;;;;;;;;;2043:902:50:o;3156:273::-;3210:15;;:::i;:::-;3237:20;3260:6;-1:-1:-1;;;;;3260:14:50;;:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3237:39;;3286:27;3316:7;-1:-1:-1;;;;;3316:26:50;;:28;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;3316:28:50;;;;;;;;;;;;:::i;:::-;3286:58;;3361:61;3374:4;3380:10;3392:4;-1:-1:-1;;;;;3392:11:50;;:13;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3407:6;-1:-1:-1;;;;;3407:12:50;;:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3361:12;:61::i;:::-;3354:68;3156:273;-1:-1:-1;;;;3156:273:50:o;3435:1851::-;3556:15;;:::i;:::-;3583:26;3631:10;:17;3612:37;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3612:37:50;;;;;;;;;;;;;;;;-1:-1:-1;3583:66:50;-1:-1:-1;3660:22:50;;3706:439;3730:10;:17;3726:1;:21;3706:439;;;3772:16;3791:10;3802:1;3791:13;;;;;;;;:::i;:::-;;;;;;;;;;;3840:41;;-1:-1:-1;;;3840:41:50;;-1:-1:-1;;;;;321:32:63;;;3840:41:50;;;303:51:63;3791:13:50;;-1:-1:-1;3822:15:50;;3840:31;;;;;276:18:63;;3840:41:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3942:33;;-1:-1:-1;;;3942:33:50;;-1:-1:-1;;;;;321:32:63;;;3942:33:50;;;303:51:63;3822:59:50;;-1:-1:-1;3899:17:50;;3919:22;;;;;;;3942:18;;276::63;;3942:33:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3919:57;;;;;;;;;;;;;9110:25:63;;9098:2;9083:18;;8964:177;3919:57:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3899:77;-1:-1:-1;3995:27:50;3899:77;3995:27;;:::i;:::-;;;4050:80;;;;;;;;4074:8;-1:-1:-1;;;;;4050:80:50;;;;;4099:7;4050:80;;;;4119:9;4050:80;;;4040:4;4045:1;4040:7;;;;;;;;:::i;:::-;;;;;;:90;;;;3754:391;;;3749:3;;;;;:::i;:::-;;;;3706:439;;;;4165:16;4184:4;-1:-1:-1;;;;;4184:10:50;;:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4220:47;;-1:-1:-1;;;4220:47:50;;-1:-1:-1;;;;;9678:15:63;;;4220:47:50;;;9660:34:63;9730:15;;;9710:18;;;9703:43;4165:31:50;;-1:-1:-1;4206:11:50;;4220:29;;;;;9595:18:63;;4220:47:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4206:61;;4277:18;4298:4;-1:-1:-1;;;;;4298:19:50;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:57;;-1:-1:-1;;;4298:57:50;;-1:-1:-1;;;;;321:32:63;;;4298:57:50;;;303:51:63;4298:42:50;;;;;;;276:18:63;;4298:57:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4277:78;;4365:16;4384:6;-1:-1:-1;;;;;4384:25:50;;4410:4;-1:-1:-1;;;;;4410:19:50;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4384:48;;-1:-1:-1;;;;;;4384:48:50;;;;;;;-1:-1:-1;;;;;321:32:63;;;4384:48:50;;;303:51:63;276:18;;4384:48:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4365:67;;4443:24;4470:4;-1:-1:-1;;;;;4470:18:50;;4489:4;-1:-1:-1;;;;;4489:16:50;;:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4470:38;;;;;;;;;;;;;9110:25:63;;9098:2;9083:18;;8964:177;4470:38:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4443:65;;4518:23;4544:6;-1:-1:-1;;;;;4544:25:50;;4570:4;-1:-1:-1;;;;;4570:21:50;;:23;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4544:50;;-1:-1:-1;;;;;;4544:50:50;;;;;;;-1:-1:-1;;;;;321:32:63;;;4544:50:50;;;303:51:63;276:18;;4544:50:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4518:76;;4607:24;4635:4;-1:-1:-1;;;;;4635:12:50;;4648:4;-1:-1:-1;;;;;4648:21:50;;:23;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4635:37;;-1:-1:-1;;;;;;4635:37:50;;;;;;;-1:-1:-1;;;;;321:32:63;;;4635:37:50;;;303:51:63;276:18;;4635:37:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4604:68;;;4690:589;;;;;;;;4734:4;-1:-1:-1;;;;;4690:589:50;;;;;4770:10;-1:-1:-1;;;;;4690:589:50;;;;;4939:16;4690:589;;;;4812:15;4690:589;;;;4859:16;4690:589;;;;4899:8;4690:589;;;;5023:4;5005:15;4986:16;:34;;;;:::i;:::-;:41;;;;:::i;:::-;4690:589;;;;;;;;;;5112:4;5088:21;5101:8;5053:10;5088:21;:::i;:::-;:28;;;;:::i;:::-;4690:589;;;;5145:4;-1:-1:-1;;;;;5145:20:50;;:22;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4690:589;;;;5192:14;4690:589;;;;5233:3;4690:589;;;;5264:4;4690:589;;;4683:596;;;;;;;;;;;3435:1851;;;;;;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;365:131:63:-;-1:-1:-1;;;;;440:31:63;;430:42;;420:70;;486:1;483;476:12;420:70;365:131;:::o;501:247::-;560:6;613:2;601:9;592:7;588:23;584:32;581:52;;;629:1;626;619:12;581:52;668:9;655:23;687:31;712:5;687:31;:::i;:::-;737:5;501:247;-1:-1:-1;;;501:247:63:o;753:640::-;818:3;856:5;850:12;883:6;878:3;871:19;909:4;938:2;933:3;929:12;922:19;;975:2;968:5;964:14;996:1;1006:362;1020:6;1017:1;1014:13;1006:362;;;1079:13;;1121:9;;-1:-1:-1;;;;;1117:35:63;1105:48;;1193:11;;;1187:18;1173:12;;;1166:40;1229:4;1273:11;;;1267:18;1253:12;;;1246:40;1315:4;1306:14;;;;1343:15;;;;1149:1;1035:9;1006:362;;;-1:-1:-1;1384:3:63;;753:640;-1:-1:-1;;;;;753:640:63:o;1398:1112::-;1524:12;;-1:-1:-1;;;;;93:31:63;81:44;;1449:3;1477:6;1588:4;1581:5;1577:16;1571:23;1603:61;1658:4;1653:3;1649:14;1635:12;-1:-1:-1;;;;;93:31:63;81:44;;14:117;1603:61;;1713:4;1706:5;1702:16;1696:23;1689:4;1684:3;1680:14;1673:47;1769:4;1762:5;1758:16;1752:23;1745:4;1740:3;1736:14;1729:47;1825:4;1818:5;1814:16;1808:23;1801:4;1796:3;1792:14;1785:47;1881:4;1874:5;1870:16;1864:23;1857:4;1852:3;1848:14;1841:47;1937:4;1930:5;1926:16;1920:23;1913:4;1908:3;1904:14;1897:47;1993:4;1986:5;1982:16;1976:23;1969:4;1964:3;1960:14;1953:47;2019:6;2072:2;2065:5;2061:14;2055:21;2050:2;2045:3;2041:12;2034:43;;2096:6;2149:2;2142:5;2138:14;2132:21;2127:2;2122:3;2118:12;2111:43;;2173:6;2226:2;2219:5;2215:14;2209:21;2204:2;2199:3;2195:12;2188:43;;2250:6;2303:2;2296:5;2292:14;2286:21;2281:2;2276:3;2272:12;2265:43;;2327:6;2381:2;2374:5;2370:14;2364:21;2415:2;2410;2405:3;2401:12;2394:24;2434:70;2500:2;2495:3;2491:12;2475:14;2434:70;:::i;:::-;2427:77;1398:1112;-1:-1:-1;;;;;;1398:1112:63:o;2515:846::-;2711:4;2740:2;2780;2769:9;2765:18;2810:2;2799:9;2792:21;2833:6;2868;2862:13;2899:6;2891;2884:22;2937:2;2926:9;2922:18;2915:25;;2999:2;2989:6;2986:1;2982:14;2971:9;2967:30;2963:39;2949:53;;3037:2;3029:6;3025:15;3058:1;3068:264;3082:6;3079:1;3076:13;3068:264;;;3175:2;3171:7;3159:9;3151:6;3147:22;3143:36;3138:3;3131:49;3203;3245:6;3236;3230:13;3203:49;:::i;:::-;3193:59;-1:-1:-1;3310:12:63;;;;3275:15;;;;3104:1;3097:9;3068:264;;;-1:-1:-1;3349:6:63;;2515:846;-1:-1:-1;;;;;;;2515:846:63:o;3636:263::-;3819:2;3808:9;3801:21;3782:4;3839:54;3889:2;3878:9;3874:18;3866:6;3839:54;:::i;4132:127::-;4193:10;4188:3;4184:20;4181:1;4174:31;4224:4;4221:1;4214:15;4248:4;4245:1;4238:15;4264:275;4335:2;4329:9;4400:2;4381:13;;-1:-1:-1;;4377:27:63;4365:40;;4435:18;4420:34;;4456:22;;;4417:62;4414:88;;;4482:18;;:::i;:::-;4518:2;4511:22;4264:275;;-1:-1:-1;4264:275:63:o;4544:194::-;4615:4;4648:18;4640:6;4637:30;4634:56;;;4670:18;;:::i;:::-;-1:-1:-1;4715:1:63;4711:14;4727:4;4707:25;;4544:194::o;4743:985::-;4856:6;4887:2;4930;4918:9;4909:7;4905:23;4901:32;4898:52;;;4946:1;4943;4936:12;4898:52;4979:9;4973:16;5012:18;5004:6;5001:30;4998:50;;;5044:1;5041;5034:12;4998:50;5067:22;;5120:4;5112:13;;5108:27;-1:-1:-1;5098:55:63;;5149:1;5146;5139:12;5098:55;5178:2;5172:9;5201:71;5217:54;5268:2;5217:54;:::i;:::-;5201:71;:::i;:::-;5306:15;;;5388:1;5384:10;;;;5376:19;;5372:28;;;5337:12;;;;5412:19;;;5409:39;;;5444:1;5441;5434:12;5409:39;5468:11;;;;5488:210;5504:6;5499:3;5496:15;5488:210;;;5577:3;5571:10;5594:31;5619:5;5594:31;:::i;:::-;5638:18;;5521:12;;;;5676;;;;5488:210;;;5717:5;4743:985;-1:-1:-1;;;;;;;4743:985:63:o;5733:127::-;5794:10;5789:3;5785:20;5782:1;5775:31;5825:4;5822:1;5815:15;5849:4;5846:1;5839:15;5865:251;5935:6;5988:2;5976:9;5967:7;5963:23;5959:32;5956:52;;;6004:1;6001;5994:12;5956:52;6036:9;6030:16;6055:31;6080:5;6055:31;:::i;6121:127::-;6182:10;6177:3;6173:20;6170:1;6163:31;6213:4;6210:1;6203:15;6237:4;6234:1;6227:15;6253:128;6293:3;6324:1;6320:6;6317:1;6314:13;6311:39;;;6330:18;;:::i;:::-;-1:-1:-1;6366:9:63;;6253:128::o;6386:135::-;6425:3;-1:-1:-1;;6446:17:63;;6443:43;;;6466:18;;:::i;:::-;-1:-1:-1;6513:1:63;6502:13;;6386:135::o;7080:983::-;7191:6;7222:2;7265;7253:9;7244:7;7240:23;7236:32;7233:52;;;7281:1;7278;7271:12;7233:52;7314:9;7308:16;7347:18;7339:6;7336:30;7333:50;;;7379:1;7376;7369:12;7333:50;7402:22;;7455:4;7447:13;;7443:27;-1:-1:-1;7433:55:63;;7484:1;7481;7474:12;7433:55;7513:2;7507:9;7536:71;7552:54;7603:2;7552:54;:::i;7536:71::-;7641:15;;;7723:1;7719:10;;;;7711:19;;7707:28;;;7672:12;;;;7747:19;;;7744:39;;;7779:1;7776;7769:12;7744:39;7803:11;;;;7823:210;7839:6;7834:3;7831:15;7823:210;;;7912:3;7906:10;7929:31;7954:5;7929:31;:::i;:::-;7973:18;;7856:12;;;;8011;;;;7823:210;;8567:184;8637:6;8690:2;8678:9;8669:7;8665:23;8661:32;8658:52;;;8706:1;8703;8696:12;8658:52;-1:-1:-1;8729:16:63;;8567:184;-1:-1:-1;8567:184:63:o;10253:338::-;10329:6;10337;10390:2;10378:9;10369:7;10365:23;10361:32;10358:52;;;10406:1;10403;10396:12;10358:52;10438:9;10432:16;10491:5;10484:13;10477:21;10470:5;10467:32;10457:60;;10513:1;10510;10503:12;10457:60;10581:2;10566:18;;;;10560:25;10536:5;;10560:25;;-1:-1:-1;;;10253:338:63:o;10596:168::-;10636:7;10702:1;10698;10694:6;10690:14;10687:1;10684:21;10679:1;10672:9;10665:17;10661:45;10658:71;;;10709:18;;:::i;:::-;-1:-1:-1;10749:9:63;;10596:168::o;10769:217::-;10809:1;10835;10825:132;;10879:10;10874:3;10870:20;10867:1;10860:31;10914:4;10911:1;10904:15;10942:4;10939:1;10932:15;10825:132;-1:-1:-1;10971:9:63;;10769:217::o",
+ "linkReferences": {},
+ "immutableReferences": {
+ "13165": [
+ {
+ "start": 86,
+ "length": 32
+ },
+ {
+ "start": 965,
+ "length": 32
+ },
+ {
+ "start": 1712,
+ "length": 32
+ },
+ {
+ "start": 3680,
+ "length": 32
+ }
+ ],
+ "13168": [
+ {
+ "start": 218,
+ "length": 32
+ },
+ {
+ "start": 258,
+ "length": 32
+ },
+ {
+ "start": 591,
+ "length": 32
+ },
+ {
+ "start": 725,
+ "length": 32
+ },
+ {
+ "start": 1469,
+ "length": 32
+ },
+ {
+ "start": 1842,
+ "length": 32
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/TurboMaster.json b/src/lib/turbo/abi/TurboMaster.json
new file mode 100644
index 00000000..a0de127f
--- /dev/null
+++ b/src/lib/turbo/abi/TurboMaster.json
@@ -0,0 +1,545 @@
+{
+ "abi": [
+ {
+ "type": "constructor",
+ "inputs": [
+ {
+ "internalType": "contract Comptroller",
+ "name": "_pool",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC20",
+ "name": "_fei",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "internalType": "contract Authority",
+ "name": "_authority",
+ "type": "address"
+ }
+ ]
+ },
+ {
+ "type": "function",
+ "name": "REVERSE_REGISTRAR",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract IReverseRegistrar",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "authority",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "booster",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract TurboBooster",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "clerk",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract TurboClerk",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "createSafe",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "asset",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "id",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "defaultSafeAuthority",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "fei",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getAllSafes",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract TurboSafe[]",
+ "name": "",
+ "type": "address[]"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getSafeId",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getTotalBoostedAgainstCollateral",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getTotalBoostedForVault",
+ "inputs": [
+ {
+ "internalType": "contract ERC4626",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "onSafeBoost",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "asset",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "feiAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "onSafeLess",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "asset",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "feiAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "owner",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "pool",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Comptroller",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "safes",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "setAuthority",
+ "inputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "newAuthority",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setBooster",
+ "inputs": [
+ {
+ "internalType": "contract TurboBooster",
+ "name": "newBooster",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setClerk",
+ "inputs": [
+ {
+ "internalType": "contract TurboClerk",
+ "name": "newClerk",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setDefaultSafeAuthority",
+ "inputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "newDefaultSafeAuthority",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setENSName",
+ "inputs": [
+ {
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setOwner",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "sweep",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC20",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "totalBoosted",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "event",
+ "name": "AuthorityUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newAuthority",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "BoosterUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newBooster",
+ "type": "address",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "ClerkUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newClerk",
+ "type": "address",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "DefaultSafeAuthorityUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newDefaultSafeAuthority",
+ "type": "address",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "OwnerUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newOwner",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "TokenSweeped",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "to",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "token",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "TurboSafeCreated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "asset",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "safe",
+ "type": "address",
+ "indexed": false
+ },
+ {
+ "name": "id",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/TurboRouter.json b/src/lib/turbo/abi/TurboRouter.json
new file mode 100644
index 00000000..c2f0b9d2
--- /dev/null
+++ b/src/lib/turbo/abi/TurboRouter.json
@@ -0,0 +1,786 @@
+{
+ "abi": [
+ {
+ "type": "constructor",
+ "inputs": [
+ {
+ "internalType": "contract TurboMaster",
+ "name": "_master",
+ "type": "address"
+ },
+ {
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "internalType": "contract IWETH9",
+ "name": "weth",
+ "type": "address"
+ }
+ ]
+ },
+ {
+ "type": "function",
+ "name": "REVERSE_REGISTRAR",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract IReverseRegistrar",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "WETH9",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract IWETH9",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "approve",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "boost",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "feiAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "createSafe",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "underlying",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "createSafeAndDeposit",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "underlying",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "minSharesOut",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "createSafeAndDepositAndBoost",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "underlying",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "minSharesOut",
+ "type": "uint256"
+ },
+ {
+ "internalType": "contract ERC4626",
+ "name": "boostedVault",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "boostedFeiAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "createSafeAndDepositAndBoostMany",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "underlying",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "minSharesOut",
+ "type": "uint256"
+ },
+ {
+ "internalType": "contract ERC4626[]",
+ "name": "boostedVaults",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "boostedFeiAmounts",
+ "type": "uint256[]"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "deposit",
+ "inputs": [
+ {
+ "internalType": "contract IERC4626",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "minSharesOut",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "less",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "feiAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "lessAll",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "master",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract TurboMaster",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "mint",
+ "inputs": [
+ {
+ "internalType": "contract IERC4626",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "shares",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "maxAmountIn",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "multicall",
+ "inputs": [
+ {
+ "internalType": "bytes[]",
+ "name": "data",
+ "type": "bytes[]"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bytes[]",
+ "name": "results",
+ "type": "bytes[]"
+ }
+ ],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "pullToken",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "redeem",
+ "inputs": [
+ {
+ "internalType": "contract IERC4626",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "shares",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "minAmountOut",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "refundETH",
+ "inputs": [],
+ "outputs": [],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "selfPermit",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "deadline",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint8",
+ "name": "v",
+ "type": "uint8"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "r",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "s",
+ "type": "bytes32"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "selfPermitAllowed",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "nonce",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "expiry",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint8",
+ "name": "v",
+ "type": "uint8"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "r",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "s",
+ "type": "bytes32"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "selfPermitAllowedIfNecessary",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "nonce",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "expiry",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint8",
+ "name": "v",
+ "type": "uint8"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "r",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "s",
+ "type": "bytes32"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "selfPermitIfNecessary",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "deadline",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint8",
+ "name": "v",
+ "type": "uint8"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "r",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "s",
+ "type": "bytes32"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "slurp",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "slurpAndLessAll",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "sweep",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC20",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "sweepAll",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC20",
+ "name": "token",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "sweepToken",
+ "inputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amountMinimum",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "unwrapWETH9",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "amountMinimum",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "withdraw",
+ "inputs": [
+ {
+ "internalType": "contract IERC4626",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "maxSharesOut",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "function",
+ "name": "wrapWETH9",
+ "inputs": [],
+ "outputs": [],
+ "stateMutability": "payable"
+ },
+ {
+ "type": "error",
+ "name": "MaxAmountError",
+ "inputs": []
+ },
+ {
+ "type": "error",
+ "name": "MaxSharesError",
+ "inputs": []
+ },
+ {
+ "type": "error",
+ "name": "MinAmountError",
+ "inputs": []
+ },
+ {
+ "type": "error",
+ "name": "MinSharesError",
+ "inputs": []
+ },
+ {
+ "type": "receive"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/TurboSafe.json b/src/lib/turbo/abi/TurboSafe.json
new file mode 100644
index 00000000..1b6ae68d
--- /dev/null
+++ b/src/lib/turbo/abi/TurboSafe.json
@@ -0,0 +1,1095 @@
+{
+ "abi": [
+ {
+ "type": "constructor",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "internalType": "contract Authority",
+ "name": "_authority",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC20",
+ "name": "_asset",
+ "type": "address"
+ }
+ ]
+ },
+ {
+ "type": "function",
+ "name": "DOMAIN_SEPARATOR",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "bytes32",
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "allowance",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "approve",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "asset",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "assetTurboCToken",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract CERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "authority",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "balanceOf",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "boost",
+ "inputs": [
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "feiAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "convertToAssets",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "shares",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "convertToShares",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "assets",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "decimals",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "deposit",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "assets",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "receiver",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "shares",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "fei",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract ERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "feiTurboCToken",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract CERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getTotalFeiBoostedForVault",
+ "inputs": [
+ {
+ "internalType": "contract ERC4626",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "gib",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "assetAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "less",
+ "inputs": [
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "feiAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "master",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract TurboMaster",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "maxDeposit",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "maxMint",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "maxRedeem",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "maxWithdraw",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "mint",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "shares",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "receiver",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "assets",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "name",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "nonces",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "owner",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "permit",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "deadline",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint8",
+ "name": "v",
+ "type": "uint8"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "r",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "s",
+ "type": "bytes32"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "pool",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Comptroller",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "previewDeposit",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "assets",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "previewMint",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "shares",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "previewRedeem",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "shares",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "previewWithdraw",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "assets",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "redeem",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "shares",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "receiver",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "assets",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setAuthority",
+ "inputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "newAuthority",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setOwner",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "slurp",
+ "inputs": [
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "safeInterestAmount",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "sweep",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC20",
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "symbol",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "totalAssets",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "totalFeiBoosted",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "totalSupply",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "transfer",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "transferFrom",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "withdraw",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "assets",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "receiver",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "shares",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "event",
+ "name": "Approval",
+ "inputs": [
+ {
+ "name": "owner",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "spender",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "AuthorityUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newAuthority",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "Deposit",
+ "inputs": [
+ {
+ "name": "caller",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "owner",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "assets",
+ "type": "uint256",
+ "indexed": false
+ },
+ {
+ "name": "shares",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "OwnerUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newOwner",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "SafeGibbed",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "to",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "assetAmount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "TokenSweeped",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "to",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "token",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "Transfer",
+ "inputs": [
+ {
+ "name": "from",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "to",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "VaultBoosted",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "vault",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "feiAmount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "VaultLessened",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "vault",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "feiAmount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "VaultSlurped",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "vault",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "protocolFeeAmount",
+ "type": "uint256",
+ "indexed": false
+ },
+ {
+ "name": "safeInterestAmount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "Withdraw",
+ "inputs": [
+ {
+ "name": "caller",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "receiver",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "owner",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "assets",
+ "type": "uint256",
+ "indexed": false
+ },
+ {
+ "name": "shares",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/TurboSavior.json b/src/lib/turbo/abi/TurboSavior.json
new file mode 100644
index 00000000..3bea9515
--- /dev/null
+++ b/src/lib/turbo/abi/TurboSavior.json
@@ -0,0 +1,281 @@
+{
+ "abi": [
+ {
+ "type": "constructor",
+ "inputs": [
+ {
+ "internalType": "contract TurboMaster",
+ "name": "_master",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "internalType": "contract Authority",
+ "name": "_authority",
+ "type": "address"
+ }
+ ]
+ },
+ {
+ "type": "function",
+ "name": "REVERSE_REGISTRAR",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract IReverseRegistrar",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "authority",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "fei",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Fei",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "feiTurboCToken",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract CERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "master",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract TurboMaster",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "minDebtPercentageForSaving",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "owner",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "pool",
+ "inputs": [],
+ "outputs": [
+ {
+ "internalType": "contract Comptroller",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "save",
+ "inputs": [
+ {
+ "internalType": "contract TurboSafe",
+ "name": "safe",
+ "type": "address"
+ },
+ {
+ "internalType": "contract ERC4626",
+ "name": "vault",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "feiAmount",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setAuthority",
+ "inputs": [
+ {
+ "internalType": "contract Authority",
+ "name": "newAuthority",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setENSName",
+ "inputs": [
+ {
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setMinDebtPercentageForSaving",
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "newMinDebtPercentageForSaving",
+ "type": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "setOwner",
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "event",
+ "name": "AuthorityUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newAuthority",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "MinDebtPercentageForSavingUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newDefaultFeePercentage",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "OwnerUpdated",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "newOwner",
+ "type": "address",
+ "indexed": true
+ }
+ ],
+ "anonymous": false
+ },
+ {
+ "type": "event",
+ "name": "SafeSaved",
+ "inputs": [
+ {
+ "name": "user",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "safe",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "vault",
+ "type": "address",
+ "indexed": true
+ },
+ {
+ "name": "feiAmount",
+ "type": "uint256",
+ "indexed": false
+ }
+ ],
+ "anonymous": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/turbo/abi/comptroller.json b/src/lib/turbo/abi/comptroller.json
new file mode 100644
index 00000000..9a4d7dc2
--- /dev/null
+++ b/src/lib/turbo/abi/comptroller.json
@@ -0,0 +1,2144 @@
+[
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "string",
+ "name": "action",
+ "type": "string"
+ },
+ {
+ "indexed": false,
+ "internalType": "bool",
+ "name": "pauseState",
+ "type": "bool"
+ }
+ ],
+ "name": "ActionPaused",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "string",
+ "name": "action",
+ "type": "string"
+ },
+ {
+ "indexed": false,
+ "internalType": "bool",
+ "name": "pauseState",
+ "type": "bool"
+ }
+ ],
+ "name": "ActionPaused",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "rewardsDistributor",
+ "type": "address"
+ }
+ ],
+ "name": "AddedRewardsDistributor",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "bool",
+ "name": "enabled",
+ "type": "bool"
+ }
+ ],
+ "name": "AutoImplementationsToggled",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "error",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "info",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "detail",
+ "type": "uint256"
+ }
+ ],
+ "name": "Failure",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "MarketEntered",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "MarketExited",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ }
+ ],
+ "name": "MarketListed",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ }
+ ],
+ "name": "MarketUnlisted",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newBorrowCap",
+ "type": "uint256"
+ }
+ ],
+ "name": "NewBorrowCap",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "oldBorrowCapGuardian",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "newBorrowCapGuardian",
+ "type": "address"
+ }
+ ],
+ "name": "NewBorrowCapGuardian",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "oldCloseFactorMantissa",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newCloseFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "NewCloseFactor",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "oldCollateralFactorMantissa",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newCollateralFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "NewCollateralFactor",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "oldLiquidationIncentiveMantissa",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newLiquidationIncentiveMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "NewLiquidationIncentive",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "oldPauseGuardian",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "newPauseGuardian",
+ "type": "address"
+ }
+ ],
+ "name": "NewPauseGuardian",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "contract PriceOracle",
+ "name": "oldPriceOracle",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "contract PriceOracle",
+ "name": "newPriceOracle",
+ "type": "address"
+ }
+ ],
+ "name": "NewPriceOracle",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newSupplyCap",
+ "type": "uint256"
+ }
+ ],
+ "name": "NewSupplyCap",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "bool",
+ "name": "enforce",
+ "type": "bool"
+ }
+ ],
+ "name": "WhitelistEnforcementChanged",
+ "type": "event"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "distributor",
+ "type": "address"
+ }
+ ],
+ "name": "_addRewardsDistributor",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [],
+ "name": "_afterNonReentrant",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contract Unitroller",
+ "name": "unitroller",
+ "type": "address"
+ }
+ ],
+ "name": "_become",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [],
+ "name": "_becomeImplementation",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [],
+ "name": "_beforeNonReentrant",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "_borrowGuardianPaused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "isCEther",
+ "type": "bool"
+ },
+ {
+ "internalType": "bytes",
+ "name": "constructorData",
+ "type": "bytes"
+ },
+ {
+ "internalType": "uint256",
+ "name": "collateralFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "_deployMarket",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "_mintGuardianPaused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newBorrowCapGuardian",
+ "type": "address"
+ }
+ ],
+ "name": "_setBorrowCapGuardian",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "bool",
+ "name": "state",
+ "type": "bool"
+ }
+ ],
+ "name": "_setBorrowPaused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "newCloseFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "_setCloseFactor",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "newCollateralFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "_setCollateralFactor",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "newLiquidationIncentiveMantissa",
+ "type": "uint256"
+ }
+ ],
+ "name": "_setLiquidationIncentive",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contract CToken[]",
+ "name": "cTokens",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "newBorrowCaps",
+ "type": "uint256[]"
+ }
+ ],
+ "name": "_setMarketBorrowCaps",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contract CToken[]",
+ "name": "cTokens",
+ "type": "address[]"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "newSupplyCaps",
+ "type": "uint256[]"
+ }
+ ],
+ "name": "_setMarketSupplyCaps",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "bool",
+ "name": "state",
+ "type": "bool"
+ }
+ ],
+ "name": "_setMintPaused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newPauseGuardian",
+ "type": "address"
+ }
+ ],
+ "name": "_setPauseGuardian",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contract PriceOracle",
+ "name": "newOracle",
+ "type": "address"
+ }
+ ],
+ "name": "_setPriceOracle",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "state",
+ "type": "bool"
+ }
+ ],
+ "name": "_setSeizePaused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "state",
+ "type": "bool"
+ }
+ ],
+ "name": "_setTransferPaused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "enforce",
+ "type": "bool"
+ }
+ ],
+ "name": "_setWhitelistEnforcement",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "suppliers",
+ "type": "address[]"
+ },
+ {
+ "internalType": "bool[]",
+ "name": "statuses",
+ "type": "bool[]"
+ }
+ ],
+ "name": "_setWhitelistStatuses",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "bool",
+ "name": "enabled",
+ "type": "bool"
+ }
+ ],
+ "name": "_toggleAutoImplementations",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ }
+ ],
+ "name": "_unsupportMarket",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "accountAssets",
+ "outputs": [
+ {
+ "internalType": "contract CToken",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "admin",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "adminHasRights",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "allBorrowers",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "allMarkets",
+ "outputs": [
+ {
+ "internalType": "contract CToken",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "autoImplementation",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "borrowAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "borrowAllowed",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "borrowCapGuardian",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "borrowCaps",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "borrowGuardianPaused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "borrowAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "borrowVerify",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "accountBorrowsNew",
+ "type": "uint256"
+ }
+ ],
+ "name": "borrowWithinLimits",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "cTokensByUnderlying",
+ "outputs": [
+ {
+ "internalType": "contract CToken",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ },
+ {
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ }
+ ],
+ "name": "checkMembership",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "closeFactorMantissa",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "comptrollerImplementation",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "enforceWhitelist",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "cTokens",
+ "type": "address[]"
+ }
+ ],
+ "name": "enterMarkets",
+ "outputs": [
+ {
+ "internalType": "uint256[]",
+ "name": "",
+ "type": "uint256[]"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cTokenAddress",
+ "type": "address"
+ }
+ ],
+ "name": "exitMarket",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "fuseAdminHasRights",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "getAccountLiquidity",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "getAllBorrowers",
+ "outputs": [
+ {
+ "internalType": "address[]",
+ "name": "",
+ "type": "address[]"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "getAllMarkets",
+ "outputs": [
+ {
+ "internalType": "contract CToken[]",
+ "name": "",
+ "type": "address[]"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "getAssetsIn",
+ "outputs": [
+ {
+ "internalType": "contract CToken[]",
+ "name": "",
+ "type": "address[]"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "cTokenModify",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "redeemTokens",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "borrowAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "getHypotheticalAccountLiquidity",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "getRewardsDistributors",
+ "outputs": [
+ {
+ "internalType": "address[]",
+ "name": "",
+ "type": "address[]"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "getWhitelist",
+ "outputs": [
+ {
+ "internalType": "address[]",
+ "name": "",
+ "type": "address[]"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "isComptroller",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "contract CToken",
+ "name": "cToken",
+ "type": "address"
+ }
+ ],
+ "name": "isDeprecated",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cTokenBorrowed",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "cTokenCollateral",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "liquidator",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "repayAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "liquidateBorrowAllowed",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cTokenBorrowed",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "cTokenCollateral",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "liquidator",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "actualRepayAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "seizeTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "liquidateBorrowVerify",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cTokenBorrowed",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "cTokenCollateral",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "actualRepayAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "liquidateCalculateSeizeTokens",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "liquidationIncentiveMantissa",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "markets",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "isListed",
+ "type": "bool"
+ },
+ {
+ "internalType": "uint256",
+ "name": "collateralFactorMantissa",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "minter",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "mintAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "mintAllowed",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "mintGuardianPaused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "minter",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "actualMintAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "mintTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "mintVerify",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "exchangeRateMantissa",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "accountTokens",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "mintAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "mintWithinLimits",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "oracle",
+ "outputs": [
+ {
+ "internalType": "contract PriceOracle",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "pauseGuardian",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "pendingAdmin",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "pendingComptrollerImplementation",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "redeemer",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "redeemTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "redeemAllowed",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "redeemer",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "redeemAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "redeemTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "redeemVerify",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "payer",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "repayAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "repayBorrowAllowed",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "payer",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "actualRepayAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "borrowerIndex",
+ "type": "uint256"
+ }
+ ],
+ "name": "repayBorrowVerify",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "rewardsDistributors",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cTokenCollateral",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "cTokenBorrowed",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "liquidator",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "seizeTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "seizeAllowed",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "seizeGuardianPaused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cTokenCollateral",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "cTokenBorrowed",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "liquidator",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "borrower",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "seizeTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "seizeVerify",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "suppliers",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "supplyCaps",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "src",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "dst",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "transferTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "transferAllowed",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "transferGuardianPaused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "cToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "src",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "dst",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "transferTokens",
+ "type": "uint256"
+ }
+ ],
+ "name": "transferVerify",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "whitelist",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "whitelistArray",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ }
+]
\ No newline at end of file
diff --git a/src/lib/turbo/fetchers/getApprovedCollaterals.tsx b/src/lib/turbo/fetchers/getApprovedCollaterals.tsx
new file mode 100644
index 00000000..15f16d25
--- /dev/null
+++ b/src/lib/turbo/fetchers/getApprovedCollaterals.tsx
@@ -0,0 +1,34 @@
+import { providers } from "@0xsequence/multicall";
+import { JsonRpcProvider, Web3Provider } from "@ethersproject/providers";
+import { FEI } from "../utils/constants";
+import {
+ createCERC20,
+ createCERC20Delegate,
+ createTurboComptroller,
+} from "../utils/turboContracts";
+
+export const getTurboApprovedCollateral = async (
+ provider: JsonRpcProvider | Web3Provider
+): Promise => {
+ const multicallProvider = new providers.MulticallProvider(provider);
+
+ const TurboPool = createTurboComptroller(multicallProvider, 1);
+ console.log({ TurboPool });
+ const markets: string[] = await TurboPool.callStatic.getAllMarkets();
+
+ console.log({ markets });
+
+ const underlyings: string[] = (
+ await Promise.all(
+ markets.map(async (market) => {
+ const CERC20 = createCERC20Delegate(multicallProvider, market);
+ const underlying: string = await CERC20.callStatic.underlying();
+ return underlying;
+ })
+ )
+ ).filter((token) => token.toLowerCase() !== FEI.toLowerCase());
+
+ console.log({ underlyings, markets });
+
+ return underlyings;
+};
diff --git a/src/lib/turbo/fetchers/getIsUserAuthorizedToCreateSafes.tsx b/src/lib/turbo/fetchers/getIsUserAuthorizedToCreateSafes.tsx
new file mode 100644
index 00000000..bbcd747f
--- /dev/null
+++ b/src/lib/turbo/fetchers/getIsUserAuthorizedToCreateSafes.tsx
@@ -0,0 +1,21 @@
+import { providers } from "ethers"
+import { createTurboAuthority, ITurboMaster } from "lib/turbo/utils/turboContracts"
+
+export const isUserAuthorizedToCreateSafes = async (
+ provider: providers.BaseProvider,
+ authority: string,
+ user: string,
+ target: string
+) => {
+ const turboBoosterContract = await createTurboAuthority(provider, authority)
+
+ const functionSig = ITurboMaster.getSighash('createSafe')
+
+ const authorized = await turboBoosterContract.canCall(
+ user,
+ target,
+ functionSig
+ )
+
+ return authorized
+}
\ No newline at end of file
diff --git a/src/lib/turbo/fetchers/safes/getAllSafes.ts b/src/lib/turbo/fetchers/safes/getAllSafes.ts
new file mode 100644
index 00000000..56c90823
--- /dev/null
+++ b/src/lib/turbo/fetchers/safes/getAllSafes.ts
@@ -0,0 +1,12 @@
+import { providers } from "ethers"
+import { createTurboMaster } from "lib/turbo/utils/turboContracts"
+
+export const getAllSafes = async (provider: providers.Provider, chainID: number) => {
+ let master = createTurboMaster(provider, chainID)
+ try {
+ let result: any[] = await master.callStatic.getAllSafes()
+ return result
+ } catch (err) {
+ console.log(err)
+ }
+}
\ No newline at end of file
diff --git a/src/lib/turbo/fetchers/safes/getAllUserSafes.ts b/src/lib/turbo/fetchers/safes/getAllUserSafes.ts
new file mode 100644
index 00000000..c51e7445
--- /dev/null
+++ b/src/lib/turbo/fetchers/safes/getAllUserSafes.ts
@@ -0,0 +1,18 @@
+import { providers } from "ethers";
+import { createTurboLens } from "lib/turbo/utils/turboContracts";
+import { formatSafeInfo } from "./getSafeInfo";
+
+export const getAllUserSafes = async (
+ provider: providers.Provider,
+ user: string,
+ chainID: number
+) => {
+ let lens = createTurboLens(provider, chainID);
+ try {
+ let result: any[] = await lens.callStatic.getAllUserSafes(user);
+ const formattedResult = result.map(formatSafeInfo);
+ return formattedResult;
+ } catch (err) {
+ console.log(err);
+ }
+};
diff --git a/src/lib/turbo/fetchers/safes/getSafeInfo.ts b/src/lib/turbo/fetchers/safes/getSafeInfo.ts
new file mode 100644
index 00000000..35f69bb4
--- /dev/null
+++ b/src/lib/turbo/fetchers/safes/getSafeInfo.ts
@@ -0,0 +1,127 @@
+import { BigNumber, constants } from "ethers";
+import { formatEther, formatUnits } from "ethers/lib/utils";
+import { createTurboLens } from "../../utils/turboContracts";
+import {
+ formatStrategiesInfo,
+ LensStrategyInfo,
+ StrategyInfo,
+} from "../strategies/formatStrategyInfo";
+
+// Data directly from the TurboLens
+export type LensSafeInfo = [
+ safeAddress: string,
+ collateralAsset: string,
+ collateralAmount: BigNumber,
+ collateralPrice: BigNumber,
+ collateralFactor: BigNumber,
+ feiPrice: BigNumber,
+ collateralValue: BigNumber,
+ debtAmount: BigNumber,
+ debtValue: BigNumber,
+ boostedAmount: BigNumber,
+ feiAmount: BigNumber,
+ tribeDAOFee: BigNumber,
+ strategyInfo: LensStrategyInfo[]
+];
+
+// Formatted Safe Data
+export type SafeInfo = {
+ safeAddress: string;
+ collateralAsset: string;
+ collateralAmount: BigNumber;
+ collateralPrice: BigNumber;
+ collateralFactor: BigNumber;
+ feiPrice: BigNumber;
+ collateralValue: BigNumber;
+ debtAmount: BigNumber;
+ debtValue: BigNumber;
+ boostedAmount: BigNumber;
+ feiAmount: BigNumber;
+ tribeDAOFee: BigNumber;
+ strategies: StrategyInfo[];
+ safeUtilization: BigNumber;
+ maxBoost: BigNumber;
+ liquidationPrice: number;
+};
+
+export const formatSafeInfo = (safe: LensSafeInfo): SafeInfo => ({
+ safeAddress: safe[0],
+ collateralAsset: safe[1],
+ collateralAmount: safe[2],
+ collateralPrice: safe[3],
+ collateralFactor: safe[4],
+ feiPrice: safe[5],
+ collateralValue: safe[6],
+ debtAmount: safe[7],
+ debtValue: safe[8],
+ boostedAmount: safe[9],
+ feiAmount: safe[10],
+ tribeDAOFee: safe[11],
+ strategies: formatStrategiesInfo(safe[12], safe[11]),
+ safeUtilization: calculateSafeUtilization(safe[8], safe[6], safe[4]),
+ maxBoost: calculateMaxBoost(safe[6], safe[4]),
+ liquidationPrice: calcuateLiquidationPrice(
+ safe[8],
+ safe[6],
+ safe[4],
+ safe[3]
+ ),
+});
+
+// debtValue * 100 / collateralValue
+export const calculateSafeUtilization = (
+ debtValue: BigNumber,
+ collateralValue: BigNumber,
+ collateralFactor: BigNumber
+) => {
+ return collateralValue.isZero()
+ ? constants.Zero
+ : debtValue
+ .mul(100)
+ .div(collateralValue.mul(collateralFactor).div(constants.WeiPerEther));
+};
+
+export const calculateMaxBoost = (
+ collateralValue: BigNumber,
+ collateralFactor: BigNumber
+) => {
+ const maxBoost = collateralValue
+ .mul(collateralFactor)
+ .div(constants.WeiPerEther);
+ return maxBoost;
+};
+
+export const calcuateLiquidationPrice = (
+ debtValue: BigNumber,
+ collateralValue: BigNumber,
+ collateralFactor: BigNumber,
+ collateralPrice: BigNumber
+): number => {
+ const util = calculateSafeUtilization(
+ debtValue,
+ collateralValue,
+ collateralFactor
+ ).toNumber();
+ const liqPriceETH = parseFloat(formatUnits(collateralPrice.mul(util), 20));
+ console.log({ liqPriceETH });
+ return liqPriceETH;
+};
+
+export const getSafeInfo = async (
+ provider: any,
+ safe: string,
+ chainID: number
+) => {
+ let lens = createTurboLens(provider, chainID);
+
+ try {
+ const result: SafeInfo = formatSafeInfo(
+ await lens.callStatic.getSafeInfo(safe, { gasLimit: 12000000 })
+ );
+
+ return result;
+ } catch (err) {
+ console.log("LENS ERR", { err, lens });
+ throw err;
+ }
+};
diff --git a/src/lib/turbo/fetchers/safes/getUSDPricedSafeInfo.ts b/src/lib/turbo/fetchers/safes/getUSDPricedSafeInfo.ts
new file mode 100644
index 00000000..19c0b467
--- /dev/null
+++ b/src/lib/turbo/fetchers/safes/getUSDPricedSafeInfo.ts
@@ -0,0 +1,162 @@
+import { getEthUsdPriceBN } from "esm/utils/getUSDPriceBN";
+import { BigNumber } from "ethers";
+import { parseEther } from "ethers/lib/utils";
+import { EMPTY_ADDRESS } from "lib/turbo/utils/constants";
+import {
+ calculateETHValueUSD,
+ calculateFEIValueUSD,
+} from "lib/turbo/utils/usdUtils";
+import { StrategyInfo } from "../strategies/formatStrategyInfo";
+import { getSafeInfo, SafeInfo } from "./getSafeInfo";
+
+export interface USDPricedTurboSafe extends SafeInfo {
+ collateralValueUSD: number;
+ collateralPriceUSD: number;
+ debtUSD: number;
+ boostedUSD: number;
+ feiAmountUSD: number;
+ feiPriceUSD: number;
+ usdPricedStrategies: USDPricedStrategy[];
+ maxBoostUSD: number;
+ liquidationPriceUSD: number;
+}
+
+export interface USDPricedStrategy extends StrategyInfo {
+ boostAmountUSD: number;
+ feiAmountUSD: number;
+ feiEarnedUSD: number;
+ feiClaimableUSD: number;
+}
+
+export const getUSDPricedSafeInfo = async (
+ provider: any,
+ safe: string,
+ chainID: number
+): Promise => {
+ try {
+ const [ethUSDBN, safeInfo]: [BigNumber, SafeInfo] = await Promise.all([
+ getEthUsdPriceBN(),
+ getSafeInfo(provider, safe, chainID),
+ ]);
+
+ const collateralValueUSD = calculateETHValueUSD(
+ safeInfo.collateralValue,
+ ethUSDBN
+ );
+ const collateralPriceUSD = calculateETHValueUSD(
+ safeInfo.collateralPrice,
+ ethUSDBN
+ );
+ const debtUSD = calculateETHValueUSD(safeInfo.debtValue, ethUSDBN);
+ const boostedUSD = calculateFEIValueUSD(
+ safeInfo.boostedAmount,
+ safeInfo.feiPrice,
+ ethUSDBN
+ );
+ const feiAmountUSD = calculateFEIValueUSD(
+ safeInfo.feiAmount,
+ safeInfo.feiPrice,
+ ethUSDBN
+ );
+ const feiPriceUSD = calculateETHValueUSD(safeInfo.feiPrice, ethUSDBN);
+ const maxBoostUSD = calculateETHValueUSD(safeInfo.maxBoost, ethUSDBN);
+
+ const liquidationPriceUSD = calculateLiquidationPriceUSD(
+ safeInfo.liquidationPrice,
+ ethUSDBN
+ );
+
+ // Add USD values to each strategyInfo
+ const usdPricedStrategies = getUSDPricedStrategies(
+ ethUSDBN,
+ safeInfo.feiPrice,
+ safeInfo.strategies
+ );
+
+ const usdPricedSafe: USDPricedTurboSafe = {
+ ...safeInfo,
+ collateralValueUSD,
+ collateralPriceUSD,
+ debtUSD,
+ feiAmountUSD,
+ boostedUSD,
+ feiPriceUSD,
+ usdPricedStrategies,
+ maxBoostUSD,
+ liquidationPriceUSD,
+ };
+
+ return usdPricedSafe;
+ } catch (err) {
+ console.log(err);
+ throw err;
+ }
+};
+
+export const getUSDPricedStrategies = (
+ ethUSDBN: BigNumber,
+ feiPriceBN: BigNumber,
+ strategies: StrategyInfo[]
+): USDPricedStrategy[] => {
+ const usdPricedStrategies: USDPricedStrategy[] = [];
+
+ strategies.forEach((strategy) => {
+ let boostAmountUSD =
+ strategy.strategy === EMPTY_ADDRESS
+ ? 0
+ : calculateFEIValueUSD(strategy.boostedAmount, feiPriceBN, ethUSDBN);
+
+ let feiAmountUSD =
+ strategy.strategy === EMPTY_ADDRESS
+ ? 0
+ : calculateFEIValueUSD(strategy.feiAmount, feiPriceBN, ethUSDBN);
+
+ let feiEarnedUSD = feiAmountUSD - boostAmountUSD;
+ let feiClaimableUSD =
+ strategy.strategy === EMPTY_ADDRESS
+ ? 0
+ : calculateFEIValueUSD(strategy.feiClaimable, feiPriceBN, ethUSDBN);
+
+ let usdStrat: USDPricedStrategy = {
+ ...strategy,
+ boostAmountUSD,
+ feiAmountUSD,
+ feiEarnedUSD,
+ feiClaimableUSD,
+ };
+
+ usdPricedStrategies.push(usdStrat);
+ });
+
+ return usdPricedStrategies;
+ /*
+ const usdPricedStrategies = strategies
+ .reduce((arr, strategy) => {
+
+ const usdStrat: USDPricedStrategy = {
+ ...strategy,
+ boostAmountUSD: parseFloat(
+ formatEther(strategy.boostedAmount
+ .mul(ethUSDBN)
+ .div(constants.WeiPerEther))
+ ),
+ feiAmountUSD: parseFloat(
+ formatEther(strategy.boostedAmount
+ .mul(ethUSDBN)
+ .div(constants.WeiPerEther))
+ )
+ }
+
+ return [...arr, usdStrat]
+ }, [])
+ */
+};
+
+export const calculateLiquidationPriceUSD = (
+ liquidationPriceETH: number,
+ ethUSDBN: BigNumber
+) =>
+ calculateETHValueUSD(
+ BigNumber.from((liquidationPriceETH * 1e20).toFixed()), // 1e18 * 100
+ ethUSDBN
+ ) / 100;
diff --git a/src/lib/turbo/fetchers/strategies/formatStrategyInfo.ts b/src/lib/turbo/fetchers/strategies/formatStrategyInfo.ts
new file mode 100644
index 00000000..79cfbe4c
--- /dev/null
+++ b/src/lib/turbo/fetchers/strategies/formatStrategyInfo.ts
@@ -0,0 +1,56 @@
+import { BigNumber, constants } from "ethers";
+import { FuseERC4626Strategy } from "hooks/turbo/useStrategyInfo";
+import { DELISTED_STRATEGIES, EMPTY_ADDRESS } from "lib/turbo/utils/constants";
+
+// Data directly from the TurboLens
+export type LensStrategyInfo = [
+ strategy: string,
+ boostedAmount: BigNumber,
+ feiAmount: BigNumber
+];
+
+// Formatted strategy data
+export type StrategyInfo = {
+ strategy: string;
+ /// @notice the amount of fei boosted by the safe to this strategy
+ boostedAmount: BigNumber;
+ /// @notice the amount of fei held by the safe in this strategy
+ feiAmount: BigNumber;
+ /// @notice the amount of fei earned by the strategy
+ feiEarned: BigNumber;
+ feiClaimable: BigNumber;
+};
+
+export const formatStrategiesInfo = (
+ strategies: LensStrategyInfo[],
+ tribeDAOFeeShare: BigNumber,
+ shouldFilterStrategies: boolean = false
+): StrategyInfo[] | [] => {
+ const formattedStrategies = strategies
+ .map((strategy) => {
+ return {
+ strategy: strategy[0],
+ boostedAmount: strategy[1],
+ feiAmount: strategy[2],
+ feiEarned: strategy[2].sub(strategy[1]),
+ feiClaimable: strategy[2]
+ .sub(strategy[1])
+ .mul(constants.WeiPerEther.sub(tribeDAOFeeShare))
+ .div(constants.WeiPerEther),
+ } as StrategyInfo;
+ })
+ .filter((s) => !DELISTED_STRATEGIES[s.strategy.toLowerCase()]);
+
+ return shouldFilterStrategies
+ ? filterUsedStrategies(formattedStrategies)
+ : formattedStrategies;
+};
+
+export const filterUsedStrategies = (strats: StrategyInfo[] = []) =>
+ strats.filter((s) => s.boostedAmount._hex !== "0x00");
+
+//IE wfFEI-8
+export const getStrategyFusePoolId = (fuseStrategyName: string | undefined) => {
+ const arr = fuseStrategyName?.split("-") ?? [];
+ return arr[arr.length - 1];
+};
diff --git a/src/lib/turbo/fetchers/strategies/getBoostCapsForStrategies.ts b/src/lib/turbo/fetchers/strategies/getBoostCapsForStrategies.ts
new file mode 100644
index 00000000..ad887328
--- /dev/null
+++ b/src/lib/turbo/fetchers/strategies/getBoostCapsForStrategies.ts
@@ -0,0 +1,51 @@
+import { JsonRpcProvider, Web3Provider } from "@ethersproject/providers"
+import { BigNumber } from "ethers"
+import { TurboAddresses } from "lib/turbo/utils/constants"
+import { createTurboBooster, createTurboMaster, ITurboBooster } from "lib/turbo/utils/turboContracts"
+import { callStaticWithMultiCall, encodeCall, EncodedCall } from "utils/multicall"
+import { providers } from "@0xsequence/multicall";
+
+type BoostCapForStrategyMap = {
+ [strategy: string]: BigNumber
+}
+
+
+// Multicall Boost Caps for all strategies passed in
+// Return a map of strategy Address to boost cap
+export const getBoostCapForStrategy = async (
+ provider: JsonRpcProvider | Web3Provider,
+ strategy: string
+): Promise<[boostCap: BigNumber, totalBoosted: BigNumber, boostRemaining: BigNumber]> => {
+ const multicallProvider = new providers.MulticallProvider(provider)
+ const booster = createTurboBooster(multicallProvider, 1)
+ const master = createTurboMaster(multicallProvider, 1)
+ const cap = await booster.callStatic.getBoostCapForVault(strategy)
+ const totalBoosted = await master.callStatic.getTotalBoostedForVault(strategy)
+ const boostRemaining = cap.sub(totalBoosted)
+ return [cap, totalBoosted, boostRemaining]
+}
+
+// Multicall Boost Caps for all strategies passed in
+// Return a map of strategy Address to boost cap
+export const getBoostCapsForStrategies = async (
+ provider: JsonRpcProvider | Web3Provider,
+ strategies: string[]
+): Promise => {
+
+
+ const encodedCalls: EncodedCall[] = strategies.map(
+ strategyAddr => encodeCall(
+ ITurboBooster,
+ TurboAddresses[1].BOOSTER,
+ "getBoostCapForVault",
+ [strategyAddr]
+ )
+ )
+
+ const { returnData } = await callStaticWithMultiCall(provider, encodedCalls)
+
+ return strategies.reduce((acc: BoostCapForStrategyMap, curr, i) => ({
+ ...acc,
+ [curr]: BigNumber.from(returnData[i])
+ }), {})
+}
\ No newline at end of file
diff --git a/src/lib/turbo/fetchers/strategies/getBoostableStrategies.tsx b/src/lib/turbo/fetchers/strategies/getBoostableStrategies.tsx
new file mode 100644
index 00000000..78e2b229
--- /dev/null
+++ b/src/lib/turbo/fetchers/strategies/getBoostableStrategies.tsx
@@ -0,0 +1,8 @@
+import { providers } from "ethers"
+import { createTurboBooster } from "lib/turbo/utils/turboContracts"
+
+export const getBoostableStrategies = async (provider: providers.Provider, chainID: number) => {
+ const turboBoosterContract = await createTurboBooster(provider, chainID)
+ const boostableStrategies: string[] = await turboBoosterContract.callStatic.getBoostableVaults()
+ return boostableStrategies
+}
\ No newline at end of file
diff --git a/src/lib/turbo/transactions/claim.ts b/src/lib/turbo/transactions/claim.ts
new file mode 100644
index 00000000..c7dd4290
--- /dev/null
+++ b/src/lib/turbo/transactions/claim.ts
@@ -0,0 +1,52 @@
+import { FEI } from "../utils/constants";
+import { sendRouterWithMultiCall } from "../utils/turboMulticall";
+import encodeCall from "./encodedCalls";
+
+interface SafeClaimParams {
+ safeAddress: string,
+ strategies: string[],
+ recipient: string,
+ signer: any,
+ chainID: number,
+}
+
+/*
+/* Multicall
+/* 1.) Slurp all strategies to safe
+/* 2.) Sweep the safe to recipient
+*/
+export const safeClaimAll = async (
+ {
+ safeAddress,
+ strategies,
+ recipient,
+ signer,
+ chainID,
+ }: SafeClaimParams
+) => {
+
+ // 1.) Slurp all strategies
+ const encodedSlurps = strategies.map(strategy =>
+ encodeCall.slurp(safeAddress, strategy)
+ );
+
+ // 2.) Sweep da safe
+ const encodedSweepAll = encodeCall.sweepAll(safeAddress, recipient, FEI);
+
+ const encodedCalls = [
+ ...encodedSlurps,
+ encodedSweepAll
+ ]
+
+ try {
+ const tx = await sendRouterWithMultiCall(
+ signer,
+ encodedCalls,
+ chainID
+ );
+ return tx
+ } catch (err) {
+ console.error(err);
+ throw err
+ }
+}
\ No newline at end of file
diff --git a/src/lib/turbo/transactions/createSafeAndDeposit.ts b/src/lib/turbo/transactions/createSafeAndDeposit.ts
new file mode 100644
index 00000000..3293c0a8
--- /dev/null
+++ b/src/lib/turbo/transactions/createSafeAndDeposit.ts
@@ -0,0 +1,50 @@
+import {
+ encodeRouterCall,
+ sendRouterWithMultiCall,
+} from "lib/turbo/utils/turboMulticall";
+import { BigNumber, Signer } from "ethers";
+import { TurboAddresses } from "lib/turbo/utils/constants";
+import { ITurboRouter } from "lib/turbo/utils/turboContracts";
+
+export const createSafeAndDeposit = async (
+ signer: Signer,
+ amount: BigNumber,
+ chainID: number,
+ underlyingTokenAddress: string
+) => {
+ const provider = signer.provider;
+ if (!provider) return;
+
+ const userAddress = await signer.getAddress();
+
+ const pullTokensArgs = [underlyingTokenAddress, amount, TurboAddresses[chainID].ROUTER];
+
+ const createAndDepositArgs = [underlyingTokenAddress, userAddress, amount, amount];
+
+ const encodedCreateSafeAndDeposit = encodeRouterCall(
+ ITurboRouter,
+ "createSafeAndDeposit",
+ createAndDepositArgs
+ );
+
+ const encodedPullTokens = encodeRouterCall(
+ ITurboRouter,
+ "pullToken",
+ pullTokensArgs
+ );
+
+ const encodedCalls = [encodedPullTokens, encodedCreateSafeAndDeposit];
+
+ try {
+ const tx = await sendRouterWithMultiCall(
+ signer,
+ encodedCalls,
+ chainID
+ );
+
+ return tx;
+ } catch (e) {
+ console.log(e);
+ throw e
+ }
+};
diff --git a/src/lib/turbo/transactions/encodedCalls.ts b/src/lib/turbo/transactions/encodedCalls.ts
new file mode 100644
index 00000000..24209717
--- /dev/null
+++ b/src/lib/turbo/transactions/encodedCalls.ts
@@ -0,0 +1,115 @@
+import { BigNumber } from "ethers";
+import { ITurboRouter } from "lib/turbo/utils/turboContracts";
+import { encodeRouterCall } from "lib/turbo/utils/turboMulticall";
+// TODO: natspec on funcs
+
+/** Periphery Payments **/
+type PullTokenArgs = [token: string, amount: BigNumber, recipient: string];
+const pullTokens = (
+ token: string,
+ amount: BigNumber,
+ recipient: string
+) => {
+ const pullTokenArgs: PullTokenArgs = [token, amount, recipient];
+ return encodeRouterCall(ITurboRouter, "pullToken", pullTokenArgs);
+};
+
+/** Create Safe **/
+type CreateSafeAndDepositArgs = [
+ token: string,
+ userAddress: string,
+ amount: BigNumber,
+ sharesRecieved: BigNumber
+];
+const createSafeAndDeposit = (
+ token: string,
+ userAddress: string,
+ amount: BigNumber,
+ sharesRecieved: BigNumber
+) => {
+ const createAndDepositArgs: CreateSafeAndDepositArgs = [
+ token,
+ userAddress,
+ amount,
+ sharesRecieved,
+ ];
+ return encodeRouterCall(
+ ITurboRouter,
+ "createSafeAndDeposit",
+ createAndDepositArgs
+ );
+};
+
+type CreateSafeArgs = [
+ token: string,
+];
+
+const createSafe = (
+ underlyingToken: string,
+) => {
+ const createSafe: CreateSafeArgs = [
+ underlyingToken
+ ];
+ return encodeRouterCall(
+ ITurboRouter,
+ "createSafe",
+ createSafe
+ );
+};
+
+/** Boost / Less **/
+type BoostAndLessArgs = [safe: string, strategy: string, amount: BigNumber];
+const boost = (safe: string, strategy: string, amount: BigNumber) => {
+ const boostArgs: BoostAndLessArgs = [safe, strategy, amount];
+ return encodeRouterCall(ITurboRouter, "boost", boostArgs);
+};
+
+const less = (safe: string, strategy: string, amount: BigNumber) => {
+ const boostArgs: BoostAndLessArgs = [safe, strategy, amount];
+ return encodeRouterCall(ITurboRouter, "less", boostArgs);
+};
+
+/** Deposit / Withdraw **/
+type DepositAndWithdrawArgs = any[];
+const deposit = (safe: string, strategy: string, amount: BigNumber) => {
+ const boostArgs: DepositAndWithdrawArgs = [safe, strategy, amount];
+ return encodeRouterCall(ITurboRouter, "boost", boostArgs);
+};
+
+const withdraw = (safe: string, strategy: string, amount: BigNumber) => {
+ const boostArgs: DepositAndWithdrawArgs = [safe, strategy, amount];
+ return encodeRouterCall(ITurboRouter, "less", boostArgs);
+};
+
+type SlurpArgs = [safe: string, strategy: string];
+const slurp = (safe: string, strategy: string) => {
+ const slurpArgs: SlurpArgs = [safe, strategy];
+ return encodeRouterCall(ITurboRouter, "slurp", slurpArgs);
+};
+
+type SweepArgs = [safe: string, recepient: string, tokenAddress: string, amount: BigNumber];
+const sweep = (safe: string, recepient: string, tokenAddress: string, amount: BigNumber) => {
+ const sweepArgs: SweepArgs = [safe, recepient, tokenAddress, amount];
+ return encodeRouterCall(ITurboRouter, "sweep", sweepArgs);
+};
+
+type SweepAllArgs = [safe: string, recepient: string, tokenAddress: string];
+const sweepAll = (safe: string, recepient: string, tokenAddress: string) => {
+ const sweepAllArgs: SweepAllArgs = [safe, recepient, tokenAddress];
+ return encodeRouterCall(ITurboRouter, "sweepAll", sweepAllArgs);
+}
+
+const encodeCall = {
+ pullTokens,
+ createSafeAndDeposit,
+ createSafe,
+ boost,
+ less,
+ deposit,
+ withdraw,
+ slurp,
+ sweep,
+ sweepAll
+}
+
+export default encodeCall
\ No newline at end of file
diff --git a/src/lib/turbo/transactions/safe.ts b/src/lib/turbo/transactions/safe.ts
new file mode 100644
index 00000000..82a712b5
--- /dev/null
+++ b/src/lib/turbo/transactions/safe.ts
@@ -0,0 +1,121 @@
+import { BigNumber, providers } from "ethers";
+import encodeCall from "lib/turbo/transactions/encodedCalls";
+import {
+ createTurboMaster,
+ createTurboSafe,
+} from "lib/turbo/utils/turboContracts";
+import { sendRouterWithMultiCall } from "lib/turbo/utils/turboMulticall";
+
+export const createSafe = async (
+ underlyingToken: string,
+ provider: providers.JsonRpcProvider | providers.Web3Provider,
+ chainID: number
+) => {
+ const turboMaster = createTurboMaster(await provider.getSigner(), 1);
+ const tx = await turboMaster.createSafe(underlyingToken);
+ return tx;
+};
+
+export const safeBoost = async (
+ safe: string,
+ strategy: string,
+ amount: BigNumber,
+ signer: providers.JsonRpcSigner
+) => {
+ const turboSafeContract = await createTurboSafe(signer.provider, safe);
+ const connectedTurboSafe = turboSafeContract.connect(signer);
+ const receipt = await connectedTurboSafe.boost(strategy, amount);
+ return await receipt;
+};
+
+export const safeDeposit = async (
+ safe: string,
+ recipient: string,
+ amount: BigNumber,
+ signer: providers.JsonRpcSigner | any
+) => {
+ if (process.env.NEXT_PUBLIC_USE_MOCKS === "true") {
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ return;
+ }
+
+ const turboSafeContract = await createTurboSafe(signer, safe);
+ const tx = await turboSafeContract.deposit(amount, recipient);
+ return tx;
+};
+
+export const safeWithdraw = async (
+ safe: string,
+ recipient: string,
+ amount: BigNumber,
+ signer: providers.JsonRpcSigner | any
+) => {
+ const turboSafeContract = await createTurboSafe(signer, safe);
+ const tx = await turboSafeContract.withdraw(amount, recipient, recipient);
+ return tx;
+};
+
+export const safeLess = async (
+ safe: string,
+ strategy: string,
+ amount: BigNumber,
+ lessingMax: boolean,
+ signer: any,
+ chainID: number
+) => {
+ let encodedCalls = [];
+
+ if (lessingMax) {
+ const encodedSlurp = encodeCall.slurp(safe, strategy);
+ encodedCalls.push(encodedSlurp);
+ }
+
+ const encodedLess = encodeCall.less(safe, strategy, amount);
+ encodedCalls.push(encodedLess);
+
+ try {
+ const tx = await sendRouterWithMultiCall(signer, encodedCalls, chainID);
+ return tx;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const safeSlurp = async (
+ safe: string,
+ strategies: string[],
+ signer: any,
+ chainID: number
+) => {
+ const encodedSlurps = strategies.map((vault) =>
+ encodeCall.slurp(safe, vault)
+ );
+
+ try {
+ const result = await sendRouterWithMultiCall(
+ signer,
+ encodedSlurps,
+ chainID
+ );
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const safeSweep = async (
+ safe: string,
+ recepient: string,
+ tokenAddress: string,
+ amount: BigNumber,
+ chainID: number,
+ signer: any
+) => {
+ const encodedSweep = encodeCall.sweep(safe, recepient, tokenAddress, amount);
+
+ try {
+ const tx = await sendRouterWithMultiCall(signer, [encodedSweep], chainID);
+ return tx;
+ } catch (err) {
+ console.error(err);
+ }
+};
diff --git a/src/lib/turbo/utils/constants.ts b/src/lib/turbo/utils/constants.ts
new file mode 100644
index 00000000..29e2f914
--- /dev/null
+++ b/src/lib/turbo/utils/constants.ts
@@ -0,0 +1,55 @@
+import { ChainID } from "esm/utils/networks";
+
+export const TurboAddresses: TurboAddresses = {
+ 1: {
+ MASTER: "0xf2e513d3b4171bb115cb9ffc45555217fbbbd00c",
+ ADMIN: "0x18413D61b335D2F46235E9E1256Fd5ec8AD03757",
+ ROUTER: "0x550b756ad4fbef34db5ac84ad41f9cf3c8927d33",
+ LENS: "0x38e16f5c4556d3ddb938954a632a0b761701e9b3",
+ STRATEGY: "0xac4c093c777581dc9c4dc935394ff11e6c58cd45",
+ BOOSTER: "0xf6c7f4a90b10c9eaaf2a6676ce81fe8673453e72",
+ CLERK: "0x1F45Af9bfDb6ab4B95311e27BEcA59B33A7E17D7",
+ COMPTROLLER: "0x1d9EEE473CC1B3b6D316740F5677Ef36E8f0329e",
+ ORACLE: "0xFb5b08d17dc5Bc5C8627c10dBed11614b43dc0F1",
+ TURBO_AUTHORITY: "0x286c9724a0C1875233cf17A4ffE475A0BD8158dE",
+ },
+ 31337: {
+ MASTER: "0xf2e513d3b4171bb115cb9ffc45555217fbbbd00c",
+ ADMIN: "0x18413D61b335D2F46235E9E1256Fd5ec8AD03757",
+ ROUTER: "0x550b756ad4fbef34db5ac84ad41f9cf3c8927d33",
+ LENS: "0x38e16f5c4556d3ddb938954a632a0b761701e9b3",
+ STRATEGY: "0xac4c093c777581dc9c4dc935394ff11e6c58cd45",
+ BOOSTER: "0xf6c7f4a90b10c9eaaf2a6676ce81fe8673453e72",
+ CLERK: "0x1F45Af9bfDb6ab4B95311e27BEcA59B33A7E17D7",
+ COMPTROLLER: "0x1d9EEE473CC1B3b6D316740F5677Ef36E8f0329e",
+ ORACLE: "0xFb5b08d17dc5Bc5C8627c10dBed11614b43dc0F1",
+ TURBO_AUTHORITY: "0x286c9724a0C1875233cf17A4ffE475A0BD8158dE",
+ },
+};
+
+type TurboAddresses = {
+ [chainId: number]: {
+ MASTER: string;
+ ADMIN: string;
+ ROUTER: string;
+ LENS: string;
+ STRATEGY: string;
+ BOOSTER: string;
+ CLERK: string;
+ COMPTROLLER: string;
+ ORACLE: string;
+ TURBO_AUTHORITY: string;
+ };
+};
+
+export const TRIBE = "0xc7283b66Eb1EB5FB86327f08e1B5816b0720212B";
+export const FEI = "0x956F47F50A910163D8BF957Cf5846D573E7f87CA";
+export const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000";
+
+export const DELISTED_STRATEGIES: { [strat: string]: boolean } = {
+ "0xb734cc08a38f0b81e7d3ddd38dfbd66a66f1a6ba": true,
+ "0xac4c093c777581dc9c4dc935394ff11e6c58cd45": true,
+};
+
+const isTurboSupportedNetwork = (chainId: number) =>
+ Object.keys(TurboAddresses).includes(chainId.toString());
diff --git a/src/lib/turbo/utils/decodeEvents.ts b/src/lib/turbo/utils/decodeEvents.ts
new file mode 100644
index 00000000..48c89b68
--- /dev/null
+++ b/src/lib/turbo/utils/decodeEvents.ts
@@ -0,0 +1,32 @@
+import { Contract, EventFilter } from "ethers"
+
+export const getRecentEvent = async (
+ contract: Contract,
+ filter: (...args: any[]) => EventFilter,
+) => {
+ const blockNumber = await contract.provider.getBlockNumber()
+ const events: any[] = await contract.queryFilter(filter(), blockNumber - 20, blockNumber )
+
+ return events[0]
+}
+
+export const decodeEvent = (
+ decode: (data: string, topics?: Array) => any,
+ data: string,
+ topics: string[]
+) => {
+ const decodedEvent = decode(data, topics)
+
+ return decodedEvent
+}
+
+export const getRecentEventDecoded = async (
+ contract: Contract,
+ filter: (...args: any[]) => EventFilter,
+) => {
+ const event = await getRecentEvent(contract, filter)
+ const decodedEvent = decodeEvent(event.decode, event.data, event.topics)
+
+
+ return decodedEvent
+}
\ No newline at end of file
diff --git a/src/lib/turbo/utils/fetchMaxSafeAmount.ts b/src/lib/turbo/utils/fetchMaxSafeAmount.ts
new file mode 100644
index 00000000..55a3e87d
--- /dev/null
+++ b/src/lib/turbo/utils/fetchMaxSafeAmount.ts
@@ -0,0 +1,130 @@
+import { BigNumber, constants } from "ethers";
+import { formatEther, parseEther } from "ethers/lib/utils";
+import { SafeInteractionMode } from "hooks/turbo/useUpdatedSafeInfo";
+import { format } from "path";
+import { balanceOf } from "utils/erc20Utils";
+import { SafeInfo } from "../fetchers/safes/getSafeInfo";
+import { USDPricedTurboSafe } from "../fetchers/safes/getUSDPricedSafeInfo";
+import { getBoostCapForStrategy } from "../fetchers/strategies/getBoostCapsForStrategies";
+import { FEI } from "./constants";
+import {
+ createFusePoolLensSecondary,
+ createTurboComptroller,
+ createTurboSafe,
+} from "./turboContracts";
+
+export async function fetchMaxSafeAmount(
+ provider: any,
+ mode: SafeInteractionMode,
+ userAddress: string,
+ safe: USDPricedTurboSafe | undefined,
+ chainId: number,
+ strategyIndex?: number,
+ limitBorrow?: boolean // Whether we should limit to 75%
+) {
+ if (!safe) return constants.Zero;
+
+ if (mode === SafeInteractionMode.DEPOSIT) {
+ const balance = await balanceOf(
+ userAddress,
+ safe.collateralAsset,
+ provider
+ );
+ return balance;
+ }
+
+ // TODO(@sharad-s) implement after Lens func is in-place: https://github.com/fei-protocol/tribe-turbo/issues/86
+ if (mode === SafeInteractionMode.WITHDRAW) {
+ let maxWithdraw;
+
+ // If Safe Utilization is above 75%, they can't withdraw anything
+ if (safe.safeUtilization.gte(75)) return constants.Zero;
+
+ // If safe has debt, calculate the amount you can withdraw to get utilization to 75%.
+ if (safe.debtAmount.gt(0)) {
+ // Utillization = maxBoost / activeBoost
+ // 1. Calculate maxBoost. Denominated in dollars.
+ // 74 - activeBoost
+ // 100 - x
+ // so...
+ // 100 * activeDebt / 74 = x
+ // where x is minimum maxBoost targeted.
+ const debt = parseEther(safe.debtUSD.toString());
+ const percentage = parseEther("100");
+ const utilization = parseEther("74");
+ const targetMaxBoostInUSD = debt.mul(percentage).div(utilization);
+
+ // 2. Get minimum collateral necessary to get the targetMaxBoost, in USD.
+ const minimumCollateralValueInUSD = parseEther(
+ targetMaxBoostInUSD.div(safe.collateralFactor).toString()
+ );
+
+ // 3. Calculate minimum collateral denominated in TRIBE
+ const minimumCollateralInTRIBE = minimumCollateralValueInUSD.div(
+ parseEther(safe.collateralPriceUSD.toString())
+ );
+
+ // 4. From current deposited collateral, substract minimum collateral to get max withdrawable amount
+ maxWithdraw = safe.collateralAmount.sub(
+ parseEther(minimumCollateralInTRIBE.toString())
+ );
+ } else {
+ // If 0 safeUtilization then withdraw full balance of safe
+ const turboSafe = createTurboSafe(provider, safe.safeAddress);
+ maxWithdraw = await turboSafe.callStatic.maxWithdraw(userAddress);
+ }
+
+ return maxWithdraw;
+ }
+
+ if (mode === SafeInteractionMode.BOOST) {
+ if (strategyIndex === undefined || !safe.strategies) return constants.Zero;
+
+ const TurboComptroller = createTurboComptroller(provider, chainId);
+ const FusePoolLensSecondary = createFusePoolLensSecondary(provider);
+
+ const cToken = await TurboComptroller.callStatic.cTokensByUnderlying(FEI);
+
+ // Safe's Max Borrow
+ const maxBorrow = await FusePoolLensSecondary.callStatic.getMaxBorrow(
+ safe.safeAddress,
+ cToken
+ );
+
+ // Strategy Boost Cap
+ const [boostCap, totalBoosted, boostRemaining] =
+ await getBoostCapForStrategy(
+ provider,
+ safe.strategies[strategyIndex].strategy
+ );
+
+ console.log({ boostCap, totalBoosted, boostRemaining });
+
+ // Prevent rekt
+ let amount: BigNumber;
+ if (!!limitBorrow) {
+ amount = maxBorrow;
+ } else {
+ amount = maxBorrow;
+ }
+
+ // // Max Amount can't be higher than Boost Cap
+ // if (amount.gt(boostRemaining)) {
+ // amount = boostRemaining;
+ // }
+
+ console.log({ boostCap, totalBoosted, boostRemaining });
+ return amount;
+ }
+
+ // This one is unique as it is applied to a specific strategy
+ if (mode === SafeInteractionMode.LESS) {
+ if (strategyIndex === undefined || !safe.strategies) return constants.Zero;
+ const strategy = safe.strategies[strategyIndex];
+ if (!strategy) return constants.Zero;
+ const maxLess = strategy.boostedAmount;
+ return maxLess;
+ }
+
+ return constants.Zero;
+}
diff --git a/src/lib/turbo/utils/formatters.ts b/src/lib/turbo/utils/formatters.ts
new file mode 100644
index 00000000..cb0ff5c3
--- /dev/null
+++ b/src/lib/turbo/utils/formatters.ts
@@ -0,0 +1 @@
+export {};
diff --git a/src/lib/turbo/utils/getMarketCF.ts b/src/lib/turbo/utils/getMarketCF.ts
new file mode 100644
index 00000000..25b82d8d
--- /dev/null
+++ b/src/lib/turbo/utils/getMarketCF.ts
@@ -0,0 +1,15 @@
+import { JsonRpcProvider } from "@ethersproject/providers"
+import { BigNumber } from "ethers"
+import { createTurboComptroller } from "./turboContracts"
+
+export async function getMarketCf(
+ provider: JsonRpcProvider,
+ chainId: number,
+ underlyingAddress: string
+): Promise {
+ const turboComptrollerContract = createTurboComptroller(provider, chainId)
+ const marketAddress = await turboComptrollerContract.callStatic.cTokensByUnderlying(underlyingAddress)
+ const market = await turboComptrollerContract.markets(marketAddress)
+ return market.collateralFactorMantissa
+}
+
diff --git a/src/lib/turbo/utils/getUserFeiOwed.ts b/src/lib/turbo/utils/getUserFeiOwed.ts
new file mode 100644
index 00000000..8b87f337
--- /dev/null
+++ b/src/lib/turbo/utils/getUserFeiOwed.ts
@@ -0,0 +1,28 @@
+import { BigNumber, constants } from "ethers";
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+
+// (boostedAmount - debtAmount) + ((feiAmount - boostedAmount) * revShare)
+export const getUserFeiOwed = (safe: SafeInfo | undefined): BigNumber => {
+ if (!safe) return constants.Zero;
+ const { boostedAmount, debtAmount, feiAmount, tribeDAOFee } = safe;
+ const boostedAmountAfterDebtRepaid = boostedAmount.sub(debtAmount);
+ const yieldAccruedBySafe = feiAmount.sub(boostedAmount);
+
+ const userShare = 1 - parseFloat(tribeDAOFee.toString()) / 1e18;
+ const revShareUser = BigNumber.from(userShare * 100)
+
+ const feiOwedForUser = boostedAmountAfterDebtRepaid.add(
+ yieldAccruedBySafe.mul(revShareUser)
+ ).div(100);
+
+ return feiOwedForUser;
+};
+
+// (boostedAmount - debtAmount) + ((feiAmount - boostedAmount) * revShare)
+export const getUserFeiOwedWithBalance = (safe: SafeInfo | undefined, safeBalanceOfFei: BigNumber): BigNumber => {
+ if (!safe) return constants.Zero;
+ const claimableFromStrategies = getUserFeiOwed(safe)
+ const total = claimableFromStrategies.add(safeBalanceOfFei)
+ return total;
+};
+
diff --git a/src/lib/turbo/utils/turboContracts.ts b/src/lib/turbo/utils/turboContracts.ts
new file mode 100644
index 00000000..932ca2f2
--- /dev/null
+++ b/src/lib/turbo/utils/turboContracts.ts
@@ -0,0 +1,190 @@
+import { Contract } from "ethers";
+
+// ABIS
+import TurboRouter from "lib/turbo/abi/TurboRouter.json";
+import TurboMaster from "lib/turbo/abi/TurboMaster.json";
+import ERC20 from "lib/turbo/abi/ERC20.json";
+import CERC20 from "lib/turbo/abi/CERC20.json";
+import CERC20Delegate from "lib/turbo/abi/CERC20Delegate.json";
+import FuseERC4626 from "lib/turbo/abi/FuseERC4626.json";
+import TurboComptroller from "lib/turbo/abi/comptroller.json";
+import TurboLens from "lib/turbo/abi/TurboLens.json";
+import TurboBooster from "lib/turbo/abi/TurboBooster.json";
+import TurboSafe from "lib/turbo/abi/TurboSafe.json";
+import TurboAuthority from "lib/turbo/abi/Authority.json";
+
+// Fuse
+import FusePoolLensSecondary from "esm/Fuse/contracts/abi/FusePoolLensSecondary.json";
+import FusePoolLens from "esm/Fuse/contracts/abi/FusePoolLens.json";
+import FusePoolDirectory from "esm/Fuse/contracts/abi/FusepoolDirectory.json";
+
+// Utils
+import { Interface } from "ethers/lib/utils";
+import { TurboAddresses } from "../utils/constants";
+import { providers } from "ethers";
+import addresses from "esm/Fuse/addresses";
+
+//** Contracts **/
+export const createTurboRouter = async (
+ provider: providers.JsonRpcProvider,
+ id: number
+) => {
+ const turboRouterContract = new Contract(
+ TurboAddresses[id].ROUTER,
+ TurboRouter.abi,
+ provider
+ );
+ return turboRouterContract;
+};
+
+export const createTurboMaster = (
+ provider: any,
+ id: number = 1
+) => {
+ const turboMasterContract = new Contract(
+ TurboAddresses[id].MASTER,
+ TurboMaster.abi,
+ provider
+ );
+
+ return turboMasterContract;
+};
+
+export const createTurboComptroller = (
+ provider: providers.Provider,
+ id: number
+) => {
+ const turboRouterContract = new Contract(
+ TurboAddresses[id].COMPTROLLER,
+ TurboComptroller,
+ provider
+ );
+
+ return turboRouterContract;
+};
+
+export const createTurboLens = (
+ provider: providers.Provider,
+ chainID: number
+) => {
+ const turboLens = new Contract(
+ TurboAddresses[chainID].LENS,
+ TurboLens.abi,
+ provider
+ );
+
+ return turboLens;
+};
+
+export const createTurboBooster = (
+ provider: providers.Provider,
+ chainID: number
+) => {
+ const turboBoosterContract = new Contract(
+ TurboAddresses[chainID].BOOSTER,
+ TurboBooster.abi,
+ provider
+ );
+
+ return turboBoosterContract;
+};
+
+export const createTurboSafe = (
+ provider: providers.Provider,
+ turboSafe: string
+) => {
+ const turboSafeContract = new Contract(turboSafe, TurboSafe.abi, provider);
+ return turboSafeContract;
+};
+
+export const createTurboAuthority = async (
+ provider: providers.BaseProvider,
+ authorityAddress: string
+) => {
+ const turboAuthorityContract = new Contract(
+ authorityAddress,
+ TurboAuthority.abi,
+ provider
+ );
+
+ return turboAuthorityContract;
+};
+
+/* Token Contracts */
+
+export const createERC20 = (token: string, provider: providers.Provider) =>
+ new Contract(token, ERC20.abi, provider);
+
+export const createCERC20 = (
+ provider: providers.Provider,
+ tokenAddress: string
+) => new Contract(tokenAddress, CERC20.abi, provider);
+
+export const createCERC20Delegate = (
+ provider: providers.Provider,
+ tokenAddress: string
+) => new Contract(tokenAddress, CERC20Delegate, provider);
+
+export const createFuseERC4626 = (
+ provider: providers.JsonRpcProvider,
+ strategyAddress: string
+) => {
+ const FuseERC4626Contract = new Contract(
+ strategyAddress,
+ FuseERC4626,
+ provider
+ );
+
+ return FuseERC4626Contract;
+};
+
+/** Lens **/
+
+export const createFusePoolLensSecondary = (
+ provider: providers.JsonRpcProvider,
+ chainId: number = 1
+) => {
+ const addr = addresses[chainId].FUSE_POOL_LENS_SECONDARY_CONTRACT_ADDRESS;
+
+ const fusePoolLensSecondary = new Contract(
+ addr,
+ FusePoolLensSecondary,
+ provider
+ );
+ return fusePoolLensSecondary;
+};
+
+export const createFusePoolLens = (
+ provider: providers.JsonRpcProvider,
+ chainId: number = 1
+) => {
+ const addr = addresses[chainId].FUSE_POOL_LENS_CONTRACT_ADDRESS;
+ const fusePoolLens = new Contract(addr, FusePoolLens, provider);
+ return fusePoolLens;
+};
+
+export const createFusePoolDirectory = (
+ provider: providers.JsonRpcProvider,
+ chainId: number = 1
+) => {
+ const addr = addresses[chainId].FUSE_POOL_DIRECTORY_CONTRACT_ADDRESS;
+ const fusePoolDir = new Contract(addr, FusePoolDirectory, provider);
+ return fusePoolDir;
+};
+
+/** Interfaces **/
+
+// Turbo Ifaces
+export const ITurboRouter = new Interface(TurboRouter.abi);
+export const ITurboMaster = new Interface(TurboMaster.abi);
+export const ITurboComptroller = new Interface(TurboComptroller);
+export const ITurboSafe = new Interface(TurboSafe.abi);
+export const ITurboBooster = new Interface(TurboBooster.abi);
+export const ITurboLens = new Interface(TurboLens.abi);
+export const ITurboAuthority = new Interface(TurboAuthority.abi);
+
+// Etc Ifaces
+export const IERC20 = new Interface(ERC20.abi);
+export const ICERC20 = new Interface(CERC20.abi);
+export const ICERC20Delegate = new Interface(CERC20Delegate);
+export const IFuseERC4626 = new Interface(FuseERC4626);
diff --git a/src/lib/turbo/utils/turboMulticall.ts b/src/lib/turbo/utils/turboMulticall.ts
new file mode 100644
index 00000000..9be704cc
--- /dev/null
+++ b/src/lib/turbo/utils/turboMulticall.ts
@@ -0,0 +1,62 @@
+import { providers, Signer } from "ethers";
+import { Interface } from "ethers/lib/utils";
+import { createTurboRouter } from './turboContracts'
+
+// returns callData for a function call to be parsed through multicall
+export const encodeRouterCall = (
+ iface: Interface,
+ functionName: string,
+ params: any[]
+): string => iface.encodeFunctionData(functionName, [...params])
+
+
+export const decodeRouterCall = (
+ iface: Interface,
+ functionName: string,
+ txResult: any
+): any => iface.decodeFunctionResult(functionName, txResult);
+
+
+export const callRouterWithMultiCall = async (
+ provider: providers.JsonRpcProvider,
+ encodedCalls: string[],
+ chainID: number,
+ simulatedAddress?: string,
+) => {
+ const router = await createTurboRouter(provider, chainID);
+
+ let options: any = {};
+ if (!!simulatedAddress) options.address = simulatedAddress;
+
+ const returnDatas = await router.callStatic.multicall(
+ encodedCalls,
+ options
+ );
+
+ return returnDatas;
+};
+
+
+export const sendRouterWithMultiCall = async (
+ signer: Signer,
+ encodedCalls: string[],
+ chainID: number,
+ simulatedAddress?: string
+) => {
+ // @ts-ignore
+ const router = await createTurboRouter(signer.provider, chainID);
+
+ const routerWithSigner = router.connect(signer)
+
+ let options: any = {};
+ if (!!simulatedAddress) options.address = simulatedAddress;
+
+ const tx = await routerWithSigner.multicall(
+ encodedCalls,
+ options
+ );
+
+
+ return tx;
+};
+
diff --git a/src/lib/turbo/utils/usdUtils.ts b/src/lib/turbo/utils/usdUtils.ts
new file mode 100644
index 00000000..e0e4ce4d
--- /dev/null
+++ b/src/lib/turbo/utils/usdUtils.ts
@@ -0,0 +1,25 @@
+import { BigNumber, constants } from "ethers"
+import { formatEther } from "ethers/lib/utils"
+
+
+export const calculateETHValueUSD = (ethValueBN: BigNumber, ethUSDBN: BigNumber) => parseFloat(
+ formatEther(ethValueBN
+ .mul(ethUSDBN)
+ .div(constants.WeiPerEther))
+)
+
+export const calculateFEIValueUSD = (
+ feiAmountBN: BigNumber,
+ feiPriceBN: BigNumber,
+ ethUSDBN: BigNumber) => parseFloat(
+ formatEther(
+ feiAmountBN
+ .mul(feiPriceBN)
+ .mul(ethUSDBN)
+ .div(constants.WeiPerEther)
+ .div(constants.WeiPerEther)
+ )
+ )
+
+
+
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index c5322518..706e6e93 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -61,7 +61,7 @@ function MyApp({ Component, pageProps }: AppProps) {
-
+
diff --git a/src/pages/turbo/index.tsx b/src/pages/turbo/index.tsx
new file mode 100644
index 00000000..3aa70a8b
--- /dev/null
+++ b/src/pages/turbo/index.tsx
@@ -0,0 +1,17 @@
+import { NextPage } from "next";
+import TurboIndexPage from "components/pages/Turbo/TurboIndexPage";
+import { serverSideTranslations } from "next-i18next/serverSideTranslations";
+
+export async function getStaticProps({ locale }: { locale: string }) {
+ return {
+ props: {
+ ...(await serverSideTranslations(locale)),
+ },
+ };
+}
+
+const Page: NextPage = () => {
+ return ;
+};
+
+export default Page;
diff --git a/src/pages/turbo/protocols/index.tsx b/src/pages/turbo/protocols/index.tsx
new file mode 100644
index 00000000..54040cc0
--- /dev/null
+++ b/src/pages/turbo/protocols/index.tsx
@@ -0,0 +1,185 @@
+import { useAllSafes } from "hooks/turbo/useAllSafes";
+import { NextPage } from "next";
+import SafeCard from 'components/pages/Turbo/TurboIndexPage/SafeCard'
+import { useSafeInfo } from "hooks/turbo/useSafeInfo";
+import { Avatar, Box, Flex, HStack, Image, SimpleGrid, Spinner, VStack } from "@chakra-ui/react";
+import { Heading, HoverableCard, Link, Statistic, Text, TokenIcon } from "rari-components";
+import { motion } from "framer-motion";
+import TurboLayout from 'components/pages/Turbo/TurboLayout';
+import { commify, formatEther } from "ethers/lib/utils";
+import { useTrustedStrategies } from "hooks/turbo/useTrustedStrategies";
+import { StrategyInfosMap, useERC4626StrategiesDataAsMap } from "hooks/turbo/useStrategyInfo";
+import { useAllUserSafes } from "hooks/turbo/useUserSafes";
+import useAggregateSafeData from "hooks/turbo/useAggregateSafeData";
+import { smallUsdFormatter } from "utils/bigUtils";
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+
+type AuthorizedUser = {
+ address: string,
+ protocolName: string,
+ logo: string
+}
+
+const authorizedUsers: AuthorizedUser[] = [
+ {
+ address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
+ protocolName: "Olympus DAO",
+ logo: "0x64aa3364F17a4D01c6f1751Fd97C2BD3D7e7f1D5"
+ },
+ {
+ address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
+ protocolName: "Balancer DAO",
+ logo: "0xba100000625a3754423978a60c9317c58a424e3d"
+ }
+]
+
+const Page: NextPage = () => {
+ return (
+
+ Protocols using Turbo
+
+
+ )
+};
+
+const ProtocolGrid = () => {
+ const allStrategies = useTrustedStrategies();
+ const getERC4626StrategyData = useERC4626StrategiesDataAsMap(allStrategies);
+
+ return (
+
+ {authorizedUsers.map((user: AuthorizedUser) => {
+ return (
+
+ )})
+ }
+
+ )
+}
+
+
+const ProtocolCard = ({
+ user,
+ getERC4626StrategyData,
+} : {
+ user: AuthorizedUser,
+ getERC4626StrategyData: StrategyInfosMap,
+}) => {
+ const safes = useAllUserSafes(user.address)
+
+
+ return (
+
+
+
+ {(hovered) => (
+
+
+
+
+ {user.protocolName}
+
+
+ { safes ? : }
+
+ )}
+
+
+
+ )
+}
+
+const AggregateSafeStatis = ({
+ safes,
+ getERC4626StrategyData
+}: {
+ safes: SafeInfo[],
+ getERC4626StrategyData: StrategyInfosMap
+}) => {
+ const { totalBoosted, totalClaimableUSD, netAPY } = useAggregateSafeData(
+ safes,
+ getERC4626StrategyData
+ );
+
+ console.log({safes})
+ return (
+
+
+ {commify(parseFloat(formatEther(totalBoosted)).toFixed(2)) + " FEI"}
+
+ }
+ />
+
+ {smallUsdFormatter(totalClaimableUSD)}
+
+ }
+ />
+ setApyIncreasing(!apyIncreasing)}
+ >
+
+ {netAPY.toFixed(2)}%
+
+ {/* {apyIncreasing ? (
+
+ ) : (
+
+ )} */}
+
+ }
+ />
+
+ )
+}
+
+
+{/*
+
+ {safes.map((safe: string) => {
+ if (safe === EMPTY_ADDRESS) return
+ return
+ })}
+
+ */}
+const SafeWrapper = ({safeAddress}: {safeAddress: string}) => {
+ const safeWithInfo = useSafeInfo(safeAddress)
+
+ if (!safeWithInfo) return
+
+ return (
+
+ )
+}
+export default Page
\ No newline at end of file
diff --git a/src/pages/turbo/safe/[id].tsx b/src/pages/turbo/safe/[id].tsx
new file mode 100644
index 00000000..2043483e
--- /dev/null
+++ b/src/pages/turbo/safe/[id].tsx
@@ -0,0 +1,24 @@
+import { NextPage } from "next";
+import TurboSafePage from "components/pages/Turbo/TurboSafePage/TurboSafePage";
+import { serverSideTranslations } from "next-i18next/serverSideTranslations";
+
+export async function getStaticProps({ locale }: { locale: string }) {
+ return {
+ props: {
+ ...(await serverSideTranslations(locale)),
+ },
+ };
+}
+
+export const getStaticPaths = async () => {
+ return {
+ paths: [],
+ fallback: "blocking",
+ };
+};
+
+const Page: NextPage = () => {
+ return ;
+};
+
+export default Page;
diff --git a/src/pages/turbo/safes/index.tsx b/src/pages/turbo/safes/index.tsx
new file mode 100644
index 00000000..723b337e
--- /dev/null
+++ b/src/pages/turbo/safes/index.tsx
@@ -0,0 +1,47 @@
+import { SimpleGrid, Spinner } from "@chakra-ui/react";
+import SafeCard from "components/pages/Turbo/TurboIndexPage/SafeCard";
+import TurboLayout from "components/pages/Turbo/TurboLayout";
+import { useAllSafes } from "hooks/turbo/useAllSafes";
+import { useSafeInfo, useSafesInfo } from "hooks/turbo/useSafeInfo";
+import { SafeInfo } from "lib/turbo/fetchers/safes/getSafeInfo";
+import { EMPTY_ADDRESS } from "lib/turbo/utils/constants";
+import { NextPage } from "next";
+import { Heading } from "rari-components";
+
+const Page: NextPage = () => {
+ return (
+
+ All safes
+
+
+ )
+};
+
+export default Page
+
+
+const SafesGrid = () => {
+ const safes: string[] = useAllSafes()
+
+ if (!safes) return
+ return (
+ <>
+
+ {safes.map((safe: string) => {
+ if (safe === EMPTY_ADDRESS) return null
+ return
+ })}
+
+ >
+ )
+}
+
+const SafeInfoFetcher = ({safe}: {safe: string}) => {
+ const safeInfo = useSafeInfo(safe)
+
+ if (!safeInfo) return
+
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/utils/apyUtils.ts b/src/utils/apyUtils.ts
index 584dbf17..486efcff 100644
--- a/src/utils/apyUtils.ts
+++ b/src/utils/apyUtils.ts
@@ -1,11 +1,13 @@
import { toInt } from "./ethersUtils";
export const convertMantissaToAPY = (mantissa: any, dayRange: number = 35) => {
+ if (!mantissa) return 0
const parsedMantissa = toInt(mantissa)
return (Math.pow((parsedMantissa / 1e18) * 6500 + 1, dayRange) - 1) * 100;
};
export const convertMantissaToAPR = (mantissa: any) => {
+ if (!mantissa) return 0
const parsedMantissa = toInt(mantissa)
return (parsedMantissa * 2372500) / 1e16;
};
diff --git a/src/utils/bigUtils.ts b/src/utils/bigUtils.ts
index 6d2e5cd5..ec097245 100644
--- a/src/utils/bigUtils.ts
+++ b/src/utils/bigUtils.ts
@@ -27,7 +27,8 @@ export function stringUsdFormatter(num: string) {
return formatter.format(parseFloat(num));
}
-export function smallUsdFormatter(num: number | string) {
+export function smallUsdFormatter(_num: number | string | undefined, dollars: boolean = true) {
+ let num = _num ?? 0
if (typeof num === typeof "") {
return smallFormatter.format(parseFloat(num as string));
}
@@ -38,17 +39,20 @@ export function usdFormatter(num: number) {
return formatter.format(num);
}
-export function shortUsdFormatter(num: number | string) {
+export function shortUsdFormatter(_num: number | string | undefined, dollars: boolean = true) {
+ let num = _num ?? 0
+ let str = dollars ? "$" : ""
if (typeof num === typeof "") {
- return "$" + shortFormatter.format(parseFloat(num as string));
+ return str + shortFormatter.format(parseFloat(num as string));
}
- return "$" + shortFormatter.format(num as number);
+ return str + shortFormatter.format(num as number);
}
-export const abbreviateAmount = (amount: number) => {
+export const abbreviateAmount = (_amount: number | string | undefined, dollars: boolean = true) => {
+ const amount = _amount ? Number(_amount) : 0
return Math.abs(amount) > 100000
- ? shortUsdFormatter(amount)
- : smallUsdFormatter(amount);
+ ? shortUsdFormatter(amount, dollars)
+ : smallUsdFormatter(amount, dollars);
};
export const bnToString = (bn: BigNumber): string => bn.toString();
diff --git a/src/utils/createComptroller.ts b/src/utils/createComptroller.ts
index 9b4b5131..18383481 100644
--- a/src/utils/createComptroller.ts
+++ b/src/utils/createComptroller.ts
@@ -94,3 +94,15 @@ export const createMasterPriceOracle = (fuse: Fuse, special?: boolean) => {
);
return masterPriceOracle;
};
+
+
+export const createFusePoolDirectory = (fuse: Fuse, special?: boolean) => {
+ const provider = special ? fuse.provider : fuse.provider.getSigner()
+ const masterPriceOracle = new Contract(
+ fuse.addresses.PUBLIC_PRICE_ORACLE_CONTRACT_ADDRESSES.MasterPriceOracle,
+ fuse.oracleContracts["MasterPriceOracle"].abi,
+ provider
+ );
+ return masterPriceOracle;
+};
+
diff --git a/src/utils/erc20Utils.ts b/src/utils/erc20Utils.ts
new file mode 100644
index 00000000..cc813fd9
--- /dev/null
+++ b/src/utils/erc20Utils.ts
@@ -0,0 +1,112 @@
+import { constants, providers } from "ethers";
+import { parseEther } from "ethers/lib/utils";
+import { Interface } from "@ethersproject/abi";
+import { BigNumber } from "@ethersproject/bignumber";
+import { Contract } from "@ethersproject/contracts";
+import { MAX_APPROVAL_AMOUNT } from "./tokenUtils";
+
+export async function checkAllowance(
+ signer: any,
+ userAddress: string,
+ spender: string,
+ underlyingAddress: string,
+ amount?: BigNumber
+) {
+ if (isAssetETH(underlyingAddress)) return true;
+ const erc20Interface = new Interface([
+ "function allowance(address owner, address spender) public view returns (uint256 remaining)",
+ ]);
+
+ const erc20Contract = new Contract(underlyingAddress, erc20Interface, signer);
+ const allowance = await erc20Contract.callStatic.allowance(
+ userAddress,
+ spender
+ );
+
+ console.log({ allowance, amount }, allowance.gte(amount))
+
+ const hasApproval = allowance.gte(amount);
+
+ return hasApproval;
+}
+
+export async function balanceOf(
+ userAddress: string | undefined,
+ underlyingAddress: string | undefined,
+ signer: any
+): Promise {
+ if (!userAddress || !underlyingAddress) return constants.Zero;
+
+ if (isAssetETH(underlyingAddress)) return parseEther("0");
+ const erc20Interface = new Interface([
+ "function balanceOf(address _owner) public view returns (uint256 balance)",
+ ]);
+
+ const erc20Contract = new Contract(underlyingAddress, erc20Interface, signer);
+
+ const balance: BigNumber = await erc20Contract.callStatic.balanceOf(
+ userAddress
+ );
+
+ return balance;
+}
+
+export async function approve(
+ signer: providers.JsonRpcSigner | providers.Web3Provider | any,
+ spender: string,
+ underlyingAddress: string,
+ amount: BigNumber
+) {
+ if (isAssetETH(underlyingAddress)) return;
+
+ const erc20Interface = new Interface([
+ "function allowance(address owner, address spender) public view returns (uint256 remaining)",
+ "function approve(address spender, uint256 value) public returns (bool success)",
+ ]);
+
+ console.log({ spender, amount })
+ const erc20Contract = new Contract(underlyingAddress, erc20Interface, signer);
+
+ return await erc20Contract.approve(spender, amount);
+}
+
+const isAssetETH = (address: string) =>
+ address === "0x0000000000000000000000000000000000000000";
+
+/**
+ * @param provider - An initiated ethers provider.
+ * @param userAddress - Address of user to check allowance for.
+ * @param spender - Address/User to give approval to.
+ * @param underlyingAddress - The token to approve.
+ * @param amount - Amount user is supplying.
+ */
+export async function checkAllowanceAndApprove(
+ signer: providers.JsonRpcSigner | providers.Web3Provider | any,
+ userAddress: string,
+ spender: string,
+ underlyingAddress: string,
+ amount?: BigNumber
+) {
+ if (process.env.NEXT_PUBLIC_USE_MOCKS === "true") {
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ return;
+ }
+
+ console.log({ signer, spender, underlyingAddress, amount, MAX_APPROVAL_AMOUNT })
+
+ const hasApprovedEnough = await checkAllowance(
+ signer,
+ userAddress,
+ spender,
+ underlyingAddress,
+ amount
+ );
+
+ // alert("HAS APPROVED ENOUGH: " + String(hasApprovedEnough))
+
+ if (!hasApprovedEnough) {
+ console.log({ signer, spender, underlyingAddress, amount })
+ const tx = await approve(signer, spender, underlyingAddress, amount ?? MAX_APPROVAL_AMOUNT);
+ return tx;
+ }
+}
diff --git a/src/utils/ethersUtils.ts b/src/utils/ethersUtils.ts
index 035f474b..2e90e37d 100644
--- a/src/utils/ethersUtils.ts
+++ b/src/utils/ethersUtils.ts
@@ -9,7 +9,7 @@ export const toBN = (input: BigNumberish) => {
else return BigNumber.from(input);
};
-export const toInt = (input: BigNumber) => {
+export const toInt = (input: BigNumber | undefined) => {
if (!input) return 0
return parseInt(input.toString())
}
diff --git a/src/utils/homepage.ts b/src/utils/homepage.ts
index 67ab1920..6f2d03ef 100644
--- a/src/utils/homepage.ts
+++ b/src/utils/homepage.ts
@@ -23,6 +23,8 @@ export const getOpportunityLink = (
return "https://xpollinate.io/";
case HomepageOpportunityType.PegExchanger:
return "https://jointhetribe.xyz/";
+ case HomepageOpportunityType.Turbo:
+ return "/turbo";
}
return "/";
};
diff --git a/src/utils/multicall.ts b/src/utils/multicall.ts
index 8599c897..9904f27b 100644
--- a/src/utils/multicall.ts
+++ b/src/utils/multicall.ts
@@ -4,7 +4,7 @@ import { JsonRpcProvider, Web3Provider } from "@ethersproject/providers";
import { Interface } from "ethers/lib/utils";
import { filterOnlyObjectProperties } from "./fetchFusePoolData";
-type EncodedCall = [string, any]
+export type EncodedCall = [string, any]
export const createMultiCall = (provider: JsonRpcProvider | Web3Provider,) => {
const multicallContract = new Contract(
@@ -33,6 +33,11 @@ export const sendWithMultiCall = async (
};
+type MultiCallReturn = {
+ returnData: any[],
+ blockNum: BigNumber
+}
+
export const callStaticWithMultiCall = async (
provider: JsonRpcProvider | Web3Provider,
encodedCalls: EncodedCall[],
@@ -42,8 +47,9 @@ export const callStaticWithMultiCall = async (
let options: any = {}
if (!!address) options.address = address
- const returnDatas = await multicall.callStatic.aggregate(encodedCalls, options)
-
+ const returnDatas: MultiCallReturn = filterOnlyObjectProperties(
+ await multicall.callStatic.aggregate(encodedCalls, options)
+ )
return returnDatas;
};
diff --git a/src/utils/multicall/multicall.ts b/src/utils/multicall/multicall.ts
deleted file mode 100644
index b3013375..00000000
--- a/src/utils/multicall/multicall.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import { JsonRpcProvider, Web3Provider } from "@ethersproject/providers";
-import { BigNumber, Contract } from "ethers";
-import { Interface } from "ethers/lib/utils";
-import { filterOnlyObjectProperties } from "utils/fetchFusePoolData";
-
-export const createMultiCall = (
- ethersProvider: JsonRpcProvider | Web3Provider
-) => new Contract(MULTICALL_ADDRESS, MULTICALL_ABI, ethersProvider);
-
-export const sendWithMultiCall = async (
- provider: JsonRpcProvider | Web3Provider,
- encodedCalls: any,
- address: string
-) => {
- const multicall = createMultiCall(provider);
-
- const returnDatas = await multicall.methods
- .aggregate(encodedCalls)
- .send({ from: address });
-
- return returnDatas;
-};
-
-export const callInterfaceWithMulticall = async (
- provider: JsonRpcProvider | Web3Provider,
- iface: Interface,
- contractAddress: string,
- functionNames: string[],
- params: any[][]
-) => {
- const encodedCalls = functionNames.map((funcName, i) =>
- encodeCall(iface, contractAddress, funcName, params[i])
- );
-
- let result: { blockNum: BigNumber; returnData: string[] } =
- filterOnlyObjectProperties(
- await callStaticWithMultiCall(provider, encodedCalls)
- );
- const { returnData } = result;
-
- const decodedCalls = functionNames.map((funcName, i) =>
- decodeCall(iface, funcName, returnData[i])
- );
-
- return decodedCalls;
-};
-
-
-type EncodedCall = [string, any];
-
-export const callStaticWithMultiCall = async (
- provider: JsonRpcProvider | Web3Provider,
- encodedCalls: EncodedCall[],
- address?: string
-) => {
- const multicall = createMultiCall(provider);
- let options: any = {};
- if (!!address) options.address = address;
-
- const returnDatas = await multicall.callStatic.aggregate(
- encodedCalls,
- options
- );
-
- return returnDatas;
-};
-
-export const encodeCall = (
- iface: Interface,
- contractAddress: string,
- functionName: string,
- params: any[]
-): EncodedCall => [
- contractAddress,
- iface.encodeFunctionData(functionName, [...params]),
- ];
-
-export const decodeCall = (
- iface: Interface,
- functionName: string,
- txResult: any
-): any => iface.decodeFunctionResult(functionName, txResult);
-
-
-const MULTICALL_ADDRESS = "0xeefba1e63905ef1d7acba5a8513c70307c1ce441";
-
-const MULTICALL_ABI = [
- {
- constant: true,
- inputs: [],
- name: "getCurrentBlockTimestamp",
- outputs: [{ name: "timestamp", type: "uint256" }],
- payable: false,
- stateMutability: "view",
- type: "function",
- },
- {
- constant: false,
- inputs: [
- {
- components: [
- { name: "target", type: "address" },
- { name: "callData", type: "bytes" },
- ],
- name: "calls",
- type: "tuple[]",
- },
- ],
- name: "aggregate",
- outputs: [
- { name: "blockNumber", type: "uint256" },
- { name: "returnData", type: "bytes[]" },
- ],
- payable: false,
- stateMutability: "nonpayable",
- type: "function",
- },
- {
- constant: true,
- inputs: [],
- name: "getLastBlockHash",
- outputs: [{ name: "blockHash", type: "bytes32" }],
- payable: false,
- stateMutability: "view",
- type: "function",
- },
- {
- constant: true,
- inputs: [{ name: "addr", type: "address" }],
- name: "getEthBalance",
- outputs: [{ name: "balance", type: "uint256" }],
- payable: false,
- stateMutability: "view",
- type: "function",
- },
- {
- constant: true,
- inputs: [],
- name: "getCurrentBlockDifficulty",
- outputs: [{ name: "difficulty", type: "uint256" }],
- payable: false,
- stateMutability: "view",
- type: "function",
- },
- {
- constant: true,
- inputs: [],
- name: "getCurrentBlockGasLimit",
- outputs: [{ name: "gaslimit", type: "uint256" }],
- payable: false,
- stateMutability: "view",
- type: "function",
- },
- {
- constant: true,
- inputs: [],
- name: "getCurrentBlockCoinbase",
- outputs: [{ name: "coinbase", type: "address" }],
- payable: false,
- stateMutability: "view",
- type: "function",
- },
- {
- constant: true,
- inputs: [{ name: "blockNumber", type: "uint256" }],
- name: "getBlockHash",
- outputs: [{ name: "blockHash", type: "bytes32" }],
- payable: false,
- stateMutability: "view",
- type: "function",
- },
-];
diff --git a/src/utils/tokenUtils.ts b/src/utils/tokenUtils.ts
index a3ed34ff..df791d65 100644
--- a/src/utils/tokenUtils.ts
+++ b/src/utils/tokenUtils.ts
@@ -9,7 +9,7 @@ import { Fuse } from "../esm/index"
import { ETH_TOKEN_DATA, TokenData } from "hooks/useTokenData";
// Ethers
-import { Contract, BigNumber } from 'ethers'
+import { Contract, BigNumber, constants } from 'ethers'
import Tokens from "../static/compiled/tokens.json";
@@ -114,10 +114,7 @@ export const checkHasApprovedEnough = async ({
.gte(approvedForAmount);
};
-export const MAX_APPROVAL_AMOUNT = BigNumber.from(2)
- .pow(256)
- .sub(1); // big fucking #
-
+export const MAX_APPROVAL_AMOUNT = constants.MaxUint256
const ETH_AND_WETH = [
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
diff --git a/src/utils/web3Providers.ts b/src/utils/web3Providers.ts
index 0e6294e8..ed046db0 100644
--- a/src/utils/web3Providers.ts
+++ b/src/utils/web3Providers.ts
@@ -18,6 +18,7 @@ export function chooseBestWeb3Provider(
*/
vaults?: boolean
): JsonRpcProvider | Web3Provider {
+
let providerURL = getChainMetadata(chainId).rpcUrl ?? "";
console.log({ chainId, providerURL });
// return new JsonRpcProvider(providerURL);
@@ -27,7 +28,7 @@ export function chooseBestWeb3Provider(
return new JsonRpcProvider(providerURL);
}
- if (window.web3) {
+ if (window.web3) {
return new Web3Provider(window.web3.currentProvider);
} else {
return new JsonRpcProvider(providerURL);
@@ -38,7 +39,7 @@ export const initFuseWithProviders = (
provider = chooseBestWeb3Provider(),
chainId: ChainID = 1
): Fuse => {
- const fuse = new Fuse(provider, chainId === 31337 ? 1 : chainId );
+ const fuse = new Fuse(provider, chainId === 31337 ? 1 : chainId);
let lensProvider = getChainMetadata(chainId).rpcUrl ?? "";
// @ts-ignore We have to do this to avoid Infura ratelimits on our large calls.
fuse.contracts.FusePoolLens = fuse.contracts.FusePoolLens.connect(