+ {blocked.message} +
+diff --git a/contracts/utils/deployment-manifest-to-app-env.ts b/contracts/utils/deployment-manifest-to-app-env.ts index 5756109c0..2b54c3367 100644 --- a/contracts/utils/deployment-manifest-to-app-env.ts +++ b/contracts/utils/deployment-manifest-to-app-env.ts @@ -30,12 +30,23 @@ const argv = minimist(process.argv.slice(2), { ], }); +const ZERO_ADDRESS = "0x" + "0".repeat(40); + const ZAddress = z.string().regex(/^0x[0-9a-fA-F]{40}$/); const ZDeploymentManifest = z.object({ collateralRegistry: ZAddress, boldToken: ZAddress, hintHelpers: ZAddress, multiTroveGetter: ZAddress, + exchangeHelpers: ZAddress, + + governance: z.object({ + LQTYToken: ZAddress, + LQTYStaking: ZAddress.default(ZERO_ADDRESS), + governance: ZAddress, + uniV4DonationsInitiative: ZAddress, + curveV2GaugeRewardsInitiative: ZAddress, + }), branches: z.array( z.object({ @@ -46,7 +57,6 @@ const ZDeploymentManifest = z.object({ collToken: ZAddress, defaultPool: ZAddress, gasPool: ZAddress, - interestRouter: ZAddress, leverageZapper: ZAddress, metadataNFT: ZAddress, priceFeed: ZAddress, @@ -128,7 +138,7 @@ function deployedContractsToAppEnvVariables(manifest: DeploymentManifest) { NEXT_PUBLIC_CONTRACT_WETH: manifest.branches[0].collToken, }; - const { branches, ...protocol } = manifest; + const { branches, governance, ...protocol } = manifest; // protocol contracts for (const [contractName, address] of Object.entries(protocol)) { @@ -138,7 +148,7 @@ function deployedContractsToAppEnvVariables(manifest: DeploymentManifest) { } } - // collateral contracts + // branches contracts for (const [index, contract] of Object.entries(branches)) { for (const [contractName, address] of Object.entries(contract)) { const envVarName = contractNameToAppEnvVariable(contractName, `COLL_${index}_CONTRACT`); @@ -148,6 +158,17 @@ function deployedContractsToAppEnvVariables(manifest: DeploymentManifest) { } } + // governance contracts + for (const [contractName, address] of Object.entries(governance)) { + const envVarName = contractNameToAppEnvVariable( + contractName, + contractName.endsWith("Initiative") ? "INITIATIVE" : "CONTRACT", + ); + if (envVarName) { + appEnvVariables[envVarName] = address; + } + } + return appEnvVariables; } @@ -163,6 +184,8 @@ function contractNameToAppEnvVariable(contractName: string, prefix: string = "") return `${prefix}_HINT_HELPERS`; case "multiTroveGetter": return `${prefix}_MULTI_TROVE_GETTER`; + case "exchangeHelpers": + return `${prefix}_EXCHANGE_HELPERS`; // collateral contracts case "activePool": @@ -189,6 +212,20 @@ function contractNameToAppEnvVariable(contractName: string, prefix: string = "") return `${prefix}_TROVE_MANAGER`; case "troveNFT": return `${prefix}_TROVE_NFT`; + + // governance contracts + case "LQTYToken": + return `${prefix}_LQTY_TOKEN`; + case "LQTYStaking": + return `${prefix}_LQTY_STAKING`; + case "governance": + return `${prefix}_GOVERNANCE`; + + // governance initiatives + case "uniV4DonationsInitiative": + return `${prefix}_UNI_V4_DONATIONS`; + case "curveV2GaugeRewardsInitiative": + return `${prefix}_CURVE_V2_GAUGE_REWARDS`; } return null; } diff --git a/frontend/app/.env b/frontend/app/.env index a425163d2..062097e2f 100644 --- a/frontend/app/.env +++ b/frontend/app/.env @@ -1,6 +1,16 @@ NEXT_PUBLIC_DEMO_MODE=false NEXT_PUBLIC_VERCEL_ANALYTICS=false NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=1 +# NEXT_PUBLIC_DEPLOYMENT_FLAVOR= + +# use demo|API_KEY (demo API) or pro|API_KEY (pro API) +NEXT_PUBLIC_COINGECKO_API_KEY= + +# the BLOCKING_LIST contract must implement isBlackListed(address)(bool) +# NEXT_PUBLIC_BLOCKING_LIST=0x97044531D0fD5B84438499A49629488105Dc58e6 + +# format: {vpnapi.io key}|{comma separated country codes} e.g. 1234|US,CA +# NEXT_PUBLIC_BLOCKING_VPNAPI= # Ethereum (mainnet) # NEXT_PUBLIC_CHAIN_ID=1 @@ -11,6 +21,8 @@ NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=1 # NEXT_PUBLIC_CHAIN_CONTRACT_ENS_REGISTRY=0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e # NEXT_PUBLIC_CHAIN_CONTRACT_ENS_RESOLVER=0xce01f8eee7E479C928F8919abD53E553a36CeF67|19258213 # NEXT_PUBLIC_CHAIN_CONTRACT_MULTICALL=0xca11bde05977b3631167028862be2a173976ca11|14353601 +# NEXT_PUBLIC_LIQUITY_STATS_URL= +# NEXT_PUBLIC_KNOWN_INITIATIVES_URL= # Hardhat / Anvil (local) # NEXT_PUBLIC_CHAIN_ID=31337 @@ -25,6 +37,7 @@ NEXT_PUBLIC_CHAIN_CURRENCY=Ether|ETH|18 NEXT_PUBLIC_CHAIN_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com NEXT_PUBLIC_CHAIN_BLOCK_EXPLORER=Etherscan Sepolia|https://sepolia.etherscan.io/ NEXT_PUBLIC_CHAIN_CONTRACT_MULTICALL=0xcA11bde05977b3631167028862bE2a173976CA11 +NEXT_PUBLIC_KNOWN_INITIATIVES_URL=https://liquity2-sepolia.vercel.app/known-initiatives/sepolia.json NEXT_PUBLIC_LIQUITY_STATS_URL=https://api.liquity.org/v2/testnet/sepolia.json NEXT_PUBLIC_SUBGRAPH_URL=https://api.studio.thegraph.com/query/42403/liquity2-sepolia/version/latest diff --git a/frontend/app/next.config.js b/frontend/app/next.config.js index 6fcf7a779..754fbe729 100644 --- a/frontend/app/next.config.js +++ b/frontend/app/next.config.js @@ -29,4 +29,17 @@ export default withBundleAnalyzer({ eslint: { ignoreDuringBuilds: true, }, + async headers() { + return [ + { + source: "/known-initiatives/:path*", + headers: [ + { key: "Access-Control-Allow-Credentials", value: "true" }, + { key: "Access-Control-Allow-Origin", value: "*" }, + { key: "Access-Control-Allow-Methods", value: "GET,OPTIONS" }, + { key: "Access-Control-Allow-Headers", value: "Origin, Content-Type, Accept" }, + ], + }, + ]; + }, }); diff --git a/frontend/app/panda.config.ts b/frontend/app/panda.config.ts index cc8d2dc60..b14c3b94f 100644 --- a/frontend/app/panda.config.ts +++ b/frontend/app/panda.config.ts @@ -5,6 +5,7 @@ import { defineConfig, defineGlobalStyles, definePreset } from "@pandacss/dev"; export default defineConfig({ preflight: true, // CSS reset + jsxFramework: "react", // needed for panda to extract props named `css` presets: [ liquityUiKitPreset as Preset, // `as Preset` prevents a type error: "Expression produces a union type that is too complex to represent." definePreset({ diff --git a/frontend/app/scripts/update-liquity-abis.ts b/frontend/app/scripts/update-liquity-abis.ts index 2a530e914..c7a90527f 100644 --- a/frontend/app/scripts/update-liquity-abis.ts +++ b/frontend/app/scripts/update-liquity-abis.ts @@ -94,10 +94,9 @@ async function main() { await Promise.all(ABIS.map(async (possibleNames) => { const abiName = possibleNames[0]; - const abi = await readJsonFromDir(`${artifactsTmpDir}`, possibleNames); - if (!abi) { + if (!abi || !abiName) { throw new Error(`Could not find ABI for ${possibleNames.join(", ")}`); } diff --git a/frontend/app/src/abi/Erc2612.ts b/frontend/app/src/abi/Erc2612.ts new file mode 100644 index 000000000..6b7e5c09e --- /dev/null +++ b/frontend/app/src/abi/Erc2612.ts @@ -0,0 +1,39 @@ +import { erc20Abi } from "viem"; + +// https://eips.ethereum.org/EIPS/eip-2612 +const permitAbiExtension = [ + { + name: "permit", + type: "function", + stateMutability: "nonpayable", + inputs: [ + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "value", type: "uint256" }, + { name: "deadline", type: "uint256" }, + { name: "v", type: "uint8" }, + { name: "r", type: "bytes32" }, + { name: "s", type: "bytes32" }, + ], + outputs: [], + }, + { + name: "nonces", + type: "function", + stateMutability: "view", + inputs: [{ name: "owner", type: "address" }], + outputs: [{ type: "uint256" }], + }, + { + name: "DOMAIN_SEPARATOR", + type: "function", + stateMutability: "view", + inputs: [], + outputs: [{ type: "bytes32" }], + }, +] as const; + +export default [ + ...erc20Abi, + ...permitAbiExtension, +] as const; diff --git a/frontend/app/src/abi/Governance.ts b/frontend/app/src/abi/Governance.ts index 5782cdb76..cef7cb30d 100644 --- a/frontend/app/src/abi/Governance.ts +++ b/frontend/app/src/abi/Governance.ts @@ -11,19 +11,19 @@ export const Governance = [ "type": "tuple", "internalType": "struct IGovernance.Configuration", "components": [ - { "name": "registrationFee", "type": "uint128", "internalType": "uint128" }, - { "name": "registrationThresholdFactor", "type": "uint128", "internalType": "uint128" }, - { "name": "unregistrationThresholdFactor", "type": "uint128", "internalType": "uint128" }, - { "name": "registrationWarmUpPeriod", "type": "uint16", "internalType": "uint16" }, - { "name": "unregistrationAfterEpochs", "type": "uint16", "internalType": "uint16" }, - { "name": "votingThresholdFactor", "type": "uint128", "internalType": "uint128" }, - { "name": "minClaim", "type": "uint88", "internalType": "uint88" }, - { "name": "minAccrual", "type": "uint88", "internalType": "uint88" }, - { "name": "epochStart", "type": "uint32", "internalType": "uint32" }, - { "name": "epochDuration", "type": "uint32", "internalType": "uint32" }, - { "name": "epochVotingCutoff", "type": "uint32", "internalType": "uint32" }, + { "name": "registrationFee", "type": "uint256", "internalType": "uint256" }, + { "name": "registrationThresholdFactor", "type": "uint256", "internalType": "uint256" }, + { "name": "unregistrationThresholdFactor", "type": "uint256", "internalType": "uint256" }, + { "name": "unregistrationAfterEpochs", "type": "uint256", "internalType": "uint256" }, + { "name": "votingThresholdFactor", "type": "uint256", "internalType": "uint256" }, + { "name": "minClaim", "type": "uint256", "internalType": "uint256" }, + { "name": "minAccrual", "type": "uint256", "internalType": "uint256" }, + { "name": "epochStart", "type": "uint256", "internalType": "uint256" }, + { "name": "epochDuration", "type": "uint256", "internalType": "uint256" }, + { "name": "epochVotingCutoff", "type": "uint256", "internalType": "uint256" }, ], }, + { "name": "_owner", "type": "address", "internalType": "address" }, { "name": "_initiatives", "type": "address[]", "internalType": "address[]" }, ], "stateMutability": "nonpayable", @@ -77,13 +77,6 @@ export const Governance = [ "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "view", }, - { - "type": "function", - "name": "REGISTRATION_WARM_UP_PERIOD", - "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "view", - }, { "type": "function", "name": "UNREGISTRATION_AFTER_EPOCHS", @@ -108,11 +101,12 @@ export const Governance = [ { "type": "function", "name": "allocateLQTY", - "inputs": [{ "name": "_initiatives", "type": "address[]", "internalType": "address[]" }, { - "name": "_deltaLQTYVotes", - "type": "int176[]", - "internalType": "int176[]", - }, { "name": "_deltaLQTYVetos", "type": "int176[]", "internalType": "int176[]" }], + "inputs": [ + { "name": "_initiativesToReset", "type": "address[]", "internalType": "address[]" }, + { "name": "_initiatives", "type": "address[]", "internalType": "address[]" }, + { "name": "_absoluteLQTYVotes", "type": "int256[]", "internalType": "int256[]" }, + { "name": "_absoluteLQTYVetos", "type": "int256[]", "internalType": "int256[]" }, + ], "outputs": [], "stateMutability": "nonpayable", }, @@ -133,10 +127,17 @@ export const Governance = [ { "type": "function", "name": "calculateVotingThreshold", - "inputs": [], + "inputs": [{ "name": "_votes", "type": "uint256", "internalType": "uint256" }], "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "view", }, + { + "type": "function", + "name": "calculateVotingThreshold", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "nonpayable", + }, { "type": "function", "name": "claimForInitiative", @@ -148,8 +149,8 @@ export const Governance = [ "type": "function", "name": "claimFromStakingV1", "inputs": [{ "name": "_rewardRecipient", "type": "address", "internalType": "address" }], - "outputs": [{ "name": "accruedLUSD", "type": "uint256", "internalType": "uint256" }, { - "name": "accruedETH", + "outputs": [{ "name": "lusdSent", "type": "uint256", "internalType": "uint256" }, { + "name": "ethSent", "type": "uint256", "internalType": "uint256", }], @@ -165,14 +166,50 @@ export const Governance = [ { "type": "function", "name": "depositLQTY", - "inputs": [{ "name": "_lqtyAmount", "type": "uint88", "internalType": "uint88" }], + "inputs": [{ "name": "_lqtyAmount", "type": "uint256", "internalType": "uint256" }], + "outputs": [], + "stateMutability": "nonpayable", + }, + { + "type": "function", + "name": "depositLQTY", + "inputs": [{ "name": "_lqtyAmount", "type": "uint256", "internalType": "uint256" }, { + "name": "_doSendRewards", + "type": "bool", + "internalType": "bool", + }, { "name": "_recipient", "type": "address", "internalType": "address" }], "outputs": [], "stateMutability": "nonpayable", }, { "type": "function", "name": "depositLQTYViaPermit", - "inputs": [{ "name": "_lqtyAmount", "type": "uint88", "internalType": "uint88" }, { + "inputs": [ + { "name": "_lqtyAmount", "type": "uint256", "internalType": "uint256" }, + { + "name": "_permitParams", + "type": "tuple", + "internalType": "struct PermitParams", + "components": [ + { "name": "owner", "type": "address", "internalType": "address" }, + { "name": "spender", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "v", "type": "uint8", "internalType": "uint8" }, + { "name": "r", "type": "bytes32", "internalType": "bytes32" }, + { "name": "s", "type": "bytes32", "internalType": "bytes32" }, + ], + }, + { "name": "_doSendRewards", "type": "bool", "internalType": "bool" }, + { "name": "_recipient", "type": "address", "internalType": "address" }, + ], + "outputs": [], + "stateMutability": "nonpayable", + }, + { + "type": "function", + "name": "depositLQTYViaPermit", + "inputs": [{ "name": "_lqtyAmount", "type": "uint256", "internalType": "uint256" }, { "name": "_permitParams", "type": "tuple", "internalType": "struct PermitParams", @@ -200,24 +237,136 @@ export const Governance = [ "type": "function", "name": "epoch", "inputs": [], - "outputs": [{ "name": "", "type": "uint16", "internalType": "uint16" }], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "view", }, { "type": "function", "name": "epochStart", "inputs": [], - "outputs": [{ "name": "", "type": "uint32", "internalType": "uint32" }], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view", + }, + { + "type": "function", + "name": "getInitiativeSnapshotAndState", + "inputs": [{ "name": "_initiative", "type": "address", "internalType": "address" }], + "outputs": [{ + "name": "initiativeSnapshot", + "type": "tuple", + "internalType": "struct IGovernance.InitiativeVoteSnapshot", + "components": [ + { "name": "votes", "type": "uint256", "internalType": "uint256" }, + { "name": "forEpoch", "type": "uint256", "internalType": "uint256" }, + { "name": "lastCountedEpoch", "type": "uint256", "internalType": "uint256" }, + { "name": "vetos", "type": "uint256", "internalType": "uint256" }, + ], + }, { + "name": "initiativeState", + "type": "tuple", + "internalType": "struct IGovernance.InitiativeState", + "components": [ + { "name": "voteLQTY", "type": "uint256", "internalType": "uint256" }, + { "name": "voteOffset", "type": "uint256", "internalType": "uint256" }, + { "name": "vetoLQTY", "type": "uint256", "internalType": "uint256" }, + { "name": "vetoOffset", "type": "uint256", "internalType": "uint256" }, + { "name": "lastEpochClaim", "type": "uint256", "internalType": "uint256" }, + ], + }, { "name": "shouldUpdate", "type": "bool", "internalType": "bool" }], + "stateMutability": "view", + }, + { + "type": "function", + "name": "getInitiativeState", + "inputs": [{ "name": "_initiative", "type": "address", "internalType": "address" }, { + "name": "_votesSnapshot", + "type": "tuple", + "internalType": "struct IGovernance.VoteSnapshot", + "components": [{ "name": "votes", "type": "uint256", "internalType": "uint256" }, { + "name": "forEpoch", + "type": "uint256", + "internalType": "uint256", + }], + }, { + "name": "_votesForInitiativeSnapshot", + "type": "tuple", + "internalType": "struct IGovernance.InitiativeVoteSnapshot", + "components": [ + { "name": "votes", "type": "uint256", "internalType": "uint256" }, + { "name": "forEpoch", "type": "uint256", "internalType": "uint256" }, + { "name": "lastCountedEpoch", "type": "uint256", "internalType": "uint256" }, + { "name": "vetos", "type": "uint256", "internalType": "uint256" }, + ], + }, { + "name": "_initiativeState", + "type": "tuple", + "internalType": "struct IGovernance.InitiativeState", + "components": [ + { "name": "voteLQTY", "type": "uint256", "internalType": "uint256" }, + { "name": "voteOffset", "type": "uint256", "internalType": "uint256" }, + { "name": "vetoLQTY", "type": "uint256", "internalType": "uint256" }, + { "name": "vetoOffset", "type": "uint256", "internalType": "uint256" }, + { "name": "lastEpochClaim", "type": "uint256", "internalType": "uint256" }, + ], + }], + "outputs": [{ "name": "status", "type": "uint8", "internalType": "enum IGovernance.InitiativeStatus" }, { + "name": "lastEpochClaim", + "type": "uint256", + "internalType": "uint256", + }, { "name": "claimableAmount", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view", + }, + { + "type": "function", + "name": "getInitiativeState", + "inputs": [{ "name": "_initiative", "type": "address", "internalType": "address" }], + "outputs": [{ "name": "status", "type": "uint8", "internalType": "enum IGovernance.InitiativeStatus" }, { + "name": "lastEpochClaim", + "type": "uint256", + "internalType": "uint256", + }, { "name": "claimableAmount", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "nonpayable", + }, + { + "type": "function", + "name": "getLatestVotingThreshold", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view", + }, + { + "type": "function", + "name": "getTotalVotesAndState", + "inputs": [], + "outputs": [{ + "name": "snapshot", + "type": "tuple", + "internalType": "struct IGovernance.VoteSnapshot", + "components": [{ "name": "votes", "type": "uint256", "internalType": "uint256" }, { + "name": "forEpoch", + "type": "uint256", + "internalType": "uint256", + }], + }, { + "name": "state", + "type": "tuple", + "internalType": "struct IGovernance.GlobalState", + "components": [{ "name": "countedVoteLQTY", "type": "uint256", "internalType": "uint256" }, { + "name": "countedVoteOffset", + "type": "uint256", + "internalType": "uint256", + }], + }, { "name": "shouldUpdate", "type": "bool", "internalType": "bool" }], "stateMutability": "view", }, { "type": "function", "name": "globalState", "inputs": [], - "outputs": [{ "name": "countedVoteLQTY", "type": "uint88", "internalType": "uint88" }, { - "name": "countedVoteLQTYAverageTimestamp", - "type": "uint32", - "internalType": "uint32", + "outputs": [{ "name": "countedVoteLQTY", "type": "uint256", "internalType": "uint256" }, { + "name": "countedVoteOffset", + "type": "uint256", + "internalType": "uint256", }], "stateMutability": "view", }, @@ -226,14 +375,21 @@ export const Governance = [ "name": "initiativeStates", "inputs": [{ "name": "", "type": "address", "internalType": "address" }], "outputs": [ - { "name": "voteLQTY", "type": "uint88", "internalType": "uint88" }, - { "name": "vetoLQTY", "type": "uint88", "internalType": "uint88" }, - { "name": "averageStakingTimestampVoteLQTY", "type": "uint32", "internalType": "uint32" }, - { "name": "averageStakingTimestampVetoLQTY", "type": "uint32", "internalType": "uint32" }, - { "name": "counted", "type": "uint16", "internalType": "uint16" }, + { "name": "voteLQTY", "type": "uint256", "internalType": "uint256" }, + { "name": "voteOffset", "type": "uint256", "internalType": "uint256" }, + { "name": "vetoLQTY", "type": "uint256", "internalType": "uint256" }, + { "name": "vetoOffset", "type": "uint256", "internalType": "uint256" }, + { "name": "lastEpochClaim", "type": "uint256", "internalType": "uint256" }, ], "stateMutability": "view", }, + { + "type": "function", + "name": "isOwner", + "inputs": [], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view", + }, { "type": "function", "name": "lqty", @@ -249,30 +405,46 @@ export const Governance = [ "type": "address", "internalType": "address", }], - "outputs": [{ "name": "voteLQTY", "type": "uint88", "internalType": "uint88" }, { - "name": "vetoLQTY", - "type": "uint88", - "internalType": "uint88", - }, { "name": "atEpoch", "type": "uint16", "internalType": "uint16" }], + "outputs": [ + { "name": "voteLQTY", "type": "uint256", "internalType": "uint256" }, + { "name": "voteOffset", "type": "uint256", "internalType": "uint256" }, + { "name": "vetoLQTY", "type": "uint256", "internalType": "uint256" }, + { "name": "vetoOffset", "type": "uint256", "internalType": "uint256" }, + { "name": "atEpoch", "type": "uint256", "internalType": "uint256" }, + ], "stateMutability": "view", }, { "type": "function", "name": "lqtyToVotes", - "inputs": [{ "name": "_lqtyAmount", "type": "uint88", "internalType": "uint88" }, { - "name": "_currentTimestamp", + "inputs": [{ "name": "_lqtyAmount", "type": "uint256", "internalType": "uint256" }, { + "name": "_timestamp", "type": "uint256", "internalType": "uint256", - }, { "name": "_averageTimestamp", "type": "uint32", "internalType": "uint32" }], - "outputs": [{ "name": "", "type": "uint240", "internalType": "uint240" }], + }, { "name": "_offset", "type": "uint256", "internalType": "uint256" }], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "pure", }, { "type": "function", - "name": "multicall", - "inputs": [{ "name": "data", "type": "bytes[]", "internalType": "bytes[]" }], - "outputs": [{ "name": "results", "type": "bytes[]", "internalType": "bytes[]" }], - "stateMutability": "payable", + "name": "multiDelegateCall", + "inputs": [{ "name": "inputs", "type": "bytes[]", "internalType": "bytes[]" }], + "outputs": [{ "name": "returnValues", "type": "bytes[]", "internalType": "bytes[]" }], + "stateMutability": "nonpayable", + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view", + }, + { + "type": "function", + "name": "registerInitialInitiatives", + "inputs": [{ "name": "_initiatives", "type": "address[]", "internalType": "address[]" }], + "outputs": [], + "stateMutability": "nonpayable", }, { "type": "function", @@ -285,14 +457,25 @@ export const Governance = [ "type": "function", "name": "registeredInitiatives", "inputs": [{ "name": "", "type": "address", "internalType": "address" }], - "outputs": [{ "name": "", "type": "uint16", "internalType": "uint16" }], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "view", }, + { + "type": "function", + "name": "resetAllocations", + "inputs": [{ "name": "_initiativesToReset", "type": "address[]", "internalType": "address[]" }, { + "name": "checkAll", + "type": "bool", + "internalType": "bool", + }], + "outputs": [], + "stateMutability": "nonpayable", + }, { "type": "function", "name": "secondsWithinEpoch", "inputs": [], - "outputs": [{ "name": "", "type": "uint32", "internalType": "uint32" }], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "view", }, { @@ -303,20 +486,21 @@ export const Governance = [ "name": "voteSnapshot", "type": "tuple", "internalType": "struct IGovernance.VoteSnapshot", - "components": [{ "name": "votes", "type": "uint240", "internalType": "uint240" }, { + "components": [{ "name": "votes", "type": "uint256", "internalType": "uint256" }, { "name": "forEpoch", - "type": "uint16", - "internalType": "uint16", + "type": "uint256", + "internalType": "uint256", }], }, { "name": "initiativeVoteSnapshot", "type": "tuple", "internalType": "struct IGovernance.InitiativeVoteSnapshot", - "components": [{ "name": "votes", "type": "uint224", "internalType": "uint224" }, { - "name": "forEpoch", - "type": "uint16", - "internalType": "uint16", - }, { "name": "lastCountedEpoch", "type": "uint16", "internalType": "uint16" }], + "components": [ + { "name": "votes", "type": "uint256", "internalType": "uint256" }, + { "name": "forEpoch", "type": "uint256", "internalType": "uint256" }, + { "name": "lastCountedEpoch", "type": "uint256", "internalType": "uint256" }, + { "name": "vetos", "type": "uint256", "internalType": "uint256" }, + ], }], "stateMutability": "nonpayable", }, @@ -345,39 +529,52 @@ export const Governance = [ "type": "function", "name": "userStates", "inputs": [{ "name": "", "type": "address", "internalType": "address" }], - "outputs": [{ "name": "allocatedLQTY", "type": "uint88", "internalType": "uint88" }, { - "name": "averageStakingTimestamp", - "type": "uint32", - "internalType": "uint32", - }], + "outputs": [ + { "name": "unallocatedLQTY", "type": "uint256", "internalType": "uint256" }, + { "name": "unallocatedOffset", "type": "uint256", "internalType": "uint256" }, + { "name": "allocatedLQTY", "type": "uint256", "internalType": "uint256" }, + { "name": "allocatedOffset", "type": "uint256", "internalType": "uint256" }, + ], "stateMutability": "view", }, { "type": "function", "name": "votesForInitiativeSnapshot", "inputs": [{ "name": "", "type": "address", "internalType": "address" }], - "outputs": [{ "name": "votes", "type": "uint224", "internalType": "uint224" }, { - "name": "forEpoch", - "type": "uint16", - "internalType": "uint16", - }, { "name": "lastCountedEpoch", "type": "uint16", "internalType": "uint16" }], + "outputs": [ + { "name": "votes", "type": "uint256", "internalType": "uint256" }, + { "name": "forEpoch", "type": "uint256", "internalType": "uint256" }, + { "name": "lastCountedEpoch", "type": "uint256", "internalType": "uint256" }, + { "name": "vetos", "type": "uint256", "internalType": "uint256" }, + ], "stateMutability": "view", }, { "type": "function", "name": "votesSnapshot", "inputs": [], - "outputs": [{ "name": "votes", "type": "uint240", "internalType": "uint240" }, { + "outputs": [{ "name": "votes", "type": "uint256", "internalType": "uint256" }, { "name": "forEpoch", - "type": "uint16", - "internalType": "uint16", + "type": "uint256", + "internalType": "uint256", }], "stateMutability": "view", }, { "type": "function", "name": "withdrawLQTY", - "inputs": [{ "name": "_lqtyAmount", "type": "uint88", "internalType": "uint88" }], + "inputs": [{ "name": "_lqtyAmount", "type": "uint256", "internalType": "uint256" }], + "outputs": [], + "stateMutability": "nonpayable", + }, + { + "type": "function", + "name": "withdrawLQTY", + "inputs": [{ "name": "_lqtyAmount", "type": "uint256", "internalType": "uint256" }, { + "name": "_doSendRewards", + "type": "bool", + "internalType": "bool", + }, { "name": "_recipient", "type": "address", "internalType": "address" }], "outputs": [], "stateMutability": "nonpayable", }, @@ -385,23 +582,24 @@ export const Governance = [ "type": "event", "name": "AllocateLQTY", "inputs": [ - { "name": "user", "type": "address", "indexed": false, "internalType": "address" }, - { "name": "initiative", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "user", "type": "address", "indexed": true, "internalType": "address" }, + { "name": "initiative", "type": "address", "indexed": true, "internalType": "address" }, { "name": "deltaVoteLQTY", "type": "int256", "indexed": false, "internalType": "int256" }, { "name": "deltaVetoLQTY", "type": "int256", "indexed": false, "internalType": "int256" }, - { "name": "atEpoch", "type": "uint16", "indexed": false, "internalType": "uint16" }, + { "name": "atEpoch", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "hookSuccess", "type": "bool", "indexed": false, "internalType": "bool" }, ], "anonymous": false, }, { "type": "event", "name": "ClaimForInitiative", - "inputs": [{ "name": "initiative", "type": "address", "indexed": false, "internalType": "address" }, { - "name": "bold", - "type": "uint256", - "indexed": false, - "internalType": "uint256", - }, { "name": "forEpoch", "type": "uint256", "indexed": false, "internalType": "uint256" }], + "inputs": [ + { "name": "initiative", "type": "address", "indexed": true, "internalType": "address" }, + { "name": "bold", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "forEpoch", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "hookSuccess", "type": "bool", "indexed": false, "internalType": "bool" }, + ], "anonymous": false, }, { @@ -418,45 +616,59 @@ export const Governance = [ { "type": "event", "name": "DepositLQTY", - "inputs": [{ "name": "user", "type": "address", "indexed": false, "internalType": "address" }, { - "name": "depositedLQTY", - "type": "uint256", - "indexed": false, - "internalType": "uint256", - }], + "inputs": [ + { "name": "user", "type": "address", "indexed": true, "internalType": "address" }, + { "name": "rewardRecipient", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "lqtyAmount", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "lusdReceived", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "lusdSent", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "ethReceived", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "ethSent", "type": "uint256", "indexed": false, "internalType": "uint256" }, + ], "anonymous": false, }, { "type": "event", - "name": "RegisterInitiative", - "inputs": [{ "name": "initiative", "type": "address", "indexed": false, "internalType": "address" }, { - "name": "registrant", + "name": "OwnershipTransferred", + "inputs": [{ "name": "previousOwner", "type": "address", "indexed": true, "internalType": "address" }, { + "name": "newOwner", "type": "address", - "indexed": false, + "indexed": true, "internalType": "address", - }, { "name": "atEpoch", "type": "uint16", "indexed": false, "internalType": "uint16" }], + }], + "anonymous": false, + }, + { + "type": "event", + "name": "RegisterInitiative", + "inputs": [ + { "name": "initiative", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "registrant", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "atEpoch", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "hookSuccess", "type": "bool", "indexed": false, "internalType": "bool" }, + ], "anonymous": false, }, { "type": "event", "name": "SnapshotVotes", - "inputs": [{ "name": "votes", "type": "uint240", "indexed": false, "internalType": "uint240" }, { + "inputs": [{ "name": "votes", "type": "uint256", "indexed": false, "internalType": "uint256" }, { "name": "forEpoch", - "type": "uint16", + "type": "uint256", "indexed": false, - "internalType": "uint16", - }], + "internalType": "uint256", + }, { "name": "boldAccrued", "type": "uint256", "indexed": false, "internalType": "uint256" }], "anonymous": false, }, { "type": "event", "name": "SnapshotVotesForInitiative", - "inputs": [{ "name": "initiative", "type": "address", "indexed": false, "internalType": "address" }, { - "name": "votes", - "type": "uint240", - "indexed": false, - "internalType": "uint240", - }, { "name": "forEpoch", "type": "uint16", "indexed": false, "internalType": "uint16" }], + "inputs": [ + { "name": "initiative", "type": "address", "indexed": true, "internalType": "address" }, + { "name": "votes", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "vetos", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "forEpoch", "type": "uint256", "indexed": false, "internalType": "uint256" }, + ], "anonymous": false, }, { @@ -464,20 +676,24 @@ export const Governance = [ "name": "UnregisterInitiative", "inputs": [{ "name": "initiative", "type": "address", "indexed": false, "internalType": "address" }, { "name": "atEpoch", - "type": "uint16", + "type": "uint256", "indexed": false, - "internalType": "uint16", - }], + "internalType": "uint256", + }, { "name": "hookSuccess", "type": "bool", "indexed": false, "internalType": "bool" }], "anonymous": false, }, { "type": "event", "name": "WithdrawLQTY", "inputs": [ - { "name": "user", "type": "address", "indexed": false, "internalType": "address" }, - { "name": "withdrawnLQTY", "type": "uint256", "indexed": false, "internalType": "uint256" }, - { "name": "accruedLUSD", "type": "uint256", "indexed": false, "internalType": "uint256" }, - { "name": "accruedETH", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "user", "type": "address", "indexed": true, "internalType": "address" }, + { "name": "recipient", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "lqtyReceived", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "lqtySent", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "lusdReceived", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "lusdSent", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "ethReceived", "type": "uint256", "indexed": false, "internalType": "uint256" }, + { "name": "ethSent", "type": "uint256", "indexed": false, "internalType": "uint256" }, ], "anonymous": false, }, @@ -500,4 +716,3 @@ export const Governance = [ "inputs": [{ "name": "token", "type": "address", "internalType": "address" }], }, ] as const; - diff --git a/frontend/app/src/abi/LqtyToken.ts b/frontend/app/src/abi/LqtyToken.ts index a9730c6c9..2f1e8a48b 100644 --- a/frontend/app/src/abi/LqtyToken.ts +++ b/frontend/app/src/abi/LqtyToken.ts @@ -1,123 +1,201 @@ -export const LqtyToken = [{ - "anonymous": false, - "inputs": [{ "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address", - }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }], - "name": "Approval", - "type": "event", -}, { - "anonymous": false, - "inputs": [{ "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address", - }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }], - "name": "Transfer", - "type": "event", -}, { - "inputs": [], - "name": "DOMAIN_SEPARATOR", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function", -}, { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }, { - "internalType": "address", - "name": "", - "type": "address", - }], - "name": "allowance", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function", -}, { - "inputs": [{ "internalType": "address", "name": "spender", "type": "address" }, { - "internalType": "uint256", - "name": "amount", - "type": "uint256", - }], - "name": "approve", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function", -}, { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function", -}, { - "inputs": [], - "name": "decimals", - "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], - "stateMutability": "view", - "type": "function", -}, { - "inputs": [{ "internalType": "uint256", "name": "amt", "type": "uint256" }], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", -}, { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function", -}, { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "nonces", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function", -}, { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "uint256", "name": "deadline", "type": "uint256" }, - { "internalType": "uint8", "name": "v", "type": "uint8" }, - { "internalType": "bytes32", "name": "r", "type": "bytes32" }, - { "internalType": "bytes32", "name": "s", "type": "bytes32" }, - ], - "name": "permit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function", -}, { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function", -}, { - "inputs": [], - "name": "totalSupply", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function", -}, { - "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { - "internalType": "uint256", - "name": "amount", - "type": "uint256", - }], - "name": "transfer", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function", -}, { - "inputs": [{ "internalType": "address", "name": "from", "type": "address" }, { - "internalType": "address", - "name": "to", - "type": "address", - }, { "internalType": "uint256", "name": "amount", "type": "uint256" }], - "name": "transferFrom", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function", -}] as const; +export const LqtyToken = [ + { + "inputs": [ + { "internalType": "string", "name": "_name", "type": "string" }, + { "internalType": "string", "name": "_symbol", "type": "string" }, + { "internalType": "uint256", "name": "_tapAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "_tapPeriod", "type": "uint256" }, + ], + "stateMutability": "nonpayable", + "type": "constructor", + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address", + }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" }], + "name": "Approval", + "type": "event", + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address", + }], + "name": "OwnershipTransferred", + "type": "event", + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address", + }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" }], + "name": "Transfer", + "type": "event", + }, + { + "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }, { + "internalType": "address", + "name": "spender", + "type": "address", + }], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [{ "internalType": "address", "name": "spender", "type": "address" }, { + "internalType": "uint256", + "name": "amount", + "type": "uint256", + }], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [{ "internalType": "address", "name": "spender", "type": "address" }, { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256", + }], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [{ "internalType": "address", "name": "spender", "type": "address" }, { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256", + }], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "lastTapped", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [{ "internalType": "address", "name": "_to", "type": "address" }, { + "internalType": "uint256", + "name": "_amount", + "type": "uint256", + }], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function", + }, + { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function", + }, + { "inputs": [], "name": "tap", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "tapAmount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "tapPeriod", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [{ "internalType": "address", "name": "receiver", "type": "address" }], + "name": "tapTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { + "internalType": "uint256", + "name": "amount", + "type": "uint256", + }], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [{ "internalType": "address", "name": "from", "type": "address" }, { + "internalType": "address", + "name": "to", + "type": "address", + }, { "internalType": "uint256", "name": "amount", "type": "uint256" }], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, +] as const; diff --git a/frontend/app/src/anim-utils.ts b/frontend/app/src/anim-utils.ts new file mode 100644 index 000000000..fdf63d60e --- /dev/null +++ b/frontend/app/src/anim-utils.ts @@ -0,0 +1,31 @@ +import { sleep } from "@/src/utils"; +import { useTransition } from "@react-spring/web"; +import { useState } from "react"; + +export function useFlashTransition(duration: number = 500) { + const [show, setShow] = useState(false); + return { + flash: () => setShow(true), + transition: useTransition(show, { + from: { opacity: 0, transform: "scale(0.9)" }, + enter: () => async (next) => { + await next({ opacity: 1, transform: "scale(1)" }); + setShow(false); + }, + leave: () => async (next) => { + await sleep(duration); + await next({ opacity: 0, transform: "scale(1)" }); + }, + config: { mass: 1, tension: 2000, friction: 80 }, + }), + }; +} + +export function useAppear(show: boolean) { + return useTransition(show, { + from: { opacity: 0, transform: "scale(0.9)" }, + enter: { opacity: 1, transform: "scale(1)" }, + leave: { opacity: 0, transform: "scale(1)", immediate: true }, + config: { mass: 1, tension: 2000, friction: 80 }, + }); +} diff --git a/frontend/app/src/app/error.tsx b/frontend/app/src/app/error.tsx new file mode 100644 index 000000000..27bba6215 --- /dev/null +++ b/frontend/app/src/app/error.tsx @@ -0,0 +1,160 @@ +"use client"; + +import { ErrorBox } from "@/src/comps/ErrorBox/ErrorBox"; +import { sleep } from "@/src/utils"; +import { css } from "@/styled-system/css"; +import { AnchorButton, Button } from "@liquity2/uikit"; +import { a, useSpring } from "@react-spring/web"; +import Link from "next/link"; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ( +
+{error.message}+
+{error.stack} +
+ Let’s get you back on track. +
+ + +{key} | +
+
+ {value}
+
+ |
+
- {contracts.map(([name, address]) => ( --- {name}: {address} -- ))} -
+ {blocked.message} +
+lqty * t - offset
+