diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index aa3f77354..b4429a856 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -8,8 +8,13 @@ import { ethers } from "hardhat" import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import type { SnapshotRestorer } from "@nomicfoundation/hardhat-toolbox/network-helpers" -import { deployment } from "./helpers/context" -import { getUnnamedSigner, getNamedSigner } from "./helpers/signer" +import { + beforeAfterEachSnapshotWrapper, + beforeAfterSnapshotWrapper, + deployment, + getNamedSigner, + getUnnamedSigner, +} from "./helpers" import { to1e18 } from "./utils" @@ -46,16 +51,8 @@ describe("Acre", () => { }) describe("stake", () => { - let snapshot: SnapshotRestorer - context("when staking as first staker", () => { - beforeEach(async () => { - snapshot = await takeSnapshot() - }) - - afterEach(async () => { - await snapshot.restore() - }) + beforeAfterEachSnapshotWrapper() context("with a referral", () => { const amountToStake = to1e18(1) @@ -258,287 +255,278 @@ describe("Acre", () => { ) }) - context("when there are two stakers", () => { + describe("when staking by multiple stakers", () => { + beforeAfterSnapshotWrapper() + const staker1AmountToStake = to1e18(7) const staker2AmountToStake = to1e18(3) + const earnedYield = to1e18(5) + let afterStakesSnapshot: SnapshotRestorer let afterSimulatingYieldSnapshot: SnapshotRestorer before(async () => { + // Mint tBTC. + await tbtc.mint(staker1.address, staker1AmountToStake) + await tbtc.mint(staker2.address, staker2AmountToStake) + + // Approve tBTC. await tbtc .connect(staker1) .approve(await acre.getAddress(), staker1AmountToStake) await tbtc .connect(staker2) .approve(await acre.getAddress(), staker2AmountToStake) - - // Mint tokens. - await tbtc.connect(staker1).mint(staker1.address, staker1AmountToStake) - await tbtc.connect(staker2).mint(staker2.address, staker2AmountToStake) }) - after(async () => { - await snapshot.restore() - }) + context("when the vault is in initial state", () => { + describe("when two stakers stake", () => { + let stakeTx1: ContractTransactionResponse + let stakeTx2: ContractTransactionResponse + + before(async () => { + stakeTx1 = await acre + .connect(staker1) + .stake(staker1AmountToStake, staker1.address, referral) + + stakeTx2 = await acre + .connect(staker2) + .stake(staker2AmountToStake, staker2.address, referral) - context( - "when the vault is empty and has not yet earned yield from strategies", - () => { - after(async () => { afterStakesSnapshot = await takeSnapshot() }) - context("when staker A stakes tokens", () => { - it("should receive shares equal to a staked amount", async () => { - const tx = await acre - .connect(staker1) - .stake(staker1AmountToStake, staker1.address, referral) - - await expect(tx).to.changeTokenBalances( - acre, - [staker1.address], - [staker1AmountToStake], - ) - }) + it("staker A should receive shares equal to a staked amount", async () => { + await expect(stakeTx1).to.changeTokenBalances( + acre, + [staker1.address], + [staker1AmountToStake], + ) }) - context("when staker B stakes tokens", () => { - it("should receive shares equal to a staked amount", async () => { - const tx = await acre - .connect(staker2) - .stake(staker2AmountToStake, staker2.address, referral) - - await expect(tx).to.changeTokenBalances( - acre, - [staker2.address], - [staker2AmountToStake], - ) - }) + it("staker B should receive shares equal to a staked amount", async () => { + await expect(stakeTx2).to.changeTokenBalances( + acre, + [staker2.address], + [staker2AmountToStake], + ) }) - }, - ) - context("when the vault has stakes", () => { - before(async () => { - await afterStakesSnapshot.restore() - }) - - it("the total assets amount should be equal to all staked tokens", async () => { - const totalAssets = await acre.totalAssets() - - expect(totalAssets).to.eq(staker1AmountToStake + staker2AmountToStake) + it("the total assets amount should be equal to all staked tokens", async () => { + expect(await acre.totalAssets()).to.eq( + staker1AmountToStake + staker2AmountToStake, + ) + }) }) }) - context("when vault earns yield", () => { - let staker1SharesBefore: bigint - let staker2SharesBefore: bigint - let vaultYield: bigint + context("when vault has two stakers", () => { + context("when vault earns yield", () => { + let staker1SharesBefore: bigint + let staker2SharesBefore: bigint - before(async () => { - // Current state: - // Staker A shares = 7 - // Staker B shares = 3 - // Total assets = 7(staker A) + 3(staker B) + 5(yield) - await afterStakesSnapshot.restore() - - staker1SharesBefore = await acre.balanceOf(staker1.address) - staker2SharesBefore = await acre.balanceOf(staker2.address) - vaultYield = to1e18(5) - - // Simulating yield returned from strategies. The vault now contains - // more tokens than deposited which causes the exchange rate to - // change. - await tbtc.mint(await acre.getAddress(), vaultYield) - }) - - after(async () => { - afterSimulatingYieldSnapshot = await takeSnapshot() - }) - - it("the vault should hold more assets", async () => { - expect(await acre.totalAssets()).to.be.eq( - staker1AmountToStake + staker2AmountToStake + vaultYield, - ) - }) - - it("the staker's shares should be the same", async () => { - expect(await acre.balanceOf(staker1.address)).to.be.eq( - staker1SharesBefore, - ) - expect(await acre.balanceOf(staker2.address)).to.be.eq( - staker2SharesBefore, - ) - }) - - it("the staker A should redeem more tokens than before", async () => { - const shares = await acre.balanceOf(staker1.address) - const availableAssetsToRedeem = await acre.previewRedeem(shares) - - // Expected amount w/o rounding: 7 * 15 / 10 = 10.5 - // Expected amount w/ support for rounding: 10499999999999999999 in - // tBTC token precision. - const expectedAssetsToRedeem = 10499999999999999999n - - expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem) - }) - - it("the staker B should redeem more tokens than before", async () => { - const shares = await acre.balanceOf(staker2.address) - const availableAssetsToRedeem = await acre.previewRedeem(shares) + before(async () => { + // Current state: + // Staker A shares = 7 + // Staker B shares = 3 + // Total assets = 7(staker A) + 3(staker B) + 5(yield) + await afterStakesSnapshot.restore() - // Expected amount w/o rounding: 3 * 15 / 10 = 4.5 - // Expected amount w/ support for rounding: 4499999999999999999 in - // tBTC token precision. - const expectedAssetsToRedeem = 4499999999999999999n + staker1SharesBefore = await acre.balanceOf(staker1.address) + staker2SharesBefore = await acre.balanceOf(staker2.address) - expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem) - }) - }) + // Simulating yield returned from strategies. The vault now contains + // more tokens than deposited which causes the exchange rate to + // change. + await tbtc.mint(await acre.getAddress(), earnedYield) + }) - context("when staker A stakes more tokens", () => { - context( - "when total tBTC amount after staking would not exceed max amount", - () => { - const newAmountToStake = to1e18(2) - // Current state: - // Total assets = 7(staker A) + 3(staker B) + 5(yield) - // Total shares = 7 + 3 = 10 - // Shares to mint = 2 * 10 / 15 = 1.(3) -> 1333333333333333333 in stBTC - // token precision - const expectedSharesToMint = 1333333333333333333n - let sharesBefore: bigint - let availableToRedeemBefore: bigint + after(async () => { + afterSimulatingYieldSnapshot = await takeSnapshot() + }) - before(async () => { - await afterSimulatingYieldSnapshot.restore() + it("the vault should hold more assets", async () => { + expect(await acre.totalAssets()).to.be.eq( + staker1AmountToStake + staker2AmountToStake + earnedYield, + ) + }) - sharesBefore = await acre.balanceOf(staker1.address) - availableToRedeemBefore = await acre.previewRedeem(sharesBefore) + it("the stakers shares should be the same", async () => { + expect(await acre.balanceOf(staker1.address)).to.be.eq( + staker1SharesBefore, + ) + expect(await acre.balanceOf(staker2.address)).to.be.eq( + staker2SharesBefore, + ) + }) - await tbtc.mint(staker1.address, newAmountToStake) + it("the staker A should be able to redeem more tokens than before", async () => { + const shares = await acre.balanceOf(staker1.address) + const availableAssetsToRedeem = await acre.previewRedeem(shares) - await tbtc - .connect(staker1) - .approve(await acre.getAddress(), newAmountToStake) + // Expected amount w/o rounding: 7 * 15 / 10 = 10.5 + // Expected amount w/ support for rounding: 10499999999999999999 in + // tBTC token precision. + const expectedAssetsToRedeem = 10499999999999999999n - // State after stake: - // Total assets = 7(staker A) + 3(staker B) + 5(yield) + 2(staker - // A) = 17 - // Total shares = 7 + 3 + 1.(3) = 11.(3) - await acre - .connect(staker1) - .stake(newAmountToStake, staker1.address, referral) - }) - - it("should receive more shares", async () => { - const shares = await acre.balanceOf(staker1.address) - - expect(shares).to.be.eq(sharesBefore + expectedSharesToMint) - }) - - it("should redeem more tokens than before", async () => { - const shares = await acre.balanceOf(staker1.address) - const availableToRedeem = await acre.previewRedeem(shares) - - // Expected amount w/o rounding: 8.(3) * 17 / 11.(3) = 12.5 - // Expected amount w/ support for rounding: 12499999999999999999 in - // tBTC token precision. - const expectedTotalAssetsAvailableToRedeem = 12499999999999999999n - - expect(availableToRedeem).to.be.greaterThan( - availableToRedeemBefore, - ) - expect(availableToRedeem).to.be.eq( - expectedTotalAssetsAvailableToRedeem, - ) - }) - }, - ) + expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem) + }) - context( - "when total tBTC amount after staking would exceed max amount", - () => { - let possibleMaxAmountToStake: bigint - let amountToStake: bigint + it("the staker B should be able to redeem more tokens than before", async () => { + const shares = await acre.balanceOf(staker2.address) + const availableAssetsToRedeem = await acre.previewRedeem(shares) - before(async () => { - await afterSimulatingYieldSnapshot.restore() + // Expected amount w/o rounding: 3 * 15 / 10 = 4.5 + // Expected amount w/ support for rounding: 4499999999999999999 in + // tBTC token precision. + const expectedAssetsToRedeem = 4499999999999999999n - // In the current implementation of the `maxDeposit` the - // `address` param is not taken into account - it means it will - // return the same value for any address. - possibleMaxAmountToStake = await acre.maxDeposit(staker1.address) - amountToStake = possibleMaxAmountToStake + 1n + expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem) + }) + }) - await tbtc - .connect(staker1) - .approve(await acre.getAddress(), amountToStake) - }) - - it("should revert", async () => { - await expect(acre.stake(amountToStake, staker1.address, referral)) - .to.be.revertedWithCustomError( - acre, - "ERC4626ExceededMaxDeposit", + context("when staker A stakes more tokens", () => { + context( + "when total tBTC amount after staking would not exceed max amount", + () => { + const newAmountToStake = to1e18(2) + // Current state: + // Total assets = 7(staker A) + 3(staker B) + 5(yield) + // Total shares = 7 + 3 = 10 + // New stake amount = 2 + // Shares to mint = 2 * 10 / 15 = 1.(3) -> 1333333333333333333 in stBTC + // token precision + const expectedSharesToMint = 1333333333333333333n + let sharesBefore: bigint + let availableToRedeemBefore: bigint + + before(async () => { + await afterSimulatingYieldSnapshot.restore() + + sharesBefore = await acre.balanceOf(staker1.address) + availableToRedeemBefore = await acre.previewRedeem(sharesBefore) + + await tbtc.mint(staker1.address, newAmountToStake) + + await tbtc + .connect(staker1) + .approve(await acre.getAddress(), newAmountToStake) + + // State after stake: + // Total assets = 7(staker A) + 3(staker B) + 5(yield) + 2(staker + // A) = 17 + // Total shares = 7 + 3 + 1.(3) = 11.(3) + await acre + .connect(staker1) + .stake(newAmountToStake, staker1.address, referral) + }) + + it("should receive more shares", async () => { + const shares = await acre.balanceOf(staker1.address) + + expect(shares).to.be.eq(sharesBefore + expectedSharesToMint) + }) + + it("should be able to redeem more tokens than before", async () => { + const shares = await acre.balanceOf(staker1.address) + const availableToRedeem = await acre.previewRedeem(shares) + + // Expected amount w/o rounding: 8.(3) * 17 / 11.(3) = 12.5 + // Expected amount w/ support for rounding: 12499999999999999999 in + // tBTC token precision. + const expectedTotalAssetsAvailableToRedeem = + 12499999999999999999n + + expect(availableToRedeem).to.be.greaterThan( + availableToRedeemBefore, ) - .withArgs( - staker1.address, - amountToStake, - possibleMaxAmountToStake, + expect(availableToRedeem).to.be.eq( + expectedTotalAssetsAvailableToRedeem, ) - }) - }, - ) - - context( - "when total tBTC amount after staking would be equal to the max amount", - () => { - let amountToStake: bigint - let tx: ContractTransactionResponse - - before(async () => { - amountToStake = await acre.maxDeposit(staker1.address) + }) + }, + ) - await tbtc - .connect(staker1) - .approve(await acre.getAddress(), amountToStake) + context( + "when total tBTC amount after staking would exceed max amount", + () => { + let possibleMaxAmountToStake: bigint + let amountToStake: bigint - tx = await acre.stake(amountToStake, staker1, referral) - }) + before(async () => { + await afterSimulatingYieldSnapshot.restore() - it("should stake tokens correctly", async () => { - await expect(tx).to.emit(acre, "Deposit") - }) + // In the current implementation of the `maxDeposit` the + // `address` param is not taken into account - it means it will + // return the same value for any address. + possibleMaxAmountToStake = await acre.maxDeposit( + staker1.address, + ) + amountToStake = possibleMaxAmountToStake + 1n - it("the max deposit amount should be equal 0", async () => { - expect(await acre.maxDeposit(staker1)).to.eq(0) - }) + await tbtc + .connect(staker1) + .approve(await acre.getAddress(), amountToStake) + }) - it("should not stake more tokens", async () => { - await expect(acre.stake(amountToStake, staker1, referral)) - .to.be.revertedWithCustomError( - acre, - "ERC4626ExceededMaxDeposit", + it("should revert", async () => { + await expect( + acre.stake(amountToStake, staker1.address, referral), ) - .withArgs(staker1.address, amountToStake, 0) - }) - }, - ) + .to.be.revertedWithCustomError( + acre, + "ERC4626ExceededMaxDeposit", + ) + .withArgs( + staker1.address, + amountToStake, + possibleMaxAmountToStake, + ) + }) + }, + ) + + context( + "when total tBTC amount after staking would be equal to the max amount", + () => { + let amountToStake: bigint + let tx: ContractTransactionResponse + + before(async () => { + amountToStake = await acre.maxDeposit(staker1.address) + + await tbtc + .connect(staker1) + .approve(await acre.getAddress(), amountToStake) + + tx = await acre.stake(amountToStake, staker1, referral) + }) + + it("should stake tokens correctly", async () => { + await expect(tx).to.emit(acre, "Deposit") + }) + + it("the max deposit amount should be equal 0", async () => { + expect(await acre.maxDeposit(staker1)).to.eq(0) + }) + + it("should not be able to stake more tokens", async () => { + await expect(acre.stake(amountToStake, staker1, referral)) + .to.be.revertedWithCustomError( + acre, + "ERC4626ExceededMaxDeposit", + ) + .withArgs(staker1.address, amountToStake, 0) + }) + }, + ) + }) }) }) }) describe("mint", () => { - let snapshot: SnapshotRestorer - - beforeEach(async () => { - snapshot = await takeSnapshot() - }) - - afterEach(async () => { - await snapshot.restore() - }) + beforeAfterEachSnapshotWrapper() context("when minting as first staker", () => { const amountToStake = to1e18(1) @@ -635,17 +623,10 @@ describe("Acre", () => { }) describe("updateDepositParameters", () => { + beforeAfterEachSnapshotWrapper() + const validMinimumDepositAmount = to1e18(1) const validMaximumTotalAssetsAmount = to1e18(30) - let snapshot: SnapshotRestorer - - beforeEach(async () => { - snapshot = await takeSnapshot() - }) - - afterEach(async () => { - await snapshot.restore() - }) context("when is called by governance", () => { context("when all parameters are valid", () => { @@ -729,20 +710,16 @@ describe("Acre", () => { }) describe("maxDeposit", () => { + beforeAfterEachSnapshotWrapper() + let maximumTotalAssets: bigint let minimumDepositAmount: bigint - let snapshot: SnapshotRestorer beforeEach(async () => { - snapshot = await takeSnapshot() ;[minimumDepositAmount, maximumTotalAssets] = await acre.depositParameters() }) - afterEach(async () => { - await snapshot.restore() - }) - context( "when total assets is greater than maximum total assets amount", () => { @@ -884,20 +861,16 @@ describe("Acre", () => { }) describe("maxMint", () => { + beforeAfterEachSnapshotWrapper() + let maximumTotalAssets: bigint let minimumDepositAmount: bigint - let snapshot: SnapshotRestorer beforeEach(async () => { - snapshot = await takeSnapshot() ;[minimumDepositAmount, maximumTotalAssets] = await acre.depositParameters() }) - afterEach(async () => { - await snapshot.restore() - }) - context( "when total assets is greater than maximum total assets amount", () => { @@ -988,26 +961,24 @@ describe("Acre", () => { }) describe("deposit", () => { + beforeAfterEachSnapshotWrapper() + + const receiver = ethers.Wallet.createRandom() + let amountToDeposit: bigint let minimumDepositAmount: bigint - let snapshot: SnapshotRestorer beforeEach(async () => { - snapshot = await takeSnapshot() minimumDepositAmount = await acre.minimumDepositAmount() }) - afterEach(async () => { - await snapshot.restore() - }) - context("when the deposit amount is less than minimum", () => { beforeEach(() => { amountToDeposit = minimumDepositAmount - 1n }) it("should revert", async () => { - await expect(acre.deposit(amountToDeposit, staker1.address)) + await expect(acre.deposit(amountToDeposit, receiver.address)) .to.be.revertedWithCustomError(acre, "DepositAmountLessThanMin") .withArgs(amountToDeposit, minimumDepositAmount) }) @@ -1024,7 +995,9 @@ describe("Acre", () => { expectedReceivedShares = amountToDeposit await tbtc.approve(await acre.getAddress(), amountToDeposit) - tx = await acre.deposit(amountToDeposit, staker1.address) + tx = await acre + .connect(staker1) + .deposit(amountToDeposit, receiver.address) }) it("should emit Deposit event", async () => { @@ -1032,7 +1005,7 @@ describe("Acre", () => { // Caller. staker1.address, // Receiver. - staker1.address, + receiver.address, // Staked tokens. amountToDeposit, // Received shares. @@ -1043,7 +1016,7 @@ describe("Acre", () => { it("should mint stBTC tokens", async () => { await expect(tx).to.changeTokenBalances( acre, - [staker1.address], + [receiver.address], [expectedReceivedShares], ) }) diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts index 435ee2808..eabcc46ac 100644 --- a/core/test/Dispatcher.test.ts +++ b/core/test/Dispatcher.test.ts @@ -1,15 +1,19 @@ import { ethers } from "hardhat" import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import { expect } from "chai" +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" + +import { ContractTransactionResponse, ZeroAddress } from "ethers" import { - SnapshotRestorer, - takeSnapshot, - loadFixture, -} from "@nomicfoundation/hardhat-toolbox/network-helpers" -import { ZeroAddress } from "ethers" + beforeAfterEachSnapshotWrapper, + beforeAfterSnapshotWrapper, + deployment, + getNamedSigner, + getUnnamedSigner, +} from "./helpers" + import type { Dispatcher, TestERC4626, Acre, TestERC20 } from "../typechain" -import { deployment } from "./helpers/context" -import { getNamedSigner, getUnnamedSigner } from "./helpers/signer" + import { to1e18 } from "./utils" async function fixture() { @@ -21,8 +25,6 @@ async function fixture() { } describe("Dispatcher", () => { - let snapshot: SnapshotRestorer - let dispatcher: Dispatcher let vault: TestERC4626 let tbtc: TestERC20 @@ -46,16 +48,12 @@ describe("Dispatcher", () => { vaultAddress4 = await ethers.Wallet.createRandom().getAddress() }) - beforeEach(async () => { - snapshot = await takeSnapshot() - }) - - afterEach(async () => { - await snapshot.restore() - }) - describe("authorizeVault", () => { + beforeAfterSnapshotWrapper() + context("when caller is not a governance account", () => { + beforeAfterSnapshotWrapper() + it("should revert when adding a vault", async () => { await expect( dispatcher.connect(thirdParty).authorizeVault(vaultAddress1), @@ -69,9 +67,11 @@ describe("Dispatcher", () => { }) context("when caller is a governance account", () => { + beforeAfterSnapshotWrapper() + let tx: ContractTransactionResponse - beforeEach(async () => { + before(async () => { tx = await dispatcher.connect(governance).authorizeVault(vaultAddress1) await dispatcher.connect(governance).authorizeVault(vaultAddress2) await dispatcher.connect(governance).authorizeVault(vaultAddress3) @@ -103,7 +103,9 @@ describe("Dispatcher", () => { }) describe("deauthorizeVault", () => { - beforeEach(async () => { + beforeAfterSnapshotWrapper() + + before(async () => { await dispatcher.connect(governance).authorizeVault(vaultAddress1) await dispatcher.connect(governance).authorizeVault(vaultAddress2) await dispatcher.connect(governance).authorizeVault(vaultAddress3) @@ -123,6 +125,8 @@ describe("Dispatcher", () => { }) context("when caller is a governance account", () => { + beforeAfterEachSnapshotWrapper() + it("should deauthorize vaults", async () => { await dispatcher.connect(governance).deauthorizeVault(vaultAddress1) @@ -168,6 +172,8 @@ describe("Dispatcher", () => { }) describe("depositToVault", () => { + beforeAfterSnapshotWrapper() + const assetsToAllocate = to1e18(100) const minSharesOut = to1e18(100) @@ -177,6 +183,8 @@ describe("Dispatcher", () => { }) context("when caller is not maintainer", () => { + beforeAfterSnapshotWrapper() + it("should revert when depositing to a vault", async () => { await expect( dispatcher @@ -192,6 +200,8 @@ describe("Dispatcher", () => { context("when caller is maintainer", () => { context("when vault is not authorized", () => { + beforeAfterSnapshotWrapper() + it("should revert", async () => { const randomAddress = await ethers.Wallet.createRandom().getAddress() await expect( @@ -210,6 +220,8 @@ describe("Dispatcher", () => { }) context("when allocation is successful", () => { + beforeAfterSnapshotWrapper() + let tx: ContractTransactionResponse before(async () => { @@ -244,6 +256,8 @@ describe("Dispatcher", () => { context( "when the expected returned shares are less than the actual returned shares", () => { + beforeAfterSnapshotWrapper() + const sharesOut = assetsToAllocate const minShares = to1e18(101) @@ -263,6 +277,8 @@ describe("Dispatcher", () => { }) describe("updateMaintainer", () => { + beforeAfterSnapshotWrapper() + let newMaintainer: string before(async () => { @@ -270,6 +286,8 @@ describe("Dispatcher", () => { }) context("when caller is not an owner", () => { + beforeAfterSnapshotWrapper() + it("should revert", async () => { await expect( dispatcher.connect(thirdParty).updateMaintainer(newMaintainer), @@ -284,6 +302,8 @@ describe("Dispatcher", () => { context("when caller is an owner", () => { context("when maintainer is a zero address", () => { + beforeAfterSnapshotWrapper() + it("should revert", async () => { await expect( dispatcher.connect(governance).updateMaintainer(ZeroAddress), @@ -292,6 +312,8 @@ describe("Dispatcher", () => { }) context("when maintainer is not a zero address", () => { + beforeAfterSnapshotWrapper() + let tx: ContractTransactionResponse before(async () => { diff --git a/core/test/helpers/context.ts b/core/test/helpers/context.ts index d6b41a822..02d2cf8fa 100644 --- a/core/test/helpers/context.ts +++ b/core/test/helpers/context.ts @@ -1,5 +1,4 @@ import { deployments } from "hardhat" - import { getDeployedContract } from "./contract" import type { diff --git a/core/test/helpers/index.ts b/core/test/helpers/index.ts index 27ddcb0b9..e4df2196a 100644 --- a/core/test/helpers/index.ts +++ b/core/test/helpers/index.ts @@ -1,3 +1,4 @@ export * from "./context" export * from "./contract" export * from "./signer" +export * from "./snapshot" diff --git a/core/test/helpers/snapshot.ts b/core/test/helpers/snapshot.ts index 9a5caf2c9..804c0455d 100644 --- a/core/test/helpers/snapshot.ts +++ b/core/test/helpers/snapshot.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/prefer-default-export */ import { SnapshotRestorer, takeSnapshot, @@ -19,3 +18,19 @@ export function beforeAfterSnapshotWrapper(): void { await snapshot.restore() }) } + +/** + * Adds a beforeEach/afterEach hook pair to snapshot the EVM state before and + * after each of tests in a test suite. + */ +export function beforeAfterEachSnapshotWrapper(): void { + let snapshot: SnapshotRestorer + + beforeEach(async () => { + snapshot = await takeSnapshot() + }) + + afterEach(async () => { + await snapshot.restore() + }) +} diff --git a/dapp/.eslintrc b/dapp/.eslintrc index 4317c87a7..35437e4ed 100644 --- a/dapp/.eslintrc +++ b/dapp/.eslintrc @@ -15,6 +15,73 @@ 2, { "allowRequiredDefaults": true } ], - "react/require-default-props": [0] + "react/require-default-props": [0], + }, + // FIXME: + // This is temporary solution after changes of the eslint-config version: @thesis-co/eslint-config: "github:thesis/eslint-config#7b9bc8c" + // Overrides rules should be fixed file by file. + "overrides": [ + { + "files": [ + "src/components/Header/ConnectWallet.tsx", + "src/components/Modals/Support/MissingAccount.tsx", + "src/components/Modals/Staking/SignMessage.tsx", + "src/hooks/useDepositBTCTransaction.ts", + "src/components/shared/Form/FormTokenBalanceInput.tsx" + ], + "rules": { + "@typescript-eslint/no-misused-promises": "off" + } + }, + { + "files": [ + "src/hooks/useSignMessage.ts" + ], + "rules": { + "@typescript-eslint/no-floating-promises": "off" + } + }, + { + "files": [ + "src/theme/*" + ], + "rules": { + "@typescript-eslint/unbound-method": "off" + } + }, + { + "files": [ + "src/theme/Alert.ts" + ], + "rules": { + "@typescript-eslint/no-unsafe-member-access": "off" + } + }, + { + "files": [ + "src/components/shared/Form/FormTokenBalanceInput.tsx" + ], + "rules": { + "@typescript-eslint/no-unsafe-assignment": "off" + } + }, + { + "files": [ + "src/components/shared/TokenAmountForm/index.tsx" + ], + "rules": { + "@typescript-eslint/require-await": "off" + } + } + ], + "settings": { + "import/resolver": { + "alias": { + "map": [ + ["#", "./src"] + ], + "extensions": [".js", ".jsx",".ts", ".tsx"] + } + } } } diff --git a/dapp/manifest-ledger-live-app.json b/dapp/manifest-ledger-live-app.json index 29f63c113..265928a59 100644 --- a/dapp/manifest-ledger-live-app.json +++ b/dapp/manifest-ledger-live-app.json @@ -18,6 +18,6 @@ "en": "Bitcoin Liquid Staking" } }, - "permissions": ["account.request"], + "permissions": ["account.request", "message.sign"], "domains": ["http://*"] } diff --git a/dapp/package.json b/dapp/package.json index 2d160d0a4..34cc514a8 100644 --- a/dapp/package.json +++ b/dapp/package.json @@ -20,21 +20,25 @@ "@emotion/styled": "^11.11.0", "@ledgerhq/wallet-api-client": "^1.5.0", "@ledgerhq/wallet-api-client-react": "^1.3.0", + "formik": "^2.4.5", "framer-motion": "^10.16.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-number-format": "^5.3.1" }, "devDependencies": { - "@thesis-co/eslint-config": "^0.6.1", + "@thesis-co/eslint-config": "github:thesis/eslint-config#7b9bc8c", "@types/react": "^18.2.38", "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", "@vitejs/plugin-react": "^4.2.0", "eslint": "^8.54.0", + "eslint-import-resolver-alias": "^1.1.2", + "eslint-plugin-import": "^2.29.1", "prettier": "^3.1.0", "typescript": "^5.3.2", - "vite": "^5.0.2" + "vite": "^5.0.2", + "vite-plugin-node-polyfills": "^0.19.0" } } diff --git a/dapp/src/components/DocsDrawer/index.tsx b/dapp/src/components/DocsDrawer/index.tsx index 5082af57b..060c6e731 100644 --- a/dapp/src/components/DocsDrawer/index.tsx +++ b/dapp/src/components/DocsDrawer/index.tsx @@ -5,8 +5,8 @@ import { DrawerContent, DrawerOverlay, } from "@chakra-ui/react" -import { useDocsDrawer } from "../../hooks" -import { TextMd } from "../shared/Typography" +import { useDocsDrawer } from "#/hooks" +import { TextMd } from "#/components/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 index 80b36e6e8..352fe7af0 100644 --- a/dapp/src/components/GlobalStyles/index.tsx +++ b/dapp/src/components/GlobalStyles/index.tsx @@ -1,11 +1,11 @@ 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" +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 3bb5af7d9..5319a04ca 100644 --- a/dapp/src/components/Header/ConnectWallet.tsx +++ b/dapp/src/components/Header/ConnectWallet.tsx @@ -1,15 +1,15 @@ import React from "react" import { Button, HStack, Icon } from "@chakra-ui/react" import { Account } from "@ledgerhq/wallet-api-client" -import { Bitcoin, Ethereum } from "../../static/icons" import { useRequestBitcoinAccount, useRequestEthereumAccount, useWalletContext, -} from "../../hooks" -import { truncateAddress } from "../../utils" -import { CurrencyBalance } from "../shared/CurrencyBalance" -import { TextMd } from "../shared/Typography" +} from "#/hooks" +import { CurrencyBalance } from "#/components/shared/CurrencyBalance" +import { TextMd } from "#/components/shared/Typography" +import { Bitcoin, Ethereum } from "#/static/icons" +import { truncateAddress } from "#/utils" export type ConnectButtonsProps = { leftIcon: typeof Icon @@ -46,7 +46,7 @@ export default function ConnectWallet() { Balance diff --git a/dapp/src/components/Header/index.tsx b/dapp/src/components/Header/index.tsx index 544fbdee9..480cb502e 100644 --- a/dapp/src/components/Header/index.tsx +++ b/dapp/src/components/Header/index.tsx @@ -1,7 +1,7 @@ import React from "react" import { Flex, HStack, Icon } from "@chakra-ui/react" +import { AcreLogo } from "#/static/icons" import ConnectWallet from "./ConnectWallet" -import { AcreLogo } from "../../static/icons" export default function Header() { return ( diff --git a/dapp/src/components/Modals/ActionForm/index.tsx b/dapp/src/components/Modals/ActionForm/index.tsx new file mode 100644 index 000000000..eabb156bb --- /dev/null +++ b/dapp/src/components/Modals/ActionForm/index.tsx @@ -0,0 +1,41 @@ +import React from "react" +import { + ModalBody, + Tabs, + TabList, + Tab, + TabPanels, + TabPanel, +} from "@chakra-ui/react" +import { useModalFlowContext } from "#/hooks" +import StakeForm from "../Staking/StakeForm" + +const TABS = ["stake", "unstake"] as const + +type Action = (typeof TABS)[number] + +function ActionForm({ action }: { action: Action }) { + const { goNext } = useModalFlowContext() + + return ( + + + + {TABS.map((tab) => ( + + {tab} + + ))} + + + + + + {/* TODO: Add form for unstake */} + + + + ) +} + +export default ActionForm diff --git a/dapp/src/components/Modals/Staking/DepositBTC.tsx b/dapp/src/components/Modals/Staking/DepositBTC.tsx new file mode 100644 index 000000000..924a0ee24 --- /dev/null +++ b/dapp/src/components/Modals/Staking/DepositBTC.tsx @@ -0,0 +1,20 @@ +import React from "react" +import { useDepositBTCTransaction, useModalFlowContext } from "#/hooks" +import Alert from "#/components/shared/Alert" +import { TextMd } from "#/components/shared/Typography" +import StakingSteps from "./components/StakingSteps" + +export default function DepositBTC() { + const { goNext } = useModalFlowContext() + const { depositBTC } = useDepositBTCTransaction(goNext) + + return ( + + + + Make a Bitcoin transaction to deposit and stake your BTC. + + + + ) +} diff --git a/dapp/src/components/Modals/Staking/Overview/StakingSteps.tsx b/dapp/src/components/Modals/Staking/Overview/StakingSteps.tsx deleted file mode 100644 index b34437226..000000000 --- a/dapp/src/components/Modals/Staking/Overview/StakingSteps.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from "react" -import { StepNumber } from "@chakra-ui/react" -import { TextLg, TextMd } from "../../../shared/Typography" -import StepperBase from "../../../shared/StepperBase" - -function Title({ children }: { children: React.ReactNode }) { - return {children} -} - -function Description({ children }: { children: React.ReactNode }) { - return {children} -} - -const STEPS = [ - { - id: "sign-message", - title: Sign message, - description: ( - - You will sign a gas-free Ethereum message to indicate the address where - you'd like to get your stBTC liquid staking token. - - ), - }, - { - id: "deposit-btc", - title: Deposit BTC, - description: ( - - You will make a Bitcoin transaction to deposit and stake your BTC. - - ), - }, -] - -export default function StakingSteps() { - return ( - } - steps={STEPS} - /> - ) -} diff --git a/dapp/src/components/Modals/Staking/Overview/index.tsx b/dapp/src/components/Modals/Staking/Overview/index.tsx index 04f858ec4..af2485db6 100644 --- a/dapp/src/components/Modals/Staking/Overview/index.tsx +++ b/dapp/src/components/Modals/Staking/Overview/index.tsx @@ -1,14 +1,30 @@ import React from "react" -import { Button, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react" -import StakingSteps from "./StakingSteps" -import { ModalStep } from "../../../../contexts" +import { + Button, + ModalBody, + ModalFooter, + ModalHeader, + StepNumber, +} from "@chakra-ui/react" +import StepperBase from "#/components/shared/StepperBase" +import { useModalFlowContext } from "#/hooks" +import { STEPS } from "./steps" + +export default function Overview() { + const { goNext } = useModalFlowContext() -export default function Overview({ goNext }: ModalStep) { return ( <> Staking steps overview - + } + steps={STEPS} + /> - - ) -} diff --git a/dapp/src/components/Modals/Staking/StakeForm/Details.tsx b/dapp/src/components/Modals/Staking/StakeForm/Details.tsx new file mode 100644 index 000000000..698e6da54 --- /dev/null +++ b/dapp/src/components/Modals/Staking/StakeForm/Details.tsx @@ -0,0 +1,48 @@ +import React from "react" +import { List } from "@chakra-ui/react" +import TransactionDetailsAmountItem from "#/components/shared/TransactionDetails/AmountItem" +import { useTokenAmountFormValue } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" +import { useTransactionDetails } from "#/hooks" +import { CurrencyType } from "#/types" + +function Details({ currency }: { currency: CurrencyType }) { + const value = useTokenAmountFormValue() + const details = useTransactionDetails(value ?? 0n) + + return ( + + + + + + ) +} + +export default Details diff --git a/dapp/src/components/Modals/Staking/StakeForm/index.tsx b/dapp/src/components/Modals/Staking/StakeForm/index.tsx new file mode 100644 index 000000000..e5d5a0754 --- /dev/null +++ b/dapp/src/components/Modals/Staking/StakeForm/index.tsx @@ -0,0 +1,40 @@ +import React, { useCallback } from "react" +import { Button } from "@chakra-ui/react" +import { BITCOIN_MIN_AMOUNT } from "#/constants" +import { ModalStep } from "#/contexts" +import TokenAmountForm from "#/components/shared/TokenAmountForm" +import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" +import { useWalletContext, useTransactionContext } from "#/hooks" +import Details from "./Details" + +function StakeForm({ goNext }: ModalStep) { + const { btcAccount } = useWalletContext() + const { setTokenAmount } = useTransactionContext() + + const handleSubmitForm = useCallback( + (values: TokenAmountFormValues) => { + if (!values.amount) return + + setTokenAmount({ amount: values.amount, currency: "bitcoin" }) + goNext() + }, + [goNext, setTokenAmount], + ) + + return ( + +
+ + + ) +} + +export default StakeForm diff --git a/dapp/src/components/Modals/Staking/components/StakingSteps.tsx b/dapp/src/components/Modals/Staking/components/StakingSteps.tsx new file mode 100644 index 000000000..f092ad079 --- /dev/null +++ b/dapp/src/components/Modals/Staking/components/StakingSteps.tsx @@ -0,0 +1,77 @@ +import React from "react" +import { + Button, + HStack, + ModalBody, + ModalFooter, + ModalHeader, +} from "@chakra-ui/react" +import { TextLg, TextMd } from "#/components/shared/Typography" +import StepperBase, { StepBase } from "#/components/shared/StepperBase" +import Spinner from "#/components/shared/Spinner" + +export function Title({ children }: { children: React.ReactNode }) { + return {children} +} + +export function Description({ children }: { children: React.ReactNode }) { + return {children} +} + +const STEPS: StepBase[] = [ + { + id: "sign-message", + title: Sign message, + description: ( + + + Sign the message in your ETH wallet. + + ), + }, + { + id: "deposit-btc", + title: Deposit BTC, + description: ( + + + Waiting for your deposit... + + ), + }, +] + +export default function StakingSteps({ + buttonText, + activeStep, + onClick, + children, +}: { + buttonText: string + activeStep: number + onClick: () => void + children: React.ReactNode +}) { + return ( + <> + {`Step ${activeStep + 1} / ${STEPS.length}`} + + + {children} + + + + + + ) +} diff --git a/dapp/src/components/Modals/Staking/index.tsx b/dapp/src/components/Modals/Staking/index.tsx index 140f5d7ef..e74dd57ea 100644 --- a/dapp/src/components/Modals/Staking/index.tsx +++ b/dapp/src/components/Modals/Staking/index.tsx @@ -1,17 +1,23 @@ import React from "react" -import { useModalFlowContext } from "../../../hooks" -import StakeForm from "./StakeForm" +import { useModalFlowContext } from "#/hooks" +import ModalBase from "#/components/shared/ModalBase" import Overview from "./Overview" -import ModalBase from "../../shared/ModalBase" +import ActionForm from "../ActionForm" +import SignMessage from "./SignMessage" +import DepositBTC from "./DepositBTC" -function StakingSteps() { - const { activeStep, goNext } = useModalFlowContext() +function ActiveStakingStep() { + const { activeStep } = useModalFlowContext() switch (activeStep) { case 1: - return + return case 2: - return + return + case 3: + return + case 4: + return default: return null } @@ -25,8 +31,8 @@ export default function StakingModal({ onClose: () => void }) { return ( - - + + ) } diff --git a/dapp/src/components/Modals/Support/MissingAccount.tsx b/dapp/src/components/Modals/Support/MissingAccount.tsx index 7b6216276..89ca3effd 100644 --- a/dapp/src/components/Modals/Support/MissingAccount.tsx +++ b/dapp/src/components/Modals/Support/MissingAccount.tsx @@ -7,41 +7,41 @@ import { ModalFooter, ModalHeader, } from "@chakra-ui/react" -import { CurrencyType, RequestAccountParams } from "../../../types" -import { TextMd } from "../../shared/Typography" -import AlertWrapper from "../../shared/Alert" -import { CURRENCIES_BY_TYPE } from "../../../constants" +import { TextMd } from "#/components/shared/Typography" +import Alert from "#/components/shared/Alert" +import { getCurrencyByType } from "#/utils" +import { CurrencyType, RequestAccountParams } from "#/types" type MissingAccountProps = { - currencyType: CurrencyType + currency: CurrencyType icon: typeof Icon requestAccount: (...params: RequestAccountParams) => Promise } export default function MissingAccount({ - currencyType, + currency, icon, requestAccount, }: MissingAccountProps) { - const currency = CURRENCIES_BY_TYPE[currencyType] + const { name, symbol } = getCurrencyByType(currency) return ( <> - {currency.name} account not installed + {name} account not installed - {currency.name} account is required to make transactions for - depositing and staking your {currency.symbol}. + {name} account is required to make transactions for depositing and + staking your {symbol}. - + You will be sent to the Ledger Accounts section to perform this action. - + @@ -161,7 +169,7 @@ export default function TokenBalanceInput({ )} diff --git a/dapp/src/components/shared/TransactionDetails/AmountItem.tsx b/dapp/src/components/shared/TransactionDetails/AmountItem.tsx new file mode 100644 index 000000000..cf0c29045 --- /dev/null +++ b/dapp/src/components/shared/TransactionDetails/AmountItem.tsx @@ -0,0 +1,36 @@ +import React, { ComponentProps } from "react" +import { Flex } from "@chakra-ui/react" +import TransactionDetailsItem, { TransactionDetailsItemProps } from "." +import { CurrencyBalanceWithConversion } from "../CurrencyBalanceWithConversion" + +type TransactionDetailsAmountItemProps = ComponentProps< + typeof CurrencyBalanceWithConversion +> & + Pick + +function TransactionDetailsAmountItem({ + label, + from, + to, +}: TransactionDetailsAmountItemProps) { + return ( + + + + + + ) +} + +export default TransactionDetailsAmountItem diff --git a/dapp/src/components/shared/TransactionDetails/index.tsx b/dapp/src/components/shared/TransactionDetails/index.tsx new file mode 100644 index 000000000..bdaf6552c --- /dev/null +++ b/dapp/src/components/shared/TransactionDetails/index.tsx @@ -0,0 +1,32 @@ +import React from "react" +import { ListItem, ListItemProps } from "@chakra-ui/react" +import { TextMd } from "../Typography" + +export type TransactionDetailsItemProps = { + label: string + value?: string + children?: React.ReactNode +} & ListItemProps + +function TransactionDetailsItem({ + label, + value, + children, + ...listItemProps +}: TransactionDetailsItemProps) { + return ( + + + {label} + + {value ? {value} : children} + + ) +} + +export default TransactionDetailsItem diff --git a/dapp/src/constants/currency.ts b/dapp/src/constants/currency.ts index 420adb8b6..6f747bb1a 100644 --- a/dapp/src/constants/currency.ts +++ b/dapp/src/constants/currency.ts @@ -1,4 +1,4 @@ -import { Currency, CurrencyType } from "../types" +import { Currency, CurrencyType } from "#/types" export const BITCOIN: Currency = { name: "Bitcoin", diff --git a/dapp/src/constants/index.ts b/dapp/src/constants/index.ts index 68cb50031..a5cb59713 100644 --- a/dapp/src/constants/index.ts +++ b/dapp/src/constants/index.ts @@ -1 +1,2 @@ export * from "./currency" +export * from "./staking" diff --git a/dapp/src/constants/staking.ts b/dapp/src/constants/staking.ts new file mode 100644 index 000000000..421c57bb5 --- /dev/null +++ b/dapp/src/constants/staking.ts @@ -0,0 +1 @@ +export const BITCOIN_MIN_AMOUNT = "1000000" // 0.01 BTC diff --git a/dapp/src/contexts/TransactionContext.tsx b/dapp/src/contexts/TransactionContext.tsx new file mode 100644 index 000000000..3064bd530 --- /dev/null +++ b/dapp/src/contexts/TransactionContext.tsx @@ -0,0 +1,37 @@ +import React, { createContext, useMemo, useState } from "react" +import { TokenAmount } from "#/types" + +type TransactionContextValue = { + tokenAmount?: TokenAmount + setTokenAmount: React.Dispatch> +} + +export const TransactionContext = createContext({ + tokenAmount: undefined, + setTokenAmount: () => {}, +}) + +export function TransactionContextProvider({ + children, +}: { + children: React.ReactNode +}): React.ReactElement { + const [tokenAmount, setTokenAmount] = useState( + undefined, + ) + + const contextValue: TransactionContextValue = + useMemo( + () => ({ + tokenAmount, + setTokenAmount, + }), + [tokenAmount], + ) + + return ( + + {children} + + ) +} diff --git a/dapp/src/contexts/index.tsx b/dapp/src/contexts/index.tsx index 6787e1cc6..a849afb3f 100644 --- a/dapp/src/contexts/index.tsx +++ b/dapp/src/contexts/index.tsx @@ -3,3 +3,4 @@ export * from "./LedgerWalletAPIProvider" export * from "./DocsDrawerContext" export * from "./SidebarContext" export * from "./ModalFlowContext" +export * from "./TransactionContext" diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index 076da9a53..d05d9cf04 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -5,3 +5,7 @@ export * from "./useWalletContext" export * from "./useSidebar" export * from "./useDocsDrawer" export * from "./useModalFlowContext" +export * from "./useTransactionContext" +export * from "./useTransactionDetails" +export * from "./useSignMessage" +export * from "./useDepositBTCTransaction" diff --git a/dapp/src/hooks/useDepositBTCTransaction.ts b/dapp/src/hooks/useDepositBTCTransaction.ts new file mode 100644 index 000000000..0bef0c4be --- /dev/null +++ b/dapp/src/hooks/useDepositBTCTransaction.ts @@ -0,0 +1,13 @@ +import { useCallback } from "react" +import { OnSuccessCallback } from "#/types" + +export function useDepositBTCTransaction(onSuccess?: OnSuccessCallback) { + // TODO: sending transactions using the SDK + const depositBTC = useCallback(() => { + if (onSuccess) { + setTimeout(onSuccess, 1000) + } + }, [onSuccess]) + + return { depositBTC } +} diff --git a/dapp/src/hooks/useDocsDrawer.ts b/dapp/src/hooks/useDocsDrawer.ts index 4ce8bba3e..3536dba2a 100644 --- a/dapp/src/hooks/useDocsDrawer.ts +++ b/dapp/src/hooks/useDocsDrawer.ts @@ -1,5 +1,5 @@ import { useContext } from "react" -import { DocsDrawerContext } from "../contexts" +import { DocsDrawerContext } from "#/contexts" export function useDocsDrawer() { const context = useContext(DocsDrawerContext) diff --git a/dapp/src/hooks/useModalFlowContext.ts b/dapp/src/hooks/useModalFlowContext.ts index 48882c561..fda6eb681 100644 --- a/dapp/src/hooks/useModalFlowContext.ts +++ b/dapp/src/hooks/useModalFlowContext.ts @@ -1,5 +1,5 @@ import { useContext } from "react" -import { ModalFlowContext } from "../contexts" +import { ModalFlowContext } from "#/contexts" export function useModalFlowContext() { const context = useContext(ModalFlowContext) diff --git a/dapp/src/hooks/useRequestBitcoinAccount.ts b/dapp/src/hooks/useRequestBitcoinAccount.ts index 036db196d..051467dcf 100644 --- a/dapp/src/hooks/useRequestBitcoinAccount.ts +++ b/dapp/src/hooks/useRequestBitcoinAccount.ts @@ -1,8 +1,8 @@ import { useRequestAccount } from "@ledgerhq/wallet-api-client-react" import { useCallback, useContext, useEffect } from "react" -import { CURRENCY_ID_BITCOIN } from "../constants" -import { UseRequestAccountReturn } from "../types" -import { WalletContext } from "../contexts" +import { WalletContext } from "#/contexts" +import { UseRequestAccountReturn } from "#/types" +import { CURRENCY_ID_BITCOIN } from "#/constants" export function useRequestBitcoinAccount(): UseRequestAccountReturn { const { setBtcAccount } = useContext(WalletContext) diff --git a/dapp/src/hooks/useRequestEthereumAccount.ts b/dapp/src/hooks/useRequestEthereumAccount.ts index 5c3cad1f1..fcf18c6ff 100644 --- a/dapp/src/hooks/useRequestEthereumAccount.ts +++ b/dapp/src/hooks/useRequestEthereumAccount.ts @@ -1,8 +1,8 @@ import { useRequestAccount } from "@ledgerhq/wallet-api-client-react" import { useCallback, useContext, useEffect } from "react" -import { CURRENCY_ID_ETHEREUM } from "../constants" -import { UseRequestAccountReturn } from "../types" -import { WalletContext } from "../contexts" +import { WalletContext } from "#/contexts" +import { UseRequestAccountReturn } from "#/types" +import { CURRENCY_ID_ETHEREUM } from "#/constants" export function useRequestEthereumAccount(): UseRequestAccountReturn { const { setEthAccount } = useContext(WalletContext) diff --git a/dapp/src/hooks/useSidebar.ts b/dapp/src/hooks/useSidebar.ts index 986143a89..944364076 100644 --- a/dapp/src/hooks/useSidebar.ts +++ b/dapp/src/hooks/useSidebar.ts @@ -1,5 +1,5 @@ import { useContext } from "react" -import { SidebarContext } from "../contexts" +import { SidebarContext } from "#/contexts" export function useSidebar() { const context = useContext(SidebarContext) diff --git a/dapp/src/hooks/useSignMessage.ts b/dapp/src/hooks/useSignMessage.ts new file mode 100644 index 000000000..cd38d1001 --- /dev/null +++ b/dapp/src/hooks/useSignMessage.ts @@ -0,0 +1,26 @@ +import { useSignMessage as useSignMessageLedgerLive } from "@ledgerhq/wallet-api-client-react" +import { useCallback, useEffect } from "react" +import { OnSuccessCallback } from "#/types" +import { useWalletContext } from "./useWalletContext" + +const SIGN_MESSAGE = "Test message" + +export function useSignMessage(onSuccess?: OnSuccessCallback) { + const { ethAccount } = useWalletContext() + const { signMessage, signature } = useSignMessageLedgerLive() + + useEffect(() => { + if (signature && onSuccess) { + onSuccess() + } + }, [onSuccess, signature]) + + // TODO: signing message using the SDK + const handleSignMessage = useCallback(async () => { + if (!ethAccount?.id) return + + await signMessage(ethAccount.id, Buffer.from(SIGN_MESSAGE, "utf-8")) + }, [ethAccount, signMessage]) + + return { signMessage: handleSignMessage } +} diff --git a/dapp/src/hooks/useTransactionContext.ts b/dapp/src/hooks/useTransactionContext.ts new file mode 100644 index 000000000..41a8a8359 --- /dev/null +++ b/dapp/src/hooks/useTransactionContext.ts @@ -0,0 +1,14 @@ +import { useContext } from "react" +import { TransactionContext } from "#/contexts" + +export function useTransactionContext() { + const context = useContext(TransactionContext) + + if (!context) { + throw new Error( + "TransactionContext used outside of TransactionContext component", + ) + } + + return context +} diff --git a/dapp/src/hooks/useTransactionDetails.ts b/dapp/src/hooks/useTransactionDetails.ts new file mode 100644 index 000000000..f8f25f96b --- /dev/null +++ b/dapp/src/hooks/useTransactionDetails.ts @@ -0,0 +1,30 @@ +import { useEffect, useState } from "react" + +type UseTransactionDetailsResult = { + btcAmount: string + protocolFee: string + estimatedAmount: string +} + +export function useTransactionDetails(amount: bigint | undefined) { + const [details, setDetails] = useState< + UseTransactionDetailsResult | undefined + >(undefined) + + useEffect(() => { + if (!amount) { + setDetails(undefined) + } else { + const protocolFee = amount / 10000n + const estimatedAmount = amount - protocolFee + + setDetails({ + btcAmount: amount.toString(), + protocolFee: protocolFee.toString(), + estimatedAmount: estimatedAmount.toString(), + }) + } + }, [amount]) + + return details +} diff --git a/dapp/src/hooks/useWalletContext.ts b/dapp/src/hooks/useWalletContext.ts index 0da19204b..afca84aff 100644 --- a/dapp/src/hooks/useWalletContext.ts +++ b/dapp/src/hooks/useWalletContext.ts @@ -1,5 +1,5 @@ import { useContext } from "react" -import { WalletContext } from "../contexts" +import { WalletContext } from "#/contexts" export function useWalletContext() { const context = useContext(WalletContext) diff --git a/dapp/src/theme/Alert.ts b/dapp/src/theme/Alert.ts index 28bbc7e3c..d03592cc0 100644 --- a/dapp/src/theme/Alert.ts +++ b/dapp/src/theme/Alert.ts @@ -6,8 +6,10 @@ import { defineStyle, } from "@chakra-ui/react" +const KEYS = [...parts.keys, "rightIconContainer"] as const + const { defineMultiStyleConfig, definePartsStyle } = - createMultiStyleConfigHelpers(parts.keys) + createMultiStyleConfigHelpers(KEYS) const baseStyleDialog = defineStyle({ py: 5, @@ -24,9 +26,23 @@ const baseStyleIcon = defineStyle({ mr: 4, }) +const baseStyleRightIconContainer = defineStyle({ + position: "absolute", + right: 0, + top: 0, + p: 5, + h: "100%", + borderLeft: "2px solid white", + color: "brand.400", + display: "flex", + alignItems: "center", + w: 14, +}) + const baseStyle = definePartsStyle({ container: baseStyleDialog, icon: baseStyleIcon, + rightIconContainer: baseStyleRightIconContainer, }) const statusInfo = definePartsStyle({ diff --git a/dapp/src/theme/Spinner.ts b/dapp/src/theme/Spinner.ts new file mode 100644 index 000000000..74b0f3ff8 --- /dev/null +++ b/dapp/src/theme/Spinner.ts @@ -0,0 +1,11 @@ +import { defineStyle, defineStyleConfig } from "@chakra-ui/react" + +const baseStyle = defineStyle({ + color: "brand.400", + borderWidth: 3, + borderTopColor: "gold.400", + borderBottomColor: "gold.400", + borderLeftColor: "gold.400", +}) + +export const spinnerTheme = defineStyleConfig({ baseStyle }) diff --git a/dapp/src/theme/Tabs.ts b/dapp/src/theme/Tabs.ts new file mode 100644 index 000000000..4f48860fa --- /dev/null +++ b/dapp/src/theme/Tabs.ts @@ -0,0 +1,51 @@ +import { tabsAnatomy as parts } from "@chakra-ui/anatomy" +import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react" + +const { definePartsStyle, defineMultiStyleConfig } = + createMultiStyleConfigHelpers(parts.keys) + +const baseStyle = definePartsStyle({ + tab: { + fontWeight: "bold", + color: "grey.400", + }, +}) + +const variantUnderlineTab = defineStyle({ + pb: 4, + borderBottom: "2px solid", + borderColor: "transparent", + background: "transparent", + textTransform: "capitalize", + + _selected: { + color: "grey.700", + borderColor: "brand.400", + }, + _hover: { + color: "grey.700", + }, +}) + +const variantUnderlineTabList = defineStyle({ + gap: 5, + pb: 6, +}) + +const variantUnderlineTabPanel = defineStyle({ + px: 0, +}) + +const variantUnderline = definePartsStyle({ + tab: variantUnderlineTab, + tablist: variantUnderlineTabList, + tabpanel: variantUnderlineTabPanel, +}) + +const variants = { + underline: variantUnderline, +} + +const Tabs = defineMultiStyleConfig({ baseStyle, variants }) + +export default Tabs diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index 6e7babc46..421fabe96 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -17,6 +17,8 @@ import Alert from "./Alert" import Form from "./Form" import FormLabel from "./FormLabel" import FormError from "./FormError" +import Tabs from "./Tabs" +import { spinnerTheme as Spinner } from "./Spinner" const defaultTheme = { colors, @@ -50,6 +52,8 @@ const defaultTheme = { Form, FormLabel, FormError, + Tabs, + Spinner, }, } diff --git a/dapp/src/types/callback.ts b/dapp/src/types/callback.ts new file mode 100644 index 000000000..526705388 --- /dev/null +++ b/dapp/src/types/callback.ts @@ -0,0 +1 @@ +export type OnSuccessCallback = () => void | Promise diff --git a/dapp/src/types/index.ts b/dapp/src/types/index.ts index 1e77e81e7..ea2933681 100644 --- a/dapp/src/types/index.ts +++ b/dapp/src/types/index.ts @@ -1,2 +1,4 @@ export * from "./ledger-live-app" export * from "./currency" +export * from "./staking" +export * from "./callback" diff --git a/dapp/src/types/staking.ts b/dapp/src/types/staking.ts new file mode 100644 index 000000000..56b4740be --- /dev/null +++ b/dapp/src/types/staking.ts @@ -0,0 +1,6 @@ +import { CurrencyType } from "./currency" + +export type TokenAmount = { + amount: bigint + currency: CurrencyType +} diff --git a/dapp/src/utils/currency.ts b/dapp/src/utils/currency.ts new file mode 100644 index 000000000..4d8b02a78 --- /dev/null +++ b/dapp/src/utils/currency.ts @@ -0,0 +1,5 @@ +import { Currency, CurrencyType } from "#/types" +import { CURRENCIES_BY_TYPE } from "#/constants" + +export const getCurrencyByType = (currency: CurrencyType): Currency => + CURRENCIES_BY_TYPE[currency] diff --git a/dapp/src/utils/forms.ts b/dapp/src/utils/forms.ts new file mode 100644 index 000000000..97872a8f4 --- /dev/null +++ b/dapp/src/utils/forms.ts @@ -0,0 +1,39 @@ +import { CurrencyType } from "#/types" +import { getCurrencyByType } from "./currency" +import { formatTokenAmount } from "./numbers" + +const ERRORS = { + REQUIRED: "Required.", + EXCEEDED_VALUE: "The amount exceeds your current balance.", + INSUFFICIENT_VALUE: (minValue: string) => + `The minimum amount must be at least ${minValue} BTC.`, +} + +export function getErrorsObj(errors: { [key in keyof T]: string }) { + return (Object.keys(errors) as Array).every((name) => !errors[name]) + ? {} + : errors +} + +export function validateTokenAmount( + value: bigint | undefined, + maxValue: string, + minValue: string, + currency: CurrencyType, +): string | undefined { + if (value === undefined) return ERRORS.REQUIRED + + const { decimals } = getCurrencyByType(currency) + + const maxValueInBI = BigInt(maxValue) + const minValueInBI = BigInt(minValue) + + const isMaximumValueExceeded = value > maxValueInBI + const isMinimumValueFulfilled = value >= minValueInBI + + if (isMaximumValueExceeded) return ERRORS.EXCEEDED_VALUE + if (!isMinimumValueFulfilled) + return ERRORS.INSUFFICIENT_VALUE(formatTokenAmount(minValue, decimals)) + + return undefined +} diff --git a/dapp/src/utils/index.ts b/dapp/src/utils/index.ts index 613e0f071..02eaa61b8 100644 --- a/dapp/src/utils/index.ts +++ b/dapp/src/utils/index.ts @@ -1,2 +1,4 @@ export * from "./numbers" export * from "./address" +export * from "./forms" +export * from "./currency" diff --git a/dapp/src/utils/numbers.ts b/dapp/src/utils/numbers.ts index 59e99a82b..faf472c31 100644 --- a/dapp/src/utils/numbers.ts +++ b/dapp/src/utils/numbers.ts @@ -16,7 +16,8 @@ export const numberToLocaleString = ( * floating point number truncated to `desiredDecimals`. * * This function is based on the solution used by the Taho extension. - * More info: https://github.com/tahowallet/extension/blob/main/background/lib/fixed-point.ts#L216-L239 + * Source: + * https://github.com/tahowallet/extension/blob/main/background/lib/fixed-point.ts#L216-L239 */ export function bigIntToUserAmount( fixedPoint: bigint, @@ -85,7 +86,8 @@ export const formatSatoshiAmount = ( * Used in cases where precision is critical. * * This function is based on the solution used by the Taho extension. - * More info: https://github.com/tahowallet/extension/blob/main/background/lib/fixed-point.ts#L172-L214 + * Source: + * https://github.com/tahowallet/extension/blob/main/background/lib/fixed-point.ts#L172-L214 */ export function fixedPointNumberToString( amount: bigint, @@ -114,3 +116,83 @@ export function fixedPointNumberToString( return `${preDecimalCharacters}${decimalString}` } + +/** + * Convert a fixed point bigint with precision `fixedPointDecimals` to another + * fixed point bigint with precision `targetDecimals`. + * + * It is highly recommended that the precision of the fixed point bigint is + * tracked alongside the number, e.g. as with the FixedPointNumber type. To this + * end, prefer `convertFixedPointNumber` unless you are already carrying + * precision information separately. + * + * Source: + * https://github.com/tahowallet/extension/blob/main/background/lib/fixed-point.ts#L25-L44 + */ +function convertFixedPoint( + fixedPoint: bigint, + fixedPointDecimals: number, + targetDecimals: number, +): bigint { + if (fixedPointDecimals >= targetDecimals) { + return fixedPoint / 10n ** BigInt(fixedPointDecimals - targetDecimals) + } + + return fixedPoint * 10n ** BigInt(targetDecimals - fixedPointDecimals) +} + +/** + * Parses a simple floating point string in US decimal format (potentially + * using commas as thousands separators, and using a single period as a decimal + * separator) to a FixedPointNumber. The decimals in the returned + * FixedPointNumber will match the number of digits after the decimal in the + * floating point string. + * + * Source: + * https://github.com/tahowallet/extension/blob/main/background/lib/fixed-point.ts#L134-L170 + */ +function parseToFixedPointNumber( + floatingPointString: string, +): { amount: bigint; decimals: number } | undefined { + if (!floatingPointString.match(/^[^0-9]*([0-9,]+)(?:\.([0-9]*))?$/)) { + return undefined + } + + const [whole, decimals, ...extra] = floatingPointString.split(".") + + // Only one `.` supported. + if (extra.length > 0) { + return undefined + } + + const noThousandsSeparatorWhole = whole.replace(",", "") + const setDecimals = decimals ?? "" + + try { + return { + amount: BigInt([noThousandsSeparatorWhole, setDecimals].join("")), + decimals: setDecimals.length, + } + } catch (error) { + return undefined + } +} + +/** + * Convert a floating point number to bigint.`. + * It is necessary to parse floating point number to fixed point first. + * Then convert a fixed point bigint with precision `parsedAmount.decimals` to another + * fixed point bigint with precision `decimals`. + */ +export function userAmountToBigInt( + amount: string, + decimals = 18, +): bigint | undefined { + const parsedAmount = parseToFixedPointNumber(amount) + + if (typeof parsedAmount === "undefined") { + return undefined + } + + return convertFixedPoint(parsedAmount.amount, parsedAmount.decimals, decimals) +} diff --git a/dapp/tsconfig.json b/dapp/tsconfig.json index 2e31274ea..03b8b2fe1 100644 --- a/dapp/tsconfig.json +++ b/dapp/tsconfig.json @@ -11,8 +11,11 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "strict": true + "strict": true, + "baseUrl": ".", + "paths": { + "#/*": ["./src/*"] + } }, - "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/dapp/vite.config.ts b/dapp/vite.config.ts index ea1889ae7..b60415bd0 100644 --- a/dapp/vite.config.ts +++ b/dapp/vite.config.ts @@ -1,6 +1,14 @@ import { defineConfig } from "vite" import react from "@vitejs/plugin-react" +import { nodePolyfills } from "vite-plugin-node-polyfills" + +import { resolve } from "path" export default defineConfig({ - plugins: [react()], + resolve: { + alias: { + "#": resolve(__dirname, "./src"), + }, + }, + plugins: [nodePolyfills(), react()], }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3161c487..dd4b99542 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -132,6 +132,9 @@ importers: '@ledgerhq/wallet-api-client-react': specifier: ^1.3.0 version: 1.3.0(react@18.2.0) + formik: + specifier: ^2.4.5 + version: 2.4.5(react@18.2.0) framer-motion: specifier: ^10.16.5 version: 10.16.5(react-dom@18.2.0)(react@18.2.0) @@ -146,8 +149,8 @@ importers: version: 5.3.1(react-dom@18.2.0)(react@18.2.0) devDependencies: '@thesis-co/eslint-config': - specifier: ^0.6.1 - version: 0.6.1(eslint@8.54.0)(prettier@3.1.0)(typescript@5.3.2) + specifier: github:thesis/eslint-config#7b9bc8c + version: github.com/thesis/eslint-config/7b9bc8c(eslint@8.54.0)(prettier@3.1.0)(typescript@5.3.2) '@types/react': specifier: ^18.2.38 version: 18.2.38 @@ -166,6 +169,12 @@ importers: eslint: specifier: ^8.54.0 version: 8.54.0 + eslint-import-resolver-alias: + specifier: ^1.1.2 + version: 1.1.2(eslint-plugin-import@2.29.1) + eslint-plugin-import: + specifier: ^2.29.1 + version: 2.29.1(@typescript-eslint/parser@6.12.0)(eslint@8.54.0) prettier: specifier: ^3.1.0 version: 3.1.0 @@ -175,6 +184,9 @@ importers: vite: specifier: ^5.0.2 version: 5.0.2 + vite-plugin-node-polyfills: + specifier: ^0.19.0 + version: 0.19.0(vite@5.0.2) sdk: devDependencies: @@ -5075,6 +5087,34 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false + /@rollup/plugin-inject@5.0.5: + resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.1.0 + estree-walker: 2.0.2 + magic-string: 0.30.5 + dev: true + + /@rollup/pluginutils@5.1.0: + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: true + /@rollup/rollup-android-arm-eabi@4.5.1: resolution: {integrity: sha512-YaN43wTyEBaMqLDYeze+gQ4ZrW5RbTEGtT5o1GVDkhpdNcsLTnLRcLccvwy3E9wiDKWg9RIhuoy3JQKDRBfaZA==} cpu: [arm] @@ -5353,35 +5393,6 @@ packages: dependencies: defer-to-connect: 2.0.1 - /@thesis-co/eslint-config@0.6.1(eslint@8.54.0)(prettier@3.1.0)(typescript@5.3.2): - resolution: {integrity: sha512-0vJCCI4UwUdniDCQeTFlMBT+bSp5pGkrtHrZrG2vmyLZwSVdJNtInjkBc/Jd0sGfMtPo3pqQRwA40Zo80lPi+Q==} - engines: {node: '>=14.0.0'} - peerDependencies: - eslint: '>=6.8.0' - dependencies: - '@thesis-co/prettier-config': github.com/thesis/prettier-config/daeaac564056a7885e4366ce12bfde6fd823fc90(prettier@3.1.0) - '@typescript-eslint/eslint-plugin': 6.12.0(@typescript-eslint/parser@6.12.0)(eslint@8.54.0)(typescript@5.3.2) - '@typescript-eslint/parser': 6.12.0(eslint@8.54.0)(typescript@5.3.2) - eslint: 8.54.0 - eslint-config-airbnb: 19.0.4(eslint-plugin-import@2.29.0)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.33.2)(eslint@8.54.0) - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.0)(eslint@8.54.0) - eslint-config-airbnb-typescript: 17.1.0(@typescript-eslint/eslint-plugin@6.12.0)(@typescript-eslint/parser@6.12.0)(eslint-plugin-import@2.29.0)(eslint@8.54.0) - eslint-config-prettier: 9.0.0(eslint@8.54.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.12.0)(eslint@8.54.0) - eslint-plugin-jsx-a11y: 6.8.0(eslint@8.54.0) - eslint-plugin-no-only-tests: 3.1.0 - eslint-plugin-prettier: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.54.0)(prettier@3.1.0) - eslint-plugin-react: 7.33.2(eslint@8.54.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.54.0) - transitivePeerDependencies: - - '@types/eslint' - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - prettier - - supports-color - - typescript - dev: true - /@tokenizer/token@0.3.0: resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} @@ -5569,6 +5580,13 @@ packages: '@types/minimatch': 5.1.2 '@types/node': 20.9.4 + /@types/hoist-non-react-statics@3.3.5: + resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==} + dependencies: + '@types/react': 18.2.38 + hoist-non-react-statics: 3.3.2 + dev: false + /@types/http-cache-semantics@4.0.4: resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} @@ -6458,6 +6476,25 @@ packages: /asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + /asn1.js@5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + dependencies: + bn.js: 4.12.0 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + safer-buffer: 2.1.2 + dev: true + + /assert@2.1.0: + resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} + dependencies: + call-bind: 1.0.5 + is-nan: 1.3.2 + object-is: 1.1.5 + object.assign: 4.1.4 + util: 0.12.5 + dev: true + /assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} dev: true @@ -6865,6 +6902,12 @@ packages: run-parallel-limit: 1.1.0 dev: true + /browser-resolve@2.0.0: + resolution: {integrity: sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==} + dependencies: + resolve: 1.22.8 + dev: true + /browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} dev: true @@ -6880,6 +6923,51 @@ packages: safe-buffer: 5.2.1 dev: true + /browserify-cipher@1.0.1: + resolution: {integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==} + dependencies: + browserify-aes: 1.2.0 + browserify-des: 1.0.2 + evp_bytestokey: 1.0.3 + dev: true + + /browserify-des@1.0.2: + resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==} + dependencies: + cipher-base: 1.0.4 + des.js: 1.1.0 + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: true + + /browserify-rsa@4.1.0: + resolution: {integrity: sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==} + dependencies: + bn.js: 5.2.1 + randombytes: 2.1.0 + dev: true + + /browserify-sign@4.2.2: + resolution: {integrity: sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==} + engines: {node: '>= 4'} + dependencies: + bn.js: 5.2.1 + browserify-rsa: 4.1.0 + create-hash: 1.2.0 + create-hmac: 1.1.7 + elliptic: 6.5.4 + inherits: 2.0.4 + parse-asn1: 5.1.6 + readable-stream: 3.6.2 + safe-buffer: 5.2.1 + dev: true + + /browserify-zlib@0.2.0: + resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} + dependencies: + pako: 1.0.11 + dev: true + /browserslist@4.22.1: resolution: {integrity: sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -6938,6 +7026,10 @@ packages: ieee754: 1.2.1 dev: true + /builtin-status-codes@3.0.0: + resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} + dev: true + /builtins@5.0.1: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} dependencies: @@ -7468,6 +7560,10 @@ packages: /confusing-browser-globals@1.0.11: resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} + /console-browserify@1.2.0: + resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} + dev: true + /constant-case@3.0.4: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} dependencies: @@ -7475,6 +7571,10 @@ packages: tslib: 2.6.2 upper-case: 2.0.2 + /constants-browserify@1.0.0: + resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==} + dev: true + /content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -7577,6 +7677,13 @@ packages: hasBin: true dev: true + /create-ecdh@4.0.4: + resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} + dependencies: + bn.js: 4.12.0 + elliptic: 6.5.4 + dev: true + /create-gatsby@3.12.3: resolution: {integrity: sha512-N0K/Z/MD5LMRJcBy669WpSgrn+31zBV72Lv0RHolX0fXa77Yx58HsEiLWz83j/dtciGMQfEOEHFRetUqZhOggA==} hasBin: true @@ -7637,6 +7744,22 @@ packages: resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} dev: true + /crypto-browserify@3.12.0: + resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==} + dependencies: + browserify-cipher: 1.0.1 + browserify-sign: 4.2.2 + create-ecdh: 4.0.4 + create-hash: 1.2.0 + create-hmac: 1.1.7 + diffie-hellman: 5.0.3 + inherits: 2.0.4 + pbkdf2: 3.1.2 + public-encrypt: 4.0.3 + randombytes: 2.1.0 + randomfill: 1.0.4 + dev: true + /crypto-random-string@2.0.0: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} @@ -7872,6 +7995,11 @@ packages: /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + /deepmerge@2.2.1: + resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==} + engines: {node: '>=0.10.0'} + dev: false + /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -7945,6 +8073,13 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + /des.js@1.1.0: + resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + dev: true + /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -8021,6 +8156,14 @@ packages: engines: {node: '>=0.3.1'} dev: true + /diffie-hellman@5.0.3: + resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} + dependencies: + bn.js: 4.12.0 + miller-rabin: 4.0.1 + randombytes: 2.1.0 + dev: true + /difflib@0.2.4: resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} dependencies: @@ -8057,6 +8200,11 @@ packages: domhandler: 4.3.1 entities: 2.2.0 + /domain-browser@4.23.0: + resolution: {integrity: sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==} + engines: {node: '>=10'} + dev: true + /domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} @@ -8457,7 +8605,7 @@ packages: eslint: 8.54.0 dev: true - /eslint-config-react-app@6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.0)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.33.2)(eslint@7.32.0)(typescript@5.3.2): + /eslint-config-react-app@6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.33.2)(eslint@7.32.0)(typescript@5.3.2): resolution: {integrity: sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -8487,12 +8635,21 @@ packages: confusing-browser-globals: 1.0.11 eslint: 7.32.0 eslint-plugin-flowtype: 5.10.0(eslint@8.54.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.12.0)(eslint@8.54.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.12.0)(eslint@8.54.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.54.0) eslint-plugin-react: 7.33.2(eslint@8.54.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.54.0) typescript: 5.3.2 + /eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.29.1): + resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} + engines: {node: '>= 4'} + peerDependencies: + eslint-plugin-import: '>=1.4.0' + dependencies: + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.12.0)(eslint@8.54.0) + dev: true + /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: @@ -8573,6 +8730,41 @@ packages: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color + dev: true + + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.12.0)(eslint@8.54.0): + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 6.12.0(eslint@8.54.0)(typescript@5.3.2) + array-includes: 3.1.7 + array.prototype.findlastindex: 1.2.3 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.54.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.12.0)(eslint-import-resolver-node@0.3.9)(eslint@8.54.0) + hasown: 2.0.0 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.7 + object.groupby: 1.0.1 + object.values: 1.1.7 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color /eslint-plugin-jsx-a11y@6.8.0(eslint@8.54.0): resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==} @@ -8850,6 +9042,10 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -9424,6 +9620,22 @@ packages: combined-stream: 1.0.8 mime-types: 2.1.35 + /formik@2.4.5(react@18.2.0): + resolution: {integrity: sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ==} + peerDependencies: + react: '>=16.8.0' + dependencies: + '@types/hoist-non-react-statics': 3.3.5 + deepmerge: 2.2.1 + hoist-non-react-statics: 3.3.2 + lodash: 4.17.21 + lodash-es: 4.17.21 + react: 18.2.0 + react-fast-compare: 2.0.4 + tiny-warning: 1.0.3 + tslib: 2.6.2 + dev: false + /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -9923,9 +10135,9 @@ packages: enhanced-resolve: 5.15.0 error-stack-parser: 2.1.4 eslint: 7.32.0 - eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.0)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.33.2)(eslint@7.32.0)(typescript@5.3.2) + eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.33.2)(eslint@7.32.0)(typescript@5.3.2) eslint-plugin-flowtype: 5.10.0(eslint@8.54.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.12.0)(eslint@8.54.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.12.0)(eslint@8.54.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.54.0) eslint-plugin-react: 7.33.2(eslint@8.54.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.54.0) @@ -10638,6 +10850,10 @@ packages: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + /https-browserify@1.0.0: + resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} + dev: true + /https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -10790,6 +11006,14 @@ packages: is-relative: 1.0.0 is-windows: 1.0.2 + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: true + /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: @@ -10937,6 +11161,14 @@ packages: /is-map@2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + /is-nan@1.3.2: + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + dev: true + /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} @@ -11107,6 +11339,11 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} + /isomorphic-timers-promises@1.0.1: + resolution: {integrity: sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==} + engines: {node: '>=10'} + dev: true + /isomorphic-unfetch@3.1.0: resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==} dependencies: @@ -11425,6 +11662,10 @@ packages: /lock@1.1.0: resolution: {integrity: sha512-NZQIJJL5Rb9lMJ0Yl1JoVr9GSdo4HTPsUEWsSFzB8dE8DSoiLCVavWZPi7Rnlv/o73u6I24S/XYc/NmG4l8EKA==} + /lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + dev: false + /lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: true @@ -11549,6 +11790,13 @@ packages: resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} dev: true + /magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -11664,6 +11912,14 @@ packages: braces: 3.0.2 picomatch: 2.3.1 + /miller-rabin@4.0.1: + resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==} + hasBin: true + dependencies: + bn.js: 4.12.0 + brorand: 1.1.0 + dev: true + /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -11983,6 +12239,39 @@ packages: /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} + /node-stdlib-browser@1.2.0: + resolution: {integrity: sha512-VSjFxUhRhkyed8AtLwSCkMrJRfQ3e2lGtG3sP6FEgaLKBBbxM/dLfjRe1+iLhjvyLFW3tBQ8+c0pcOtXGbAZJg==} + engines: {node: '>=10'} + dependencies: + assert: 2.1.0 + browser-resolve: 2.0.0 + browserify-zlib: 0.2.0 + buffer: 5.7.1 + console-browserify: 1.2.0 + constants-browserify: 1.0.0 + create-require: 1.1.1 + crypto-browserify: 3.12.0 + domain-browser: 4.23.0 + events: 3.3.0 + https-browserify: 1.0.0 + isomorphic-timers-promises: 1.0.1 + os-browserify: 0.3.0 + path-browserify: 1.0.1 + pkg-dir: 5.0.0 + process: 0.11.10 + punycode: 1.4.1 + querystring-es3: 0.2.1 + readable-stream: 3.6.2 + stream-browserify: 3.0.0 + stream-http: 3.2.0 + string_decoder: 1.3.0 + timers-browserify: 2.0.12 + tty-browserify: 0.0.1 + url: 0.11.3 + util: 0.12.5 + vm-browserify: 1.1.2 + dev: true + /nofilter@3.1.0: resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} engines: {node: '>=12.19'} @@ -12079,6 +12368,14 @@ packages: /object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + /object-is@1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + dev: true + /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -12236,6 +12533,10 @@ packages: resolution: {integrity: sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==} dev: true + /os-browserify@0.3.0: + resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} + dev: true + /os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -12329,6 +12630,10 @@ packages: registry-url: 6.0.1 semver: 7.5.4 + /pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: true + /param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} dependencies: @@ -12341,6 +12646,16 @@ packages: dependencies: callsites: 3.1.0 + /parse-asn1@5.1.6: + resolution: {integrity: sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==} + dependencies: + asn1.js: 5.4.1 + browserify-aes: 1.2.0 + evp_bytestokey: 1.0.3 + pbkdf2: 3.1.2 + safe-buffer: 5.2.1 + dev: true + /parse-cache-control@1.0.1: resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} dev: true @@ -12388,6 +12703,10 @@ packages: ansi-escapes: 4.3.2 cross-spawn: 7.0.3 + /path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: true + /path-case@3.0.4: resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} dependencies: @@ -12479,6 +12798,13 @@ packages: dependencies: find-up: 4.1.0 + /pkg-dir@5.0.0: + resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} + engines: {node: '>=10'} + dependencies: + find-up: 5.0.0 + dev: true + /pkg-up@3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} @@ -12896,6 +13222,11 @@ packages: /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: true + /progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -12952,12 +13283,27 @@ packages: /pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + /public-encrypt@4.0.3: + resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} + dependencies: + bn.js: 4.12.0 + browserify-rsa: 4.1.0 + create-hash: 1.2.0 + parse-asn1: 5.1.6 + randombytes: 2.1.0 + safe-buffer: 5.2.1 + dev: true + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: end-of-stream: 1.4.4 once: 1.4.0 + /punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + dev: true + /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -12988,6 +13334,11 @@ packages: split-on-first: 1.1.0 strict-uri-encode: 2.0.0 + /querystring-es3@0.2.1: + resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} + engines: {node: '>=0.4.x'} + dev: true + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -13004,6 +13355,13 @@ packages: dependencies: safe-buffer: 5.2.1 + /randomfill@1.0.4: + resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} + dependencies: + randombytes: 2.1.0 + safe-buffer: 5.2.1 + dev: true + /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -13107,6 +13465,10 @@ packages: /react-error-overlay@6.0.11: resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==} + /react-fast-compare@2.0.4: + resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==} + dev: false + /react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} dev: false @@ -14117,6 +14479,22 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + /stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + + /stream-http@3.2.0: + resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==} + dependencies: + builtin-status-codes: 3.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + xtend: 4.0.2 + dev: true + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -14531,6 +14909,13 @@ packages: engines: {node: '>=14'} dev: true + /timers-browserify@2.0.12: + resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==} + engines: {node: '>=0.6.0'} + dependencies: + setimmediate: 1.0.5 + dev: true + /timers-ext@0.1.7: resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==} dependencies: @@ -14541,6 +14926,10 @@ packages: resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} dev: false + /tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + dev: false + /title-case@3.0.3: resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} dependencies: @@ -14662,6 +15051,15 @@ packages: json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 + dev: true + + /tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -14688,6 +15086,10 @@ packages: tslib: 1.14.1 typescript: 5.3.2 + /tty-browserify@0.0.1: + resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} + dev: true + /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} requiresBuild: true @@ -14958,6 +15360,13 @@ packages: schema-utils: 3.3.0 webpack: 5.89.0 + /url@0.11.3: + resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==} + dependencies: + punycode: 1.4.1 + qs: 6.11.2 + dev: true + /use-callback-ref@1.3.0(@types/react@18.2.38)(react@18.2.0): resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} engines: {node: '>=10'} @@ -14996,6 +15405,16 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + /util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + dependencies: + inherits: 2.0.4 + is-arguments: 1.1.1 + is-generator-function: 1.0.10 + is-typed-array: 1.1.12 + which-typed-array: 1.1.13 + dev: true + /utila@0.4.0: resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} @@ -15038,6 +15457,18 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + /vite-plugin-node-polyfills@0.19.0(vite@5.0.2): + resolution: {integrity: sha512-AhdVxAmVnd1doUlIRGUGV6ZRPfB9BvIwDF10oCOmL742IsvsFIAV4tSMxSfu5e0Px0QeJLgWVOSbtHIvblzqMw==} + peerDependencies: + vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + dependencies: + '@rollup/plugin-inject': 5.0.5 + node-stdlib-browser: 1.2.0 + vite: 5.0.2 + transitivePeerDependencies: + - rollup + dev: true + /vite@5.0.2: resolution: {integrity: sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g==} engines: {node: ^18.0.0 || >=20.0.0} @@ -15073,6 +15504,10 @@ packages: fsevents: 2.3.3 dev: true + /vm-browserify@1.1.2: + resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} + dev: true + /watchpack@2.4.0: resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} engines: {node: '>=10.13.0'}