diff --git a/core/.solhint.json b/core/.solhint.json
index b53dba35b..f54af0b9a 100644
--- a/core/.solhint.json
+++ b/core/.solhint.json
@@ -1,7 +1,4 @@
{
"extends": "thesis",
- "plugins": [],
- "rules": {
- "func-visibility": ["off", { "ignoreConstructors": true }]
- }
+ "plugins": []
}
diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol
index fda7ab237..bb8f357e9 100644
--- a/core/contracts/Acre.sol
+++ b/core/contracts/Acre.sol
@@ -3,14 +3,21 @@ pragma solidity ^0.8.21;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
+/// @title Acre
+/// @notice Implementation of the ERR-4626 tokenized vault standard. ERC-4626 is
+/// a standard to optimize and unify the technical parameters of
+/// yield-bearing vaults. This contract allows the minting and burning
+/// of shares, represented as standard ERC20 token, in exchange for tBTC
+/// tokens.
+/// @dev ERC-4626 standard extends the ERC-20 token.
contract Acre is ERC4626 {
event Staked(bytes32 indexed referral, uint256 assets, uint256 shares);
constructor(
- IERC20 _token
- ) ERC4626(_token) ERC20("Acre Staked Bitcoin", "stBTC") {}
+ IERC20 tbtc
+ ) ERC4626(tbtc) ERC20("Acre Staked Bitcoin", "stBTC") {}
- /// @notice Stakes a given amount of underlying token and mints shares to a
+ /// @notice Stakes a given amount of tBTC token and mints shares to a
/// receiver.
/// @dev This function calls `deposit` function from `ERC4626` contract.
/// @param assets Approved amount for the transfer and stake.
@@ -21,8 +28,9 @@ contract Acre is ERC4626 {
uint256 assets,
address receiver,
bytes32 referral
- ) public returns (uint256 shares) {
- shares = deposit(assets, receiver);
+ ) public returns (uint256) {
+ // TODO: revisit the type of referral.
+ uint256 shares = deposit(assets, receiver);
emit Staked(referral, assets, shares);
diff --git a/core/contracts/test/TestToken.sol b/core/contracts/test/TestToken.sol
index 5a83eabd8..44e5e14dc 100644
--- a/core/contracts/test/TestToken.sol
+++ b/core/contracts/test/TestToken.sol
@@ -2,30 +2,11 @@
pragma solidity ^0.8.21;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
-import "@thesis/solidity-contracts/contracts/token/IApproveAndCall.sol";
-import "@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol";
-contract TestToken is ERC20, IApproveAndCall {
+contract TestERC20 is ERC20 {
constructor() ERC20("Test Token", "TEST") {}
function mint(address account, uint256 value) external {
_mint(account, value);
}
-
- function approveAndCall(
- address spender,
- uint256 amount,
- bytes memory extraData
- ) external returns (bool) {
- if (approve(spender, amount)) {
- IReceiveApproval(spender).receiveApproval(
- msg.sender,
- amount,
- address(this),
- extraData
- );
- return true;
- }
- return false;
- }
}
diff --git a/core/package.json b/core/package.json
index 783907808..603cb8643 100644
--- a/core/package.json
+++ b/core/package.json
@@ -57,7 +57,6 @@
"typescript": "^5.3.2"
},
"dependencies": {
- "@openzeppelin/contracts": "^5.0.0",
- "@thesis/solidity-contracts": "github:thesis/solidity-contracts#c315b9d"
+ "@openzeppelin/contracts": "^5.0.0"
}
}
diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts
index 1a74af049..369f80704 100644
--- a/core/test/Acre.test.ts
+++ b/core/test/Acre.test.ts
@@ -6,105 +6,198 @@ import {
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"
import { ethers } from "hardhat"
import { expect } from "chai"
-import { WeiPerEther } from "ethers"
-import type { TestToken, Acre } from "../typechain"
+import { ContractTransactionResponse, ZeroAddress } from "ethers"
+import type { TestERC20, Acre } from "../typechain"
+import { to1e18 } from "./utils"
async function acreFixture() {
- const [_, staker] = await ethers.getSigners()
- const Token = await ethers.getContractFactory("TestToken")
+ const [staker, staker2] = await ethers.getSigners()
+ const Token = await ethers.getContractFactory("TestERC20")
const tbtc = await Token.deploy()
- const amountToMint = WeiPerEther * 100000n
+ const amountToMint = to1e18(100000)
tbtc.mint(staker, amountToMint)
+ tbtc.mint(staker2, amountToMint)
const Acre = await ethers.getContractFactory("Acre")
const acre = await Acre.deploy(await tbtc.getAddress())
- return { acre, tbtc, staker }
+ return { acre, tbtc, staker, staker2 }
}
describe("Acre", () => {
let acre: Acre
- let tbtc: TestToken
+ let tbtc: TestERC20
let staker: HardhatEthersSigner
+ let staker2: HardhatEthersSigner
before(async () => {
- ;({ acre, tbtc, staker } = await loadFixture(acreFixture))
+ ;({ acre, tbtc, staker, staker2 } = await loadFixture(acreFixture))
})
describe("Staking", () => {
const referral = ethers.encodeBytes32String("referral")
let snapshot: SnapshotRestorer
- context("when staking via Acre contract", () => {
- const amountToStake = 1000n * WeiPerEther
-
+ context("when staking", () => {
beforeEach(async () => {
snapshot = await takeSnapshot()
-
- await tbtc
- .connect(staker)
- .approve(await acre.getAddress(), amountToStake)
})
afterEach(async () => {
await snapshot.restore()
})
- it("should stake tokens and receive shares", async () => {
- const stakerAddress = staker.address
- const balanceOfBeforeStake = await tbtc.balanceOf(stakerAddress)
-
- const tx = await acre
- .connect(staker)
- .stake(amountToStake, stakerAddress, referral)
-
- const stakedTokens = amountToStake
+ context("with a referral", () => {
+ const amountToStake = to1e18(1000)
// In this test case there is only one staker and
// the token vault has not earned anythig yet so received shares are
// equal to staked tokens amount.
- const receivedShares = amountToStake
-
- expect(tx).to.emit(acre, "Deposit").withArgs(
- // Caller.
- stakerAddress,
- // Receiver.
- stakerAddress,
- // Staked tokens.
- stakedTokens,
- // Received shares.
- receivedShares,
- )
-
- expect(tx)
- .to.emit(acre, "Staked")
- .withArgs(referral, stakedTokens, receivedShares)
-
- expect(await acre.balanceOf(stakerAddress)).to.be.eq(amountToStake)
- expect(await tbtc.balanceOf(stakerAddress)).to.be.eq(
- balanceOfBeforeStake - amountToStake,
- )
+ const expectedReceivedShares = amountToStake
+
+ let tx: ContractTransactionResponse
+
+ beforeEach(async () => {
+ await tbtc
+ .connect(staker)
+ .approve(await acre.getAddress(), amountToStake)
+
+ tx = await acre
+ .connect(staker)
+ .stake(amountToStake, staker.address, referral)
+ })
+
+ it("should emit Deposit event", () => {
+ expect(tx).to.emit(acre, "Deposit").withArgs(
+ // Caller.
+ staker.address,
+ // Receiver.
+ staker.address,
+ // Staked tokens.
+ amountToStake,
+ // Received shares.
+ expectedReceivedShares,
+ )
+ })
+
+ it("should emit Staked event", () => {
+ expect(tx)
+ .to.emit(acre, "Staked")
+ .withArgs(referral, amountToStake, expectedReceivedShares)
+ })
+
+ it("should mint stBTC tokens", async () => {
+ await expect(tx).to.changeTokenBalances(
+ acre,
+ [staker.address],
+ [amountToStake],
+ )
+ })
+
+ it("should transfer tBTC tokens", async () => {
+ await expect(tx).to.changeTokenBalances(
+ tbtc,
+ [staker.address, acre],
+ [-amountToStake, amountToStake],
+ )
+ })
})
- it("should not revert if the referral is zero value", async () => {
+ context("without referral", () => {
const emptyReferral = ethers.encodeBytes32String("")
+ let tx: ContractTransactionResponse
+
+ beforeEach(async () => {
+ await tbtc.connect(staker).approve(await acre.getAddress(), 1)
- await expect(
- acre
+ tx = await acre
.connect(staker)
- .stake(amountToStake, staker.address, emptyReferral),
- ).to.be.not.reverted
+ .stake(1, staker.address, emptyReferral)
+ })
+
+ it("should not revert", async () => {
+ await expect(tx).to.be.not.reverted
+ })
})
- it("should revert if a staker wants to stake more tokens than approved", async () => {
- await expect(
- acre
+ context(
+ "when amount to stake is greater than the approved amount",
+ () => {
+ const amountToStake = to1e18(10)
+ beforeEach(async () => {
+ await tbtc
+ .connect(staker)
+ .approve(await acre.getAddress(), amountToStake)
+ })
+
+ it("should revert", async () => {
+ await expect(
+ acre
+ .connect(staker)
+ .stake(amountToStake + 1n, staker.address, referral),
+ ).to.be.reverted
+ })
+ },
+ )
+
+ context("when amount to stake is 1", () => {
+ const amountToStake = 1
+
+ beforeEach(async () => {
+ await tbtc
.connect(staker)
- .stake(amountToStake + 1n, staker.address, referral),
- ).to.be.reverted
+ .approve(await acre.getAddress(), amountToStake)
+ })
+
+ it("should not revert", async () => {
+ await expect(
+ acre.connect(staker).stake(amountToStake, staker.address, referral),
+ ).to.not.be.reverted
+ })
+ })
+
+ context("when the receiver is zero address", () => {
+ const amountToStake = to1e18(10)
+
+ beforeEach(async () => {
+ await tbtc
+ .connect(staker)
+ .approve(await acre.getAddress(), amountToStake)
+ })
+
+ it("should revert", async () => {
+ await expect(
+ acre.connect(staker).stake(amountToStake, ZeroAddress, referral),
+ ).to.be.revertedWithCustomError(acre, "ERC20InvalidReceiver")
+ })
})
+
+ context(
+ "when a staker approved and staked tokens and wants to stake more but w/o another apporval",
+ () => {
+ const amountToStake = to1e18(10)
+
+ beforeEach(async () => {
+ await tbtc
+ .connect(staker)
+ .approve(await acre.getAddress(), amountToStake)
+
+ await acre
+ .connect(staker)
+ .stake(amountToStake, staker.address, referral)
+ })
+
+ it("should revert", async () => {
+ await expect(
+ acre
+ .connect(staker)
+ .stake(amountToStake, staker.address, referral),
+ ).to.be.revertedWithCustomError(acre, "ERC20InsufficientAllowance")
+ })
+ },
+ )
})
context("when there are two stakers, A and B ", () => {
@@ -119,25 +212,24 @@ describe("Acre", () => {
let afterSimulatingYieldSnapshot: SnapshotRestorer
before(async () => {
- const [staker1, staker2] = await ethers.getSigners()
- const staker1AmountToStake = WeiPerEther * 10000n
- const staker2AmountToStake = WeiPerEther * 5000n
+ const stakerAmountToStake = to1e18(75)
+ const staker2AmountToStake = to1e18(25)
// Infinite approval for staking contract.
await tbtc
- .connect(staker1)
+ .connect(staker)
.approve(await acre.getAddress(), ethers.MaxUint256)
await tbtc
.connect(staker2)
.approve(await acre.getAddress(), ethers.MaxUint256)
// Mint tokens.
- await tbtc.connect(staker1).mint(staker1.address, staker1AmountToStake)
+ await tbtc.connect(staker).mint(staker.address, stakerAmountToStake)
await tbtc.connect(staker2).mint(staker2.address, staker2AmountToStake)
stakerA = {
- signer: staker1,
- address: staker1.address,
- amountToStake: staker1AmountToStake,
+ signer: staker,
+ address: staker.address,
+ amountToStake: stakerAmountToStake,
}
stakerB = {
signer: staker2,
@@ -154,7 +246,7 @@ describe("Acre", () => {
})
context("when staker A stakes tokens", () => {
- it("should stake tokens", async () => {
+ it("should stake tokens correctly", async () => {
await expect(
acre
.connect(stakerA.signer)
@@ -205,13 +297,22 @@ describe("Acre", () => {
let stakerASharesBefore: bigint
let stakerBSharesBefore: bigint
let vaultYield: bigint
+ let expectedTotalAssets: bigint
+ let expectedTotalSupply: bigint
before(async () => {
await afterStakesSnapshot.restore()
stakerASharesBefore = await acre.balanceOf(stakerA.address)
stakerBSharesBefore = await acre.balanceOf(stakerB.address)
- vaultYield = WeiPerEther * 10000n
+ vaultYield = to1e18(100)
+ // Staker A shares = 75
+ // Staker B shares = 25
+ // Total assets = 75(staker A) + 25(staker B) + 100(yield)
+ expectedTotalAssets =
+ stakerA.amountToStake + stakerB.amountToStake + vaultYield
+ // Total shares = 75 + 25 = 100
+ expectedTotalSupply = stakerA.amountToStake + stakerB.amountToStake
// Simulating yield returned from strategies. The vault now contains
// more tokens than deposited which causes the exchange rate to
@@ -242,42 +343,93 @@ describe("Acre", () => {
const shares = await acre.balanceOf(stakerA.address)
const availableAssetsToRedeem = await acre.previewRedeem(shares)
+ // Expected amount w/o rounding: 75 * 200 / 100 = 150
+ // Expected amount w/ support for rounding: 149999999999999999999 in
+ // tBTC token precision.
+ const expectedAssetsToRedeem =
+ (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n)
+
expect(availableAssetsToRedeem).to.be.greaterThan(
stakerA.amountToStake,
)
+ expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem)
})
it("the staker B should be able to redeem more tokens than before", async () => {
const shares = await acre.balanceOf(stakerB.address)
const availableAssetsToRedeem = await acre.previewRedeem(shares)
+ // Expected amount w/o rounding: 25 * 200 / 100 = 50
+ // Expected amount w/ support for rounding: 49999999999999999999 in
+ // tBTC token precision.
+ const expectedAssetsToRedeem =
+ (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n)
+
expect(availableAssetsToRedeem).to.be.greaterThan(
stakerB.amountToStake,
)
+ expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem)
})
})
context("when staker A stakes more tokens", () => {
+ const newAmountToStake = to1e18(20)
let sharesBefore: bigint
let availableToRedeemBefore: bigint
+ let totalAssetsBefore: bigint
+ let totalSupplyBefore: bigint
+ let expectedSharesToMint: bigint
+ let expectedTotalSupply: bigint
+ let expectedTotalAssets: bigint
before(async () => {
+ // Current state:
+ // Total assets = 75(staker A) + 25(staker B) + 100(yield)
+ // Total shares = 75 + 25 = 100
await afterSimulatingYieldSnapshot.restore()
sharesBefore = await acre.balanceOf(stakerA.address)
availableToRedeemBefore = await acre.previewRedeem(sharesBefore)
+ totalAssetsBefore = await acre.totalAssets()
+ totalSupplyBefore = await acre.totalSupply()
+
+ tbtc.mint(stakerA.address, newAmountToStake)
+
+ expectedSharesToMint =
+ (newAmountToStake * (totalSupplyBefore + 1n)) /
+ (totalAssetsBefore + 1n)
- tbtc.mint(stakerA.address, stakerA.amountToStake)
+ await acre.stake(newAmountToStake, stakerA.address, referral)
- await acre.stake(stakerA.amountToStake, stakerA.address, referral)
+ // State after stake:
+ // Total assets = 75(staker A) + 25(staker B) + 100(yield) + 20(staker
+ // A) = 220
+ // Total shares = 75 + 25 + 10 = 110
+ expectedTotalAssets = totalAssetsBefore + newAmountToStake
+ expectedTotalSupply = totalSupplyBefore + expectedSharesToMint
})
it("should receive more shares", async () => {
const shares = await acre.balanceOf(stakerA.address)
- const availableToRedeem = await acre.previewRedeem(shares)
expect(shares).to.be.greaterThan(sharesBefore)
+ expect(shares).to.be.eq(sharesBefore + expectedSharesToMint)
+ })
+
+ it("should be able to redeem more tokens than before", async () => {
+ const shares = await acre.balanceOf(stakerA.address)
+ const availableToRedeem = await acre.previewRedeem(shares)
+
+ // Expected amount w/o rounding: 85 * 220 / 110 = 170
+ // Expected amount w/ support for rounding: 169999999999999999999 in
+ // tBTC token precision.
+ const expectedTotalAssetsAvailableToRedeem =
+ (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n)
+
expect(availableToRedeem).to.be.greaterThan(availableToRedeemBefore)
+ expect(availableToRedeem).to.be.eq(
+ expectedTotalAssetsAvailableToRedeem,
+ )
})
})
})
diff --git a/core/test/utils/index.ts b/core/test/utils/index.ts
new file mode 100644
index 000000000..6654cca0e
--- /dev/null
+++ b/core/test/utils/index.ts
@@ -0,0 +1 @@
+export * from "./number"
diff --git a/core/test/utils/number.ts b/core/test/utils/number.ts
new file mode 100644
index 000000000..9784b499c
--- /dev/null
+++ b/core/test/utils/number.ts
@@ -0,0 +1,10 @@
+export function to1ePrecision(
+ n: string | number | bigint,
+ precision: number,
+): bigint {
+ return BigInt(n) * 10n ** BigInt(precision)
+}
+
+export function to1e18(n: string | number | bigint): bigint {
+ return to1ePrecision(n, 18)
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2d7a91c33..257a3a017 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,9 +23,6 @@ importers:
'@openzeppelin/contracts':
specifier: ^5.0.0
version: 5.0.0
- '@thesis/solidity-contracts':
- specifier: github:thesis/solidity-contracts#c315b9d
- version: github.com/thesis/solidity-contracts/c315b9d
devDependencies:
'@nomicfoundation/hardhat-chai-matchers':
specifier: ^2.0.2
@@ -95,7 +92,7 @@ importers:
version: 4.0.0
solhint-config-thesis:
specifier: github:thesis/solhint-config
- version: github.com/thesis/solhint-config/159587cd6103f21aaa7fbadc17a06717a51b5b42(solhint@4.0.0)
+ version: github.com/thesis/solhint-config/266de12d96d58f01171e20858b855ec80520de8d(solhint@4.0.0)
solidity-coverage:
specifier: ^0.8.5
version: 0.8.5(hardhat@2.19.1)
@@ -172,12 +169,21 @@ importers:
gatsby:
specifier: ^5.12.11
version: 5.12.11(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)
+ gatsby-plugin-manifest:
+ specifier: ^5.12.3
+ version: 5.12.3(gatsby@5.12.11)(graphql@16.8.1)
+ gatsby-plugin-react-helmet:
+ specifier: ^6.12.0
+ version: 6.12.0(gatsby@5.12.11)(react-helmet@6.1.0)
react:
specifier: ^18.2.0
version: 18.2.0
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
+ react-helmet:
+ specifier: ^6.1.0
+ version: 6.1.0(react@18.2.0)
devDependencies:
'@thesis-co/eslint-config':
specifier: ^0.6.1
@@ -191,6 +197,9 @@ importers:
'@types/react-dom':
specifier: ^18.2.17
version: 18.2.17
+ '@types/react-helmet':
+ specifier: ^6.1.9
+ version: 6.1.9
'@typescript-eslint/eslint-plugin':
specifier: ^6.12.0
version: 6.12.0(@typescript-eslint/parser@6.12.0)(eslint@8.54.0)(typescript@5.3.2)
@@ -4416,10 +4425,6 @@ packages:
- supports-color
dev: true
- /@openzeppelin/contracts@4.9.3:
- resolution: {integrity: sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg==}
- dev: false
-
/@openzeppelin/contracts@5.0.0:
resolution: {integrity: sha512-bv2sdS6LKqVVMLI5+zqnNrNU/CA+6z6CmwFXm/MzmOPBRSO5reEJN7z0Gbzvs0/bv/MZZXNklubpwy3v2+azsw==}
dev: false
@@ -5605,6 +5610,12 @@ packages:
'@types/react': 18.2.38
dev: true
+ /@types/react-helmet@6.1.9:
+ resolution: {integrity: sha512-nuOeTefP4yPTWHvjGksCBKb/4hsgJxSX7aSTjTIDFXJIkZ6Wo2Y4/cmE1FO9OlYBrHjKOer/0zLwY7s4qiQBtw==}
+ dependencies:
+ '@types/react': 18.2.38
+ dev: true
+
/@types/react@18.2.38:
resolution: {integrity: sha512-cBBXHzuPtQK6wNthuVMV6IjHAFkdl/FOPFIlkd81/Cd1+IqkHu/A+w4g43kaQQoYHik/ruaQBDL72HyCy1vuMw==}
dependencies:
@@ -9613,6 +9624,22 @@ packages:
'@parcel/transformer-js': 2.8.3(@parcel/core@2.8.3)
'@parcel/transformer-json': 2.8.3(@parcel/core@2.8.3)
+ /gatsby-plugin-manifest@5.12.3(gatsby@5.12.11)(graphql@16.8.1):
+ resolution: {integrity: sha512-qpH0pSIIt7ggO7OnP127eKn6fhD1DKTzg9Aw8vaMCO8MMOQ5qfOn3ZrRCgH6DuaU1admZU18gFKlCKH+QHoGfQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ gatsby: ^5.0.0-next
+ dependencies:
+ '@babel/runtime': 7.23.4
+ gatsby: 5.12.11(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)
+ gatsby-core-utils: 4.12.1
+ gatsby-plugin-utils: 4.12.3(gatsby@5.12.11)(graphql@16.8.1)
+ semver: 7.5.4
+ sharp: 0.32.6
+ transitivePeerDependencies:
+ - graphql
+ dev: false
+
/gatsby-plugin-page-creator@5.12.3(gatsby@5.12.11)(graphql@16.8.1):
resolution: {integrity: sha512-li9jKy70h4vXNxxRrXP2DpgEx05m5E7EDOLCjAWNsm7e9EO1szixXQ0ev6Ie1SBKT6vAHAoIonet6+oFattf9w==}
engines: {node: '>=18.0.0'}
@@ -9647,6 +9674,18 @@ packages:
lodash.uniq: 4.5.0
dev: true
+ /gatsby-plugin-react-helmet@6.12.0(gatsby@5.12.11)(react-helmet@6.1.0):
+ resolution: {integrity: sha512-agcBCT9H8nlpkAU3D1fUeJbjh7IMPjGO/njoa7avIYLGsQ2nyGlHwcrEmS2zBHxYKaxPkztvr47OpCdnuEIvEw==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ gatsby: ^5.0.0-next
+ react-helmet: ^5.1.3 || ^6.0.0
+ dependencies:
+ '@babel/runtime': 7.23.4
+ gatsby: 5.12.11(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)
+ react-helmet: 6.1.0(react@18.2.0)
+ dev: false
+
/gatsby-plugin-typescript@5.12.1(gatsby@5.12.11):
resolution: {integrity: sha512-NIigc9TnhjLam/WAQxvVLKpRgjOXzDDgetOt2F2qtO+1KjMuUgLxHdd613Z0JoSPGpi5ug0KG8U99gh9zge7jA==}
engines: {node: '>=18.0.0'}
@@ -13015,6 +13054,18 @@ packages:
use-sidecar: 1.1.2(@types/react@18.2.38)(react@18.2.0)
dev: false
+ /react-helmet@6.1.0(react@18.2.0):
+ resolution: {integrity: sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==}
+ peerDependencies:
+ react: '>=16.3.0'
+ dependencies:
+ object-assign: 4.1.1
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-fast-compare: 3.2.2
+ react-side-effect: 2.1.2(react@18.2.0)
+ dev: false
+
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@@ -13070,6 +13121,14 @@ packages:
react: 18.2.0
webpack: 5.89.0
+ /react-side-effect@2.1.2(react@18.2.0):
+ resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==}
+ peerDependencies:
+ react: ^16.3.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/react-style-singleton@2.2.1(@types/react@18.2.38)(react@18.2.0):
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
engines: {node: '>=10'}
@@ -15351,9 +15410,9 @@ packages:
prettier: 3.1.0
dev: true
- github.com/thesis/solhint-config/159587cd6103f21aaa7fbadc17a06717a51b5b42(solhint@4.0.0):
- resolution: {tarball: https://codeload.github.com/thesis/solhint-config/tar.gz/159587cd6103f21aaa7fbadc17a06717a51b5b42}
- id: github.com/thesis/solhint-config/159587cd6103f21aaa7fbadc17a06717a51b5b42
+ github.com/thesis/solhint-config/266de12d96d58f01171e20858b855ec80520de8d(solhint@4.0.0):
+ resolution: {tarball: https://codeload.github.com/thesis/solhint-config/tar.gz/266de12d96d58f01171e20858b855ec80520de8d}
+ id: github.com/thesis/solhint-config/266de12d96d58f01171e20858b855ec80520de8d
name: solhint-config-thesis
version: 0.1.0
engines: {node: '>=0.10.0'}
@@ -15362,11 +15421,3 @@ packages:
dependencies:
solhint: 4.0.0
dev: true
-
- github.com/thesis/solidity-contracts/c315b9d:
- resolution: {tarball: https://codeload.github.com/thesis/solidity-contracts/tar.gz/c315b9d}
- name: '@thesis-co/solidity-contracts'
- version: 0.0.1-pre
- dependencies:
- '@openzeppelin/contracts': 4.9.3
- dev: false
diff --git a/website/gatsby-browser.js b/website/gatsby-browser.js
new file mode 100644
index 000000000..e942f19b3
--- /dev/null
+++ b/website/gatsby-browser.js
@@ -0,0 +1 @@
+import "./src/styles/global-styles.css"
diff --git a/website/gatsby-config.ts b/website/gatsby-config.ts
index ecb1bd0a3..01a0d10f1 100644
--- a/website/gatsby-config.ts
+++ b/website/gatsby-config.ts
@@ -2,12 +2,27 @@ import type { GatsbyConfig } from "gatsby"
const config: GatsbyConfig = {
siteMetadata: {
- // TODO: Update the needed data when the final domain name will be ready.
- siteUrl: "https://www.yourdomain.tld",
+ siteUrl: "https://acre.fi/",
title: "Acre",
},
graphqlTypegen: true,
- plugins: ["gatsby-plugin-pnpm"],
+ plugins: [
+ "gatsby-plugin-pnpm",
+ "gatsby-plugin-react-helmet",
+ {
+ resolve: "gatsby-plugin-manifest",
+ options: {
+ name: "Acre",
+ short_name: "Acre",
+ start_url: "/",
+ background_color: "#F3E5C1",
+ theme_color: "#F3E5C1",
+ display: "standalone",
+ icon: "src/images/favicon.png",
+ crossOrigin: "use-credentials",
+ },
+ },
+ ],
}
export default config
diff --git a/website/package.json b/website/package.json
index 531edab47..23f6fdb53 100644
--- a/website/package.json
+++ b/website/package.json
@@ -21,14 +21,18 @@
},
"dependencies": {
"gatsby": "^5.12.11",
+ "gatsby-plugin-manifest": "^5.12.3",
+ "gatsby-plugin-react-helmet": "^6.12.0",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-helmet": "^6.1.0"
},
"devDependencies": {
"@thesis-co/eslint-config": "^0.6.1",
"@types/node": "^20.9.4",
"@types/react": "^18.2.38",
"@types/react-dom": "^18.2.17",
+ "@types/react-helmet": "^6.1.9",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"eslint": "^8.54.0",
diff --git a/website/src/components/SEO/index.tsx b/website/src/components/SEO/index.tsx
new file mode 100644
index 000000000..98065d036
--- /dev/null
+++ b/website/src/components/SEO/index.tsx
@@ -0,0 +1,14 @@
+import React from "react"
+import { Helmet } from "react-helmet"
+
+function SEO() {
+ return (
+
+
+ Acre
+
+
+ )
+}
+
+export default SEO
diff --git a/website/src/images/favicon.png b/website/src/images/favicon.png
new file mode 100644
index 000000000..108f98dc9
Binary files /dev/null and b/website/src/images/favicon.png differ
diff --git a/website/src/images/placeholder-mobile.svg b/website/src/images/placeholder-mobile.svg
new file mode 100644
index 000000000..b9189ab6c
--- /dev/null
+++ b/website/src/images/placeholder-mobile.svg
@@ -0,0 +1,43 @@
+
diff --git a/website/src/images/placeholder.svg b/website/src/images/placeholder.svg
new file mode 100644
index 000000000..04bae5360
--- /dev/null
+++ b/website/src/images/placeholder.svg
@@ -0,0 +1,70 @@
+
diff --git a/website/src/pages/404.tsx b/website/src/pages/404.tsx
index a537ff411..9a959c1dc 100644
--- a/website/src/pages/404.tsx
+++ b/website/src/pages/404.tsx
@@ -1,10 +1,14 @@
import * as React from "react"
+import SEO from "../components/SEO"
function NotFoundPage() {
return (
-
- Page not found
-
+ <>
+
+
+ Page not found
+
+ >
)
}
diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx
index 0003e8d76..6b594ada3 100644
--- a/website/src/pages/index.tsx
+++ b/website/src/pages/index.tsx
@@ -1,10 +1,16 @@
import * as React from "react"
+import SEO from "../components/SEO"
function IndexPage() {
return (
-
- Landing page
-
+ <>
+
+
+
+
+ >
)
}
diff --git a/website/src/styles/global-styles.css b/website/src/styles/global-styles.css
new file mode 100644
index 000000000..bdd150f7e
--- /dev/null
+++ b/website/src/styles/global-styles.css
@@ -0,0 +1,33 @@
+body {
+ width: 100vw;
+ height: 100vh;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+.placeholder {
+ width: 100vw;
+ height: 100vh;
+ padding: 10px;
+ box-sizing: border-box;
+ background-color: #F3E5C1;
+}
+
+.placeholder_svg {
+ width: 100%;
+ height: 100%;
+ background: url('../images/placeholder.svg');
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: cover;
+}
+
+@media (max-width: 854px) {
+ .placeholder_svg {
+ background: url('../images/placeholder-mobile.svg');
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: cover;
+ }
+}