diff --git a/bread.theme.ts b/bread.theme.ts index fecba90..62014a3 100644 --- a/bread.theme.ts +++ b/bread.theme.ts @@ -10,6 +10,7 @@ const colors = { "og-dark": "#2E2E2E", charcoal: "#242424", darkest: "#272727", + pitchblack: "#181818", "cta-bg": "#2c2c2c", grey300: "#222222", grey200: "#1c1c1c", @@ -23,6 +24,7 @@ const colors = { 300: "#E873D3", 400: "#A416AD", 500: "#D04EC5", + 600: "#F5E8F6", }, status: { danger: "#D8745C", diff --git a/contracts/script/Deploy.s.sol b/contracts/script/Deploy.s.sol index 5a7212c..ab11e41 100644 --- a/contracts/script/Deploy.s.sol +++ b/contracts/script/Deploy.s.sol @@ -23,7 +23,7 @@ contract Deploy is Script { // Distributor Config address _bread = address(0xa555d5344f6FB6c65da19e403Cb4c1eC4a1a5Ee3); uint256 _minRequiredVotingPower = 5; - uint256 _cycleLength = 518400; + uint256 _cycleLength = 60; uint256 _maxPoints = 10000; uint256 _precision = 1000000000000000000; uint256 _yieldFixedSplitDivisor = 2; diff --git a/package.json b/package.json index 77c76be..9428970 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "serve": "serve out", "deploy": "npm install -g pnpm@9.1.4 && pnpm install --prod=true --frozen-lockfile && pnpm run build", "chain:start": "anvil --fork-url https://rpc.gnosis.gateway.fm --chain-id 31337 --block-time 5", - "chain:setup": "tsx scripts/setup.ts && cd contracts && forge script script/Deploy.s.sol:Deploy --broadcast --rpc-url http://localhost:8545 --private-key 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 --legacy", + "chain:setup": "NODE_ENV=development tsx scripts/setupPreDeploy.ts && cd contracts && forge script script/Deploy.s.sol:Deploy --broadcast --rpc-url http://localhost:8545 --private-key 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 --legacy && cd .. && NODE_ENV=development tsx scripts/setupPostDeploy.ts", "lint": "next lint", "find-deadcode": "ts-prune | grep -v '(used in module)'" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fafa67b..620793e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9954,7 +9954,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -9977,8 +9977,8 @@ snapshots: debug: 4.3.4 enhanced-resolve: 5.15.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.3 is-core-module: 2.13.1 @@ -9989,7 +9989,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -10000,7 +10000,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.7 array.prototype.findlastindex: 1.2.4 @@ -10010,7 +10010,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) hasown: 2.0.1 is-core-module: 2.13.1 is-glob: 4.0.3 diff --git a/public/wxdai_bread_lp_icon.png b/public/wxdai_bread_lp_icon.png new file mode 100644 index 0000000..9c6e39b Binary files /dev/null and b/public/wxdai_bread_lp_icon.png differ diff --git a/public/wxdai_icon.png b/public/wxdai_icon.png new file mode 100644 index 0000000..e7b6598 Binary files /dev/null and b/public/wxdai_icon.png differ diff --git a/scripts/bakeDevBread.ts b/scripts/bakeDevBread.ts deleted file mode 100644 index bdd1c95..0000000 --- a/scripts/bakeDevBread.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { DEV_ACCOUNT, anvilAccounts, testClient } from "./lib"; -import { bakeBread, balanceOf } from "./lib"; - -/** - * - * Bakes BREAD for test wallets - * - */ - -export async function main() { - await bakeBread(DEV_ACCOUNT); - - await balanceOf(DEV_ACCOUNT); -} - -main(); diff --git a/scripts/checkYield.ts b/scripts/checkYield.ts deleted file mode 100644 index 97ce95a..0000000 --- a/scripts/checkYield.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createPublicClient, http } from "viem"; -import { BREAD_ABI } from "../src/abi"; -import { BREAD_ADDRESS } from "../src/chainConfig"; -import fs from "fs"; -import path from "path"; -import { gnosis } from "viem/chains"; - -const publicClient = createPublicClient({ - chain: gnosis, - transport: http(), -}); - -async function main() { - let lastVal; - - setInterval(async () => { - const res = await publicClient.readContract({ - address: BREAD_ADDRESS, - abi: BREAD_ABI, - functionName: "yieldAccrued", - }); - if (res !== lastVal) { - await fs.promises.appendFile( - path.join(__dirname, "vals.txt"), - `${new Date(Date.now()).toTimeString()} :: ${res}\n` - ); - lastVal = res; - } - - console.log(`yieldAccrued - ${res}`); - }, 1000); -} - -main(); diff --git a/scripts/createCheckpoint.ts b/scripts/createCheckpoint.ts deleted file mode 100644 index 4cb695c..0000000 --- a/scripts/createCheckpoint.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { BREAD_ABI } from "../src/abi"; -import { BREAD_ADDRESS } from "../src/chainConfig"; -import { publicClient, testClient } from "./lib"; - -async function createCheckpoint() { - await testClient.impersonateAccount({ - address: "0x458cD345B4C05e8DF39d0A07220feb4Ec19F5e6f", - }); - - try { - const hash = await testClient.writeContract({ - account: "0x458cD345B4C05e8DF39d0A07220feb4Ec19F5e6f", - address: BREAD_ADDRESS, - abi: BREAD_ABI, - functionName: "mint", - value: 1n, - args: ["0x458cd345b4c05e8df39d0a07220feb4ec19f5e6f"], - }); - - const transaction = await publicClient.waitForTransactionReceipt({ hash }); - - console.log(`baked 1 wei of bread`); - } catch (err) { - console.log(err); - } -} - -createCheckpoint(); diff --git a/scripts/fundWallet.js b/scripts/fundWallet.js deleted file mode 100644 index 3e7c1a0..0000000 --- a/scripts/fundWallet.js +++ /dev/null @@ -1,63 +0,0 @@ -const hre = require("hardhat"); - -const ABI = require("../src/abi/ERC20.json"); - -require("dotenv").config(); - -const contracts = { - DAI: { - symbol: "DAI", - decimals: 18, - address: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", - }, - BREAD: { - symbol: "BREAD", - decimals: 18, - address: "0x11d9efDf4Ab4A3bfabf5C7089F56AA4F059AA14C", - }, -}; - -const addressWithDAI = "0xd6b26861139a52877Cd7adc437Edd7c5383fF585"; - -const fundAccount = async ({ address, name }) => { - const sourceSigner = await hre.ethers.getImpersonatedSigner(addressWithDAI); - - const DAIcontract = new hre.ethers.Contract( - contracts.DAI.address, - ABI, - sourceSigner - ); - - await DAIcontract.transfer(address, hre.ethers.utils.parseEther("20000.00")); - - let DAIbalanceNEW = await DAIcontract.balanceOf(address); - const parsedDAIbalanceNEW = hre.ethers.utils.formatUnits(DAIbalanceNEW, 18); - - console.log( - `transfer complete -> ${name} wallet DAI balance: `, - parsedDAIbalanceNEW - ); -}; - -const main = async () => { - const accounts = [ - { - name: "hardhat1", - address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - }, - { - name: "hardhat2", - address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", - }, - { - name: "hardhat3", - address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", - }, - ]; - - setTimeout(async () => { - await fundAccount(accounts[0]); - }, 3000); -}; - -main(); diff --git a/scripts/getDistributions.ts b/scripts/getDistributions.ts deleted file mode 100644 index e1adbb6..0000000 --- a/scripts/getDistributions.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { createPublicClient, http, parseAbiItem } from "viem"; -import { foundry, gnosis } from "viem/chains"; - -import { getConfig } from "../src/chainConfig"; - -import { DISTRIBUTOR_ABI } from "../src/abi"; - -async function main() { - const publicClient = createPublicClient({ - chain: gnosis, - transport: http(), - }); - - const config = getConfig(100); - - // const logs = await publicClient.getContractEvents({ - // address: config.DISBURSER.address, - // abi: DISTRIBUTOR_ABI, - // // eventName: "YieldDistributed", - // fromBlock: BigInt(34695057), - // toBlock: "latest", - // }); - - const logs = await publicClient.getLogs({ - address: config.DISBURSER.address, - event: parseAbiItem("event YieldDistributed(uint256, uint256, uint256[])"), - // args: { - // from: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", - // to: "0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac", - // }, - fromBlock: BigInt(34695057), - toBlock: "latest", - }); - - return logs; -} - -main(); diff --git a/scripts/lib.ts b/scripts/lib.ts index b1103d7..4aa753b 100644 --- a/scripts/lib.ts +++ b/scripts/lib.ts @@ -1,7 +1,13 @@ -import { BREAD_ABI, DISTRIBUTOR_ABI, ERC20_ABI } from "../src/abi"; +import { + BREAD_ABI, + BUTTERED_BREAD_ABI, + DISTRIBUTOR_ABI, + ERC20_ABI, +} from "../src/abi"; import { getConfig } from "../src/chainConfig"; import { Hex, + TransactionReceipt, createPublicClient, createTestClient, http, @@ -11,7 +17,7 @@ import { } from "viem"; import { foundry } from "viem/chains"; -const config = getConfig(31337); +export const anvilConfig = getConfig(31337); export const anvilAccounts: Array = [ // mock wallet 2 @@ -27,8 +33,8 @@ export const anvilAccounts: Array = [ ]; export const DEV_ACCOUNT = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; -const LP_TOKEN_WHALE = "0xc2fB4B3EA53E10c88D193E709A81C4dc7aEC902e" as Hex; -const LP_TOKEN_CONTRACT = "0xf3d8f3de71657d342db60dd714c8a2ae37eac6b4" as Hex; +const BUTTER_WHALE = "0xc2fB4B3EA53E10c88D193E709A81C4dc7aEC902e" as Hex; +const BREAD_OWNER = "0x918dEf5d593F46735f74F9E2B280Fe51AF3A99ad" as Hex; // test client is useful as you can use it to send transactions // with any wallet without having the private key @@ -56,7 +62,7 @@ export async function bakeBread( try { const hash = await testClient.writeContract({ account: account, - address: config.BREAD.address, + address: anvilConfig.BREAD.address, abi: BREAD_ABI, functionName: "mint", // mint is a payable function so we pass the value like this @@ -83,22 +89,22 @@ export async function fundLpTokens(account: Hex = DEV_ACCOUNT) { // so it can pay the gas fees to send the LP tokens await testClient.sendTransaction({ account: account, - to: LP_TOKEN_WHALE, + to: BUTTER_WHALE, value: parseUnits("5", 18), }); // impersonate the account with the LP tokens await testClient.impersonateAccount({ - address: LP_TOKEN_WHALE, + address: BUTTER_WHALE, }); await testClient.writeContract({ - account: LP_TOKEN_WHALE, - address: LP_TOKEN_CONTRACT, + account: BUTTER_WHALE, + address: anvilConfig.BUTTER.address, abi: ERC20_ABI, functionName: "transfer", // this isn't a payable function so we pass the value as an argument - args: [DEV_ACCOUNT, parseUnits("1000", 18)], + args: [DEV_ACCOUNT, parseUnits("10000", 18)], }); } @@ -108,7 +114,7 @@ export async function balanceOf(anvilAccount: Hex) { }); const res = await publicClient.readContract({ - address: config.BREAD.address, + address: anvilConfig.BREAD.address, abi: BREAD_ABI, functionName: "balanceOf", args: [anvilAccount], @@ -123,24 +129,24 @@ function generateVote() { return vote; } -export async function submitVote(anvilAccount: Hex) { +export async function castVote(account: Hex = DEV_ACCOUNT) { await testClient.impersonateAccount({ - address: anvilAccount, + address: account, }); try { const hash = await testClient.writeContract({ - address: config.DISBURSER.address, + address: anvilConfig.DISBURSER.address, abi: DISTRIBUTOR_ABI, functionName: "castVote", - account: anvilAccount, + account: account, args: [ [ - generateVote(), - generateVote(), - generateVote(), - generateVote(), - generateVote(), + BigInt(generateVote()), + BigInt(generateVote()), + BigInt(generateVote()), + BigInt(generateVote()), + BigInt(generateVote()), ], ], }); @@ -148,32 +154,14 @@ export async function submitVote(anvilAccount: Hex) { const receipt = await publicClient.waitForTransactionReceipt({ hash }); if (receipt.status !== "success") { - console.log(`Failed to vote: ${anvilAccount} - `, Object.keys(receipt)); - // console.log({ receipt }); - - const transaction = await publicClient.getTransaction({ hash }); + console.log(`Failed to vote: ${account} - `, Object.keys(receipt)); - console.log({ transaction }); - - // const callData = { - // to: receipt.to, - // data: transaction.input, - // from: receipt.from, - // gas: receipt.gasUsed, - // }; - - await publicClient - .call({ data: transaction.input, blockNumber: receipt.blockNumber }) - .catch((error) => { - const revertReason = error.data; - console.log("Revert error:", error); - console.log("Revert reason:", revertReason); - }); + await logRevertReason(receipt); return; } - console.log(`Mock Bread Holder voted: ${anvilAccount} - ${receipt.status}`); + console.log(`vote cast: ${account} - ${receipt.status}`); } catch (err) { console.log(err); } @@ -181,10 +169,123 @@ export async function submitVote(anvilAccount: Hex) { export async function getCurrentDistribution() { const res = await publicClient.readContract({ - address: config.DISBURSER.address, + address: anvilConfig.DISBURSER.address, abi: DISTRIBUTOR_ABI, functionName: "getCurrentVotingDistribution", }); console.log(`Current distribution: - ${res}`); } + +export async function setClaimer(newClaimer: Hex) { + await testClient.impersonateAccount({ + address: BREAD_OWNER, + }); + + try { + const hash = await testClient.writeContract({ + address: anvilConfig.BREAD.address, + abi: BREAD_ABI, + functionName: "setYieldClaimer", + account: BREAD_OWNER, + args: [newClaimer], + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + if (receipt.status === "reverted") { + console.log("setYieldClaimer reverted: ", receipt); + } + console.log("setYieldClaimer: ", receipt.status); + } catch (err) { + console.log("failed to set claimer: ", err); + } +} + +export async function lockLpTokens(account: Hex = DEV_ACCOUNT) { + await testClient.impersonateAccount({ + address: account, + }); + + try { + const hash = await testClient.writeContract({ + account: account, + address: anvilConfig.BUTTER.address, + abi: ERC20_ABI, + functionName: "approve", + args: [anvilConfig.BUTTERED_BREAD.address, parseUnits("5000", 18)], + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + + if (receipt.status !== "success") { + await logRevertReason(receipt); + return; + } + console.log("allowance transaction: ", receipt.status); + } catch (err) { + console.log("allowance transaction failed: ", err); + } + + try { + const hash = await testClient.writeContract({ + account: account, + address: anvilConfig.BUTTERED_BREAD.address, + abi: BUTTERED_BREAD_ABI, + functionName: "deposit", + args: [anvilConfig.BUTTER.address, parseUnits("5000", 18)], + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + + if (receipt.status !== "success") { + await logRevertReason(receipt); + return; + } + console.log("deposit transaction: ", receipt.status); + } catch (err) { + console.log("deposit transaction failed: ", err); + } +} + +export async function distributeYield(account: Hex = DEV_ACCOUNT) { + await testClient.impersonateAccount({ + address: account, + }); + + try { + const hash = await testClient.writeContract({ + account: account, + address: anvilConfig.DISBURSER.address, + abi: DISTRIBUTOR_ABI, + functionName: "distributeYield", + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + + if (receipt.status !== "success") { + await logRevertReason(receipt); + return; + } + console.log("distributeYield transaction: ", receipt.status); + } catch (err) { + console.log("distributeYield transaction failed: ", err); + } +} + +export async function mineBlocks(blocks: number) { + await testClient.mine({ blocks }); +} + +async function logRevertReason(receipt: TransactionReceipt) { + const transaction = await publicClient.getTransaction({ + hash: receipt.transactionHash, + }); + + await publicClient + .call({ data: transaction.input, blockNumber: receipt.blockNumber }) + .catch((error) => { + const revertReason = error.data; + console.log("Revert error:", error); + console.log("Revert reason:", revertReason); + }); +} diff --git a/scripts/setClaimer.ts b/scripts/setClaimer.ts deleted file mode 100644 index cbc8929..0000000 --- a/scripts/setClaimer.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { setClaimer } from "./lib"; - -export async function main() { - await setClaimer(); -} - -main(); diff --git a/scripts/setupPostDeploy.ts b/scripts/setupPostDeploy.ts new file mode 100644 index 0000000..46a09e3 --- /dev/null +++ b/scripts/setupPostDeploy.ts @@ -0,0 +1,26 @@ +import { + setClaimer, + castVote, + lockLpTokens, + anvilConfig, + distributeYield, + mineBlocks, +} from "./lib"; + +async function main() { + const DISTRIBUTOR_ADDRESS = anvilConfig.DISBURSER.address; + if (DISTRIBUTOR_ADDRESS === "0x") + throw new Error("invalid DISTRIBUTOR_ADDRESS"); + + await lockLpTokens(); + + await setClaimer(DISTRIBUTOR_ADDRESS); + + await castVote(); + + await mineBlocks(60); + + await distributeYield(); +} + +main(); diff --git a/scripts/setup.ts b/scripts/setupPreDeploy.ts similarity index 93% rename from scripts/setup.ts rename to scripts/setupPreDeploy.ts index 1fdbc27..d1c81fb 100644 --- a/scripts/setup.ts +++ b/scripts/setupPreDeploy.ts @@ -1,4 +1,4 @@ -import { bakeBread, fundLpTokens } from "./lib"; +import { bakeBread, fundLpTokens, lockLpTokens } from "./lib"; /* This script sets a dev wallet up with bread and lp tokens diff --git a/scripts/submitVotes.ts b/scripts/submitVotes.ts deleted file mode 100644 index 16aef7a..0000000 --- a/scripts/submitVotes.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { anvilAccounts, getCurrentDistribution } from "./lib"; -import { submitVote } from "./lib"; - -async function main() { - // for (let i = 0; i < 15 * 24; i++) { - // await testClient.mine({ blocks: 535680 / 31 / 24 }); - // await testClient.increaseTime({ seconds: 60 * 60 }); - // } - - for (let i = 0; i < anvilAccounts.length; i++) { - await submitVote(anvilAccounts[i]); - } - - await getCurrentDistribution(); -} - -main(); diff --git a/src/abi/Bread.json b/src/abi/Bread.json deleted file mode 100644 index 4bbad96..0000000 --- a/src/abi/Bread.json +++ /dev/null @@ -1,686 +0,0 @@ -[ - { - "type": "constructor", - "inputs": [ - { "name": "_wxDai", "type": "address", "internalType": "address" }, - { "name": "_sexyDai", "type": "address", "internalType": "address" } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "CLOCK_MODE", - "inputs": [], - "outputs": [{ "name": "", "type": "string", "internalType": "string" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "allowance", - "inputs": [ - { "name": "owner", "type": "address", "internalType": "address" }, - { "name": "spender", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "approve", - "inputs": [ - { "name": "spender", "type": "address", "internalType": "address" }, - { "name": "value", "type": "uint256", "internalType": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "balanceOf", - "inputs": [ - { "name": "account", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "burn", - "inputs": [ - { "name": "amount", "type": "uint256", "internalType": "uint256" }, - { "name": "receiver", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "checkpoints", - "inputs": [ - { "name": "account", "type": "address", "internalType": "address" }, - { "name": "pos", "type": "uint32", "internalType": "uint32" } - ], - "outputs": [ - { - "name": "", - "type": "tuple", - "internalType": "struct Checkpoints.Checkpoint208", - "components": [ - { "name": "_key", "type": "uint48", "internalType": "uint48" }, - { "name": "_value", "type": "uint208", "internalType": "uint208" } - ] - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "claimYield", - "inputs": [ - { "name": "amount", "type": "uint256", "internalType": "uint256" }, - { "name": "receiver", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "clock", - "inputs": [], - "outputs": [{ "name": "", "type": "uint48", "internalType": "uint48" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "decimals", - "inputs": [], - "outputs": [{ "name": "", "type": "uint8", "internalType": "uint8" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "delegate", - "inputs": [ - { "name": "delegatee", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "delegateBySig", - "inputs": [ - { "name": "delegatee", "type": "address", "internalType": "address" }, - { "name": "nonce", "type": "uint256", "internalType": "uint256" }, - { "name": "expiry", "type": "uint256", "internalType": "uint256" }, - { "name": "v", "type": "uint8", "internalType": "uint8" }, - { "name": "r", "type": "bytes32", "internalType": "bytes32" }, - { "name": "s", "type": "bytes32", "internalType": "bytes32" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "delegates", - "inputs": [ - { "name": "account", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "address", "internalType": "address" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "eip712Domain", - "inputs": [], - "outputs": [ - { "name": "fields", "type": "bytes1", "internalType": "bytes1" }, - { "name": "name", "type": "string", "internalType": "string" }, - { "name": "version", "type": "string", "internalType": "string" }, - { "name": "chainId", "type": "uint256", "internalType": "uint256" }, - { - "name": "verifyingContract", - "type": "address", - "internalType": "address" - }, - { "name": "salt", "type": "bytes32", "internalType": "bytes32" }, - { "name": "extensions", "type": "uint256[]", "internalType": "uint256[]" } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getPastTotalSupply", - "inputs": [ - { "name": "timepoint", "type": "uint256", "internalType": "uint256" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getPastVotes", - "inputs": [ - { "name": "account", "type": "address", "internalType": "address" }, - { "name": "timepoint", "type": "uint256", "internalType": "uint256" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getVotes", - "inputs": [ - { "name": "account", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "initialize", - "inputs": [ - { "name": "name_", "type": "string", "internalType": "string" }, - { "name": "symbol_", "type": "string", "internalType": "string" }, - { "name": "owner_", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "mint", - "inputs": [ - { "name": "receiver", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "payable" - }, - { - "type": "function", - "name": "name", - "inputs": [], - "outputs": [{ "name": "", "type": "string", "internalType": "string" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "nonces", - "inputs": [ - { "name": "owner", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "numCheckpoints", - "inputs": [ - { "name": "account", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "uint32", "internalType": "uint32" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "owner", - "inputs": [], - "outputs": [{ "name": "", "type": "address", "internalType": "address" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "renounceOwnership", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "rescueToken", - "inputs": [ - { "name": "tok", "type": "address", "internalType": "address" }, - { "name": "amount", "type": "uint256", "internalType": "uint256" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setYieldClaimer", - "inputs": [ - { "name": "_yieldClaimer", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "sexyDai", - "inputs": [], - "outputs": [ - { "name": "", "type": "address", "internalType": "contract ISXDAI" } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "symbol", - "inputs": [], - "outputs": [{ "name": "", "type": "string", "internalType": "string" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "totalSupply", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "transfer", - "inputs": [ - { "name": "recipient", "type": "address", "internalType": "address" }, - { "name": "amount", "type": "uint256", "internalType": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "transferFrom", - "inputs": [ - { "name": "from", "type": "address", "internalType": "address" }, - { "name": "to", "type": "address", "internalType": "address" }, - { "name": "value", "type": "uint256", "internalType": "uint256" } - ], - "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "transferOwnership", - "inputs": [ - { "name": "newOwner", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "wxDai", - "inputs": [], - "outputs": [ - { "name": "", "type": "address", "internalType": "contract IWXDAI" } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "yieldAccrued", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "yieldClaimer", - "inputs": [], - "outputs": [{ "name": "", "type": "address", "internalType": "address" }], - "stateMutability": "view" - }, - { - "type": "event", - "name": "Approval", - "inputs": [ - { - "name": "owner", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "spender", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "value", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Burned", - "inputs": [ - { - "name": "receiver", - "type": "address", - "indexed": false, - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ClaimedYield", - "inputs": [ - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "DelegateChanged", - "inputs": [ - { - "name": "delegator", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "fromDelegate", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "toDelegate", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "DelegateVotesChanged", - "inputs": [ - { - "name": "delegate", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "previousVotes", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "newVotes", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "EIP712DomainChanged", - "inputs": [], - "anonymous": false - }, - { - "type": "event", - "name": "Initialized", - "inputs": [ - { - "name": "version", - "type": "uint64", - "indexed": false, - "internalType": "uint64" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Minted", - "inputs": [ - { - "name": "receiver", - "type": "address", - "indexed": false, - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "OwnershipTransferred", - "inputs": [ - { - "name": "previousOwner", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "newOwner", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Transfer", - "inputs": [ - { - "name": "from", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "to", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "value", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "YieldClaimerSet", - "inputs": [ - { - "name": "yieldClaimer", - "type": "address", - "indexed": false, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "error", - "name": "AddressEmptyCode", - "inputs": [ - { "name": "target", "type": "address", "internalType": "address" } - ] - }, - { - "type": "error", - "name": "AddressInsufficientBalance", - "inputs": [ - { "name": "account", "type": "address", "internalType": "address" } - ] - }, - { "type": "error", "name": "BurnZero", "inputs": [] }, - { "type": "error", "name": "CheckpointUnorderedInsertion", "inputs": [] }, - { "type": "error", "name": "ClaimZero", "inputs": [] }, - { "type": "error", "name": "ECDSAInvalidSignature", "inputs": [] }, - { - "type": "error", - "name": "ECDSAInvalidSignatureLength", - "inputs": [ - { "name": "length", "type": "uint256", "internalType": "uint256" } - ] - }, - { - "type": "error", - "name": "ECDSAInvalidSignatureS", - "inputs": [{ "name": "s", "type": "bytes32", "internalType": "bytes32" }] - }, - { - "type": "error", - "name": "ERC20ExceededSafeSupply", - "inputs": [ - { - "name": "increasedSupply", - "type": "uint256", - "internalType": "uint256" - }, - { "name": "cap", "type": "uint256", "internalType": "uint256" } - ] - }, - { - "type": "error", - "name": "ERC20InsufficientAllowance", - "inputs": [ - { "name": "spender", "type": "address", "internalType": "address" }, - { "name": "allowance", "type": "uint256", "internalType": "uint256" }, - { "name": "needed", "type": "uint256", "internalType": "uint256" } - ] - }, - { - "type": "error", - "name": "ERC20InsufficientBalance", - "inputs": [ - { "name": "sender", "type": "address", "internalType": "address" }, - { "name": "balance", "type": "uint256", "internalType": "uint256" }, - { "name": "needed", "type": "uint256", "internalType": "uint256" } - ] - }, - { - "type": "error", - "name": "ERC20InvalidApprover", - "inputs": [ - { "name": "approver", "type": "address", "internalType": "address" } - ] - }, - { - "type": "error", - "name": "ERC20InvalidReceiver", - "inputs": [ - { "name": "receiver", "type": "address", "internalType": "address" } - ] - }, - { - "type": "error", - "name": "ERC20InvalidSender", - "inputs": [ - { "name": "sender", "type": "address", "internalType": "address" } - ] - }, - { - "type": "error", - "name": "ERC20InvalidSpender", - "inputs": [ - { "name": "spender", "type": "address", "internalType": "address" } - ] - }, - { - "type": "error", - "name": "ERC5805FutureLookup", - "inputs": [ - { "name": "timepoint", "type": "uint256", "internalType": "uint256" }, - { "name": "clock", "type": "uint48", "internalType": "uint48" } - ] - }, - { "type": "error", "name": "ERC6372InconsistentClock", "inputs": [] }, - { "type": "error", "name": "FailedInnerCall", "inputs": [] }, - { - "type": "error", - "name": "InvalidAccountNonce", - "inputs": [ - { "name": "account", "type": "address", "internalType": "address" }, - { "name": "currentNonce", "type": "uint256", "internalType": "uint256" } - ] - }, - { "type": "error", "name": "InvalidInitialization", "inputs": [] }, - { "type": "error", "name": "IsCollateral", "inputs": [] }, - { "type": "error", "name": "MintZero", "inputs": [] }, - { "type": "error", "name": "MismatchAmount", "inputs": [] }, - { "type": "error", "name": "MismatchArray", "inputs": [] }, - { "type": "error", "name": "NativeTransferFailed", "inputs": [] }, - { "type": "error", "name": "NotInitializing", "inputs": [] }, - { "type": "error", "name": "OnlyClaimers", "inputs": [] }, - { - "type": "error", - "name": "OwnableInvalidOwner", - "inputs": [ - { "name": "owner", "type": "address", "internalType": "address" } - ] - }, - { - "type": "error", - "name": "OwnableUnauthorizedAccount", - "inputs": [ - { "name": "account", "type": "address", "internalType": "address" } - ] - }, - { - "type": "error", - "name": "SafeCastOverflowedUintDowncast", - "inputs": [ - { "name": "bits", "type": "uint8", "internalType": "uint8" }, - { "name": "value", "type": "uint256", "internalType": "uint256" } - ] - }, - { - "type": "error", - "name": "SafeERC20FailedOperation", - "inputs": [ - { "name": "token", "type": "address", "internalType": "address" } - ] - }, - { - "type": "error", - "name": "VotesExpiredSignature", - "inputs": [ - { "name": "expiry", "type": "uint256", "internalType": "uint256" } - ] - }, - { "type": "error", "name": "YieldInsufficient", "inputs": [] } -] diff --git a/src/abi/Bread.ts b/src/abi/Bread.ts new file mode 100644 index 0000000..1006ffd --- /dev/null +++ b/src/abi/Bread.ts @@ -0,0 +1,642 @@ +export const breadAbi = [ + { + type: "constructor", + inputs: [ + { name: "_wxDai", type: "address", internalType: "address" }, + { name: "_sexyDai", type: "address", internalType: "address" }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "CLOCK_MODE", + inputs: [], + outputs: [{ name: "", type: "string", internalType: "string" }], + stateMutability: "view", + }, + { + type: "function", + name: "allowance", + inputs: [ + { name: "owner", type: "address", internalType: "address" }, + { name: "spender", type: "address", internalType: "address" }, + ], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "approve", + inputs: [ + { name: "spender", type: "address", internalType: "address" }, + { name: "value", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "bool", internalType: "bool" }], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "balanceOf", + inputs: [{ name: "account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "burn", + inputs: [ + { name: "amount", type: "uint256", internalType: "uint256" }, + { name: "receiver", type: "address", internalType: "address" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "checkpoints", + inputs: [ + { name: "account", type: "address", internalType: "address" }, + { name: "pos", type: "uint32", internalType: "uint32" }, + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct Checkpoints.Checkpoint208", + components: [ + { name: "_key", type: "uint48", internalType: "uint48" }, + { name: "_value", type: "uint208", internalType: "uint208" }, + ], + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "claimYield", + inputs: [ + { name: "amount", type: "uint256", internalType: "uint256" }, + { name: "receiver", type: "address", internalType: "address" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "clock", + inputs: [], + outputs: [{ name: "", type: "uint48", internalType: "uint48" }], + stateMutability: "view", + }, + { + type: "function", + name: "decimals", + inputs: [], + outputs: [{ name: "", type: "uint8", internalType: "uint8" }], + stateMutability: "view", + }, + { + type: "function", + name: "delegate", + inputs: [{ name: "delegatee", type: "address", internalType: "address" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "delegateBySig", + inputs: [ + { name: "delegatee", type: "address", internalType: "address" }, + { name: "nonce", type: "uint256", internalType: "uint256" }, + { name: "expiry", type: "uint256", internalType: "uint256" }, + { name: "v", type: "uint8", internalType: "uint8" }, + { name: "r", type: "bytes32", internalType: "bytes32" }, + { name: "s", type: "bytes32", internalType: "bytes32" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "delegates", + inputs: [{ name: "account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "eip712Domain", + inputs: [], + outputs: [ + { name: "fields", type: "bytes1", internalType: "bytes1" }, + { name: "name", type: "string", internalType: "string" }, + { name: "version", type: "string", internalType: "string" }, + { name: "chainId", type: "uint256", internalType: "uint256" }, + { + name: "verifyingContract", + type: "address", + internalType: "address", + }, + { name: "salt", type: "bytes32", internalType: "bytes32" }, + { name: "extensions", type: "uint256[]", internalType: "uint256[]" }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getPastTotalSupply", + inputs: [{ name: "timepoint", type: "uint256", internalType: "uint256" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "getPastVotes", + inputs: [ + { name: "account", type: "address", internalType: "address" }, + { name: "timepoint", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "getVotes", + inputs: [{ name: "account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "initialize", + inputs: [ + { name: "name_", type: "string", internalType: "string" }, + { name: "symbol_", type: "string", internalType: "string" }, + { name: "owner_", type: "address", internalType: "address" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "mint", + inputs: [{ name: "receiver", type: "address", internalType: "address" }], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "name", + inputs: [], + outputs: [{ name: "", type: "string", internalType: "string" }], + stateMutability: "view", + }, + { + type: "function", + name: "nonces", + inputs: [{ name: "owner", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "numCheckpoints", + inputs: [{ name: "account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint32", internalType: "uint32" }], + stateMutability: "view", + }, + { + type: "function", + name: "owner", + inputs: [], + outputs: [{ name: "", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "renounceOwnership", + inputs: [], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "rescueToken", + inputs: [ + { name: "tok", type: "address", internalType: "address" }, + { name: "amount", type: "uint256", internalType: "uint256" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setYieldClaimer", + inputs: [ + { name: "_yieldClaimer", type: "address", internalType: "address" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "sexyDai", + inputs: [], + outputs: [{ name: "", type: "address", internalType: "contract ISXDAI" }], + stateMutability: "view", + }, + { + type: "function", + name: "symbol", + inputs: [], + outputs: [{ name: "", type: "string", internalType: "string" }], + stateMutability: "view", + }, + { + type: "function", + name: "totalSupply", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "transfer", + inputs: [ + { name: "recipient", type: "address", internalType: "address" }, + { name: "amount", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "bool", internalType: "bool" }], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferFrom", + inputs: [ + { name: "from", type: "address", internalType: "address" }, + { name: "to", type: "address", internalType: "address" }, + { name: "value", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "bool", internalType: "bool" }], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferOwnership", + inputs: [{ name: "newOwner", type: "address", internalType: "address" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "wxDai", + inputs: [], + outputs: [{ name: "", type: "address", internalType: "contract IWXDAI" }], + stateMutability: "view", + }, + { + type: "function", + name: "yieldAccrued", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "yieldClaimer", + inputs: [], + outputs: [{ name: "", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "event", + name: "Approval", + inputs: [ + { + name: "owner", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "spender", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "value", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Burned", + inputs: [ + { + name: "receiver", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ClaimedYield", + inputs: [ + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "DelegateChanged", + inputs: [ + { + name: "delegator", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "fromDelegate", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "toDelegate", + type: "address", + indexed: true, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "DelegateVotesChanged", + inputs: [ + { + name: "delegate", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "previousVotes", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "newVotes", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "EIP712DomainChanged", + inputs: [], + anonymous: false, + }, + { + type: "event", + name: "Initialized", + inputs: [ + { + name: "version", + type: "uint64", + indexed: false, + internalType: "uint64", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Minted", + inputs: [ + { + name: "receiver", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "OwnershipTransferred", + inputs: [ + { + name: "previousOwner", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "newOwner", + type: "address", + indexed: true, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Transfer", + inputs: [ + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "value", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "YieldClaimerSet", + inputs: [ + { + name: "yieldClaimer", + type: "address", + indexed: false, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "error", + name: "AddressEmptyCode", + inputs: [{ name: "target", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "AddressInsufficientBalance", + inputs: [{ name: "account", type: "address", internalType: "address" }], + }, + { type: "error", name: "BurnZero", inputs: [] }, + { type: "error", name: "CheckpointUnorderedInsertion", inputs: [] }, + { type: "error", name: "ClaimZero", inputs: [] }, + { type: "error", name: "ECDSAInvalidSignature", inputs: [] }, + { + type: "error", + name: "ECDSAInvalidSignatureLength", + inputs: [{ name: "length", type: "uint256", internalType: "uint256" }], + }, + { + type: "error", + name: "ECDSAInvalidSignatureS", + inputs: [{ name: "s", type: "bytes32", internalType: "bytes32" }], + }, + { + type: "error", + name: "ERC20ExceededSafeSupply", + inputs: [ + { + name: "increasedSupply", + type: "uint256", + internalType: "uint256", + }, + { name: "cap", type: "uint256", internalType: "uint256" }, + ], + }, + { + type: "error", + name: "ERC20InsufficientAllowance", + inputs: [ + { name: "spender", type: "address", internalType: "address" }, + { name: "allowance", type: "uint256", internalType: "uint256" }, + { name: "needed", type: "uint256", internalType: "uint256" }, + ], + }, + { + type: "error", + name: "ERC20InsufficientBalance", + inputs: [ + { name: "sender", type: "address", internalType: "address" }, + { name: "balance", type: "uint256", internalType: "uint256" }, + { name: "needed", type: "uint256", internalType: "uint256" }, + ], + }, + { + type: "error", + name: "ERC20InvalidApprover", + inputs: [{ name: "approver", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "ERC20InvalidReceiver", + inputs: [{ name: "receiver", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "ERC20InvalidSender", + inputs: [{ name: "sender", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "ERC20InvalidSpender", + inputs: [{ name: "spender", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "ERC5805FutureLookup", + inputs: [ + { name: "timepoint", type: "uint256", internalType: "uint256" }, + { name: "clock", type: "uint48", internalType: "uint48" }, + ], + }, + { type: "error", name: "ERC6372InconsistentClock", inputs: [] }, + { type: "error", name: "FailedInnerCall", inputs: [] }, + { + type: "error", + name: "InvalidAccountNonce", + inputs: [ + { name: "account", type: "address", internalType: "address" }, + { name: "currentNonce", type: "uint256", internalType: "uint256" }, + ], + }, + { type: "error", name: "InvalidInitialization", inputs: [] }, + { type: "error", name: "IsCollateral", inputs: [] }, + { type: "error", name: "MintZero", inputs: [] }, + { type: "error", name: "MismatchAmount", inputs: [] }, + { type: "error", name: "MismatchArray", inputs: [] }, + { type: "error", name: "NativeTransferFailed", inputs: [] }, + { type: "error", name: "NotInitializing", inputs: [] }, + { type: "error", name: "OnlyClaimers", inputs: [] }, + { + type: "error", + name: "OwnableInvalidOwner", + inputs: [{ name: "owner", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "OwnableUnauthorizedAccount", + inputs: [{ name: "account", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "SafeCastOverflowedUintDowncast", + inputs: [ + { name: "bits", type: "uint8", internalType: "uint8" }, + { name: "value", type: "uint256", internalType: "uint256" }, + ], + }, + { + type: "error", + name: "SafeERC20FailedOperation", + inputs: [{ name: "token", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "VotesExpiredSignature", + inputs: [{ name: "expiry", type: "uint256", internalType: "uint256" }], + }, + { type: "error", name: "YieldInsufficient", inputs: [] }, +] as const; diff --git a/src/abi/ButteredBread.ts b/src/abi/ButteredBread.ts new file mode 100644 index 0000000..166e741 --- /dev/null +++ b/src/abi/ButteredBread.ts @@ -0,0 +1,615 @@ +export const butteredBreadAbi = [ + { type: "constructor", inputs: [], stateMutability: "nonpayable" }, + { + type: "function", + name: "CLOCK_MODE", + inputs: [], + outputs: [{ name: "", type: "string", internalType: "string" }], + stateMutability: "view", + }, + { + type: "function", + name: "FIXED_POINT_PERCENT", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "accountToLPBalance", + inputs: [ + { name: "_account", type: "address", internalType: "address" }, + { name: "_lp", type: "address", internalType: "address" }, + ], + outputs: [{ name: "_lpBalance", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "allowance", + inputs: [ + { name: "owner", type: "address", internalType: "address" }, + { name: "spender", type: "address", internalType: "address" }, + ], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "allowlistedLPs", + inputs: [{ name: "lp", type: "address", internalType: "address" }], + outputs: [{ name: "allowed", type: "bool", internalType: "bool" }], + stateMutability: "view", + }, + { + type: "function", + name: "approve", + inputs: [ + { name: "spender", type: "address", internalType: "address" }, + { name: "value", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "bool", internalType: "bool" }], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "balanceOf", + inputs: [{ name: "account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "checkpoints", + inputs: [ + { name: "account", type: "address", internalType: "address" }, + { name: "pos", type: "uint32", internalType: "uint32" }, + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct Checkpoints.Checkpoint208", + components: [ + { name: "_key", type: "uint48", internalType: "uint48" }, + { name: "_value", type: "uint208", internalType: "uint208" }, + ], + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "clock", + inputs: [], + outputs: [{ name: "", type: "uint48", internalType: "uint48" }], + stateMutability: "view", + }, + { + type: "function", + name: "decimals", + inputs: [], + outputs: [{ name: "", type: "uint8", internalType: "uint8" }], + stateMutability: "view", + }, + { + type: "function", + name: "delegate", + inputs: [{ name: "delegatee", type: "address", internalType: "address" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "delegateBySig", + inputs: [ + { name: "delegatee", type: "address", internalType: "address" }, + { name: "nonce", type: "uint256", internalType: "uint256" }, + { name: "expiry", type: "uint256", internalType: "uint256" }, + { name: "v", type: "uint8", internalType: "uint8" }, + { name: "r", type: "bytes32", internalType: "bytes32" }, + { name: "s", type: "bytes32", internalType: "bytes32" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "delegates", + inputs: [{ name: "account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "deposit", + inputs: [ + { name: "_lp", type: "address", internalType: "address" }, + { name: "_amount", type: "uint256", internalType: "uint256" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "eip712Domain", + inputs: [], + outputs: [ + { name: "fields", type: "bytes1", internalType: "bytes1" }, + { name: "name", type: "string", internalType: "string" }, + { name: "version", type: "string", internalType: "string" }, + { name: "chainId", type: "uint256", internalType: "uint256" }, + { + name: "verifyingContract", + type: "address", + internalType: "address", + }, + { name: "salt", type: "bytes32", internalType: "bytes32" }, + { name: "extensions", type: "uint256[]", internalType: "uint256[]" }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getPastTotalSupply", + inputs: [{ name: "timepoint", type: "uint256", internalType: "uint256" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "getPastVotes", + inputs: [ + { name: "account", type: "address", internalType: "address" }, + { name: "timepoint", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "getVotes", + inputs: [{ name: "account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "initialize", + inputs: [ + { + name: "_initData", + type: "tuple", + internalType: "struct IButteredBread.InitData", + components: [ + { + name: "liquidityPools", + type: "address[]", + internalType: "address[]", + }, + { + name: "scalingFactors", + type: "uint256[]", + internalType: "uint256[]", + }, + { name: "name", type: "string", internalType: "string" }, + { name: "symbol", type: "string", internalType: "string" }, + ], + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "modifyAllowList", + inputs: [ + { name: "_lp", type: "address", internalType: "address" }, + { name: "_allowed", type: "bool", internalType: "bool" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "modifyScalingFactor", + inputs: [ + { name: "_lp", type: "address", internalType: "address" }, + { name: "_factor", type: "uint256", internalType: "uint256" }, + { name: "_holders", type: "address[]", internalType: "address[]" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "name", + inputs: [], + outputs: [{ name: "", type: "string", internalType: "string" }], + stateMutability: "view", + }, + { + type: "function", + name: "nonces", + inputs: [{ name: "owner", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "numCheckpoints", + inputs: [{ name: "account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint32", internalType: "uint32" }], + stateMutability: "view", + }, + { + type: "function", + name: "owner", + inputs: [], + outputs: [{ name: "", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "renounceOwnership", + inputs: [], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "scalingFactors", + inputs: [{ name: "lp", type: "address", internalType: "address" }], + outputs: [{ name: "factor", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "symbol", + inputs: [], + outputs: [{ name: "", type: "string", internalType: "string" }], + stateMutability: "view", + }, + { + type: "function", + name: "totalSupply", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "transfer", + inputs: [ + { name: "", type: "address", internalType: "address" }, + { name: "", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "bool", internalType: "bool" }], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferFrom", + inputs: [ + { name: "", type: "address", internalType: "address" }, + { name: "", type: "address", internalType: "address" }, + { name: "", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "bool", internalType: "bool" }], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferOwnership", + inputs: [{ name: "newOwner", type: "address", internalType: "address" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "withdraw", + inputs: [ + { name: "_lp", type: "address", internalType: "address" }, + { name: "_amount", type: "uint256", internalType: "uint256" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "event", + name: "AddButter", + inputs: [ + { + name: "_account", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "_lp", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "_amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Approval", + inputs: [ + { + name: "owner", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "spender", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "value", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "DelegateChanged", + inputs: [ + { + name: "delegator", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "fromDelegate", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "toDelegate", + type: "address", + indexed: true, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "DelegateVotesChanged", + inputs: [ + { + name: "delegate", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "previousVotes", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "newVotes", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "EIP712DomainChanged", + inputs: [], + anonymous: false, + }, + { + type: "event", + name: "Initialized", + inputs: [ + { + name: "version", + type: "uint64", + indexed: false, + internalType: "uint64", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "OwnershipTransferred", + inputs: [ + { + name: "previousOwner", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "newOwner", + type: "address", + indexed: true, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "RemoveButter", + inputs: [ + { + name: "_account", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "_lp", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "_amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Transfer", + inputs: [ + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "value", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { type: "error", name: "CheckpointUnorderedInsertion", inputs: [] }, + { type: "error", name: "ECDSAInvalidSignature", inputs: [] }, + { + type: "error", + name: "ECDSAInvalidSignatureLength", + inputs: [{ name: "length", type: "uint256", internalType: "uint256" }], + }, + { + type: "error", + name: "ECDSAInvalidSignatureS", + inputs: [{ name: "s", type: "bytes32", internalType: "bytes32" }], + }, + { + type: "error", + name: "ERC20ExceededSafeSupply", + inputs: [ + { + name: "increasedSupply", + type: "uint256", + internalType: "uint256", + }, + { name: "cap", type: "uint256", internalType: "uint256" }, + ], + }, + { + type: "error", + name: "ERC20InsufficientAllowance", + inputs: [ + { name: "spender", type: "address", internalType: "address" }, + { name: "allowance", type: "uint256", internalType: "uint256" }, + { name: "needed", type: "uint256", internalType: "uint256" }, + ], + }, + { + type: "error", + name: "ERC20InsufficientBalance", + inputs: [ + { name: "sender", type: "address", internalType: "address" }, + { name: "balance", type: "uint256", internalType: "uint256" }, + { name: "needed", type: "uint256", internalType: "uint256" }, + ], + }, + { + type: "error", + name: "ERC20InvalidApprover", + inputs: [{ name: "approver", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "ERC20InvalidReceiver", + inputs: [{ name: "receiver", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "ERC20InvalidSender", + inputs: [{ name: "sender", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "ERC20InvalidSpender", + inputs: [{ name: "spender", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "ERC5805FutureLookup", + inputs: [ + { name: "timepoint", type: "uint256", internalType: "uint256" }, + { name: "clock", type: "uint48", internalType: "uint48" }, + ], + }, + { type: "error", name: "ERC6372InconsistentClock", inputs: [] }, + { type: "error", name: "InsufficientFunds", inputs: [] }, + { + type: "error", + name: "InvalidAccountNonce", + inputs: [ + { name: "account", type: "address", internalType: "address" }, + { name: "currentNonce", type: "uint256", internalType: "uint256" }, + ], + }, + { type: "error", name: "InvalidInitialization", inputs: [] }, + { type: "error", name: "InvalidValue", inputs: [] }, + { type: "error", name: "NonTransferable", inputs: [] }, + { type: "error", name: "NotAllowListed", inputs: [] }, + { type: "error", name: "NotInitializing", inputs: [] }, + { + type: "error", + name: "OwnableInvalidOwner", + inputs: [{ name: "owner", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "OwnableUnauthorizedAccount", + inputs: [{ name: "account", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "SafeCastOverflowedUintDowncast", + inputs: [ + { name: "bits", type: "uint8", internalType: "uint8" }, + { name: "value", type: "uint256", internalType: "uint256" }, + ], + }, + { type: "error", name: "Unset", inputs: [] }, + { + type: "error", + name: "VotesExpiredSignature", + inputs: [{ name: "expiry", type: "uint256", internalType: "uint256" }], + }, +] as const; diff --git a/src/abi/Distributor.json b/src/abi/Distributor.json deleted file mode 100644 index 02f4f65..0000000 --- a/src/abi/Distributor.json +++ /dev/null @@ -1,448 +0,0 @@ -[ - { "type": "constructor", "inputs": [], "stateMutability": "nonpayable" }, - { - "type": "function", - "name": "BREAD", - "inputs": [], - "outputs": [ - { "name": "", "type": "address", "internalType": "contract Bread" } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "BUTTERED_BREAD", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract ERC20VotesUpgradeable" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "PRECISION", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "accountLastVoted", - "inputs": [{ "name": "", "type": "address", "internalType": "address" }], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "castVote", - "inputs": [ - { "name": "_points", "type": "uint256[]", "internalType": "uint256[]" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "currentVotes", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "cycleLength", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "distributeYield", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "getCurrentAccumulatedVotingPower", - "inputs": [ - { "name": "_account", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getCurrentVotingDistribution", - "inputs": [], - "outputs": [ - { "name": "", "type": "address[]", "internalType": "address[]" }, - { "name": "", "type": "uint256[]", "internalType": "uint256[]" } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getCurrentVotingPower", - "inputs": [ - { "name": "_account", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getVotingPowerForPeriod", - "inputs": [ - { - "name": "_sourceContract", - "type": "address", - "internalType": "contract ERC20VotesUpgradeable" - }, - { "name": "_start", "type": "uint256", "internalType": "uint256" }, - { "name": "_end", "type": "uint256", "internalType": "uint256" }, - { "name": "_account", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "initialize", - "inputs": [ - { "name": "_bread", "type": "address", "internalType": "address" }, - { - "name": "_butteredBread", - "type": "address", - "internalType": "address" - }, - { "name": "_precision", "type": "uint256", "internalType": "uint256" }, - { - "name": "_minRequiredVotingPower", - "type": "uint256", - "internalType": "uint256" - }, - { "name": "_maxPoints", "type": "uint256", "internalType": "uint256" }, - { "name": "_cycleLength", "type": "uint256", "internalType": "uint256" }, - { - "name": "_yieldFixedSplitDivisor", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "_lastClaimedBlockNumber", - "type": "uint256", - "internalType": "uint256" - }, - { "name": "_projects", "type": "address[]", "internalType": "address[]" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "lastClaimedBlockNumber", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "maxPoints", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "minRequiredVotingPower", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "owner", - "inputs": [], - "outputs": [{ "name": "", "type": "address", "internalType": "address" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "previousCycleStartingBlock", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "projectDistributions", - "inputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "projects", - "inputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "outputs": [{ "name": "", "type": "address", "internalType": "address" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "queueProjectAddition", - "inputs": [ - { "name": "_project", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "queueProjectRemoval", - "inputs": [ - { "name": "_project", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "queuedProjectsForAddition", - "inputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "outputs": [{ "name": "", "type": "address", "internalType": "address" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "queuedProjectsForRemoval", - "inputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "outputs": [{ "name": "", "type": "address", "internalType": "address" }], - "stateMutability": "view" - }, - { - "type": "function", - "name": "renounceOwnership", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "resolveYieldDistribution", - "inputs": [], - "outputs": [ - { "name": "", "type": "bool", "internalType": "bool" }, - { "name": "", "type": "bytes", "internalType": "bytes" } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "setButteredBread", - "inputs": [ - { "name": "_butteredBread", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setCycleLength", - "inputs": [ - { "name": "_cycleLength", "type": "uint256", "internalType": "uint256" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setMaxPoints", - "inputs": [ - { "name": "_maxPoints", "type": "uint256", "internalType": "uint256" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setMinRequiredVotingPower", - "inputs": [ - { - "name": "_minRequiredVotingPower", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setyieldFixedSplitDivisor", - "inputs": [ - { - "name": "_yieldFixedSplitDivisor", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "transferOwnership", - "inputs": [ - { "name": "newOwner", "type": "address", "internalType": "address" } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "yieldFixedSplitDivisor", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view" - }, - { - "type": "event", - "name": "BreadHolderVoted", - "inputs": [ - { - "name": "account", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "points", - "type": "uint256[]", - "indexed": false, - "internalType": "uint256[]" - }, - { - "name": "projects", - "type": "address[]", - "indexed": false, - "internalType": "address[]" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Initialized", - "inputs": [ - { - "name": "version", - "type": "uint64", - "indexed": false, - "internalType": "uint64" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "OwnershipTransferred", - "inputs": [ - { - "name": "previousOwner", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "newOwner", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ProjectAdded", - "inputs": [ - { - "name": "project", - "type": "address", - "indexed": false, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ProjectRemoved", - "inputs": [ - { - "name": "project", - "type": "address", - "indexed": false, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "YieldDistributed", - "inputs": [ - { - "name": "yield", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "totalVotes", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "projectDistributions", - "type": "uint256[]", - "indexed": false, - "internalType": "uint256[]" - } - ], - "anonymous": false - }, - { "type": "error", "name": "AlreadyMemberProject", "inputs": [] }, - { "type": "error", "name": "BelowMinRequiredVotingPower", "inputs": [] }, - { "type": "error", "name": "EndAfterCurrentBlock", "inputs": [] }, - { "type": "error", "name": "ExceedsMaxPoints", "inputs": [] }, - { "type": "error", "name": "IncorrectNumberOfProjects", "inputs": [] }, - { "type": "error", "name": "InvalidInitialization", "inputs": [] }, - { "type": "error", "name": "MustBeGreaterThanZero", "inputs": [] }, - { "type": "error", "name": "NotInitializing", "inputs": [] }, - { - "type": "error", - "name": "OwnableInvalidOwner", - "inputs": [ - { "name": "owner", "type": "address", "internalType": "address" } - ] - }, - { - "type": "error", - "name": "OwnableUnauthorizedAccount", - "inputs": [ - { "name": "account", "type": "address", "internalType": "address" } - ] - }, - { "type": "error", "name": "ProjectAlreadyQueued", "inputs": [] }, - { "type": "error", "name": "ProjectNotFound", "inputs": [] }, - { "type": "error", "name": "StartMustBeBeforeEnd", "inputs": [] }, - { "type": "error", "name": "YieldNotResolved", "inputs": [] }, - { "type": "error", "name": "ZeroVotePoints", "inputs": [] } -] diff --git a/src/abi/Distributor.ts b/src/abi/Distributor.ts new file mode 100644 index 0000000..e1a9179 --- /dev/null +++ b/src/abi/Distributor.ts @@ -0,0 +1,428 @@ +export const distributorAbi = [ + { type: "constructor", inputs: [], stateMutability: "nonpayable" }, + { + type: "function", + name: "BREAD", + inputs: [], + outputs: [{ name: "", type: "address", internalType: "contract Bread" }], + stateMutability: "view", + }, + { + type: "function", + name: "BUTTERED_BREAD", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract ERC20VotesUpgradeable", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "PRECISION", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "accountLastVoted", + inputs: [{ name: "", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "castVote", + inputs: [{ name: "_points", type: "uint256[]", internalType: "uint256[]" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "currentVotes", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "cycleLength", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "distributeYield", + inputs: [], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "getCurrentAccumulatedVotingPower", + inputs: [{ name: "_account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "getCurrentVotingDistribution", + inputs: [], + outputs: [ + { name: "", type: "address[]", internalType: "address[]" }, + { name: "", type: "uint256[]", internalType: "uint256[]" }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getCurrentVotingPower", + inputs: [{ name: "_account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "getVotingPowerForPeriod", + inputs: [ + { + name: "_sourceContract", + type: "address", + internalType: "contract ERC20VotesUpgradeable", + }, + { name: "_start", type: "uint256", internalType: "uint256" }, + { name: "_end", type: "uint256", internalType: "uint256" }, + { name: "_account", type: "address", internalType: "address" }, + ], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "initialize", + inputs: [ + { name: "_bread", type: "address", internalType: "address" }, + { + name: "_butteredBread", + type: "address", + internalType: "address", + }, + { name: "_precision", type: "uint256", internalType: "uint256" }, + { + name: "_minRequiredVotingPower", + type: "uint256", + internalType: "uint256", + }, + { name: "_maxPoints", type: "uint256", internalType: "uint256" }, + { name: "_cycleLength", type: "uint256", internalType: "uint256" }, + { + name: "_yieldFixedSplitDivisor", + type: "uint256", + internalType: "uint256", + }, + { + name: "_lastClaimedBlockNumber", + type: "uint256", + internalType: "uint256", + }, + { name: "_projects", type: "address[]", internalType: "address[]" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "lastClaimedBlockNumber", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "maxPoints", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "minRequiredVotingPower", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "owner", + inputs: [], + outputs: [{ name: "", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "previousCycleStartingBlock", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "projectDistributions", + inputs: [{ name: "", type: "uint256", internalType: "uint256" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "projects", + inputs: [{ name: "", type: "uint256", internalType: "uint256" }], + outputs: [{ name: "", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "queueProjectAddition", + inputs: [{ name: "_project", type: "address", internalType: "address" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "queueProjectRemoval", + inputs: [{ name: "_project", type: "address", internalType: "address" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "queuedProjectsForAddition", + inputs: [{ name: "", type: "uint256", internalType: "uint256" }], + outputs: [{ name: "", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "queuedProjectsForRemoval", + inputs: [{ name: "", type: "uint256", internalType: "uint256" }], + outputs: [{ name: "", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "renounceOwnership", + inputs: [], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "resolveYieldDistribution", + inputs: [], + outputs: [ + { name: "", type: "bool", internalType: "bool" }, + { name: "", type: "bytes", internalType: "bytes" }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "setButteredBread", + inputs: [ + { name: "_butteredBread", type: "address", internalType: "address" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setCycleLength", + inputs: [ + { name: "_cycleLength", type: "uint256", internalType: "uint256" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setMaxPoints", + inputs: [{ name: "_maxPoints", type: "uint256", internalType: "uint256" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setMinRequiredVotingPower", + inputs: [ + { + name: "_minRequiredVotingPower", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setyieldFixedSplitDivisor", + inputs: [ + { + name: "_yieldFixedSplitDivisor", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferOwnership", + inputs: [{ name: "newOwner", type: "address", internalType: "address" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "yieldFixedSplitDivisor", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "event", + name: "BreadHolderVoted", + inputs: [ + { + name: "account", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "points", + type: "uint256[]", + indexed: false, + internalType: "uint256[]", + }, + { + name: "projects", + type: "address[]", + indexed: false, + internalType: "address[]", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Initialized", + inputs: [ + { + name: "version", + type: "uint64", + indexed: false, + internalType: "uint64", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "OwnershipTransferred", + inputs: [ + { + name: "previousOwner", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "newOwner", + type: "address", + indexed: true, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ProjectAdded", + inputs: [ + { + name: "project", + type: "address", + indexed: false, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ProjectRemoved", + inputs: [ + { + name: "project", + type: "address", + indexed: false, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "YieldDistributed", + inputs: [ + { + name: "yield", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "totalVotes", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "projectDistributions", + type: "uint256[]", + indexed: false, + internalType: "uint256[]", + }, + ], + anonymous: false, + }, + { type: "error", name: "AlreadyMemberProject", inputs: [] }, + { type: "error", name: "BelowMinRequiredVotingPower", inputs: [] }, + { type: "error", name: "EndAfterCurrentBlock", inputs: [] }, + { type: "error", name: "ExceedsMaxPoints", inputs: [] }, + { type: "error", name: "IncorrectNumberOfProjects", inputs: [] }, + { type: "error", name: "InvalidInitialization", inputs: [] }, + { type: "error", name: "MustBeGreaterThanZero", inputs: [] }, + { type: "error", name: "NotInitializing", inputs: [] }, + { + type: "error", + name: "OwnableInvalidOwner", + inputs: [{ name: "owner", type: "address", internalType: "address" }], + }, + { + type: "error", + name: "OwnableUnauthorizedAccount", + inputs: [{ name: "account", type: "address", internalType: "address" }], + }, + { type: "error", name: "ProjectAlreadyQueued", inputs: [] }, + { type: "error", name: "ProjectNotFound", inputs: [] }, + { type: "error", name: "StartMustBeBeforeEnd", inputs: [] }, + { type: "error", name: "YieldNotResolved", inputs: [] }, + { type: "error", name: "ZeroVotePoints", inputs: [] }, +] as const; diff --git a/src/abi/ERC20.json b/src/abi/ERC20.ts similarity index 100% rename from src/abi/ERC20.json rename to src/abi/ERC20.ts diff --git a/src/abi/Multicall3.ts b/src/abi/Multicall3.ts new file mode 100644 index 0000000..5ab3d9e --- /dev/null +++ b/src/abi/Multicall3.ts @@ -0,0 +1,238 @@ +export const multicall3Abi = [ + { + inputs: [ + { + components: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + ], + internalType: "struct Multicall3.Call[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "aggregate", + outputs: [ + { internalType: "uint256", name: "blockNumber", type: "uint256" }, + { internalType: "bytes[]", name: "returnData", type: "bytes[]" }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bool", name: "allowFailure", type: "bool" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + ], + internalType: "struct Multicall3.Call3[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "aggregate3", + outputs: [ + { + components: [ + { internalType: "bool", name: "success", type: "bool" }, + { internalType: "bytes", name: "returnData", type: "bytes" }, + ], + internalType: "struct Multicall3.Result[]", + name: "returnData", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bool", name: "allowFailure", type: "bool" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + ], + internalType: "struct Multicall3.Call3Value[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "aggregate3Value", + outputs: [ + { + components: [ + { internalType: "bool", name: "success", type: "bool" }, + { internalType: "bytes", name: "returnData", type: "bytes" }, + ], + internalType: "struct Multicall3.Result[]", + name: "returnData", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + ], + internalType: "struct Multicall3.Call[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "blockAndAggregate", + outputs: [ + { internalType: "uint256", name: "blockNumber", type: "uint256" }, + { internalType: "bytes32", name: "blockHash", type: "bytes32" }, + { + components: [ + { internalType: "bool", name: "success", type: "bool" }, + { internalType: "bytes", name: "returnData", type: "bytes" }, + ], + internalType: "struct Multicall3.Result[]", + name: "returnData", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "getBasefee", + outputs: [{ internalType: "uint256", name: "basefee", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "blockNumber", type: "uint256" }], + name: "getBlockHash", + outputs: [{ internalType: "bytes32", name: "blockHash", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getBlockNumber", + outputs: [ + { internalType: "uint256", name: "blockNumber", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getChainId", + outputs: [{ internalType: "uint256", name: "chainid", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCurrentBlockCoinbase", + outputs: [{ internalType: "address", name: "coinbase", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCurrentBlockDifficulty", + outputs: [{ internalType: "uint256", name: "difficulty", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCurrentBlockGasLimit", + outputs: [{ internalType: "uint256", name: "gaslimit", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCurrentBlockTimestamp", + outputs: [{ internalType: "uint256", name: "timestamp", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "addr", type: "address" }], + name: "getEthBalance", + outputs: [{ internalType: "uint256", name: "balance", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getLastBlockHash", + outputs: [{ internalType: "bytes32", name: "blockHash", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bool", name: "requireSuccess", type: "bool" }, + { + components: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + ], + internalType: "struct Multicall3.Call[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "tryAggregate", + outputs: [ + { + components: [ + { internalType: "bool", name: "success", type: "bool" }, + { internalType: "bytes", name: "returnData", type: "bytes" }, + ], + internalType: "struct Multicall3.Result[]", + name: "returnData", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "bool", name: "requireSuccess", type: "bool" }, + { + components: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + ], + internalType: "struct Multicall3.Call[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "tryBlockAndAggregate", + outputs: [ + { internalType: "uint256", name: "blockNumber", type: "uint256" }, + { internalType: "bytes32", name: "blockHash", type: "bytes32" }, + { + components: [ + { internalType: "bool", name: "success", type: "bool" }, + { internalType: "bytes", name: "returnData", type: "bytes" }, + ], + internalType: "struct Multicall3.Result[]", + name: "returnData", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/src/abi/SDAIAdaptor.json b/src/abi/SDAIAdaptor.json deleted file mode 100644 index 150c296..0000000 --- a/src/abi/SDAIAdaptor.json +++ /dev/null @@ -1,140 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "address", - "name": "interestReceiver_", - "type": "address" - }, - { "internalType": "address payable", "name": "sDAI_", "type": "address" } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "assets", "type": "uint256" }, - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "deposit", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "depositXDAI", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "interestReceiver", - "outputs": [ - { - "internalType": "contract IBridgeInterestReceiver", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "shares", "type": "uint256" }, - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "mint", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "shares", "type": "uint256" }, - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "redeem", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "redeemAll", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "redeemAllXDAI", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "shares", "type": "uint256" }, - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "redeemXDAI", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "sDAI", - "outputs": [ - { "internalType": "contract SavingsXDai", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "vaultAPY", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "assets", "type": "uint256" }, - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "withdraw", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "assets", "type": "uint256" }, - { "internalType": "address", "name": "receiver", "type": "address" } - ], - "name": "withdrawXDAI", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "wxdai", - "outputs": [ - { "internalType": "contract IWXDAI", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { "stateMutability": "payable", "type": "receive" } -] diff --git a/src/abi/SDAIAdaptor.ts b/src/abi/SDAIAdaptor.ts new file mode 100644 index 0000000..9624ad9 --- /dev/null +++ b/src/abi/SDAIAdaptor.ts @@ -0,0 +1,132 @@ +export const sdaiAdaptorAbi = [ + { + inputs: [ + { + internalType: "address", + name: "interestReceiver_", + type: "address", + }, + { internalType: "address payable", name: "sDAI_", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [ + { internalType: "uint256", name: "assets", type: "uint256" }, + { internalType: "address", name: "receiver", type: "address" }, + ], + name: "deposit", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "receiver", type: "address" }], + name: "depositXDAI", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "interestReceiver", + outputs: [ + { + internalType: "contract IBridgeInterestReceiver", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "shares", type: "uint256" }, + { internalType: "address", name: "receiver", type: "address" }, + ], + name: "mint", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "shares", type: "uint256" }, + { internalType: "address", name: "receiver", type: "address" }, + ], + name: "redeem", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "receiver", type: "address" }], + name: "redeemAll", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "receiver", type: "address" }], + name: "redeemAllXDAI", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "shares", type: "uint256" }, + { internalType: "address", name: "receiver", type: "address" }, + ], + name: "redeemXDAI", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "sDAI", + outputs: [ + { internalType: "contract SavingsXDai", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "vaultAPY", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "assets", type: "uint256" }, + { internalType: "address", name: "receiver", type: "address" }, + ], + name: "withdraw", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "assets", type: "uint256" }, + { internalType: "address", name: "receiver", type: "address" }, + ], + name: "withdrawXDAI", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "wxdai", + outputs: [{ internalType: "contract IWXDAI", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { stateMutability: "payable", type: "receive" }, +] as const; diff --git a/src/abi/index.ts b/src/abi/index.ts index fde6a9f..32adba9 100644 --- a/src/abi/index.ts +++ b/src/abi/index.ts @@ -1,9 +1,13 @@ -import BREAD from "./Bread.json"; -import DISTRIBUTOR from "./Distributor.json"; -import ERC20 from "./ERC20.json"; -import SDAI_ADAPTOR from "./SDAIAdaptor.json"; +import { sdaiAdaptorAbi } from "./SDAIAdaptor"; +import { breadAbi } from "./Bread"; +import { distributorAbi } from "./Distributor"; +import { butteredBreadAbi } from "./ButteredBread"; +import { multicall3Abi } from "./Multicall3"; +import { erc20ABI } from "wagmi"; -export const BREAD_ABI = BREAD; -export const ERC20_ABI = ERC20; -export const DISTRIBUTOR_ABI = DISTRIBUTOR; -export const SDAI_ADAPTOR_ABI = SDAI_ADAPTOR; +export const BREAD_ABI = breadAbi; +export const ERC20_ABI = erc20ABI; +export const MULTICALL3_ABI = multicall3Abi; +export const DISTRIBUTOR_ABI = distributorAbi; +export const SDAI_ADAPTOR_ABI = sdaiAdaptorAbi; +export const BUTTERED_BREAD_ABI = butteredBreadAbi; diff --git a/src/app/app.css b/src/app/app.css index f602c7b..a2d3600 100644 --- a/src/app/app.css +++ b/src/app/app.css @@ -149,3 +149,15 @@ } } } + +.border-gradient { + background: linear-gradient( + 220deg, + #b72085 2%, + #cd19aa 28%, + #c463ca 55%, + #f5ac37 72%, + #f9c625 86%, + #ffe70f 100% + ); +} diff --git a/src/app/bakery/components/FAQ/FAQ.tsx b/src/app/bakery/components/FAQ/FAQ.tsx index c1b5416..9429516 100644 --- a/src/app/bakery/components/FAQ/FAQ.tsx +++ b/src/app/bakery/components/FAQ/FAQ.tsx @@ -31,7 +31,7 @@ function AccordionHeader({ children }: { children: ReactNode }) { function AccordionTrigger({ children }: { children: ReactNode }) { return ( - + {children}
{tokenBalance.status === "SUCCESS" && ( - + )} ); diff --git a/src/app/bakery/components/Swap/Swap.tsx b/src/app/bakery/components/Swap/Swap.tsx index 6654deb..137eb50 100644 --- a/src/app/bakery/components/Swap/Swap.tsx +++ b/src/app/bakery/components/Swap/Swap.tsx @@ -6,7 +6,6 @@ import { useChainModal } from "@rainbow-me/rainbowkit"; import { FromPanel } from "./FromPanel"; import SwapReverse from "../SwapReverse"; import ToPanel from "./ToPanel"; -import { sanitizeInputValue } from "../swapUtils"; import { useConnectedUser } from "@/app/core/hooks/useConnectedUser"; import Button from "@/app/core/components/Button"; import { AccountMenu } from "@/app/core/components/Header/AccountMenu"; @@ -17,6 +16,7 @@ import { Address } from "viem"; import { InsufficentBalance } from "./InsufficentBalance"; import { LiquidityBanner } from "../LiquidityBanner/LiquidityBanner"; import { TotalSupply } from "../TotalSupply"; +import { sanitizeInputValue } from "@/app/core/util/sanitizeInput"; export type TSwapMode = "BAKE" | "BURN"; diff --git a/src/app/bakery/components/TotalSupply.tsx b/src/app/bakery/components/TotalSupply.tsx index cbe13d9..8d4e8c3 100644 --- a/src/app/bakery/components/TotalSupply.tsx +++ b/src/app/bakery/components/TotalSupply.tsx @@ -10,7 +10,6 @@ export function TotalSupply() { address: "0xa555d5344f6FB6c65da19e403Cb4c1eC4a1a5Ee3", abi: ERC20_ABI, functionName: "totalSupply", - args: [], watch: true, cacheTime: 6_000, }); @@ -20,9 +19,7 @@ export function TotalSupply() {
- {data - ? formatSupply(parseInt(formatUnits(BigInt(data as string), 18))) - : "--.--"} + {data ? formatSupply(parseInt(formatUnits(data, 18))) : "--.--"} $BREAD strong diff --git a/src/app/bakery/hooks/useTokenBalance.ts b/src/app/bakery/hooks/useTokenBalance.ts index 4b7953e..f22ac71 100644 --- a/src/app/bakery/hooks/useTokenBalance.ts +++ b/src/app/bakery/hooks/useTokenBalance.ts @@ -10,7 +10,7 @@ export interface UseTokenBalanceResult { export function useTokenBalance( tokenAddress: Hex, - holderAddress: string + holderAddress: Hex ): UseTokenBalanceResult { const { data, status, error } = useContractRead({ address: tokenAddress, @@ -20,7 +20,7 @@ export function useTokenBalance( watch: true, }); - const value = data ? formatUnits(BigInt(data as string), 18).toString() : "0"; + const value = data ? formatUnits(data, 18).toString() : "0"; return { value, status, error }; } diff --git a/src/app/core/components/GradientBorder.tsx b/src/app/core/components/GradientBorder.tsx new file mode 100644 index 0000000..3961a02 --- /dev/null +++ b/src/app/core/components/GradientBorder.tsx @@ -0,0 +1,29 @@ +import clsx from "clsx"; +import { ReactNode } from "react"; + +const borderWidths: { + [key: number]: string; +} = { + 1: "p-[1px]", + 2: "p-[2px]", +}; + +export function GradientBorder({ + children, + borderWidth = 1, +}: { + children: ReactNode; + borderWidth?: 1 | 2; +}) { + return ( +
+
+
{children}
+
+ ); +} diff --git a/src/app/core/components/Header/DesktopNavigation.tsx b/src/app/core/components/Header/DesktopNavigation.tsx index 76aae45..308192a 100644 --- a/src/app/core/components/Header/DesktopNavigation.tsx +++ b/src/app/core/components/Header/DesktopNavigation.tsx @@ -52,7 +52,7 @@ function DesktopNavigation({ currentPath }: { currentPath: string }) { {features.governancePage === true && ( Governance diff --git a/src/app/core/components/Header/Header.tsx b/src/app/core/components/Header/Header.tsx index 638ee34..894a8c1 100644 --- a/src/app/core/components/Header/Header.tsx +++ b/src/app/core/components/Header/Header.tsx @@ -1,3 +1,4 @@ +"use client"; import clsx from "clsx"; import { type ReactNode, useState } from "react"; diff --git a/src/app/core/components/Icons/Bread.tsx b/src/app/core/components/Icons/Bread.tsx index 7d83610..ccb02b5 100644 --- a/src/app/core/components/Icons/Bread.tsx +++ b/src/app/core/components/Icons/Bread.tsx @@ -13,8 +13,8 @@ export function BreadSVG({ size = "regular" }: { size?: "small" | "regular" }) { className={size === "small" ? "w-4 h-4" : ""} > @@ -27,8 +27,8 @@ export function BreadSVG({ size = "regular" }: { size?: "small" | "regular" }) { y2="5.8642" gradientUnits="userSpaceOnUse" > - - + + diff --git a/src/app/core/components/Icons/FistIcon.tsx b/src/app/core/components/Icons/FistIcon.tsx new file mode 100644 index 0000000..ff966ff --- /dev/null +++ b/src/app/core/components/Icons/FistIcon.tsx @@ -0,0 +1,67 @@ +export function FistIcon() { + return ( + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/app/core/components/Icons/PowerIcon.tsx b/src/app/core/components/Icons/PowerIcon.tsx deleted file mode 100644 index c661ae5..0000000 --- a/src/app/core/components/Icons/PowerIcon.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useId } from "react"; -export function PowerIcon() { - const gradientId = useId(); - - return ( -
- - - - - - - - - -
- ); -} diff --git a/src/app/core/components/Icons/TokenIcons.tsx b/src/app/core/components/Icons/TokenIcons.tsx index 1eed2b8..9beb12c 100644 --- a/src/app/core/components/Icons/TokenIcons.tsx +++ b/src/app/core/components/Icons/TokenIcons.tsx @@ -10,15 +10,19 @@ export function XDAIIcon() { ); } -// export function BreadIcon() { -// return ( -//
-//
-// -//
-//
-// ); -// } +export function WXDAIIcon({ size = "regular" }: { size?: "regular" | "full" }) { + return ( +
+ wxdai icon +
+ ); +} export function BreadIcon({ size = "regular", diff --git a/src/app/core/components/Icons/index.tsx b/src/app/core/components/Icons/index.tsx index b8dbd8c..c970f79 100644 --- a/src/app/core/components/Icons/index.tsx +++ b/src/app/core/components/Icons/index.tsx @@ -1,5 +1,4 @@ export * from "./IconContainer"; export * from "./NetworkIcon"; export * from "./WalletIcon"; -export * from "./PowerIcon"; export * from "./TooltipIcon"; diff --git a/src/app/core/components/MaxButton.tsx b/src/app/core/components/MaxButton.tsx new file mode 100644 index 0000000..2b1a0cb --- /dev/null +++ b/src/app/core/components/MaxButton.tsx @@ -0,0 +1,19 @@ +import { ReactNode } from "react"; + +export function MaxButton({ + children, + onClick, +}: { + children: ReactNode; + onClick: () => void; +}) { + return ( + + ); +} diff --git a/src/app/core/components/Modal/LPModalUI.tsx b/src/app/core/components/Modal/LPModalUI.tsx new file mode 100644 index 0000000..b60f87f --- /dev/null +++ b/src/app/core/components/Modal/LPModalUI.tsx @@ -0,0 +1,169 @@ +import { forwardRef, Ref, type ReactNode } from "react"; +import { Close as DialogPrimitiveClose } from "@radix-ui/react-dialog"; + +import CloseIcon from "../Icons/CloseIcon"; +import { formatBalance } from "../../util/formatter"; +import { motion } from "framer-motion"; +import { Spinner } from "../Icons/Spinner"; +import { TTransactionStatus } from "../../context/TransactionsContext/TransactionsReducer"; + +export const ModalContainer = forwardRef( + ( + { children, ...props }: { children: ReactNode }, + ref: Ref + ) => { + return ( +
+ + + + + {children} + +
+ ); + } +); + +ModalContainer.displayName = "LPModalContainer"; + +export function ModalHeading({ children }: { children: ReactNode }) { + return ( +
+

+ {children} +

+
+ ); +} + +export function ModalContent({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +export function ModalAdviceText({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +export function TransactionValue({ value }: { value: string }) { + return ( +
+ {formatBalance(parseFloat(value), 2)} +
+ ); +} + +export const ModalOverlay = forwardRef((props, ref: Ref) => { + return ( +
+ +
+ ); +}); + +ModalOverlay.displayName = "ModalOverlay"; + +export function TransactionStatusSpinner() { + return ( + + + + ); +} + +export function TransactionStatusCheck() { + return ( + + + + + + ); +} + +export function TransactionStatusCross() { + return ( + + + + + + ); +} + +function StatusIconWrapper({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +export const transactionIcons: { + [key in TTransactionStatus]: JSX.Element; +} & { PREPARED: JSX.Element } = { + PREPARED: , + SUBMITTED: , + CONFIRMED: , + SAFE_SUBMITTED: , + REVERTED: , +}; + +export function StatusMessage({ children }: { children: ReactNode }) { + return

{children}

; +} + +export function StatusMessageSmall({ children }: { children: ReactNode }) { + return ( +

{children}

+ ); +} diff --git a/src/app/core/components/Modal/LPVaultTransactionModal/LPVaultTransactionModal.tsx b/src/app/core/components/Modal/LPVaultTransactionModal/LPVaultTransactionModal.tsx new file mode 100644 index 0000000..e9d0543 --- /dev/null +++ b/src/app/core/components/Modal/LPVaultTransactionModal/LPVaultTransactionModal.tsx @@ -0,0 +1,36 @@ +import { useEffect } from "react"; +import { + LPVaultTransactionModalState, + useModal, +} from "../../../context/ModalContext"; +import { ModalContainer } from "../LPModalUI"; +import { useConnectedUser } from "../../../hooks/useConnectedUser"; +import { LockingTransaction } from "./Locking/LockingTransaction"; +import { WithdrawTransaction } from "./WithdrawTransaction"; + +export function LPVaultTransactionModal({ + modalState, +}: { + modalState: LPVaultTransactionModalState; +}) { + const { user } = useConnectedUser(); + + const { setModal } = useModal(); + + useEffect(() => { + if (user.status !== "CONNECTED") { + setModal(null); + } + }, [user, setModal]); + + return ( + + {user.status === "CONNECTED" && + (modalState.transactionType === "LOCK" ? ( + + ) : ( + + ))} + + ); +} diff --git a/src/app/core/components/Modal/LPVaultTransactionModal/Locking/IncreaseAllowance.tsx b/src/app/core/components/Modal/LPVaultTransactionModal/Locking/IncreaseAllowance.tsx new file mode 100644 index 0000000..047a881 --- /dev/null +++ b/src/app/core/components/Modal/LPVaultTransactionModal/Locking/IncreaseAllowance.tsx @@ -0,0 +1,115 @@ +import { useEffect, useState } from "react"; +import { useContractWrite, usePrepareContractWrite } from "wagmi"; + +import Button from "@/app/core/components/Button"; +import { TUserConnected } from "@/app/core/hooks/useConnectedUser"; +import { LockingAllowance, LockingEvent } from "./lockingReducer"; +import { useTransactions } from "@/app/core/context/TransactionsContext/TransactionsContext"; +import { getConfig } from "@/chainConfig"; +import { ERC20_ABI } from "@/abi"; + +export function IncreaseAllowance({ + user, + lockingState, + lockingDispatch, +}: { + user: TUserConnected; + lockingState: LockingAllowance; + lockingDispatch: (value: LockingEvent) => void; +}) { + const { transactionsDispatch, transactionsState } = useTransactions(); + const [isWalletOpen, setIsWalletOpen] = useState(false); + const chainConfig = getConfig(user.chain.id); + + useEffect(() => { + transactionsDispatch({ + type: "NEW", + payload: { + data: { type: "LP_VAULT_ALLOWANCE", transactionType: "LOCK" }, + }, + }); + }, [transactionsDispatch]); + + const { + status: prepareWriteStatus, + error: prepareWriteError, + config: prepareWriteConfig, + } = usePrepareContractWrite({ + address: chainConfig.BUTTER.address, + abi: ERC20_ABI, + functionName: "approve", + args: [ + chainConfig.BUTTERED_BREAD.address, + lockingState.depositAmount - lockingState.allowance, + ], + }); + + useEffect(() => { + if (prepareWriteStatus === "error") { + console.log(prepareWriteError); + } + }, [prepareWriteStatus, prepareWriteError]); + + const { + write: contractWriteWrite, + status: contractWriteStatus, + data: contractWriteData, + } = useContractWrite(prepareWriteConfig); + + useEffect(() => { + if (contractWriteStatus === "success" && contractWriteData) { + transactionsDispatch({ + type: "SET_SUBMITTED", + payload: { hash: contractWriteData.hash }, + }); + lockingDispatch({ + type: "TRANSACTION_SUBMITTED", + payload: { hash: contractWriteData.hash }, + }); + } + if (contractWriteStatus === "error") { + setIsWalletOpen(false); + } + }, [ + transactionsDispatch, + contractWriteStatus, + contractWriteData, + lockingDispatch, + ]); + + useEffect(() => { + if (lockingState.status !== "allowance_transaction_submitted") return; + const tx = transactionsState.submitted.find( + (t) => t.hash === lockingState.txHash + ); + if (tx?.status === "REVERTED") { + lockingDispatch({ type: "TRANSACTION_REVERTED" }); + } + }, [transactionsState, lockingState, lockingDispatch]); + + if (lockingState.status === "allowance_transaction_submitted") { + return ( + + ); + } + + if (lockingState.status === "allowance_transaction_reverted") { + return
reverted!
; + } + + return ( + + ); +} diff --git a/src/app/core/components/Modal/LPVaultTransactionModal/Locking/Lock.tsx b/src/app/core/components/Modal/LPVaultTransactionModal/Locking/Lock.tsx new file mode 100644 index 0000000..e72cee6 --- /dev/null +++ b/src/app/core/components/Modal/LPVaultTransactionModal/Locking/Lock.tsx @@ -0,0 +1,174 @@ +import { useEffect, useState } from "react"; +import Button from "@/app/core/components/Button"; +import { TUserConnected } from "@/app/core/hooks/useConnectedUser"; +import { LockingDeposit, LockingEvent } from "./lockingReducer"; +import { useTransactions } from "@/app/core/context/TransactionsContext/TransactionsContext"; +import { getConfig } from "@/chainConfig"; +import { useContractWrite, usePrepareContractWrite } from "wagmi"; +import { BUTTERED_BREAD_ABI } from "@/abi"; +import { useModal } from "@/app/core/context/ModalContext"; +import { formatUnits } from "viem"; + +import { LinkIcon } from "../../../Icons/LinkIcon"; +import { LockVPRate } from "../VPRate"; +import { ExternalLink } from "@/app/core/components/ExternalLink"; + +export function Lock({ + user, + lockingState, + lockingDispatch, +}: { + user: TUserConnected; + lockingState: LockingDeposit; + lockingDispatch: (value: LockingEvent) => void; +}) { + const { transactionsState, transactionsDispatch } = useTransactions(); + const [isWalletOpen, setIsWalletOpen] = useState(false); + const chainConfig = getConfig(user.chain.id); + const { setModal } = useModal(); + + useEffect(() => { + transactionsDispatch({ + type: "NEW", + payload: { + data: { type: "LP_VAULT_DEPOSIT", transactionType: "LOCK" }, + }, + }); + }, [transactionsDispatch]); + + const { + status: prepareWriteStatus, + error: prepareWriteError, + config: prepareWriteConfig, + } = usePrepareContractWrite({ + address: chainConfig.BUTTERED_BREAD.address, + abi: BUTTERED_BREAD_ABI, + functionName: "deposit", + args: [chainConfig.BUTTER.address, lockingState.depositAmount], + }); + + useEffect(() => { + if (prepareWriteStatus === "error") { + console.log({ prepareWriteError }); + } + }, [prepareWriteStatus, prepareWriteError]); + + const { + write: contractWriteWrite, + status: contractWriteStatus, + data: contractWriteData, + } = useContractWrite(prepareWriteConfig); + + useEffect(() => { + if (contractWriteStatus === "success" && contractWriteData) { + transactionsDispatch({ + type: "SET_SUBMITTED", + payload: { hash: contractWriteData.hash }, + }); + lockingDispatch({ + type: "TRANSACTION_SUBMITTED", + payload: { hash: contractWriteData.hash }, + }); + setIsWalletOpen(false); + } + if (contractWriteStatus === "error") { + setIsWalletOpen(false); + } + }, [ + contractWriteStatus, + contractWriteData, + transactionsDispatch, + lockingDispatch, + ]); + + useEffect(() => { + if (lockingState.status !== "deposit_transaction_submitted") return; + const tx = transactionsState.submitted.find( + (t) => t.hash === lockingState.txHash + ); + if (tx?.status === "REVERTED") { + lockingDispatch({ type: "TRANSACTION_REVERTED" }); + } + if (tx?.status === "CONFIRMED") { + console.log("deposit transaction confirmed!!"); + lockingDispatch({ type: "TRANSACTION_CONFIRMED" }); + } + }, [transactionsState, lockingState, lockingDispatch]); + + if (lockingState.status === "deposit_transaction_confirmed") { + return ( + <> + + + + ); + } + + if (lockingState.status === "deposit_transaction_reverted") { + return
reverted!
; + } + + if (lockingState.status === "deposit_transaction_submitted") { + return ( + + ); + } + + return ( + + ); +} + +function LockSuccess({ + value, + explorerLink, +}: { + value: bigint; + explorerLink: string; +}) { + const tokenAmount = formatUnits(value, 18); + const vpAmount = tokenAmount; + + return ( +
+

+ You successfully locked {tokenAmount} LP tokens. In the + next voting cycles you will have a{" "} + voting power of {vpAmount}. +

+ +

+ You can unlock your LP tokens anytime. +

+ +
+ View on Explorer + +
+
+
+ ); +} diff --git a/src/app/core/components/Modal/LPVaultTransactionModal/Locking/LockingTransaction.tsx b/src/app/core/components/Modal/LPVaultTransactionModal/Locking/LockingTransaction.tsx new file mode 100644 index 0000000..d89fc91 --- /dev/null +++ b/src/app/core/components/Modal/LPVaultTransactionModal/Locking/LockingTransaction.tsx @@ -0,0 +1,226 @@ +import { ReactNode, useEffect, useReducer } from "react"; +import { useContractRead } from "wagmi"; +import clsx from "clsx"; + +import { getConfig } from "@/chainConfig"; +import { ERC20_ABI } from "@/abi"; +import { TUserConnected } from "@/app/core/hooks/useConnectedUser"; +import { LPVaultTransactionModalState } from "@/app/core/context/ModalContext"; +import { + ModalContent, + ModalHeading, + StatusMessage, + StatusMessageSmall, +} from "../../LPModalUI"; +import { lockingReducer } from "./lockingReducer"; +import { CheckIcon } from "../../../Icons/CheckIcon"; +import { IncreaseAllowance } from "./IncreaseAllowance"; +import { Lock } from "./Lock"; +import { formatUnits } from "viem"; +import { LockVPRate } from "../VPRate"; +import { Spinner } from "../../../Icons/Spinner"; + +export function LockingTransaction({ + user, + modalState, +}: { + user: TUserConnected; + modalState: LPVaultTransactionModalState; +}) { + const chainConfig = getConfig(user.chain.id); + const [lockingState, lockingDispatch] = useReducer(lockingReducer, { + depositAmount: modalState.parsedValue, + status: "loading", + }); + + const { status: userAllowanceStatus, data: userAllowanceData } = + useContractRead({ + address: chainConfig.BUTTER.address, + abi: ERC20_ABI, + functionName: "allowance", + args: [user.address, chainConfig.BUTTERED_BREAD.address], + watch: true, + }); + + useEffect(() => { + if (userAllowanceStatus === "success") { + lockingDispatch({ + type: "ALLOWANCE_UPDATE", + payload: { allowance: userAllowanceData as bigint }, + }); + } + }, [userAllowanceStatus, userAllowanceData]); + + return ( + <> + Locking LP Tokens + + + + {lockingState.status === "allowance_transaction_idle" && ( + + Press ‘Confirm transaction’ to allow LP tokens to be locked. + + )} +
+ + 1. Token allowance + + + {lockingState.status === "allowance_transaction_idle" && + "Please confirm the transaction"} + {lockingState.status === "allowance_transaction_submitted" && + "Confirm the transaction..."} + {lockingState.status.includes("deposit") && + "Token allowance granted!"} + +
+ +
+ + 2. Token locking + + + {lockingState.status.includes("allowance") && + "Waiting for next action..."} + {lockingState.status === "deposit_transaction_idle" && + "Lock your LP tokens"} + {lockingState.status === "deposit_transaction_submitted" && + "Locking your LP tokens..."} + {lockingState.status === "deposit_transaction_confirmed" && + formatUnits(lockingState.depositAmount, 18) + + " LP tokens successfully locked!"} + +
+ {lockingState.status !== "deposit_transaction_confirmed" && ( + + )} + {(() => { + if (lockingState.status === "loading") { + return "loading...."; + } + if ( + lockingState.status === "allowance_transaction_idle" || + lockingState.status === "allowance_transaction_submitted" || + lockingState.status === "allowance_transaction_reverted" + ) { + return ( + + ); + } + if ( + lockingState.status === "deposit_transaction_idle" || + lockingState.status === "deposit_transaction_submitted" || + lockingState.status === "deposit_transaction_confirmed" || + lockingState.status === "deposit_transaction_reverted" + ) { + return ( + + ); + } + })()} +
+ + ); +} + +export function StatusBadge({ + variant, +}: { + variant: "complete" | "in-progress"; +}) { + return ( + + {variant === "in-progress" && "In progress"} + {variant === "complete" && "Complete"} + + ); +} + +type TransactionStatus = "disabled" | "pending" | "success"; + +const stageIcons: { + [key in TransactionStatus]: ReactNode; +} = { + disabled: , + pending: , + success: , +}; + +function TransactionStage({ + status, + children, +}: { + status: TransactionStatus; + children: ReactNode; +}) { + return ( +
+ {stageIcons[status]} +
{children}
+
+ ); +} + +function SuccessIcon() { + return ( + +
+ +
+
+ ); +} + +function DisabledIcon() { + return ( + + ); +} + +function PendingIcon() { + return ( +
+ +
+ ); +} diff --git a/src/app/core/components/Modal/LPVaultTransactionModal/Locking/lockingReducer.tsx b/src/app/core/components/Modal/LPVaultTransactionModal/Locking/lockingReducer.tsx new file mode 100644 index 0000000..bd61d9b --- /dev/null +++ b/src/app/core/components/Modal/LPVaultTransactionModal/Locking/lockingReducer.tsx @@ -0,0 +1,181 @@ +import { Hex } from "viem"; + +/** + * STATES + */ +export type LockingLoading = { + depositAmount: bigint; + status: "loading"; +}; + +export type LockingAllowanceIdle = { + depositAmount: bigint; + allowance: bigint; + status: "allowance_transaction_idle"; +}; + +export type LockingAllowanceSubmitted = { + depositAmount: bigint; + allowance: bigint; + status: "allowance_transaction_submitted"; + txHash: Hex; +}; + +export type LockingAllowanceReverted = { + depositAmount: bigint; + allowance: bigint; + status: "allowance_transaction_reverted"; + txHash: Hex; +}; + +export type LockingAllowance = + | LockingAllowanceIdle + | LockingAllowanceSubmitted + | LockingAllowanceReverted; + +export type LockingDepositIdle = { + depositAmount: bigint; + status: "deposit_transaction_idle"; +}; + +export type LockingDepositSubmitted = { + depositAmount: bigint; + status: "deposit_transaction_submitted"; + txHash: Hex; +}; + +export type LockingDepositConfirmed = { + depositAmount: bigint; + status: "deposit_transaction_confirmed"; + txHash: Hex; +}; + +export type LockingDepositReverted = { + depositAmount: bigint; + status: "deposit_transaction_reverted"; + txHash: Hex; +}; + +export type LockingDeposit = + | LockingDepositIdle + | LockingDepositSubmitted + | LockingDepositConfirmed + | LockingDepositReverted; + +export type LockingState = LockingLoading | LockingAllowance | LockingDeposit; + +/** + * EVENTS + */ +export type LockingAllowanceUpdate = { + type: "ALLOWANCE_UPDATE"; + payload: { + allowance: bigint; + }; +}; + +export type LockingTransactionSubmitted = { + type: "TRANSACTION_SUBMITTED"; + payload: { hash: Hex }; +}; + +export type LockingTransactionReverted = { + type: "TRANSACTION_REVERTED"; +}; + +export type LockingTransactionConfirmed = { + type: "TRANSACTION_CONFIRMED"; +}; + +export type LockingEvent = + | LockingAllowanceUpdate + | LockingTransactionSubmitted + | LockingTransactionReverted + | LockingTransactionConfirmed; + +export function lockingReducer( + state: LockingState, + event: LockingEvent +): LockingState { + switch (state.status) { + case "loading": + if (event.type === "ALLOWANCE_UPDATE") { + return { + ...state, + allowance: event.payload.allowance, + status: + event.payload.allowance >= state.depositAmount + ? "deposit_transaction_idle" + : "allowance_transaction_idle", + }; + } + return state; + + case "allowance_transaction_idle": + if (event.type === "TRANSACTION_SUBMITTED") { + return { + ...state, + status: "allowance_transaction_submitted", + txHash: event.payload.hash, + }; + } + if (event.type === "ALLOWANCE_UPDATE") { + return { + ...state, + allowance: event.payload.allowance, + status: + event.payload.allowance >= state.depositAmount + ? "deposit_transaction_idle" + : "allowance_transaction_idle", + }; + } + return state; + + case "allowance_transaction_submitted": + if (event.type === "ALLOWANCE_UPDATE") { + return { + ...state, + allowance: event.payload.allowance, + status: + event.payload.allowance >= state.depositAmount + ? "deposit_transaction_idle" + : "allowance_transaction_idle", + }; + } + if (event.type === "TRANSACTION_REVERTED") { + return { + ...state, + status: "allowance_transaction_reverted", + }; + } + return state; + + case "deposit_transaction_idle": + if (event.type === "TRANSACTION_SUBMITTED") { + return { + ...state, + status: "deposit_transaction_submitted", + txHash: event.payload.hash, + }; + } + return state; + + case "deposit_transaction_submitted": + if (event.type === "TRANSACTION_CONFIRMED") { + return { + ...state, + status: "deposit_transaction_confirmed", + }; + } + if (event.type === "TRANSACTION_REVERTED") { + return { + ...state, + status: "deposit_transaction_reverted", + }; + } + return state; + + default: + return state; + } +} diff --git a/src/app/core/components/Modal/LPVaultTransactionModal/VPRate.tsx b/src/app/core/components/Modal/LPVaultTransactionModal/VPRate.tsx new file mode 100644 index 0000000..d5d9a72 --- /dev/null +++ b/src/app/core/components/Modal/LPVaultTransactionModal/VPRate.tsx @@ -0,0 +1,138 @@ +import { formatUnits } from "viem"; +import { FistIcon } from "../../Icons/FistIcon"; +import clsx from "clsx"; +import { ReactNode } from "react"; +import { formatBalance } from "@/app/core/util/formatter"; + +export function LockVPRate({ value }: { value: bigint }) { + const tokenAmount = formatBalance(Number(formatUnits(value, 18)), 3); + const vpAmount = tokenAmount; + + return ( + + + Locked LP tokens + + + Voting Power + + + +
+ wxdai bread lp token icon + {tokenAmount} +
+
+ + + +
+
+ +
+ ~ {vpAmount} +
+
+
+ ); +} + +export function UnlockVPRate({ value }: { value: bigint }) { + const tokenAmount = formatBalance(Number(formatUnits(value, 18)), 3); + const vpAmount = tokenAmount; + + return ( + + + Voting Power + + + Locked LP tokens + + + + +
+ +
+ ~ {vpAmount} +
+
+ + + + + + {tokenAmount} + + +
+ ); +} + +function VPRateGrid({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} +function TextLabel({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +function LeftLabel({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function RightLabel({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function EqualityIcon() { + return ( +
+ = +
+ ); +} + +function LeftValueDisplay({ children }: { children: ReactNode }) { + return
{children}
; +} + +function RightValueDisplay({ children }: { children: ReactNode }) { + return
{children}
; +} + +export function WXDaiBreadIcon() { + return wxdai bread lp token icon; +} + +export function PillContainer({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +export function ValueText({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} diff --git a/src/app/core/components/Modal/LPVaultTransactionModal/WithdrawTransaction.tsx b/src/app/core/components/Modal/LPVaultTransactionModal/WithdrawTransaction.tsx new file mode 100644 index 0000000..4e6a12d --- /dev/null +++ b/src/app/core/components/Modal/LPVaultTransactionModal/WithdrawTransaction.tsx @@ -0,0 +1,209 @@ +import { formatUnits, Hex } from "viem"; +import Button from "../../Button"; +import { ModalContent, ModalHeading, StatusMessage } from "../LPModalUI"; +import { useEffect, useReducer, useState } from "react"; +import { + useContractRead, + useContractWrite, + usePrepareContractWrite, +} from "wagmi"; +import { BUTTERED_BREAD_ABI } from "@/abi"; +import { TUserConnected } from "@/app/core/hooks/useConnectedUser"; +import { + LPVaultTransactionModalState, + useModal, +} from "@/app/core/context/ModalContext"; +import { getConfig } from "@/chainConfig"; + +import { useTransactions } from "@/app/core/context/TransactionsContext/TransactionsContext"; +import { withdrawReducer } from "./withdrawReducer"; +import { + PillContainer, + UnlockVPRate, + ValueText, + WXDaiBreadIcon, +} from "./VPRate"; +import { StatusBadge } from "./Locking/LockingTransaction"; +import { LinkIcon } from "../../Icons/LinkIcon"; +import { ExternalLink } from "@/app/core/components/ExternalLink"; + +export function WithdrawTransaction({ + user, + modalState, +}: { + user: TUserConnected; + modalState: LPVaultTransactionModalState; +}) { + const { transactionsState, transactionsDispatch } = useTransactions(); + const { setModal } = useModal(); + const [isWalletOpen, setIsWalletOpen] = useState(false); + const chainConfig = getConfig(user.chain.id); + const [withdrawState, withdrawDispatch] = useReducer(withdrawReducer, { + status: "idle", + }); + + useEffect(() => { + transactionsDispatch({ + type: "NEW", + payload: { + data: { type: "LP_VAULT_WITHDRAW", transactionType: "UNLOCK" }, + }, + }); + }, [transactionsDispatch]); + + const lockedBalance = useContractRead({ + address: chainConfig.BUTTERED_BREAD.address, + abi: BUTTERED_BREAD_ABI, + functionName: "accountToLPBalance", + args: [user.address, chainConfig.BUTTER.address], + watch: true, + }); + + const prepareWrite = usePrepareContractWrite({ + address: chainConfig.BUTTERED_BREAD.address, + abi: BUTTERED_BREAD_ABI, + functionName: "withdraw", + args: [chainConfig.BUTTER.address, modalState.parsedValue], + enabled: + lockedBalance.status === "success" && + modalState.parsedValue <= (lockedBalance.data as bigint), + }); + + useEffect(() => { + if (prepareWrite.status === "error") { + console.log(prepareWrite.error); + } + }, [prepareWrite]); + + const { + write: contractWriteWrite, + status: contractWriteStatus, + data: contractWriteData, + } = useContractWrite(prepareWrite.config); + + useEffect(() => { + if (contractWriteStatus === "success" && contractWriteData) { + transactionsDispatch({ + type: "SET_SUBMITTED", + payload: { hash: contractWriteData.hash }, + }); + withdrawDispatch({ + type: "TRANSACTION_SUBMITTED", + payload: { hash: contractWriteData.hash }, + }); + setIsWalletOpen(false); + } + if (contractWriteStatus === "error") { + setIsWalletOpen(false); + } + }, [contractWriteStatus, contractWriteData, transactionsDispatch]); + + useEffect(() => { + if (withdrawState.status === "idle") return; + const tx = transactionsState.submitted.find((t) => { + return t.hash === withdrawState.txHash; + }); + if (!tx || tx.status === "SUBMITTED") return; + withdrawDispatch({ + type: + tx.status === "CONFIRMED" + ? "TRANSACTION_CONFIRMED" + : "TRANSACTION_REVERTED", + }); + }, [transactionsState, withdrawState]); + + return ( + <> + Unlocking LP Tokens + + + {withdrawState.status !== "confirmed" && ( + <> + +

+ By unlocking your LP tokens you will not be eligible to receive + voting power within the Breadchain cooperative network in future + voting cycles. +

+ + )} + {withdrawState.status === "idle" && ( + + Press ‘Unlock LP tokens’ to execute the transaction + + )} + {withdrawState.status === "submitted" && ( + Awaiting on-chain confirmation... + )} + {(() => { + if (withdrawState.status === "confirmed") + return ( + <> + + + + ); + if (withdrawState.status === "submitted") + return ( + + ); + return ( + + ); + })()} +
+ + ); +} + +function UnlockingSuccess({ + value, + explorerLink, +}: { + value: bigint; + explorerLink: string; +}) { + const tokenAmount = formatUnits(value, 18); + + return ( +
+ + + {tokenAmount} LP TOKENS + +

Successfully unlocked!

+ +
+ View on Explorer + +
+
+
+ ); +} diff --git a/src/app/core/components/Modal/LPVaultTransactionModal/withdrawReducer.tsx b/src/app/core/components/Modal/LPVaultTransactionModal/withdrawReducer.tsx new file mode 100644 index 0000000..5800601 --- /dev/null +++ b/src/app/core/components/Modal/LPVaultTransactionModal/withdrawReducer.tsx @@ -0,0 +1,78 @@ +import { Hex } from "viem"; + +export type WithdrawState = + | WithdrawStateIdle + | WithdrawStateSubmitted + | WithdrawStateConfirmed + | WithdrawStateReverted; + +export type WithdrawStateIdle = { + status: "idle"; +}; + +export type WithdrawStateSubmitted = { + status: "submitted"; + txHash: Hex; +}; + +export type WithdrawStateConfirmed = { + status: "confirmed"; + txHash: Hex; +}; +export type WithdrawStateReverted = { + status: "reverted"; + txHash: Hex; +}; + +export type WithdrawEvent = + | WithdrawTransactionSubmitted + | WithdrawTransactionConfirmed + | WithdrawTransactionReverted; + +export type WithdrawTransactionSubmitted = { + type: "TRANSACTION_SUBMITTED"; + payload: { hash: Hex }; +}; + +export type WithdrawTransactionReverted = { + type: "TRANSACTION_REVERTED"; +}; + +export type WithdrawTransactionConfirmed = { + type: "TRANSACTION_CONFIRMED"; +}; + +export function withdrawReducer( + state: WithdrawState, + event: WithdrawEvent +): WithdrawState { + switch (state.status) { + case "idle": + if (event.type === "TRANSACTION_SUBMITTED") { + return { + ...state, + status: "submitted", + txHash: event.payload.hash, + }; + } + return state; + + case "submitted": + if (event.type === "TRANSACTION_CONFIRMED") { + return { + ...state, + status: "confirmed", + }; + } + if (event.type === "TRANSACTION_REVERTED") { + return { + ...state, + status: "reverted", + }; + } + return state; + + default: + return state; + } +} diff --git a/src/app/core/components/Modal/ModalPresenter.tsx b/src/app/core/components/Modal/ModalPresenter.tsx index 7d87af7..747ac2b 100644 --- a/src/app/core/components/Modal/ModalPresenter.tsx +++ b/src/app/core/components/Modal/ModalPresenter.tsx @@ -1,3 +1,4 @@ +"use client"; import * as Dialog from "@radix-ui/react-dialog"; import { AnimatePresence, motion } from "framer-motion"; import { useModal } from "@/app/core/context/ModalContext"; @@ -5,6 +6,7 @@ import { ConfirmRecastModal } from "./ConfirmRecastModal"; import { ModalOverlay } from "./ModalUI"; import { VoteTransactionModal } from "./TransactionModal/VoteTransactionModal"; import { BakeryTransactionModal } from "./TransactionModal/BakeryTransactionModal"; +import { LPVaultTransactionModal } from "./LPVaultTransactionModal/LPVaultTransactionModal"; export function ModalPresenter() { const { modalState, setModal } = useModal(); @@ -27,6 +29,10 @@ export function ModalPresenter() { return ; case "CONFIRM_RECAST": return ; + case "LP_VAULT_TRANSACTION": + return ( + + ); default: throw new Error( "unhandled modalState.type in switch statement" diff --git a/src/app/core/components/Modal/ModalUI.tsx b/src/app/core/components/Modal/ModalUI.tsx index d6ffd7d..41b8c4e 100644 --- a/src/app/core/components/Modal/ModalUI.tsx +++ b/src/app/core/components/Modal/ModalUI.tsx @@ -15,7 +15,7 @@ export const ModalContainer = forwardRef( return (
@@ -49,7 +49,9 @@ export function ModalHeading({ children }: { children: ReactNode }) { export function ModalContent({ children }: { children: ReactNode }) { return ( -
{children}
+
+ {children} +
); } diff --git a/src/app/core/components/Modal/TransactionModal/BakeryTransactionModal.tsx b/src/app/core/components/Modal/TransactionModal/BakeryTransactionModal.tsx index 19f60ef..25897e9 100644 --- a/src/app/core/components/Modal/TransactionModal/BakeryTransactionModal.tsx +++ b/src/app/core/components/Modal/TransactionModal/BakeryTransactionModal.tsx @@ -54,7 +54,12 @@ export function BakeryTransactionModal({ if (!transaction) throw new Error("Transaction modal requires a transaction!"); - if (transaction.data.type === "VOTE") { + if ( + transaction.data.type === "VOTE" || + transaction.data.type === "LP_VAULT_ALLOWANCE" || + transaction.data.type === "LP_VAULT_DEPOSIT" || + transaction.data.type === "LP_VAULT_WITHDRAW" + ) { throw new Error("Incorrect transaction type for modal!"); } diff --git a/src/app/core/context/ModalContext.tsx b/src/app/core/context/ModalContext.tsx index 5d8029c..ce860e0 100644 --- a/src/app/core/context/ModalContext.tsx +++ b/src/app/core/context/ModalContext.tsx @@ -1,3 +1,4 @@ +import { TransactionType } from "@/app/governance/lp-vaults/components/VaultPanel"; import { type ReactNode, createContext, useContext, useState } from "react"; export type RecastModalState = { @@ -15,10 +16,17 @@ export type BakeryTransactionModalState = { hash: string | null; }; +export type LPVaultTransactionModalState = { + type: "LP_VAULT_TRANSACTION"; + transactionType: TransactionType; + parsedValue: bigint; +}; + export type ModalState = | BakeryTransactionModalState | VoteModalState | RecastModalState + | LPVaultTransactionModalState | null; export type ModalContext = { modalState: ModalState; diff --git a/src/app/core/context/ProjectContext/ProjectContext.tsx b/src/app/core/context/ProjectContext/ProjectContext.tsx index 29c183e..168d9c6 100644 --- a/src/app/core/context/ProjectContext/ProjectContext.tsx +++ b/src/app/core/context/ProjectContext/ProjectContext.tsx @@ -90,13 +90,13 @@ const ProjectsProvider = ({ ); setVotingPowerState({ status: "SUCCESS", dataKey: "POWER", value }); } - }, [currentVotingPowerData, currentVotingPowerStatus]); + }, [currentVotingPowerData, currentVotingPowerStatus, cycleLength]); useEffect(() => { if (breadBalanceStatus === "error") { setBreadBalanceState({ status: "ERROR" }); } else if (breadBalanceStatus === "success" && breadBalanceData) { - const value = formatUnits(BigInt(breadBalanceData as string), 18); + const value = formatUnits(breadBalanceData, 18); setBreadBalanceState({ status: "SUCCESS", dataKey: "BREAD", value }); } else if (breadBalanceStatus === "success") { const value = "0.00"; diff --git a/src/app/core/context/TokenBalanceContext/TokenBalanceContext.tsx b/src/app/core/context/TokenBalanceContext/TokenBalanceContext.tsx index e70b5ba..8f59ee1 100644 --- a/src/app/core/context/TokenBalanceContext/TokenBalanceContext.tsx +++ b/src/app/core/context/TokenBalanceContext/TokenBalanceContext.tsx @@ -83,7 +83,7 @@ function ProviderWithUser({ } if (breadBalanceStatus === "success") { const value = breadBalanceData - ? formatUnits(BigInt(breadBalanceData as string), 18).toString() + ? formatUnits(breadBalanceData, 18).toString() : "0"; setBreadBalanceState({ diff --git a/src/app/core/context/TransactionsContext/TransactionsReducer.ts b/src/app/core/context/TransactionsContext/TransactionsReducer.ts index f74d731..52907e6 100644 --- a/src/app/core/context/TransactionsContext/TransactionsReducer.ts +++ b/src/app/core/context/TransactionsContext/TransactionsReducer.ts @@ -1,3 +1,4 @@ +import { TransactionType } from "@/app/governance/lp-vaults/components/VaultPanel"; import { WriteContractReturnType } from "viem"; export type TTransactionHash = WriteContractReturnType; @@ -10,6 +11,18 @@ export type TTransactionData = } | { type: "VOTE"; + } + | { + type: "LP_VAULT_ALLOWANCE"; + transactionType: TransactionType; + } + | { + type: "LP_VAULT_DEPOSIT"; + transactionType: TransactionType; + } + | { + type: "LP_VAULT_WITHDRAW"; + transactionType: TransactionType; }; export type TTransactionSubmitted = { @@ -93,7 +106,7 @@ export function TransactionsReducer( } case "SET_SUBMITTED": { const tx = state.new; - if (!tx) throw new Error("no new tx to submit"); + if (!tx) return state; return { new: null, submitted: [ @@ -125,9 +138,6 @@ export function TransactionsReducer( ...state, submitted: state.submitted.map((tx) => { if (tx.hash === action.payload.hash) { - if (tx.status !== "SUBMITTED") { - throw new Error("can only set REVERTED status on SUBMITTED tx!"); - } return { ...tx, status: "REVERTED", @@ -138,7 +148,7 @@ export function TransactionsReducer( }; case "SET_SAFE_SUBMITTED": const tx = state.new; - if (!tx) throw new Error("no new tx to submit"); + if (!tx) return state; return { new: null, submitted: [ diff --git a/src/app/core/context/TransactionsContext/TransactionsWatcher.tsx b/src/app/core/context/TransactionsContext/TransactionsWatcher.tsx index d6bcd2a..5ef4f82 100644 --- a/src/app/core/context/TransactionsContext/TransactionsWatcher.tsx +++ b/src/app/core/context/TransactionsContext/TransactionsWatcher.tsx @@ -45,7 +45,15 @@ export function TransactionWatcher({ payload: { toastType: "REVERTED", txHash: hash }, }); } - }, [status, hash, waitData, transactionsDispatch, toastDispatch]); + }, [ + status, + hash, + waitData, + transactionsDispatch, + toastDispatch, + playSound, + transaction, + ]); return null; } diff --git a/src/app/core/hooks/AppProvider.tsx b/src/app/core/hooks/AppProvider.tsx index 65da04c..8c8ea54 100644 --- a/src/app/core/hooks/AppProvider.tsx +++ b/src/app/core/hooks/AppProvider.tsx @@ -6,14 +6,14 @@ import { TokenBalancesProvider } from "@/app/core/context/TokenBalanceContext/To import { ConnectedUserProvider } from "@/app/core/hooks/useConnectedUser"; import { TransactionsProvider } from "@/app/core/context/TransactionsContext/TransactionsContext"; import { ToastProvider } from "@/app/core/context/ToastContext/ToastContext"; -import Header from "../components/Header/Header"; -import { Footer } from "../components/Footer/Footer"; + import { Features } from "@/app/layout"; import { useSentry } from "./useSentry"; -import { Toaster } from "../components/Toaster/Toaster"; + import { ModalProvider } from "../context/ModalContext"; -import { ModalPresenter } from "../components/Modal/ModalPresenter"; + import { QueryClient, QueryClientProvider } from "react-query"; +import { ReactQueryDevtools } from "react-query/devtools"; const queryClient = new QueryClient(); @@ -33,28 +33,13 @@ export function AppProvider({ - - {children} - + {children} + ); } - -function Layout({ children }: { children: ReactNode }) { - return ( -
-
-
- - - {children} -
-
-
- ); -} diff --git a/src/app/core/hooks/WagmiProvider/config/devConfig.ts b/src/app/core/hooks/WagmiProvider/config/devConfig.ts index 35495b2..c3a7dc9 100644 --- a/src/app/core/hooks/WagmiProvider/config/devConfig.ts +++ b/src/app/core/hooks/WagmiProvider/config/devConfig.ts @@ -16,7 +16,16 @@ if (!WALLET_CONNECT_PROJECT_ID) const chainsConfig = configureChains( [ - { ...foundry, id: 31337 }, + { + ...foundry, + id: 31337, + contracts: { + multicall3: { + address: "0xcA11bde05977b3631167028862bE2a173976CA11", + blockCreated: 21_022_491, + }, + }, + }, { ...gnosis, iconUrl: "gnosis_icon.svg", @@ -64,7 +73,6 @@ const connectors = connectorsForWallets([ ]); const config = createConfig({ - autoConnect: true, connectors, publicClient, }); diff --git a/src/app/core/hooks/useConnectedUser.tsx b/src/app/core/hooks/useConnectedUser.tsx index 08fea62..9e7be34 100644 --- a/src/app/core/hooks/useConnectedUser.tsx +++ b/src/app/core/hooks/useConnectedUser.tsx @@ -45,7 +45,12 @@ const ConnectedUserContext = createContext<{ }>({ user: { status: "LOADING", - features: { governancePage: false, breadCounter: false, recastVote: false }, + features: { + governancePage: false, + breadCounter: false, + recastVote: false, + lpVaults: false, + }, }, isSafe: false, }); diff --git a/src/app/core/hooks/useModal.tsx b/src/app/core/hooks/useModal.tsx deleted file mode 100644 index 87de612..0000000 --- a/src/app/core/hooks/useModal.tsx +++ /dev/null @@ -1,150 +0,0 @@ -// import * as DialogPrimitive from "@radix-ui/react-dialog"; -// import { -// ReactNode, -// createContext, -// useCallback, -// useContext, -// useEffect, -// useMemo, -// useReducer, -// useState, -// } from "react"; -// import { TTransactionDisplayState } from "./useTransactionDisplay"; - -// export type TModalType = -// | "DISCLAIMER" -// | "CONNECT_WALLET" -// | "CHANGE_NETWORK" -// | "CHANGING_NETWORK" -// | "APPROVAL" -// | "BAKING" -// | "BURNING" -// | "CLAIMING" -// | "CONNECTORS" -// | "CONNECTORS" -// | "SAFE_TRANSACTION"; - -// export type TModalStatus = "LOCKED" | "UNLOCKED"; - -// export interface IBaseModalState { -// type: TModalType; -// status: TModalStatus; -// } -// export interface IBakeModalState extends IBaseModalState { -// amount: string; -// } - -// export interface IBurnModalState extends IBaseModalState { -// amount: string; -// } - -// export type TModalState = -// | null -// | IBaseModalState -// | IBakeModalState -// | IBurnModalState; - -// export type TModalAction = -// | { -// type: "SET_MODAL"; -// payload: { -// type: TModalType; -// }; -// } -// | { -// type: "UNLOCK_MODAL"; -// } -// | { -// type: "CLEAR_MODAL"; -// }; - -// /* eslint-disable-next-line no-unused-vars */ -// export type TModalDispatch = (action: TModalAction) => void; - -// const ModalContext = createContext< -// | { -// state: TModalState; -// dispatch: TModalDispatch; -// } -// | undefined -// >(undefined); - -// const modalReducer = ( -// state: TModalState, -// action: TModalAction -// ): TModalState => { -// const { type: actionType } = action; -// switch (actionType) { -// case "SET_MODAL": -// /* eslint-disable-next-line no-case-declarations */ -// const { -// payload: { type }, -// } = action; -// return { -// type, -// status: "LOCKED", -// }; -// case "UNLOCK_MODAL": -// if (state === null) throw new Error("modal not set"); -// if ( -// state.type !== "BAKING" && -// state.type !== "BURNING" && -// state.type !== "CLAIMING" -// ) -// throw new Error("modal type cannot be unlocked"); -// return { -// ...state, -// status: "UNLOCKED", -// }; -// case "CLEAR_MODAL": -// return null; -// default: -// return state; -// } -// }; - -// function ModalProvider({ -// children, -// initialState = null, -// }: { -// children: ReactNode; -// initialState?: TModalState; -// }) { -// const [state, dispatch] = useReducer(modalReducer, initialState); -// const [isOpen, setIsOpen] = useState(false); - -// const value = useMemo(() => ({ state, dispatch }), [state, dispatch]); - -// const onOpenChange = useCallback( -// (open: boolean) => { -// setIsOpen(open); -// }, -// [setIsOpen] -// ); - -// useEffect(() => { -// if (state === null) { -// onOpenChange(false); -// } else { -// onOpenChange(true); -// } -// }, [state, onOpenChange]); - -// return ( -// -// {/* */} -// {children} -// {/* */} -// -// ); -// } - -// const useModal = () => { -// const context = useContext(ModalContext); -// if (context === undefined) { -// throw new Error("useModal must be used within a ModalProvider"); -// } -// return context; -// }; - -// export { ModalProvider, useModal }; diff --git a/src/app/core/hooks/useTokenBalance.tsx b/src/app/core/hooks/useTokenBalance.tsx new file mode 100644 index 0000000..7f99fce --- /dev/null +++ b/src/app/core/hooks/useTokenBalance.tsx @@ -0,0 +1,15 @@ +import { Hex } from "viem"; +import { TConnectedUserState, TUserConnected } from "./useConnectedUser"; +import { useContractRead } from "wagmi"; +import { ERC20_ABI } from "@/abi"; + +export function useTokenBalance(user: TConnectedUserState, tokenAddress: Hex) { + return useContractRead({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "balanceOf", + args: [user.status === "CONNECTED" ? user.address : "0x"], + watch: true, + enabled: user.status === "CONNECTED", + }); +} diff --git a/src/app/core/util/formatter.tsx b/src/app/core/util/formatter.tsx index a6f090b..8e1ff2d 100644 --- a/src/app/core/util/formatter.tsx +++ b/src/app/core/util/formatter.tsx @@ -5,7 +5,7 @@ export const truncateAddress = (address: string): string => export function formatBalance(value: number, decimals: number) { const balanceFormatter = new Intl.NumberFormat("en-US", { - minimumFractionDigits: decimals, + minimumFractionDigits: 0, maximumFractionDigits: decimals, minimumIntegerDigits: 1, useGrouping: true, diff --git a/src/app/core/util/parseFeatureVar.ts b/src/app/core/util/parseFeatureVar.ts new file mode 100644 index 0000000..bc3bac5 --- /dev/null +++ b/src/app/core/util/parseFeatureVar.ts @@ -0,0 +1,3 @@ +export function parseFeatureVar(feature: string | undefined): boolean { + return feature === "true" ? true : false; +} diff --git a/src/app/bakery/components/swapUtils.tsx b/src/app/core/util/sanitizeInput.ts similarity index 100% rename from src/app/bakery/components/swapUtils.tsx rename to src/app/core/util/sanitizeInput.ts diff --git a/src/app/governance/GovernancePage.tsx b/src/app/governance/GovernancePage.tsx index 1f64bb3..7f3973b 100644 --- a/src/app/governance/GovernancePage.tsx +++ b/src/app/governance/GovernancePage.tsx @@ -1,5 +1,6 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import { Hex } from "viem"; import { ProjectRow, VoteDisplay, VoteForm } from "./components/ProjectRow"; import { CastVotePanel } from "./components/CastVote"; @@ -7,9 +8,7 @@ import { useConnectedUser } from "@/app/core/hooks/useConnectedUser"; import { ResultsPanel } from "./components/ResultsPanel"; import { useCurrentVotingDistribution } from "./useCurrentVotingDistribution"; import { useCastVote } from "./useCastVote"; -import { useUserVotingPower } from "./useUserVotingPower"; import { DistributionOverview } from "./components/DistributionOverview"; -import { Hex } from "viem"; import { VotingPower } from "./components/VotingPower"; import { useLastClaimedBlockNumber } from "./useLastClaimedBlockNumber"; import { useCycleLength } from "./useCycleLength"; @@ -20,7 +19,9 @@ import { InfoCallout } from "./components/InfoCallout"; import { useDistributions } from "./useDistributions"; import { useModal } from "../core/context/ModalContext"; import { projectsMeta } from "../projectsMeta"; +import { PageGrid } from "./components/PageGrid"; import { ProjectsProvider } from "@/app/core/context/ProjectContext/ProjectContext"; +import { useVotingPower } from "./context/VotingPowerContext"; export function GovernancePage() { const { user, isSafe } = useConnectedUser(); @@ -28,10 +29,11 @@ export function GovernancePage() { const { lastClaimedBlocknumber } = useLastClaimedBlockNumber(); const { cycleLength } = useCycleLength(); const { castVote } = useCastVote(user, lastClaimedBlocknumber); - const { userVotingPower } = useUserVotingPower(user, cycleLength); const { minRequiredVotingPower } = useMinRequiredVotingPower(); const { data: distributionsData } = useDistributions(); + const userVotingPower = useVotingPower(); + const { cycleDates } = useCycleDates(cycleLength); const [voteFormState, setVoteFormState] = useState Number(minRequiredVotingPower || 0) - ? true - : false; + const { userCanVote, totalUserVotingPower } = useMemo(() => { + const totalUserVotingPower = + userVotingPower && + userVotingPower.bread.status === "success" && + userVotingPower.butteredBread.status === "success" + ? userVotingPower.bread.value + userVotingPower.butteredBread.value + : null; + + const userCanVote = + totalUserVotingPower && + totalUserVotingPower > Number(minRequiredVotingPower || 0) + ? true + : false; + + return { userCanVote, totalUserVotingPower }; + }, [minRequiredVotingPower, userVotingPower]); if ( castVote.status === "ERROR" || @@ -188,8 +202,8 @@ export function GovernancePage() { ); return ( -
-
+
+

Bread Governance @@ -215,7 +229,7 @@ export function GovernancePage() {
+ .toSorted((a, b) => { + return ( projectsMeta[a.account].order - projectsMeta[b.account].order - ) + ); + }) .map((project, i) => { return (
@@ -279,7 +294,7 @@ export function GovernancePage() { resetFormState={resetFormState} />
-
+ {/* */}

); diff --git a/src/app/governance/components/CastVote.tsx b/src/app/governance/components/CastVote.tsx index 7d16c96..8bda5db 100644 --- a/src/app/governance/components/CastVote.tsx +++ b/src/app/governance/components/CastVote.tsx @@ -106,7 +106,7 @@ export function CastVote({ address: distributorAddress, abi: DISTRIBUTOR_ABI, functionName: "castVote", - args: [vote], + args: [vote.map((num) => BigInt(num))], enabled: writeIsEnabled && distributorAddress !== "0x", }); @@ -222,8 +222,8 @@ export function CastVote({ xmlns="http://www.w3.org/2000/svg" > diff --git a/src/app/governance/components/DistributionOverview.tsx b/src/app/governance/components/DistributionOverview.tsx index cf16af5..adc8ea6 100644 --- a/src/app/governance/components/DistributionOverview.tsx +++ b/src/app/governance/components/DistributionOverview.tsx @@ -9,12 +9,7 @@ import { useContractRead, useNetwork } from "wagmi"; import { ERC20_ABI, SDAI_ADAPTOR_ABI } from "@/abi"; import { useEffect, useMemo, useState } from "react"; -import { - differenceInDays, - differenceInHours, - differenceInSeconds, - format, -} from "date-fns"; +import { differenceInDays, differenceInHours, format } from "date-fns"; import { getConfig } from "@/chainConfig"; import { formatUnits } from "viem"; import clsx from "clsx"; @@ -50,7 +45,6 @@ export function DistributionOverview({ address: config.BREAD.address, abi: ERC20_ABI, functionName: "totalSupply", - args: [], watch: true, cacheTime: 6_000, }); @@ -108,7 +102,7 @@ export function DistributionOverview({ return (
-
+

@@ -172,7 +166,7 @@ export function DistributionOverview({

-
+

Voting cycle # {distributions == undefined ? "--" : distributions.length + 1} diff --git a/src/app/governance/components/PageGrid.tsx b/src/app/governance/components/PageGrid.tsx new file mode 100644 index 0000000..0fcc40f --- /dev/null +++ b/src/app/governance/components/PageGrid.tsx @@ -0,0 +1,21 @@ +import clsx from "clsx"; +import { ReactNode } from "react"; + +export function PageGrid({ + children, + className = "", +}: { + children: ReactNode; + className?: string; +}) { + return ( +

+ {children} +
+ ); +} diff --git a/src/app/governance/components/ProjectRow.tsx b/src/app/governance/components/ProjectRow.tsx index feca8c2..64c2699 100644 --- a/src/app/governance/components/ProjectRow.tsx +++ b/src/app/governance/components/ProjectRow.tsx @@ -1,9 +1,8 @@ import { ReactNode } from "react"; import { Hex } from "viem"; import Image from "next/image"; -import { Badge, LinkBadge } from "@/app/core/components/Badge/Badge"; +import { LinkBadge } from "@/app/core/components/Badge/Badge"; import { BreadIcon } from "@/app/core/components/Icons/TokenIcons"; -import { PowerIcon } from "@/app/core/components/Icons/PowerIcon"; import { formatVotePercentage } from "@/app/core/util/formatter"; import { projectsMeta } from "@/app/projectsMeta"; import type { TConnectedUserState } from "@/app/core/hooks/useConnectedUser"; diff --git a/src/app/governance/components/SlicesPanel.tsx b/src/app/governance/components/SlicesPanel.tsx deleted file mode 100644 index cb6ebb2..0000000 --- a/src/app/governance/components/SlicesPanel.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useMemo } from "react"; -import { useQuery } from "react-query"; - -export function SlicesPanel() { - const { data, error, isLoading } = useQuery( - "slices", - async () => { - const res = await fetch( - `${process.env.NEXT_PUBLIC_BREAD_API_URL}/slices` - ); - return res.json(); - }, - { - refetchInterval: 1000, - } - ); - // const totals = useMemo(() => { - // return data?.reduce( - // (acc, vote) => { - // console.log("\n\n-----", { vote }); - // acc[vote.project_id] = - // acc[vote["project_id"]] + vote.value || vote.value; - // acc.count += 1; - // return acc; - // }, - // { count: 0 } as Record - // ); - // }, [data]); - - return ( -
-

slices

-
{JSON.stringify(data, null, 2)}
-
- ); -} diff --git a/src/app/governance/components/VotingPower.tsx b/src/app/governance/components/VotingPower.tsx index c9c42c5..81391c5 100644 --- a/src/app/governance/components/VotingPower.tsx +++ b/src/app/governance/components/VotingPower.tsx @@ -5,6 +5,8 @@ import { formatBalance, formatDate } from "@/app/core/util/formatter"; import { TConnectedUserState } from "@/app/core/hooks/useConnectedUser"; import { CycleDatesState } from "../useCycleDates"; import { CycleLengthSuccess } from "../useCycleLength"; +import { FistIcon } from "@/app/core/components/Icons/FistIcon"; +import { formatUnits } from "viem"; export function VotingPower({ minRequiredVotingPower, @@ -18,7 +20,7 @@ export function VotingPower({ isRecasting, }: { minRequiredVotingPower: number | null; - userVotingPower: number | null; + userVotingPower: bigint | null; userHasVoted: boolean; userCanVote: boolean; cycleDates: CycleDatesState; @@ -32,84 +34,15 @@ export function VotingPower({
- - - - - - - - - - - - - - - - - - - - - - - + + My voting power:{" "} - {user.status === "CONNECTED" && - userVotingPower && - formatBalance(userVotingPower / cycleLength.data, 2)} + {userVotingPower !== null && + formatBalance(Number(formatUnits(userVotingPower, 18)), 2)}

diff --git a/src/app/governance/context/UserVotingPowerContext.tsx b/src/app/governance/context/UserVotingPowerContext.tsx new file mode 100644 index 0000000..2b52328 --- /dev/null +++ b/src/app/governance/context/UserVotingPowerContext.tsx @@ -0,0 +1,52 @@ +import { createContext, ReactNode, useContext } from "react"; + +import { + TVotingPowerState, + VotingPowerContext, + VotingPowerProvider, +} from "./VotingPowerContext"; +import { useConnectedUser } from "@/app/core/hooks/useConnectedUser"; +import { usePreviousCycleStartingBlock } from "../usePreviousCycleStartingBlock"; +import { useLastClaimedBlockNumber } from "../useLastClaimedBlockNumber"; + +const UserVotingPowerContext = createContext(null); + +export function UserVotingPowerProvider({ children }: { children: ReactNode }) { + const { user } = useConnectedUser(); + + const { lastClaimedBlocknumber } = useLastClaimedBlockNumber(); + const { data: previousCycleStartingBlockData } = + usePreviousCycleStartingBlock(); + + if ( + user.status === "CONNECTED" && + previousCycleStartingBlockData && + lastClaimedBlocknumber + ) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +} + +export function useUserVotingPower() { + const context = useContext(UserVotingPowerContext); + if (context === undefined) { + throw new Error( + "useUserVotingPower must be used within a UserVotingPowerProvider" + ); + } + return context; +} diff --git a/src/app/governance/context/VotingPowerContext.tsx b/src/app/governance/context/VotingPowerContext.tsx new file mode 100644 index 0000000..fa8ff54 --- /dev/null +++ b/src/app/governance/context/VotingPowerContext.tsx @@ -0,0 +1,117 @@ +import { + type ReactNode, + createContext, + useContext, + useEffect, + useState, +} from "react"; +import { useNetwork, useQuery } from "wagmi"; +import { multicall } from "@wagmi/core"; +import { Hex } from "viem"; + +import { getConfig } from "@/chainConfig"; +import { DISTRIBUTOR_ABI } from "@/abi"; + +export type VpTokenLoading = { + status: "loading"; +}; + +export type VpTokenError = { + status: "error"; +}; + +export type VpTokenSuccess = { + status: "success"; + value: bigint; +}; + +export type TVotingPowerState = null | { + bread: VpTokenLoading | VpTokenSuccess | VpTokenError; + butteredBread: VpTokenLoading | VpTokenSuccess | VpTokenError; +}; + +export const VotingPowerContext = createContext(null); + +export function VotingPowerProvider({ + account, + previousCycleStartingBlock, + lastClaimedBlocknumber, + + children, +}: { + account: Hex; + previousCycleStartingBlock: bigint; + lastClaimedBlocknumber: bigint; + + children: ReactNode; +}) { + const [votingPowerState, setVotingPowerState] = useState({ + bread: { status: "loading" }, + butteredBread: { status: "loading" }, + }); + const network = useNetwork(); + const config = getConfig(network.chain?.id || "DEFAULT"); + + const { data, status, error } = useQuery(["vpMulticall"], async () => { + return await multicall({ + contracts: [ + { + address: config.DISBURSER.address, + abi: DISTRIBUTOR_ABI, + functionName: "getVotingPowerForPeriod", + args: [ + config.BREAD.address, + previousCycleStartingBlock, + lastClaimedBlocknumber, + account, + ], + }, + { + address: config.DISBURSER.address, + abi: DISTRIBUTOR_ABI, + functionName: "getVotingPowerForPeriod", + args: [ + config.BUTTERED_BREAD.address, + previousCycleStartingBlock, + lastClaimedBlocknumber, + account, + ], + }, + ], + }); + }); + + useEffect(() => { + if (status === "error") { + setVotingPowerState(() => ({ + bread: { status: "error" }, + butteredBread: { status: "error" }, + })); + return; + } + if (status === "success" && data && data[0].result && data[1].result) { + const cycleLength = lastClaimedBlocknumber - previousCycleStartingBlock; + const breadResult = data[0].result / cycleLength; + const butteredBreadResult = data[1].result / cycleLength; + + setVotingPowerState(() => ({ + bread: { status: "success", value: breadResult }, + butteredBread: { status: "success", value: butteredBreadResult }, + })); + } + }, [data, status, error, lastClaimedBlocknumber, previousCycleStartingBlock]); + + return ( + + {children} + + ); +} + +export function useVotingPower() { + const context = useContext(VotingPowerContext); + if (context === undefined) { + throw new Error("useVotingPower must be used within a VotingPowerProvider"); + } + return context; +} diff --git a/src/app/governance/layout.tsx b/src/app/governance/layout.tsx new file mode 100644 index 0000000..b672966 --- /dev/null +++ b/src/app/governance/layout.tsx @@ -0,0 +1,95 @@ +"use client"; +import { ReactNode } from "react"; +import clsx from "clsx"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +import { UserVotingPowerProvider } from "./context/UserVotingPowerContext"; +import { useConnectedUser } from "@/app/core/hooks/useConnectedUser"; + +export default function GovernanceLayout({ + children, +}: { + children: ReactNode; +}) { + const { user } = useConnectedUser(); + + return ( + <> + {user.features.lpVaults && ( +

+ +
+ )} + +
{children}
+
+ + ); +} + +function GovernanceNavigation() { + return ( +
+ +
+ ); +} + +function NavLink({ + children, + ...props +}: { + children: ReactNode; + href: string; +}) { + const currentPath = usePathname(); + + return ( + + {children} + + ); +} diff --git a/src/app/governance/lp-vaults/LPVotingPowerPage.tsx b/src/app/governance/lp-vaults/LPVotingPowerPage.tsx new file mode 100644 index 0000000..9f286d2 --- /dev/null +++ b/src/app/governance/lp-vaults/LPVotingPowerPage.tsx @@ -0,0 +1,48 @@ +"use client"; +import { useConnectedUser } from "@/app/core/hooks/useConnectedUser"; +import { VaultPanel } from "./components/VaultPanel"; +import { VotingPowerPanel } from "./components/VotingPowerPanel"; +import { Accordion } from "@radix-ui/react-accordion"; + +import { getConfig } from "@/chainConfig"; + +export function LPVotingPowerPage() { + const { user } = useConnectedUser(); + const config = getConfig( + user.status === "CONNECTED" ? user.chain.id : "DEFAULT" + ); + + return ( +
+
+
+ +
+
+ +
+
+ + + +
+
+
+ ); +} + +function TitleSection() { + return ( +
+

+ Voting Power LP Vaults +

+

+ Lock your LP tokens in the vault(s) below to receive voting power and + participate in Breadchain Cooperative governance voting cycles. When + locking your LP tokens you receive voting power for the next voting + cycle. +

+
+ ); +} diff --git a/src/app/governance/lp-vaults/components/SelectTransaction.tsx b/src/app/governance/lp-vaults/components/SelectTransaction.tsx new file mode 100644 index 0000000..91d5698 --- /dev/null +++ b/src/app/governance/lp-vaults/components/SelectTransaction.tsx @@ -0,0 +1,93 @@ +import { ReactNode } from "react"; +import { TransactionType } from "./VaultPanel"; +import clsx from "clsx"; + +export function SelectTransaction({ + transactionType, + setTransactionType, +}: { + transactionType: TransactionType; + setTransactionType: (type: TransactionType) => void; +}) { + return ( +
+ + + + } + setTransactionType={() => { + setTransactionType("LOCK"); + }} + /> + + + + } + setTransactionType={() => { + setTransactionType("UNLOCK"); + }} + /> +
+ ); +} + +function RadioInput({ + setTransactionType, + icon, + name, + isSelected, +}: { + setTransactionType: () => void; + icon: ReactNode; + name: string; + isSelected: boolean; +}) { + return ( +
+ + +
+ ); +} diff --git a/src/app/governance/lp-vaults/components/VaultPanel.tsx b/src/app/governance/lp-vaults/components/VaultPanel.tsx new file mode 100644 index 0000000..bbc316b --- /dev/null +++ b/src/app/governance/lp-vaults/components/VaultPanel.tsx @@ -0,0 +1,335 @@ +import { useEffect, useState } from "react"; +import { formatUnits, Hex, parseEther } from "viem"; +import { + AccordionItem, + AccordionHeader, + AccordionTrigger, + AccordionContent, +} from "@radix-ui/react-accordion"; + +import useDebounce from "@/app/bakery/hooks/useDebounce"; +import Button from "@/app/core/components/Button"; +import { useModal } from "@/app/core/context/ModalContext"; + +import { useConnectedUser } from "@/app/core/hooks/useConnectedUser"; +import { SelectTransaction } from "./SelectTransaction"; +import { sanitizeInputValue } from "@/app/core/util/sanitizeInput"; +import { WXDAIIcon, BreadIcon } from "@/app/core/components/Icons/TokenIcons"; +import { ExternalLink } from "@/app/core/components/ExternalLink"; +import { LinkIcon } from "@/app/core/components/Icons/LinkIcon"; +import { useTokenBalance } from "@/app/core/hooks/useTokenBalance"; +import { lpTokenMeta } from "@/app/lpTokenMeta"; +import { GradientBorder } from "@/app/core/components/GradientBorder"; +import { WXDaiBreadIcon } from "@/app/core/components/Modal/LPVaultTransactionModal/VPRate"; +import { MaxButton } from "@/app/core/components/MaxButton"; +import { useTransactions } from "@/app/core/context/TransactionsContext/TransactionsContext"; +import { formatBalance } from "@/app/core/util/formatter"; +import { useVaultTokenBalance } from "../context/VaultTokenBalanceContext"; +import { AccountMenu } from "@/app/core/components/Header/AccountMenu"; + +export type TransactionType = "LOCK" | "UNLOCK"; + +export function VaultPanel({ tokenAddress }: { tokenAddress: Hex }) { + const [inputValue, setInputValue] = useState(""); + const [transactionType, setTransactionType] = + useState("LOCK"); + const { user } = useConnectedUser(); + const { transactionsState } = useTransactions(); + + const lpTokenBalance = useTokenBalance(user, tokenAddress); + const vaultTokenBalance = useVaultTokenBalance(); + + useEffect(() => { + const lpVaultTransaction = transactionsState.submitted.filter( + (tx) => tx.data.type === "LP_VAULT_DEPOSIT" && tx.status === "CONFIRMED" + ); + if (lpVaultTransaction.length > 0) setInputValue(""); + }, [transactionsState, setInputValue]); + + const debouncedValue = useDebounce(inputValue, 500); + + const parsedValue = parseEther( + debouncedValue === "." ? "0" : debouncedValue || "0" + ); + + const { setModal } = useModal(); + + function submitTransaction() { + if (transactionType === "LOCK") { + setModal({ + type: "LP_VAULT_TRANSACTION", + transactionType: "LOCK", + parsedValue, + }); + return; + } + if (vaultTokenBalance?.butter.status !== "success") { + return; + } + setModal({ + type: "LP_VAULT_TRANSACTION", + transactionType: "UNLOCK", + parsedValue: vaultTokenBalance.butter.value, + }); + } + + return ( + + + +
+
+ +
+ +
+
+

+ {lpTokenMeta[tokenAddress].poolName} +

+ + {/* desktop token balances */} +
+
+ Unlocked LP tokens: + {lpTokenBalance.status === "success" ? ( + + {formatBalance( + Number(formatUnits(lpTokenBalance.data as bigint, 18)), + 3 + )} + + ) : ( + "-" + )} +
+ +
+ Locked tokens: + {user.status === "CONNECTED" ? ( + + {vaultTokenBalance?.butter.status === "success" + ? formatBalance( + Number( + formatUnits(vaultTokenBalance.butter.value, 18) + ), + 3 + ) + : "-"} + + ) : ( + "-" + )} +
+
+
+
+ + + +
+
+
+
+ +
+ + Visit pool on Curve + +
+ +
+
+
+ + +
+ + Inspect vault contract + +
+ +
+
+
+
+ + {/* mobile token balances */} +
+
+
Unlocked LP tokens:
+ {lpTokenBalance.status === "success" ? ( + + {formatBalance( + Number(formatUnits(lpTokenBalance.data as bigint, 18)), + 3 + )} + + ) : ( + "-" + )} +
+
+ +
+
+ Locked tokens: +
+ + {user.status === "CONNECTED" + ? vaultTokenBalance?.butter.status === "success" + ? formatBalance( + Number( + formatUnits(vaultTokenBalance.butter.value, 18) + ), + 3 + ) + : "-" + : "-"} + +
+
+
+
+
+ +
+
+

+ Lock LP tokens, get voting power +

+

+ Enter a desired amount of LP tokens to lock to receive voting + power. +

+ The amount you choose to lock can always be retrieved by + selecting the unlock button. +

+

+
+
+
+ +
+
+ {transactionType === "LOCK" ? "You deposit" : "You withdraw"} +
+
{ + event.preventDefault(); + submitTransaction(); + }} + > +
+
+ {transactionType === "LOCK" ? ( + { + setInputValue(sanitizeInputValue(event.target.value)); + }} + placeholder="0" + inputMode="decimal" + autoComplete="off" + autoCorrect="off" + pattern="^[0-9]*[.,]?[0-9]*$" + minLength={1} + maxLength={79} + spellCheck="false" + /> + ) : ( +
+
+ {vaultTokenBalance?.butter.status === "success" + ? formatBalance( + Number( + formatUnits(vaultTokenBalance.butter.value, 18) + ), + 3 + ) + : "-"} +
+
+ )} +
+ +
+ {lpTokenMeta[tokenAddress].tokenName} +
+
+
+
+ {transactionType === "LOCK" ? ( + <> + Unlocked LP tokens: + {lpTokenBalance.status === "success" + ? formatBalance( + Number( + formatUnits(lpTokenBalance.data as bigint, 18) + ), + 3 + ) + : "-"} + { + if (lpTokenBalance.status !== "success") return; + setInputValue( + formatUnits(lpTokenBalance.data as bigint, 18) + ); + }} + > + Max. + + + ) : ( + + )} +
+
+ {user.status === "CONNECTED" ? ( + + ) : ( + + Connect + + )} +
+
+
+
+
+ ); +} diff --git a/src/app/governance/lp-vaults/components/VotingPowerPanel.tsx b/src/app/governance/lp-vaults/components/VotingPowerPanel.tsx new file mode 100644 index 0000000..5fc7e84 --- /dev/null +++ b/src/app/governance/lp-vaults/components/VotingPowerPanel.tsx @@ -0,0 +1,157 @@ +import { CardBox } from "@/app/core/components/CardBox"; +import { FistIcon } from "@/app/core/components/Icons/FistIcon"; +import { AccountMenu } from "@/app/core/components/Header/AccountMenu"; +import { LinkIcon } from "@/app/core/components/Icons/LinkIcon"; +import { + TUserConnected, + useConnectedUser, +} from "@/app/core/hooks/useConnectedUser"; +import { useVotingPower } from "../../context/VotingPowerContext"; +import { formatBalance } from "@/app/core/util/formatter"; +import { useCurrentAccumulatedVotingPower } from "../../useCurrentAccumulatedVotingPower"; +import Elipsis from "@/app/core/components/Elipsis"; + +import { useCycleLength } from "../../useCycleLength"; +import { useVaultTokenBalance } from "../context/VaultTokenBalanceContext"; +import Tooltip from "@/app/core/components/Tooltip"; +import { useDistributions } from "../../useDistributions"; + +export function VotingPowerPanel() { + const { user } = useConnectedUser(); + + const votingPower = useVotingPower(); + const vaultTokenBalance = useVaultTokenBalance(); + const { data: distributions } = useDistributions(); + + return ( + +
+

+ MY VOTING POWER +

+
+ + + +
+ {votingPower && + votingPower.bread.status === "success" && + votingPower.butteredBread.status === "success" + ? formatBalance( + Number( + votingPower.bread.value + votingPower.butteredBread.value + ) / + 10 ** 18, + 3 + ) + : "-"} +
+
+
+ + Accessible voting power + + Your total available voting power for voting cycle # + {distributions ? distributions.length + 1 + "." : "-"} + + +
+ + {/* voting power grid */} +
+ + +

+ Voting power from locked LP +

+ + + {votingPower && votingPower.butteredBread.status === "success" + ? formatBalance( + Number(votingPower.butteredBread.value) / 10 ** 18, + 3 + ) + : "-"} + + +

+ Voting power from $BREAD +

+ + {votingPower && votingPower.bread.status === "success" + ? formatBalance(Number(votingPower.bread.value) / 10 ** 18, 3) + : "-"} + + + + +

+ Total locked LP tokens +

+ + + {vaultTokenBalance && vaultTokenBalance.butter.status === "success" + ? formatBalance( + Number(vaultTokenBalance.butter.value) / 10 ** 18, + 3 + ) + : "-"} + + + {user.status === "CONNECTED" ? ( + <> +

+ Pending voting power +

+ + + + + + ) : ( + + Connect + + )} +
+ + How does this work? +
+ +
+
+
+
+ ); +} + +function Divider() { + return ( +
+ ); +} + +function PendingVotingPowerDisplay({ user }: { user: TUserConnected }) { + const { + status: currentAccumulatedVotingPowerStatus, + data: currentAccumulatedVotingPowerData, + } = useCurrentAccumulatedVotingPower(user); + + const { cycleLength } = useCycleLength(); + + return currentAccumulatedVotingPowerStatus === "success" && + cycleLength.status === "SUCCESS" && + currentAccumulatedVotingPowerData ? ( + formatBalance( + Number(currentAccumulatedVotingPowerData) / 10 ** 18 / cycleLength.data, + 3 + ) + ) : ( + + ); +} diff --git a/src/app/governance/lp-vaults/context/VaultTokenBalanceContext.tsx b/src/app/governance/lp-vaults/context/VaultTokenBalanceContext.tsx new file mode 100644 index 0000000..dbfc5e1 --- /dev/null +++ b/src/app/governance/lp-vaults/context/VaultTokenBalanceContext.tsx @@ -0,0 +1,105 @@ +"use client"; +import { + type ReactNode, + createContext, + useContext, + useEffect, + useState, +} from "react"; +import { useContractRead } from "wagmi"; + +import { + TUserConnected, + useConnectedUser, +} from "@/app/core/hooks/useConnectedUser"; +import { getConfig } from "@/chainConfig"; +import { BUTTERED_BREAD_ABI } from "@/abi"; + +type VaultTokenBalanceLoading = { + status: "loading"; +}; + +type VaultTokenBalanceError = { + status: "error"; +}; + +type VaultTokenBalanceSuccess = { + status: "success"; + value: bigint; +}; + +type VaultTokenBalanceState = null | { + butter: + | VaultTokenBalanceLoading + | VaultTokenBalanceSuccess + | VaultTokenBalanceError; +}; + +const VaultTokenBalanceContext = createContext(null); + +function VaultTokenBalanceProvider({ children }: { children: ReactNode }) { + const { user } = useConnectedUser(); + + if (user.status === "CONNECTED") { + return {children}; + } + + return ( + + {children} + + ); +} + +function useVaultTokenBalance() { + const context = useContext(VaultTokenBalanceContext); + if (context === undefined) { + throw new Error( + "useVaultTokenBalance must be used within a VaultTokenBalanceProvider" + ); + } + return context; +} + +function ProviderWithUser({ + user, + + children, +}: { + user: TUserConnected; + children: ReactNode; +}) { + const [votingPowerState, setVotingPowerState] = + useState({ + butter: { status: "loading" }, + }); + + const config = getConfig(user.chain.id); + + const { data, status, error } = useContractRead({ + address: config.BUTTERED_BREAD.address, + abi: BUTTERED_BREAD_ABI, + functionName: "accountToLPBalance", + args: [user.address, config.BUTTER.address], + watch: true, + }); + + useEffect(() => { + if (status === "success" && data !== undefined) { + setVotingPowerState({ + butter: { + status: "success", + value: data, + }, + }); + } + }, [status, data, error]); + + return ( + + {children} + + ); +} + +export { VaultTokenBalanceProvider, useVaultTokenBalance }; diff --git a/src/app/governance/lp-vaults/page.tsx b/src/app/governance/lp-vaults/page.tsx new file mode 100644 index 0000000..5bcb277 --- /dev/null +++ b/src/app/governance/lp-vaults/page.tsx @@ -0,0 +1,22 @@ +import { notFound } from "next/navigation"; +import { Metadata } from "next"; +import { LPVotingPowerPage } from "./LPVotingPowerPage"; +import { parseFeatureVar } from "@/app/core/util/parseFeatureVar"; +import { VaultTokenBalanceProvider } from "./context/VaultTokenBalanceContext"; + +export const metadata: Metadata = { + title: "LP Vaults", + description: "Bake and burn BREAD. Fund post-capitalist web3.", +}; + +export default function LPVotingPower() { + if (!parseFeatureVar(process.env.FEATURE_LP_VAULTS)) { + notFound(); + } + + return ( + + + + ); +} diff --git a/src/app/governance/page.tsx b/src/app/governance/page.tsx index f67de26..8cc7679 100644 --- a/src/app/governance/page.tsx +++ b/src/app/governance/page.tsx @@ -2,7 +2,7 @@ import { GovernancePage } from "./GovernancePage"; import { Metadata } from "next"; export const metadata: Metadata = { - title: "Bread Governance", + title: "Bread Voting", description: "Bake and burn BREAD. Fund post-capitalist web3.", }; diff --git a/src/app/governance/useCastVote.ts b/src/app/governance/useCastVote.ts index ded3d81..925b6f5 100644 --- a/src/app/governance/useCastVote.ts +++ b/src/app/governance/useCastVote.ts @@ -39,7 +39,6 @@ export function useCastVote( }, null ); - console.log({ mostRecentVote }); setCastVote({ status: "SUCCESS", data: mostRecentVote diff --git a/src/app/governance/useCurrentAccumulatedVotingPower.tsx b/src/app/governance/useCurrentAccumulatedVotingPower.tsx new file mode 100644 index 0000000..5207f72 --- /dev/null +++ b/src/app/governance/useCurrentAccumulatedVotingPower.tsx @@ -0,0 +1,17 @@ +import { useContractRead } from "wagmi"; +import { TUserConnected } from "../core/hooks/useConnectedUser"; +import { getConfig } from "@/chainConfig"; +import { DISTRIBUTOR_ABI } from "@/abi"; + +export function useCurrentAccumulatedVotingPower(user: TUserConnected) { + const config = getConfig(user.chain.id); + + return useContractRead({ + address: config.DISBURSER.address, + abi: DISTRIBUTOR_ABI, + functionName: "getCurrentAccumulatedVotingPower", + args: [user.address], + watch: true, + cacheTime: 5_000, + }); +} diff --git a/src/app/governance/useCycleDates.ts b/src/app/governance/useCycleDates.ts index cd9ef3b..4c24ca6 100644 --- a/src/app/governance/useCycleDates.ts +++ b/src/app/governance/useCycleDates.ts @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; import { useBlockNumber, useContractRead, useNetwork } from "wagmi"; import { add, sub } from "date-fns"; import { CycleLengthState } from "./useCycleLength"; +import { useLastClaimedBlockNumber } from "./useLastClaimedBlockNumber"; export type CycleDatesLoading = { status: "LOADING"; @@ -29,42 +30,29 @@ export function useCycleDates(cycleLength: CycleLengthState) { const { chain: activeChain } = useNetwork(); const config = activeChain ? getConfig(activeChain.id) : getConfig("DEFAULT"); - const distributorAddress = config.DISBURSER.address; - const { - data: lastClaimedBlockNumberData, - status: lastClaimedBlockNumberStatus, - } = useContractRead({ - address: distributorAddress, - abi: DISTRIBUTOR_ABI, - functionName: "lastClaimedBlockNumber", - watch: true, - cacheTime: 3_000, - }); + const { lastClaimedBlocknumber } = useLastClaimedBlockNumber(); const { data: currentBlockNumberData, status: currentBlockNumberStatus } = useBlockNumber(); useEffect(() => { if ( - lastClaimedBlockNumberStatus === "error" || currentBlockNumberStatus === "error" || - lastClaimedBlockNumberData || + lastClaimedBlocknumber || cycleLength !== null ) { } if ( - lastClaimedBlockNumberStatus === "success" && - lastClaimedBlockNumberData && + lastClaimedBlocknumber && cycleLength.status === "SUCCESS" && currentBlockNumberStatus === "success" && currentBlockNumberData ) { const secondsSinceStart = - (Number(currentBlockNumberData) - Number(lastClaimedBlockNumberData)) * - 5; + (Number(currentBlockNumberData) - Number(lastClaimedBlocknumber)) * 5; const cycleBlocksRemaining = - Number(lastClaimedBlockNumberData) + + Number(lastClaimedBlocknumber) + cycleLength.data - Number(currentBlockNumberData); const cycleSecondsRemaining = cycleBlocksRemaining * 5; @@ -77,8 +65,7 @@ export function useCycleDates(cycleLength: CycleLengthState) { }); } }, [ - lastClaimedBlockNumberData, - lastClaimedBlockNumberStatus, + lastClaimedBlocknumber, cycleLength, currentBlockNumberData, currentBlockNumberStatus, diff --git a/src/app/governance/useLastClaimedBlockNumber.ts b/src/app/governance/useLastClaimedBlockNumber.ts index 3f78db6..ebeecda 100644 --- a/src/app/governance/useLastClaimedBlockNumber.ts +++ b/src/app/governance/useLastClaimedBlockNumber.ts @@ -3,6 +3,7 @@ import { useContractRead, useNetwork } from "wagmi"; import { DISTRIBUTOR_ABI } from "@/abi"; import { getConfig } from "@/chainConfig"; +import { formatUnits } from "viem"; export function useLastClaimedBlockNumber() { const [lastClaimedBlocknumber, setLastClaimedBlockNumber] = useState< @@ -28,7 +29,7 @@ export function useLastClaimedBlockNumber() { lastClaimedBlockNumberStatus === "success" && lastClaimedBlockNumberData ) { - setLastClaimedBlockNumber(lastClaimedBlockNumberData as bigint); + setLastClaimedBlockNumber(lastClaimedBlockNumberData); } }, [lastClaimedBlockNumberStatus, lastClaimedBlockNumberData]); diff --git a/src/app/governance/usePreviousCycleStartingBlock.tsx b/src/app/governance/usePreviousCycleStartingBlock.tsx new file mode 100644 index 0000000..c0a46f3 --- /dev/null +++ b/src/app/governance/usePreviousCycleStartingBlock.tsx @@ -0,0 +1,15 @@ +import { DISTRIBUTOR_ABI } from "@/abi"; +import { getConfig } from "@/chainConfig"; +import { useContractRead, useNetwork } from "wagmi"; + +export function usePreviousCycleStartingBlock() { + const { chain: activeChain } = useNetwork(); + const config = activeChain ? getConfig(activeChain.id) : getConfig("DEFAULT"); + + return useContractRead({ + address: config.DISBURSER.address, + abi: DISTRIBUTOR_ABI, + functionName: "previousCycleStartingBlock", + enabled: true, + }); +} diff --git a/src/app/governance/useUserVotingPower.ts b/src/app/governance/useUserVotingPower.ts deleted file mode 100644 index 950b625..0000000 --- a/src/app/governance/useUserVotingPower.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { DISTRIBUTOR_ABI } from "@/abi"; -import { getConfig } from "@/chainConfig"; -import { useEffect, useState } from "react"; -import { useContractRead, useNetwork } from "wagmi"; -import { TConnectedUserState } from "../core/hooks/useConnectedUser"; -import { formatUnits } from "viem"; -import { CycleLengthState, useCycleLength } from "./useCycleLength"; - -type UserVotingPowerState = - | { - status: "ACTUAL"; - value: number; - } - | { - status: "NOMINAL"; - value: number; - }; - -export function useUserVotingPower( - user: TConnectedUserState, - cycleLength: CycleLengthState -) { - const [userVotingPower, setUserVotingPower] = useState(null); - - const { chain: activeChain } = useNetwork(); - const config = activeChain ? getConfig(activeChain.id) : getConfig("DEFAULT"); - const distributorAddress = config.DISBURSER.address; - - const { - data: currentVotingPowerData, - status: currentVotingPowerStatus, - error: currentVotingPowerError, - } = useContractRead({ - enabled: user.status === "CONNECTED" && distributorAddress !== "0x", - address: distributorAddress, - abi: DISTRIBUTOR_ABI, - functionName: "getCurrentVotingPower", - args: [user.status === "CONNECTED" ? user.address : ""], - }); - - useEffect(() => { - if (currentVotingPowerStatus === "error" && currentVotingPowerError) { - console.error(currentVotingPowerError); - } - if ( - currentVotingPowerStatus === "success" && - currentVotingPowerData !== null && - cycleLength.status === "SUCCESS" - ) { - const vp = Number(formatUnits(currentVotingPowerData as bigint, 18)); - setUserVotingPower(vp); - } - }, [ - currentVotingPowerStatus, - currentVotingPowerData, - currentVotingPowerError, - cycleLength, - ]); - - return { userVotingPower }; -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5a90108..ca707f1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -7,15 +7,18 @@ import "./app.css"; import "@rainbow-me/rainbowkit/styles.css"; import { Metadata } from "next"; import Script from "next/script"; - -function parseFeatureVar(feature: string | undefined): boolean { - return feature === "true" ? true : false; -} +import { ReactNode } from "react"; +import Header from "./core/components/Header/Header"; +import { ModalPresenter } from "./core/components/Modal/ModalPresenter"; +import { Toaster } from "./core/components/Toaster/Toaster"; +import { Footer } from "./core/components/Footer/Footer"; +import { parseFeatureVar } from "./core/util/parseFeatureVar"; const features = { governancePage: parseFeatureVar(process.env.FEATURE_GOVERNANCE), breadCounter: parseFeatureVar(process.env.FEATURE_BREAD_COUNTER), recastVote: parseFeatureVar(process.env.FEATURE_RECAST_VOTE), + lpVaults: parseFeatureVar(process.env.FEATURE_LP_VAULTS), }; export type Features = { @@ -65,8 +68,24 @@ export default function App({ children }: { children: React.ReactNode }) { redhat.variable )} > - {children} + + {children} + ); } + +function Layout({ children }: { children: ReactNode }) { + return ( +
+
+
+ + + {children} +
+
+ ); +} diff --git a/src/app/lpTokenMeta.ts b/src/app/lpTokenMeta.ts new file mode 100644 index 0000000..32b8d23 --- /dev/null +++ b/src/app/lpTokenMeta.ts @@ -0,0 +1,20 @@ +import { Hex } from "viem"; + +export type LPTokenMeta = { + tokenName: string; + poolName: string; + inspectContract: string; + visitPool: string; +}; + +export const lpTokenMeta: { + [key: Hex]: LPTokenMeta; +} = { + "0xf3d8f3de71657d342db60dd714c8a2ae37eac6b4": { + tokenName: "BREAD/WXDAI LP", + poolName: "BREAD/WXDAI", + inspectContract: + "https://gnosisscan.io/address/DEPLOYED_CONTRACT_ADDRESS#code", + visitPool: "https://curve.fi/#/xdai/pools/factory-stable-ng-15", + }, +}; diff --git a/src/chainConfig.ts b/src/chainConfig.ts index ad541c1..08c65fb 100644 --- a/src/chainConfig.ts +++ b/src/chainConfig.ts @@ -22,6 +22,9 @@ export interface ChainConfiguration { DISBURSER: { address: Hex; }; + BUTTER: { + address: Hex; + }; BUTTERED_BREAD: { address: Hex; }; @@ -31,7 +34,6 @@ export interface ChainConfiguration { } export const BREAD_ADDRESS = "0xa555d5344f6FB6c65da19e403Cb4c1eC4a1a5Ee3"; -const DISBURSER_ADDRESS = "0x8ce361602B935680E8DeC218b820ff5056BeB7af"; export interface IConfig { [chainId: number]: ChainConfiguration; @@ -53,6 +55,9 @@ const sepolia: ChainConfiguration = { BUTTERED_BREAD: { address: "0x", }, + BUTTER: { + address: "0xf3d8f3de71657d342db60dd714c8a2ae37eac6b4", + }, SDAI_ADAPTOR: { address: "0x", }, @@ -67,12 +72,14 @@ const gnosis: ChainConfiguration = { decimals: 18, address: "0xa555d5344f6FB6c65da19e403Cb4c1eC4a1a5Ee3", }, - DISBURSER: { address: "0xeE95A62b749d8a2520E0128D9b3aCa241269024b", }, BUTTERED_BREAD: { - address: "0x", + address: "0x680B581605DC0A6902735a80dE35Cb0Ef6E90865", + }, + BUTTER: { + address: "0xf3d8f3de71657d342db60dd714c8a2ae37eac6b4", }, SDAI_ADAPTOR: { address: "0xD499b51fcFc66bd31248ef4b28d656d67E591A94", @@ -82,7 +89,7 @@ const gnosis: ChainConfiguration = { const anvil: ChainConfiguration = { ID: 31337, NETWORK_STRING: "Anvil", - EXPLORER: "NONE", + EXPLORER: "https://gnosisscan.io", BREAD: { symbol: "BREAD", decimals: 18, @@ -94,7 +101,11 @@ const anvil: ChainConfiguration = { }, BUTTERED_BREAD: { address: - (!!DISTRIBUTOR_DEPLOYED && (DISTRIBUTOR_DEPLOYED.ADDRESS as Hex)) || "0x", + (!!BUTTERED_BREAD_DEPLOYED && (BUTTERED_BREAD_DEPLOYED.ADDRESS as Hex)) || + "0x", + }, + BUTTER: { + address: "0xf3d8f3de71657d342db60dd714c8a2ae37eac6b4", }, SDAI_ADAPTOR: { address: "0xD499b51fcFc66bd31248ef4b28d656d67E591A94",