diff --git a/core/.solhint.json b/core/.solhint.json index f54af0b9a..7afe6405c 100644 --- a/core/.solhint.json +++ b/core/.solhint.json @@ -1,4 +1,5 @@ { "extends": "thesis", - "plugins": [] + "plugins": [], + "rules": {} } diff --git a/core/contracts/Dispatcher.sol b/core/contracts/Dispatcher.sol new file mode 100644 index 000000000..309da2ae8 --- /dev/null +++ b/core/contracts/Dispatcher.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +/// @title Dispatcher +/// @notice Dispatcher is a contract that routes TBTC from stBTC (Acre) to +/// a given vault and back. Vaults supply yield strategies with TBTC that +/// generate yield for Bitcoin holders. +contract Dispatcher is Ownable { + error VaultAlreadyAuthorized(); + error VaultUnauthorized(); + + struct VaultInfo { + bool authorized; + } + + /// @notice Authorized Yield Vaults that implement ERC4626 standard. These + /// vaults deposit assets to yield strategies, e.g. Uniswap V3 + /// WBTC/TBTC pool. Vault can be a part of Acre ecosystem or can be + /// implemented externally. As long as it complies with ERC4626 + /// standard and is authorized by the owner it can be plugged into + /// Acre. + address[] public vaults; + mapping(address => VaultInfo) public vaultsInfo; + + event VaultAuthorized(address indexed vault); + event VaultDeauthorized(address indexed vault); + + constructor() Ownable(msg.sender) {} + + /// @notice Adds a vault to the list of authorized vaults. + /// @param vault Address of the vault to add. + function authorizeVault(address vault) external onlyOwner { + if (vaultsInfo[vault].authorized) { + revert VaultAlreadyAuthorized(); + } + + vaults.push(vault); + vaultsInfo[vault].authorized = true; + + emit VaultAuthorized(vault); + } + + /// @notice Removes a vault from the list of authorized vaults. + /// @param vault Address of the vault to remove. + function deauthorizeVault(address vault) external onlyOwner { + if (!vaultsInfo[vault].authorized) { + revert VaultUnauthorized(); + } + + vaultsInfo[vault].authorized = false; + + for (uint256 i = 0; i < vaults.length; i++) { + if (vaults[i] == vault) { + vaults[i] = vaults[vaults.length - 1]; + // slither-disable-next-line costly-loop + vaults.pop(); + break; + } + } + + emit VaultDeauthorized(vault); + } + + function getVaults() external view returns (address[] memory) { + return vaults; + } +} diff --git a/core/deploy/02_deploy_acre_router.ts b/core/deploy/02_deploy_acre_router.ts new file mode 100644 index 000000000..bf99d4d73 --- /dev/null +++ b/core/deploy/02_deploy_acre_router.ts @@ -0,0 +1,22 @@ +import type { HardhatRuntimeEnvironment } from "hardhat/types" +import type { DeployFunction } from "hardhat-deploy/types" + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { getNamedAccounts, deployments } = hre + const { deployer } = await getNamedAccounts() + + await deployments.deploy("Dispatcher", { + from: deployer, + args: [], + log: true, + waitConfirmations: 1, + }) + + // TODO: Add Etherscan verification + // TODO: Add Tenderly verification +} + +export default func + +func.tags = ["Dispatcher"] +func.dependencies = ["Acre"] diff --git a/core/deploy/22_transfer_ownership_acre_router.ts b/core/deploy/22_transfer_ownership_acre_router.ts new file mode 100644 index 000000000..686d378c4 --- /dev/null +++ b/core/deploy/22_transfer_ownership_acre_router.ts @@ -0,0 +1,22 @@ +import type { HardhatRuntimeEnvironment } from "hardhat/types" +import type { DeployFunction } from "hardhat-deploy/types" + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { getNamedAccounts, deployments } = hre + const { deployer, governance } = await getNamedAccounts() + const { log } = deployments + + log(`transferring ownership of Dispatcher contract to ${governance}`) + + await deployments.execute( + "Dispatcher", + { from: deployer, log: true, waitConfirmations: 1 }, + "transferOwnership", + governance, + ) +} + +export default func + +func.tags = ["TransferOwnershipAcreRouter"] +func.dependencies = ["Dispatcher"] diff --git a/core/scripts/fetch_external_artifacts.sh b/core/scripts/fetch_external_artifacts.sh index 5c043568e..c75c98122 100755 --- a/core/scripts/fetch_external_artifacts.sh +++ b/core/scripts/fetch_external_artifacts.sh @@ -1,7 +1,10 @@ #! /bin/bash set -eou pipefail -ROOT_DIR="$(realpath "$(dirname $0)/../")" +ROOT_DIR=$( + cd "$(dirname $0)/../" + pwd -P +) TMP_DIR=${ROOT_DIR}/tmp/external-artifacts EXTERNAL_ARTIFACTS_DIR=${ROOT_DIR}/external diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 74e764ca0..4a5960e3e 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -1,23 +1,23 @@ import { - SnapshotRestorer, - loadFixture, takeSnapshot, + loadFixture, } from "@nomicfoundation/hardhat-toolbox/network-helpers" -import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" -import { ethers } from "hardhat" import { expect } from "chai" import { ContractTransactionResponse, ZeroAddress } from "ethers" -import type { TestERC20, Acre } from "../typechain" + +import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" +import type { SnapshotRestorer } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { deployment } from "./helpers/context" +import { getUnnamedSigner } from "./helpers/signer" + import { to1e18 } from "./utils" -async function acreFixture() { - const [staker1, staker2] = await ethers.getSigners() +import type { Acre, TestERC20 } from "../typechain" - const TestERC20 = await ethers.getContractFactory("TestERC20") - const tbtc = await TestERC20.deploy() +async function fixture() { + const { tbtc, acre } = await deployment() - const Acre = await ethers.getContractFactory("Acre") - const acre = await Acre.deploy(await tbtc.getAddress()) + const [staker1, staker2] = await getUnnamedSigner() const amountToMint = to1e18(100000) tbtc.mint(staker1, amountToMint) @@ -33,7 +33,7 @@ describe("Acre", () => { let staker2: HardhatEthersSigner before(async () => { - ;({ acre, tbtc, staker1, staker2 } = await loadFixture(acreFixture)) + ;({ acre, tbtc, staker1, staker2 } = await loadFixture(fixture)) }) describe("stake", () => { diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts new file mode 100644 index 000000000..1fdecc5d9 --- /dev/null +++ b/core/test/Dispatcher.test.ts @@ -0,0 +1,156 @@ +import { ethers } from "hardhat" +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" +import { expect } from "chai" +import { + SnapshotRestorer, + takeSnapshot, + loadFixture, +} from "@nomicfoundation/hardhat-toolbox/network-helpers" +import type { Dispatcher } from "../typechain" +import { deployment } from "./helpers/context" +import { getNamedSigner, getUnnamedSigner } from "./helpers/signer" + +async function fixture() { + const { dispatcher } = await deployment() + const { governance } = await getNamedSigner() + const [thirdParty] = await getUnnamedSigner() + + return { dispatcher, governance, thirdParty } +} + +describe("Dispatcher", () => { + let snapshot: SnapshotRestorer + + let dispatcher: Dispatcher + let governance: HardhatEthersSigner + let thirdParty: HardhatEthersSigner + let vaultAddress1: string + let vaultAddress2: string + let vaultAddress3: string + let vaultAddress4: string + + before(async () => { + ;({ dispatcher, governance, thirdParty } = await loadFixture(fixture)) + + vaultAddress1 = await ethers.Wallet.createRandom().getAddress() + vaultAddress2 = await ethers.Wallet.createRandom().getAddress() + vaultAddress3 = await ethers.Wallet.createRandom().getAddress() + vaultAddress4 = await ethers.Wallet.createRandom().getAddress() + }) + + beforeEach(async () => { + snapshot = await takeSnapshot() + }) + + afterEach(async () => { + await snapshot.restore() + }) + + describe("authorizeVault", () => { + context("when caller is not a governance account", () => { + it("should revert when adding a vault", async () => { + await expect( + dispatcher.connect(thirdParty).authorizeVault(vaultAddress1), + ).to.be.revertedWithCustomError( + dispatcher, + "OwnableUnauthorizedAccount", + ) + }) + }) + + context("when caller is a governance account", () => { + it("should be able to authorize vaults", async () => { + await dispatcher.connect(governance).authorizeVault(vaultAddress1) + await dispatcher.connect(governance).authorizeVault(vaultAddress2) + await dispatcher.connect(governance).authorizeVault(vaultAddress3) + + expect(await dispatcher.vaults(0)).to.equal(vaultAddress1) + expect(await dispatcher.vaultsInfo(vaultAddress1)).to.be.equal(true) + + expect(await dispatcher.vaults(1)).to.equal(vaultAddress2) + expect(await dispatcher.vaultsInfo(vaultAddress2)).to.be.equal(true) + + expect(await dispatcher.vaults(2)).to.equal(vaultAddress3) + expect(await dispatcher.vaultsInfo(vaultAddress3)).to.be.equal(true) + }) + + it("should not be able to authorize the same vault twice", async () => { + await dispatcher.connect(governance).authorizeVault(vaultAddress1) + await expect( + dispatcher.connect(governance).authorizeVault(vaultAddress1), + ).to.be.revertedWithCustomError(dispatcher, "VaultAlreadyAuthorized") + }) + + it("should emit an event when adding a vault", async () => { + await expect( + dispatcher.connect(governance).authorizeVault(vaultAddress1), + ) + .to.emit(dispatcher, "VaultAuthorized") + .withArgs(vaultAddress1) + }) + }) + }) + + describe("deauthorizeVault", () => { + beforeEach(async () => { + await dispatcher.connect(governance).authorizeVault(vaultAddress1) + await dispatcher.connect(governance).authorizeVault(vaultAddress2) + await dispatcher.connect(governance).authorizeVault(vaultAddress3) + }) + + context("when caller is not a governance account", () => { + it("should revert when adding a vault", async () => { + await expect( + dispatcher.connect(thirdParty).deauthorizeVault(vaultAddress1), + ).to.be.revertedWithCustomError( + dispatcher, + "OwnableUnauthorizedAccount", + ) + }) + }) + + context("when caller is a governance account", () => { + it("should be able to authorize vaults", async () => { + await dispatcher.connect(governance).deauthorizeVault(vaultAddress1) + + // Last vault replaced the first vault in the 'vaults' array + expect(await dispatcher.vaults(0)).to.equal(vaultAddress3) + expect(await dispatcher.vaultsInfo(vaultAddress1)).to.be.equal(false) + expect((await dispatcher.getVaults()).length).to.equal(2) + + await dispatcher.connect(governance).deauthorizeVault(vaultAddress2) + + // Last vault (vaultAddress2) was removed from the 'vaults' array + expect(await dispatcher.vaults(0)).to.equal(vaultAddress3) + expect((await dispatcher.getVaults()).length).to.equal(1) + expect(await dispatcher.vaultsInfo(vaultAddress2)).to.be.equal(false) + + await dispatcher.connect(governance).deauthorizeVault(vaultAddress3) + expect((await dispatcher.getVaults()).length).to.equal(0) + expect(await dispatcher.vaultsInfo(vaultAddress3)).to.be.equal(false) + }) + + it("should be able to deauthorize a vault and authorize it again", async () => { + await dispatcher.connect(governance).deauthorizeVault(vaultAddress1) + expect(await dispatcher.vaultsInfo(vaultAddress1)).to.be.equal(false) + + await dispatcher.connect(governance).authorizeVault(vaultAddress1) + expect(await dispatcher.vaultsInfo(vaultAddress1)).to.be.equal(true) + }) + + it("should not be able to deauthorize a vault that is not authorized", async () => { + await expect( + dispatcher.connect(governance).deauthorizeVault(vaultAddress4), + ).to.be.revertedWithCustomError(dispatcher, "VaultUnauthorized") + }) + + it("should emit an event when removing a vault", async () => { + await expect( + dispatcher.connect(governance).deauthorizeVault(vaultAddress1), + ) + .to.emit(dispatcher, "VaultDeauthorized") + .withArgs(vaultAddress1) + }) + }) + }) +}) diff --git a/core/test/helpers/context.ts b/core/test/helpers/context.ts new file mode 100644 index 000000000..34875e43d --- /dev/null +++ b/core/test/helpers/context.ts @@ -0,0 +1,16 @@ +import { deployments } from "hardhat" + +import { getDeployedContract } from "./contract" + +import type { Acre, Dispatcher, TestERC20 } from "../../typechain" + +// eslint-disable-next-line import/prefer-default-export +export async function deployment() { + await deployments.fixture() + + const tbtc: TestERC20 = await getDeployedContract("TBTC") + const acre: Acre = await getDeployedContract("Acre") + const dispatcher: Dispatcher = await getDeployedContract("Dispatcher") + + return { tbtc, acre, dispatcher } +} diff --git a/core/test/helpers/contract.ts b/core/test/helpers/contract.ts new file mode 100644 index 000000000..88a83812a --- /dev/null +++ b/core/test/helpers/contract.ts @@ -0,0 +1,22 @@ +import { ethers } from "ethers" +import { deployments } from "hardhat" + +import type { BaseContract } from "ethers" +import { getUnnamedSigner } from "./signer" + +/** + * Get instance of a contract from Hardhat Deployments. + * @param deploymentName Name of the contract deployment. + * @returns Deployed Ethers contract instance. + */ +// eslint-disable-next-line import/prefer-default-export +export async function getDeployedContract( + deploymentName: string, +): Promise { + const { address, abi } = await deployments.get(deploymentName) + + // Use default unnamed signer from index 0 to initialize the contract runner. + const [defaultSigner] = await getUnnamedSigner() + + return new ethers.BaseContract(address, abi, defaultSigner) as T +} diff --git a/core/test/helpers/index.ts b/core/test/helpers/index.ts new file mode 100644 index 000000000..27ddcb0b9 --- /dev/null +++ b/core/test/helpers/index.ts @@ -0,0 +1,3 @@ +export * from "./context" +export * from "./contract" +export * from "./signer" diff --git a/core/test/helpers/signer.ts b/core/test/helpers/signer.ts new file mode 100644 index 000000000..0ae57f35e --- /dev/null +++ b/core/test/helpers/signer.ts @@ -0,0 +1,31 @@ +import { ethers, getNamedAccounts, getUnnamedAccounts } from "hardhat" + +import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" + +/** + * Get named Hardhat Ethers Signers. + * @returns Map of named Hardhat Ethers Signers. + */ +export async function getNamedSigner(): Promise<{ + [name: string]: HardhatEthersSigner +}> { + const namedSigners: { [name: string]: HardhatEthersSigner } = {} + + await Promise.all( + Object.entries(await getNamedAccounts()).map(async ([name, address]) => { + namedSigners[name] = await ethers.getSigner(address) + }), + ) + + return namedSigners +} + +/** + * Get unnamed Hardhat Ethers Signers. + * @returns Array of unnamed Hardhat Ethers Signers. + */ +export async function getUnnamedSigner(): Promise { + const accounts = await getUnnamedAccounts() + + return Promise.all(accounts.map(ethers.getSigner)) +} diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index 1566c2e15..9f496615e 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -12,6 +12,7 @@ import Header from "./components/Header" import Overview from "./components/Overview" import Sidebar from "./components/Sidebar" import DocsDrawer from "./components/DocsDrawer" +import GlobalStyles from "./components/GlobalStyles" function DApp() { useDetectThemeMode() @@ -35,6 +36,7 @@ function DAppProviders() { + diff --git a/dapp/src/components/DocsDrawer/index.tsx b/dapp/src/components/DocsDrawer/index.tsx index bf7d48e41..0c948f1d5 100644 --- a/dapp/src/components/DocsDrawer/index.tsx +++ b/dapp/src/components/DocsDrawer/index.tsx @@ -6,8 +6,8 @@ import { DrawerOverlay, } from "@chakra-ui/react" import { useDocsDrawer } from "../../hooks" -import { TextMd } from "../Typography" import { HEADER_HEIGHT } from "../Header" +import { TextMd } from "../shared/Typography" export default function DocsDrawer() { const { isOpen, onClose } = useDocsDrawer() diff --git a/dapp/src/components/GlobalStyles/index.tsx b/dapp/src/components/GlobalStyles/index.tsx new file mode 100644 index 000000000..80b36e6e8 --- /dev/null +++ b/dapp/src/components/GlobalStyles/index.tsx @@ -0,0 +1,47 @@ +import React from "react" +import { Global } from "@emotion/react" + +import SegmentRegular from "../../fonts/Segment-Regular.otf" +import SegmentMedium from "../../fonts/Segment-Medium.otf" +import SegmentSemiBold from "../../fonts/Segment-SemiBold.otf" +import SegmentBold from "../../fonts/Segment-Bold.otf" +import SegmentBlack from "../../fonts/Segment-Black.otf" + +export default function GlobalStyles() { + return ( + + ) +} diff --git a/dapp/src/components/Header/ConnectWallet.tsx b/dapp/src/components/Header/ConnectWallet.tsx index bec7df49e..bdeb051ce 100644 --- a/dapp/src/components/Header/ConnectWallet.tsx +++ b/dapp/src/components/Header/ConnectWallet.tsx @@ -1,7 +1,7 @@ import React from "react" -import { Button, HStack, Icon, Text, Tooltip } from "@chakra-ui/react" +import { Button, HStack, Icon, Text } from "@chakra-ui/react" import { Account } from "@ledgerhq/wallet-api-client" -import { Bitcoin, Ethereum, Info } from "../../static/icons" +import { Bitcoin, Ethereum } from "../../static/icons" import { BITCOIN } from "../../constants" import { useRequestBitcoinAccount, @@ -12,34 +12,22 @@ import { formatSatoshiAmount, truncateAddress } from "../../utils" export type ConnectButtonsProps = { leftIcon: typeof Icon - rightIcon: typeof Icon account: Account | undefined requestAccount: () => Promise } function ConnectButton({ leftIcon, - rightIcon, account, requestAccount, }: ConnectButtonsProps) { - const styles = !account - ? { color: "red.400", borderColor: "red.400" } - : undefined + const colorScheme = !account ? "error" : undefined return ( - + + - - {/* TODO: Handle click actions */} - Show values in {USD.symbol} + + + {/* TODO: Handle click actions */} + + Show values in {USD.symbol} + - - - + + + ) diff --git a/dapp/src/components/StakingModal/index.tsx b/dapp/src/components/StakingModal/index.tsx index 678c48707..9e2324f9a 100644 --- a/dapp/src/components/StakingModal/index.tsx +++ b/dapp/src/components/StakingModal/index.tsx @@ -7,8 +7,6 @@ import ModalBase from "../shared/ModalBase" function StakingModalSteps() { const { activeStep, goNext } = useModalFlowContext() - console.log("activeStep ", activeStep) - switch (activeStep) { case "overview": return diff --git a/dapp/src/components/Typography/index.tsx b/dapp/src/components/Typography/index.tsx deleted file mode 100644 index 1ffbd9ff1..000000000 --- a/dapp/src/components/Typography/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from "react" -import { Text, TextProps } from "@chakra-ui/react" - -export function TextXl(props: TextProps) { - return ( - - ) -} - -export function TextLg(props: TextProps) { - return ( - - ) -} - -export function TextMd(props: TextProps) { - return ( - - ) -} - -export function TextSm(props: TextProps) { - return ( - - ) -} - -export function TextXs(props: TextProps) { - return ( - - ) -} diff --git a/dapp/src/components/shared/Typography/index.tsx b/dapp/src/components/shared/Typography/index.tsx new file mode 100644 index 000000000..1e2080c48 --- /dev/null +++ b/dapp/src/components/shared/Typography/index.tsx @@ -0,0 +1,56 @@ +import React from "react" +import { Heading, HeadingProps, Text, TextProps } from "@chakra-ui/react" + +export function H1(props: HeadingProps) { + return +} + +export function H2(props: HeadingProps) { + return +} + +export function H3(props: HeadingProps) { + return +} + +export function H4(props: HeadingProps) { + return +} + +export function H5(props: HeadingProps) { + return +} + +export function H6(props: HeadingProps) { + return +} + +export function TextXl(props: TextProps) { + return ( + + ) +} + +export function TextLg(props: TextProps) { + return ( + + ) +} + +export function TextMd(props: TextProps) { + return ( + + ) +} + +export function TextSm(props: TextProps) { + return ( + + ) +} + +export function TextXs(props: TextProps) { + return ( + + ) +} diff --git a/dapp/src/fonts/Segment-Black.otf b/dapp/src/fonts/Segment-Black.otf new file mode 100644 index 000000000..11d290a00 Binary files /dev/null and b/dapp/src/fonts/Segment-Black.otf differ diff --git a/dapp/src/fonts/Segment-Bold.otf b/dapp/src/fonts/Segment-Bold.otf new file mode 100644 index 000000000..b1f708029 Binary files /dev/null and b/dapp/src/fonts/Segment-Bold.otf differ diff --git a/dapp/src/fonts/Segment-Medium.otf b/dapp/src/fonts/Segment-Medium.otf new file mode 100644 index 000000000..9df63cbd5 Binary files /dev/null and b/dapp/src/fonts/Segment-Medium.otf differ diff --git a/dapp/src/fonts/Segment-Regular.otf b/dapp/src/fonts/Segment-Regular.otf new file mode 100644 index 000000000..124d0b60e Binary files /dev/null and b/dapp/src/fonts/Segment-Regular.otf differ diff --git a/dapp/src/fonts/Segment-SemiBold.otf b/dapp/src/fonts/Segment-SemiBold.otf new file mode 100644 index 000000000..79a8e6489 Binary files /dev/null and b/dapp/src/fonts/Segment-SemiBold.otf differ diff --git a/dapp/src/static/icons/ArrowUpRight.tsx b/dapp/src/static/icons/ArrowUpRight.tsx new file mode 100644 index 000000000..a61f54b38 --- /dev/null +++ b/dapp/src/static/icons/ArrowUpRight.tsx @@ -0,0 +1,16 @@ +import React from "react" +import { createIcon } from "@chakra-ui/react" + +export const ArrowUpRight = createIcon({ + displayName: "ArrowUpRight", + viewBox: "0 0 16 17", + path: ( + + ), +}) diff --git a/dapp/src/static/icons/ChevronRight.tsx b/dapp/src/static/icons/ChevronRight.tsx deleted file mode 100644 index 8a11116cb..000000000 --- a/dapp/src/static/icons/ChevronRight.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react" -import { createIcon } from "@chakra-ui/react" - -export const ChevronRight = createIcon({ - displayName: "ChevronRight", - viewBox: "0 0 20 20", - path: ( - - ), -}) diff --git a/dapp/src/static/icons/index.ts b/dapp/src/static/icons/index.ts index c625a102e..0752c5839 100644 --- a/dapp/src/static/icons/index.ts +++ b/dapp/src/static/icons/index.ts @@ -1,5 +1,5 @@ export * from "./Info" export * from "./Bitcoin" export * from "./Ethereum" -export * from "./ChevronRight" +export * from "./ArrowUpRight" export * from "./AcreLogo" diff --git a/dapp/src/theme/Button.ts b/dapp/src/theme/Button.ts index cb59ad1b8..1de8af9e9 100644 --- a/dapp/src/theme/Button.ts +++ b/dapp/src/theme/Button.ts @@ -1,26 +1,70 @@ -import { mode } from "@chakra-ui/theme-tools" -import type { StyleFunctionProps } from "@chakra-ui/styled-system" -import { ComponentSingleStyleConfig } from "@chakra-ui/react" +import { + ComponentSingleStyleConfig, + StyleFunctionProps, +} from "@chakra-ui/react" +// TODO: Update the button styles correctly when ready const Button: ComponentSingleStyleConfig = { baseStyle: { - rounded: "none", + size: "md", + borderRadius: "lg", + }, + sizes: { + md: { + fontSize: "sm", + py: "0.5rem", + }, + lg: { + fontSize: "md", + py: "1rem", + }, }, variants: { - solid: (props: StyleFunctionProps) => ({ - // TODO: Update when the dark theme is ready - backgroundColor: mode("brand.400", "brand.400")(props), + solid: { + bg: "brand.400", color: "white", - }), - outline: (props: StyleFunctionProps) => ({ - // TODO: Update when the dark theme is ready - color: mode("grey.700", "grey.700")(props), - borderColor: mode("grey.700", "grey.700")(props), - }), - link: (props: StyleFunctionProps) => ({ - color: mode("grey.700", "grey.700")(props), - textDecoration: "underline", - }), + _hover: { + bg: "brand.500", + }, + _active: { + bg: "brand.400", + }, + }, + outline: { + color: "grey.700", + borderColor: "grey.700", + _hover: { + bg: "opacity.grey.700.5", + }, + _active: { + bg: "transparent", + }, + }, + card: (props: StyleFunctionProps) => { + const defaultStyles = { + borderWidth: "2px", + borderColor: "gold.100", + borderRadius: "xl", + bg: "gold.200", + _hover: { + bg: "opacity.grey.700.5", + }, + _active: { + bg: "transparent", + }, + } + + if (props.colorScheme === "error") { + return { + ...defaultStyles, + color: "red.400", + borderColor: "red.400", + bg: "transparent", + } + } + + return defaultStyles + }, }, } diff --git a/dapp/src/theme/Card.ts b/dapp/src/theme/Card.ts new file mode 100644 index 000000000..c30266abb --- /dev/null +++ b/dapp/src/theme/Card.ts @@ -0,0 +1,15 @@ +import { ComponentSingleStyleConfig } from "@chakra-ui/react" + +const Card: ComponentSingleStyleConfig = { + baseStyle: { + container: { + boxShadow: "none", + borderWidth: "2px", + borderColor: "gold.100", + borderRadius: "xl", + bg: "gold.200", + }, + }, +} + +export default Card diff --git a/dapp/src/theme/Heading.ts b/dapp/src/theme/Heading.ts new file mode 100644 index 000000000..fbdf450d1 --- /dev/null +++ b/dapp/src/theme/Heading.ts @@ -0,0 +1,32 @@ +import { ComponentSingleStyleConfig } from "@chakra-ui/react" + +const Heading: ComponentSingleStyleConfig = { + sizes: { + "7xl": { + fontSize: "4.5rem", + lineHeight: "5.625rem", + }, + "6xl": { + fontSize: "3.75rem", + lineHeight: "4.5rem", + }, + "5xl": { + fontSize: "3rem", + lineHeight: "3.75rem", + }, + "4xl": { + fontSize: "2.25rem", + lineHeight: "2.75rem", + }, + "3xl": { + fontSize: "1.875rem", + lineHeight: "2.375rem", + }, + "2xl": { + fontSize: "1.5rem", + lineHeight: "2rem", + }, + }, +} + +export default Heading diff --git a/dapp/src/theme/Switch.ts b/dapp/src/theme/Switch.ts index 52ca179b0..c62fc6616 100644 --- a/dapp/src/theme/Switch.ts +++ b/dapp/src/theme/Switch.ts @@ -3,13 +3,9 @@ import { ComponentSingleStyleConfig } from "@chakra-ui/react" const Switch: ComponentSingleStyleConfig = { baseStyle: { track: { + bg: "grey.700", _checked: { - _dark: { - bg: "grey.700", - }, - _light: { - bg: "grey.700", - }, + bg: "brand.400", }, }, }, diff --git a/dapp/src/theme/Tooltip.ts b/dapp/src/theme/Tooltip.ts new file mode 100644 index 000000000..e7ee1e349 --- /dev/null +++ b/dapp/src/theme/Tooltip.ts @@ -0,0 +1,25 @@ +import { + ComponentSingleStyleConfig, + Tooltip as ChakraTooltip, + cssVar, +} from "@chakra-ui/react" + +// Currently, there is no possibility to set all tooltips with hasArrow by +// defaultProps. Let's override the defaultProps as follows. +ChakraTooltip.defaultProps = { ...ChakraTooltip.defaultProps, hasArrow: true } + +// To make the arrow the same color as the background, the css variable needs to +// be set correctly. +// More info: +// https://github.com/chakra-ui/chakra-ui/issues/4695#issuecomment-991023319 +const $arrowBg = cssVar("popper-arrow-bg") + +const Tooltip: ComponentSingleStyleConfig = { + baseStyle: { + borderRadius: "md", + bg: "grey.700", + [$arrowBg.variable]: "colors.grey.700", + }, +} + +export default Tooltip diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index b9c0373c9..c0c289344 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -1,19 +1,17 @@ -import { StyleFunctionProps, Tooltip, extendTheme } from "@chakra-ui/react" +import { StyleFunctionProps, extendTheme } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" import Button from "./Button" import Switch from "./Switch" -import { colors, fontSizes, fontWeights, lineHeights, zIndices } from "./utils" +import { colors, fonts, lineHeights, zIndices } from "./utils" import Drawer from "./Drawer" import Modal from "./Modal" - -// Currently, there is no possibility to set all tooltips with hasArrow by defaultProps. -// Let's override the defaultProps as follows. -Tooltip.defaultProps = { ...Tooltip.defaultProps, hasArrow: true } +import Card from "./Card" +import Tooltip from "./Tooltip" +import Heading from "./Heading" const defaultTheme = { colors, - fontSizes, - fontWeights, + fonts, lineHeights, zIndices, styles: { @@ -30,6 +28,9 @@ const defaultTheme = { Switch, Drawer, Modal, + Heading, + Card, + Tooltip, }, } diff --git a/dapp/src/theme/utils/colors.ts b/dapp/src/theme/utils/colors.ts index ed006c76e..f9a53d8f0 100644 --- a/dapp/src/theme/utils/colors.ts +++ b/dapp/src/theme/utils/colors.ts @@ -53,4 +53,11 @@ export const colors = { 600: "#443D3F", 700: "#231F20", // Acre Dirt }, + opacity: { + grey: { + 700: { + 5: "rgba(35, 31, 32, 0.05)", + }, + }, + }, } diff --git a/dapp/src/theme/utils/fonts.ts b/dapp/src/theme/utils/fonts.ts index 18a482760..5fdcc15a6 100644 --- a/dapp/src/theme/utils/fonts.ts +++ b/dapp/src/theme/utils/fonts.ts @@ -1,22 +1,12 @@ -export const fontSizes = { - xs: "0.75rem", - sm: "0.875rem", - md: "1rem", - lg: "1.125rem", - xl: "1.25rem", -} - -export const fontWeights = { - medium: 500, - semibold: 600, - bold: 700, - black: 900, -} - export const lineHeights = { - xs: "1.125rem", - sm: "1.25rem", - md: "1.5rem", - lg: "1.75rem", xl: "1.875rem", + lg: "1.75rem", + md: "1.5rem", + sm: "1.25rem", + xs: "1.125rem", +} + +export const fonts = { + heading: "Segment, sans-serif", + body: "Segment, sans-serif", }