From 65309007eff15b830fc4ac8cd93e51cf16ba6c3c Mon Sep 17 00:00:00 2001 From: Artur Korotenko Date: Thu, 4 Sep 2025 21:38:54 +0500 Subject: [PATCH 01/19] chore: ALIGN-70 frontend patches and neura testnet --- frontend/app/.env | 219 +--- .../app/src/app/borrow/[collateral]/page.tsx | 5 +- frontend/app/src/app/earn/[pool]/layout.tsx | 7 +- frontend/app/src/app/earn/[pool]/page.tsx | 5 +- frontend/app/src/app/legacy/page.tsx | 5 - .../src/app/multiply/[collateral]/page.tsx | 5 +- frontend/app/src/app/stake/[action]/page.tsx | 12 - frontend/app/src/app/stake/layout.tsx | 5 - frontend/app/src/app/stake/page.tsx | 3 - .../app/src/comps/ActionCard/ActionCard.tsx | 12 +- .../app/src/comps/AppLayout/AppLayout.tsx | 3 - .../app/src/comps/AppLayout/BottomBar.tsx | 7 +- frontend/app/src/comps/AppLayout/TopBar.tsx | 3 +- .../LegacyPositionsBanner.tsx | 112 -- .../src/comps/Positions/NewPositionCard.tsx | 10 - .../src/comps/Positions/PositionCardStake.tsx | 151 --- .../app/src/comps/Positions/Positions.tsx | 15 +- .../StakePositionSummary.tsx | 550 -------- frontend/app/src/constants.ts | 83 +- frontend/app/src/content.tsx | 120 +- frontend/app/src/contracts.ts | 23 +- frontend/app/src/env.ts | 44 +- frontend/app/src/graphql/gql.ts | 18 - frontend/app/src/graphql/graphql.ts | 590 +-------- frontend/app/src/liquity-governance.ts | 841 ------------ frontend/app/src/liquity-utils.ts | 296 +---- .../screens/AccountScreen/AccountScreen.tsx | 35 +- .../src/screens/BorrowScreen/BorrowScreen.tsx | 4 +- .../EarnPoolScreen/PanelClaimRewards.tsx | 6 +- .../app/src/screens/HomeScreen/HomeScreen.tsx | 120 +- .../src/screens/LegacyScreen/LegacyScreen.tsx | 753 ----------- .../screens/LeverageScreen/LeverageScreen.tsx | 4 +- .../LoanScreen/PanelUpdateBorrowPosition.tsx | 2 +- .../PanelUpdateLeveragePosition.tsx | 6 +- .../src/screens/StakeScreen/PanelRewards.tsx | 160 --- .../src/screens/StakeScreen/PanelStaking.tsx | 294 ----- .../src/screens/StakeScreen/PanelVoting.tsx | 1159 ----------------- .../src/screens/StakeScreen/StakeScreen.tsx | 292 ----- frontend/app/src/services/TransactionFlow.tsx | 32 - frontend/app/src/subgraph.ts | 96 -- .../app/src/tx-flows/allocateVotingPower.tsx | 303 ----- .../app/src/tx-flows/closeLoanPosition.tsx | 12 +- .../src/tx-flows/legacyCloseLoanPosition.tsx | 190 --- .../src/tx-flows/legacyEarnWithdrawAll.tsx | 137 -- .../src/tx-flows/legacyRedeemCollateral.tsx | 255 ---- .../app/src/tx-flows/legacyUnstakeAll.tsx | 134 -- .../app/src/tx-flows/openBorrowPosition.tsx | 6 +- .../app/src/tx-flows/openLeveragePosition.tsx | 8 +- .../app/src/tx-flows/redeemCollateral.tsx | 2 +- .../app/src/tx-flows/stakeClaimRewards.tsx | 108 -- frontend/app/src/tx-flows/stakeDeposit.tsx | 241 ---- frontend/app/src/tx-flows/unstakeDeposit.tsx | 111 -- .../app/src/tx-flows/updateBorrowPosition.tsx | 18 +- .../src/tx-flows/updateLeveragePosition.tsx | 10 +- frontend/app/src/types.ts | 13 +- frontend/app/src/valibot-utils.ts | 34 +- frontend/app/src/wagmi-utils.ts | 12 +- frontend/uikit/src/tokens.ts | 74 +- subgraph/networks.json | 17 +- subgraph/schema.graphql | 31 - subgraph/src/Governance.mapping.ts | 201 --- subgraph/subgraph.yaml | 42 +- 62 files changed, 208 insertions(+), 7858 deletions(-) delete mode 100644 frontend/app/src/app/legacy/page.tsx delete mode 100644 frontend/app/src/app/stake/[action]/page.tsx delete mode 100644 frontend/app/src/app/stake/layout.tsx delete mode 100644 frontend/app/src/app/stake/page.tsx delete mode 100644 frontend/app/src/comps/LegacyPositionsBanner/LegacyPositionsBanner.tsx delete mode 100644 frontend/app/src/comps/Positions/PositionCardStake.tsx delete mode 100644 frontend/app/src/comps/StakePositionSummary/StakePositionSummary.tsx delete mode 100644 frontend/app/src/liquity-governance.ts delete mode 100644 frontend/app/src/screens/LegacyScreen/LegacyScreen.tsx delete mode 100644 frontend/app/src/screens/StakeScreen/PanelRewards.tsx delete mode 100644 frontend/app/src/screens/StakeScreen/PanelStaking.tsx delete mode 100644 frontend/app/src/screens/StakeScreen/PanelVoting.tsx delete mode 100644 frontend/app/src/screens/StakeScreen/StakeScreen.tsx delete mode 100644 frontend/app/src/tx-flows/allocateVotingPower.tsx delete mode 100644 frontend/app/src/tx-flows/legacyCloseLoanPosition.tsx delete mode 100644 frontend/app/src/tx-flows/legacyEarnWithdrawAll.tsx delete mode 100644 frontend/app/src/tx-flows/legacyRedeemCollateral.tsx delete mode 100644 frontend/app/src/tx-flows/legacyUnstakeAll.tsx delete mode 100644 frontend/app/src/tx-flows/stakeClaimRewards.tsx delete mode 100644 frontend/app/src/tx-flows/stakeDeposit.tsx delete mode 100644 frontend/app/src/tx-flows/unstakeDeposit.tsx delete mode 100644 subgraph/src/Governance.mapping.ts diff --git a/frontend/app/.env b/frontend/app/.env index 258be471a..8c9bc84ed 100644 --- a/frontend/app/.env +++ b/frontend/app/.env @@ -2,191 +2,52 @@ NEXT_PUBLIC_ACCOUNT_SCREEN=false NEXT_PUBLIC_DEMO_MODE=false NEXT_PUBLIC_VERCEL_ANALYTICS=false -# Get a WalletConnect project ID at https://cloud.reown.com/app NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=fd37755b4d4f8a59a1a064af3782ded4 -# Use this to indicate a specific variant (e.g. "preview"). -# This will be displayed in the app header. -# NEXT_PUBLIC_DEPLOYMENT_FLAVOR= - -# 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= - -# Uncomment to change the app commit URL (see “About” modal): -# NEXT_PUBLIC_APP_COMMIT_URL=https://github.com/liquity/bold/tree/{commit} -# Or disable it: -# NEXT_PUBLIC_APP_COMMIT_URL=false -# -# Uncomment to change the contracts commit URL (see “About” modal): -# NEXT_PUBLIC_CONTRACTS_COMMIT_URL=https://github.com/liquity/bold/tree/{commit} -# Or disable it: -# NEXT_PUBLIC_CONTRACTS_COMMIT_URL=false -# -# Uncomment to change the version URL: -# NEXT_PUBLIC_APP_VERSION_URL=https://github.com/liquity/bold/releases/tag/%40liquity2%2Fapp-v{version} -# Or disable it: -# NEXT_PUBLIC_APP_VERSION_URL=false - ###################### # Ethereum (mainnet) # ###################### -NEXT_PUBLIC_CHAIN_BLOCK_EXPLORER=Etherscan|https://etherscan.io +NEXT_PUBLIC_CHAIN_BLOCK_EXPLORER=Blockscout|https://testnet-blockscout.infra.neuraprotocol.io/ NEXT_PUBLIC_CHAIN_CONTRACT_MULTICALL=0xca11bde05977b3631167028862be2a173976ca11 -NEXT_PUBLIC_CHAIN_CURRENCY=Ether|ETH|18 -NEXT_PUBLIC_CHAIN_ID=1 -NEXT_PUBLIC_CHAIN_NAME=Ethereum -NEXT_PUBLIC_CHAIN_RPC_URL=https://cloudflare-eth.com -NEXT_PUBLIC_DELEGATE_AUTO=0x0000000000000000000000000000000000000000 -NEXT_PUBLIC_KNOWN_INITIATIVES_URL=https://api.liquity.org/v2/known-initiatives/ethereum.json -NEXT_PUBLIC_LIQUITY_STATS_URL=https://api.liquity.org/v2/ethereum.json -NEXT_PUBLIC_LIQUITY_GOVERNANCE_URL=https://api.liquity.org/v2/governance -NEXT_PUBLIC_SAFE_API_URL=https://safe-transaction-mainnet.safe.global/api -NEXT_PUBLIC_SBOLD=0x50Bd66D59911F5e086Ec87aE43C811e0D059DD11 -NEXT_PUBLIC_TROVE_EXPLORER_0=DeFi Explore|https://liquityv2.defiexplore.com/trove/{branch}/{troveId} -NEXT_PUBLIC_TROVE_EXPLORER_1=Rails|https://rails.finance/explorer/trove/{troveId}/{branch} - -# Uncomment one of these: -# -# Create an account on https://beta.graphseer.com/ and insert your API key: -# NEXT_PUBLIC_SUBGRAPH_URL=https://graph-gateway.graphops.xyz/api/ad592eaf307cf800835a228774674bd0/subgraphs/id/6bg574MHrEZXopJDYTu7S7TAvJKEMsV111gpKLM7ZCA7 -# -# Or use a subgraph API key: -NEXT_PUBLIC_SUBGRAPH_URL=https://gateway.thegraph.com/api/e6637aa857aed5275df47cc1bea63550/subgraphs/id/6bg574MHrEZXopJDYTu7S7TAvJKEMsV111gpKLM7ZCA7 - -NEXT_PUBLIC_COLL_0_TOKEN_ID=ETH -NEXT_PUBLIC_COLL_1_TOKEN_ID=WSTETH -NEXT_PUBLIC_COLL_2_TOKEN_ID=RETH - -# Use the following format for the IC strategies: -# NEXT_PUBLIC_COLL_{index}_IC_STRATEGIES={strategy_name}:{address},{strategy_name}:{address},… -# NEXT_PUBLIC_COLL_0_IC_STRATEGIES= -# NEXT_PUBLIC_COLL_1_IC_STRATEGIES= -# NEXT_PUBLIC_COLL_2_IC_STRATEGIES= - -NEXT_PUBLIC_COLL_0_CONTRACT_ACTIVE_POOL=0xeb5a8c825582965f1d84606e078620a84ab16afe -NEXT_PUBLIC_COLL_0_CONTRACT_ADDRESSES_REGISTRY=0x20f7c9ad66983f6523a0881d0f82406541417526 -NEXT_PUBLIC_COLL_0_CONTRACT_BORROWER_OPERATIONS=0x372abd1810eaf23cb9d941bbe7596dfb2c46bc65 -NEXT_PUBLIC_COLL_0_CONTRACT_COLL_SURPLUS_POOL=0xedbe2509e502c0320d2e7f8b6746a49b4b50e2bf -NEXT_PUBLIC_COLL_0_CONTRACT_COLL_TOKEN=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 -NEXT_PUBLIC_COLL_0_CONTRACT_DEFAULT_POOL=0xd4558240d50c2e219a21c9d25afd513bb6e5b1a0 +NEXT_PUBLIC_CHAIN_CURRENCY=Ankr|ANKR|18 +NEXT_PUBLIC_CHAIN_ID=267 +NEXT_PUBLIC_CHAIN_NAME=NeuraTestnet +NEXT_PUBLIC_CHAIN_RPC_URL=https://testnet.rpc.neuraprotocol.io/ + +NEXT_PUBLIC_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_cmevjjq0rde9e01ubb66qg6es/subgraphs/liquity-fork/0.0.1/gn + +NEXT_PUBLIC_COLL_0_TOKEN_ID=ANKR +NEXT_PUBLIC_COLL_1_TOKEN_ID=USN + +NEXT_PUBLIC_COLL_0_CONTRACT_ACTIVE_POOL=0xe9786d98c6203ca0746061abfbfdeeb02e0fe5da +NEXT_PUBLIC_COLL_0_CONTRACT_ADDRESSES_REGISTRY=0x9cdc1c9582136916362d84a1dc03f5aa25e318d1 +NEXT_PUBLIC_COLL_0_CONTRACT_BORROWER_OPERATIONS=0x1ca56ac150e747074114a33223262f979ffd026c +NEXT_PUBLIC_COLL_0_CONTRACT_COLL_SURPLUS_POOL=0xed8614f48a494a8fca53978f1183516a093006a8 +NEXT_PUBLIC_COLL_0_CONTRACT_COLL_TOKEN=0xBd833b6eCC30CAEaBf81dB18BB0f1e00C6997E7a +NEXT_PUBLIC_COLL_0_CONTRACT_DEFAULT_POOL=0x2c6853ee6d9311f1c9aebad7f003999ac2f011c2 +NEXT_PUBLIC_COLL_0_CONTRACT_PRICE_FEED=0x6f2c76f8105f723df8847a4ed97d3fc2fd335b7a NEXT_PUBLIC_COLL_0_CONTRACT_LEVERAGE_ZAPPER=0xdccbd7a365aee086aa3b4ede8afe895b20770ae3 -NEXT_PUBLIC_COLL_0_CONTRACT_PRICE_FEED=0xcc5f8102eb670c89a4a3c567c13851260303c24f -NEXT_PUBLIC_COLL_0_CONTRACT_SORTED_TROVES=0xa25269e41bd072513849f2e64ad221e84f3063f4 -NEXT_PUBLIC_COLL_0_CONTRACT_STABILITY_POOL=0x5721cbbd64fc7ae3ef44a0a3f9a790a9264cf9bf -NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_MANAGER=0x7bcb64b2c9206a5b699ed43363f6f98d4776cf5a -NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_NFT=0x1a0fc0b843afd9140267d25d4e575cb37a838013 -NEXT_PUBLIC_COLL_1_CONTRACT_ACTIVE_POOL=0x531a8f99c70d6a56a7cee02d6b4281650d7919a0 -NEXT_PUBLIC_COLL_1_CONTRACT_ADDRESSES_REGISTRY=0x8d733f7ea7c23cbea7c613b6ebd845d46d3aac54 -NEXT_PUBLIC_COLL_1_CONTRACT_BORROWER_OPERATIONS=0xa741a32f9dcfe6adba088fd0f97e90742d7d5da3 -NEXT_PUBLIC_COLL_1_CONTRACT_COLL_SURPLUS_POOL=0x36e6cbdf68f64cf00fc3a6c634a25be32dd0a235 -NEXT_PUBLIC_COLL_1_CONTRACT_COLL_TOKEN=0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0 -NEXT_PUBLIC_COLL_1_CONTRACT_DEFAULT_POOL=0xd796e1648526400386cc4d12fa05e5f11e6a22a1 +NEXT_PUBLIC_COLL_0_CONTRACT_SORTED_TROVES=0xc383b06357a330d1f326d8f3e0a92c8e5d918343 +NEXT_PUBLIC_COLL_0_CONTRACT_STABILITY_POOL=0x0c8a5ebc5b8bac0dfdad159ca61fd0906bf6f53e +NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_MANAGER=0x4d2044a26fde2876db02e19e4eac2e75ecb8d510 +NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_NFT=0xf92ea65bd8b2c038693e385a5b2965483b1ad60f +NEXT_PUBLIC_COLL_1_CONTRACT_ACTIVE_POOL=0x9e00f9c4b769e11f1768da12a255e2b6e3ca9969 +NEXT_PUBLIC_COLL_1_CONTRACT_ADDRESSES_REGISTRY=0x9869a3ce15587b32dec8049aabd0572fffc9a5fc +NEXT_PUBLIC_COLL_1_CONTRACT_BORROWER_OPERATIONS=0x53ca33f16a98c877f19bf64a3ecc3411d049fe9f +NEXT_PUBLIC_COLL_1_CONTRACT_COLL_SURPLUS_POOL=0xbc94877d8ecd73f329ecf38fc3c032d8b82c5fbd +NEXT_PUBLIC_COLL_1_CONTRACT_COLL_TOKEN=0xe95455FEc4Be722122401B5dCAbb2428171AfF0c +NEXT_PUBLIC_COLL_1_CONTRACT_DEFAULT_POOL=0xd7662a952fb043a444ffe52115cfcf133ed238d5 +NEXT_PUBLIC_COLL_1_CONTRACT_PRICE_FEED=0x4abf5e742d9b44d4b0f0309cd3928b2e78ebd187 NEXT_PUBLIC_COLL_1_CONTRACT_LEVERAGE_ZAPPER=0xe85230de04147c4ea363b21cdb801c1c19df0a56 -NEXT_PUBLIC_COLL_1_CONTRACT_PRICE_FEED=0xe7aa2ba9e086a379d3beb224098bc634a46e314e -NEXT_PUBLIC_COLL_1_CONTRACT_SORTED_TROVES=0x84eb85a8c25049255614f0536bea8f31682e86f1 -NEXT_PUBLIC_COLL_1_CONTRACT_STABILITY_POOL=0x9502b7c397e9aa22fe9db7ef7daf21cd2aebe56b -NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_MANAGER=0xa2895d6a3bf110561dfe4b71ca539d84e1928b22 -NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_NFT=0x857aecebf75f1012dc18e15020c97096aea31b04 -NEXT_PUBLIC_COLL_2_CONTRACT_ACTIVE_POOL=0x9074d72cc82dad1e13e454755aa8f144c479532f -NEXT_PUBLIC_COLL_2_CONTRACT_ADDRESSES_REGISTRY=0x6106046f031a22713697e04c08b330ddaf3e8789 -NEXT_PUBLIC_COLL_2_CONTRACT_BORROWER_OPERATIONS=0xe8119fc02953b27a1b48d2573855738485a17329 -NEXT_PUBLIC_COLL_2_CONTRACT_COLL_SURPLUS_POOL=0xba4a2bd8b76df84cac98eba3f4b967d8423192bf -NEXT_PUBLIC_COLL_2_CONTRACT_COLL_TOKEN=0xae78736cd615f374d3085123a210448e74fc6393 -NEXT_PUBLIC_COLL_2_CONTRACT_DEFAULT_POOL=0x5cc5cefd034fdc4728d487a72ca58a410cddcd6b -NEXT_PUBLIC_COLL_2_CONTRACT_LEVERAGE_ZAPPER=0x75036b1d6de5665c60f5c33bb4a64e8e123211a2 -NEXT_PUBLIC_COLL_2_CONTRACT_PRICE_FEED=0x34f1e9c7dcc279ec70d3c4488eb2d80fba8b7b2b -NEXT_PUBLIC_COLL_2_CONTRACT_SORTED_TROVES=0x14d8d8011df2b396ed2bbc4959bb73250324f386 -NEXT_PUBLIC_COLL_2_CONTRACT_STABILITY_POOL=0xd442e41019b7f5c4dd78f50dc03726c446148695 -NEXT_PUBLIC_COLL_2_CONTRACT_TROVE_MANAGER=0xb2b2abeb5c357a234363ff5d180912d319e3e19e -NEXT_PUBLIC_COLL_2_CONTRACT_TROVE_NFT=0x7ae430e25b67f19b431e1d1dc048a5bcf24c0873 -NEXT_PUBLIC_CONTRACT_BOLD_TOKEN=0x6440f144b7e50d6a8439336510312d2f54beb01d -NEXT_PUBLIC_CONTRACT_COLLATERAL_REGISTRY=0xf949982b91c8c61e952b3ba942cbbfaef5386684 -NEXT_PUBLIC_CONTRACT_DEBT_IN_FRONT_HELPER=0x4bb5e28fdb12891369b560f2fab3c032600677c6 -NEXT_PUBLIC_CONTRACT_EXCHANGE_HELPERS=0x2f60bab0072abec7058017f48d7256ec288c8686 -NEXT_PUBLIC_CONTRACT_GOVERNANCE=0x807def5e7d057df05c796f4bc75c3fe82bd6eee1 -NEXT_PUBLIC_CONTRACT_HINT_HELPERS=0xf0cae19c96e572234398d6665cc1147a16cbe657 -NEXT_PUBLIC_CONTRACT_LQTY_STAKING=0x4f9fbb3f1e99b56e0fe2892e623ed36a76fc605d -NEXT_PUBLIC_CONTRACT_LQTY_TOKEN=0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d -NEXT_PUBLIC_CONTRACT_LUSD_TOKEN=0x5f98805a4e8be255a32880fdec7f6728c6568ba0 -NEXT_PUBLIC_CONTRACT_MULTI_TROVE_GETTER=0xfa61db085510c64b83056db3a7acf3b6f631d235 -NEXT_PUBLIC_CONTRACT_WETH=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 - -########### -# Sepolia # -########### - -# NEXT_PUBLIC_CHAIN_BLOCK_EXPLORER=Etherscan Sepolia|https://sepolia.etherscan.io/ -# NEXT_PUBLIC_CHAIN_CONTRACT_MULTICALL=0xcA11bde05977b3631167028862bE2a173976CA11 -# NEXT_PUBLIC_CHAIN_CURRENCY=Ether|ETH|18 -# NEXT_PUBLIC_CHAIN_ID=11155111 -# NEXT_PUBLIC_CHAIN_NAME=Sepolia -# NEXT_PUBLIC_CHAIN_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com -# NEXT_PUBLIC_DELEGATE_AUTO=0x0000000000000000000000000000000000000000 -# NEXT_PUBLIC_KNOWN_INITIATIVES_URL=https://api.liquity.org/v2/known-initiatives/sepolia.json -# NEXT_PUBLIC_LIQUITY_STATS_URL=https://api.liquity.org/v2/testnet/sepolia.json -# NEXT_PUBLIC_LIQUITY_GOVERNANCE_URL= -# NEXT_PUBLIC_SAFE_API_URL=https://safe-transaction-sepolia.safe.global/api -# NEXT_PUBLIC_SUBGRAPH_URL=https://gateway.thegraph.com/api/ca398de57ba730c08b369b5b59b7e104/subgraphs/id/Fh19Rc9sm53ymzUtayzTWrbcjc8G6Zu3TQeMDQ6u2x23 - -# NEXT_PUBLIC_COLL_0_TOKEN_ID=ETH -# NEXT_PUBLIC_COLL_1_TOKEN_ID=WSTETH -# NEXT_PUBLIC_COLL_2_TOKEN_ID=RETH - -# NEXT_PUBLIC_COLL_0_CONTRACT_ACTIVE_POOL=0x50f2f8f931955bb415c4d06a63c150756eb8ef66 -# NEXT_PUBLIC_COLL_0_CONTRACT_ADDRESSES_REGISTRY=0x3734cfb34f202b933a54b0b404f1073fd27dd78a -# NEXT_PUBLIC_COLL_0_CONTRACT_BORROWER_OPERATIONS=0xe006d06c9ae1e9636e6cef0fac21179af7470297 -# NEXT_PUBLIC_COLL_0_CONTRACT_COLL_SURPLUS_POOL=0xdaa009e9c65d4a229d5595844f5451be36c42350 -# NEXT_PUBLIC_COLL_0_CONTRACT_COLL_TOKEN=0x8116d0a0e8d4f0197b428c520953f302adca0b50 -# NEXT_PUBLIC_COLL_0_CONTRACT_DEFAULT_POOL=0x4638c071e5ca35be737339f7ebe74c42f95465ea -# NEXT_PUBLIC_COLL_0_CONTRACT_LEVERAGE_ZAPPER=0x482bf4d6a2e61d259a7f97ef6aac8b3ce5dd9f99 -# NEXT_PUBLIC_COLL_0_CONTRACT_PRICE_FEED=0x8fc6c1963d7c33f8c1adf4a25d7a18d45bac5e87 -# NEXT_PUBLIC_COLL_0_CONTRACT_SORTED_TROVES=0x47dca3701ab0a70e39fd900015ac6a32bceafb87 -# NEXT_PUBLIC_COLL_0_CONTRACT_STABILITY_POOL=0x89fb98c98792c8b9e9d468148c6593fa0fc47b40 -# NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_MANAGER=0x364038750236739e0cd96d5754516c9b8168fb0c -# NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_NFT=0x814541303b140a70741613a62eef7a8d6a940bee -# NEXT_PUBLIC_COLL_1_CONTRACT_ACTIVE_POOL=0x43765f5697a4dd925b7570966160b466b75ba0b1 -# NEXT_PUBLIC_COLL_1_CONTRACT_ADDRESSES_REGISTRY=0x7026ba66c9ac775e33541b2f959c9ec19977b2fe -# NEXT_PUBLIC_COLL_1_CONTRACT_BORROWER_OPERATIONS=0x4e7de0a55e967a174f47621b9f993068f96f6898 -# NEXT_PUBLIC_COLL_1_CONTRACT_COLL_SURPLUS_POOL=0x02ecdb54ffbbeb4b42b658277dd3881a794077b2 -# NEXT_PUBLIC_COLL_1_CONTRACT_COLL_TOKEN=0xff9f477b09c6937ff6313ae90e79022609851a9c -# NEXT_PUBLIC_COLL_1_CONTRACT_DEFAULT_POOL=0x6d6bbad2d2fb319587e36f5c78d80d76bd6816f5 -# NEXT_PUBLIC_COLL_1_CONTRACT_LEVERAGE_ZAPPER=0xea7fb1919bf9bae007df10ad8b748ee75fd5971d -# NEXT_PUBLIC_COLL_1_CONTRACT_PRICE_FEED=0xb73f2741520c040ab44ed1e796d9d73b7d5ba7d8 -# NEXT_PUBLIC_COLL_1_CONTRACT_SORTED_TROVES=0x1fc168b4e3edc762b1602a35b24617073518b2e0 -# NEXT_PUBLIC_COLL_1_CONTRACT_STABILITY_POOL=0x68320bd4bbc16fe14f91501380edaa9ffe5890e1 -# NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_MANAGER=0xa7b57913b5643025a15c80ca3a56eb6fb59d095d -# NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_NFT=0xa043d764044a239ee888370c22043132ee97817e -# NEXT_PUBLIC_COLL_2_CONTRACT_ACTIVE_POOL=0x770dc60f7d51a0b9e86a22adbcc0dba80925e860 -# NEXT_PUBLIC_COLL_2_CONTRACT_ADDRESSES_REGISTRY=0xcc2034f1fbf906fcc0ddc0a4280c163dac88830f -# NEXT_PUBLIC_COLL_2_CONTRACT_BORROWER_OPERATIONS=0x274ba4234b3e0a16f830b4a07bc99a33fbc19ba8 -# NEXT_PUBLIC_COLL_2_CONTRACT_COLL_SURPLUS_POOL=0x593d0467b30c69e9b4d79971446aca038a913f15 -# NEXT_PUBLIC_COLL_2_CONTRACT_COLL_TOKEN=0xbdb72f78302e6174e48aa5872f0dd986ed6d98d9 -# NEXT_PUBLIC_COLL_2_CONTRACT_DEFAULT_POOL=0xe5dd48b7ec4c8cf407c14997f6ea0f0288c5ab28 -# NEXT_PUBLIC_COLL_2_CONTRACT_LEVERAGE_ZAPPER=0x251dfe2078a910c644289f2344fac96bffea7c02 -# NEXT_PUBLIC_COLL_2_CONTRACT_PRICE_FEED=0xb5522d218751abc57310e2862ba8ee9742367b83 -# NEXT_PUBLIC_COLL_2_CONTRACT_SORTED_TROVES=0x6706e79997613705f60a46240ab932874ffdabf1 -# NEXT_PUBLIC_COLL_2_CONTRACT_STABILITY_POOL=0x8492ad1df9f89e4b6c54c81149058172592e1c94 -# NEXT_PUBLIC_COLL_2_CONTRACT_TROVE_MANAGER=0x310fa1d1d711c75da45952029861bcf0d330aa81 -# NEXT_PUBLIC_COLL_2_CONTRACT_TROVE_NFT=0xee4e0af750653ad654933ffb53354e62b5c409ef -# NEXT_PUBLIC_CONTRACT_BOLD_TOKEN=0xb01d32c05f4aa066eef2bfd4d461833fddd56d0a -# NEXT_PUBLIC_CONTRACT_COLLATERAL_REGISTRY=0x55cefb9c04724ba3c67d92df5e386c6f1585a83b -# NEXT_PUBLIC_CONTRACT_EXCHANGE_HELPERS=0x9bd72e33348b1b71ca23400b09a261498436d261 -# NEXT_PUBLIC_CONTRACT_GOVERNANCE=0xe3f9ca5398cc3d0099c3ad37d3252e37431555b8 -# NEXT_PUBLIC_CONTRACT_HINT_HELPERS=0x81d5a871dcdf4be5cf590502188363a86dea4a1a -# NEXT_PUBLIC_CONTRACT_LQTY_STAKING=0x0000000000000000000000000000000000000000 -# NEXT_PUBLIC_CONTRACT_LQTY_TOKEN=0xaca5de8d4da62d163f765b81405963c68e3c0014 -# NEXT_PUBLIC_CONTRACT_MULTI_TROVE_GETTER=0x6fe8fa23416bd35bebe6d9a157ecd526514a2f09 -# NEXT_PUBLIC_CONTRACT_WETH=0x8116d0a0e8d4f0197b428c520953f302adca0b50 - -########################### -# Hardhat / Anvil (local) # -########################### - -# NEXT_PUBLIC_CHAIN_ID=31337 -# NEXT_PUBLIC_CHAIN_NAME=Hardhat -# NEXT_PUBLIC_CHAIN_CURRENCY=Ether|ETH|18 -# NEXT_PUBLIC_CHAIN_RPC_URL=http://127.0.0.1:8545 +NEXT_PUBLIC_COLL_1_CONTRACT_SORTED_TROVES=0xf6371a5c497170dbae162c830564fac462f206d6 +NEXT_PUBLIC_COLL_1_CONTRACT_STABILITY_POOL=0x54ccb90ef683a7256fbd600db3d64d6d1015586c +NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_MANAGER=0xb048a13fad98f35470ce962ddfce8c8450822d33 +NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_NFT=0x23197d6c5199fdcddd441fc4924b5356313dc936 + +NEXT_PUBLIC_CONTRACT_BOLD_TOKEN=0xc13441f42e0aca71ea246aba87bdcdf646a456dd +NEXT_PUBLIC_CONTRACT_COLLATERAL_REGISTRY=0x39e982e327bb27549c447cb91ae220ad8a566afa +NEXT_PUBLIC_CONTRACT_EXCHANGE_HELPERS=0x0000000000000000000000000000000000000000 +NEXT_PUBLIC_CONTRACT_HINT_HELPERS=0x7375ff5c5e993d2dfbff6b1d099d16971fae16b8 +NEXT_PUBLIC_CONTRACT_MULTI_TROVE_GETTER=0x580d8e94b61a2c3132b9b1f50d944ff4add3c3bb +NEXT_PUBLIC_CONTRACT_WETH=0xBd833b6eCC30CAEaBf81dB18BB0f1e00C6997E7a diff --git a/frontend/app/src/app/borrow/[collateral]/page.tsx b/frontend/app/src/app/borrow/[collateral]/page.tsx index 9a0422f37..cc9ea5405 100644 --- a/frontend/app/src/app/borrow/[collateral]/page.tsx +++ b/frontend/app/src/app/borrow/[collateral]/page.tsx @@ -1,8 +1,7 @@ export function generateStaticParams() { return [ - { collateral: "eth" }, - { collateral: "reth" }, - { collateral: "wsteth" }, + { collateral: "ankr" }, + { collateral: "usn" }, ]; } diff --git a/frontend/app/src/app/earn/[pool]/layout.tsx b/frontend/app/src/app/earn/[pool]/layout.tsx index ddb0f51b3..a6dd6c14b 100644 --- a/frontend/app/src/app/earn/[pool]/layout.tsx +++ b/frontend/app/src/app/earn/[pool]/layout.tsx @@ -3,9 +3,8 @@ import { SboldPoolScreen } from "@/src/screens/EarnPoolScreen/SboldPoolScreen"; export function generateStaticParams() { return [ - { pool: "eth" }, - { pool: "reth" }, - { pool: "wsteth" }, + { pool: "ankr" }, + { pool: "usn" }, { pool: "sbold" }, ]; } @@ -14,7 +13,7 @@ export default async function Layout({ params, }: { params: Promise<{ - pool: "eth" | "reth" | "wsteth" | "sbold"; + pool: "ankr" | "usn" | "sbold"; }>; }) { const { pool } = await params; diff --git a/frontend/app/src/app/earn/[pool]/page.tsx b/frontend/app/src/app/earn/[pool]/page.tsx index 1ad0372d9..61ff0058e 100644 --- a/frontend/app/src/app/earn/[pool]/page.tsx +++ b/frontend/app/src/app/earn/[pool]/page.tsx @@ -1,9 +1,8 @@ export function generateStaticParams() { return [ - { pool: "eth" }, - { pool: "reth" }, - { pool: "wsteth" }, + { pool: "ankr" }, { pool: "sbold" }, + { pool: "usn" }, ]; } diff --git a/frontend/app/src/app/legacy/page.tsx b/frontend/app/src/app/legacy/page.tsx deleted file mode 100644 index d5f639cfa..000000000 --- a/frontend/app/src/app/legacy/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { LegacyScreen } from "@/src/screens/LegacyScreen/LegacyScreen"; - -export default function Page() { - return ; -} diff --git a/frontend/app/src/app/multiply/[collateral]/page.tsx b/frontend/app/src/app/multiply/[collateral]/page.tsx index a3c084015..7f5867971 100644 --- a/frontend/app/src/app/multiply/[collateral]/page.tsx +++ b/frontend/app/src/app/multiply/[collateral]/page.tsx @@ -1,8 +1,7 @@ export function generateStaticParams() { return [ - { collateral: "eth" }, - { collateral: "reth" }, - { collateral: "wsteth" }, + { collateral: "ankr" }, + { collateral: "usn" }, ]; } diff --git a/frontend/app/src/app/stake/[action]/page.tsx b/frontend/app/src/app/stake/[action]/page.tsx deleted file mode 100644 index 7dfc5726f..000000000 --- a/frontend/app/src/app/stake/[action]/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export function generateStaticParams() { - return [ - { action: "deposit" }, - { action: "rewards" }, - { action: "voting" }, - ]; -} - -export default function Page() { - // see layout in the parent folder - return null; -} diff --git a/frontend/app/src/app/stake/layout.tsx b/frontend/app/src/app/stake/layout.tsx deleted file mode 100644 index 1caec08ba..000000000 --- a/frontend/app/src/app/stake/layout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { StakeScreen } from "@/src/screens/StakeScreen/StakeScreen"; - -export default function Layout() { - return ; -} diff --git a/frontend/app/src/app/stake/page.tsx b/frontend/app/src/app/stake/page.tsx deleted file mode 100644 index 67e085913..000000000 --- a/frontend/app/src/app/stake/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return null; -} diff --git a/frontend/app/src/comps/ActionCard/ActionCard.tsx b/frontend/app/src/comps/ActionCard/ActionCard.tsx index e99732c5f..bd9ec1f05 100644 --- a/frontend/app/src/comps/ActionCard/ActionCard.tsx +++ b/frontend/app/src/comps/ActionCard/ActionCard.tsx @@ -10,7 +10,7 @@ import { ActionIcon } from "./ActionIcon"; export function ActionCard({ type, }: { - type: "borrow" | "multiply" | "earn" | "stake"; + type: "borrow" | "multiply" | "earn"; }) { const [hint, setHint] = useState(false); const [active, setActive] = useState(false); @@ -64,16 +64,6 @@ export function ActionCard({ path: "/earn", title: ac.earn.title, })) - .with("stake", () => ({ - colors: { - background: token("colors.brandGolden"), - foreground: token("colors.brandGoldenContent"), - foregroundAlt: token("colors.brandGoldenContentAlt"), - }, - description: ac.stake.description, - path: "/stake", - title: ac.stake.title, - })) .exhaustive(); return ( diff --git a/frontend/app/src/comps/AppLayout/AppLayout.tsx b/frontend/app/src/comps/AppLayout/AppLayout.tsx index 4b11dd0cf..8bfa489d6 100644 --- a/frontend/app/src/comps/AppLayout/AppLayout.tsx +++ b/frontend/app/src/comps/AppLayout/AppLayout.tsx @@ -3,8 +3,6 @@ import type { ReactNode } from "react"; import { Banner } from "@/Banner"; -import { LegacyPositionsBanner } from "@/src/comps/LegacyPositionsBanner/LegacyPositionsBanner"; -import { LEGACY_CHECK } from "@/src/env"; import { css } from "@/styled-system/css"; import { BottomBar } from "./BottomBar"; import { TopBar } from "./TopBar"; @@ -36,7 +34,6 @@ export function AppLayout({ width: "100%", })} > - {LEGACY_CHECK && }
{ - setCompact(!medium); - }); - - const showTransition = useTransition( - Boolean(account.address && legacyPositions.data?.hasAnyPosition), - { - from: { marginTop: -41 }, - enter: { marginTop: 0 }, - leave: { marginTop: -41 }, - config: { - mass: 1, - tension: 2000, - friction: 160, - }, - }, - ); - - return showTransition((style, show) => ( - show && ( - -
-
- -
- {!compact && <>{"You still have open positions on Liquity V2-Legacy. "}} - - {compact - ? ( -
- Your Liquity V2-Legacy positions -
- ) - : ( -
- Check legacy positions -
- )} -
- -
-
- } - className={css({ - color: "yellow:400!", - textDecoration: "underline", - })} - /> -
-
-
- - ) - )); -} diff --git a/frontend/app/src/comps/Positions/NewPositionCard.tsx b/frontend/app/src/comps/Positions/NewPositionCard.tsx index 1ff7cf92b..39771e592 100644 --- a/frontend/app/src/comps/Positions/NewPositionCard.tsx +++ b/frontend/app/src/comps/Positions/NewPositionCard.tsx @@ -40,16 +40,6 @@ const actions = { path: "/earn", title: "Earn", }, - stake: { - colors: { - background: token("colors.brandGolden"), - foreground: token("colors.brandGoldenContent"), - foregroundAlt: token("colors.brandGoldenContentAlt"), - }, - description: contentActions.stake.description, - path: "/stake", - title: "Stake", - }, } as const; const actionsEntries = Object.entries(actions); diff --git a/frontend/app/src/comps/Positions/PositionCardStake.tsx b/frontend/app/src/comps/Positions/PositionCardStake.tsx deleted file mode 100644 index 8deb92635..000000000 --- a/frontend/app/src/comps/Positions/PositionCardStake.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import type { PositionStake } from "@/src/types"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { fmtnum } from "@/src/formatting"; -import { useVotingPower } from "@/src/liquity-governance"; -import { css } from "@/styled-system/css"; -import { HFlex, IconStake, TokenIcon } from "@liquity2/uikit"; -import { useRef } from "react"; -import { PositionCard } from "./PositionCard"; -import { CardRow, CardRows } from "./shared"; - -export function PositionCardStake({ - deposit, - owner, - rewards, -}: Pick< - PositionStake, - | "deposit" - | "owner" - | "rewards" ->) { - const votingPowerRef = useRef(null); - useVotingPower(owner, (share) => { - if (!votingPowerRef.current) { - return; - } - - if (!share) { - votingPowerRef.current.innerHTML = "−"; - votingPowerRef.current.title = ""; - return; - } - - const shareFormatted = fmtnum(share, { preset: "12z", scale: 100 }) + "%"; - votingPowerRef.current.innerHTML = fmtnum(share, "pct2z") + "%"; - votingPowerRef.current.title = shareFormatted; - }); - return ( - - LQTY stake - , - ]} - contextual={ -
- -
- } - main={{ - value: ( - - - - - ), - label: ( - - Staked LQTY - - ), - }} - secondary={ - - -
- Voting power -
-
-
-
-
- } - /> - -
- Rewards -
-
- - -
-
- - -
- - } - /> -
- } - /> - ); -} diff --git a/frontend/app/src/comps/Positions/Positions.tsx b/frontend/app/src/comps/Positions/Positions.tsx index 725fd6bdc..80563c843 100644 --- a/frontend/app/src/comps/Positions/Positions.tsx +++ b/frontend/app/src/comps/Positions/Positions.tsx @@ -4,7 +4,7 @@ import type { ReactNode } from "react"; import { useBreakpointName } from "@/src/breakpoints"; import { ActionCard } from "@/src/comps/ActionCard/ActionCard"; import content from "@/src/content"; -import { useEarnPositionsByAccount, useLoansByAccount, useStakePosition } from "@/src/liquity-utils"; +import { useEarnPositionsByAccount, useLoansByAccount } from "@/src/liquity-utils"; import { useSboldPosition } from "@/src/sbold"; import { css } from "@/styled-system/css"; import { a, useSpring, useTransition } from "@react-spring/web"; @@ -16,7 +16,6 @@ import { PositionCard } from "./PositionCard"; import { PositionCardEarn } from "./PositionCardEarn"; import { PositionCardLoan } from "./PositionCardLoan"; import { PositionCardSbold } from "./PositionCardSbold"; -import { PositionCardStake } from "./PositionCardStake"; type Mode = "positions" | "loading" | "actions"; @@ -24,7 +23,6 @@ const actionCards = [ "borrow", // "multiply", "earn", - "stake", ] as const; export function Positions({ @@ -47,24 +45,20 @@ export function Positions({ const loans = useLoansByAccount(address); const earnPositions = useEarnPositionsByAccount(address); const sboldPosition = useSboldPosition(address); - const stakePosition = useStakePosition(address); const isPositionsPending = Boolean( address && ( loans.isPending || earnPositions.isPending || sboldPosition.isPending - || stakePosition.isPending ), ); - const hasStakePosition = stakePosition.data && dn.gt(stakePosition.data.deposit, 0); const hasSboldPosition = sboldPosition.data && dn.gt(sboldPosition.data.sbold, 0); const positions = isPositionsPending ? [] : [ ...(loans.data ?? []), ...(earnPositions.data ?? []), - ...(stakePosition.data && hasStakePosition ? [stakePosition.data] : []), ...(sboldPosition.data && hasSboldPosition ? [sboldPosition.data] : []), ]; @@ -118,7 +112,7 @@ function PositionsGroup({ title: (mode: Mode) => ReactNode; showNewPositionCard: boolean; }) { - columns ??= mode === "actions" ? actionCards.length : 3; + columns ??= mode === "actions" ? actionCards.length : 2; const title_ = title(mode); @@ -143,10 +137,6 @@ function PositionsGroup({ index, , ]) - .with({ type: "stake" }, (p) => [ - index, - , - ]) .with({ type: "sbold" }, (p) => [ index, , @@ -160,7 +150,6 @@ function PositionsGroup({ .with("loading", () => [ [0, ], [1, ], - [2, ], ]) .with("actions", () => ( (showNewPositionCard ? actionCards : []).map((type, index) => [ diff --git a/frontend/app/src/comps/StakePositionSummary/StakePositionSummary.tsx b/frontend/app/src/comps/StakePositionSummary/StakePositionSummary.tsx deleted file mode 100644 index 55063bb26..000000000 --- a/frontend/app/src/comps/StakePositionSummary/StakePositionSummary.tsx +++ /dev/null @@ -1,550 +0,0 @@ -import type { PositionStake } from "@/src/types"; -import type { ReactNode } from "react"; - -import { useAppear } from "@/src/anim-utils"; -import { Amount } from "@/src/comps/Amount/Amount"; -import { Tag } from "@/src/comps/Tag/Tag"; -import { TagPreview } from "@/src/comps/TagPreview/TagPreview"; -import content from "@/src/content"; -import { dnum18 } from "@/src/dnum-utils"; -import { fmtnum } from "@/src/formatting"; -import { useGovernanceStats, useGovernanceUser, useVotingPower } from "@/src/liquity-governance"; -import { useAccount } from "@/src/wagmi-utils"; -import { css } from "@/styled-system/css"; -import { HFlex, IconStake, InfoTooltip, TokenIcon } from "@liquity2/uikit"; -import { a } from "@react-spring/web"; -import * as dn from "dnum"; -import { useRef } from "react"; - -export function StakePositionSummary({ - loadingState = "success", - prevStakePosition, - stakePosition, - txPreviewMode = false, -}: { - loadingState?: "error" | "pending" | "success"; - prevStakePosition?: null | PositionStake; - stakePosition: null | PositionStake; - txPreviewMode?: boolean; -}) { - const account = useAccount(); - - const govStats = useGovernanceStats(); - const govUser = useGovernanceUser(stakePosition?.owner ?? null); - - const stakedLqty = dnum18(govUser.data?.stakedLQTY); - const allocatedLqty = dnum18(govUser.data?.allocatedLQTY); - const totalStakedLqty = dnum18(govStats.data?.totalLQTYStaked); - - const stakedShare = stakedLqty && totalStakedLqty && dn.gt(totalStakedLqty, 0) - ? dn.div(stakedLqty, totalStakedLqty) - : null; - - const appear = useAppear( - !account.isConnected || ( - loadingState === "success" && govUser.status === "success" - ), - ); - - const votingPowerRef = useRef(null); - const votingPowerTooltipRef = useRef(null); - - useVotingPower(stakePosition?.owner ?? null, (share) => { - if (!votingPowerRef.current) { - return; - } - - if (!share) { - votingPowerRef.current.innerHTML = "−"; - votingPowerRef.current.title = ""; - return; - } - - const shareFormatted = fmtnum(share, { preset: "12z", scale: 100 }) + "%"; - - votingPowerRef.current.innerHTML = fmtnum(share, "pct2z") + "%"; - votingPowerRef.current.title = shareFormatted; - - if (votingPowerTooltipRef.current) { - votingPowerTooltipRef.current.innerHTML = shareFormatted; - } - }); - - return ( -
- {txPreviewMode && } -
-

-
-
- -
- LQTY Stake -
-

-
-
-
-
-
- {appear((style, show) => ( - show && ( - -
- -
- -
- ) - ))} - {prevStakePosition - && stakePosition - && !dn.eq(prevStakePosition.deposit, stakePosition.deposit) - && ( -
- {fmtnum(prevStakePosition.deposit, 2)} -
- )} -
-
-
- Staked -
-
-
-
-
-
-
- Rewards -
- - - - - - - - - - -
- {!txPreviewMode && ( -
-
- Voting share -
- {appear((style, show) => ( - show && ( - - - {!txPreviewMode && ( - -

- {content.stakeScreen.infoTooltips.votingShare} -

- {account.address && stakedLqty && ( -
- - } - /> - - } - /> -
- )} -
- ), - footerLink: { - href: content.stakeScreen.learnMore[0], - label: content.stakeScreen.learnMore[1], - }, - }} - /> - )} - - ) - ))} -
- )} - {!txPreviewMode && ( -
-
- Voting power -
- {appear((style, show) => ( - show && ( - -
-
- {!txPreviewMode && ( - -

- {content.stakeScreen.infoTooltips.votingPower} -

- {account.address && (govUser.data?.stakedLQTY ?? 0n) > 0n && ( -
- } - /> -
- )} -
- ), - footerLink: { - href: content.stakeScreen.learnMore[0], - label: content.stakeScreen.learnMore[1], - }, - }} - /> - )} - - ) - ))} -
- )} - {!txPreviewMode && ( -
-
- Allocated -
-
- - - {stakedLqty - && allocatedLqty - && dn.gt(allocatedLqty, 0) - && dn.lt(allocatedLqty, stakedLqty) - && ( - - partial - - )} -
-
- )} -
- - - ); -} - -function TooltipRow({ - label, - value, -}: { - label: ReactNode; - value: ReactNode; -}) { - return ( -
-
- {label} -
-
- {value} -
-
- ); -} diff --git a/frontend/app/src/constants.ts b/frontend/app/src/constants.ts index 0e1f90b82..98b41c9db 100644 --- a/frontend/app/src/constants.ts +++ b/frontend/app/src/constants.ts @@ -2,10 +2,8 @@ import type { BranchId, ChainId, CollateralSymbol, IcStrategy, RiskLevel } from "@/src/types"; -import { vEnvLegacyCheck } from "@/src/valibot-utils"; import { norm } from "@liquity2/uikit"; import * as dn from "dnum"; -import * as v from "valibot"; // make sure the icons in /public/fork-icons/ // are 54x54px, especially for PNGs. @@ -66,9 +64,8 @@ export const TROVE_STATUS_CLOSED_BY_LIQUIDATION = 3; export const TROVE_STATUS_ZOMBIE = 4; export const MAX_COLLATERAL_DEPOSITS: Record = { - ETH: dn.from(100_000_000n, 18), - WSTETH: dn.from(100_000_000n, 18), - RETH: dn.from(100_000_000n, 18), + ANKR: dn.from(100_000_000n, 18), + USN: dn.from(100_000_000n, 18), }; // LTV factor suggestions, as ratios of the multiply factor range @@ -97,73 +94,6 @@ export const REDEMPTION_RISK: Record, number> = { low: 0.60, // 60% of total debt in front }; -// default LEGACY_CHECKS when not set by the env -export const DEFAULT_LEGACY_CHECKS = new Map< - ChainId, - Exclude>, boolean> ->([ - // mainnet - [1, { - BOLD_TOKEN: "0xb01dd87b29d187f3e3a4bf6cdaebfb97f3d9ab98", - COLLATERAL_REGISTRY: "0xd99de73b95236f69a559117ecd6f519af780f3f7", - GOVERNANCE: "0x636deb767cd7d0f15ca4ab8ea9a9b26e98b426ac", - INITIATIVES_SNAPSHOT_URL: "/initiatives-snapshot-1.json", - TROVES_SNAPSHOT_URL: "/troves-snapshot-1.json", - BRANCHES: [{ - symbol: "ETH", - name: "ETH", - COLL_TOKEN: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - LEVERAGE_ZAPPER: "0x978d7188ae01881d254ad7e94874653b0c268004", - STABILITY_POOL: "0xf69eb8c0d95d4094c16686769460f678727393cf", - TROVE_MANAGER: "0x81d78814df42da2cab0e8870c477bc3ed861de66", - }, { - symbol: "WSTETH", - name: "wstETH", - COLL_TOKEN: "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", - LEVERAGE_ZAPPER: "0xc3d864adc2a9b49d52e640b697241408d896179f", - STABILITY_POOL: "0xcf46dab575c364a8b91bda147720ff4361f4627f", - TROVE_MANAGER: "0xb47ef60132deabc89580fd40e49c062d93070046", - }, { - symbol: "RETH", - name: "rETH", - COLL_TOKEN: "0xae78736cd615f374d3085123a210448e74fc6393", - LEVERAGE_ZAPPER: "0x7d5f19a1e48479a95c4eb40fd1a534585026e7e5", - STABILITY_POOL: "0xc4463b26be1a6064000558a84ef9b6a58abe4f7a", - TROVE_MANAGER: "0xde026433882a9dded65cac4fff8402fafff40fca", - }], - }], - // sepolia - [11155111, { - BOLD_TOKEN: "0xb01d32c05f4aa066eef2bfd4d461833fddd56d0a", - COLLATERAL_REGISTRY: "0x55cefb9c04724ba3c67d92df5e386c6f1585a83b", - GOVERNANCE: "0xe3f9ca5398cc3d0099c3ad37d3252e37431555b8", - INITIATIVES_SNAPSHOT_URL: "/initiatives-snapshot-11155111.json", - TROVES_SNAPSHOT_URL: "/troves-snapshot-11155111.json", - BRANCHES: [{ - symbol: "ETH", - name: "ETH", - COLL_TOKEN: "0x8116d0a0e8d4f0197b428c520953f302adca0b50", - LEVERAGE_ZAPPER: "0x482bf4d6a2e61d259a7f97ef6aac8b3ce5dd9f99", - STABILITY_POOL: "0x89fb98c98792c8b9e9d468148c6593fa0fc47b40", - TROVE_MANAGER: "0x364038750236739e0cd96d5754516c9b8168fb0c", - }, { - symbol: "WSTETH", - name: "wstETH", - COLL_TOKEN: "0xff9f477b09c6937ff6313ae90e79022609851a9c", - LEVERAGE_ZAPPER: "0xea7fb1919bf9bae007df10ad8b748ee75fd5971d", - STABILITY_POOL: "0x68320bd4bbc16fe14f91501380edaa9ffe5890e1", - TROVE_MANAGER: "0xa7b57913b5643025a15c80ca3a56eb6fb59d095d", - }, { - symbol: "RETH", - name: "rETH", - COLL_TOKEN: "0xbdb72f78302e6174e48aa5872f0dd986ed6d98d9", - LEVERAGE_ZAPPER: "0x251dfe2078a910c644289f2344fac96bffea7c02", - STABILITY_POOL: "0x8492ad1df9f89e4b6c54c81149058172592e1c94", - TROVE_MANAGER: "0x310fa1d1d711c75da45952029861bcf0d330aa81", - }], - }], -]); - // default COLL_$INDEX_IC_STRATEGIES values when not set by the env export const DEFAULT_STRATEGIES: Array<[ ChainId, @@ -171,21 +101,16 @@ export const DEFAULT_STRATEGIES: Array<[ ]> = [ // mainnet [1, [ - // ETH + // ANKR [0, [{ name: "Conservative Strategy", address: "0xE507E4d0763851A6287238aadD243948D18AB60a", }]], - // WSTETH + // USN [1, [{ name: "Conservative Strategy", address: "0x8869a6FB59a8Df330F90D9Fbf46eBfaFf6D4BC14", }]], - // RETH - [2, [{ - name: "Conservative Strategy", - address: "0x7700B2D305f47aE82e9598BAb6D7CCb57299A82b", - }]], ]], ]; diff --git a/frontend/app/src/content.tsx b/frontend/app/src/content.tsx index da786a841..4e3f59ec2 100644 --- a/frontend/app/src/content.tsx +++ b/frontend/app/src/content.tsx @@ -9,7 +9,7 @@ export default { appName: "Liquity V2", appDescription: ` Liquity V2 is a new borrowing protocol that lets users - deposit ETH or LSTs as collateral and mint the stablecoin BOLD. + deposit ANKR or LSTs as collateral and mint the stablecoin BOLD. `, appUrl: typeof window === "undefined" ? "https://www.liquity.org/" @@ -24,7 +24,6 @@ export default { borrow: "Borrow", multiply: "Multiply", earn: "Earn", - stake: "Stake", }, accountButton: { @@ -66,10 +65,10 @@ export default { <>The collateral price at which a loan can be liquidated., ], ethPrice: [ - "ETH Price", + "ANKR Price", <> - The current price of ETH, as reported by the oracle. The ETH price is used to calculate the Loan-To-Value (LTV) - ratio of a loan. + The current price of ANKR, as reported by the oracle. The ANKR price is used to calculate the Loan-To-Value + (LTV) ratio of a loan. , ], interestRateBoldPerYear: [ @@ -206,20 +205,16 @@ export default { }, multiply: { title: "Multiply", - description: "Increase your exposure to ETH and its staking yield with a single click", + description: "Increase your exposure to ANKR and its staking yield with a single click", }, earn: { title: "Earn with BOLD", description: "Deposit BOLD to earn protocol revenues and liquidation proceeds", }, - stake: { - title: "Stake LQTY", - description: "Direct protocol incentives with LQTY while earning from Liquity V1", - }, }, earnTable: { title: "Earn rewards with BOLD", - subtitle: "Earn BOLD & (staked) ETH rewards by depositing your BOLD in a stability pool", + subtitle: "Earn BOLD & (staked) ANKR rewards by depositing your BOLD in a stability pool", forksInfo: { text: ( <> @@ -247,7 +242,7 @@ export default { }, infoTooltips: { avgInterestRate: [ - "The current average interest rate being paid by ETH-backed positions.", + "The current average interest rate being paid by ANKR-backed positions.", ], spApr: [ "Annual Percentage Rate", @@ -278,7 +273,7 @@ export default { label: "Loan", }, liquidationPriceField: { - label: "ETH liquidation price", + label: "ANKR liquidation price", }, interestRateField: { label: "Interest rate", @@ -302,7 +297,7 @@ export default { label: "You deposit", }, liquidationPriceField: { - label: "ETH liquidation price", + label: "ANKR liquidation price", }, interestRateField: { label: "Interest rate", @@ -422,7 +417,7 @@ export default { "Average annualized return for BOLD deposits over the past 7 days.", ], rewardsEth: [ - "ETH rewards", + "ANKR rewards", "Your proceeds from liquidations conducted by this stability pool.", ], rewardsBold: [ @@ -431,103 +426,8 @@ export default { ], }, }, - - // Stake screen - stakeScreen: { - headline: (lqtyIcon: N) => ( - <> - Stake - {lqtyIcon} LQTY & get - voting power - - ), - subheading: ( - <> - By staking LQTY you can vote on incentives for Liquity V2, while still earning Liquity V1 fees. - - ), - learnMore: [ - "https://docs.liquity.org/v2-faq/lqty-staking", - "Learn more", - ], - accountDetails: { - myDeposit: "My deposit", - votingPower: "Voting power", - votingPowerHelp: ( - <> - Voting power is the percentage of the total staked LQTY that you own. - - ), - unclaimed: "Unclaimed rewards", - }, - tabs: { - deposit: "Staking", - rewards: "Rewards", - voting: "Voting", - }, - depositPanel: { - label: "Deposit", - shareLabel: "Pool share", - rewardsLabel: "Available rewards", - action: "Next: Summary", - }, - rewardsPanel: { - label: "You claim", - details: (usdAmount: N, fee: N) => ( - <> - ~${usdAmount} • Expected gas fee ~${fee} - - ), - action: "Next: Summary", - }, - votingPanel: { - title: "Allocate your voting power", - intro: ( - <> - Direct incentives from Liquity V2 protocol revenues towards liquidity providers for BOLD. Upvote from Thursday - to Tuesday. Downvote all week. Learn more - - ), - }, - infoTooltips: { - alsoClaimRewardsDeposit: [ - <> - Rewards will be paid out as part of the update transaction. - , - ], - votingShare: ( - <> - Your voting share is the amount of LQTY you have staked and that is available to vote, divided by the total - amount of LQTY staked via the governance contract. - - ), - votingPower: ( - <> - Your relative voting power changes over time, depending on your and others allocations of LQTY. - - ), - }, - }, } as const; -function Link({ - href, - children, -}: { - href: string; - children: N; -}) { - const props = !href.startsWith("http") ? {} : { - target: "_blank", - rel: "noopener noreferrer", - }; - return ( - - {children} - - ); -} - function NoWrap({ children, gap = 8, diff --git a/frontend/app/src/contracts.ts b/frontend/app/src/contracts.ts index 56ef64cf1..f46989fe4 100644 --- a/frontend/app/src/contracts.ts +++ b/frontend/app/src/contracts.ts @@ -5,15 +5,11 @@ import { ActivePool } from "@/src/abi/ActivePool"; import { BorrowerOperations } from "@/src/abi/BorrowerOperations"; import { CollateralRegistry } from "@/src/abi/CollateralRegistry"; import { CollSurplusPool } from "@/src/abi/CollSurplusPool"; -import { DebtInFrontHelper } from "@/src/abi/DebtInFrontHelper"; import { DefaultPool } from "@/src/abi/DefaultPool"; import { ExchangeHelpers } from "@/src/abi/ExchangeHelpers"; -import { Governance } from "@/src/abi/Governance"; import { HintHelpers } from "@/src/abi/HintHelpers"; import { LeverageLSTZapper } from "@/src/abi/LeverageLSTZapper"; import { LeverageWETHZapper } from "@/src/abi/LeverageWETHZapper"; -import { LqtyStaking } from "@/src/abi/LqtyStaking"; -import { LqtyToken } from "@/src/abi/LqtyToken"; import { MultiTroveGetter } from "@/src/abi/MultiTroveGetter"; import { PriceFeed } from "@/src/abi/PriceFeed"; import { SortedTroves } from "@/src/abi/SortedTroves"; @@ -23,13 +19,8 @@ import { TroveNFT } from "@/src/abi/TroveNFT"; import { CONTRACT_BOLD_TOKEN, CONTRACT_COLLATERAL_REGISTRY, - CONTRACT_DEBT_IN_FRONT_HELPER, CONTRACT_EXCHANGE_HELPERS, - CONTRACT_GOVERNANCE, CONTRACT_HINT_HELPERS, - CONTRACT_LQTY_STAKING, - CONTRACT_LQTY_TOKEN, - CONTRACT_LUSD_TOKEN, CONTRACT_MULTI_TROVE_GETTER, CONTRACT_WETH, ENV_BRANCHES, @@ -39,13 +30,8 @@ import { erc20Abi, zeroAddress } from "viem"; const protocolAbis = { BoldToken: erc20Abi, CollateralRegistry, - DebtInFrontHelper, ExchangeHelpers, - Governance, HintHelpers, - LqtyStaking, - LqtyToken, - LusdToken: erc20Abi, MultiTroveGetter, WETH: erc20Abi, } as const; @@ -120,16 +106,11 @@ export const CONTRACTS: Contracts = { abi: abis.CollateralRegistry, address: CONTRACT_COLLATERAL_REGISTRY, }, - DebtInFrontHelper: { abi: abis.DebtInFrontHelper, address: CONTRACT_DEBT_IN_FRONT_HELPER }, - Governance: { abi: abis.Governance, address: CONTRACT_GOVERNANCE }, ExchangeHelpers: { abi: abis.ExchangeHelpers, address: CONTRACT_EXCHANGE_HELPERS, }, HintHelpers: { abi: abis.HintHelpers, address: CONTRACT_HINT_HELPERS }, - LqtyStaking: { abi: abis.LqtyStaking, address: CONTRACT_LQTY_STAKING }, - LqtyToken: { abi: abis.LqtyToken, address: CONTRACT_LQTY_TOKEN }, - LusdToken: { abi: abis.LusdToken, address: CONTRACT_LUSD_TOKEN }, MultiTroveGetter: { abi: abis.MultiTroveGetter, address: CONTRACT_MULTI_TROVE_GETTER, @@ -152,11 +133,11 @@ export const CONTRACTS: Contracts = { CollToken: { address: contracts.COLL_TOKEN, abi: abis.CollToken }, DefaultPool: { address: contracts.DEFAULT_POOL, abi: abis.DefaultPool }, LeverageLSTZapper: { - address: symbol === "ETH" ? zeroAddress : contracts.LEVERAGE_ZAPPER, + address: symbol === "ANKR" ? zeroAddress : contracts.LEVERAGE_ZAPPER, abi: abis.LeverageLSTZapper, }, LeverageWETHZapper: { - address: symbol === "ETH" ? contracts.LEVERAGE_ZAPPER : zeroAddress, + address: symbol === "ANKR" ? contracts.LEVERAGE_ZAPPER : zeroAddress, abi: abis.LeverageWETHZapper, }, PriceFeed: { address: contracts.PRICE_FEED, abi: abis.PriceFeed }, diff --git a/frontend/app/src/env.ts b/frontend/app/src/env.ts index 735873958..1c2a40669 100644 --- a/frontend/app/src/env.ts +++ b/frontend/app/src/env.ts @@ -2,14 +2,13 @@ import type { Address, Branch, BranchId, IcStrategy } from "@/src/types"; -import { DEFAULT_COMMIT_URL, DEFAULT_LEGACY_CHECKS, DEFAULT_STRATEGIES, DEFAULT_VERSION_URL } from "@/src/constants"; +import { DEFAULT_COMMIT_URL, DEFAULT_STRATEGIES, DEFAULT_VERSION_URL } from "@/src/constants"; import { isBranchId } from "@/src/types"; import { vAddress, vEnvAddressAndBlock, vEnvCurrency, vEnvFlag, - vEnvLegacyCheck, vEnvLink, vEnvUrlOrDefault, vIcStrategy, @@ -29,9 +28,8 @@ function isIcStrategyList(value: unknown): value is IcStrategy[] { } export const CollateralSymbolSchema = v.union([ - v.literal("ETH"), - v.literal("RETH"), - v.literal("WSTETH"), + v.literal("ANKR"), + v.literal("USN"), ]); function isCollateralSymbol(value: unknown) { @@ -176,7 +174,6 @@ export const EnvSchema = v.pipe( v.transform((value) => value.trim() || null), ), KNOWN_INITIATIVES_URL: v.optional(v.pipe(v.string(), v.url())), - LEGACY_CHECK: v.optional(vEnvLegacyCheck(), "true"), LIQUITY_STATS_URL: v.optional(v.pipe(v.string(), v.url())), LIQUITY_GOVERNANCE_URL: v.optional(v.union([v.pipe(v.string(), v.url()), v.literal("")])), SAFE_API_URL: v.optional(v.union([v.pipe(v.string(), v.url()), v.literal("")])), @@ -196,13 +193,8 @@ export const EnvSchema = v.pipe( CONTRACT_BOLD_TOKEN: vAddress(), CONTRACT_COLLATERAL_REGISTRY: vAddress(), - CONTRACT_DEBT_IN_FRONT_HELPER: vAddress(), CONTRACT_EXCHANGE_HELPERS: vAddress(), - CONTRACT_GOVERNANCE: vAddress(), CONTRACT_HINT_HELPERS: vAddress(), - CONTRACT_LQTY_STAKING: vAddress(), - CONTRACT_LQTY_TOKEN: vAddress(), - CONTRACT_LUSD_TOKEN: vAddress(), CONTRACT_MULTI_TROVE_GETTER: vAddress(), CONTRACT_WETH: vAddress(), @@ -286,11 +278,7 @@ export const EnvSchema = v.pipe( } } - const legacyCheck = env.LEGACY_CHECK === true - ? DEFAULT_LEGACY_CHECKS.get(env.CHAIN_ID) ?? null - : env.LEGACY_CHECK === false - ? null - : env.LEGACY_CHECK; + const legacyCheck = null; return { ...env, @@ -353,23 +341,16 @@ const parsedEnv = v.safeParse(EnvSchema, { CONTRACT_BOLD_TOKEN: process.env.NEXT_PUBLIC_CONTRACT_BOLD_TOKEN, CONTRACT_COLLATERAL_REGISTRY: process.env.NEXT_PUBLIC_CONTRACT_COLLATERAL_REGISTRY, - CONTRACT_DEBT_IN_FRONT_HELPER: process.env.NEXT_PUBLIC_CONTRACT_DEBT_IN_FRONT_HELPER, CONTRACT_EXCHANGE_HELPERS: process.env.NEXT_PUBLIC_CONTRACT_EXCHANGE_HELPERS, - CONTRACT_GOVERNANCE: process.env.NEXT_PUBLIC_CONTRACT_GOVERNANCE, CONTRACT_HINT_HELPERS: process.env.NEXT_PUBLIC_CONTRACT_HINT_HELPERS, - CONTRACT_LQTY_STAKING: process.env.NEXT_PUBLIC_CONTRACT_LQTY_STAKING, - CONTRACT_LQTY_TOKEN: process.env.NEXT_PUBLIC_CONTRACT_LQTY_TOKEN, - CONTRACT_LUSD_TOKEN: process.env.NEXT_PUBLIC_CONTRACT_LUSD_TOKEN, CONTRACT_MULTI_TROVE_GETTER: process.env.NEXT_PUBLIC_CONTRACT_MULTI_TROVE_GETTER, CONTRACT_WETH: process.env.NEXT_PUBLIC_CONTRACT_WETH, COLL_0_TOKEN_ID: process.env.NEXT_PUBLIC_COLL_0_TOKEN_ID, COLL_1_TOKEN_ID: process.env.NEXT_PUBLIC_COLL_1_TOKEN_ID, - COLL_2_TOKEN_ID: process.env.NEXT_PUBLIC_COLL_2_TOKEN_ID, COLL_0_IC_STRATEGIES: process.env.NEXT_PUBLIC_COLL_0_IC_STRATEGIES, COLL_1_IC_STRATEGIES: process.env.NEXT_PUBLIC_COLL_1_IC_STRATEGIES, - COLL_2_IC_STRATEGIES: process.env.NEXT_PUBLIC_COLL_2_IC_STRATEGIES, COLL_0_CONTRACT_ACTIVE_POOL: process.env.NEXT_PUBLIC_COLL_0_CONTRACT_ACTIVE_POOL, COLL_0_CONTRACT_BORROWER_OPERATIONS: process.env.NEXT_PUBLIC_COLL_0_CONTRACT_BORROWER_OPERATIONS, @@ -394,18 +375,6 @@ const parsedEnv = v.safeParse(EnvSchema, { COLL_1_CONTRACT_STABILITY_POOL: process.env.NEXT_PUBLIC_COLL_1_CONTRACT_STABILITY_POOL, COLL_1_CONTRACT_TROVE_MANAGER: process.env.NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_MANAGER, COLL_1_CONTRACT_TROVE_NFT: process.env.NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_NFT, - - COLL_2_CONTRACT_ACTIVE_POOL: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_ACTIVE_POOL, - COLL_2_CONTRACT_BORROWER_OPERATIONS: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_BORROWER_OPERATIONS, - COLL_2_CONTRACT_COLL_SURPLUS_POOL: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_COLL_SURPLUS_POOL, - COLL_2_CONTRACT_COLL_TOKEN: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_COLL_TOKEN, - COLL_2_CONTRACT_DEFAULT_POOL: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_DEFAULT_POOL, - COLL_2_CONTRACT_LEVERAGE_ZAPPER: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_LEVERAGE_ZAPPER, - COLL_2_CONTRACT_PRICE_FEED: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_PRICE_FEED, - COLL_2_CONTRACT_SORTED_TROVES: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_SORTED_TROVES, - COLL_2_CONTRACT_STABILITY_POOL: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_STABILITY_POOL, - COLL_2_CONTRACT_TROVE_MANAGER: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_TROVE_MANAGER, - COLL_2_CONTRACT_TROVE_NFT: process.env.NEXT_PUBLIC_COLL_2_CONTRACT_TROVE_NFT, }); if (!parsedEnv.success) { @@ -443,13 +412,8 @@ export const { CONTRACTS_COMMIT_URL, CONTRACT_BOLD_TOKEN, CONTRACT_COLLATERAL_REGISTRY, - CONTRACT_DEBT_IN_FRONT_HELPER, CONTRACT_EXCHANGE_HELPERS, - CONTRACT_GOVERNANCE, CONTRACT_HINT_HELPERS, - CONTRACT_LQTY_STAKING, - CONTRACT_LQTY_TOKEN, - CONTRACT_LUSD_TOKEN, CONTRACT_MULTI_TROVE_GETTER, CONTRACT_WETH, DEPLOYMENT_FLAVOR, diff --git a/frontend/app/src/graphql/gql.ts b/frontend/app/src/graphql/gql.ts index f304963aa..1e75ea9ab 100644 --- a/frontend/app/src/graphql/gql.ts +++ b/frontend/app/src/graphql/gql.ts @@ -21,9 +21,6 @@ type Documents = { "\n query TroveById($id: ID!) {\n trove(id: $id) {\n id\n borrower\n closedAt\n createdAt\n lastUserActionAt\n mightBeLeveraged\n previousOwner\n status\n debt\n redemptionCount\n redeemedColl\n redeemedDebt\n }\n }\n": typeof types.TroveByIdDocument, "\n query InterestBatches($ids: [ID!]!) {\n interestBatches(where: { id_in: $ids }) {\n collateral {\n collIndex\n }\n batchManager\n debt\n coll\n annualInterestRate\n annualManagementFee\n }\n }\n": typeof types.InterestBatchesDocument, "\n query AllInterestRateBrackets {\n interestRateBrackets(\n first: 1000\n where: { totalDebt_gt: 0 }\n orderBy: rate\n ) {\n collateral {\n collIndex\n }\n rate\n totalDebt\n }\n }\n": typeof types.AllInterestRateBracketsDocument, - "\n query GovernanceGlobalData {\n governanceInitiatives {\n id\n }\n\n governanceVotingPower(id: \"total\") {\n allocatedLQTY\n allocatedOffset\n unallocatedLQTY\n unallocatedOffset\n }\n }\n": typeof types.GovernanceGlobalDataDocument, - "\n query UserAllocationHistory($user: String) {\n governanceAllocations(\n where: { user: $user }\n orderBy: epoch\n orderDirection: desc\n first: 1000\n ) {\n epoch\n initiative { id }\n voteLQTY\n vetoLQTY\n voteOffset\n vetoOffset\n }\n }\n": typeof types.UserAllocationHistoryDocument, - "\n query TotalAllocationHistory($initiative: String) {\n governanceAllocations(\n where: { initiative: $initiative, user: null }\n orderBy: epoch\n orderDirection: desc\n first: 1000\n ) {\n epoch\n voteLQTY\n vetoLQTY\n voteOffset\n vetoOffset\n }\n }\n": typeof types.TotalAllocationHistoryDocument, }; const documents: Documents = { "\n query BlockNumber {\n _meta {\n block {\n number\n }\n }\n }\n": types.BlockNumberDocument, @@ -32,9 +29,6 @@ const documents: Documents = { "\n query TroveById($id: ID!) {\n trove(id: $id) {\n id\n borrower\n closedAt\n createdAt\n lastUserActionAt\n mightBeLeveraged\n previousOwner\n status\n debt\n redemptionCount\n redeemedColl\n redeemedDebt\n }\n }\n": types.TroveByIdDocument, "\n query InterestBatches($ids: [ID!]!) {\n interestBatches(where: { id_in: $ids }) {\n collateral {\n collIndex\n }\n batchManager\n debt\n coll\n annualInterestRate\n annualManagementFee\n }\n }\n": types.InterestBatchesDocument, "\n query AllInterestRateBrackets {\n interestRateBrackets(\n first: 1000\n where: { totalDebt_gt: 0 }\n orderBy: rate\n ) {\n collateral {\n collIndex\n }\n rate\n totalDebt\n }\n }\n": types.AllInterestRateBracketsDocument, - "\n query GovernanceGlobalData {\n governanceInitiatives {\n id\n }\n\n governanceVotingPower(id: \"total\") {\n allocatedLQTY\n allocatedOffset\n unallocatedLQTY\n unallocatedOffset\n }\n }\n": types.GovernanceGlobalDataDocument, - "\n query UserAllocationHistory($user: String) {\n governanceAllocations(\n where: { user: $user }\n orderBy: epoch\n orderDirection: desc\n first: 1000\n ) {\n epoch\n initiative { id }\n voteLQTY\n vetoLQTY\n voteOffset\n vetoOffset\n }\n }\n": types.UserAllocationHistoryDocument, - "\n query TotalAllocationHistory($initiative: String) {\n governanceAllocations(\n where: { initiative: $initiative, user: null }\n orderBy: epoch\n orderDirection: desc\n first: 1000\n ) {\n epoch\n voteLQTY\n vetoLQTY\n voteOffset\n vetoOffset\n }\n }\n": types.TotalAllocationHistoryDocument, }; /** @@ -61,18 +55,6 @@ export function graphql(source: "\n query InterestBatches($ids: [ID!]!) {\n * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n query AllInterestRateBrackets {\n interestRateBrackets(\n first: 1000\n where: { totalDebt_gt: 0 }\n orderBy: rate\n ) {\n collateral {\n collIndex\n }\n rate\n totalDebt\n }\n }\n"): typeof import('./graphql').AllInterestRateBracketsDocument; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query GovernanceGlobalData {\n governanceInitiatives {\n id\n }\n\n governanceVotingPower(id: \"total\") {\n allocatedLQTY\n allocatedOffset\n unallocatedLQTY\n unallocatedOffset\n }\n }\n"): typeof import('./graphql').GovernanceGlobalDataDocument; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query UserAllocationHistory($user: String) {\n governanceAllocations(\n where: { user: $user }\n orderBy: epoch\n orderDirection: desc\n first: 1000\n ) {\n epoch\n initiative { id }\n voteLQTY\n vetoLQTY\n voteOffset\n vetoOffset\n }\n }\n"): typeof import('./graphql').UserAllocationHistoryDocument; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query TotalAllocationHistory($initiative: String) {\n governanceAllocations(\n where: { initiative: $initiative, user: null }\n orderBy: epoch\n orderDirection: desc\n first: 1000\n ) {\n epoch\n voteLQTY\n vetoLQTY\n voteOffset\n vetoOffset\n }\n }\n"): typeof import('./graphql').TotalAllocationHistoryDocument; export function graphql(source: string) { diff --git a/frontend/app/src/graphql/graphql.ts b/frontend/app/src/graphql/graphql.ts index c212761a9..6c24d92ac 100644 --- a/frontend/app/src/graphql/graphql.ts +++ b/frontend/app/src/graphql/graphql.ts @@ -282,322 +282,6 @@ export enum Collateral_OrderBy { Troves = 'troves' } -export type GovernanceAllocation = { - __typename?: 'GovernanceAllocation'; - epoch: Scalars['BigInt']['output']; - id: Scalars['ID']['output']; - initiative: GovernanceInitiative; - user?: Maybe; - vetoLQTY: Scalars['BigInt']['output']; - vetoOffset: Scalars['BigInt']['output']; - voteLQTY: Scalars['BigInt']['output']; - voteOffset: Scalars['BigInt']['output']; -}; - -export type GovernanceAllocationIndex = { - __typename?: 'GovernanceAllocationIndex'; - id: Scalars['ID']['output']; - initiative: GovernanceInitiative; - latestAllocation: GovernanceAllocation; - user?: Maybe; -}; - -export type GovernanceAllocationIndex_Filter = { - /** Filter for the block changed event. */ - _change_block?: InputMaybe; - and?: InputMaybe>>; - id?: InputMaybe; - id_gt?: InputMaybe; - id_gte?: InputMaybe; - id_in?: InputMaybe>; - id_lt?: InputMaybe; - id_lte?: InputMaybe; - id_not?: InputMaybe; - id_not_in?: InputMaybe>; - initiative?: InputMaybe; - initiative_?: InputMaybe; - initiative_contains?: InputMaybe; - initiative_contains_nocase?: InputMaybe; - initiative_ends_with?: InputMaybe; - initiative_ends_with_nocase?: InputMaybe; - initiative_gt?: InputMaybe; - initiative_gte?: InputMaybe; - initiative_in?: InputMaybe>; - initiative_lt?: InputMaybe; - initiative_lte?: InputMaybe; - initiative_not?: InputMaybe; - initiative_not_contains?: InputMaybe; - initiative_not_contains_nocase?: InputMaybe; - initiative_not_ends_with?: InputMaybe; - initiative_not_ends_with_nocase?: InputMaybe; - initiative_not_in?: InputMaybe>; - initiative_not_starts_with?: InputMaybe; - initiative_not_starts_with_nocase?: InputMaybe; - initiative_starts_with?: InputMaybe; - initiative_starts_with_nocase?: InputMaybe; - latestAllocation?: InputMaybe; - latestAllocation_?: InputMaybe; - latestAllocation_contains?: InputMaybe; - latestAllocation_contains_nocase?: InputMaybe; - latestAllocation_ends_with?: InputMaybe; - latestAllocation_ends_with_nocase?: InputMaybe; - latestAllocation_gt?: InputMaybe; - latestAllocation_gte?: InputMaybe; - latestAllocation_in?: InputMaybe>; - latestAllocation_lt?: InputMaybe; - latestAllocation_lte?: InputMaybe; - latestAllocation_not?: InputMaybe; - latestAllocation_not_contains?: InputMaybe; - latestAllocation_not_contains_nocase?: InputMaybe; - latestAllocation_not_ends_with?: InputMaybe; - latestAllocation_not_ends_with_nocase?: InputMaybe; - latestAllocation_not_in?: InputMaybe>; - latestAllocation_not_starts_with?: InputMaybe; - latestAllocation_not_starts_with_nocase?: InputMaybe; - latestAllocation_starts_with?: InputMaybe; - latestAllocation_starts_with_nocase?: InputMaybe; - or?: InputMaybe>>; - user?: InputMaybe; - user_contains?: InputMaybe; - user_contains_nocase?: InputMaybe; - user_ends_with?: InputMaybe; - user_ends_with_nocase?: InputMaybe; - user_gt?: InputMaybe; - user_gte?: InputMaybe; - user_in?: InputMaybe>; - user_lt?: InputMaybe; - user_lte?: InputMaybe; - user_not?: InputMaybe; - user_not_contains?: InputMaybe; - user_not_contains_nocase?: InputMaybe; - user_not_ends_with?: InputMaybe; - user_not_ends_with_nocase?: InputMaybe; - user_not_in?: InputMaybe>; - user_not_starts_with?: InputMaybe; - user_not_starts_with_nocase?: InputMaybe; - user_starts_with?: InputMaybe; - user_starts_with_nocase?: InputMaybe; -}; - -export enum GovernanceAllocationIndex_OrderBy { - Id = 'id', - Initiative = 'initiative', - InitiativeId = 'initiative__id', - InitiativeRegistered = 'initiative__registered', - LatestAllocation = 'latestAllocation', - LatestAllocationEpoch = 'latestAllocation__epoch', - LatestAllocationId = 'latestAllocation__id', - LatestAllocationUser = 'latestAllocation__user', - LatestAllocationVetoLqty = 'latestAllocation__vetoLQTY', - LatestAllocationVetoOffset = 'latestAllocation__vetoOffset', - LatestAllocationVoteLqty = 'latestAllocation__voteLQTY', - LatestAllocationVoteOffset = 'latestAllocation__voteOffset', - User = 'user' -} - -export type GovernanceAllocation_Filter = { - /** Filter for the block changed event. */ - _change_block?: InputMaybe; - and?: InputMaybe>>; - epoch?: InputMaybe; - epoch_gt?: InputMaybe; - epoch_gte?: InputMaybe; - epoch_in?: InputMaybe>; - epoch_lt?: InputMaybe; - epoch_lte?: InputMaybe; - epoch_not?: InputMaybe; - epoch_not_in?: InputMaybe>; - id?: InputMaybe; - id_gt?: InputMaybe; - id_gte?: InputMaybe; - id_in?: InputMaybe>; - id_lt?: InputMaybe; - id_lte?: InputMaybe; - id_not?: InputMaybe; - id_not_in?: InputMaybe>; - initiative?: InputMaybe; - initiative_?: InputMaybe; - initiative_contains?: InputMaybe; - initiative_contains_nocase?: InputMaybe; - initiative_ends_with?: InputMaybe; - initiative_ends_with_nocase?: InputMaybe; - initiative_gt?: InputMaybe; - initiative_gte?: InputMaybe; - initiative_in?: InputMaybe>; - initiative_lt?: InputMaybe; - initiative_lte?: InputMaybe; - initiative_not?: InputMaybe; - initiative_not_contains?: InputMaybe; - initiative_not_contains_nocase?: InputMaybe; - initiative_not_ends_with?: InputMaybe; - initiative_not_ends_with_nocase?: InputMaybe; - initiative_not_in?: InputMaybe>; - initiative_not_starts_with?: InputMaybe; - initiative_not_starts_with_nocase?: InputMaybe; - initiative_starts_with?: InputMaybe; - initiative_starts_with_nocase?: InputMaybe; - or?: InputMaybe>>; - user?: InputMaybe; - user_contains?: InputMaybe; - user_contains_nocase?: InputMaybe; - user_ends_with?: InputMaybe; - user_ends_with_nocase?: InputMaybe; - user_gt?: InputMaybe; - user_gte?: InputMaybe; - user_in?: InputMaybe>; - user_lt?: InputMaybe; - user_lte?: InputMaybe; - user_not?: InputMaybe; - user_not_contains?: InputMaybe; - user_not_contains_nocase?: InputMaybe; - user_not_ends_with?: InputMaybe; - user_not_ends_with_nocase?: InputMaybe; - user_not_in?: InputMaybe>; - user_not_starts_with?: InputMaybe; - user_not_starts_with_nocase?: InputMaybe; - user_starts_with?: InputMaybe; - user_starts_with_nocase?: InputMaybe; - vetoLQTY?: InputMaybe; - vetoLQTY_gt?: InputMaybe; - vetoLQTY_gte?: InputMaybe; - vetoLQTY_in?: InputMaybe>; - vetoLQTY_lt?: InputMaybe; - vetoLQTY_lte?: InputMaybe; - vetoLQTY_not?: InputMaybe; - vetoLQTY_not_in?: InputMaybe>; - vetoOffset?: InputMaybe; - vetoOffset_gt?: InputMaybe; - vetoOffset_gte?: InputMaybe; - vetoOffset_in?: InputMaybe>; - vetoOffset_lt?: InputMaybe; - vetoOffset_lte?: InputMaybe; - vetoOffset_not?: InputMaybe; - vetoOffset_not_in?: InputMaybe>; - voteLQTY?: InputMaybe; - voteLQTY_gt?: InputMaybe; - voteLQTY_gte?: InputMaybe; - voteLQTY_in?: InputMaybe>; - voteLQTY_lt?: InputMaybe; - voteLQTY_lte?: InputMaybe; - voteLQTY_not?: InputMaybe; - voteLQTY_not_in?: InputMaybe>; - voteOffset?: InputMaybe; - voteOffset_gt?: InputMaybe; - voteOffset_gte?: InputMaybe; - voteOffset_in?: InputMaybe>; - voteOffset_lt?: InputMaybe; - voteOffset_lte?: InputMaybe; - voteOffset_not?: InputMaybe; - voteOffset_not_in?: InputMaybe>; -}; - -export enum GovernanceAllocation_OrderBy { - Epoch = 'epoch', - Id = 'id', - Initiative = 'initiative', - InitiativeId = 'initiative__id', - InitiativeRegistered = 'initiative__registered', - User = 'user', - VetoLqty = 'vetoLQTY', - VetoOffset = 'vetoOffset', - VoteLqty = 'voteLQTY', - VoteOffset = 'voteOffset' -} - -export type GovernanceInitiative = { - __typename?: 'GovernanceInitiative'; - id: Scalars['ID']['output']; - registered: Scalars['Boolean']['output']; -}; - -export type GovernanceInitiative_Filter = { - /** Filter for the block changed event. */ - _change_block?: InputMaybe; - and?: InputMaybe>>; - id?: InputMaybe; - id_gt?: InputMaybe; - id_gte?: InputMaybe; - id_in?: InputMaybe>; - id_lt?: InputMaybe; - id_lte?: InputMaybe; - id_not?: InputMaybe; - id_not_in?: InputMaybe>; - or?: InputMaybe>>; - registered?: InputMaybe; - registered_in?: InputMaybe>; - registered_not?: InputMaybe; - registered_not_in?: InputMaybe>; -}; - -export enum GovernanceInitiative_OrderBy { - Id = 'id', - Registered = 'registered' -} - -export type GovernanceVotingPower = { - __typename?: 'GovernanceVotingPower'; - allocatedLQTY: Scalars['BigInt']['output']; - allocatedOffset: Scalars['BigInt']['output']; - id: Scalars['ID']['output']; - unallocatedLQTY: Scalars['BigInt']['output']; - unallocatedOffset: Scalars['BigInt']['output']; -}; - -export type GovernanceVotingPower_Filter = { - /** Filter for the block changed event. */ - _change_block?: InputMaybe; - allocatedLQTY?: InputMaybe; - allocatedLQTY_gt?: InputMaybe; - allocatedLQTY_gte?: InputMaybe; - allocatedLQTY_in?: InputMaybe>; - allocatedLQTY_lt?: InputMaybe; - allocatedLQTY_lte?: InputMaybe; - allocatedLQTY_not?: InputMaybe; - allocatedLQTY_not_in?: InputMaybe>; - allocatedOffset?: InputMaybe; - allocatedOffset_gt?: InputMaybe; - allocatedOffset_gte?: InputMaybe; - allocatedOffset_in?: InputMaybe>; - allocatedOffset_lt?: InputMaybe; - allocatedOffset_lte?: InputMaybe; - allocatedOffset_not?: InputMaybe; - allocatedOffset_not_in?: InputMaybe>; - and?: InputMaybe>>; - id?: InputMaybe; - id_gt?: InputMaybe; - id_gte?: InputMaybe; - id_in?: InputMaybe>; - id_lt?: InputMaybe; - id_lte?: InputMaybe; - id_not?: InputMaybe; - id_not_in?: InputMaybe>; - or?: InputMaybe>>; - unallocatedLQTY?: InputMaybe; - unallocatedLQTY_gt?: InputMaybe; - unallocatedLQTY_gte?: InputMaybe; - unallocatedLQTY_in?: InputMaybe>; - unallocatedLQTY_lt?: InputMaybe; - unallocatedLQTY_lte?: InputMaybe; - unallocatedLQTY_not?: InputMaybe; - unallocatedLQTY_not_in?: InputMaybe>; - unallocatedOffset?: InputMaybe; - unallocatedOffset_gt?: InputMaybe; - unallocatedOffset_gte?: InputMaybe; - unallocatedOffset_in?: InputMaybe>; - unallocatedOffset_lt?: InputMaybe; - unallocatedOffset_lte?: InputMaybe; - unallocatedOffset_not?: InputMaybe; - unallocatedOffset_not_in?: InputMaybe>; -}; - -export enum GovernanceVotingPower_OrderBy { - AllocatedLqty = 'allocatedLQTY', - AllocatedOffset = 'allocatedOffset', - Id = 'id', - UnallocatedLqty = 'unallocatedLQTY', - UnallocatedOffset = 'unallocatedOffset' -} - export type InterestBatch = { __typename?: 'InterestBatch'; annualInterestRate: Scalars['BigInt']['output']; @@ -608,6 +292,7 @@ export type InterestBatch = { debt: Scalars['BigInt']['output']; id: Scalars['ID']['output']; troves: Array; + updatedAt: Scalars['BigInt']['output']; }; @@ -696,6 +381,14 @@ export type InterestBatch_Filter = { id_not_in?: InputMaybe>; or?: InputMaybe>>; troves_?: InputMaybe; + updatedAt?: InputMaybe; + updatedAt_gt?: InputMaybe; + updatedAt_gte?: InputMaybe; + updatedAt_in?: InputMaybe>; + updatedAt_lt?: InputMaybe; + updatedAt_lte?: InputMaybe; + updatedAt_not?: InputMaybe; + updatedAt_not_in?: InputMaybe>; }; export enum InterestBatch_OrderBy { @@ -709,15 +402,19 @@ export enum InterestBatch_OrderBy { CollateralMinCollRatio = 'collateral__minCollRatio', Debt = 'debt', Id = 'id', - Troves = 'troves' + Troves = 'troves', + UpdatedAt = 'updatedAt' } export type InterestRateBracket = { __typename?: 'InterestRateBracket'; collateral: Collateral; id: Scalars['ID']['output']; + pendingDebtTimesOneYearD36: Scalars['BigInt']['output']; rate: Scalars['BigInt']['output']; + sumDebtTimesRateD36: Scalars['BigInt']['output']; totalDebt: Scalars['BigInt']['output']; + updatedAt: Scalars['BigInt']['output']; }; export type InterestRateBracket_Filter = { @@ -754,6 +451,14 @@ export type InterestRateBracket_Filter = { id_not?: InputMaybe; id_not_in?: InputMaybe>; or?: InputMaybe>>; + pendingDebtTimesOneYearD36?: InputMaybe; + pendingDebtTimesOneYearD36_gt?: InputMaybe; + pendingDebtTimesOneYearD36_gte?: InputMaybe; + pendingDebtTimesOneYearD36_in?: InputMaybe>; + pendingDebtTimesOneYearD36_lt?: InputMaybe; + pendingDebtTimesOneYearD36_lte?: InputMaybe; + pendingDebtTimesOneYearD36_not?: InputMaybe; + pendingDebtTimesOneYearD36_not_in?: InputMaybe>; rate?: InputMaybe; rate_gt?: InputMaybe; rate_gte?: InputMaybe; @@ -762,6 +467,14 @@ export type InterestRateBracket_Filter = { rate_lte?: InputMaybe; rate_not?: InputMaybe; rate_not_in?: InputMaybe>; + sumDebtTimesRateD36?: InputMaybe; + sumDebtTimesRateD36_gt?: InputMaybe; + sumDebtTimesRateD36_gte?: InputMaybe; + sumDebtTimesRateD36_in?: InputMaybe>; + sumDebtTimesRateD36_lt?: InputMaybe; + sumDebtTimesRateD36_lte?: InputMaybe; + sumDebtTimesRateD36_not?: InputMaybe; + sumDebtTimesRateD36_not_in?: InputMaybe>; totalDebt?: InputMaybe; totalDebt_gt?: InputMaybe; totalDebt_gte?: InputMaybe; @@ -770,6 +483,14 @@ export type InterestRateBracket_Filter = { totalDebt_lte?: InputMaybe; totalDebt_not?: InputMaybe; totalDebt_not_in?: InputMaybe>; + updatedAt?: InputMaybe; + updatedAt_gt?: InputMaybe; + updatedAt_gte?: InputMaybe; + updatedAt_in?: InputMaybe>; + updatedAt_lt?: InputMaybe; + updatedAt_lte?: InputMaybe; + updatedAt_not?: InputMaybe; + updatedAt_not_in?: InputMaybe>; }; export enum InterestRateBracket_OrderBy { @@ -778,8 +499,11 @@ export enum InterestRateBracket_OrderBy { CollateralId = 'collateral__id', CollateralMinCollRatio = 'collateral__minCollRatio', Id = 'id', + PendingDebtTimesOneYearD36 = 'pendingDebtTimesOneYearD36', Rate = 'rate', - TotalDebt = 'totalDebt' + SumDebtTimesRateD36 = 'sumDebtTimesRateD36', + TotalDebt = 'totalDebt', + UpdatedAt = 'updatedAt' } /** Defines the order direction, either ascending or descending */ @@ -798,14 +522,6 @@ export type Query = { collateralAddresses?: Maybe; collateralAddresses_collection: Array; collaterals: Array; - governanceAllocation?: Maybe; - governanceAllocationIndex?: Maybe; - governanceAllocationIndexes: Array; - governanceAllocations: Array; - governanceInitiative?: Maybe; - governanceInitiatives: Array; - governanceVotingPower?: Maybe; - governanceVotingPowers: Array; interestBatch?: Maybe; interestBatches: Array; interestRateBracket?: Maybe; @@ -874,78 +590,6 @@ export type QueryCollateralsArgs = { }; -export type QueryGovernanceAllocationArgs = { - block?: InputMaybe; - id: Scalars['ID']['input']; - subgraphError?: _SubgraphErrorPolicy_; -}; - - -export type QueryGovernanceAllocationIndexArgs = { - block?: InputMaybe; - id: Scalars['ID']['input']; - subgraphError?: _SubgraphErrorPolicy_; -}; - - -export type QueryGovernanceAllocationIndexesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - - -export type QueryGovernanceAllocationsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - - -export type QueryGovernanceInitiativeArgs = { - block?: InputMaybe; - id: Scalars['ID']['input']; - subgraphError?: _SubgraphErrorPolicy_; -}; - - -export type QueryGovernanceInitiativesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - - -export type QueryGovernanceVotingPowerArgs = { - block?: InputMaybe; - id: Scalars['ID']['input']; - subgraphError?: _SubgraphErrorPolicy_; -}; - - -export type QueryGovernanceVotingPowersArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - - export type QueryInterestBatchArgs = { block?: InputMaybe; id: Scalars['ID']['input']; @@ -1009,14 +653,6 @@ export type Subscription = { collateralAddresses?: Maybe; collateralAddresses_collection: Array; collaterals: Array; - governanceAllocation?: Maybe; - governanceAllocationIndex?: Maybe; - governanceAllocationIndexes: Array; - governanceAllocations: Array; - governanceInitiative?: Maybe; - governanceInitiatives: Array; - governanceVotingPower?: Maybe; - governanceVotingPowers: Array; interestBatch?: Maybe; interestBatches: Array; interestRateBracket?: Maybe; @@ -1085,78 +721,6 @@ export type SubscriptionCollateralsArgs = { }; -export type SubscriptionGovernanceAllocationArgs = { - block?: InputMaybe; - id: Scalars['ID']['input']; - subgraphError?: _SubgraphErrorPolicy_; -}; - - -export type SubscriptionGovernanceAllocationIndexArgs = { - block?: InputMaybe; - id: Scalars['ID']['input']; - subgraphError?: _SubgraphErrorPolicy_; -}; - - -export type SubscriptionGovernanceAllocationIndexesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - - -export type SubscriptionGovernanceAllocationsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - - -export type SubscriptionGovernanceInitiativeArgs = { - block?: InputMaybe; - id: Scalars['ID']['input']; - subgraphError?: _SubgraphErrorPolicy_; -}; - - -export type SubscriptionGovernanceInitiativesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - - -export type SubscriptionGovernanceVotingPowerArgs = { - block?: InputMaybe; - id: Scalars['ID']['input']; - subgraphError?: _SubgraphErrorPolicy_; -}; - - -export type SubscriptionGovernanceVotingPowersArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - - export type SubscriptionInterestBatchArgs = { block?: InputMaybe; id: Scalars['ID']['input']; @@ -1451,6 +1015,7 @@ export enum Trove_OrderBy { InterestBatchColl = 'interestBatch__coll', InterestBatchDebt = 'interestBatch__debt', InterestBatchId = 'interestBatch__id', + InterestBatchUpdatedAt = 'interestBatch__updatedAt', InterestRate = 'interestRate', LastUserActionAt = 'lastUserActionAt', MightBeLeveraged = 'mightBeLeveraged', @@ -1538,25 +1103,6 @@ export type AllInterestRateBracketsQueryVariables = Exact<{ [key: string]: never export type AllInterestRateBracketsQuery = { __typename?: 'Query', interestRateBrackets: Array<{ __typename?: 'InterestRateBracket', rate: string, totalDebt: string, collateral: { __typename?: 'Collateral', collIndex: number } }> }; -export type GovernanceGlobalDataQueryVariables = Exact<{ [key: string]: never; }>; - - -export type GovernanceGlobalDataQuery = { __typename?: 'Query', governanceInitiatives: Array<{ __typename?: 'GovernanceInitiative', id: string }>, governanceVotingPower?: { __typename?: 'GovernanceVotingPower', allocatedLQTY: string, allocatedOffset: string, unallocatedLQTY: string, unallocatedOffset: string } | null }; - -export type UserAllocationHistoryQueryVariables = Exact<{ - user?: InputMaybe; -}>; - - -export type UserAllocationHistoryQuery = { __typename?: 'Query', governanceAllocations: Array<{ __typename?: 'GovernanceAllocation', epoch: string, voteLQTY: string, vetoLQTY: string, voteOffset: string, vetoOffset: string, initiative: { __typename?: 'GovernanceInitiative', id: string } }> }; - -export type TotalAllocationHistoryQueryVariables = Exact<{ - initiative?: InputMaybe; -}>; - - -export type TotalAllocationHistoryQuery = { __typename?: 'Query', governanceAllocations: Array<{ __typename?: 'GovernanceAllocation', epoch: string, voteLQTY: string, vetoLQTY: string, voteOffset: string, vetoOffset: string }> }; - export class TypedDocumentString extends String implements DocumentTypeDecoration @@ -1654,52 +1200,4 @@ export const AllInterestRateBracketsDocument = new TypedDocumentString(` totalDebt } } - `) as unknown as TypedDocumentString; -export const GovernanceGlobalDataDocument = new TypedDocumentString(` - query GovernanceGlobalData { - governanceInitiatives { - id - } - governanceVotingPower(id: "total") { - allocatedLQTY - allocatedOffset - unallocatedLQTY - unallocatedOffset - } -} - `) as unknown as TypedDocumentString; -export const UserAllocationHistoryDocument = new TypedDocumentString(` - query UserAllocationHistory($user: String) { - governanceAllocations( - where: {user: $user} - orderBy: epoch - orderDirection: desc - first: 1000 - ) { - epoch - initiative { - id - } - voteLQTY - vetoLQTY - voteOffset - vetoOffset - } -} - `) as unknown as TypedDocumentString; -export const TotalAllocationHistoryDocument = new TypedDocumentString(` - query TotalAllocationHistory($initiative: String) { - governanceAllocations( - where: {initiative: $initiative, user: null} - orderBy: epoch - orderDirection: desc - first: 1000 - ) { - epoch - voteLQTY - vetoLQTY - voteOffset - vetoOffset - } -} - `) as unknown as TypedDocumentString; \ No newline at end of file + `) as unknown as TypedDocumentString; \ No newline at end of file diff --git a/frontend/app/src/liquity-governance.ts b/frontend/app/src/liquity-governance.ts deleted file mode 100644 index 22f43f3cd..000000000 --- a/frontend/app/src/liquity-governance.ts +++ /dev/null @@ -1,841 +0,0 @@ -import type { Address, Dnum, Initiative } from "@/src/types"; -import type { UseQueryResult } from "@tanstack/react-query"; -import type { Config as WagmiConfig } from "wagmi"; - -import { BribeInitiative } from "@/src/abi/BribeInitiative"; -import { getProtocolContract } from "@/src/contracts"; -import { dnum18, DNUM_0, jsonStringifyWithDnum } from "@/src/dnum-utils"; -import { CHAIN_CONTRACT_MULTICALL, KNOWN_INITIATIVES_URL, LIQUITY_GOVERNANCE_URL } from "@/src/env"; -import { - getGovernanceGlobalData, - getTotalAllocationHistoryFromSubgraph, - getUserAllocationHistoryFromSubgraph, -} from "@/src/subgraph"; -import { jsonStringifyWithBigInt } from "@/src/utils"; -import { vAddress } from "@/src/valibot-utils"; -import { useRaf } from "@liquity2/uikit"; -import { useQuery } from "@tanstack/react-query"; -import * as dn from "dnum"; -import { useMemo } from "react"; -import * as v from "valibot"; -import { erc20Abi, parseAbi } from "viem"; -import { useConfig as useWagmiConfig, useReadContract, useReadContracts } from "wagmi"; -import { readContract, readContracts } from "wagmi/actions"; - -export type InitiativeStatus = - | "nonexistent" - | "warm up" - | "skip" - | "claimable" - | "claimed" - | "unregisterable" - | "disabled"; - -export function initiativeStatusFromNumber(status: number): InitiativeStatus { - const statuses: Record = { - 0: "nonexistent", - 1: "warm up", - 2: "skip", - 3: "claimable", - 4: "claimed", - 5: "unregisterable", - 6: "disabled", - }; - return statuses[status] || "nonexistent"; -} - -export function useGovernanceState() { - const Governance = getProtocolContract("Governance"); - return useReadContracts({ - contracts: [{ - ...Governance, - functionName: "epoch", - }, { - ...Governance, - functionName: "epochStart", - }, { - ...Governance, - functionName: "globalState", - }, { - ...Governance, - functionName: "secondsWithinEpoch", - }, { - ...Governance, - functionName: "EPOCH_DURATION", - }, { - ...Governance, - functionName: "EPOCH_VOTING_CUTOFF", - }], - query: { - select: ([ - epoch_, - epochStart_, - globalState, - secondsWithinEpoch, - GOVERNANCE_EPOCH_DURATION, - GOVERNANCE_EPOCH_VOTING_CUTOFF, - ]) => { - const epoch = epoch_.result ?? 0n; - const epochStart = epochStart_.result ?? 0n; - const epochDuration = GOVERNANCE_EPOCH_DURATION.result ?? 0n; - const epochVotingCutoff = GOVERNANCE_EPOCH_VOTING_CUTOFF.result ?? 0n; - const cutoffStart = epochStart + epochVotingCutoff; - - const period: "cutoff" | "voting" = (secondsWithinEpoch.result ?? 0n) > epochVotingCutoff - ? "cutoff" - : "voting"; - - const seconds = Number(secondsWithinEpoch.result ?? 0n); - const daysLeft = (Number(epochDuration) - seconds) / (24 * 60 * 60); - const daysLeftRounded = Math.ceil(daysLeft); - - return { - countedVoteLQTY: globalState.result?.[0] ?? 0n, - countedVoteOffset: globalState.result?.[1] ?? 0n, - cutoffStart, - daysLeft, - daysLeftRounded, - epoch, - epochEnd: epochStart + epochDuration, - epochStart, - period, - secondsWithinEpoch: secondsWithinEpoch.result, - }; - }, - }, - }); -} - -const KnownInitiativesSchema = v.record( - v.pipe( - vAddress(), - v.transform((address) => address.toLowerCase()), - ), - v.pipe( - v.object({ - name: v.string(), - name_link: v.optional(v.pipe(v.string(), v.url())), - group: v.string(), - }), - v.transform(({ name_link = null, ...initiative }) => ({ - ...initiative, - url: name_link, - })), - ), -); - -function useKnownInitiatives() { - return useQuery({ - queryKey: ["knownInitiatives"], - queryFn: async () => { - if (!KNOWN_INITIATIVES_URL) return null; - - const response = await fetch(KNOWN_INITIATIVES_URL); - const data = await response.json(); - return v.parse(KnownInitiativesSchema, data); - }, - }); -} - -function useGovernanceGlobalData() { - return useQuery({ - queryKey: ["governanceGlobalData"], - queryFn: getGovernanceGlobalData, - }); -} - -export function useNamedInitiatives() { - const knownInitiatives = useKnownInitiatives(); - const governanceGlobal = useGovernanceGlobalData(); - - return useQuery({ - queryKey: ["namedInitiatives"], - enabled: ( - knownInitiatives.isSuccess && governanceGlobal.isSuccess - || knownInitiatives.isError - || governanceGlobal.isError - ), - queryFn: () => { - if (knownInitiatives.isError) throw knownInitiatives.error; - if (governanceGlobal.isError) throw governanceGlobal.error; - if (knownInitiatives.isPending || governanceGlobal.isPending) throw new Error("should not happen"); // see enabled - - return governanceGlobal.data.registeredInitiatives.map((address): Initiative => { - const ki = knownInitiatives.data?.[address]; - return { - address, - name: ki?.name ?? null, - pairVolume: null, - protocol: ki?.group ?? null, - tvl: null, - url: ki?.url ?? null, - votesDistribution: null, - }; - }); - }, - }); -} - -export function useInitiativesStates(initiatives: Address[]) { - const wagmiConfig = useWagmiConfig(); - - const Governance = getProtocolContract("Governance"); - - return useQuery({ - queryKey: ["initiativesStates", initiatives.join("")], - queryFn: async () => { - const results = await readContracts(wagmiConfig, { - contracts: initiatives.map((address) => ({ - ...Governance, - functionName: "getInitiativeState", - args: [address], - } as const)), - }); - - const initiativesStates: Record = {}; - - for (const [i, { result }] of results.entries()) { - if (result && initiatives[i]) { - initiativesStates[initiatives[i]] = { - status: initiativeStatusFromNumber(result[0]), - lastEpochClaim: result[1], - claimableAmount: result[2], - }; - } - } - - return initiativesStates; - }, - }); -} - -export type VoteTotals = { - voteLQTY: bigint; - voteOffset: bigint; - vetoLQTY: bigint; - vetoOffset: bigint; -}; - -export function useInitiativesVoteTotals(initiatives: Address[]) { - const wagmiConfig = useWagmiConfig(); - const Governance = getProtocolContract("Governance"); - - return useQuery({ - queryKey: ["initiativesVoteTotals", initiatives.join("")], - queryFn: async () => { - const voteTotals: Record = {}; - - const results = await readContracts(wagmiConfig, { - contracts: initiatives.map((address) => ({ - ...Governance, - functionName: "initiativeStates", - args: [address], - } as const)), - }); - - for (const [i, { result }] of results.entries()) { - if (result && initiatives[i]) { - const [voteLQTY, voteOffset, vetoLQTY, vetoOffset] = result; - voteTotals[initiatives[i]] = { - voteLQTY, - voteOffset, - vetoLQTY, - vetoOffset, - }; - } - } - - return voteTotals; - }, - }); -} - -export async function getUserAllocations( - wagmiConfig: WagmiConfig, - account: Address, - initiatives?: Address[], -) { - if (!initiatives) { - initiatives = (await getGovernanceGlobalData()).registeredInitiatives; - } - - const Governance = getProtocolContract("Governance"); - - const allocationsByInitiative = await readContracts(wagmiConfig, { - allowFailure: false, - contracts: initiatives.map((address) => ({ - ...Governance, - functionName: "lqtyAllocatedByUserToInitiative", - args: [account, address], - } as const)), - }); - - return allocationsByInitiative.map((allocation, index) => { - const initiative = initiatives[index]; - if (!initiative) throw new Error(); // should never happen - const [voteLQTY, voteOffset, vetoLQTY, vetoOffset] = allocation; - return { voteLQTY, voteOffset, vetoLQTY, vetoOffset, initiative }; - }); -} - -export async function getUserAllocatedInitiatives( - wagmiConfig: WagmiConfig, - account: Address, - initiatives?: Address[], -) { - const allocations = await getUserAllocations(wagmiConfig, account, initiatives); - return allocations - .filter(({ voteLQTY, vetoLQTY }) => (voteLQTY + vetoLQTY) > 0n) - .map(({ initiative }) => initiative); -} - -export async function getUserStates( - wagmiConfig: WagmiConfig, - account: Address, -) { - const Governance = getProtocolContract("Governance"); - const result = await readContract(wagmiConfig, { - ...Governance, - functionName: "userStates", - args: [account], - }); - - const [ - unallocatedLQTY, - unallocatedOffset, - allocatedLQTY, - allocatedOffset, - ] = result; - - return { - allocatedLQTY, - allocatedOffset, - stakedLQTY: allocatedLQTY + unallocatedLQTY, - stakedOffset: allocatedOffset + unallocatedOffset, - unallocatedLQTY, - unallocatedOffset, - }; -} - -export function useGovernanceUser(account: Address | null) { - const initiatives = useNamedInitiatives(); - const wagmiConfig = useWagmiConfig(); - - let queryFn = async () => { - if (!account || !initiatives.data) return null; - - const [userState, allocations] = await Promise.all([ - getUserStates(wagmiConfig, account), - getUserAllocations( - wagmiConfig, - account, - initiatives.data.map((i) => i.address), - ), - ]); - - return { ...userState, allocations }; - }; - - return useQuery({ - queryKey: [ - "GovernanceUser", - account, - jsonStringifyWithDnum(initiatives.data), - ], - queryFn, - }); -} - -function useLatestBlockTimestampInMilliseconds() { - return useReadContract({ - address: CHAIN_CONTRACT_MULTICALL, - abi: parseAbi(["function getCurrentBlockTimestamp() view returns (uint256)"]), - functionName: "getCurrentBlockTimestamp", - query: { select: (blockTimestamp) => blockTimestamp * 1000n }, - }); -} - -// votingPower(t) = lqty * t - offset -export function votingPower( - stakedLQTY: bigint, - offset: bigint, - timestampInSeconds: bigint, -) { - return stakedLQTY * timestampInSeconds - offset; -} - -function votingPowerMs( - stakedLQTY: bigint, - offset: bigint, - timestampInMilliseconds: bigint, -) { - return (stakedLQTY * timestampInMilliseconds - offset * 1000n) / 1000n; -} - -export function useVotingPower( - account: Address | null, - callback: (share: Dnum | null) => void, - updatesPerSecond: number = 30, -) { - const govStats = useGovernanceStats(); - const govUser = useGovernanceUser(account); - const blockTimestamp = useLatestBlockTimestampInMilliseconds(); - const startTime = useMemo(() => BigInt(Date.now()), []); - - useRaf(() => { - if (!govStats.data || !govUser.data || !blockTimestamp.data) { - callback(null); - return; - } - - const { totalLQTYStaked, totalOffset } = govStats.data; - const userLQTYStaked = govUser.data.allocatedLQTY + govUser.data.unallocatedLQTY; - const userOffset = govUser.data.allocatedOffset + govUser.data.unallocatedOffset; - - const timeElapsed = BigInt(Date.now()) - startTime; - const correctedStartTime = startTime > blockTimestamp.data ? startTime : blockTimestamp.data; - const timestamp = correctedStartTime + timeElapsed; - const userVp = votingPowerMs(userLQTYStaked, userOffset, timestamp); - const totalVP = votingPowerMs(totalLQTYStaked, totalOffset, timestamp); - - // pctShare(t) = userVotingPower(t) / totalVotingPower(t) - callback( - totalVP === 0n ? DNUM_0 : dn.div( - dnum18(userVp), - dnum18(totalVP), - ), - ); - }, updatesPerSecond); -} - -export function useGovernanceStats() { - const governanceGlobal = useGovernanceGlobalData(); - - return useQuery({ - queryKey: ["governanceStats"], - enabled: !governanceGlobal.isPending, - queryFn: () => { - if (governanceGlobal.isError) throw governanceGlobal.error; - if (governanceGlobal.isPending) throw new Error("should not happen"); // see enabled - - return { - totalLQTYStaked: ( - governanceGlobal.data.totalVotingPower.allocatedLQTY - + governanceGlobal.data.totalVotingPower.unallocatedLQTY - ), - totalOffset: ( - governanceGlobal.data.totalVotingPower.allocatedOffset - + governanceGlobal.data.totalVotingPower.unallocatedOffset - ), - }; - }, - }); -} - -type ClaimData = { - epoch: number; - prevLQTYAllocationEpoch: number; - prevTotalLQTYAllocationEpoch: number; -}; - -type BribeClaim = { - bribeTokens: Array<{ - address: Address; - symbol: string; - amount: Dnum; - }>; - claimableInitiatives: Array<{ - initiative: Address; - boldAmount: Dnum; - bribeTokenAmount: Dnum; - bribeTokenAddress: Address; - epochs: number[]; - claimData: ClaimData[]; - }>; - totalBold: Dnum; -}; - -// represents an initiative bribe for a given epoch -type InitiativeBribe = { - boldAmount: Dnum; - tokenAmount: Dnum; - tokenAddress: Address; - tokenSymbol: string; -}; - -export function useCurrentEpochBribes( - initiatives: Address[], -): UseQueryResult> { - const wagmiConfig = useWagmiConfig(); - const govState = useGovernanceState(); - - return useQuery({ - queryKey: [ - "currentEpochBribes", - initiatives.join(""), - String(govState.data?.epoch), - ], - queryFn: async () => { - if (!govState.data || initiatives.length === 0) { - return {}; - } - - const bribeTokens = await readContracts(wagmiConfig, { - contracts: initiatives.map((initiative) => ({ - abi: BribeInitiative, - address: initiative, - functionName: "bribeToken", - } as const)), - // this is needed because some initiatives may revert if they don't have a bribe token - allowFailure: true, - }); - - // initiatives with a bribe token - const bribeInitiatives: Array<{ - initiative: Address; - bribeToken: Address; - }> = []; - - for (const [index, bribeTokenResult] of bribeTokens.entries()) { - if (bribeTokenResult.result && initiatives[index]) { - bribeInitiatives.push({ - initiative: initiatives[index], - bribeToken: bribeTokenResult.result, - }); - } - } - - if (bribeInitiatives.length === 0) { - return {}; - } - - const bribeAmounts = await readContracts(wagmiConfig, { - contracts: bribeInitiatives.map(({ initiative }) => ({ - abi: BribeInitiative, - address: initiative, - functionName: "bribeByEpoch", - args: [govState.data.epoch], - } as const)), - allowFailure: false, - }); - - const tokenSymbols = await readContracts(wagmiConfig, { - contracts: bribeInitiatives.map(({ bribeToken }) => ({ - abi: erc20Abi, - address: bribeToken, - functionName: "symbol", - } as const)), - allowFailure: false, - }); - - const bribes: Record = {}; - - for (const [index, [remainingBold, remainingBribeToken]] of bribeAmounts.entries()) { - const bribeInitiative = bribeInitiatives[index]; - if (!bribeInitiative) continue; - - const { initiative, bribeToken } = bribeInitiative; - - bribes[initiative] = { - boldAmount: dnum18(remainingBold), - tokenAmount: dnum18(remainingBribeToken), - tokenAddress: bribeToken, - tokenSymbol: tokenSymbols[index] ?? "Unknown", - }; - } - - return bribes; - }, - enabled: Boolean(govState.data && initiatives.length > 0), - }); -} - -const AllocationSchema = v.object({ - epoch: v.number(), - voteLQTY: v.pipe(v.string(), v.transform((x) => BigInt(x))), - vetoLQTY: v.pipe(v.string(), v.transform((x) => BigInt(x))), - voteOffset: v.pipe(v.string(), v.transform((x) => BigInt(x))), - vetoOffset: v.pipe(v.string(), v.transform((x) => BigInt(x))), -}); - -const TotalAllocationHistorySchema = v.array(AllocationSchema); - -const UserAllocationSchema = v.object({ - ...AllocationSchema.entries, - initiative: v.string(), -}); - -const UserAllocationHistorySchema = v.array(UserAllocationSchema); - -// A user's allocation history ordered by descending epoch -async function getUserAllocationHistory(user: Address) { - if (LIQUITY_GOVERNANCE_URL) { - const response = await fetch(`${LIQUITY_GOVERNANCE_URL}/allocation/user/${user.toLowerCase()}.json`); - return v.parse(UserAllocationHistorySchema, await response.json()).sort((a, b) => b.epoch - a.epoch); - } else { - return getUserAllocationHistoryFromSubgraph(user); - } -} - -// An initiative's total allocation history ordered by descending epoch -async function getTotalAllocationHistory(initiative: Address) { - if (LIQUITY_GOVERNANCE_URL) { - const response = await fetch(`${LIQUITY_GOVERNANCE_URL}/allocation/total/${initiative.toLowerCase()}.json`); - return v.parse(TotalAllocationHistorySchema, await response.json()).sort((a, b) => b.epoch - a.epoch); - } else { - return getTotalAllocationHistoryFromSubgraph(initiative); - } -} - -async function getLatestCompletedEpoch(currentEpoch: bigint) { - if (LIQUITY_GOVERNANCE_URL) { - const response = await fetch(`${LIQUITY_GOVERNANCE_URL}/latest_completed_epoch.json`); - return v.parse(v.number(), await response.json()); - } else { - return Number(currentEpoch) - 1; - } -} - -// limit checks to the last 52 epochs -const BRIBING_CHECK_EPOCH_LIMIT = 52; - -export function useBribingClaim( - account: Address | null, -): UseQueryResult { - const wagmiConfig = useWagmiConfig(); - const govState = useGovernanceState(); - const govUser = useGovernanceUser(account); - const initiatives = useNamedInitiatives(); - - return useQuery({ - queryKey: [ - "bribingClaim", - account, - String(govState.data?.epoch), - jsonStringifyWithBigInt(govUser.data), - ], - queryFn: async () => { - if (!account || !govState.data || !govUser.data || !initiatives.data) { - return null; - } - - const currentEpoch = govState.data.epoch; - const epochDuration = govState.data.epochEnd - govState.data.epochStart; - const initiativesToCheck = initiatives.data.map(({ address }) => address); - - if (initiativesToCheck.length === 0) { - return { - bribeTokens: [], - claimableInitiatives: [], - totalBold: DNUM_0, - }; - } - - const [completedEpochs, userAllocations, bribeChecks] = await Promise.all([ - getLatestCompletedEpoch(currentEpoch), - getUserAllocationHistory(account), - readContracts(wagmiConfig, { - contracts: initiativesToCheck.map((initiative) => ({ - abi: BribeInitiative, - address: initiative, - functionName: "bribeToken", - } as const)), - allowFailure: true, - }), - ]); - - const bribeInitiatives: Array<{ - address: Address; - bribeToken: Address; - }> = []; - - for (const [index, token] of bribeChecks.entries()) { - const address = initiativesToCheck[index]?.toLowerCase() as Address | undefined; - if ( - address - && token.result - && userAllocations.find((allocation) => allocation.initiative === address && allocation.voteLQTY > 0n) - ) { - bribeInitiatives.push({ - address, - bribeToken: token.result, - }); - } - } - - if (bribeInitiatives.length === 0) { - return { - bribeTokens: [], - claimableInitiatives: [], - totalBold: DNUM_0, - }; - } - - // for each bribe initiative, check claimable epochs - const claimableInitiatives: BribeClaim["claimableInitiatives"] = []; - const bribeTokenData = new Map(); - - const bribeDetailsPerInitiative = await Promise.all( - bribeInitiatives.map(async ({ address, bribeToken }) => { - // no completed epochs yet - if (completedEpochs < 1) { - return null; - } - - const epochsToCheck = Math.min(completedEpochs, BRIBING_CHECK_EPOCH_LIMIT); - const startEpoch = Math.max(1, completedEpochs - epochsToCheck + 1); - const userAllocationsToInitiative = userAllocations.filter((allocation) => allocation.initiative === address); - - const [totalAllocationsToInitiative, results] = await Promise.all([ - getTotalAllocationHistory(address), - readContracts(wagmiConfig, { - contracts: Array.from({ length: epochsToCheck }, (_, index) => { - const BribeContract = { abi: BribeInitiative, address } as const; - const epoch = BigInt(startEpoch + index); - return [{ - ...BribeContract, - functionName: "claimedBribeAtEpoch", - args: [account, epoch], - }, { - ...BribeContract, - functionName: "bribeByEpoch", - args: [epoch], - }] as const; - }).flat(), - allowFailure: false, - }), - ]); - - const claimableEpochs: number[] = []; - const claimData: ClaimData[] = []; - let initiativeBold = DNUM_0; - let initiativeBribeToken = DNUM_0; - - // process results in groups of 2 (one per epoch) - for (let epochIndex = 0; epochIndex < epochsToCheck; epochIndex++) { - const epoch = startEpoch + epochIndex; - const resultIndex = epochIndex * 2; - - const hasClaimed = results[resultIndex] as boolean; - const [ - remainingBold, - remainingBribeToken, - claimedVotes, - ] = results[resultIndex + 1] as [bigint, bigint, bigint]; - - // allocations are ordered by descending epoch, - // so this finds the most recent one at the time of `epoch` - const userAllocation = userAllocationsToInitiative.find((allocation) => allocation.epoch <= epoch); - const totalAllocation = totalAllocationsToInitiative.find((allocation) => allocation.epoch <= epoch); - - // skip if already claimed or no bribes available - if ( - hasClaimed - || (remainingBold === 0n && remainingBribeToken === 0n) - || !totalAllocation - || !userAllocation || userAllocation.voteLQTY === 0n - ) { - continue; - } - - const epochEnd = govState.data.epochStart - - ((currentEpoch - BigInt(epoch)) * epochDuration) - + epochDuration; - - // voting power at the end of the epoch - const userVP = votingPower(userAllocation.voteLQTY, userAllocation.voteOffset, epochEnd); - const totalVP = votingPower(totalAllocation.voteLQTY, totalAllocation.voteOffset, epochEnd); - const remainingVP = totalVP - claimedVotes; - - if (remainingVP > 0n && userVP > 0n) { - const userShare = userVP <= remainingVP ? userVP : remainingVP; - const shareRatio = dn.div(dnum18(userShare), dnum18(remainingVP)); - - const boldClaim = dn.mul(dnum18(remainingBold), shareRatio); - const bribeTokenClaim = dn.mul(dnum18(remainingBribeToken), shareRatio); - - initiativeBold = dn.add(initiativeBold, boldClaim); - initiativeBribeToken = dn.add(initiativeBribeToken, bribeTokenClaim); - claimableEpochs.push(epoch); - claimData.push({ - epoch, - prevLQTYAllocationEpoch: Number(userAllocation.epoch), - prevTotalLQTYAllocationEpoch: Number(totalAllocation.epoch), - }); - } - } - - if (claimableEpochs.length === 0) { - return null; - } - - return { - boldAmount: initiativeBold, - bribeTokenAddress: bribeToken, - bribeTokenAmount: initiativeBribeToken, - epochs: claimableEpochs, - initiative: address, - claimData, - }; - }), - ); - - // filter out null results and prepare final data - let totalBold = DNUM_0; - for (const details of bribeDetailsPerInitiative) { - if (!details) continue; - claimableInitiatives.push(details); - bribeTokenData.set(details.bribeTokenAddress, { - amount: dn.add( - bribeTokenData.get(details.bribeTokenAddress)?.amount ?? DNUM_0, - details.bribeTokenAmount, - ), - }); - totalBold = dn.add(totalBold, details.boldAmount); - } - - // fetch token symbols for all bribe tokens - const tokenAddresses = Array.from(bribeTokenData.keys()); - if (tokenAddresses.length > 0) { - const symbols = await readContracts(wagmiConfig, { - contracts: tokenAddresses.map((tokenAddress) => ({ - abi: erc20Abi, - address: tokenAddress, - functionName: "symbol", - } as const)), - allowFailure: false, - }); - - for (const [index, symbol] of symbols.entries()) { - const tokenAddress = tokenAddresses[index]; - if (!tokenAddress) { - // should not happen since symbols is derived from tokenAddresses - throw new Error("Unexpected undefined token address in bribe token data"); - } - const current = bribeTokenData.get(tokenAddress); - if (current) { - bribeTokenData.set(tokenAddress, { ...current, symbol }); - } - } - } - - return { - bribeTokens: [...bribeTokenData.entries()].map( - ([address, { amount, symbol }]) => { - if (!symbol) { - throw new Error(`Failed to fetch symbol for token ${address}`); - } - return { address, symbol, amount }; - }, - ), - claimableInitiatives, - totalBold, - }; - }, - enabled: Boolean(account && govState.isSuccess && govUser.isSuccess), - }); -} diff --git a/frontend/app/src/liquity-utils.ts b/frontend/app/src/liquity-utils.ts index 54ae6ed72..7fbe93bc6 100644 --- a/frontend/app/src/liquity-utils.ts +++ b/frontend/app/src/liquity-utils.ts @@ -6,7 +6,6 @@ import type { Dnum, PositionEarn, PositionLoanCommitted, - PositionStake, PrefixedTroveId, RiskLevel, Token, @@ -17,9 +16,6 @@ import type { Address, CollateralSymbol, CollateralToken } from "@liquity2/uikit import type { UseQueryResult } from "@tanstack/react-query"; import type { Config as WagmiConfig } from "wagmi"; -import { Governance } from "@/src/abi/Governance"; -import { StabilityPool } from "@/src/abi/StabilityPool"; -import { TroveManager } from "@/src/abi/TroveManager"; import { INTEREST_RATE_ADJ_COOLDOWN, INTEREST_RATE_END, @@ -31,7 +27,7 @@ import { } from "@/src/constants"; import { CONTRACTS, getBranchContract, getProtocolContract } from "@/src/contracts"; import { dnum18, DNUM_0, dnumOrNull, jsonStringifyWithDnum } from "@/src/dnum-utils"; -import { CHAIN_BLOCK_EXPLORER, ENV_BRANCHES, LEGACY_CHECK, LIQUITY_STATS_URL } from "@/src/env"; +import { CHAIN_BLOCK_EXPLORER, ENV_BRANCHES, LIQUITY_STATS_URL } from "@/src/env"; import { getRedemptionRisk } from "@/src/liquity-math"; import { usePrice } from "@/src/services/Prices"; import { @@ -43,15 +39,14 @@ import { type IndexedTrove, } from "@/src/subgraph"; import { isBranchId, isPrefixedtroveId, isTroveId } from "@/src/types"; -import { bigIntAbs, jsonStringifyWithBigInt } from "@/src/utils"; -import { vAddress, vPrefixedTroveId } from "@/src/valibot-utils"; +import { bigIntAbs } from "@/src/utils"; import { addressesEqual, COLLATERALS, isAddress, shortenAddress, TOKENS_BY_SYMBOL } from "@liquity2/uikit"; import { useQuery } from "@tanstack/react-query"; import * as dn from "dnum"; import { useMemo } from "react"; import * as v from "valibot"; -import { encodeAbiParameters, erc20Abi, isAddressEqual, keccak256, parseAbiParameters, zeroAddress } from "viem"; -import { useBalance, useConfig as useWagmiConfig, useReadContract, useReadContracts } from "wagmi"; +import { encodeAbiParameters, isAddressEqual, keccak256, parseAbiParameters, zeroAddress } from "viem"; +import { useConfig as useWagmiConfig, useReadContract, useReadContracts } from "wagmi"; import { readContract, readContracts } from "wagmi/actions"; export function shortenTroveId(troveId: TroveId, chars = 8) { @@ -318,76 +313,6 @@ export function useEarnPositionsByAccount(account: null | Address) { }); } -export function useStakePosition(address: null | Address) { - const LqtyStaking = getProtocolContract("LqtyStaking"); - const LusdToken = getProtocolContract("LusdToken"); - const Governance = getProtocolContract("Governance"); - - const userProxyAddress = useReadContract({ - ...Governance, - functionName: "deriveUserProxyAddress", - args: [address ?? "0x"], - query: { - enabled: Boolean(address), - }, - }); - - const userProxyBalance = useBalance({ - address: userProxyAddress.data ?? "0x", - query: { - enabled: Boolean(address) && userProxyAddress.isSuccess, - }, - }); - - return useReadContracts({ - contracts: [{ - ...LqtyStaking, - functionName: "stakes", - args: [userProxyAddress.data ?? "0x"], - }, { - ...LqtyStaking, - functionName: "getPendingETHGain", - args: [userProxyAddress.data ?? "0x"], - }, { - ...LqtyStaking, - functionName: "getPendingLUSDGain", - args: [userProxyAddress.data ?? "0x"], - }, { - ...LusdToken, - functionName: "balanceOf", - args: [userProxyAddress.data ?? "0x"], - }], - query: { - enabled: Boolean(address) && userProxyAddress.isSuccess && userProxyBalance.isSuccess, - select: ([ - depositResult, - pendingEthGainResult, - pendingLusdGainResult, - lusdBalanceResult, - ]): PositionStake | undefined => { - if ( - depositResult.status === "failure" - || pendingEthGainResult.status === "failure" - || pendingLusdGainResult.status === "failure" - || lusdBalanceResult.status === "failure" - ) { - return undefined; - } - const deposit = dnum18(depositResult.result); - return { - type: "stake", - deposit, - owner: address ?? "0x", - rewards: { - eth: dnum18(pendingEthGainResult.result + (userProxyBalance.data?.value ?? 0n)), - lusd: dnum18(pendingLusdGainResult.result + lusdBalanceResult.result), - }, - }; - }, - }, - }); -} - export function useTroveNftUrl(branchId: null | BranchId, troveId: null | TroveId) { const TroveNft = getBranchContract(branchId, "TroveNFT"); return TroveNft && troveId && `${CHAIN_BLOCK_EXPLORER?.url}nft/${TroveNft.address}/${BigInt(troveId)}`; @@ -727,7 +652,7 @@ export const StatsSchema = v.pipe( branch: Object.fromEntries( Object.entries(value.branch).map(([symbol, branch]) => { symbol = symbol.toUpperCase(); - if (symbol === "WETH") symbol = "ETH"; + if (symbol === "WANKR") symbol = "ANKR"; return [symbol, { collActive: dnumOrNull(branch.coll_active, 18), collDefault: dnumOrNull(branch.coll_default, 18), @@ -1031,217 +956,6 @@ export function useLoansByAccount(account?: Address | null) { }); } -const TrovesSnapshotSchema = v.record( - vAddress(), - v.array(vPrefixedTroveId()), -); - -export function useLegacyPositions(account: Address | null): UseQueryResult<{ - boldBalance: bigint; - hasAnyEarnPosition: boolean; - hasAnyLoan: boolean; - hasAnyPosition: boolean; - hasStakeDeposit: boolean; - spDeposits: Array<{ - branchId: BranchId; - collGain: bigint; - deposit: bigint; - yieldGain: bigint; - }>; - stakeDeposit: bigint; - troves: Array<{ - accruedBatchManagementFee: bigint; - accruedInterest: bigint; - annualInterestRate: bigint; - branchId: BranchId; - collToken: { name: string; symbol: TokenSymbol }; - entireColl: bigint; - entireDebt: bigint; - lastInterestRateAdjTime: bigint; - recordedDebt: bigint; - redistBoldDebtGain: bigint; - redistCollGain: bigint; - troveId: TroveId; - weightedRecordedDebt: bigint; - }>; -}> { - const checkLegacyPositions = Boolean(account && LEGACY_CHECK); - - const legacyTrovesFromSnapshot = useQuery({ - queryKey: ["legacyTrovesFromSnapshot", account], - queryFn: async () => { - if (!LEGACY_CHECK || !account) { - throw new Error("LEGACY_CHECK or account not defined"); - } - const result = await fetch(LEGACY_CHECK.TROVES_SNAPSHOT_URL); - const trovesByAccount = v.parse(TrovesSnapshotSchema, await result.json()); - return trovesByAccount[account.toLowerCase() as `0x${string}`] ?? []; - }, - enabled: checkLegacyPositions, - staleTime: Infinity, - }); - - const legacyTroves = useReadContracts({ - contracts: legacyTrovesFromSnapshot.data?.map((prefixedTroveId) => { - const { branchId, troveId } = parsePrefixedTroveId(prefixedTroveId); - const branch = LEGACY_CHECK?.BRANCHES[branchId as number]; - const address: Address = branch?.TROVE_MANAGER ?? "0x"; - return { - abi: TroveManager, - address, - functionName: "getLatestTroveData", - args: [BigInt(troveId)], - } as const; - }), - allowFailure: false, - query: { - enabled: checkLegacyPositions, - select: (results) => { - return ( - results - .map((data, index) => { - const prefixedTroveId = legacyTrovesFromSnapshot.data?.[index]; - if (!prefixedTroveId) { - throw new Error("Trove ID not found"); - } - const { branchId, troveId } = parsePrefixedTroveId(prefixedTroveId); - const branch = LEGACY_CHECK?.BRANCHES[branchId as number]; - if (!branch) { - throw new Error(`Invalid branch ID: ${branchId}`); - } - return { - ...data, - branchId, - collToken: { - name: branch.name, - symbol: branch.symbol, - }, - troveId, - }; - }) - .filter((trove) => trove.entireDebt > 0n) - ); - }, - }, - }); - - const hasAnyLegacyTrove = (legacyTrovesFromSnapshot.data?.length ?? 0) > 0; - - const spDeposits = useReadContracts({ - contracts: LEGACY_CHECK - ? [ - ...LEGACY_CHECK.BRANCHES.map(({ STABILITY_POOL }) => ({ - abi: StabilityPool, - address: STABILITY_POOL, - functionName: "getCompoundedBoldDeposit" as const, - args: [account], - })), - ...LEGACY_CHECK.BRANCHES.map(({ STABILITY_POOL }) => ({ - abi: StabilityPool, - address: STABILITY_POOL, - functionName: "getDepositorYieldGainWithPending" as const, - args: [account], - })), - ...LEGACY_CHECK.BRANCHES.map(({ STABILITY_POOL }) => ({ - abi: StabilityPool, - address: STABILITY_POOL, - functionName: "getDepositorCollGain" as const, - args: [account], - })), - ] - : undefined, - allowFailure: false, - query: { - enabled: checkLegacyPositions, - select: (results) => { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK not defined"); - } - const branchCount = LEGACY_CHECK.BRANCHES.length; - const getBranchSlice = (index: number) => ( - results.slice(branchCount * index, branchCount * (index + 1)) - ); - - const deposits = getBranchSlice(0); - const yieldGains = getBranchSlice(1); - const collGains = getBranchSlice(2); - - return { - hasAnySpDeposit: deposits.some((deposit) => deposit > 0n), - branches: LEGACY_CHECK.BRANCHES.map((_, index) => ({ - branchId: index as BranchId, - collGain: collGains[index] ?? 0n, - deposit: deposits[index] ?? 0n, - yieldGain: yieldGains[index] ?? 0n, - })), - }; - }, - }, - }); - - const legacyBoldBalance = useReadContract({ - abi: erc20Abi, - address: LEGACY_CHECK?.BOLD_TOKEN, - functionName: "balanceOf", - args: [account ?? "0x"], - query: { - enabled: checkLegacyPositions, - }, - }); - - const stakedLqty = useReadContract({ - abi: Governance, - address: LEGACY_CHECK?.GOVERNANCE, - functionName: "userStates" as const, - args: [account ?? "0x"], - query: { - enabled: checkLegacyPositions, - select: ([ - unallocatedLQTY, - _unallocatedOffset, - allocatedLQTY, - _allocatedOffset, - ]) => unallocatedLQTY + allocatedLQTY, - }, - }); - - return useQuery({ - queryKey: [ - "hasAnyLegacyPosition", - account, - jsonStringifyWithBigInt(legacyTroves.data), - String(legacyBoldBalance.data), - jsonStringifyWithBigInt(spDeposits.data), - String(stakedLqty.data), - ], - queryFn: () => { - const stakeDeposit = stakedLqty.data ?? 0n; - const hasAnyEarnPosition = spDeposits.data?.hasAnySpDeposit ?? false; - const hasStakeDeposit = stakeDeposit > 0n; - return { - boldBalance: legacyBoldBalance.data ?? 0n, - hasAnyEarnPosition, - hasAnyLoan: hasAnyLegacyTrove, - hasAnyPosition: hasAnyEarnPosition || hasAnyLegacyTrove || hasStakeDeposit, - hasStakeDeposit, - spDeposits: (spDeposits.data?.branches ?? []).filter( - (branch) => branch.deposit > 0n, - ), - stakeDeposit: stakedLqty.data ?? 0n, - troves: legacyTroves.data ?? [], - }; - }, - placeholderData: (data) => data, - enabled: ( - checkLegacyPositions - && legacyBoldBalance.isSuccess - && (legacyTroves.isSuccess || !hasAnyLegacyTrove) - && spDeposits.isSuccess - && stakedLqty.isSuccess - ), - }); -} - export function useNextOwnerIndex( borrower: null | Address, branchId: null | BranchId, diff --git a/frontend/app/src/screens/AccountScreen/AccountScreen.tsx b/frontend/app/src/screens/AccountScreen/AccountScreen.tsx index 97d1a7b49..fa5898b9d 100644 --- a/frontend/app/src/screens/AccountScreen/AccountScreen.tsx +++ b/frontend/app/src/screens/AccountScreen/AccountScreen.tsx @@ -6,7 +6,7 @@ import type { ReactNode } from "react"; import { ERC20Faucet } from "@/src/abi/ERC20Faucet"; import { Positions } from "@/src/comps/Positions/Positions"; import { Screen } from "@/src/comps/Screen/Screen"; -import { getBranchContract, getProtocolContract } from "@/src/contracts"; +import { getBranchContract } from "@/src/contracts"; import { CHAIN_ID } from "@/src/env"; import { fmtnum } from "@/src/formatting"; import { getBranches } from "@/src/liquity-utils"; @@ -112,21 +112,6 @@ export function AccountScreen({ tokenSymbol="BOLD" /> - - - - - - {branches.map(({ symbol }) => ( @@ -165,7 +150,6 @@ function Balance({ }) { const balance = useBalance(address, tokenSymbol); - const LqtyToken = getProtocolContract("LqtyToken"); const CollToken = getBranchContract( isCollateralSymbol(tokenSymbol) ? tokenSymbol : null, "CollToken", @@ -201,7 +185,7 @@ function Balance({ size="mini" label="tap" onClick={() => { - if ((tokenSymbol === "WSTETH" || tokenSymbol === "RETH") && CollToken) { + if ((tokenSymbol === "USN") && CollToken) { writeContract({ abi: ERC20Faucet, address: CollToken.address, @@ -214,19 +198,6 @@ function Balance({ }); return; } - - if (tokenSymbol === "LQTY") { - writeContract({ - abi: LqtyToken.abi, - address: LqtyToken.address, - functionName: "tap", - }, { - onError: (error) => { - alert(error.message); - }, - }); - return; - } }} style={{ padding: "0 6px", diff --git a/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx b/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx index 076fee8fe..32d28b61c 100644 --- a/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx +++ b/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx @@ -143,7 +143,7 @@ export function BorrowScreen() { maxCollDeposit, dnumMax( // Only keep a reserve for ETH, not LSTs - dn.sub(collBalance.data, collSymbol === "ETH" ? ETH_MAX_RESERVE : 0), + dn.sub(collBalance.data, collSymbol === "ANKR" ? ETH_MAX_RESERVE : 0), dnum18(0), ), ); @@ -187,7 +187,7 @@ export function BorrowScreen() { /> ))} - {NBSP}ETH + {NBSP}ANKR ,
-
); @@ -117,13 +114,11 @@ function BorrowTable({ return (
} columns={columns} - rows={getBranches().map(({ symbol }) => ( - - ))} + rows={getBranches().map(({ symbol }) => )} />
); @@ -174,7 +169,6 @@ function EarnTable({ columns={columns} rows={[ ...getBranches(), - { symbol: "SBOLD" as const }, ].map(({ symbol }) => ( -
- -
- - ); -} - -function ForksInfoDrawer() { - const pickedForkIcons = useMemo(() => pickRandomForks(2), []); - return ( -
-
-
- {pickedForkIcons.map(([name, icon], index) => ( -
0 ? -4 : 0, - }} - > - {name} -
- ))} -
-
- - {content.home.earnTable.forksInfo.text} - -
-
-
- - Learn more - -
); } diff --git a/frontend/app/src/screens/LegacyScreen/LegacyScreen.tsx b/frontend/app/src/screens/LegacyScreen/LegacyScreen.tsx deleted file mode 100644 index e5cac2cf6..000000000 --- a/frontend/app/src/screens/LegacyScreen/LegacyScreen.tsx +++ /dev/null @@ -1,753 +0,0 @@ -"use client"; - -import type { Dnum, TokenSymbol } from "@/src/types"; - -import { CollateralRegistry } from "@/src/abi/CollateralRegistry"; -import { Amount } from "@/src/comps/Amount/Amount"; -import { ConnectWarningBox } from "@/src/comps/ConnectWarningBox/ConnectWarningBox"; -import { Field } from "@/src/comps/Field/Field"; -import { Screen } from "@/src/comps/Screen/Screen"; -import { dnum18 } from "@/src/dnum-utils"; -import { LEGACY_CHECK } from "@/src/env"; -import { parseInputPercentage, useInputFieldValue } from "@/src/form-utils"; -import { fmtnum } from "@/src/formatting"; -import { useLegacyPositions } from "@/src/liquity-utils"; -import { HomeTable } from "@/src/screens/HomeScreen/HomeTable"; -import { useTransactionFlow } from "@/src/services/TransactionFlow"; -import { useAccount } from "@/src/wagmi-utils"; -import { css } from "@/styled-system/css"; -import { - Button, - IconBorrow, - IconEarn, - IconStake, - InfoTooltip, - InputField, - TextButton, - TokenIcon, - TOKENS_BY_SYMBOL, -} from "@liquity2/uikit"; -import { a, useTransition } from "@react-spring/web"; -import * as dn from "dnum"; -import Link from "next/link"; -import { notFound } from "next/navigation"; -import { useRef } from "react"; -import { useReadContract } from "wagmi"; - -function getLegacyBranch(branchId: number) { - const branch = LEGACY_CHECK?.BRANCHES[branchId]; - if (!branch) { - throw new Error(`Invalid branch ID: ${branchId}`); - } - return branch; -} - -export function LegacyScreen() { - if (!LEGACY_CHECK) { - notFound(); - } - - const account = useAccount(); - const legacyPositions = useLegacyPositions(account.address ?? null); - - const positionsTransition = useTransition({ - account, - legacyPositions, - }, { - keys: ({ account, legacyPositions }) => ( - !account.address - ? "no-account" - : `${account.address}${legacyPositions.data?.hasAnyPosition}` - ), - from: { - opacity: 0, - transform: ` - scale3d(0.97, 0.97, 1) - translate3d(0, 8px, 0) - `, - }, - enter: { - opacity: 1, - transform: ` - scale3d(1, 1, 1) - translate3d(0, 0px, 0) - `, - immediate: !account.address, - }, - leave: { - display: "none", - immediate: true, - }, - config: { - mass: 1, - tension: 2800, - friction: 120, - }, - }); - - return ( - - {positionsTransition((style, { account, legacyPositions }) => ( - account.address - ? ( - -
- {legacyPositions.isLoading - ? "Fetching your legacy positions…" - : legacyPositions.data?.hasAnyPosition - ? "You have active positions in Liquity V2-Legacy." - + " These positions are not compatible with Liquity V2." - + " You can withdraw these positions from here at any time." - : "You do not have any active positions in Liquity V2-Legacy."} -
- {legacyPositions.isSuccess && ( -
- - - - -
- )} - {legacyPositions.isLoading && ( -
- Loading… -
- )} -
- ) - : ( - - - - ) - ))} -
- ); -} - -function EarnPositionsTable() { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK is not defined."); - } - const txFlow = useTransactionFlow(); - const account = useAccount(); - const legacyPositions = useLegacyPositions(account.address ?? null); - const spDeposits = legacyPositions.data?.spDeposits ?? []; - return ( - } - columns={[ - "Stability Pool", - "Deposit", - "Rewards", - ] as const} - placeholder="No active positions." - rows={legacyPositions.isSuccess && spDeposits.length === 0 ? [] : [ - ...spDeposits.map((spPosition) => { - const branch = getLegacyBranch(spPosition.branchId); - return ( - - -
- - {branch.name} -
- - -
- -
- - -
- - -
- - - ); - }), - - -
-
- - , - ]} - /> - ); -} - -function LoanPositionsTable() { - const txFlow = useTransactionFlow(); - const account = useAccount(); - const legacyPositions = useLegacyPositions(account.address ?? null); - const troves = legacyPositions.data?.troves ?? []; - const boldBalance = dnum18(legacyPositions.data?.boldBalance ?? 0n); - return ( - } - columns={[ - "Collateral", - "Deposit", - "Borrowed", - null, - ] as const} - placeholder="No opened loans." - rows={troves.map((trove) => { - const debt = dnum18(trove.entireDebt); - const coll = dnum18(trove.entireColl); - const hasEnoughBold = dn.lte(debt, boldBalance); - return ( - - -
- - {trove.collToken.name} -
- - -
- -
- - -
- -
- - -
- {!hasEnoughBold && ( - - Your current Legacy BOLD balance ({fmtnum( - boldBalance, - { digits: 2 }, - )} BOLD) is too low to close this loan. You need at least {fmtnum(debt, { digits: 2 })}{" "} - BOLD to close this loan. - - )} -
- - - ); - })} - /> - ); -} - -function StakingPositionsTable() { - const txFlow = useTransactionFlow(); - const account = useAccount(); - const legacyPositions = useLegacyPositions(account.address ?? null); - const stakeDeposit = legacyPositions.data && dnum18(legacyPositions.data.stakeDeposit); - return ( - } - columns={["Staked LQTY", null] as const} - placeholder="No active staking position." - rows={stakeDeposit && dn.eq(stakeDeposit, 0) ? [] : [ - - - - - -
-
- - , - ]} - /> - ); -} - -function RedeemSection() { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK is not defined."); - } - - const account = useAccount(); - const txFlow = useTransactionFlow(); - const legacyPositions = useLegacyPositions(account.address ?? null); - const boldBalance = legacyPositions.data ? dnum18(legacyPositions.data.boldBalance) : null; - - const redemptionRate = useReadContract({ - abi: CollateralRegistry, - address: LEGACY_CHECK.COLLATERAL_REGISTRY, - functionName: "getRedemptionRateWithDecay", - account: account.address, - }); - - const amount = useInputFieldValue(fmtnum); - const maxFee = useInputFieldValue((value) => `${fmtnum(value, "pct2z")}%`, { - parse: parseInputPercentage, - }); - - const hasUpdatedRedemptionRate = useRef(false); - if (!hasUpdatedRedemptionRate.current && redemptionRate.data) { - if (maxFee.isEmpty) { - maxFee.setValue( - fmtnum( - dn.mul(dnum18(redemptionRate.data), 1.1), - "pct2z", - ), - ); - } - hasUpdatedRedemptionRate.current = true; - } - - const allowSubmit = account.isConnected - && amount.parsed - && maxFee.parsed - && boldBalance - && dn.gte(boldBalance, amount.parsed); - - return ( - } - columns={[]} - rows={[ - - -
- } - label="Legacy BOLD" - /> - } - drawer={amount.isFocused - ? null - : boldBalance - && amount.parsed - && dn.gt(amount.parsed, boldBalance) - ? { - mode: "error", - message: `Insufficient BOLD balance. You have ${fmtnum(boldBalance)} BOLD.`, - } - : null} - label="Redeeming" - placeholder="0.00" - secondary={{ - start: `$${ - amount.parsed - ? fmtnum(amount.parsed) - : "0.00" - }`, - end: ( - boldBalance && dn.gt(boldBalance, 0) && ( - { - amount.setValue(dn.toString(boldBalance)); - }} - /> - ) - ), - }} - {...amount.inputFieldProps} - /> - } - /> - - - } - footer={[ - { - end: ( - - <> - Current redemption rate: - - - - This is the maximum redemption fee you are willing to pay. The redemption fee is a - percentage of the redeemed amount that is paid to the protocol. The redemption fee must - be higher than the current fee. - - ), - footerLink: { - href: "https://dune.com/queries/4641717/7730245", - label: "Redemption fee on Dune", - }, - }} - /> - - ), - }, - ]} - /> - -
-

- Important note -

-
-

- You will be charged a dynamic redemption fee (the more redemptions, the higher the fee).{" "} - - Learn more about redemptions. - -

-
-
- -
-
-
- - , - ]} - /> - ); -} - -function TokenAmount({ - digits = 2, - symbol, - value, -}: { - digits?: number | null; - symbol: TokenSymbol | "LEGACY_BOLD"; - value?: Dnum | null; -}) { - const token = TOKENS_BY_SYMBOL[symbol === "LEGACY_BOLD" ? "BOLD" : symbol]; - return ( -
- {fmtnum(value, { - compact: true, - digits: 2, - })} -
- -
-
- ); -} diff --git a/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx b/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx index 6a10e9fd0..03f11929c 100644 --- a/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx +++ b/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx @@ -17,7 +17,7 @@ import { useInputFieldValue } from "@/src/form-utils"; import { fmtnum } from "@/src/formatting"; import { useCheckLeverageSlippage } from "@/src/liquity-leverage"; import { getRedemptionRisk } from "@/src/liquity-math"; -import { getBranch, getBranches, getCollToken, useNextOwnerIndex, useDebtPositioning } from "@/src/liquity-utils"; +import { getBranch, getBranches, getCollToken, useDebtPositioning, useNextOwnerIndex } from "@/src/liquity-utils"; import { usePrice } from "@/src/services/Prices"; import { useTransactionFlow } from "@/src/services/TransactionFlow"; import { infoTooltipProps } from "@/src/uikit-utils"; @@ -102,7 +102,7 @@ export function LeverageScreen() { const collBalance = balances[collateral.symbol]?.data; const maxAmount = collBalance && dnumMax( - dn.sub(collBalance, collSymbol === "ETH" ? ETH_MAX_RESERVE : 0), // Only keep a reserve for ETH, not LSTs + dn.sub(collBalance, collSymbol === "ANKR" ? ETH_MAX_RESERVE : 0), // Only keep a reserve for ETH, not LSTs dnum18(0), ); diff --git a/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx b/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx index a930018fd..0d437a0e3 100644 --- a/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx +++ b/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx @@ -81,7 +81,7 @@ export function PanelUpdateBorrowPosition({ collBalance.data && dnumMax( dn.sub( collBalance.data, - collToken.symbol === "ETH" ? ETH_MAX_RESERVE : 0, // Only keep a reserve for ETH, not LSTs + collToken.symbol === "ANKR" ? ETH_MAX_RESERVE : 0, // Only keep a reserve for ETH, not LSTs ), dnum18(0), ) diff --git a/frontend/app/src/screens/LoanScreen/PanelUpdateLeveragePosition.tsx b/frontend/app/src/screens/LoanScreen/PanelUpdateLeveragePosition.tsx index 3e1cfb3e0..317ced564 100644 --- a/frontend/app/src/screens/LoanScreen/PanelUpdateLeveragePosition.tsx +++ b/frontend/app/src/screens/LoanScreen/PanelUpdateLeveragePosition.tsx @@ -121,7 +121,7 @@ export function PanelUpdateLeveragePosition({ const collMax = depositMode === "remove" ? null : ( collBalance.data && dn.sub( collBalance.data, - collToken?.symbol === "ETH" ? ETH_MAX_RESERVE : 0, // Only keep a reserve for ETH, not LSTs + collToken?.symbol === "ANKR" ? ETH_MAX_RESERVE : 0, // Only keep a reserve for ETH, not LSTs ) ); @@ -279,7 +279,7 @@ export function PanelUpdateLeveragePosition({ } footer={[ { - start: , + start: , end: ( , + start: , end: ( - - - - -
- -
Total in USD
- -
- -
Expected Gas Fee
- -
-
-
- - - - ); -} - -function Rewards({ - amount, - label, - symbol, -}: { - amount: Dnum; - label: ReactNode; - symbol: TokenSymbol; -}) { - return ( -
-
- {label} -
-
- - -
-
- ); -} diff --git a/frontend/app/src/screens/StakeScreen/PanelStaking.tsx b/frontend/app/src/screens/StakeScreen/PanelStaking.tsx deleted file mode 100644 index 6b3fcefbe..000000000 --- a/frontend/app/src/screens/StakeScreen/PanelStaking.tsx +++ /dev/null @@ -1,294 +0,0 @@ -import { Amount } from "@/src/comps/Amount/Amount"; -import { Field } from "@/src/comps/Field/Field"; -import { FlowButton } from "@/src/comps/FlowButton/FlowButton"; -import { InputTokenBadge } from "@/src/comps/InputTokenBadge/InputTokenBadge"; -import content from "@/src/content"; -import { dnum18, DNUM_0, dnumMax } from "@/src/dnum-utils"; -import { parseInputFloat } from "@/src/form-utils"; -import { fmtnum } from "@/src/formatting"; -import { useGovernanceStats, useGovernanceUser } from "@/src/liquity-governance"; -import { useStakePosition } from "@/src/liquity-utils"; -import { usePrice } from "@/src/services/Prices"; -import { infoTooltipProps } from "@/src/uikit-utils"; -import { useAccount, useBalance } from "@/src/wagmi-utils"; -import { css } from "@/styled-system/css"; -import { HFlex, InfoTooltip, InputField, Tabs, TextButton, TokenIcon } from "@liquity2/uikit"; -import * as dn from "dnum"; -import { useState } from "react"; - -export function PanelStaking() { - const account = useAccount(); - const lqtyPrice = usePrice("LQTY"); - - const [mode, setMode] = useState<"deposit" | "withdraw">("deposit"); - const [value, setValue] = useState(""); - const [focused, setFocused] = useState(false); - - const govStats = useGovernanceStats(); - const govUser = useGovernanceUser(account.address ?? null); - - const stakedLqty = dnum18(govUser.data?.stakedLQTY); - const totalStakedLqty = dnum18(govStats.data?.totalLQTYStaked); - - const stakePosition = useStakePosition(account.address ?? null); - - const parsedValue = parseInputFloat(value); - - const value_ = (focused || !parsedValue || dn.lte(parsedValue, 0)) - ? value - : fmtnum(parsedValue, "full"); - - const depositDifference = dn.mul( - parsedValue ?? DNUM_0, - mode === "withdraw" ? -1 : 1, - ); - - const updatedShare = (() => { - if (!stakedLqty || !totalStakedLqty) { - return DNUM_0; - } - - const updatedUserLqtyAllocated = dn.add(stakedLqty, depositDifference); - const updatedTotalLqtyStaked = dn.add(totalStakedLqty, depositDifference); - - // make sure we don't divide by zero or show negative percentages - if (dn.lte(updatedUserLqtyAllocated, 0) || dn.lte(updatedTotalLqtyStaked, 0)) { - return DNUM_0; - } - - return dn.div(updatedUserLqtyAllocated, updatedTotalLqtyStaked); - })(); - - const updatedDeposit = stakedLqty - ? dnumMax(dn.add(stakedLqty, depositDifference), DNUM_0) - : DNUM_0; - - const lqtyBalance = useBalance(account.address, "LQTY"); - const isDepositFilled = parsedValue && dn.gt(parsedValue, 0); - const hasDeposit = stakedLqty && dn.gt(stakedLqty, 0); - - const insufficientBalance = mode === "deposit" && isDepositFilled && ( - !lqtyBalance.data || dn.lt(lqtyBalance.data, parsedValue) - ); - - const withdrawOutOfRange = Boolean( - mode === "withdraw" && isDepositFilled && ( - !stakedLqty || dn.lt(stakedLqty, parsedValue) - ), - ); - - const allowSubmit = Boolean( - account.isConnected - && isDepositFilled - && !insufficientBalance, - ); - - const rewardsLusd = stakePosition.data?.rewards.lusd ?? DNUM_0; - const rewardsEth = stakePosition.data?.rewards.eth ?? DNUM_0; - - return ( - <> - } - label="LQTY" - /> - } - label={{ - start: mode === "withdraw" ? "You withdraw" : "You deposit", - end: ( - { - setMode(index === 1 ? "withdraw" : "deposit"); - setValue(""); - if (origin !== "keyboard") { - event.preventDefault(); - (event.target as HTMLElement).focus(); - } - }} - selected={mode === "withdraw" ? 1 : 0} - /> - ), - }} - labelHeight={32} - onFocus={() => setFocused(true)} - onChange={setValue} - onBlur={() => setFocused(false)} - value={value_} - placeholder="0.00" - secondary={{ - start: ( - - ), - end: mode === "deposit" - ? ( - lqtyBalance.data && dn.gt(lqtyBalance.data, 0) && ( - { - if (lqtyBalance.data) { - setValue(dn.toString(lqtyBalance.data)); - } - }} - /> - ) - ) - : ( - stakePosition.data?.deposit && dn.gt(stakePosition.data?.deposit, 0) && ( - { - if (stakePosition.data) { - setValue(dn.toString(stakePosition.data.deposit)); - } - }} - /> - ) - ), - }} - /> - } - footer={{ - start: ( - -
- -
- - {content.stakeScreen.infoTooltips.votingShare} - - - } - /> - ), - }} - /> -
- {hasDeposit && ( - -
- - -
-
-
- {" "} - - LUSD - -
-
- {" "} - - ETH - -
-
-
- )} - - -
- - ); -} diff --git a/frontend/app/src/screens/StakeScreen/PanelVoting.tsx b/frontend/app/src/screens/StakeScreen/PanelVoting.tsx deleted file mode 100644 index 910c89412..000000000 --- a/frontend/app/src/screens/StakeScreen/PanelVoting.tsx +++ /dev/null @@ -1,1159 +0,0 @@ -import type { InitiativeStatus, VoteTotals } from "@/src/liquity-governance"; -import type { Address, Dnum, Entries, Initiative, Vote, VoteAllocation, VoteAllocations } from "@/src/types"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { FlowButton } from "@/src/comps/FlowButton/FlowButton"; -import { LinkTextButton } from "@/src/comps/LinkTextButton/LinkTextButton"; -import { Spinner } from "@/src/comps/Spinner/Spinner"; -import { Tag } from "@/src/comps/Tag/Tag"; -import { VoteInput } from "@/src/comps/VoteInput/VoteInput"; -import content from "@/src/content"; -import { dnum18, DNUM_0 } from "@/src/dnum-utils"; -import { CHAIN_BLOCK_EXPLORER, CHAIN_ID } from "@/src/env"; -import { fmtnum, formatDate } from "@/src/formatting"; -import { - useCurrentEpochBribes, - useGovernanceState, - useGovernanceUser, - useInitiativesStates, - useInitiativesVoteTotals, - useNamedInitiatives, - votingPower, -} from "@/src/liquity-governance"; -import { usePrice } from "@/src/services/Prices"; -import { tokenIconUrl } from "@/src/utils"; -import { jsonStringifyWithBigInt } from "@/src/utils"; -import { useAccount } from "@/src/wagmi-utils"; -import { css } from "@/styled-system/css"; -import { Button, IconDownvote, IconEdit, IconExternal, IconUpvote, shortenAddress, TokenIcon } from "@liquity2/uikit"; -import * as dn from "dnum"; -import { useEffect, useMemo, useRef, useState } from "react"; - -function isInitiativeStatusActive( - status: InitiativeStatus, -): status is Exclude { - return status !== "disabled" - && status !== "nonexistent" - && status !== "unregisterable" - && status !== "warm up"; -} - -function initiativeStatusLabel(status: InitiativeStatus) { - if (status === "skip" || status === "claimable" || status === "claimed") { - return "Active"; - } - if (status === "warm up") { - return "Warm-up period"; - } - if (status === "unregisterable") { - return "Unregistering"; - } - if (status === "disabled") { - return "Disabled"; - } - return ""; -} - -function filterVoteAllocationsForSubmission( - voteAllocations: VoteAllocations, - initiativesStates: Record, -) { - const voteAllocationsFiltered = { ...voteAllocations }; - - for (const [address, data] of Object.entries(voteAllocations) as Entries) { - // Filter out allocations with null or zero values. No need to explicitly set them to 0, - // as allocated initiatives always get reset when allocating new votes. - if (data.vote === null || dn.eq(data.value, 0)) { - delete voteAllocationsFiltered[address]; - } - - // filter out invalid initiatives - const initiativeStatus = initiativesStates[address]?.status; - if (!isInitiativeStatusActive(initiativeStatus ?? "nonexistent")) { - delete voteAllocationsFiltered[address]; - } - } - - return voteAllocationsFiltered; -} - -export function PanelVoting() { - const account = useAccount(); - const governanceState = useGovernanceState(); - const governanceUser = useGovernanceUser(account.address ?? null); - const initiatives = useNamedInitiatives(); - - const initiativesAddresses = initiatives.data?.map((i) => i.address) ?? []; - const initiativesStates = useInitiativesStates(initiativesAddresses); - const currentBribes = useCurrentEpochBribes(initiativesAddresses); - const voteTotals = useInitiativesVoteTotals(initiativesAddresses); - - const stakedLQTY = governanceUser.data?.stakedLQTY; - const stakedOffset = governanceUser.data?.stakedOffset; - const epochEnd = governanceState.data?.epochEnd; - const absoluteAllocations = governanceUser.data?.allocations; - - // current vote allocations - const [voteAllocations, setVoteAllocations] = useState({}); - - // vote allocations from user input - const [inputVoteAllocations, setInputVoteAllocations] = useState({}); - - // fill input vote allocations from user data - useEffect(() => { - if (!stakedLQTY || !stakedOffset || !epochEnd || !absoluteAllocations) return; - - const stakedVotingPower = votingPower( - stakedLQTY, - stakedOffset, - epochEnd, - ); - - if (stakedVotingPower === 0n) { - setVoteAllocations({}); - setInputVoteAllocations({}); - return; - } - - const allocations: VoteAllocations = {}; - - for (const allocation of absoluteAllocations) { - const vote = allocation.voteLQTY > 0n - ? "for" as const - : allocation.vetoLQTY > 0n - ? "against" as const - : null; - - if (vote === null) continue; - - // rounded to 4 decimals - const value = ( - BigInt(1e4) * ( - vote === "for" - ? votingPower(allocation.voteLQTY, allocation.voteOffset, epochEnd) - : votingPower(allocation.vetoLQTY, allocation.vetoOffset, epochEnd) - ) + stakedVotingPower / 2n - ) / stakedVotingPower; - - allocations[allocation.initiative] = { - vote, - value: [value, 4], - }; - } - - setVoteAllocations(allocations); - setInputVoteAllocations(allocations); - }, [stakedLQTY, stakedOffset, epochEnd, jsonStringifyWithBigInt(absoluteAllocations)]); - - const hasAnyAllocationChange = useMemo(() => { - if (!governanceUser.data || !initiativesStates.data) { - return false; - } - - const serialize = (allocations: VoteAllocations) => ( - jsonStringifyWithBigInt( - Object.entries(allocations).sort(([a], [b]) => a.localeCompare(b)), - ) - ); - - // filter the current vote allocations, taking care of removing - // disabled + allocated initiatives as removing them doesn’t count as a change - const voteAllocationsFiltered = filterVoteAllocationsForSubmission( - voteAllocations, - initiativesStates.data, - ); - - const voteAllocationsToSubmit = filterVoteAllocationsForSubmission( - inputVoteAllocations, - initiativesStates.data, - ); - - return serialize(voteAllocationsFiltered) !== serialize(voteAllocationsToSubmit); - }, [voteAllocations, inputVoteAllocations, initiativesStates.data]); - - const isCutoff = governanceState.data?.period === "cutoff"; - - const hasAnyAllocations = (governanceUser.data?.allocations ?? []).length > 0; - - const remainingVotingPower = useMemo(() => { - let remaining = dn.from(1, 18); - - const combinedAllocations: Record = {}; - const stakedLQTY = governanceUser.data?.stakedLQTY ?? 0n; - const allocations = governanceUser.data?.allocations ?? []; - - // current allocations - if (stakedLQTY > 0n) { - for (const allocation of allocations) { - const currentVoteAmount = allocation.voteLQTY > 0n - ? allocation.voteLQTY - : allocation.vetoLQTY; - - if (currentVoteAmount > 0n) { - const proportion = dn.div([currentVoteAmount, 18], [stakedLQTY, 18]); - combinedAllocations[allocation.initiative] = proportion; - } - } - } - - // input allocations (takes precedence) - for (const [address, voteData] of Object.entries(inputVoteAllocations) as Entries) { - if (voteData.vote !== null) { - combinedAllocations[address] = voteData.value; - } else { - delete combinedAllocations[address]; - } - } - - for (const [address, value] of Object.entries(combinedAllocations)) { - // check if the initiative is still active - const initiativeState = initiativesStates.data?.[address as Address]; - if (!isInitiativeStatusActive(initiativeState?.status ?? "nonexistent")) { - continue; - } - remaining = dn.sub(remaining, value); - } - - return remaining; - }, [ - governanceUser.data, - inputVoteAllocations, - initiativesStates.data, - ]); - - const daysLeft = governanceState.data?.daysLeft ?? 0; - const rtf = new Intl.RelativeTimeFormat("en", { style: "long" }); - - const remaining = daysLeft > 1 - ? rtf.format(Math.ceil(daysLeft), "day") - : daysLeft > (1 / 24) - ? rtf.format(Math.ceil(daysLeft * 24), "hours") - : rtf.format(Math.ceil(daysLeft * 24 * 60), "minute"); - - const handleVote = (initiativeAddress: Address, vote: Vote | null) => { - setInputVoteAllocations((prev) => ({ - ...prev, - [initiativeAddress]: { - vote: prev[initiativeAddress]?.vote === vote ? null : vote, - value: dn.from(0), - }, - })); - }; - - const handleVoteInputChange = (initiativeAddress: Address, value: Dnum) => { - setInputVoteAllocations((prev) => ({ - ...prev, - [initiativeAddress]: { - vote: prev[initiativeAddress]?.vote ?? null, - value: dn.div(value, 100), - }, - })); - }; - - const allowSubmit = hasAnyAllocationChange && stakedLQTY && ( - ( - dn.eq(remainingVotingPower, 0) && hasAnyAllocations - ) || ( - dn.eq(remainingVotingPower, 1) - ) - ); - - const cutoffStartDate = governanceState.data && new Date( - Number(governanceState.data.cutoffStart) * 1000, - ); - const epochEndDate = governanceState.data && new Date( - Number(governanceState.data.epochEnd) * 1000, - ); - - if ( - governanceState.status !== "success" - || initiatives.status !== "success" - || initiativesStates.status !== "success" - || voteTotals.status !== "success" - || governanceUser.status !== "success" - ) { - return ( -
-
- - Loading -
-
- ); - } - - return ( -
-
-

- {content.stakeScreen.votingPanel.title} -

-
- {content.stakeScreen.votingPanel.intro} -
-
- -
- {governanceState.data && ( -
-
- Current voting round ends in{" "} -
- - {governanceState.data.daysLeftRounded} {governanceState.data.daysLeftRounded === 1 ? "day" : "days"} - -
- )} - -
- - Discuss - - - } - href="https://voting.liquity.org/" - external - /> -
-
- - {isCutoff && ( -
-
-
- - - -
-
Only downvotes are accepted today.
-
-
- )} - - div, & td:nth-of-type(3) > div, & td:nth-of-type(4) > div": { - display: "flex", - alignItems: "center", - justifyContent: "flex-end", - minHeight: 34, - }, - "& tr:last-child td": { - paddingBottom: 16, - }, - }, - "& tfoot": { - fontSize: 14, - color: "contentAlt", - "& td": { - borderTop: "1px solid token(colors.tableBorder)", - padding: "16px 0 32px", - }, - "& td:last-child": { - textAlign: "right", - }, - }, - })} - > - - - - - - - - {initiatives.data - // remove inactive initiatives that are not voted on - ?.filter((initiative) => ( - isInitiativeStatusActive( - initiativesStates.data?.[initiative.address]?.status ?? "nonexistent", - ) || Boolean( - voteAllocations[initiative.address], - ) - )) - .sort((a, b) => { - // 1. sort by allocation - const allocationA = voteAllocations[a.address]; - const allocationB = voteAllocations[b.address]; - if (allocationA && !allocationB) return -1; - if (!allocationA && allocationB) return 1; - - // 2. sort by status - const statusA = initiativesStates.data?.[a.address]?.status ?? "nonexistent"; - const statusB = initiativesStates.data?.[b.address]?.status ?? "nonexistent"; - const isActiveA = isInitiativeStatusActive(statusA); - const isActiveB = isInitiativeStatusActive(statusB); - if (isActiveA && !isActiveB) return -1; - if (!isActiveA && isActiveB) return 1; - - return 0; - }) - .map((initiative, index) => { - const status = initiativesStates.data?.[initiative.address]?.status; - return ( - - ); - })} - - - - - - -
- Epoch
Initiatives -
{hasAnyAllocations ? "Allocation" : "Decision"}
-
-
-
- 100% of your voting power needs to be allocated. -
-
-
- {"Remaining: "} - -
-
-
- - - - {governanceState.data && ( -
-
-
- - - -
-
-
- {cutoffStartDate && epochEndDate && ( -
- {isCutoff ? "Upvotes ended on " : "Upvotes accepted until "} - . - {" Downvotes accepted until "} - . -
- )} -
- Votes for epoch #{String(governanceState.data.epoch)} will be snapshotted {remaining}. -
-
-
- )} - - -
- ); -} - -function calculateVotesPct( - governanceState?: { countedVoteLQTY: bigint; countedVoteOffset: bigint; epochEnd: bigint }, - initiativeState?: VoteTotals, -) { - if (!governanceState || !initiativeState) return null; - - const totalVotingPower = votingPower( - governanceState.countedVoteLQTY, - governanceState.countedVoteOffset, - governanceState.epochEnd, - ); - - if (totalVotingPower === 0n) return DNUM_0; - - const initiativeVotingPower = votingPower( - initiativeState.voteLQTY, - initiativeState.voteOffset, - governanceState.epochEnd, - ); - - return dn.div(dnum18(initiativeVotingPower), dnum18(totalVotingPower)); -} - -function InitiativeRow({ - bribe, - disableFor, - disabled, - initiative, - initiativesStatus, - inputVoteAllocation, - onVote, - onVoteInputChange, - voteAllocation, - voteTotals, -}: { - bribe?: { - boldAmount: Dnum; - tokenAmount: Dnum; - tokenAddress: Address; - tokenSymbol: string; - }; - disableFor: boolean; - disabled: boolean; - initiative: Initiative; - initiativesStatus?: InitiativeStatus; - inputVoteAllocation?: VoteAllocations[Address]; - onVote: (initiative: Address, vote: Vote) => void; - onVoteInputChange: (initiative: Address, value: Dnum) => void; - voteAllocation?: VoteAllocation; - voteTotals?: VoteTotals; -}) { - const inputRef = useRef(null); - const [editIntent, setEditIntent] = useState(false); - const editMode = (editIntent || !voteAllocation?.vote) && !disabled; - const boldPrice = usePrice(bribe ? "BOLD" : null); - const bribeTokenPrice = usePrice(bribe ? bribe.tokenSymbol : null); - const governanceState = useGovernanceState(); - const votesPct = calculateVotesPct(governanceState.data, voteTotals); - - return ( - - -
-
-
- {initiative.url - ? ( - - {initiative.name ?? "Initiative"} - - - } - /> - ) - : ( - initiative.name ?? "Initiative" - )} -
- {initiativesStatus && ( -
- {initiativeStatusLabel(initiativesStatus)} -
- )} -
-
- -
- -
- Votes: - -
- - {bribe && (dn.gt(bribe.boldAmount, 0) || dn.gt(bribe.tokenAmount, 0)) && ( -
- Bribing: -
- {dn.gt(bribe.boldAmount, 0) && ( -
- - - {boldPrice.data && ( - - )} -
- )} - {dn.gt(bribe.tokenAmount, 0) && ( -
- - - {bribeTokenPrice.data && ( - - )} -
- )} -
-
- )} -
- - -
- {editMode - ? ( - { - onVoteInputChange(initiative.address, value); - }} - onVote={(vote) => { - onVote(initiative.address, vote); - }} - value={inputVoteAllocation?.value ?? null} - vote={inputVoteAllocation?.vote ?? null} - /> - ) - : voteAllocation?.vote - ? ( - { - setEditIntent(true); - setTimeout(() => { - inputRef.current?.focus(); - }, 0); - }} - disabled={disabled} - share={voteAllocation.value} - vote={voteAllocation.vote} - /> - ) - : ( - {}} - onVote={() => {}} - value={null} - vote={null} - /> - )} -
- - - ); -} - -function Vote({ - onEdit, - disabled, - share, - vote, -}: { - onEdit?: () => void; - disabled: boolean; - share: Dnum; - vote: Vote; -}) { - return ( -
-
-
- {vote === "for" && } - {vote === "against" && ( -
- -
- )} -
- {fmtnum(share, { preset: "pct2", suffix: "%" })} -
-
-
-
- ); -} - -function BribeMarketsInfo() { - return ( -
-
-

- Bribe Markets in Liquity V2 -

-

- Initiatives may offer bribes to incentivize votes, which are displayed in the table above and can be claimed - afterwards on this page. -

-
-
- - Learn more about bribes - - - } - /> -
-
- ); -} diff --git a/frontend/app/src/screens/StakeScreen/StakeScreen.tsx b/frontend/app/src/screens/StakeScreen/StakeScreen.tsx deleted file mode 100644 index 3f8ffef6f..000000000 --- a/frontend/app/src/screens/StakeScreen/StakeScreen.tsx +++ /dev/null @@ -1,292 +0,0 @@ -"use client"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { FlowButton } from "@/src/comps/FlowButton/FlowButton"; -import { LinkTextButton } from "@/src/comps/LinkTextButton/LinkTextButton"; -import { Screen } from "@/src/comps/Screen/Screen"; -import { StakePositionSummary } from "@/src/comps/StakePositionSummary/StakePositionSummary"; -import content from "@/src/content"; -import { CHAIN_ID } from "@/src/env"; -import { fmtnum } from "@/src/formatting"; -import { useBribingClaim, useNamedInitiatives } from "@/src/liquity-governance"; -import { useStakePosition } from "@/src/liquity-utils"; -import type { Address, Initiative } from "@/src/types"; -import { tokenIconUrl } from "@/src/utils"; -import { useAccount } from "@/src/wagmi-utils"; -import { css } from "@/styled-system/css"; -import { shortenAddress, Tabs, TokenIcon, VFlex } from "@liquity2/uikit"; -import * as dn from "dnum"; -import { useParams, useRouter } from "next/navigation"; -import { useMemo } from "react"; -import { PanelRewards } from "./PanelRewards"; -import { PanelStaking } from "./PanelStaking"; -import { PanelVoting } from "./PanelVoting"; - -const TABS = [ - { label: content.stakeScreen.tabs.deposit, id: "deposit" }, - { label: content.stakeScreen.tabs.rewards, id: "rewards" }, - { label: content.stakeScreen.tabs.voting, id: "voting" }, -]; - -export function StakeScreen() { - const router = useRouter(); - const { action = "deposit" } = useParams(); - const account = useAccount(); - const stakePosition = useStakePosition(account.address ?? null); - const bribingClaim = useBribingClaim(account.address ?? null); - const initiatives = useNamedInitiatives(); - - return ( - - {content.stakeScreen.headline()} - - ), - subtitle: ( - <> - {content.stakeScreen.subheading}{" "} - - - ), - }} - > - - {bribingClaim.data && bribingClaim.data.claimableInitiatives.length > 0 && ( -
- -
- )} - - ({ - label, - panelId: `p-${id}`, - tabId: `t-${id}`, - }))} - selected={TABS.findIndex(({ id }) => id === action)} - onSelect={(index) => { - const tab = TABS[index]; - if (!tab) { - throw new Error("Invalid tab index"); - } - router.push(`/stake/${tab.id}`, { scroll: false }); - }} - /> - - {action === "deposit" && } - {action === "rewards" && } - {action === "voting" && } - -
- ); -} - -function BribesInfoBox({ - bribingClaim, - initiatives, -}: { - bribingClaim: NonNullable["data"]>; - initiatives: Initiative[]; -}) { - const { claimableInitiatives } = bribingClaim; - - const bribesByInitiative = useMemo(() => ( - new Map(claimableInitiatives.map((claim) => [ - claim.initiative.toLowerCase() as Address, - claim, - ])) - ), [claimableInitiatives]); - - const initiativesByAddress = useMemo(() => ( - new Map(initiatives.map((initiative) => [ - initiative.address.toLowerCase() as Address, - initiative, - ])) - ), [initiatives]); - - return ( -
-
- Claimable Bribes -
- -
- You have unclaimed bribe rewards from your past votes. Claim them below. -
- -
- {[...bribesByInitiative.entries()].map(([initiative, data]) => { - const initiativeData = initiativesByAddress.get(initiative.toLowerCase() as Address); - const initiativeName = initiativeData?.name ?? shortenAddress(initiative, 6); - const bribeToken = bribingClaim.bribeTokens.find((token) => token.address === data.bribeTokenAddress); - return ( -
-
- {initiativeName} -
-
-
- - -
- {dn.gt(data.bribeTokenAmount, 0) && ( -
- - -
- )} -
- {data.epochs.length} epoch{data.epochs.length > 1 ? "s" : ""} -
-
- { - const claimData = bribingClaim.claimableInitiatives - .find((c) => c.initiative === initiative)?.claimData; - return claimData && { - flowId: "claimBribes", - backLink: ["/stake", "Back to staking"], - successLink: ["/stake", "Back to staking"], - successMessage: "Bribes have been claimed successfully.", - initiative, - initiativeName, - boldAmount: data.boldAmount, - bribeTokenAmount: data.bribeTokenAmount, - bribeTokenAddress: data.bribeTokenAddress, - bribeTokenSymbol: bribeToken?.symbol ?? "UNKNOWN", - claimData, - }; - }} - size="mini" - /> -
-
-
- ); - })} -
-
- ); -} diff --git a/frontend/app/src/services/TransactionFlow.tsx b/frontend/app/src/services/TransactionFlow.tsx index 186817e05..33677277c 100644 --- a/frontend/app/src/services/TransactionFlow.tsx +++ b/frontend/app/src/services/TransactionFlow.tsx @@ -24,95 +24,63 @@ import { estimateGas, readContract, writeContract } from "wagmi/actions"; /* flows registration */ -import { allocateVotingPower, type AllocateVotingPowerRequest } from "@/src/tx-flows/allocateVotingPower"; import { claimBribes, type ClaimBribesRequest } from "@/src/tx-flows/claimBribes"; import { claimCollateralSurplus, type ClaimCollateralSurplusRequest } from "@/src/tx-flows/claimCollateralSurplus"; import { closeLoanPosition, type CloseLoanPositionRequest } from "@/src/tx-flows/closeLoanPosition"; import { earnClaimRewards, type EarnClaimRewardsRequest } from "@/src/tx-flows/earnClaimRewards"; import { earnUpdate, type EarnUpdateRequest } from "@/src/tx-flows/earnUpdate"; -import { legacyCloseLoanPosition, type LegacyCloseLoanPositionRequest } from "@/src/tx-flows/legacyCloseLoanPosition"; -import { legacyEarnWithdrawAll, type LegacyEarnWithdrawAllRequest } from "@/src/tx-flows/legacyEarnWithdrawAll"; -import { legacyRedeemCollateral, type LegacyRedeemCollateralRequest } from "@/src/tx-flows/legacyRedeemCollateral"; -import { legacyUnstakeAll, type LegacyUnstakeAllRequest } from "@/src/tx-flows/legacyUnstakeAll"; import { openBorrowPosition, type OpenBorrowPositionRequest } from "@/src/tx-flows/openBorrowPosition"; import { openLeveragePosition, type OpenLeveragePositionRequest } from "@/src/tx-flows/openLeveragePosition"; import { redeemCollateral, type RedeemCollateralRequest } from "@/src/tx-flows/redeemCollateral"; import { sboldDeposit, type SboldDepositRequest } from "@/src/tx-flows/sboldDeposit"; import { sboldRedeem, type SboldRedeemRequest } from "@/src/tx-flows/sboldRedeem"; -import { stakeClaimRewards, type StakeClaimRewardsRequest } from "@/src/tx-flows/stakeClaimRewards"; -import { stakeDeposit, type StakeDepositRequest } from "@/src/tx-flows/stakeDeposit"; -import { unstakeDeposit, type UnstakeDepositRequest } from "@/src/tx-flows/unstakeDeposit"; import { updateBorrowPosition, type UpdateBorrowPositionRequest } from "@/src/tx-flows/updateBorrowPosition"; import { updateLeveragePosition, type UpdateLeveragePositionRequest } from "@/src/tx-flows/updateLeveragePosition"; import { updateLoanInterestRate, type UpdateLoanInterestRateRequest } from "@/src/tx-flows/updateLoanInterestRate"; export type FlowRequestMap = { - "allocateVotingPower": AllocateVotingPowerRequest; "claimBribes": ClaimBribesRequest; "claimCollateralSurplus": ClaimCollateralSurplusRequest; "closeLoanPosition": CloseLoanPositionRequest; "earnClaimRewards": EarnClaimRewardsRequest; "earnUpdate": EarnUpdateRequest; - "legacyCloseLoanPosition": LegacyCloseLoanPositionRequest; - "legacyEarnWithdrawAll": LegacyEarnWithdrawAllRequest; - "legacyRedeemCollateral": LegacyRedeemCollateralRequest; - "legacyUnstakeAll": LegacyUnstakeAllRequest; "openBorrowPosition": OpenBorrowPositionRequest; "openLeveragePosition": OpenLeveragePositionRequest; "redeemCollateral": RedeemCollateralRequest; "sboldDeposit": SboldDepositRequest; "sboldRedeem": SboldRedeemRequest; - "stakeClaimRewards": StakeClaimRewardsRequest; - "stakeDeposit": StakeDepositRequest; - "unstakeDeposit": UnstakeDepositRequest; "updateBorrowPosition": UpdateBorrowPositionRequest; "updateLeveragePosition": UpdateLeveragePositionRequest; "updateLoanInterestRate": UpdateLoanInterestRateRequest; }; const FlowIdSchema = v.union([ - v.literal("allocateVotingPower"), v.literal("claimBribes"), v.literal("claimCollateralSurplus"), v.literal("closeLoanPosition"), v.literal("earnClaimRewards"), v.literal("earnUpdate"), - v.literal("legacyCloseLoanPosition"), - v.literal("legacyEarnWithdrawAll"), - v.literal("legacyRedeemCollateral"), - v.literal("legacyUnstakeAll"), v.literal("openBorrowPosition"), v.literal("openLeveragePosition"), v.literal("redeemCollateral"), v.literal("sboldDeposit"), v.literal("sboldRedeem"), - v.literal("stakeClaimRewards"), - v.literal("stakeDeposit"), - v.literal("unstakeDeposit"), v.literal("updateBorrowPosition"), v.literal("updateLeveragePosition"), v.literal("updateLoanInterestRate"), ]); export const flows: FlowsMap = { - allocateVotingPower, claimBribes, claimCollateralSurplus, closeLoanPosition, earnClaimRewards, earnUpdate, - legacyCloseLoanPosition, - legacyEarnWithdrawAll, - legacyRedeemCollateral, - legacyUnstakeAll, openBorrowPosition, openLeveragePosition, redeemCollateral, sboldDeposit, sboldRedeem, - stakeClaimRewards, - stakeDeposit, - unstakeDeposit, updateBorrowPosition, updateLeveragePosition, updateLoanInterestRate, diff --git a/frontend/app/src/subgraph.ts b/frontend/app/src/subgraph.ts index 4d1849b9e..0b99fa612 100644 --- a/frontend/app/src/subgraph.ts +++ b/frontend/app/src/subgraph.ts @@ -249,99 +249,3 @@ export async function getAllInterestRateBrackets() { })) .sort((a, b) => dn.cmp(a.rate, b.rate)); } - -const GovernanceGlobalDataQuery = graphql(` - query GovernanceGlobalData { - governanceInitiatives { - id - } - - governanceVotingPower(id: "total") { - allocatedLQTY - allocatedOffset - unallocatedLQTY - unallocatedOffset - } - } -`); - -// get all the registered initiatives and total voting power -export async function getGovernanceGlobalData() { - const { governanceInitiatives, governanceVotingPower } = await graphQuery(GovernanceGlobalDataQuery); - return { - registeredInitiatives: governanceInitiatives.map((initiative) => initiative.id as Address), - - totalVotingPower: { - allocatedLQTY: BigInt(governanceVotingPower?.allocatedLQTY ?? 0), - allocatedOffset: BigInt(governanceVotingPower?.allocatedOffset ?? 0), - unallocatedLQTY: BigInt(governanceVotingPower?.unallocatedLQTY ?? 0), - unallocatedOffset: BigInt(governanceVotingPower?.unallocatedOffset ?? 0), - }, - }; -} - -const UserAllocationHistoryQuery = graphql(` - query UserAllocationHistory($user: String) { - governanceAllocations( - where: { user: $user } - orderBy: epoch - orderDirection: desc - first: 1000 - ) { - epoch - initiative { id } - voteLQTY - vetoLQTY - voteOffset - vetoOffset - } - } -`); - -const TotalAllocationHistoryQuery = graphql(` - query TotalAllocationHistory($initiative: String) { - governanceAllocations( - where: { initiative: $initiative, user: null } - orderBy: epoch - orderDirection: desc - first: 1000 - ) { - epoch - voteLQTY - vetoLQTY - voteOffset - vetoOffset - } - } -`); - -// A user's allocation history ordered by descending epoch -export async function getUserAllocationHistoryFromSubgraph(user: Address) { - const { governanceAllocations } = await graphQuery(UserAllocationHistoryQuery, { - user: user.toLowerCase(), - }); - - return governanceAllocations.map((allocation) => ({ - epoch: Number(allocation.epoch), - initiative: allocation.initiative.id, - voteLQTY: BigInt(allocation.voteLQTY), - vetoLQTY: BigInt(allocation.vetoLQTY), - voteOffset: BigInt(allocation.voteOffset), - vetoOffset: BigInt(allocation.vetoOffset), - })); -} - -// An initiative's total allocation history ordered by descending epoch -export async function getTotalAllocationHistoryFromSubgraph(initiative: Address) { - const { governanceAllocations } = await graphQuery(TotalAllocationHistoryQuery, { - initiative: initiative.toLowerCase(), - }); - - return governanceAllocations.map((allocation) => ({ - epoch: Number(allocation.epoch), - voteLQTY: BigInt(allocation.voteLQTY), - vetoLQTY: BigInt(allocation.vetoLQTY), - voteOffset: BigInt(allocation.voteOffset), - vetoOffset: BigInt(allocation.vetoOffset), - })); -} diff --git a/frontend/app/src/tx-flows/allocateVotingPower.tsx b/frontend/app/src/tx-flows/allocateVotingPower.tsx deleted file mode 100644 index 06b95abc7..000000000 --- a/frontend/app/src/tx-flows/allocateVotingPower.tsx +++ /dev/null @@ -1,303 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; -import type { Address, Dnum, Initiative, VoteAllocation } from "@/src/types"; - -import { AddressLink } from "@/src/comps/AddressLink/AddressLink"; -import { Amount } from "@/src/comps/Amount/Amount"; -import { GAS_ALLOCATE_LQTY_MIN_HEADROOM } from "@/src/constants"; -import { getUserAllocatedInitiatives } from "@/src/liquity-governance"; -import { getUserStates, useGovernanceUser, useNamedInitiatives } from "@/src/liquity-governance"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { vVoteAllocations } from "@/src/valibot-utils"; -import { css } from "@/styled-system/css"; -import { IconDownvote, IconStake, IconUpvote } from "@liquity2/uikit"; -import * as dn from "dnum"; -import * as v from "valibot"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "allocateVotingPower", - { - voteAllocations: vVoteAllocations(), - }, -); - -export type AllocateVotingPowerRequest = v.InferOutput; - -export const allocateVotingPower: FlowDeclaration = { - title: "Review & Send Transaction", - - Summary({ request, account }) { - const governanceUser = useGovernanceUser(account); - const stakedLqty: Dnum = [governanceUser.data?.stakedLQTY ?? 0n, 18]; - - let totalLqtyAllocation: Dnum = [0n, 18]; - for (const vote of Object.values(request.voteAllocations)) { - if (vote) { - totalLqtyAllocation = dn.add( - totalLqtyAllocation, - dn.mul(stakedLqty, vote.value), - ); - } - } - - const votesCount = Object.keys(request.voteAllocations).length; - - return ( -
-
-

-
-
- -
- LQTY Stake -
-

-
-
-
-
-
- {" "} - LQTY -
-
-
- Allocated to {votesCount} initiative{votesCount === 1 ? "" : "s"} -
-
-
-
- ); - }, - - Details({ request, account }) { - const initiatives = useNamedInitiatives(); - const governanceUser = useGovernanceUser(account); - const stakedLQTY = governanceUser.data?.stakedLQTY ?? 0n; - const allocations = Object.entries(request.voteAllocations); - if (allocations.length === 0) { - return ( - - ); - } - return allocations.map(([address, vote]) => { - const initiative = initiatives.data?.find((i) => i.address === address); - return !initiative || !vote ? null : ( - - ); - }); - }, - - steps: { - allocateVotingPower: { - name: () => "Cast votes", - Status: TransactionStatus, - - async commit(ctx) { - const userStates = await getUserStates(ctx.wagmiConfig, ctx.account); - - const { voteAllocations } = ctx.request; - const { stakedLQTY } = userStates; - - const initiativeAddresses = Object.keys(voteAllocations) as Address[]; - - const allocationArgs = { - initiatives: initiativeAddresses, - votes: Array.from({ length: initiativeAddresses.length }).fill(0n), - vetos: Array.from({ length: initiativeAddresses.length }).fill(0n), - }; - - let remainingLQTY = stakedLQTY; - let [remainingVotePct] = Object.values(voteAllocations) - .map((x) => x?.value) - .filter((x) => x !== undefined) - .reduce((a, b) => dn.add(a, b), [0n, 18]); - - for (const [index, address] of initiativeAddresses.entries()) { - const vote = voteAllocations[address]; - if (!vote) { - throw new Error("Vote not found"); - } - - const votePct = dn.from(vote.value, 18)[0]; - const qty = remainingLQTY * votePct / remainingVotePct; - remainingLQTY -= qty; - remainingVotePct -= votePct; - - if (vote?.vote === "for") { - allocationArgs.votes[index] = qty; - } else if (vote?.vote === "against") { - allocationArgs.vetos[index] = qty; - } - } - - const allocated = await getUserAllocatedInitiatives( - ctx.wagmiConfig, - ctx.account, - ); - - return ctx.writeContract({ - ...ctx.contracts.Governance, - functionName: "allocateLQTY", - args: [ - allocated, // allocations to reset - allocationArgs.initiatives, - allocationArgs.votes, - allocationArgs.vetos, - ], - }, GAS_ALLOCATE_LQTY_MIN_HEADROOM); - }, - - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - }, - - async getSteps() { - return ["allocateVotingPower"]; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; - -function VoteAllocation({ - initiative, - vote, - stakedLQTY, -}: { - initiative: Initiative; - vote: VoteAllocation; - stakedLQTY: bigint; -}) { - const lqtyAllocation = dn.mul([stakedLQTY, 18], vote.value); - return ( - - {initiative.protocol ?? } - , - ]} - value={[ -
- - {vote.vote === "for" - ? - : } -
, -
- {vote.vote === "for" ? "Upvote" : "Downvote"} with LQTY -
, - ]} - /> - ); -} diff --git a/frontend/app/src/tx-flows/closeLoanPosition.tsx b/frontend/app/src/tx-flows/closeLoanPosition.tsx index 634940f77..3ab05b206 100644 --- a/frontend/app/src/tx-flows/closeLoanPosition.tsx +++ b/frontend/app/src/tx-flows/closeLoanPosition.tsx @@ -90,9 +90,9 @@ export const closeLoanPosition: FlowDeclaration = { value={[
- {fmtnum(ETH_GAS_COMPENSATION, 4)} ETH + {fmtnum(ETH_GAS_COMPENSATION, 4)} ANKR
, ]} /> @@ -118,7 +118,7 @@ export const closeLoanPosition: FlowDeclaration = { args: [BigInt(loan.troveId)], }); - const Zapper = branch.symbol === "ETH" + const Zapper = branch.symbol === "ANKR" ? branch.contracts.LeverageWETHZapper : branch.contracts.LeverageLSTZapper; @@ -148,7 +148,7 @@ export const closeLoanPosition: FlowDeclaration = { const branch = getBranch(loan.branchId); // repay with BOLD => get ETH - if (!ctx.request.repayWithCollateral && branch.symbol === "ETH") { + if (!ctx.request.repayWithCollateral && branch.symbol === "ANKR") { return ctx.writeContract({ ...branch.contracts.LeverageWETHZapper, functionName: "closeTroveToRawETH", @@ -178,7 +178,7 @@ export const closeLoanPosition: FlowDeclaration = { } // repay with collateral => get ETH - if (branch.symbol === "ETH") { + if (branch.symbol === "ANKR") { return ctx.writeContract({ ...branch.contracts.LeverageWETHZapper, functionName: "closeTroveFromCollateral", @@ -216,7 +216,7 @@ export const closeLoanPosition: FlowDeclaration = { const { loan } = ctx.request; const branch = getBranch(loan.branchId); - const Zapper = branch.symbol === "ETH" + const Zapper = branch.symbol === "ANKR" ? branch.contracts.LeverageWETHZapper : branch.contracts.LeverageLSTZapper; diff --git a/frontend/app/src/tx-flows/legacyCloseLoanPosition.tsx b/frontend/app/src/tx-flows/legacyCloseLoanPosition.tsx deleted file mode 100644 index 4c6855cee..000000000 --- a/frontend/app/src/tx-flows/legacyCloseLoanPosition.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; - -import { LeverageLSTZapper } from "@/src/abi/LeverageLSTZapper"; -import { LeverageWETHZapper } from "@/src/abi/LeverageWETHZapper"; -import { Amount } from "@/src/comps/Amount/Amount"; -import { ETH_GAS_COMPENSATION } from "@/src/constants"; -import { LEGACY_CHECK } from "@/src/env"; -import { fmtnum } from "@/src/formatting"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { usePrice } from "@/src/services/Prices"; -import { vBranchId, vDnum, vTroveId } from "@/src/valibot-utils"; -import * as dn from "dnum"; -import * as v from "valibot"; -import { erc20Abi, maxUint256 } from "viem"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "legacyCloseLoanPosition", - { - trove: v.object({ - branchId: vBranchId(), - troveId: vTroveId(), - deposit: vDnum(), - borrowed: vDnum(), - }), - }, -); - -export type LegacyCloseLoanPositionRequest = v.InferOutput; - -function getLegacyBranch(branchId: number) { - const branch = LEGACY_CHECK?.BRANCHES[branchId]; - if (!branch) { - throw new Error(`Invalid branch ID: ${branchId}`); - } - return branch; -} - -export const legacyCloseLoanPosition: FlowDeclaration = { - title: "Close Legacy Loan Position", - Summary: null, - - Details({ request }) { - const { trove } = request; - const branch = getLegacyBranch(trove.branchId); - const collPrice = usePrice(branch.symbol); - - if (!collPrice.data) { - return null; - } - - return ( - <> - {dn.gt(trove.borrowed, 0) && ( - , - ]} - /> - )} - , - ]} - /> - - {fmtnum(ETH_GAS_COMPENSATION, 4)} ETH - , - ]} - /> - - ); - }, - - steps: { - approveBold: { - name: () => "Approve BOLD", - Status: (props) => ( - - ), - async commit(ctx) { - const { trove } = ctx.request; - const { LEVERAGE_ZAPPER } = getLegacyBranch(trove.branchId); - if (!LEGACY_CHECK?.BOLD_TOKEN) { - throw new Error("BOLD token address not available"); - } - return ctx.writeContract({ - abi: erc20Abi, - address: LEGACY_CHECK.BOLD_TOKEN, - functionName: "approve", - args: [ - LEVERAGE_ZAPPER, - ctx.preferredApproveMethod === "approve-infinite" - ? maxUint256 // infinite approval - : dn.mul(trove.borrowed, 1.1)[0], // exact amount (TODO: better estimate) - ], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - - // Close a loan position, repaying with BOLD or with the collateral - closeLoanPosition: { - name: () => "Close loan", - Status: TransactionStatus, - - async commit(ctx) { - const { trove } = ctx.request; - const branch = getLegacyBranch(trove.branchId); - const { LEVERAGE_ZAPPER } = branch; - - // repay with BOLD => get ETH - if (branch.symbol === "ETH") { - return ctx.writeContract({ - abi: LeverageWETHZapper, - address: LEVERAGE_ZAPPER, - functionName: "closeTroveToRawETH", - args: [BigInt(trove.troveId)], - }); - } - - // repay with BOLD => get LST - return ctx.writeContract({ - abi: LeverageLSTZapper, - address: LEVERAGE_ZAPPER, - functionName: "closeTroveToRawETH", - args: [BigInt(trove.troveId)], - }); - }, - - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - }, - - async getSteps(ctx) { - const { trove } = ctx.request; - const branch = getLegacyBranch(trove.branchId); - - if (!LEGACY_CHECK?.BOLD_TOKEN) { - throw new Error("BOLD token address not available"); - } - const isBoldApproved = !dn.gt(trove.borrowed, [ - (await ctx.readContract({ - abi: erc20Abi, - address: LEGACY_CHECK.BOLD_TOKEN, - functionName: "allowance", - args: [ctx.account, branch.LEVERAGE_ZAPPER], - })) ?? 0n, - 18, - ]); - - const steps: string[] = []; - - if (!isBoldApproved) { - steps.push("approveBold"); - } - - steps.push("closeLoanPosition"); - - return steps; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; diff --git a/frontend/app/src/tx-flows/legacyEarnWithdrawAll.tsx b/frontend/app/src/tx-flows/legacyEarnWithdrawAll.tsx deleted file mode 100644 index 64ab99036..000000000 --- a/frontend/app/src/tx-flows/legacyEarnWithdrawAll.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; - -import { StabilityPool } from "@/src/abi/StabilityPool"; -import { Amount } from "@/src/comps/Amount/Amount"; -import { LEGACY_CHECK } from "@/src/env"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { vDnum } from "@/src/valibot-utils"; -import { Fragment } from "react"; -import * as v from "valibot"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "legacyEarnWithdrawAll", - { - pools: v.array( - v.object({ - branchIndex: v.number(), - deposit: vDnum(), - rewards: v.object({ - bold: vDnum(), - coll: vDnum(), - }), - }), - ), - }, -); - -export type LegacyEarnWithdrawAllRequest = v.InferOutput; -type Step = FlowDeclaration["steps"][number]; - -export const legacyEarnWithdrawAll: FlowDeclaration = { - title: "Withdraw from Legacy Earn Pools", - Summary: null, - Details({ request }) { - return request.pools.map((pool) => { - const branch = LEGACY_CHECK?.BRANCHES[pool.branchIndex]; - return ( - branch && ( - - 0n || pool.rewards.bold[0] > 0n) - ? "And claim rewards" - : null, - ]} - value={[ - , - pool.rewards.coll[0] > 0n - ? ( - - ) - : null, - pool.rewards.bold[0] > 0n - ? ( - - ) - : null, - ]} - /> - - ) - ); - }); - }, - - // doing this is easier than supporting dynamic steps, - // since this is the only tx flow using them - steps: LEGACY_CHECK - ? { - withdrawFromStabilityPool0: getWithdrawStep(0), - withdrawFromStabilityPool1: getWithdrawStep(1), - withdrawFromStabilityPool2: getWithdrawStep(2), - withdrawFromStabilityPool3: getWithdrawStep(3), - withdrawFromStabilityPool4: getWithdrawStep(4), - withdrawFromStabilityPool5: getWithdrawStep(5), - withdrawFromStabilityPool6: getWithdrawStep(6), - withdrawFromStabilityPool7: getWithdrawStep(7), - withdrawFromStabilityPool8: getWithdrawStep(8), - withdrawFromStabilityPool9: getWithdrawStep(9), - } - : {}, - - async getSteps({ request }) { - return request.pools.map( - (pool) => `withdrawFromStabilityPool${pool.branchIndex}`, - ); - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; - -function getWithdrawStep(legacyBranchIndex: number): Step { - const legacyBranch = LEGACY_CHECK?.BRANCHES[legacyBranchIndex]; - if (!legacyBranch) { - if (legacyBranchIndex === 0) { - throw new Error("Legacy branch not found"); - } - return getWithdrawStep(0); // fallback to make the type checker happy - } - return { - name: () => `Withdraw from ${legacyBranch.name} pool`, - Status: TransactionStatus, - async commit({ request, writeContract }) { - const pool = request.pools.find((pool) => ( - pool.branchIndex === legacyBranchIndex - )); - if (!pool) { - throw new Error("Pool not found"); - } - return writeContract({ - abi: StabilityPool, - address: legacyBranch.STABILITY_POOL, - functionName: "withdrawFromSP", - args: [pool.deposit[0], true], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }; -} diff --git a/frontend/app/src/tx-flows/legacyRedeemCollateral.tsx b/frontend/app/src/tx-flows/legacyRedeemCollateral.tsx deleted file mode 100644 index 3ead69bc6..000000000 --- a/frontend/app/src/tx-flows/legacyRedeemCollateral.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; -import type { Address } from "@/src/types"; - -import { CollateralRegistry } from "@/src/abi/CollateralRegistry"; -import { Amount } from "@/src/comps/Amount/Amount"; -import { dnum18, jsonStringifyWithDnum } from "@/src/dnum-utils"; -import { LEGACY_CHECK } from "@/src/env"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { vDnum } from "@/src/valibot-utils"; -import { useQuery } from "@tanstack/react-query"; -import * as dn from "dnum"; -import { Fragment } from "react"; -import * as v from "valibot"; -import { createPublicClient, erc20Abi } from "viem"; -import { http, useConfig as useWagmiConfig } from "wagmi"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "legacyRedeemCollateral", - { - amount: vDnum(), - maxFee: vDnum(), - }, -); - -export type LegacyRedeemCollateralRequest = v.InferOutput; - -export const legacyRedeemCollateral: FlowDeclaration = { - title: "Review & Send Transaction", - Summary: () => null, - Details(ctx) { - const estimatedGains = useSimulatedBalancesChange(ctx); - const boldChange = estimatedGains.data?.find(({ symbol }) => symbol === "BOLD")?.change; - const collChanges = estimatedGains.data?.filter(({ symbol }) => symbol !== "BOLD"); - return ( - <> - , - ]} - /> - , - - Estimated BOLD that will be redeemed. - , - ]} - /> - {LEGACY_CHECK?.BRANCHES.map(({ symbol }) => { - const collChange = collChanges?.find((change) => symbol === change.symbol)?.change; - const symbol_ = symbol === "ETH" ? "WETH" : symbol; - return ( - , - - Estimated {symbol_} you will receive. - , - ]} - /> - ); - })} - - ); - }, - steps: { - approve: { - name: () => "Approve BOLD", - Status: TransactionStatus, - async commit({ request, writeContract }) { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK is not defined"); - } - return writeContract({ - abi: erc20Abi, - address: LEGACY_CHECK.BOLD_TOKEN, - functionName: "approve", - args: [LEGACY_CHECK.COLLATERAL_REGISTRY, request.amount[0]], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - redeemCollateral: { - name: () => "Redeem BOLD", - Status: TransactionStatus, - async commit({ request, writeContract }) { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK is not defined"); - } - return writeContract({ - abi: CollateralRegistry, - address: LEGACY_CHECK.COLLATERAL_REGISTRY, - functionName: "redeemCollateral", - args: [ - request.amount[0], - 0n, - request.maxFee[0], - ], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - }, - - async getSteps(ctx) { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK is not defined"); - } - - const steps = []; - - // check for allowance - const boldAllowance = await ctx.readContract({ - abi: erc20Abi, - address: LEGACY_CHECK.BOLD_TOKEN, - functionName: "allowance", - args: [ctx.account, LEGACY_CHECK.COLLATERAL_REGISTRY], - }); - - if (dn.gt(ctx.request.amount, dnum18(boldAllowance))) { - steps.push("approve"); - } - - steps.push("redeemCollateral"); - - return steps; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; - -export const StoredBalancesChangeSchema = v.object({ - stringifiedRequest: v.string(), - balanceChanges: v.array(v.object({ - symbol: v.string(), - change: vDnum(), - })), -}); - -export function useSimulatedBalancesChange({ - account, - request, -}: { - account: Address; - request: LegacyRedeemCollateralRequest; -}) { - const wagmiConfig = useWagmiConfig(); - return useQuery({ - queryKey: ["simulatedBalancesChange", account, jsonStringifyWithDnum(request)], - queryFn: async () => { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK is not defined"); - } - - const [chain] = wagmiConfig.chains; - const [rpcUrl] = chain.rpcUrls.default.http; - const client = createPublicClient({ chain, transport: http(rpcUrl) }); - - const branchesBalanceCalls = LEGACY_CHECK.BRANCHES.map((branch) => ({ - to: branch.COLL_TOKEN, - abi: erc20Abi, - functionName: "balanceOf" as const, - args: [account], - } as const)); - - const boldBalanceCall = { - to: LEGACY_CHECK.BOLD_TOKEN, - abi: erc20Abi, - functionName: "balanceOf" as const, - args: [account], - } as const; - - const simulation = await client.simulateCalls({ - account, - calls: [ - // 1. get balances before - boldBalanceCall, - ...branchesBalanceCalls, - - // 2. redeem - { - to: LEGACY_CHECK.COLLATERAL_REGISTRY, - abi: CollateralRegistry, - functionName: "redeemCollateral", - args: [request.amount[0], 0n, request.maxFee[0]], - }, - - // 3. get balances after - boldBalanceCall, - ...branchesBalanceCalls, - ], - - // This is needed to avoid a “nonce too low” error with certain RPCs - stateOverrides: [{ address: account, nonce: 0 }], - }); - - const getBalancesFromSimulated = (position: number) => { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK is not defined"); - } - return simulation.results - .slice(position, position + LEGACY_CHECK.BRANCHES.length + 1) - .map((result, index) => { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK is not defined"); - } - const symbol = index === 0 ? "BOLD" : LEGACY_CHECK.BRANCHES[index - 1]?.symbol; - return { - symbol, - balance: dnum18(result.data ?? 0n), - }; - }); - }; - - const balancesBefore = getBalancesFromSimulated(0); - const balancesAfter = getBalancesFromSimulated(LEGACY_CHECK.BRANCHES.length + 2); - - return balancesBefore.map((balanceBefore, index) => { - const balanceAfter = balancesAfter[index]; - if (!balanceAfter) throw new Error(); - return { - symbol: balanceBefore.symbol, - change: dn.sub(balanceAfter.balance, balanceBefore.balance), - }; - }); - }, - }); -} diff --git a/frontend/app/src/tx-flows/legacyUnstakeAll.tsx b/frontend/app/src/tx-flows/legacyUnstakeAll.tsx deleted file mode 100644 index 069e04167..000000000 --- a/frontend/app/src/tx-flows/legacyUnstakeAll.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; - -import { Governance } from "@/src/abi/Governance"; -import { Amount } from "@/src/comps/Amount/Amount"; -import { LEGACY_CHECK } from "@/src/env"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { usePrice } from "@/src/services/Prices"; -import { vAddress, vDnum } from "@/src/valibot-utils"; -import * as dn from "dnum"; -import * as v from "valibot"; -import { encodeFunctionData } from "viem"; -import { readContracts } from "wagmi/actions"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "legacyUnstakeAll", - { - lqtyAmount: vDnum(), - }, -); - -export type LegacyUnstakeAllRequest = v.InferOutput; - -export const legacyUnstakeAll: FlowDeclaration = { - title: "Withdraw from Legacy Stake", - Summary: null, - - Details({ request }) { - const lqtyPrice = usePrice("LQTY"); - return ( - , - , - ]} - /> - ); - }, - - steps: { - resetVotesAndWithdrawAll: { - name: () => "Unstake", - Status: TransactionStatus, - async commit(ctx) { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK is not defined"); - } - - const inputs: `0x${string}`[] = []; - - const initiativesFromSnapshotResult = await fetch(LEGACY_CHECK.INITIATIVES_SNAPSHOT_URL).catch((err) => { - console.error("Error fetching initiatives from snapshot."); - console.error("LEGACY_CHECK.INITIATIVES_SNAPSHOT_URL:", LEGACY_CHECK?.INITIATIVES_SNAPSHOT_URL); - throw err; - }); - - const initiativesFromSnapshot = v.parse( - v.array(vAddress()), - await initiativesFromSnapshotResult.json(), - ); - - const lqtyAllocatedByUser = await readContracts(ctx.wagmiConfig, { - contracts: initiativesFromSnapshot.map((initiative) => { - if (!LEGACY_CHECK) { - throw new Error("LEGACY_CHECK is not defined"); - } - return { - abi: Governance, - address: LEGACY_CHECK.GOVERNANCE, - functionName: "lqtyAllocatedByUserToInitiative", - args: [ctx.account, initiative], - } as const; - }), - allowFailure: false, - }); - - const allocatedInitiatives = lqtyAllocatedByUser - .map((allocation, index) => { - const [voteLQTY, _, vetoLQTY] = allocation; - const initiative = initiativesFromSnapshot[index]; - if (!initiative) { - throw new Error("initiative missing"); - } - return voteLQTY > 0n || vetoLQTY > 0n ? initiative : null; - }) - .filter((initiative) => initiative !== null); - - // reset allocations if the user has any - if (allocatedInitiatives.length > 0) { - inputs.push(encodeFunctionData({ - abi: Governance, - functionName: "resetAllocations", - args: [allocatedInitiatives, true], - })); - } - - // withdraw all staked LQTY - inputs.push(encodeFunctionData({ - abi: Governance, - functionName: "withdrawLQTY", - args: [ctx.request.lqtyAmount[0]], - })); - - return ctx.writeContract({ - abi: Governance, - address: LEGACY_CHECK.GOVERNANCE, - functionName: "multiDelegateCall", - args: [inputs], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - }, - - async getSteps() { - return ["resetVotesAndWithdrawAll"]; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; diff --git a/frontend/app/src/tx-flows/openBorrowPosition.tsx b/frontend/app/src/tx-flows/openBorrowPosition.tsx index b80123ce6..386962073 100644 --- a/frontend/app/src/tx-flows/openBorrowPosition.tsx +++ b/frontend/app/src/tx-flows/openBorrowPosition.tsx @@ -211,9 +211,9 @@ export const openBorrowPosition: FlowDeclaration = { value={[
- {fmtnum(ETH_GAS_COMPENSATION, 4)} ETH + {fmtnum(ETH_GAS_COMPENSATION, 4)} ANKR
, "Only used in case of liquidation", ]} @@ -373,7 +373,7 @@ export const openBorrowPosition: FlowDeclaration = { const branch = getBranch(ctx.request.branchId); // ETH doesn't need approval - if (branch.symbol === "ETH") { + if (branch.symbol === "ANKR") { return ["openTroveEth"]; } diff --git a/frontend/app/src/tx-flows/openLeveragePosition.tsx b/frontend/app/src/tx-flows/openLeveragePosition.tsx index d85b6cc56..630b1e320 100644 --- a/frontend/app/src/tx-flows/openLeveragePosition.tsx +++ b/frontend/app/src/tx-flows/openLeveragePosition.tsx @@ -134,9 +134,9 @@ export const openLeveragePosition: FlowDeclaration value={[
- {fmtnum(ETH_GAS_COMPENSATION, 4)} ETH + {fmtnum(ETH_GAS_COMPENSATION, 4)} ANKR
, "Only used in case of liquidation", ]} @@ -219,7 +219,7 @@ export const openLeveragePosition: FlowDeclaration }; // ETH collateral case - if (branch.symbol === "ETH") { + if (branch.symbol === "ANKR") { return ctx.writeContract({ ...LeverageWETHZapper, functionName: "openLeveragedTroveWithRawETH", @@ -278,7 +278,7 @@ export const openLeveragePosition: FlowDeclaration } // ETH doesn't need approval - if (collToken.symbol === "ETH") { + if (collToken.symbol === "ANKR") { return ["openLeveragedTrove"]; } diff --git a/frontend/app/src/tx-flows/redeemCollateral.tsx b/frontend/app/src/tx-flows/redeemCollateral.tsx index 2381d2a0e..096ae8af6 100644 --- a/frontend/app/src/tx-flows/redeemCollateral.tsx +++ b/frontend/app/src/tx-flows/redeemCollateral.tsx @@ -64,7 +64,7 @@ export const redeemCollateral: FlowDeclaration = { /> {branches.map(({ symbol }) => { const collChange = collChanges?.find((change) => symbol === change.symbol)?.change; - const symbol_ = symbol === "ETH" ? "WETH" : symbol; + const symbol_ = symbol === "ANKR" ? "WANKR" : symbol; return ( ; - -export const stakeClaimRewards: FlowDeclaration = { - title: "Review & Send Transaction", - - Summary({ request }) { - return ( - - ); - }, - - Details({ request }) { - const { rewards } = request.stakePosition; - const lusdPrice = usePrice("LUSD"); - const ethPrice = usePrice("ETH"); - - const rewardsLusdInUsd = lusdPrice.data && dn.mul(rewards.lusd, lusdPrice.data); - const rewardsEthInUsd = ethPrice.data && dn.mul(rewards.eth, ethPrice.data); - - return ( - <> - , - , - ]} - /> - , - , - ]} - /> - - ); - }, - - steps: { - stakeClaimRewards: { - name: () => "Claim rewards", - Status: TransactionStatus, - - async commit(ctx) { - return ctx.writeContract({ - ...ctx.contracts.Governance, - functionName: "claimFromStakingV1", - args: [ctx.request.stakePosition.owner], // address to receive the payout - }); - }, - - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - }, - - async getSteps() { - return ["stakeClaimRewards"]; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; diff --git a/frontend/app/src/tx-flows/stakeDeposit.tsx b/frontend/app/src/tx-flows/stakeDeposit.tsx deleted file mode 100644 index e52a2c14e..000000000 --- a/frontend/app/src/tx-flows/stakeDeposit.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { StakePositionSummary } from "@/src/comps/StakePositionSummary/StakePositionSummary"; -import { dnum18 } from "@/src/dnum-utils"; -import { signPermit } from "@/src/permit"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { usePrice } from "@/src/services/Prices"; -import { vDnum, vPositionStake } from "@/src/valibot-utils"; -import { useAccount } from "@/src/wagmi-utils"; -import * as dn from "dnum"; -import * as v from "valibot"; -import { encodeFunctionData, maxUint256 } from "viem"; -import { getBytecode } from "wagmi/actions"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "stakeDeposit", - { - lqtyAmount: vDnum(), - stakePosition: vPositionStake(), - prevStakePosition: v.union([v.null(), vPositionStake()]), - }, -); - -export type StakeDepositRequest = v.InferOutput; - -export const stakeDeposit: FlowDeclaration = { - title: "Review & Send Transaction", - - Summary({ request }) { - return ( - - ); - }, - - Details({ request }) { - const lqtyPrice = usePrice("LQTY"); - return ( - , - , - ]} - /> - ); - }, - - steps: { - deployUserProxy: { - name: () => "Initialize Staking", - Status: TransactionStatus, - async commit(ctx) { - return ctx.writeContract({ - ...ctx.contracts.Governance, - functionName: "deployUserProxy", - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - - approve: { - name: () => "Approve LQTY", - Status: (props) => { - const account = useAccount(); - return ( - - ); - }, - async commit(ctx) { - const userProxyAddress = await ctx.readContract({ - ...ctx.contracts.Governance, - functionName: "deriveUserProxyAddress", - args: [ctx.account], - }); - - // permit - if (ctx.preferredApproveMethod === "permit" && !ctx.isSafe) { - const { deadline, ...permit } = await signPermit({ - token: ctx.contracts.LqtyToken.address, - spender: userProxyAddress, - value: ctx.request.lqtyAmount[0], - account: ctx.account, - wagmiConfig: ctx.wagmiConfig, - }); - - return "permit:" + JSON.stringify({ - ...permit, - deadline: Number(deadline), - userProxyAddress, - }); - } - - // approve() - return ctx.writeContract({ - ...ctx.contracts.LqtyToken, - functionName: "approve", - args: [ - userProxyAddress, - ctx.preferredApproveMethod === "approve-infinite" - ? maxUint256 // infinite approval - : ctx.request.lqtyAmount[0], // exact amount - ], - }); - }, - async verify(ctx, hash) { - if (!hash.startsWith("permit:")) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - } - }, - }, - - // reset allocations + deposit LQTY in a single transaction - deposit: { - name: () => "Stake", - Status: TransactionStatus, - async commit(ctx) { - const { Governance } = ctx.contracts; - const inputs: `0x${string}`[] = []; - - const approveStep = ctx.steps?.find((step) => step.id === "approve"); - const isPermit = approveStep?.artifact?.startsWith("permit:") === true; - - // deposit LQTY via permit - if (isPermit) { - const { userProxyAddress, ...permit } = JSON.parse( - approveStep?.artifact?.replace(/^permit:/, "") ?? "{}", - ); - inputs.push(encodeFunctionData({ - abi: Governance.abi, - functionName: "depositLQTYViaPermit", - args: [ctx.request.lqtyAmount[0], { - owner: ctx.account, - spender: userProxyAddress, - value: ctx.request.lqtyAmount[0], - deadline: permit.deadline, - v: permit.v, - r: permit.r, - s: permit.s, - }], - })); - } else { - const userProxyAddress = await ctx.readContract({ - ...Governance, - functionName: "deriveUserProxyAddress", - args: [ctx.account], - }); - - const lqtyAllowance = await ctx.readContract({ - ...ctx.contracts.LqtyToken, - functionName: "allowance", - args: [ctx.account, userProxyAddress], - }); - - if (dn.gt(ctx.request.lqtyAmount, dnum18(lqtyAllowance))) { - throw new Error("LQTY allowance is not enough"); - } - - // deposit approved LQTY - inputs.push(encodeFunctionData({ - abi: Governance.abi, - functionName: "depositLQTY", - args: [ctx.request.lqtyAmount[0]], - })); - } - - return ctx.writeContract({ - ...Governance, - functionName: "multiDelegateCall", - args: [inputs], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - }, - - async getSteps(ctx) { - const steps: string[] = []; - - // get the user proxy address - const userProxyAddress = await ctx.readContract({ - ...ctx.contracts.Governance, - functionName: "deriveUserProxyAddress", - args: [ctx.account], - }); - - // check if the user proxy contract exists - const userProxyBytecode = await getBytecode(ctx.wagmiConfig, { - address: userProxyAddress, - }); - - // deploy the user proxy (optional, but prevents wallets - // to show a warning for approving a non-deployed contract) - if (!userProxyBytecode) { - steps.push("deployUserProxy"); - } - - // check for allowance - const lqtyAllowance = await ctx.readContract({ - ...ctx.contracts.LqtyToken, - functionName: "allowance", - args: [ctx.account, userProxyAddress], - }); - - // approve needed - if (dn.gt(ctx.request.lqtyAmount, dnum18(lqtyAllowance))) { - steps.push("approve"); - } - - // stake - steps.push("deposit"); - - return steps; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; diff --git a/frontend/app/src/tx-flows/unstakeDeposit.tsx b/frontend/app/src/tx-flows/unstakeDeposit.tsx deleted file mode 100644 index c3134dac7..000000000 --- a/frontend/app/src/tx-flows/unstakeDeposit.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { StakePositionSummary } from "@/src/comps/StakePositionSummary/StakePositionSummary"; -import { getUserAllocatedInitiatives } from "@/src/liquity-governance"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { usePrice } from "@/src/services/Prices"; -import { vDnum, vPositionStake } from "@/src/valibot-utils"; -import * as dn from "dnum"; -import * as v from "valibot"; -import { encodeFunctionData } from "viem"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "unstakeDeposit", - { - lqtyAmount: vDnum(), - stakePosition: vPositionStake(), - prevStakePosition: v.union([v.null(), vPositionStake()]), - }, -); - -export type UnstakeDepositRequest = v.InferOutput; - -export const unstakeDeposit: FlowDeclaration = { - title: "Review & Send Transaction", - - Summary({ request }) { - return ( - - ); - }, - - Details({ request }) { - const lqtyPrice = usePrice("LQTY"); - return ( - , - , - ]} - /> - ); - }, - - steps: { - resetVotesAndWithdraw: { - name: () => "Unstake", - Status: TransactionStatus, - async commit(ctx) { - const { Governance } = ctx.contracts; - const inputs: `0x${string}`[] = []; - - const allocatedInitiatives = await getUserAllocatedInitiatives( - ctx.wagmiConfig, - ctx.account, - ); - - // reset allocations if the user has any - if (allocatedInitiatives.length > 0) { - inputs.push(encodeFunctionData({ - abi: Governance.abi, - functionName: "resetAllocations", - args: [allocatedInitiatives, true], - })); - } - - // withdraw LQTY - inputs.push(encodeFunctionData({ - abi: Governance.abi, - functionName: "withdrawLQTY", - args: [ctx.request.lqtyAmount[0]], - })); - - return ctx.writeContract({ - ...Governance, - functionName: "multiDelegateCall", - args: [inputs], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - }, - - async getSteps() { - return ["resetVotesAndWithdraw"]; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; diff --git a/frontend/app/src/tx-flows/updateBorrowPosition.tsx b/frontend/app/src/tx-flows/updateBorrowPosition.tsx index 828eb6c9b..9fe14244c 100644 --- a/frontend/app/src/tx-flows/updateBorrowPosition.tsx +++ b/frontend/app/src/tx-flows/updateBorrowPosition.tsx @@ -141,7 +141,7 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(ctx.request.loan.branchId); - const Controller = branch.symbol === "ETH" + const Controller = branch.symbol === "ANKR" ? branch.contracts.LeverageWETHZapper : branch.contracts.LeverageLSTZapper; @@ -207,7 +207,7 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(loan.branchId); - if (branch.symbol === "ETH") { + if (branch.symbol === "ANKR") { return ctx.writeContract({ ...branch.contracts.LeverageWETHZapper, functionName: "adjustTroveWithRawETH", @@ -273,7 +273,7 @@ export const updateBorrowPosition: FlowDeclaration interestRate: loan.interestRate[0], }); - if (branch.symbol === "ETH") { + if (branch.symbol === "ANKR") { return ctx.writeContract({ ...branch.contracts.LeverageWETHZapper, functionName: "adjustZombieTroveWithRawETH", @@ -322,7 +322,7 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(loan.branchId); - if (branch.symbol === "ETH") { + if (branch.symbol === "ANKR") { return ctx.writeContract({ ...branch.contracts.LeverageWETHZapper, functionName: "repayBold", @@ -352,7 +352,7 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(loan.branchId); - if (branch.symbol === "ETH") { + if (branch.symbol === "ANKR") { return ctx.writeContract({ ...branch.contracts.LeverageWETHZapper, functionName: "addCollWithRawETH", @@ -382,7 +382,7 @@ export const updateBorrowPosition: FlowDeclaration const debtChange = getDebtChange(loan, ctx.request.prevLoan); const branch = getBranch(loan.branchId); - if (branch.symbol === "ETH") { + if (branch.symbol === "ANKR") { return ctx.writeContract({ ...branch.contracts.LeverageWETHZapper, functionName: "withdrawBold", @@ -411,7 +411,7 @@ export const updateBorrowPosition: FlowDeclaration const collChange = getCollChange(loan, ctx.request.prevLoan); const branch = getBranch(loan.branchId); - if (branch.symbol === "ETH") { + if (branch.symbol === "ANKR") { return ctx.writeContract({ ...branch.contracts.LeverageWETHZapper, functionName: "withdrawCollToRawETH", @@ -438,7 +438,7 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(ctx.request.loan.branchId); - const Controller = branch.symbol === "ETH" + const Controller = branch.symbol === "ANKR" ? branch.contracts.LeverageWETHZapper : branch.contracts.LeverageLSTZapper; @@ -455,7 +455,7 @@ export const updateBorrowPosition: FlowDeclaration ); // Collateral token needs to be approved if collChange > 0 and collToken != "ETH" (no LeverageWETHZapper) - const isCollApproved = branch.symbol === "ETH" || !dn.gt(collChange, 0) || !dn.gt(collChange, [ + const isCollApproved = branch.symbol === "ANKR" || !dn.gt(collChange, 0) || !dn.gt(collChange, [ (await ctx.readContract({ ...branch.contracts.CollToken, functionName: "allowance", diff --git a/frontend/app/src/tx-flows/updateLeveragePosition.tsx b/frontend/app/src/tx-flows/updateLeveragePosition.tsx index b4c6fae14..597938fd3 100644 --- a/frontend/app/src/tx-flows/updateLeveragePosition.tsx +++ b/frontend/app/src/tx-flows/updateLeveragePosition.tsx @@ -221,7 +221,7 @@ export const updateLeveragePosition: FlowDeclaration JSON.parse(value)), - v.object({ - BOLD_TOKEN: vAddress(), - COLLATERAL_REGISTRY: vAddress(), - GOVERNANCE: vAddress(), - INITIATIVES_SNAPSHOT_URL: v.string(), - TROVES_SNAPSHOT_URL: v.string(), - BRANCHES: v.array( - v.object({ - symbol: vCollateralSymbol(), - name: v.string(), - COLL_TOKEN: vAddress(), - LEVERAGE_ZAPPER: vAddress(), - STABILITY_POOL: vAddress(), - TROVE_MANAGER: vAddress(), - }), - ), - }), - ), - ]); -} - export function vIcStrategy() { return v.union([ vEnvFlag(), diff --git a/frontend/app/src/wagmi-utils.ts b/frontend/app/src/wagmi-utils.ts index c7715861f..bbf7df694 100644 --- a/frontend/app/src/wagmi-utils.ts +++ b/frontend/app/src/wagmi-utils.ts @@ -2,7 +2,7 @@ import type { Dnum, Token } from "@/src/types"; import type { Address } from "@liquity2/uikit"; import { dnum18 } from "@/src/dnum-utils"; -import { CONTRACT_BOLD_TOKEN, CONTRACT_LQTY_TOKEN, CONTRACT_LUSD_TOKEN } from "@/src/env"; +import { CONTRACT_BOLD_TOKEN } from "@/src/env"; import { getBranch } from "@/src/liquity-utils"; import { getSafeStatus } from "@/src/safe-utils"; import { isCollateralSymbol } from "@liquity2/uikit"; @@ -30,23 +30,21 @@ export function useBalances( const tokenConfigs = tokens.map((token) => { const tokenAddress = match(token) .when( - (symbol) => Boolean(symbol && isCollateralSymbol(symbol) && symbol !== "ETH"), + (symbol) => Boolean(symbol && isCollateralSymbol(symbol) && symbol !== "ANKR"), (symbol) => { - if (!symbol || !isCollateralSymbol(symbol) || symbol === "ETH") { + if (!symbol || !isCollateralSymbol(symbol) || symbol === "ANKR") { return null; } return getBranch(symbol).contracts.CollToken.address; }, ) - .with("LUSD", () => CONTRACT_LUSD_TOKEN) .with("BOLD", () => CONTRACT_BOLD_TOKEN) - .with("LQTY", () => CONTRACT_LQTY_TOKEN) .otherwise(() => null); return { token, tokenAddress, - isEth: token === "ETH", + isEth: token === "ANKR", }; }); @@ -74,7 +72,7 @@ export function useBalances( // combine results return tokens.reduce((result, token) => { - if (token === "ETH") { + if (token === "ANKR") { result[token] = { data: ethBalance.data ? dnum18(ethBalance.data.value) : undefined, isLoading: ethBalance.isLoading, diff --git a/frontend/uikit/src/tokens.ts b/frontend/uikit/src/tokens.ts index 6b7ae76d3..5b48085e4 100644 --- a/frontend/uikit/src/tokens.ts +++ b/frontend/uikit/src/tokens.ts @@ -1,10 +1,6 @@ import tokenBold from "./token-icons/bold.svg"; import tokenEth from "./token-icons/eth.svg"; -import tokenLqty from "./token-icons/lqty.svg"; -import tokenLusd from "./token-icons/lusd.svg"; -import tokenReth from "./token-icons/reth.svg"; import tokenSbold from "./token-icons/sbold.svg"; -import tokenSteth from "./token-icons/wsteth.svg"; // any external token, without a known symbol export type ExternalToken = { @@ -22,38 +18,29 @@ export type Token = ExternalToken & { export type TokenSymbol = | "BOLD" - | "ETH" - | "LQTY" - | "LUSD" - | "RETH" | "SBOLD" - | "WSTETH"; + | "ANKR" + | "USN"; export type CollateralSymbol = & TokenSymbol & ( - | "ETH" - | "RETH" - | "WSTETH" + | "ANKR" + | "USN" ); export function isTokenSymbol(symbolOrUrl: string): symbolOrUrl is TokenSymbol { return ( symbolOrUrl === "BOLD" - || symbolOrUrl === "ETH" - || symbolOrUrl === "LQTY" - || symbolOrUrl === "LUSD" - || symbolOrUrl === "RETH" - || symbolOrUrl === "SBOLD" - || symbolOrUrl === "WSTETH" + || symbolOrUrl === "ANKR" + || symbolOrUrl === "USN" ); } export function isCollateralSymbol(symbol: string): symbol is CollateralSymbol { return ( - symbol === "ETH" - || symbol === "RETH" - || symbol === "WSTETH" + symbol === "ANKR" + || symbol === "USN" ); } @@ -62,63 +49,40 @@ export type CollateralToken = Token & { symbol: CollateralSymbol; }; -export const LUSD: Token = { - icon: tokenLusd, - name: "LUSD", - symbol: "LUSD" as const, -} as const; - export const BOLD: Token = { icon: tokenBold, name: "BOLD", symbol: "BOLD" as const, } as const; -export const LQTY: Token = { - icon: tokenLqty, - name: "LQTY", - symbol: "LQTY" as const, -} as const; - export const SBOLD: Token = { icon: tokenSbold, name: "sBOLD", symbol: "SBOLD" as const, } as const; -export const ETH: CollateralToken = { +export const ANKR: CollateralToken = { collateralRatio: 1.1, icon: tokenEth, - name: "ETH", - symbol: "ETH" as const, + name: "ANKR", + symbol: "ANKR" as const, } as const; -export const RETH: CollateralToken = { +export const USN: CollateralToken = { collateralRatio: 1.2, - icon: tokenReth, - name: "rETH", - symbol: "RETH" as const, -} as const; - -export const WSTETH: CollateralToken = { - collateralRatio: 1.2, - icon: tokenSteth, - name: "wstETH", - symbol: "WSTETH" as const, + icon: tokenEth, + name: "USN", + symbol: "USN" as const, } as const; export const COLLATERALS: CollateralToken[] = [ - ETH, - RETH, - WSTETH, + ANKR, + USN, ]; export const TOKENS_BY_SYMBOL = { BOLD, - ETH, - LQTY, - LUSD, - RETH, + ANKR, + USN, SBOLD, - WSTETH, } as const; diff --git a/subgraph/networks.json b/subgraph/networks.json index b24d2abfd..04ec42d61 100644 --- a/subgraph/networks.json +++ b/subgraph/networks.json @@ -2,29 +2,24 @@ "local": { "BoldToken": { "address": "0x0e18b884ec3095f7c27bbbeb0a266a5674bcaffd" - }, - "Governance": { - "address": "0x79fdb65c20e75f8961d54f6714d2fcc4c30d1073" } }, "mainnet": { "BoldToken": { "address": "0x6440f144b7e50d6a8439336510312d2f54beb01d", "startBlock": 22483043 - }, - "Governance": { - "address": "0x807def5e7d057df05c796f4bc75c3fe82bd6eee1", - "startBlock": 22496547 } }, "sepolia": { "BoldToken": { "address": "0x181dff47198bf3f3ed65877332e8395eb6817c4c", "startBlock": 8316063 - }, - "Governance": { - "address": "0xf68db4e851d89daf0fb842b01f75d94e0cb44f49", - "startBlock": 8316191 + } + }, + "neura-testnet": { + "BoldToken": { + "address": "0xc13441f42e0aca71ea246aba87bdcdf646a456dd", + "startBlock": 100000 } } } diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index ce8e82df3..1ab1fa1a5 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -74,34 +74,3 @@ type InterestBatch @entity(immutable: false) { updatedAt: BigInt! troves: [Trove!]! @derivedFrom(field: "interestBatch") } - -type GovernanceVotingPower @entity(immutable: false) { - id: ID! # userAddress, e.g. "0x0000000000000000000000000000000000000000", or "total" - allocatedLQTY: BigInt! - allocatedOffset: BigInt! - unallocatedLQTY: BigInt! - unallocatedOffset: BigInt! -} - -type GovernanceAllocationIndex @entity(immutable: false) { - id: ID! # "userAddress:initiativeAddress" or "initiativeAddress" - user: String - initiative: GovernanceInitiative! - latestAllocation: GovernanceAllocation! -} - -type GovernanceAllocation @entity(immutable: false) { - id: ID! # "userAddress:initiativeAddress:epoch" or "initiativeAddress:epoch" - user: String - initiative: GovernanceInitiative! - epoch: BigInt! - voteLQTY: BigInt! - vetoLQTY: BigInt! - voteOffset: BigInt! - vetoOffset: BigInt! -} - -type GovernanceInitiative @entity(immutable: false) { - id: ID! # "initiativeAddress", e.g. "0x0000000000000000000000000000000000000000" - registered: Boolean! -} diff --git a/subgraph/src/Governance.mapping.ts b/subgraph/src/Governance.mapping.ts deleted file mode 100644 index b4ffb1df3..000000000 --- a/subgraph/src/Governance.mapping.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { BigInt } from "@graphprotocol/graph-ts"; -import { - AllocateLQTY as AllocateLQTYEvent, - DepositLQTY as DepositLQTYEvent, - RegisterInitiative as RegisterInitiativeEvent, - UnregisterInitiative as UnregisterInitiativeEvent, - WithdrawLQTY as WithdrawLQTYEvent, -} from "../generated/Governance/Governance"; -import { - GovernanceAllocation, - GovernanceAllocationIndex, - GovernanceInitiative, - GovernanceVotingPower, -} from "../generated/schema"; - -export function handleRegisterInitiative(event: RegisterInitiativeEvent): void { - let initiative = new GovernanceInitiative(event.params.initiative.toHex()); - initiative.registered = true; - initiative.save(); -} - -export function handleUnregisterInitiative(event: UnregisterInitiativeEvent): void { - let initiative = GovernanceInitiative.load(event.params.initiative.toHex()); - if (initiative === null) { - throw new Error("UnregisterInitiative event for non-existing initiative"); - } - initiative.registered = false; - initiative.save(); -} - -function getVotingPower(id: string): GovernanceVotingPower { - let votingPower = GovernanceVotingPower.load(id); - - if (votingPower === null) { - votingPower = new GovernanceVotingPower(id); - votingPower.allocatedLQTY = BigInt.zero(); - votingPower.allocatedOffset = BigInt.zero(); - votingPower.unallocatedLQTY = BigInt.zero(); - votingPower.unallocatedOffset = BigInt.zero(); - } - - return votingPower; -} - -export function handleDepositLQTY(event: DepositLQTYEvent): void { - let userVotingPower = getVotingPower(event.params.user.toHex()); - let toteVotingPower = getVotingPower("total"); - - let offsetIncrease = event.params.lqtyAmount.times(event.block.timestamp); - userVotingPower.unallocatedLQTY = userVotingPower.unallocatedLQTY.plus(event.params.lqtyAmount); - toteVotingPower.unallocatedLQTY = toteVotingPower.unallocatedLQTY.plus(event.params.lqtyAmount); - userVotingPower.unallocatedOffset = userVotingPower.unallocatedOffset.plus(offsetIncrease); - toteVotingPower.unallocatedOffset = toteVotingPower.unallocatedOffset.plus(offsetIncrease); - - userVotingPower.save(); - toteVotingPower.save(); -} - -export function handleWithdrawLQTY(event: WithdrawLQTYEvent): void { - let userVotingPower = getVotingPower(event.params.user.toHex()); - let toteVotingPower = getVotingPower("total"); - - let offsetDecrease = userVotingPower.unallocatedLQTY.notEqual(BigInt.zero()) - ? userVotingPower.unallocatedOffset - .times(event.params.lqtyReceived) - .div(userVotingPower.unallocatedLQTY) - : BigInt.zero(); - - userVotingPower.unallocatedLQTY = userVotingPower.unallocatedLQTY.minus(event.params.lqtyReceived); - toteVotingPower.unallocatedLQTY = toteVotingPower.unallocatedLQTY.minus(event.params.lqtyReceived); - userVotingPower.unallocatedOffset = userVotingPower.unallocatedOffset.minus(offsetDecrease); - toteVotingPower.unallocatedOffset = toteVotingPower.unallocatedOffset.minus(offsetDecrease); - - userVotingPower.save(); - toteVotingPower.save(); -} - -class GovernanceAllocationAndIndex { - allocation: GovernanceAllocation; - allocationIndex: GovernanceAllocationIndex | null; - - constructor(allocation: GovernanceAllocation, allocationIndex: GovernanceAllocationIndex | null) { - this.allocation = allocation; - this.allocationIndex = allocationIndex; - } - - save(): void { - this.allocation.save(); - - // Workaround for AssemblyScript compiler being stupid - let allocationIndex = this.allocationIndex; - if (allocationIndex !== null) allocationIndex.save(); - } -} - -function getAllocation( - userId: string | null, - initiativeId: string, - epoch: BigInt, -): GovernanceAllocationAndIndex { - let allocationIndexId = userId !== null ? userId + ":" + initiativeId : initiativeId; - let allocationIndex: GovernanceAllocationIndex | null = null; - let allocationId = allocationIndexId + ":" + epoch.toString(); - let allocation = GovernanceAllocation.load(allocationId); - - if (allocation === null) { - allocation = new GovernanceAllocation(allocationId); - allocation.user = userId; - allocation.initiative = initiativeId; - allocation.epoch = epoch; - - allocationIndex = GovernanceAllocationIndex.load(allocationIndexId); - if (allocationIndex === null) { - allocationIndex = new GovernanceAllocationIndex(allocationIndexId); - allocationIndex.user = userId; - allocationIndex.initiative = initiativeId; - - allocation.voteLQTY = BigInt.zero(); - allocation.vetoLQTY = BigInt.zero(); - allocation.voteOffset = BigInt.zero(); - allocation.vetoOffset = BigInt.zero(); - } else { - let prevAllocation = GovernanceAllocation.load(allocationIndex.latestAllocation); - if (prevAllocation === null) { - throw new Error("GovernanceAllocation not found: " + allocationIndex.latestAllocation); - } - - allocation.voteLQTY = prevAllocation.voteLQTY; - allocation.vetoLQTY = prevAllocation.vetoLQTY; - allocation.voteOffset = prevAllocation.voteOffset; - allocation.vetoOffset = prevAllocation.vetoOffset; - } - - allocationIndex.latestAllocation = allocationId; - } - - return new GovernanceAllocationAndIndex(allocation, allocationIndex); -} - -export function handleAllocateLQTY(event: AllocateLQTYEvent): void { - let userId = event.params.user.toHex(); - let initiativeId = event.params.initiative.toHex(); - - let userVotingPower = getVotingPower(userId); - let toteVotingPower = getVotingPower("total"); - - let user = getAllocation(userId, initiativeId, event.params.atEpoch); - let tote = getAllocation(null, initiativeId, event.params.atEpoch); - - let deltaVoteOffset = event.params.deltaVoteLQTY.gt(BigInt.zero()) - ? (userVotingPower.unallocatedLQTY.notEqual(BigInt.zero()) - ? userVotingPower.unallocatedOffset.times(event.params.deltaVoteLQTY).div(userVotingPower.unallocatedLQTY) - : BigInt.zero()) - : (user.allocation.voteLQTY.notEqual(BigInt.zero()) - ? user.allocation.voteOffset.times(event.params.deltaVoteLQTY).div(user.allocation.voteLQTY) - : BigInt.zero()); - - let deltaVetoOffset = event.params.deltaVetoLQTY.gt(BigInt.zero()) - ? (userVotingPower.unallocatedLQTY.notEqual(BigInt.zero()) - ? userVotingPower.unallocatedOffset.times(event.params.deltaVetoLQTY).div(userVotingPower.unallocatedLQTY) - : BigInt.zero()) - : (user.allocation.vetoLQTY.notEqual(BigInt.zero()) - ? user.allocation.vetoOffset.times(event.params.deltaVetoLQTY).div(user.allocation.vetoLQTY) - : BigInt.zero()); - - user.allocation.voteLQTY = user.allocation.voteLQTY.plus(event.params.deltaVoteLQTY); - tote.allocation.voteLQTY = tote.allocation.voteLQTY.plus(event.params.deltaVoteLQTY); - user.allocation.vetoLQTY = user.allocation.vetoLQTY.plus(event.params.deltaVetoLQTY); - tote.allocation.vetoLQTY = tote.allocation.vetoLQTY.plus(event.params.deltaVetoLQTY); - - userVotingPower.allocatedLQTY = userVotingPower.allocatedLQTY.plus(event.params.deltaVoteLQTY); - toteVotingPower.allocatedLQTY = toteVotingPower.allocatedLQTY.plus(event.params.deltaVoteLQTY); - userVotingPower.allocatedLQTY = userVotingPower.allocatedLQTY.plus(event.params.deltaVetoLQTY); - toteVotingPower.allocatedLQTY = toteVotingPower.allocatedLQTY.plus(event.params.deltaVetoLQTY); - - userVotingPower.unallocatedLQTY = userVotingPower.unallocatedLQTY.minus(event.params.deltaVoteLQTY); - toteVotingPower.unallocatedLQTY = toteVotingPower.unallocatedLQTY.minus(event.params.deltaVoteLQTY); - userVotingPower.unallocatedLQTY = userVotingPower.unallocatedLQTY.minus(event.params.deltaVetoLQTY); - toteVotingPower.unallocatedLQTY = toteVotingPower.unallocatedLQTY.minus(event.params.deltaVetoLQTY); - - user.allocation.voteOffset = user.allocation.voteOffset.plus(deltaVoteOffset); - tote.allocation.voteOffset = tote.allocation.voteOffset.plus(deltaVoteOffset); - user.allocation.vetoOffset = user.allocation.vetoOffset.plus(deltaVetoOffset); - tote.allocation.vetoOffset = tote.allocation.vetoOffset.plus(deltaVetoOffset); - - userVotingPower.allocatedOffset = userVotingPower.allocatedOffset.plus(deltaVoteOffset); - toteVotingPower.allocatedOffset = toteVotingPower.allocatedOffset.plus(deltaVoteOffset); - userVotingPower.allocatedOffset = userVotingPower.allocatedOffset.plus(deltaVetoOffset); - toteVotingPower.allocatedOffset = toteVotingPower.allocatedOffset.plus(deltaVetoOffset); - - userVotingPower.unallocatedOffset = userVotingPower.unallocatedOffset.minus(deltaVoteOffset); - toteVotingPower.unallocatedOffset = toteVotingPower.unallocatedOffset.minus(deltaVoteOffset); - userVotingPower.unallocatedOffset = userVotingPower.unallocatedOffset.minus(deltaVetoOffset); - toteVotingPower.unallocatedOffset = toteVotingPower.unallocatedOffset.minus(deltaVetoOffset); - - userVotingPower.save(); - toteVotingPower.save(); - - user.save(); - tote.save(); -} diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 18c7458e2..274274356 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -6,8 +6,8 @@ dataSources: name: BoldToken source: abi: BoldToken - address: "0x6440f144b7e50d6a8439336510312d2f54beb01d" - startBlock: 22483043 + address: "0xc13441f42e0aca71ea246aba87bdcdf646a456dd" + startBlock: 100000 mapping: kind: ethereum/events apiVersion: 0.0.9 @@ -32,41 +32,11 @@ dataSources: - event: CollateralRegistryAddressChanged(address) handler: handleCollateralRegistryAddressChanged file: ./src/BoldToken.mapping.ts - network: mainnet - - kind: ethereum/contract - name: Governance - source: - abi: Governance - address: "0x807def5e7d057df05c796f4bc75c3fe82bd6eee1" - startBlock: 22496547 - mapping: - kind: ethereum/events - apiVersion: 0.0.9 - language: wasm/assemblyscript - entities: - - GovernanceAllocation - - GovernanceInitiative - - GovernanceUser - abis: - - name: Governance - file: ../contracts/out/Governance.sol/Governance.json - eventHandlers: - - event: DepositLQTY(indexed - address,address,uint256,uint256,uint256,uint256,uint256) - handler: handleDepositLQTY - - event: WithdrawLQTY(indexed - address,address,uint256,uint256,uint256,uint256,uint256,uint256) - handler: handleWithdrawLQTY - - event: AllocateLQTY(indexed address,indexed address,int256,int256,uint256,uint8) - handler: handleAllocateLQTY - - event: RegisterInitiative(address,address,uint256,uint8) - handler: handleRegisterInitiative - file: ./src/Governance.mapping.ts - network: mainnet + network: neura-testnet templates: - name: TroveManager kind: ethereum/contract - network: mainnet + network: neura-testnet source: abi: TroveManager mapping: @@ -102,7 +72,7 @@ templates: handler: handleBatchUpdated - name: TroveNFT kind: ethereum/contract - network: mainnet + network: neura-testnet source: abi: TroveNFT mapping: @@ -118,4 +88,4 @@ templates: file: ../contracts/out/TroveNFT.sol/TroveNFT.json eventHandlers: - event: Transfer(indexed address,indexed address,indexed uint256) - handler: handleTransfer + handler: handleTransfer \ No newline at end of file From 7fea578e1a75df178a82b92c6dc6ccae43a8e87d Mon Sep 17 00:00:00 2001 From: Artur Korotenko Date: Thu, 4 Sep 2025 21:44:41 +0500 Subject: [PATCH 02/19] fix: build --- .../app/src/screens/HomeScreen/HomeScreen.tsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/frontend/app/src/screens/HomeScreen/HomeScreen.tsx b/frontend/app/src/screens/HomeScreen/HomeScreen.tsx index 9912f4b6c..747ac09c2 100644 --- a/frontend/app/src/screens/HomeScreen/HomeScreen.tsx +++ b/frontend/app/src/screens/HomeScreen/HomeScreen.tsx @@ -7,7 +7,6 @@ import { useBreakpoint } from "@/src/breakpoints"; import { Amount } from "@/src/comps/Amount/Amount"; import { LinkTextButton } from "@/src/comps/LinkTextButton/LinkTextButton"; import { Positions } from "@/src/comps/Positions/Positions"; -import { FORKS_INFO } from "@/src/constants"; import content from "@/src/content"; import { DNUM_1 } from "@/src/dnum-utils"; import { @@ -27,8 +26,6 @@ import * as dn from "dnum"; import { useState } from "react"; import { HomeTable } from "./HomeTable"; -type ForkInfo = (typeof FORKS_INFO)[number]; - export function HomeScreen() { const account = useAccount(); @@ -354,19 +351,3 @@ function EarnRewardsRow({ ); } - -function pickRandomForks(count: number): ForkInfo[] { - const forks = [...FORKS_INFO]; - if (forks.length < count) { - return forks; - } - const picked: ForkInfo[] = []; - for (let i = 0; i < count; i++) { - const [info] = forks.splice( - Math.floor(Math.random() * forks.length), - 1, - ); - if (info) picked.push(info); - } - return picked; -} From 6425944fe9431b33888b371fe23ba9d7da2605de Mon Sep 17 00:00:00 2001 From: Artur Korotenko Date: Thu, 4 Sep 2025 23:35:31 +0500 Subject: [PATCH 03/19] chore: update multicall address --- frontend/app/.env | 7 +- .../src/app/multiply/[collateral]/page.tsx | 11 - frontend/app/src/app/multiply/layout.tsx | 5 - frontend/app/src/app/multiply/page.tsx | 4 - frontend/app/src/constants.ts | 2 +- frontend/app/src/contracts.ts | 7 - frontend/app/src/env.ts | 3 - frontend/app/src/liquity-leverage.ts | 66 +-- .../screens/LeverageScreen/LeverageScreen.tsx | 493 ------------------ 9 files changed, 5 insertions(+), 593 deletions(-) delete mode 100644 frontend/app/src/app/multiply/[collateral]/page.tsx delete mode 100644 frontend/app/src/app/multiply/layout.tsx delete mode 100644 frontend/app/src/app/multiply/page.tsx delete mode 100644 frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx diff --git a/frontend/app/.env b/frontend/app/.env index 8c9bc84ed..906c47a2d 100644 --- a/frontend/app/.env +++ b/frontend/app/.env @@ -9,7 +9,7 @@ NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=fd37755b4d4f8a59a1a064af3782ded4 ###################### NEXT_PUBLIC_CHAIN_BLOCK_EXPLORER=Blockscout|https://testnet-blockscout.infra.neuraprotocol.io/ -NEXT_PUBLIC_CHAIN_CONTRACT_MULTICALL=0xca11bde05977b3631167028862be2a173976ca11 +NEXT_PUBLIC_CHAIN_CONTRACT_MULTICALL=0xfd5BcFE82490461E67BE874073D49d8473Dc3146 NEXT_PUBLIC_CHAIN_CURRENCY=Ankr|ANKR|18 NEXT_PUBLIC_CHAIN_ID=267 NEXT_PUBLIC_CHAIN_NAME=NeuraTestnet @@ -27,7 +27,7 @@ NEXT_PUBLIC_COLL_0_CONTRACT_COLL_SURPLUS_POOL=0xed8614f48a494a8fca53978f1183516a NEXT_PUBLIC_COLL_0_CONTRACT_COLL_TOKEN=0xBd833b6eCC30CAEaBf81dB18BB0f1e00C6997E7a NEXT_PUBLIC_COLL_0_CONTRACT_DEFAULT_POOL=0x2c6853ee6d9311f1c9aebad7f003999ac2f011c2 NEXT_PUBLIC_COLL_0_CONTRACT_PRICE_FEED=0x6f2c76f8105f723df8847a4ed97d3fc2fd335b7a -NEXT_PUBLIC_COLL_0_CONTRACT_LEVERAGE_ZAPPER=0xdccbd7a365aee086aa3b4ede8afe895b20770ae3 +NEXT_PUBLIC_COLL_0_CONTRACT_LEVERAGE_ZAPPER=0x0000000000000000000000000000000000000000 NEXT_PUBLIC_COLL_0_CONTRACT_SORTED_TROVES=0xc383b06357a330d1f326d8f3e0a92c8e5d918343 NEXT_PUBLIC_COLL_0_CONTRACT_STABILITY_POOL=0x0c8a5ebc5b8bac0dfdad159ca61fd0906bf6f53e NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_MANAGER=0x4d2044a26fde2876db02e19e4eac2e75ecb8d510 @@ -39,7 +39,7 @@ NEXT_PUBLIC_COLL_1_CONTRACT_COLL_SURPLUS_POOL=0xbc94877d8ecd73f329ecf38fc3c032d8 NEXT_PUBLIC_COLL_1_CONTRACT_COLL_TOKEN=0xe95455FEc4Be722122401B5dCAbb2428171AfF0c NEXT_PUBLIC_COLL_1_CONTRACT_DEFAULT_POOL=0xd7662a952fb043a444ffe52115cfcf133ed238d5 NEXT_PUBLIC_COLL_1_CONTRACT_PRICE_FEED=0x4abf5e742d9b44d4b0f0309cd3928b2e78ebd187 -NEXT_PUBLIC_COLL_1_CONTRACT_LEVERAGE_ZAPPER=0xe85230de04147c4ea363b21cdb801c1c19df0a56 +NEXT_PUBLIC_COLL_1_CONTRACT_LEVERAGE_ZAPPER=0x0000000000000000000000000000000000000000 NEXT_PUBLIC_COLL_1_CONTRACT_SORTED_TROVES=0xf6371a5c497170dbae162c830564fac462f206d6 NEXT_PUBLIC_COLL_1_CONTRACT_STABILITY_POOL=0x54ccb90ef683a7256fbd600db3d64d6d1015586c NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_MANAGER=0xb048a13fad98f35470ce962ddfce8c8450822d33 @@ -47,7 +47,6 @@ NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_NFT=0x23197d6c5199fdcddd441fc4924b5356313dc936 NEXT_PUBLIC_CONTRACT_BOLD_TOKEN=0xc13441f42e0aca71ea246aba87bdcdf646a456dd NEXT_PUBLIC_CONTRACT_COLLATERAL_REGISTRY=0x39e982e327bb27549c447cb91ae220ad8a566afa -NEXT_PUBLIC_CONTRACT_EXCHANGE_HELPERS=0x0000000000000000000000000000000000000000 NEXT_PUBLIC_CONTRACT_HINT_HELPERS=0x7375ff5c5e993d2dfbff6b1d099d16971fae16b8 NEXT_PUBLIC_CONTRACT_MULTI_TROVE_GETTER=0x580d8e94b61a2c3132b9b1f50d944ff4add3c3bb NEXT_PUBLIC_CONTRACT_WETH=0xBd833b6eCC30CAEaBf81dB18BB0f1e00C6997E7a diff --git a/frontend/app/src/app/multiply/[collateral]/page.tsx b/frontend/app/src/app/multiply/[collateral]/page.tsx deleted file mode 100644 index 7f5867971..000000000 --- a/frontend/app/src/app/multiply/[collateral]/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export function generateStaticParams() { - return [ - { collateral: "ankr" }, - { collateral: "usn" }, - ]; -} - -export default function LeverageCollateralPage() { - // see layout in parent folder - return null; -} diff --git a/frontend/app/src/app/multiply/layout.tsx b/frontend/app/src/app/multiply/layout.tsx deleted file mode 100644 index c47afcdf7..000000000 --- a/frontend/app/src/app/multiply/layout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { LeverageScreen } from "@/src/screens/LeverageScreen/LeverageScreen"; - -export default function Page() { - return ; -} diff --git a/frontend/app/src/app/multiply/page.tsx b/frontend/app/src/app/multiply/page.tsx deleted file mode 100644 index e6ab08307..000000000 --- a/frontend/app/src/app/multiply/page.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export default function LeveragePage() { - // see layout in this folder - return null; -} diff --git a/frontend/app/src/constants.ts b/frontend/app/src/constants.ts index 98b41c9db..1a6b84786 100644 --- a/frontend/app/src/constants.ts +++ b/frontend/app/src/constants.ts @@ -100,7 +100,7 @@ export const DEFAULT_STRATEGIES: Array<[ Array<[BranchId, IcStrategy[]]>, ]> = [ // mainnet - [1, [ + [267, [ // ANKR [0, [{ name: "Conservative Strategy", diff --git a/frontend/app/src/contracts.ts b/frontend/app/src/contracts.ts index f46989fe4..80b0a2744 100644 --- a/frontend/app/src/contracts.ts +++ b/frontend/app/src/contracts.ts @@ -6,7 +6,6 @@ import { BorrowerOperations } from "@/src/abi/BorrowerOperations"; import { CollateralRegistry } from "@/src/abi/CollateralRegistry"; import { CollSurplusPool } from "@/src/abi/CollSurplusPool"; import { DefaultPool } from "@/src/abi/DefaultPool"; -import { ExchangeHelpers } from "@/src/abi/ExchangeHelpers"; import { HintHelpers } from "@/src/abi/HintHelpers"; import { LeverageLSTZapper } from "@/src/abi/LeverageLSTZapper"; import { LeverageWETHZapper } from "@/src/abi/LeverageWETHZapper"; @@ -19,7 +18,6 @@ import { TroveNFT } from "@/src/abi/TroveNFT"; import { CONTRACT_BOLD_TOKEN, CONTRACT_COLLATERAL_REGISTRY, - CONTRACT_EXCHANGE_HELPERS, CONTRACT_HINT_HELPERS, CONTRACT_MULTI_TROVE_GETTER, CONTRACT_WETH, @@ -30,7 +28,6 @@ import { erc20Abi, zeroAddress } from "viem"; const protocolAbis = { BoldToken: erc20Abi, CollateralRegistry, - ExchangeHelpers, HintHelpers, MultiTroveGetter, WETH: erc20Abi, @@ -106,10 +103,6 @@ export const CONTRACTS: Contracts = { abi: abis.CollateralRegistry, address: CONTRACT_COLLATERAL_REGISTRY, }, - ExchangeHelpers: { - abi: abis.ExchangeHelpers, - address: CONTRACT_EXCHANGE_HELPERS, - }, HintHelpers: { abi: abis.HintHelpers, address: CONTRACT_HINT_HELPERS }, MultiTroveGetter: { abi: abis.MultiTroveGetter, diff --git a/frontend/app/src/env.ts b/frontend/app/src/env.ts index 1c2a40669..a43148819 100644 --- a/frontend/app/src/env.ts +++ b/frontend/app/src/env.ts @@ -193,7 +193,6 @@ export const EnvSchema = v.pipe( CONTRACT_BOLD_TOKEN: vAddress(), CONTRACT_COLLATERAL_REGISTRY: vAddress(), - CONTRACT_EXCHANGE_HELPERS: vAddress(), CONTRACT_HINT_HELPERS: vAddress(), CONTRACT_MULTI_TROVE_GETTER: vAddress(), CONTRACT_WETH: vAddress(), @@ -341,7 +340,6 @@ const parsedEnv = v.safeParse(EnvSchema, { CONTRACT_BOLD_TOKEN: process.env.NEXT_PUBLIC_CONTRACT_BOLD_TOKEN, CONTRACT_COLLATERAL_REGISTRY: process.env.NEXT_PUBLIC_CONTRACT_COLLATERAL_REGISTRY, - CONTRACT_EXCHANGE_HELPERS: process.env.NEXT_PUBLIC_CONTRACT_EXCHANGE_HELPERS, CONTRACT_HINT_HELPERS: process.env.NEXT_PUBLIC_CONTRACT_HINT_HELPERS, CONTRACT_MULTI_TROVE_GETTER: process.env.NEXT_PUBLIC_CONTRACT_MULTI_TROVE_GETTER, CONTRACT_WETH: process.env.NEXT_PUBLIC_CONTRACT_WETH, @@ -412,7 +410,6 @@ export const { CONTRACTS_COMMIT_URL, CONTRACT_BOLD_TOKEN, CONTRACT_COLLATERAL_REGISTRY, - CONTRACT_EXCHANGE_HELPERS, CONTRACT_HINT_HELPERS, CONTRACT_MULTI_TROVE_GETTER, CONTRACT_WETH, diff --git a/frontend/app/src/liquity-leverage.ts b/frontend/app/src/liquity-leverage.ts index a81191bea..1c4a062b1 100644 --- a/frontend/app/src/liquity-leverage.ts +++ b/frontend/app/src/liquity-leverage.ts @@ -1,14 +1,8 @@ -import type { BranchId, Dnum, TroveId } from "@/src/types"; +import type { BranchId, TroveId } from "@/src/types"; import type { Config as WagmiConfig } from "wagmi"; import { CLOSE_FROM_COLLATERAL_SLIPPAGE } from "@/src/constants"; -import { getProtocolContract } from "@/src/contracts"; -import { dnum18 } from "@/src/dnum-utils"; import { getBranch } from "@/src/liquity-utils"; -import { useDebouncedQueryKey } from "@/src/react-utils"; -import { useQuery } from "@tanstack/react-query"; -import * as dn from "dnum"; -import { useConfig as useWagmiConfig } from "wagmi"; import { readContract, readContracts } from "wagmi/actions"; const DECIMAL_PRECISION = 10n ** 18n; @@ -187,61 +181,3 @@ export async function getCloseFlashLoanAmount( function leverageRatioToCollateralRatio(inputRatio: bigint) { return inputRatio * DECIMAL_PRECISION / (inputRatio - DECIMAL_PRECISION); } - -export function useCheckLeverageSlippage({ - branchId, - initialDeposit, - leverageFactor, - ownerIndex, -}: { - branchId: BranchId; - initialDeposit: Dnum | null; - leverageFactor: number; - ownerIndex: number | null; -}) { - const wagmiConfig = useWagmiConfig(); - const WethContract = getProtocolContract("WETH"); - const ExchangeHelpersContract = getProtocolContract("ExchangeHelpers"); - - const debouncedQueryKey = useDebouncedQueryKey([ - "openLeveragedTroveParams", - branchId, - String(!initialDeposit || initialDeposit[0]), - leverageFactor, - ownerIndex, - ], 100); - - return useQuery({ - queryKey: debouncedQueryKey, - queryFn: async () => { - const params = initialDeposit && (await getOpenLeveragedTroveParams( - branchId, - initialDeposit[0], - leverageFactor, - wagmiConfig, - )); - - if (params === null) { - return null; - } - - const [_, slippage] = await readContract(wagmiConfig, { - abi: ExchangeHelpersContract.abi, - address: ExchangeHelpersContract.address, - functionName: "getCollFromBold", - args: [ - params.expectedBoldAmount, - WethContract.address, - params.flashLoanAmount, - ], - }); - - return dnum18(slippage); - }, - enabled: Boolean( - initialDeposit - && dn.gt(initialDeposit, 0) - && ownerIndex !== null, - ), - }); -} diff --git a/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx b/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx deleted file mode 100644 index 03f11929c..000000000 --- a/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx +++ /dev/null @@ -1,493 +0,0 @@ -"use client"; - -import type { DelegateMode } from "@/src/comps/InterestRateField/InterestRateField"; -import type { Address, Dnum, PositionLoanUncommitted } from "@/src/types"; -import type { ComponentPropsWithoutRef, ReactNode } from "react"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { Field } from "@/src/comps/Field/Field"; -import { InterestRateField } from "@/src/comps/InterestRateField/InterestRateField"; -import { LeverageField, useLeverageField } from "@/src/comps/LeverageField/LeverageField"; -import { RedemptionInfo } from "@/src/comps/RedemptionInfo/RedemptionInfo"; -import { Screen } from "@/src/comps/Screen/Screen"; -import { ETH_MAX_RESERVE, LEVERAGE_MAX_SLIPPAGE, MAX_COLLATERAL_DEPOSITS, MIN_DEBT } from "@/src/constants"; -import content from "@/src/content"; -import { dnum18, dnumMax } from "@/src/dnum-utils"; -import { useInputFieldValue } from "@/src/form-utils"; -import { fmtnum } from "@/src/formatting"; -import { useCheckLeverageSlippage } from "@/src/liquity-leverage"; -import { getRedemptionRisk } from "@/src/liquity-math"; -import { getBranch, getBranches, getCollToken, useDebtPositioning, useNextOwnerIndex } from "@/src/liquity-utils"; -import { usePrice } from "@/src/services/Prices"; -import { useTransactionFlow } from "@/src/services/TransactionFlow"; -import { infoTooltipProps } from "@/src/uikit-utils"; -import { useAccount, useBalance } from "@/src/wagmi-utils"; -import { css } from "@/styled-system/css"; -import { - ADDRESS_ZERO, - Button, - Dropdown, - HFlex, - IconSuggestion, - InfoTooltip, - InputField, - isCollateralSymbol, - TextButton, - TokenIcon, - VFlex, -} from "@liquity2/uikit"; -import * as dn from "dnum"; -import { useParams, useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; - -export function LeverageScreen() { - const branches = getBranches(); - - // useParams() can return an array but not with the current - // routing setup, so we can safely cast it to a string - const collSymbol = `${useParams().collateral ?? branches[0]?.symbol}`.toUpperCase(); - if (!isCollateralSymbol(collSymbol)) { - throw new Error(`Invalid collateral symbol: ${collSymbol}`); - } - - const router = useRouter(); - const account = useAccount(); - const txFlow = useTransactionFlow(); - - const branch = getBranch(collSymbol); - const collaterals = branches.map((b) => getCollToken(b.branchId)); - const collateral = getCollToken(branch.id); - - const balances = Object.fromEntries(collaterals.map(({ symbol }) => ( - [symbol, useBalance(account.address, symbol)] as const - ))); - - const nextOwnerIndex = useNextOwnerIndex(account.address ?? null, branch.id); - - const collPrice = usePrice(collateral.symbol); - - const maxCollDeposit = MAX_COLLATERAL_DEPOSITS[collSymbol] ?? null; - const depositPreLeverage = useInputFieldValue(fmtnum, { - validate: (parsed, value) => { - const isAboveMax = maxCollDeposit && parsed && dn.gt(parsed, maxCollDeposit); - return { - parsed: isAboveMax ? maxCollDeposit : parsed, - value: isAboveMax ? dn.toString(maxCollDeposit) : value, - }; - }, - }); - - const [interestRate, setInterestRate] = useState(null); - const [interestRateMode, setInterestRateMode] = useState("manual"); - const [interestRateDelegate, setInterestRateDelegate] = useState
(null); - - const leverageField = useLeverageField({ - depositPreLeverage: depositPreLeverage.parsed, - collPrice: collPrice.data ?? dn.from(0, 18), - collToken: collateral, - }); - - // reset leverage when collateral changes - useEffect(() => { - leverageField.updateLeverageFactor(leverageField.leverageFactorSuggestions[0] ?? 1.1); - }, [collateral.symbol, leverageField.leverageFactorSuggestions]); - - const debtPositioning = useDebtPositioning(branch.id, interestRate); - const redemptionRisk = getRedemptionRisk(debtPositioning.debtInFront, debtPositioning.totalDebt); - const depositUsd = depositPreLeverage.parsed && collPrice.data && dn.mul( - depositPreLeverage.parsed, - collPrice.data, - ); - - const collBalance = balances[collateral.symbol]?.data; - - const maxAmount = collBalance && dnumMax( - dn.sub(collBalance, collSymbol === "ANKR" ? ETH_MAX_RESERVE : 0), // Only keep a reserve for ETH, not LSTs - dnum18(0), - ); - - const newLoan: PositionLoanUncommitted = { - type: "multiply", - status: "active", - batchManager: interestRateDelegate, - borrowed: leverageField.debt ?? dn.from(0, 18), - borrower: account.address ?? ADDRESS_ZERO, - branchId: branch.id, - deposit: depositPreLeverage.parsed - ? dn.mul(depositPreLeverage.parsed, leverageField.leverageFactor) - : dn.from(0, 18), - interestRate: interestRate ?? dn.from(0, 18), - troveId: null, - }; - - const hasDeposit = Boolean(depositPreLeverage.parsed && dn.gt(depositPreLeverage.parsed, 0)); - - const leverageSlippage = useCheckLeverageSlippage({ - branchId: branch.id, - initialDeposit: depositPreLeverage.parsed, - leverageFactor: leverageField.leverageFactor, - ownerIndex: nextOwnerIndex.data ?? null, - }); - - const leverageSlippageElements = useSlippageElements( - leverageSlippage, - hasDeposit && account.isConnected, - ); - - const hasAllowedSlippage = leverageSlippage.data - && dn.lte(leverageSlippage.data, LEVERAGE_MAX_SLIPPAGE); - - const leverageFieldDrawer = (hasDeposit && newLoan.borrowed && dn.lt(newLoan.borrowed, MIN_DEBT)) - ? { mode: "error" as const, message: `You must borrow at least ${fmtnum(MIN_DEBT, 2)} BOLD.` } - : leverageSlippageElements.drawer; - - const allowSubmit = account.isConnected - && hasDeposit - && interestRate && dn.gt(interestRate, 0) - && leverageField.debt && dn.gt(leverageField.debt, 0) - && hasAllowedSlippage; - - return ( - - {content.leverageScreen.headline( - - {collaterals.map(({ symbol }) => ( - - ))} - , - )} - - ), - }} - > -
- ({ - icon: , - label: name, - value: account.isConnected - ? fmtnum(balances[symbol]?.data ?? 0) - : "−", - }))} - menuPlacement="end" - menuWidth={300} - onSelect={(index) => { - setTimeout(() => { - depositPreLeverage.setValue(""); - depositPreLeverage.focus(); - }, 0); - const collToken = collaterals[index]; - if (!collToken) { - throw new Error(`Unknown branch: ${index}`); - } - const { symbol } = collToken; - router.push( - `/multiply/${symbol.toLowerCase()}`, - { scroll: false }, - ); - }} - selected={branch.id} - /> - } - label={content.leverageScreen.depositField.label} - placeholder="0.00" - secondary={{ - start: fmtnum(depositUsd, { prefix: "$", preset: "2z" }), - end: maxAmount - ? ( - { - depositPreLeverage.setValue(dn.toString(maxAmount)); - }} - /> - ) - : "Fetching balance…", - }} - {...depositPreLeverage.inputFieldProps} - /> - } - footer={{ - start: collPrice.data && ( - - ), - end: ( - - ), - }} - /> - - - } - footer={{ - start: ( - <> - - - - ), - end: ( - -
- -
- - - } - /> - ), - }} - /> - - - } - footer={{ - start: ( - - ), - end: ( - - - <>You can adjust this rate at any time - - - ), - }} - /> - - - -
- {/**/} -
-
-
-
-
- ); -} - -function useSlippageElements( - leverageSlippage: ReturnType, - ready: boolean, -): { - mode: "error" | "loading" | "success"; - drawer: ComponentPropsWithoutRef["drawer"]; - message?: ReactNode; - onClose: () => void; -} { - const [forceDrawerClosed, setForceDrawerClosed] = useState(false); - - useEffect(() => { - setForceDrawerClosed(false); - }, [leverageSlippage.status]); - - const onClose = () => { - setForceDrawerClosed(true); - }; - - if (forceDrawerClosed || !ready) { - return { - drawer: null, - mode: "success", - onClose, - }; - } - - if (leverageSlippage.status === "error") { - const retry = ( - { - leverageSlippage.refetch(); - }} - /> - ); - return { - drawer: { - mode: "error", - message: ( - -
Slippage calculation failed.
- {retry} -
- ), - }, - message: ( - -
Slippage calculation failed. ({leverageSlippage.error.message})
- {retry} -
- ), - mode: "error", - onClose, - }; - } - - if (leverageSlippage.status === "pending" || leverageSlippage.fetchStatus === "fetching") { - const message = "Calculating slippage…"; - return { - drawer: null, - message, - mode: "loading", - onClose, - }; - } - - if (leverageSlippage.data && dn.gt(leverageSlippage.data, LEVERAGE_MAX_SLIPPAGE)) { - const message = ( - <> - Slippage too high: {fmtnum( - leverageSlippage.data, - "pct2", - )}% (max {fmtnum(LEVERAGE_MAX_SLIPPAGE, "pct2")}%) - - ); - return { - drawer: { mode: "error", message }, - message, - mode: "error", - onClose, - }; - } - - return { - drawer: null, - onClose, - mode: "success", - }; -} From 0b5b776a13a9bf36a8ebb60af8aae65c567618d3 Mon Sep 17 00:00:00 2001 From: Artur Korotenko Date: Fri, 5 Sep 2025 15:59:24 +0500 Subject: [PATCH 04/19] chore: ankr -> wankr --- frontend/app/.env | 9 +- .../app/src/app/borrow/[collateral]/page.tsx | 2 +- frontend/app/src/app/earn/[pool]/layout.tsx | 16 +- frontend/app/src/app/earn/[pool]/page.tsx | 3 +- .../app/src/comps/AppLayout/BottomBar.tsx | 2 +- .../app/src/comps/Positions/Positions.tsx | 2 +- frontend/app/src/constants.ts | 6 +- frontend/app/src/content.tsx | 2 +- frontend/app/src/contracts.ts | 24 +- frontend/app/src/env.ts | 6 +- frontend/app/src/liquity-utils.ts | 2 +- .../screens/AccountScreen/AccountScreen.tsx | 2 +- .../src/screens/BorrowScreen/BorrowScreen.tsx | 3 +- .../app/src/screens/LoanScreen/LoanScreen.tsx | 3 +- .../screens/LoanScreen/PanelClosePosition.tsx | 5 +- .../LoanScreen/PanelUpdateBorrowPosition.tsx | 2 +- .../PanelUpdateLeveragePosition.tsx | 499 ------------------ frontend/app/src/services/TransactionFlow.tsx | 7 - .../app/src/tx-flows/closeLoanPosition.tsx | 58 +- .../app/src/tx-flows/openBorrowPosition.tsx | 177 ++++--- .../app/src/tx-flows/openLeveragePosition.tsx | 311 ----------- .../app/src/tx-flows/redeemCollateral.tsx | 7 +- .../app/src/tx-flows/updateBorrowPosition.tsx | 91 +--- .../src/tx-flows/updateLeveragePosition.tsx | 419 --------------- frontend/app/src/valibot-utils.ts | 2 +- frontend/app/src/wagmi-utils.ts | 23 +- frontend/uikit/src/tokens.ts | 18 +- 27 files changed, 159 insertions(+), 1542 deletions(-) delete mode 100644 frontend/app/src/screens/LoanScreen/PanelUpdateLeveragePosition.tsx delete mode 100644 frontend/app/src/tx-flows/openLeveragePosition.tsx delete mode 100644 frontend/app/src/tx-flows/updateLeveragePosition.tsx diff --git a/frontend/app/.env b/frontend/app/.env index 906c47a2d..def80de10 100644 --- a/frontend/app/.env +++ b/frontend/app/.env @@ -4,10 +4,6 @@ NEXT_PUBLIC_VERCEL_ANALYTICS=false NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=fd37755b4d4f8a59a1a064af3782ded4 -###################### -# Ethereum (mainnet) # -###################### - NEXT_PUBLIC_CHAIN_BLOCK_EXPLORER=Blockscout|https://testnet-blockscout.infra.neuraprotocol.io/ NEXT_PUBLIC_CHAIN_CONTRACT_MULTICALL=0xfd5BcFE82490461E67BE874073D49d8473Dc3146 NEXT_PUBLIC_CHAIN_CURRENCY=Ankr|ANKR|18 @@ -16,8 +12,9 @@ NEXT_PUBLIC_CHAIN_NAME=NeuraTestnet NEXT_PUBLIC_CHAIN_RPC_URL=https://testnet.rpc.neuraprotocol.io/ NEXT_PUBLIC_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_cmevjjq0rde9e01ubb66qg6es/subgraphs/liquity-fork/0.0.1/gn +NEXT_PUBLIC_LIQUITY_STATS_URL=https://api.liquity.org/v2/testnet/sepolia.json -NEXT_PUBLIC_COLL_0_TOKEN_ID=ANKR +NEXT_PUBLIC_COLL_0_TOKEN_ID=WANKR NEXT_PUBLIC_COLL_1_TOKEN_ID=USN NEXT_PUBLIC_COLL_0_CONTRACT_ACTIVE_POOL=0xe9786d98c6203ca0746061abfbfdeeb02e0fe5da @@ -27,7 +24,6 @@ NEXT_PUBLIC_COLL_0_CONTRACT_COLL_SURPLUS_POOL=0xed8614f48a494a8fca53978f1183516a NEXT_PUBLIC_COLL_0_CONTRACT_COLL_TOKEN=0xBd833b6eCC30CAEaBf81dB18BB0f1e00C6997E7a NEXT_PUBLIC_COLL_0_CONTRACT_DEFAULT_POOL=0x2c6853ee6d9311f1c9aebad7f003999ac2f011c2 NEXT_PUBLIC_COLL_0_CONTRACT_PRICE_FEED=0x6f2c76f8105f723df8847a4ed97d3fc2fd335b7a -NEXT_PUBLIC_COLL_0_CONTRACT_LEVERAGE_ZAPPER=0x0000000000000000000000000000000000000000 NEXT_PUBLIC_COLL_0_CONTRACT_SORTED_TROVES=0xc383b06357a330d1f326d8f3e0a92c8e5d918343 NEXT_PUBLIC_COLL_0_CONTRACT_STABILITY_POOL=0x0c8a5ebc5b8bac0dfdad159ca61fd0906bf6f53e NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_MANAGER=0x4d2044a26fde2876db02e19e4eac2e75ecb8d510 @@ -39,7 +35,6 @@ NEXT_PUBLIC_COLL_1_CONTRACT_COLL_SURPLUS_POOL=0xbc94877d8ecd73f329ecf38fc3c032d8 NEXT_PUBLIC_COLL_1_CONTRACT_COLL_TOKEN=0xe95455FEc4Be722122401B5dCAbb2428171AfF0c NEXT_PUBLIC_COLL_1_CONTRACT_DEFAULT_POOL=0xd7662a952fb043a444ffe52115cfcf133ed238d5 NEXT_PUBLIC_COLL_1_CONTRACT_PRICE_FEED=0x4abf5e742d9b44d4b0f0309cd3928b2e78ebd187 -NEXT_PUBLIC_COLL_1_CONTRACT_LEVERAGE_ZAPPER=0x0000000000000000000000000000000000000000 NEXT_PUBLIC_COLL_1_CONTRACT_SORTED_TROVES=0xf6371a5c497170dbae162c830564fac462f206d6 NEXT_PUBLIC_COLL_1_CONTRACT_STABILITY_POOL=0x54ccb90ef683a7256fbd600db3d64d6d1015586c NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_MANAGER=0xb048a13fad98f35470ce962ddfce8c8450822d33 diff --git a/frontend/app/src/app/borrow/[collateral]/page.tsx b/frontend/app/src/app/borrow/[collateral]/page.tsx index cc9ea5405..6e10ad9a4 100644 --- a/frontend/app/src/app/borrow/[collateral]/page.tsx +++ b/frontend/app/src/app/borrow/[collateral]/page.tsx @@ -1,6 +1,6 @@ export function generateStaticParams() { return [ - { collateral: "ankr" }, + { collateral: "wankr" }, { collateral: "usn" }, ]; } diff --git a/frontend/app/src/app/earn/[pool]/layout.tsx b/frontend/app/src/app/earn/[pool]/layout.tsx index a6dd6c14b..7f945e535 100644 --- a/frontend/app/src/app/earn/[pool]/layout.tsx +++ b/frontend/app/src/app/earn/[pool]/layout.tsx @@ -3,21 +3,11 @@ import { SboldPoolScreen } from "@/src/screens/EarnPoolScreen/SboldPoolScreen"; export function generateStaticParams() { return [ - { pool: "ankr" }, + { pool: "wankr" }, { pool: "usn" }, - { pool: "sbold" }, ]; } -export default async function Layout({ - params, -}: { - params: Promise<{ - pool: "ankr" | "usn" | "sbold"; - }>; -}) { - const { pool } = await params; - return pool === "sbold" - ? - : ; +export default async function Layout() { + return ; } diff --git a/frontend/app/src/app/earn/[pool]/page.tsx b/frontend/app/src/app/earn/[pool]/page.tsx index 61ff0058e..115eb3963 100644 --- a/frontend/app/src/app/earn/[pool]/page.tsx +++ b/frontend/app/src/app/earn/[pool]/page.tsx @@ -1,7 +1,6 @@ export function generateStaticParams() { return [ - { pool: "ankr" }, - { pool: "sbold" }, + { pool: "wankr" }, { pool: "usn" }, ]; } diff --git a/frontend/app/src/comps/AppLayout/BottomBar.tsx b/frontend/app/src/comps/AppLayout/BottomBar.tsx index 2815626fe..ff2e545b7 100644 --- a/frontend/app/src/comps/AppLayout/BottomBar.tsx +++ b/frontend/app/src/comps/AppLayout/BottomBar.tsx @@ -14,7 +14,7 @@ import { blo } from "blo"; import Image from "next/image"; import { AboutButton } from "./AboutButton"; -const DISPLAYED_PRICES = ["BOLD", "ANKR"] as const; +const DISPLAYED_PRICES = ["BOLD", "WANKR"] as const; const ENABLE_REDEEM = false; export function BottomBar() { diff --git a/frontend/app/src/comps/Positions/Positions.tsx b/frontend/app/src/comps/Positions/Positions.tsx index 80563c843..bec21f93d 100644 --- a/frontend/app/src/comps/Positions/Positions.tsx +++ b/frontend/app/src/comps/Positions/Positions.tsx @@ -28,7 +28,7 @@ const actionCards = [ export function Positions({ address, columns, - showNewPositionCard = true, + showNewPositionCard = false, title = (mode) => ( mode === "loading" ? " " diff --git a/frontend/app/src/constants.ts b/frontend/app/src/constants.ts index 1a6b84786..2ea1e2d2d 100644 --- a/frontend/app/src/constants.ts +++ b/frontend/app/src/constants.ts @@ -64,7 +64,7 @@ export const TROVE_STATUS_CLOSED_BY_LIQUIDATION = 3; export const TROVE_STATUS_ZOMBIE = 4; export const MAX_COLLATERAL_DEPOSITS: Record = { - ANKR: dn.from(100_000_000n, 18), + WANKR: dn.from(100_000_000n, 18), USN: dn.from(100_000_000n, 18), }; @@ -100,8 +100,8 @@ export const DEFAULT_STRATEGIES: Array<[ Array<[BranchId, IcStrategy[]]>, ]> = [ // mainnet - [267, [ - // ANKR + [1, [ + // WANKR [0, [{ name: "Conservative Strategy", address: "0xE507E4d0763851A6287238aadD243948D18AB60a", diff --git a/frontend/app/src/content.tsx b/frontend/app/src/content.tsx index 4e3f59ec2..6b9523110 100644 --- a/frontend/app/src/content.tsx +++ b/frontend/app/src/content.tsx @@ -9,7 +9,7 @@ export default { appName: "Liquity V2", appDescription: ` Liquity V2 is a new borrowing protocol that lets users - deposit ANKR or LSTs as collateral and mint the stablecoin BOLD. + deposit LSTs as collateral and mint the stablecoin BOLD. `, appUrl: typeof window === "undefined" ? "https://www.liquity.org/" diff --git a/frontend/app/src/contracts.ts b/frontend/app/src/contracts.ts index 80b0a2744..0518b59fa 100644 --- a/frontend/app/src/contracts.ts +++ b/frontend/app/src/contracts.ts @@ -7,8 +7,6 @@ import { CollateralRegistry } from "@/src/abi/CollateralRegistry"; import { CollSurplusPool } from "@/src/abi/CollSurplusPool"; import { DefaultPool } from "@/src/abi/DefaultPool"; import { HintHelpers } from "@/src/abi/HintHelpers"; -import { LeverageLSTZapper } from "@/src/abi/LeverageLSTZapper"; -import { LeverageWETHZapper } from "@/src/abi/LeverageWETHZapper"; import { MultiTroveGetter } from "@/src/abi/MultiTroveGetter"; import { PriceFeed } from "@/src/abi/PriceFeed"; import { SortedTroves } from "@/src/abi/SortedTroves"; @@ -23,7 +21,7 @@ import { CONTRACT_WETH, ENV_BRANCHES, } from "@/src/env"; -import { erc20Abi, zeroAddress } from "viem"; +import { erc20Abi } from "viem"; const protocolAbis = { BoldToken: erc20Abi, @@ -33,24 +31,12 @@ const protocolAbis = { WETH: erc20Abi, } as const; -const BorrowerOperationsErrorsAbi = BorrowerOperations.filter( - (f) => f.type === "error", -); - const collateralAbis = { ActivePool, BorrowerOperations, CollSurplusPool, CollToken: erc20Abi, DefaultPool, - LeverageLSTZapper: [ - ...LeverageLSTZapper, - ...BorrowerOperationsErrorsAbi, - ], - LeverageWETHZapper: [ - ...LeverageWETHZapper, - ...BorrowerOperationsErrorsAbi, - ], PriceFeed: PriceFeed.map((f) => ( f.name !== "fetchPrice" ? f : { ...f, @@ -125,14 +111,6 @@ export const CONTRACTS: Contracts = { }, CollToken: { address: contracts.COLL_TOKEN, abi: abis.CollToken }, DefaultPool: { address: contracts.DEFAULT_POOL, abi: abis.DefaultPool }, - LeverageLSTZapper: { - address: symbol === "ANKR" ? zeroAddress : contracts.LEVERAGE_ZAPPER, - abi: abis.LeverageLSTZapper, - }, - LeverageWETHZapper: { - address: symbol === "ANKR" ? contracts.LEVERAGE_ZAPPER : zeroAddress, - abi: abis.LeverageWETHZapper, - }, PriceFeed: { address: contracts.PRICE_FEED, abi: abis.PriceFeed }, SortedTroves: { address: contracts.SORTED_TROVES, abi: abis.SortedTroves }, StabilityPool: { diff --git a/frontend/app/src/env.ts b/frontend/app/src/env.ts index a43148819..c09d557ef 100644 --- a/frontend/app/src/env.ts +++ b/frontend/app/src/env.ts @@ -28,7 +28,7 @@ function isIcStrategyList(value: unknown): value is IcStrategy[] { } export const CollateralSymbolSchema = v.union([ - v.literal("ANKR"), + v.literal("WANKR"), v.literal("USN"), ]); @@ -42,7 +42,6 @@ const contractsEnvNames = [ "COLL_SURPLUS_POOL", "COLL_TOKEN", "DEFAULT_POOL", - "LEVERAGE_ZAPPER", "PRICE_FEED", "SORTED_TROVES", "STABILITY_POOL", @@ -63,7 +62,6 @@ function vBranchEnvVars(branchId: BranchId) { [`${prefix}_CONTRACT_COLL_SURPLUS_POOL`]: v.optional(vAddress()), [`${prefix}_CONTRACT_COLL_TOKEN`]: v.optional(vAddress()), [`${prefix}_CONTRACT_DEFAULT_POOL`]: v.optional(vAddress()), - [`${prefix}_CONTRACT_LEVERAGE_ZAPPER`]: v.optional(vAddress()), [`${prefix}_CONTRACT_PRICE_FEED`]: v.optional(vAddress()), [`${prefix}_CONTRACT_SORTED_TROVES`]: v.optional(vAddress()), [`${prefix}_CONTRACT_STABILITY_POOL`]: v.optional(vAddress()), @@ -355,7 +353,6 @@ const parsedEnv = v.safeParse(EnvSchema, { COLL_0_CONTRACT_COLL_SURPLUS_POOL: process.env.NEXT_PUBLIC_COLL_0_CONTRACT_COLL_SURPLUS_POOL, COLL_0_CONTRACT_COLL_TOKEN: process.env.NEXT_PUBLIC_COLL_0_CONTRACT_COLL_TOKEN, COLL_0_CONTRACT_DEFAULT_POOL: process.env.NEXT_PUBLIC_COLL_0_CONTRACT_DEFAULT_POOL, - COLL_0_CONTRACT_LEVERAGE_ZAPPER: process.env.NEXT_PUBLIC_COLL_0_CONTRACT_LEVERAGE_ZAPPER, COLL_0_CONTRACT_PRICE_FEED: process.env.NEXT_PUBLIC_COLL_0_CONTRACT_PRICE_FEED, COLL_0_CONTRACT_SORTED_TROVES: process.env.NEXT_PUBLIC_COLL_0_CONTRACT_SORTED_TROVES, COLL_0_CONTRACT_STABILITY_POOL: process.env.NEXT_PUBLIC_COLL_0_CONTRACT_STABILITY_POOL, @@ -367,7 +364,6 @@ const parsedEnv = v.safeParse(EnvSchema, { COLL_1_CONTRACT_COLL_SURPLUS_POOL: process.env.NEXT_PUBLIC_COLL_1_CONTRACT_COLL_SURPLUS_POOL, COLL_1_CONTRACT_COLL_TOKEN: process.env.NEXT_PUBLIC_COLL_1_CONTRACT_COLL_TOKEN, COLL_1_CONTRACT_DEFAULT_POOL: process.env.NEXT_PUBLIC_COLL_1_CONTRACT_DEFAULT_POOL, - COLL_1_CONTRACT_LEVERAGE_ZAPPER: process.env.NEXT_PUBLIC_COLL_1_CONTRACT_LEVERAGE_ZAPPER, COLL_1_CONTRACT_PRICE_FEED: process.env.NEXT_PUBLIC_COLL_1_CONTRACT_PRICE_FEED, COLL_1_CONTRACT_SORTED_TROVES: process.env.NEXT_PUBLIC_COLL_1_CONTRACT_SORTED_TROVES, COLL_1_CONTRACT_STABILITY_POOL: process.env.NEXT_PUBLIC_COLL_1_CONTRACT_STABILITY_POOL, diff --git a/frontend/app/src/liquity-utils.ts b/frontend/app/src/liquity-utils.ts index 7fbe93bc6..0997d9242 100644 --- a/frontend/app/src/liquity-utils.ts +++ b/frontend/app/src/liquity-utils.ts @@ -652,7 +652,7 @@ export const StatsSchema = v.pipe( branch: Object.fromEntries( Object.entries(value.branch).map(([symbol, branch]) => { symbol = symbol.toUpperCase(); - if (symbol === "WANKR") symbol = "ANKR"; + return [symbol, { collActive: dnumOrNull(branch.coll_active, 18), collDefault: dnumOrNull(branch.coll_default, 18), diff --git a/frontend/app/src/screens/AccountScreen/AccountScreen.tsx b/frontend/app/src/screens/AccountScreen/AccountScreen.tsx index fa5898b9d..52064054b 100644 --- a/frontend/app/src/screens/AccountScreen/AccountScreen.tsx +++ b/frontend/app/src/screens/AccountScreen/AccountScreen.tsx @@ -121,7 +121,7 @@ export function AccountScreen({ address={address} tokenSymbol={symbol} tapButton={tapEnabled - && symbol !== "ANKR" && account.address + && account.address && addressesEqual(address, account.address)} /> diff --git a/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx b/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx index 32d28b61c..4ea96bc81 100644 --- a/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx +++ b/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx @@ -142,8 +142,7 @@ export function BorrowScreen() { const maxAmount = collBalance.data && dnumMin( maxCollDeposit, dnumMax( - // Only keep a reserve for ETH, not LSTs - dn.sub(collBalance.data, collSymbol === "ANKR" ? ETH_MAX_RESERVE : 0), + dn.sub(collBalance.data, 0), dnum18(0), ), ); diff --git a/frontend/app/src/screens/LoanScreen/LoanScreen.tsx b/frontend/app/src/screens/LoanScreen/LoanScreen.tsx index a143f7eaf..8ce4277cf 100644 --- a/frontend/app/src/screens/LoanScreen/LoanScreen.tsx +++ b/frontend/app/src/screens/LoanScreen/LoanScreen.tsx @@ -31,7 +31,6 @@ import { LoanScreenCard } from "./LoanScreenCard"; import { PanelClosePosition } from "./PanelClosePosition"; import { PanelInterestRate } from "./PanelInterestRate"; import { PanelUpdateBorrowPosition } from "./PanelUpdateBorrowPosition"; -import { PanelUpdateLeveragePosition } from "./PanelUpdateLeveragePosition"; const troveExplorers = [ ...(TROVE_EXPLORER_0 ? [TROVE_EXPLORER_0] : []), @@ -341,7 +340,7 @@ export function LoanScreen() { {action === "colldebt" && ( loanMode === "multiply" - ? + ? null : )} {action === "rate" && } diff --git a/frontend/app/src/screens/LoanScreen/PanelClosePosition.tsx b/frontend/app/src/screens/LoanScreen/PanelClosePosition.tsx index 27d810e20..943ae9c7b 100644 --- a/frontend/app/src/screens/LoanScreen/PanelClosePosition.tsx +++ b/frontend/app/src/screens/LoanScreen/PanelClosePosition.tsx @@ -27,10 +27,7 @@ export function PanelClosePosition({ const boldPriceUsd = usePrice("BOLD"); const boldBalance = useBalance(account.address, "BOLD"); - // const [repayDropdownIndex, setRepayDropdownIndex] = useState(0); - const repayDropdownIndex = 0; - - const repayToken = TOKENS_BY_SYMBOL[repayDropdownIndex === 0 ? "BOLD" : collateral.symbol]; + const repayToken = TOKENS_BY_SYMBOL["BOLD"]; // either in BOLD or in collateral const amountToRepay = repayToken.symbol === "BOLD" diff --git a/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx b/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx index 0d437a0e3..397bc10d9 100644 --- a/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx +++ b/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx @@ -81,7 +81,7 @@ export function PanelUpdateBorrowPosition({ collBalance.data && dnumMax( dn.sub( collBalance.data, - collToken.symbol === "ANKR" ? ETH_MAX_RESERVE : 0, // Only keep a reserve for ETH, not LSTs + 0, ), dnum18(0), ) diff --git a/frontend/app/src/screens/LoanScreen/PanelUpdateLeveragePosition.tsx b/frontend/app/src/screens/LoanScreen/PanelUpdateLeveragePosition.tsx deleted file mode 100644 index 317ced564..000000000 --- a/frontend/app/src/screens/LoanScreen/PanelUpdateLeveragePosition.tsx +++ /dev/null @@ -1,499 +0,0 @@ -import type { PositionLoanCommitted } from "@/src/types"; - -import { INFINITY } from "@/src/characters"; -import { Amount } from "@/src/comps/Amount/Amount"; -import { Field } from "@/src/comps/Field/Field"; -import { FlowButton } from "@/src/comps/FlowButton/FlowButton"; -import { InputTokenBadge } from "@/src/comps/InputTokenBadge/InputTokenBadge"; -import { LeverageField, useLeverageField } from "@/src/comps/LeverageField/LeverageField"; -import { UpdateBox } from "@/src/comps/UpdateBox/UpdateBox"; -import { Value } from "@/src/comps/Value/Value"; -import { ValueUpdate } from "@/src/comps/ValueUpdate/ValueUpdate"; -import { WarningBox } from "@/src/comps/WarningBox/WarningBox"; -import { ETH_MAX_RESERVE, MAX_LTV_RESERVE_RATIO, MIN_DEBT } from "@/src/constants"; -import { dnum18 } from "@/src/dnum-utils"; -import { useInputFieldValue } from "@/src/form-utils"; -import { fmtnum, formatRisk } from "@/src/formatting"; -import { getLiquidationPriceFromLeverage, getLoanDetails } from "@/src/liquity-math"; -import { getCollToken } from "@/src/liquity-utils"; -import { usePrice } from "@/src/services/Prices"; -import { riskLevelToStatusMode } from "@/src/uikit-utils"; -import { useAccount, useBalance } from "@/src/wagmi-utils"; -import { css } from "@/styled-system/css"; -import { - Checkbox, - HFlex, - InfoTooltip, - InputField, - StatusDot, - Tabs, - TextButton, - TokenIcon, - VFlex, -} from "@liquity2/uikit"; -import * as dn from "dnum"; -import { useEffect, useId, useRef, useState } from "react"; - -export function PanelUpdateLeveragePosition({ - loan, -}: { - loan: PositionLoanCommitted; -}) { - const account = useAccount(); - - const collToken = getCollToken(loan.branchId); - if (!collToken) { - throw new Error("collToken not found"); - } - - const collPrice = usePrice(collToken.symbol); - - // loan details before the update - const initialLoanDetails = getLoanDetails( - loan.deposit, - loan.borrowed, - loan.interestRate, - collToken.collateralRatio, - collPrice.data ?? null, - ); - - // deposit change - const [depositMode, setDepositMode] = useState<"add" | "remove">("add"); - const depositChange = useInputFieldValue((value) => fmtnum(value, "full")); - const [userLeverageFactor, setUserLeverageFactor] = useState( - initialLoanDetails.leverageFactor ?? 1, - ); - - let newDepositPreLeverage = depositChange.parsed - ? ( - depositMode === "remove" - ? dn.sub( - initialLoanDetails.depositPreLeverage ?? dnum18(0), - depositChange.parsed, - ) - : dn.add( - initialLoanDetails.depositPreLeverage ?? dnum18(0), - depositChange.parsed, - ) - ) - : initialLoanDetails.depositPreLeverage; - - if (newDepositPreLeverage && dn.lt(newDepositPreLeverage, 0)) { - newDepositPreLeverage = dnum18(0); - } - - const newDeposit = dn.mul( - newDepositPreLeverage ?? dnum18(0), - userLeverageFactor, - ); - - const totalPositionValue = dn.mul(newDeposit, collPrice.data ?? dnum18(0)); - - const newDebt = dn.sub( - totalPositionValue, - dn.mul(newDepositPreLeverage ?? dnum18(0), collPrice.data ?? dnum18(0)), - ); - - const newLoanDetails = getLoanDetails( - newDeposit, - newDebt, - initialLoanDetails.interestRate, - collToken.collateralRatio, - collPrice.data ?? null, - ); - - const liquidationPrice = getLiquidationPriceFromLeverage( - userLeverageFactor, - collPrice.data ?? dnum18(0), - collToken.collateralRatio, - ); - - // leverage factor - const leverageField = useLeverageField({ - collPrice: collPrice.data ?? dnum18(0), - collToken, - depositPreLeverage: newDepositPreLeverage, - maxLtvAllowedRatio: 1 - MAX_LTV_RESERVE_RATIO, - }); - - const collBalance = useBalance(account.address, collToken.symbol); - - const collMax = depositMode === "remove" ? null : ( - collBalance.data && dn.sub( - collBalance.data, - collToken?.symbol === "ANKR" ? ETH_MAX_RESERVE : 0, // Only keep a reserve for ETH, not LSTs - ) - ); - - useEffect(() => { - if (leverageField.leverageFactor !== userLeverageFactor) { - setUserLeverageFactor(leverageField.leverageFactor); - } - }, [leverageField.leverageFactor]); - - const initialLeverageFactorSet = useRef(false); - useEffect(() => { - if (initialLoanDetails.leverageFactor && !initialLeverageFactorSet.current) { - leverageField.updateLeverageFactor(initialLoanDetails.leverageFactor); - initialLeverageFactorSet.current = true; - } - }, [leverageField.updateLeverageFactor, initialLoanDetails.leverageFactor]); - - const [agreeToLiquidationRisk, setAgreeToLiquidationRisk] = useState(false); - - useEffect(() => { - setAgreeToLiquidationRisk(false); - }, [newLoanDetails.status]); - - const agreeCheckboxId = useId(); - - const allowSubmit = account.isConnected - && (newLoanDetails.status !== "at-risk" || agreeToLiquidationRisk) - && newLoanDetails.status !== "underwater" - && newLoanDetails.status !== "liquidatable" - && ( - // either the deposit or the leverage factor has changed - !dn.eq( - initialLoanDetails.deposit ?? dnum18(0), - newLoanDetails.deposit ?? dnum18(0), - ) || (initialLoanDetails.leverageFactor !== newLoanDetails.leverageFactor) - ) - // above the minimum debt - && newLoanDetails.debt && dn.gt(newLoanDetails.debt, MIN_DEBT); - - return ( - <> - - } - label={collToken.name} - /> - } - label={{ - start: depositMode === "remove" - ? "Decrease your deposit" - : "Increase your deposit", - end: ( - { - setDepositMode(index === 1 ? "remove" : "add"); - depositChange.setValue(""); - if (origin !== "keyboard") { - event.preventDefault(); - depositChange.focus(); - } - }} - selected={depositMode === "remove" ? 1 : 0} - /> - ), - }} - labelHeight={32} - placeholder="0.00" - secondary={{ - start: collPrice.data && ( - fmtnum( - depositChange.parsed - ? dn.mul(depositChange.parsed, collPrice.data) - : 0, - { preset: "2z", prefix: "$" }, - ) - ), - end: collMax && dn.gt(collMax, 0) && ( - { - depositChange.setValue(dn.toString(collMax)); - }} - /> - ), - }} - /> - } - footer={{ - start: , - end: initialLoanDetails.depositPreLeverage && newDepositPreLeverage && ( - - - {fmtnum(initialLoanDetails.depositPreLeverage)} - - } - after={ - - - {fmtnum(newDepositPreLeverage)} {collToken.name} - - - - } - fontSize={14} - /> - - } - /> - ), - }} - /> - - - } - footer={[ - { - start: , - end: ( - - ), - }, - { - start: , - end: ( - - {fmtnum(initialLoanDetails.deposit)} {collToken.name} - - )} - after={newDepositPreLeverage && ( - - {fmtnum(newLoanDetails.deposit)} {collToken.name} - - )} - /> - ), - }, - { - start: , - end: ( - - {initialLoanDetails.status === "underwater" ? INFINITY : ( - `${fmtnum(initialLoanDetails.leverageFactor, 4)}x` - )} - - } - after={ - <> - {fmtnum(userLeverageFactor, "1z")}x - - } - /> - ), - }, - { - start: , - end: ( - - ), - }, - ]} - /> - - - - - {formatRisk(initialLoanDetails.liquidationRisk)} - - ), - after: newLoanDetails.liquidationRisk && ( - <> - - {formatRisk(newLoanDetails.liquidationRisk)} - - ), - }, - { - label: LTV, - before: initialLoanDetails.ltv && ( - - - - ), - after: ( - - {newLoanDetails.status === "underwater" - || newLoanDetails.status === "liquidatable" - ? "N/A" - : } - - ), - }, - ]} - /> - - {newLoanDetails.status === "underwater" || newLoanDetails.status === "liquidatable" - ? ( - -
- Your position is above the maximum LTV of{" "} - . You need to add at least{" "} - - {" "} - {collToken.name} to prevent liquidation. -
-
- ) - : newLoanDetails.status === "at-risk" - ? ( - -
- The maximum LTV for the position is{" "} - {fmtnum(newLoanDetails.maxLtv, "pct2z")}%. Your updated position is close and is at risk of being - liquidated. -
- -
- ) - : null} -
-
- - - ); -} diff --git a/frontend/app/src/services/TransactionFlow.tsx b/frontend/app/src/services/TransactionFlow.tsx index 33677277c..8a7b50dd2 100644 --- a/frontend/app/src/services/TransactionFlow.tsx +++ b/frontend/app/src/services/TransactionFlow.tsx @@ -30,12 +30,10 @@ import { closeLoanPosition, type CloseLoanPositionRequest } from "@/src/tx-flows import { earnClaimRewards, type EarnClaimRewardsRequest } from "@/src/tx-flows/earnClaimRewards"; import { earnUpdate, type EarnUpdateRequest } from "@/src/tx-flows/earnUpdate"; import { openBorrowPosition, type OpenBorrowPositionRequest } from "@/src/tx-flows/openBorrowPosition"; -import { openLeveragePosition, type OpenLeveragePositionRequest } from "@/src/tx-flows/openLeveragePosition"; import { redeemCollateral, type RedeemCollateralRequest } from "@/src/tx-flows/redeemCollateral"; import { sboldDeposit, type SboldDepositRequest } from "@/src/tx-flows/sboldDeposit"; import { sboldRedeem, type SboldRedeemRequest } from "@/src/tx-flows/sboldRedeem"; import { updateBorrowPosition, type UpdateBorrowPositionRequest } from "@/src/tx-flows/updateBorrowPosition"; -import { updateLeveragePosition, type UpdateLeveragePositionRequest } from "@/src/tx-flows/updateLeveragePosition"; import { updateLoanInterestRate, type UpdateLoanInterestRateRequest } from "@/src/tx-flows/updateLoanInterestRate"; export type FlowRequestMap = { @@ -45,12 +43,10 @@ export type FlowRequestMap = { "earnClaimRewards": EarnClaimRewardsRequest; "earnUpdate": EarnUpdateRequest; "openBorrowPosition": OpenBorrowPositionRequest; - "openLeveragePosition": OpenLeveragePositionRequest; "redeemCollateral": RedeemCollateralRequest; "sboldDeposit": SboldDepositRequest; "sboldRedeem": SboldRedeemRequest; "updateBorrowPosition": UpdateBorrowPositionRequest; - "updateLeveragePosition": UpdateLeveragePositionRequest; "updateLoanInterestRate": UpdateLoanInterestRateRequest; }; @@ -61,7 +57,6 @@ const FlowIdSchema = v.union([ v.literal("earnClaimRewards"), v.literal("earnUpdate"), v.literal("openBorrowPosition"), - v.literal("openLeveragePosition"), v.literal("redeemCollateral"), v.literal("sboldDeposit"), v.literal("sboldRedeem"), @@ -77,12 +72,10 @@ export const flows: FlowsMap = { earnClaimRewards, earnUpdate, openBorrowPosition, - openLeveragePosition, redeemCollateral, sboldDeposit, sboldRedeem, updateBorrowPosition, - updateLeveragePosition, updateLoanInterestRate, }; diff --git a/frontend/app/src/tx-flows/closeLoanPosition.tsx b/frontend/app/src/tx-flows/closeLoanPosition.tsx index 3ab05b206..1d11fecfc 100644 --- a/frontend/app/src/tx-flows/closeLoanPosition.tsx +++ b/frontend/app/src/tx-flows/closeLoanPosition.tsx @@ -3,7 +3,6 @@ import type { FlowDeclaration } from "@/src/services/TransactionFlow"; import { Amount } from "@/src/comps/Amount/Amount"; import { ETH_GAS_COMPENSATION } from "@/src/constants"; import { fmtnum } from "@/src/formatting"; -import { getCloseFlashLoanAmount } from "@/src/liquity-leverage"; import { getBranch, getCollToken } from "@/src/liquity-utils"; import { LoanCard } from "@/src/screens/TransactionsScreen/LoanCard"; import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; @@ -118,15 +117,11 @@ export const closeLoanPosition: FlowDeclaration = { args: [BigInt(loan.troveId)], }); - const Zapper = branch.symbol === "ANKR" - ? branch.contracts.LeverageWETHZapper - : branch.contracts.LeverageLSTZapper; - return ctx.writeContract({ ...ctx.contracts.BoldToken, functionName: "approve", args: [ - Zapper.address, + branch.contracts.BorrowerOperations.address, ctx.preferredApproveMethod === "approve-infinite" ? maxUint256 // infinite approval : dn.mul([entireDebt, 18], 1.1)[0], // exact amount (TODO: better estimate) @@ -147,51 +142,18 @@ export const closeLoanPosition: FlowDeclaration = { const { loan } = ctx.request; const branch = getBranch(loan.branchId); - // repay with BOLD => get ETH - if (!ctx.request.repayWithCollateral && branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "closeTroveToRawETH", - args: [BigInt(loan.troveId)], - }); - } - - // repay with BOLD => get LST + // repay with BOLD => close trove directly on BorrowerOperations if (!ctx.request.repayWithCollateral) { return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, - functionName: "closeTroveToRawETH", + ...branch.contracts.BorrowerOperations, + functionName: "closeTrove", args: [BigInt(loan.troveId)], }); } - // from here, we are repaying with the collateral - - const closeFlashLoanAmount = await getCloseFlashLoanAmount( - loan.branchId, - loan.troveId, - ctx.wagmiConfig, - ); - - if (closeFlashLoanAmount === null) { - throw new Error("The flash loan amount could not be calculated."); - } - - // repay with collateral => get ETH - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "closeTroveFromCollateral", - args: [BigInt(loan.troveId), closeFlashLoanAmount], - }); - } - - // repay with collateral => get LST - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, - functionName: "closeTroveFromCollateral", - args: [BigInt(loan.troveId), closeFlashLoanAmount], - }); + // Repay with collateral path requires zapper/flash loan in current architecture. + // Since LeverageLSTZapper is removed, fall back to a standard close (user should repay with BOLD). + throw new Error("Repay with collateral is not supported without LeverageZapper. Please repay with BOLD."); }, async verify(ctx, hash) { @@ -216,10 +178,6 @@ export const closeLoanPosition: FlowDeclaration = { const { loan } = ctx.request; const branch = getBranch(loan.branchId); - const Zapper = branch.symbol === "ANKR" - ? branch.contracts.LeverageWETHZapper - : branch.contracts.LeverageLSTZapper; - const [{ entireDebt }, boldAllowance] = await readContracts(ctx.wagmiConfig, { contracts: [{ ...branch.contracts.TroveManager, @@ -228,7 +186,7 @@ export const closeLoanPosition: FlowDeclaration = { }, { ...ctx.contracts.BoldToken, functionName: "allowance", - args: [ctx.account, Zapper.address], + args: [ctx.account, branch.contracts.BorrowerOperations.address], }], allowFailure: false, }); diff --git a/frontend/app/src/tx-flows/openBorrowPosition.tsx b/frontend/app/src/tx-flows/openBorrowPosition.tsx index 386962073..8701818e1 100644 --- a/frontend/app/src/tx-flows/openBorrowPosition.tsx +++ b/frontend/app/src/tx-flows/openBorrowPosition.tsx @@ -20,11 +20,13 @@ import { getIndexedTroveById } from "@/src/subgraph"; import { sleep } from "@/src/utils"; import { vAddress, vBranchId, vDnum } from "@/src/valibot-utils"; import { css } from "@/styled-system/css"; -import { ADDRESS_ZERO, InfoTooltip } from "@liquity2/uikit"; +import { ADDRESS_ZERO, COLLATERALS, InfoTooltip, TOKENS_BY_SYMBOL } from "@liquity2/uikit"; import * as dn from "dnum"; +import { match } from "ts-pattern"; import * as v from "valibot"; import { maxUint256, parseEventLogs } from "viem"; import { readContract } from "wagmi/actions"; +import { getProtocolContract } from "../contracts"; import { createRequestSchema, verifyTransaction } from "./shared"; const RequestSchema = createRequestSchema( @@ -213,7 +215,7 @@ export const openBorrowPosition: FlowDeclaration = { key="start" title={`${fmtnum(ETH_GAS_COMPENSATION, "full")} ANKR`} > - {fmtnum(ETH_GAS_COMPENSATION, 4)} ANKR + {fmtnum(ETH_GAS_COMPENSATION, 4)} wANKR , "Only used in case of liquidation", ]} @@ -237,16 +239,49 @@ export const openBorrowPosition: FlowDeclaration = { ), async commit(ctx) { const branch = getBranch(ctx.request.branchId); - const { LeverageLSTZapper, CollToken } = branch.contracts; + const { BorrowerOperations, CollToken } = branch.contracts; + + const approveAmount = branch.symbol === "WANKR" + ? dn.add(ctx.request.collAmount, ETH_GAS_COMPENSATION) + : ctx.request.collAmount; return ctx.writeContract({ ...CollToken, functionName: "approve", args: [ - LeverageLSTZapper.address, + BorrowerOperations.address, + ctx.preferredApproveMethod === "approve-infinite" + ? maxUint256 // infinite approval + : approveAmount[0], // exact amount + ], + }); + }, + async verify(ctx, hash) { + await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); + }, + }, + + approveWETH: { + name: () => `Approve wANKR`, + Status: (props) => ( + + ), + async commit(ctx) { + const branch = getBranch(ctx.request.branchId); + const wETH = getProtocolContract("WETH"); + const { BorrowerOperations } = branch.contracts; + + return ctx.writeContract({ + ...wETH, + functionName: "approve", + args: [ + BorrowerOperations.address, ctx.preferredApproveMethod === "approve-infinite" ? maxUint256 // infinite approval - : ctx.request.collAmount[0], // exact amount + : ETH_GAS_COMPENSATION[0], // exact amount ], }); }, @@ -255,7 +290,7 @@ export const openBorrowPosition: FlowDeclaration = { }, }, - // LeverageLSTZapper mode + // BorrowerOperations mode openTroveLst: { name: () => "Open Position", Status: TransactionStatus, @@ -269,28 +304,45 @@ export const openBorrowPosition: FlowDeclaration = { }); const branch = getBranch(ctx.request.branchId); + const isBatch = !!ctx.request.interestRateDelegate; + + if (isBatch) { + return ctx.writeContract({ + ...branch.contracts.BorrowerOperations, + functionName: "openTroveAndJoinInterestBatchManager", + args: [{ + owner: ctx.request.owner, + ownerIndex: BigInt(ctx.request.ownerIndex), + collAmount: ctx.request.collAmount[0], + boldAmount: ctx.request.boldAmount[0], + upperHint, + lowerHint, + interestBatchManager: ctx.request.interestRateDelegate, + maxUpfrontFee: ctx.request.maxUpfrontFee[0], + addManager: ctx.request.owner, + removeManager: ctx.request.owner, + receiver: ctx.request.owner, + }], + // No ETH value is required when sending ERC20 collateral + }); + } + return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, - functionName: "openTroveWithRawETH" as const, - args: [{ - owner: ctx.request.owner, - ownerIndex: BigInt(ctx.request.ownerIndex), - collAmount: ctx.request.collAmount[0], - boldAmount: ctx.request.boldAmount[0], + ...branch.contracts.BorrowerOperations, + functionName: "openTrove", + args: [ + ctx.request.owner, + BigInt(ctx.request.ownerIndex), + ctx.request.collAmount[0], + ctx.request.boldAmount[0], upperHint, lowerHint, - annualInterestRate: ctx.request.interestRateDelegate - ? 0n - : ctx.request.annualInterestRate[0], - batchManager: ctx.request.interestRateDelegate - ? ctx.request.interestRateDelegate - : ADDRESS_ZERO, - maxUpfrontFee: ctx.request.maxUpfrontFee[0], - addManager: ADDRESS_ZERO, - removeManager: ADDRESS_ZERO, - receiver: ADDRESS_ZERO, - }], - value: ETH_GAS_COMPENSATION[0], + ctx.request.annualInterestRate[0], + ctx.request.maxUpfrontFee[0], + ctx.request.owner, + ctx.request.owner, + ctx.request.owner, + ], }); }, @@ -322,74 +374,43 @@ export const openBorrowPosition: FlowDeclaration = { } }, }, - - // LeverageWETHZapper mode - openTroveEth: { - name: () => "Open Position", - Status: TransactionStatus, - - async commit(ctx) { - const { upperHint, lowerHint } = await getTroveOperationHints({ - wagmiConfig: ctx.wagmiConfig, - contracts: ctx.contracts, - branchId: ctx.request.branchId, - interestRate: ctx.request.annualInterestRate[0], - }); - - const branch = getBranch(ctx.request.branchId); - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "openTroveWithRawETH", - args: [{ - owner: ctx.request.owner, - ownerIndex: BigInt(ctx.request.ownerIndex), - collAmount: 0n, - boldAmount: ctx.request.boldAmount[0], - upperHint, - lowerHint, - annualInterestRate: ctx.request.interestRateDelegate - ? 0n - : ctx.request.annualInterestRate[0], - batchManager: ctx.request.interestRateDelegate - ? ctx.request.interestRateDelegate - : ADDRESS_ZERO, - maxUpfrontFee: ctx.request.maxUpfrontFee[0], - addManager: ADDRESS_ZERO, - removeManager: ADDRESS_ZERO, - receiver: ADDRESS_ZERO, - }], - value: ctx.request.collAmount[0] + ETH_GAS_COMPENSATION[0], - }); - }, - - async verify(...args) { - // same verification as openTroveLst - return openBorrowPosition.steps.openTroveLst?.verify(...args); - }, - }, }, async getSteps(ctx) { const branch = getBranch(ctx.request.branchId); - // ETH doesn't need approval - if (branch.symbol === "ANKR") { - return ["openTroveEth"]; - } + const spender = branch.contracts.BorrowerOperations; - // Check if approval is needed - const allowance = await readContract(ctx.wagmiConfig, { + // CollToken allowance threshold: for WANKR include gas compensation + const requiredCollAllowance = branch.symbol === "WANKR" + ? (dn.add(ctx.request.collAmount, ETH_GAS_COMPENSATION)[0]) + : ctx.request.collAmount[0]; + + const collAllowance = await readContract(ctx.wagmiConfig, { ...branch.contracts.CollToken, functionName: "allowance", - args: [ctx.account, branch.contracts.LeverageLSTZapper.address], + args: [ctx.account, spender.address], }); const steps: string[] = []; - if (allowance < ctx.request.collAmount[0]) { + if (collAllowance < requiredCollAllowance) { steps.push("approveLst"); } + // For non-WANKR collaterals, also require WETH allowance for gas compensation + if (branch.symbol !== "WANKR") { + const wETH = getProtocolContract("WETH"); + const wethAllowance = await readContract(ctx.wagmiConfig, { + ...wETH, + functionName: "allowance", + args: [ctx.account, spender.address], + }); + if (wethAllowance < ETH_GAS_COMPENSATION[0]) { + steps.push("approveWETH"); + } + } + steps.push("openTroveLst"); return steps; }, diff --git a/frontend/app/src/tx-flows/openLeveragePosition.tsx b/frontend/app/src/tx-flows/openLeveragePosition.tsx deleted file mode 100644 index 630b1e320..000000000 --- a/frontend/app/src/tx-flows/openLeveragePosition.tsx +++ /dev/null @@ -1,311 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { ETH_GAS_COMPENSATION, MAX_UPFRONT_FEE } from "@/src/constants"; -import { dnum18 } from "@/src/dnum-utils"; -import { fmtnum } from "@/src/formatting"; -import { getOpenLeveragedTroveParams } from "@/src/liquity-leverage"; -import { getBranch, getCollToken, getTroveOperationHints, usePredictOpenTroveUpfrontFee } from "@/src/liquity-utils"; -import { AccountButton } from "@/src/screens/TransactionsScreen/AccountButton"; -import { LoanCard } from "@/src/screens/TransactionsScreen/LoanCard"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { usePrice } from "@/src/services/Prices"; -import { getIndexedTroveById } from "@/src/subgraph"; -import { noop, sleep } from "@/src/utils"; -import { vPositionLoanUncommited } from "@/src/valibot-utils"; -import { css } from "@/styled-system/css"; -import { ADDRESS_ZERO, InfoTooltip } from "@liquity2/uikit"; -import * as dn from "dnum"; -import * as v from "valibot"; -import { maxUint256, parseEventLogs } from "viem"; -import { readContract } from "wagmi/actions"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "openLeveragePosition", - { - ownerIndex: v.number(), - leverageFactor: v.number(), - loan: vPositionLoanUncommited(), - }, -); - -export type OpenLeveragePositionRequest = v.InferOutput; - -export const openLeveragePosition: FlowDeclaration = { - title: "Review & Send Transaction", - - Summary({ request }) { - return ( - - ); - }, - - Details({ request }) { - const { loan } = request; - const collToken = getCollToken(loan.branchId); - if (!collToken) { - throw new Error(`Invalid branch: ${loan.branchId}`); - } - - const collPrice = usePrice(collToken.symbol); - const upfrontFee = usePredictOpenTroveUpfrontFee( - loan.branchId, - loan.borrowed, - loan.interestRate, - ); - - const initialDeposit = dn.div(loan.deposit, request.leverageFactor); - const yearlyBoldInterest = dn.mul(loan.borrowed, loan.interestRate); - const borrowedWithFee = upfrontFee.data && dn.add(loan.borrowed, upfrontFee.data); - - return ( - <> - - - - - This fee is charged when you open a new loan or increase your debt. It corresponds to 7 days of average - interest for the respective collateral asset. - - , - ]} - /> - {loan.batchManager - ? ( - , -
- {fmtnum(loan.interestRate, "pctfull")}% ({fmtnum(yearlyBoldInterest, { - digits: 4, - dust: false, - prefix: "~", - })} BOLD per year) -
, - ]} - /> - ) - : ( - - )} - - {fmtnum(ETH_GAS_COMPENSATION, 4)} ANKR - , - "Only used in case of liquidation", - ]} - /> - - ); - }, - - steps: { - approveLst: { - name: ({ request }) => { - const collToken = getCollToken(request.loan.branchId); - return `Approve ${collToken?.name ?? ""}`; - }, - Status: (props) => ( - - ), - async commit(ctx) { - const { loan } = ctx.request; - const initialDeposit = dn.div(loan.deposit, ctx.request.leverageFactor); - const branch = getBranch(loan.branchId); - const { LeverageLSTZapper, CollToken } = branch.contracts; - return ctx.writeContract({ - ...CollToken, - functionName: "approve", - args: [ - LeverageLSTZapper.address, - ctx.preferredApproveMethod === "approve-infinite" - ? maxUint256 // infinite approval - : initialDeposit[0], // exact amount - ], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - - openLeveragedTrove: { - name: () => "Open Multiply Position", - Status: TransactionStatus, - - async commit(ctx) { - const { loan } = ctx.request; - const initialDeposit = dn.div(loan.deposit, ctx.request.leverageFactor); - const branch = getBranch(loan.branchId); - const { LeverageLSTZapper, LeverageWETHZapper } = branch.contracts; - - const openLeveragedParams = await getOpenLeveragedTroveParams( - loan.branchId, - initialDeposit[0], - ctx.request.leverageFactor, - ctx.wagmiConfig, - ); - - const { upperHint, lowerHint } = await getTroveOperationHints({ - wagmiConfig: ctx.wagmiConfig, - contracts: ctx.contracts, - branchId: loan.branchId, - interestRate: loan.interestRate[0], - }); - - const txParams = { - owner: loan.borrower, - ownerIndex: BigInt(ctx.request.ownerIndex), - collAmount: initialDeposit[0], - flashLoanAmount: openLeveragedParams.flashLoanAmount, - boldAmount: openLeveragedParams.effectiveBoldAmount, - upperHint, - lowerHint, - annualInterestRate: loan.batchManager ? 0n : loan.interestRate[0], - batchManager: loan.batchManager ?? ADDRESS_ZERO, - maxUpfrontFee: MAX_UPFRONT_FEE, - addManager: ADDRESS_ZERO, - removeManager: ADDRESS_ZERO, - receiver: ADDRESS_ZERO, - }; - - // ETH collateral case - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...LeverageWETHZapper, - functionName: "openLeveragedTroveWithRawETH", - args: [txParams], - value: initialDeposit[0] + ETH_GAS_COMPENSATION[0], - }); - } - - // LST collateral case - return ctx.writeContract({ - ...LeverageLSTZapper, - functionName: "openLeveragedTroveWithRawETH", - args: [txParams], - value: ETH_GAS_COMPENSATION[0], - }); - }, - - async verify(ctx, hash) { - const receipt = await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - - // Extract trove ID from logs - const collToken = getCollToken(ctx.request.loan.branchId); - if (!collToken) throw new Error("Invalid branch"); - - const branch = getBranch(ctx.request.loan.branchId); - const [troveOperation] = parseEventLogs({ - abi: branch.contracts.TroveManager.abi, - logs: receipt.logs, - eventName: "TroveOperation", - }); - - if (!troveOperation?.args?._troveId) { - throw new Error("Failed to extract trove ID from transaction"); - } - - // Wait for trove to appear in subgraph - while (true) { - const trove = await getIndexedTroveById( - branch.branchId, - `0x${troveOperation.args._troveId.toString(16)}`, - ); - if (trove !== null) { - break; - } - await sleep(1000); - } - }, - }, - }, - - async getSteps(ctx) { - const { loan } = ctx.request; - const collToken = getCollToken(loan.branchId); - if (!collToken) { - throw new Error("Invalid branch: " + loan.branchId); - } - - // ETH doesn't need approval - if (collToken.symbol === "ANKR") { - return ["openLeveragedTrove"]; - } - - const branch = getBranch(loan.branchId); - const { LeverageLSTZapper, CollToken } = branch.contracts; - - const allowance = dnum18( - await readContract(ctx.wagmiConfig, { - ...CollToken, - functionName: "allowance", - args: [ctx.account, LeverageLSTZapper.address], - }), - ); - - const steps: string[] = []; - - const initialDeposit = dn.div(loan.deposit, ctx.request.leverageFactor); - if (dn.lt(allowance, initialDeposit)) { - steps.push("approveLst"); - } - - steps.push("openLeveragedTrove"); - - return steps; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; diff --git a/frontend/app/src/tx-flows/redeemCollateral.tsx b/frontend/app/src/tx-flows/redeemCollateral.tsx index 096ae8af6..5b97a8b43 100644 --- a/frontend/app/src/tx-flows/redeemCollateral.tsx +++ b/frontend/app/src/tx-flows/redeemCollateral.tsx @@ -64,20 +64,19 @@ export const redeemCollateral: FlowDeclaration = { /> {branches.map(({ symbol }) => { const collChange = collChanges?.find((change) => symbol === change.symbol)?.change; - const symbol_ = symbol === "ANKR" ? "WANKR" : symbol; return ( , - Estimated {symbol_} you will receive. + Estimated {symbol} you will receive. , ]} /> diff --git a/frontend/app/src/tx-flows/updateBorrowPosition.tsx b/frontend/app/src/tx-flows/updateBorrowPosition.tsx index 9fe14244c..58568fd60 100644 --- a/frontend/app/src/tx-flows/updateBorrowPosition.tsx +++ b/frontend/app/src/tx-flows/updateBorrowPosition.tsx @@ -141,9 +141,7 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(ctx.request.loan.branchId); - const Controller = branch.symbol === "ANKR" - ? branch.contracts.LeverageWETHZapper - : branch.contracts.LeverageLSTZapper; + const Controller = branch.contracts.BorrowerOperations; return ctx.writeContract({ ...ctx.contracts.BoldToken, @@ -177,7 +175,7 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(ctx.request.loan.branchId); - const Controller = branch.contracts.LeverageLSTZapper; + const Controller = branch.contracts.BorrowerOperations; return ctx.writeContract({ ...branch.contracts.CollToken, @@ -207,24 +205,8 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(loan.branchId); - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "adjustTroveWithRawETH", - args: [ - BigInt(loan.troveId), - dn.abs(collChange)[0], - dn.gt(collChange, 0n), - dn.abs(debtChange)[0], - dn.gt(debtChange, 0n), - maxUpfrontFee[0], - ], - value: dn.gt(collChange, 0n) ? collChange[0] : 0n, - }); - } - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, + ...branch.contracts.BorrowerOperations, functionName: "adjustTrove", args: [ BigInt(loan.troveId), @@ -273,26 +255,8 @@ export const updateBorrowPosition: FlowDeclaration interestRate: loan.interestRate[0], }); - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "adjustZombieTroveWithRawETH", - args: [ - BigInt(loan.troveId), - dn.abs(collChange)[0], - dn.gt(collChange, 0n), - dn.abs(debtChange)[0], - dn.gt(debtChange, 0n), - upperHint, - lowerHint, - maxUpfrontFee[0], - ], - value: dn.gt(collChange, 0n) ? collChange[0] : 0n, - }); - } - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, + ...branch.contracts.BorrowerOperations, functionName: "adjustZombieTrove", args: [ BigInt(loan.troveId), @@ -322,16 +286,8 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(loan.branchId); - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "repayBold", - args: [BigInt(loan.troveId), dn.abs(debtChange)[0]], - }); - } - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, + ...branch.contracts.BorrowerOperations, functionName: "repayBold", args: [BigInt(loan.troveId), dn.abs(debtChange)[0]], }); @@ -352,17 +308,8 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(loan.branchId); - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "addCollWithRawETH", - args: [BigInt(loan.troveId)], - value: dn.abs(collChange)[0], - }); - } - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, + ...branch.contracts.BorrowerOperations, functionName: "addColl", args: [BigInt(loan.troveId), dn.abs(collChange)[0]], }); @@ -382,16 +329,8 @@ export const updateBorrowPosition: FlowDeclaration const debtChange = getDebtChange(loan, ctx.request.prevLoan); const branch = getBranch(loan.branchId); - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "withdrawBold", - args: [BigInt(loan.troveId), dn.abs(debtChange)[0], maxUpfrontFee[0]], - }); - } - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, + ...branch.contracts.BorrowerOperations, functionName: "withdrawBold", args: [BigInt(loan.troveId), dn.abs(debtChange)[0], maxUpfrontFee[0]], }); @@ -411,16 +350,8 @@ export const updateBorrowPosition: FlowDeclaration const collChange = getCollChange(loan, ctx.request.prevLoan); const branch = getBranch(loan.branchId); - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "withdrawCollToRawETH", - args: [BigInt(loan.troveId), dn.abs(collChange)[0]], - }); - } - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, + ...branch.contracts.BorrowerOperations, functionName: "withdrawColl", args: [BigInt(loan.troveId), dn.abs(collChange)[0]], }); @@ -438,9 +369,7 @@ export const updateBorrowPosition: FlowDeclaration const branch = getBranch(ctx.request.loan.branchId); - const Controller = branch.symbol === "ANKR" - ? branch.contracts.LeverageWETHZapper - : branch.contracts.LeverageLSTZapper; + const Controller = branch.contracts.BorrowerOperations; const isBoldApproved = !dn.lt(debtChange, 0) || !dn.gt( dn.abs(debtChange), @@ -455,7 +384,7 @@ export const updateBorrowPosition: FlowDeclaration ); // Collateral token needs to be approved if collChange > 0 and collToken != "ETH" (no LeverageWETHZapper) - const isCollApproved = branch.symbol === "ANKR" || !dn.gt(collChange, 0) || !dn.gt(collChange, [ + const isCollApproved = !dn.gt(collChange, 0) || !dn.gt(collChange, [ (await ctx.readContract({ ...branch.contracts.CollToken, functionName: "allowance", diff --git a/frontend/app/src/tx-flows/updateLeveragePosition.tsx b/frontend/app/src/tx-flows/updateLeveragePosition.tsx deleted file mode 100644 index 597938fd3..000000000 --- a/frontend/app/src/tx-flows/updateLeveragePosition.tsx +++ /dev/null @@ -1,419 +0,0 @@ -import type { LoadingState } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { MAX_UPFRONT_FEE } from "@/src/constants"; -import { dnum18 } from "@/src/dnum-utils"; -import { fmtnum } from "@/src/formatting"; -import { getLeverDownTroveParams, getLeverUpTroveParams } from "@/src/liquity-leverage"; -import { getBranch, getCollToken, usePredictAdjustTroveUpfrontFee } from "@/src/liquity-utils"; -import { LoanCard } from "@/src/screens/TransactionsScreen/LoanCard"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { usePrice } from "@/src/services/Prices"; -import { vDnum, vPositionLoanCommited } from "@/src/valibot-utils"; -import { ADDRESS_ZERO } from "@liquity2/uikit"; -import * as dn from "dnum"; -import { match, P } from "ts-pattern"; -import * as v from "valibot"; -import { maxUint256 } from "viem"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "updateLeveragePosition", - { - depositChange: v.union([v.null(), vDnum()]), - // set to null to indicate no multiply change - leverageFactorChange: v.union([ - v.null(), - v.tuple([ - v.number(), // prev multiply - v.number(), // new multiply - ]), - ]), - prevLoan: vPositionLoanCommited(), - loan: vPositionLoanCommited(), - }, -); - -export type UpdateLeveragePositionRequest = v.InferOutput; - -function useUpfrontFeeData( - loan: UpdateLeveragePositionRequest["loan"], - prevLoan: UpdateLeveragePositionRequest["prevLoan"], -) { - const debtChange = dn.sub(loan.borrowed, prevLoan.borrowed); - const isBorrowing = dn.gt(debtChange, 0); - - const upfrontFee = usePredictAdjustTroveUpfrontFee( - loan.branchId, - loan.troveId, - isBorrowing ? debtChange : [0n, 18], - ); - - return { - ...upfrontFee, - data: !upfrontFee.data ? null : { - isBorrowing, - debtChangeWithFee: isBorrowing - ? dn.add(debtChange, upfrontFee.data) - : debtChange, - upfrontFee: upfrontFee.data, - }, - }; -} - -export const updateLeveragePosition: FlowDeclaration = { - title: "Review & Send Transaction", - - Summary({ request }) { - const { loan, prevLoan } = request; - - const upfrontFeeData = useUpfrontFeeData(loan, prevLoan); - const loadingState = match(upfrontFeeData) - .returnType() - .with({ status: "error" }, () => "error") - .with({ status: "pending" }, () => "loading") - .with({ data: null }, () => "not-found") - .with({ data: P.nonNullable }, () => "success") - .otherwise(() => "error"); - - const borrowedWithFee = dn.add( - loan.borrowed, - upfrontFeeData.data?.upfrontFee ?? dn.from(0, 18), - ); - - return ( - { - upfrontFeeData.refetch(); - }} - txPreviewMode - /> - ); - }, - - Details({ request }) { - const { loan, prevLoan, depositChange, leverageFactorChange } = request; - - const branch = getBranch(loan.branchId); - const collateral = getCollToken(branch.id); - - const collPrice = usePrice(collateral.symbol); - const upfrontFeeData = useUpfrontFeeData(loan, prevLoan); - - const debtChangeWithFee = upfrontFeeData.data?.debtChangeWithFee; - const isBorrowing = upfrontFeeData.data?.isBorrowing; - - return ( - <> - {depositChange !== null && ( - , - , - ]} - /> - )} - {leverageFactorChange && ( - - {fmtnum(leverageFactorChange[1] - leverageFactorChange[0], { - digits: 2, - signDisplay: "exceptZero", - })}x - , -
- {fmtnum(leverageFactorChange[1], 2)}x multiply -
, - ]} - /> - )} - , - upfrontFeeData.data?.upfrontFee - && dn.gt(upfrontFeeData.data.upfrontFee, 0) - && ( - - ), - ]} - /> - - ); - }, - - steps: { - approveLst: { - name: ({ request }) => { - const token = getCollToken(request.loan.branchId); - return `Approve ${token?.name ?? ""}`; - }, - Status: (props) => ( - - ), - async commit(ctx) { - if (!ctx.request.depositChange) { - throw new Error("Invalid step: depositChange is required with approveLst"); - } - - const branch = getBranch(ctx.request.loan.branchId); - const Zapper = branch.contracts.LeverageLSTZapper; - - return ctx.writeContract({ - ...branch.contracts.CollToken, - functionName: "approve", - args: [ - Zapper.address, - ctx.preferredApproveMethod === "approve-infinite" - ? maxUint256 // infinite approval - : ctx.request.depositChange[0], // exact amount - ], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - - increaseDeposit: { - name: () => "Increase Deposit", - Status: TransactionStatus, - - async commit(ctx) { - if (!ctx.request.depositChange) { - throw new Error("Invalid step: depositChange is required with increaseDeposit"); - } - - const branch = getBranch(ctx.request.loan.branchId); - - // add ETH - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "addCollWithRawETH", - args: [BigInt(ctx.request.loan.troveId)], - value: ctx.request.depositChange[0], - }); - } - - // add LST - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, - functionName: "addColl", - args: [BigInt(ctx.request.loan.troveId), ctx.request.depositChange[0]], - }); - }, - - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - - decreaseDeposit: { - name: () => "Decrease Deposit", - Status: TransactionStatus, - - async commit(ctx) { - if (!ctx.request.depositChange) { - throw new Error("Invalid step: depositChange is required with decreaseDeposit"); - } - - const branch = getBranch(ctx.request.loan.branchId); - - const args = [ - BigInt(ctx.request.loan.troveId), - ctx.request.depositChange[0] * -1n, - ] as const; - - // withdraw ETH - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "withdrawCollToRawETH", - args, - }); - } - - // withdraw LST - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, - functionName: "withdrawColl", - args, - }); - }, - - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - - leverUpTrove: { - name: () => "Increase Multiplier", - Status: TransactionStatus, - - async commit(ctx) { - if (!ctx.request.leverageFactorChange) { - throw new Error("Invalid step: leverageFactorChange is required with leverUpTrove"); - } - - const params = await getLeverUpTroveParams( - ctx.request.loan.branchId, - ctx.request.loan.troveId, - ctx.request.leverageFactorChange[1], - ctx.wagmiConfig, - ); - if (!params) { - throw new Error("Couldn't fetch trove lever up params"); - } - - const branch = getBranch(ctx.request.loan.branchId); - - const args = [{ - troveId: BigInt(ctx.request.loan.troveId), - flashLoanAmount: params.flashLoanAmount, - boldAmount: params.effectiveBoldAmount, - maxUpfrontFee: MAX_UPFRONT_FEE, - }] as const; - - // leverage up ETH trove - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "leverUpTrove", - args, - }); - } - - // leverage up LST trove - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, - functionName: "leverUpTrove", - args, - }); - }, - - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - - leverDownTrove: { - name: () => "Decrease Multiplier", - Status: TransactionStatus, - - async commit(ctx) { - if (!ctx.request.leverageFactorChange) { - throw new Error("Invalid step: leverageFactorChange is required with leverDownTrove"); - } - - const params = await getLeverDownTroveParams( - ctx.request.loan.branchId, - ctx.request.loan.troveId, - ctx.request.leverageFactorChange[1], - ctx.wagmiConfig, - ); - if (!params) { - throw new Error("Couldn't fetch trove lever down params"); - } - - const branch = getBranch(ctx.request.loan.branchId); - - const args = [{ - troveId: BigInt(ctx.request.loan.troveId), - flashLoanAmount: params.flashLoanAmount, - minBoldAmount: params.minBoldAmount, - }] as const; - - if (branch.symbol === "ANKR") { - return ctx.writeContract({ - ...branch.contracts.LeverageWETHZapper, - functionName: "leverDownTrove", - args, - }); - } - - return ctx.writeContract({ - ...branch.contracts.LeverageLSTZapper, - functionName: "leverDownTrove", - args, - }); - }, - - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe); - }, - }, - }, - - async getSteps(ctx) { - const { depositChange, leverageFactorChange, loan } = ctx.request; - - const steps: string[] = []; - const branch = getBranch(loan.branchId); - - // only check approval for non-ETH collaterals - if (branch.symbol !== "ANKR" && depositChange && dn.gt(depositChange, 0)) { - const { LeverageLSTZapper, CollToken } = branch.contracts; - const allowance = dnum18( - await ctx.readContract({ - ...CollToken, - functionName: "allowance", - args: [ctx.account ?? ADDRESS_ZERO, LeverageLSTZapper.address], - }), - ); - - if (dn.lt(allowance, depositChange)) { - steps.push("approveLst"); - } - } - - if (depositChange) { - steps.push(dn.gt(depositChange, 0) ? "increaseDeposit" : "decreaseDeposit"); - } - - if (leverageFactorChange) { - const [oldLeverage, newLeverage] = leverageFactorChange; - steps.push(newLeverage > oldLeverage ? "leverUpTrove" : "leverDownTrove"); - } - - return steps; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; diff --git a/frontend/app/src/valibot-utils.ts b/frontend/app/src/valibot-utils.ts index 1bf76dc34..c4119e0df 100644 --- a/frontend/app/src/valibot-utils.ts +++ b/frontend/app/src/valibot-utils.ts @@ -266,7 +266,7 @@ export function vVoteAllocations() { export function vCollateralSymbol() { return v.union([ - v.literal("ANKR"), + v.literal("WANKR"), v.literal("USN"), ]); } diff --git a/frontend/app/src/wagmi-utils.ts b/frontend/app/src/wagmi-utils.ts index bbf7df694..5b03f2d87 100644 --- a/frontend/app/src/wagmi-utils.ts +++ b/frontend/app/src/wagmi-utils.ts @@ -30,9 +30,9 @@ export function useBalances( const tokenConfigs = tokens.map((token) => { const tokenAddress = match(token) .when( - (symbol) => Boolean(symbol && isCollateralSymbol(symbol) && symbol !== "ANKR"), + (symbol) => Boolean(symbol && isCollateralSymbol(symbol)), (symbol) => { - if (!symbol || !isCollateralSymbol(symbol) || symbol === "ANKR") { + if (!symbol || !isCollateralSymbol(symbol)) { return null; } return getBranch(symbol).contracts.CollToken.address; @@ -44,7 +44,7 @@ export function useBalances( return { token, tokenAddress, - isEth: token === "ANKR", + isEth: false, }; }); @@ -72,20 +72,13 @@ export function useBalances( // combine results return tokens.reduce((result, token) => { - if (token === "ANKR") { + const erc20Index = erc20Tokens.findIndex((config) => config.token === token); + if (erc20Index !== -1) { + const balance = erc20Balances.data?.[erc20Index]; result[token] = { - data: ethBalance.data ? dnum18(ethBalance.data.value) : undefined, - isLoading: ethBalance.isLoading, + data: balance?.result !== undefined ? dnum18(balance.result) : undefined, + isLoading: erc20Balances.isLoading, }; - } else { - const erc20Index = erc20Tokens.findIndex((config) => config.token === token); - if (erc20Index !== -1) { - const balance = erc20Balances.data?.[erc20Index]; - result[token] = { - data: balance?.result !== undefined ? dnum18(balance.result) : undefined, - isLoading: erc20Balances.isLoading, - }; - } } return result; }, {} as Record< diff --git a/frontend/uikit/src/tokens.ts b/frontend/uikit/src/tokens.ts index 5b48085e4..ad05f79cd 100644 --- a/frontend/uikit/src/tokens.ts +++ b/frontend/uikit/src/tokens.ts @@ -19,27 +19,27 @@ export type Token = ExternalToken & { export type TokenSymbol = | "BOLD" | "SBOLD" - | "ANKR" + | "WANKR" | "USN"; export type CollateralSymbol = & TokenSymbol & ( - | "ANKR" + | "WANKR" | "USN" ); export function isTokenSymbol(symbolOrUrl: string): symbolOrUrl is TokenSymbol { return ( symbolOrUrl === "BOLD" - || symbolOrUrl === "ANKR" + || symbolOrUrl === "WANKR" || symbolOrUrl === "USN" ); } export function isCollateralSymbol(symbol: string): symbol is CollateralSymbol { return ( - symbol === "ANKR" + symbol === "WANKR" || symbol === "USN" ); } @@ -61,11 +61,11 @@ export const SBOLD: Token = { symbol: "SBOLD" as const, } as const; -export const ANKR: CollateralToken = { +export const WANKR: CollateralToken = { collateralRatio: 1.1, icon: tokenEth, - name: "ANKR", - symbol: "ANKR" as const, + name: "wANKR", + symbol: "WANKR" as const, } as const; export const USN: CollateralToken = { @@ -76,13 +76,13 @@ export const USN: CollateralToken = { } as const; export const COLLATERALS: CollateralToken[] = [ - ANKR, + WANKR, USN, ]; export const TOKENS_BY_SYMBOL = { BOLD, - ANKR, + WANKR, USN, SBOLD, } as const; From e5c51c5cd2baae01c75fb4f63f18ae271d77f28e Mon Sep 17 00:00:00 2001 From: Artur Korotenko Date: Fri, 5 Sep 2025 16:45:44 +0500 Subject: [PATCH 05/19] chore: remove sbold --- frontend/app/src/app/earn/[pool]/layout.tsx | 1 - .../app/src/comps/AppLayout/BottomBar.tsx | 2 +- .../SboldPositionSummary.tsx | 252 ----------- .../src/comps/Positions/PositionCardSbold.tsx | 163 ------- .../app/src/comps/Positions/Positions.tsx | 12 - .../app/src/comps/SboldInfo/SboldInfo.tsx | 219 --------- frontend/app/src/env.ts | 3 - frontend/app/src/sbold.ts | 201 --------- .../src/screens/BorrowScreen/BorrowScreen.tsx | 2 +- .../EarnPoolScreen/SboldPoolScreen.tsx | 417 ------------------ .../EarnPoolsListScreen.tsx | 23 +- .../app/src/screens/HomeScreen/HomeScreen.tsx | 30 +- .../LoanScreen/PanelUpdateBorrowPosition.tsx | 2 +- frontend/app/src/services/TransactionFlow.tsx | 9 - .../app/src/tx-flows/openBorrowPosition.tsx | 3 +- frontend/app/src/tx-flows/sboldDeposit.tsx | 189 -------- frontend/app/src/tx-flows/sboldRedeem.tsx | 96 ---- frontend/app/src/types.ts | 10 +- frontend/app/src/valibot-utils.ts | 11 - frontend/app/src/wagmi-utils.ts | 10 +- frontend/uikit/src/tokens.ts | 9 - 21 files changed, 15 insertions(+), 1649 deletions(-) delete mode 100644 frontend/app/src/comps/EarnPositionSummary/SboldPositionSummary.tsx delete mode 100644 frontend/app/src/comps/Positions/PositionCardSbold.tsx delete mode 100644 frontend/app/src/comps/SboldInfo/SboldInfo.tsx delete mode 100644 frontend/app/src/sbold.ts delete mode 100644 frontend/app/src/screens/EarnPoolScreen/SboldPoolScreen.tsx delete mode 100644 frontend/app/src/tx-flows/sboldDeposit.tsx delete mode 100644 frontend/app/src/tx-flows/sboldRedeem.tsx diff --git a/frontend/app/src/app/earn/[pool]/layout.tsx b/frontend/app/src/app/earn/[pool]/layout.tsx index 7f945e535..1d1c23783 100644 --- a/frontend/app/src/app/earn/[pool]/layout.tsx +++ b/frontend/app/src/app/earn/[pool]/layout.tsx @@ -1,5 +1,4 @@ import { EarnPoolScreen } from "@/src/screens/EarnPoolScreen/EarnPoolScreen"; -import { SboldPoolScreen } from "@/src/screens/EarnPoolScreen/SboldPoolScreen"; export function generateStaticParams() { return [ diff --git a/frontend/app/src/comps/AppLayout/BottomBar.tsx b/frontend/app/src/comps/AppLayout/BottomBar.tsx index ff2e545b7..f78855e6a 100644 --- a/frontend/app/src/comps/AppLayout/BottomBar.tsx +++ b/frontend/app/src/comps/AppLayout/BottomBar.tsx @@ -227,7 +227,7 @@ function getTokenLink(address: Address) { return address && `${CHAIN_BLOCK_EXPLORER.url}token/${address}`; } -function Price({ symbol }: { symbol: Exclude }) { +function Price({ symbol }: { symbol: TokenSymbol }) { const price = usePrice(symbol); const tokenAddress = getTokenAddress(symbol); const tokenUrl = tokenAddress && getTokenLink(tokenAddress); diff --git a/frontend/app/src/comps/EarnPositionSummary/SboldPositionSummary.tsx b/frontend/app/src/comps/EarnPositionSummary/SboldPositionSummary.tsx deleted file mode 100644 index dea4bbb20..000000000 --- a/frontend/app/src/comps/EarnPositionSummary/SboldPositionSummary.tsx +++ /dev/null @@ -1,252 +0,0 @@ -import type { Dnum, PositionSbold } from "@/src/types"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { TagPreview } from "@/src/comps/TagPreview/TagPreview"; -import { fmtnum } from "@/src/formatting"; -import { getBranch } from "@/src/liquity-utils"; -import { useSboldStats } from "@/src/sbold"; -import { isBranchId } from "@/src/types"; -import { css } from "@/styled-system/css"; -import { InfoTooltip, TokenIcon } from "@liquity2/uikit"; -import * as dn from "dnum"; -import { EarnPositionSummaryBase } from "./EarnPositionSummaryBase"; - -export function SboldPositionSummary({ - linkToScreen, - prevSboldPosition, - sboldPosition, - tvl, - txPreviewMode, -}: { - linkToScreen?: boolean; - prevSboldPosition?: PositionSbold | null; - sboldPosition: PositionSbold | null; - tvl?: Dnum | null; - txPreviewMode?: boolean; -}) { - const stats = useSboldStats(); - const tvl_ = tvl ?? stats.data?.totalBold ?? null; - - const active = Boolean( - txPreviewMode || (sboldPosition && dn.gt(sboldPosition.sbold, 0)), - ); - - return ( - : ( - <> -
-
- APR -
-
- -
- The annualized rate sBOLD deposits earned over the last 24 hours., - footerLink: { - label: "Check Dune for more details", - href: "https://dune.com/liquity/liquity-v2", - }, - }} - /> -
-
-
- 7d APR -
- - The annualized rate sBOLD deposits earned over the last 7 days., - footerLink: { - label: "Check Dune for more details", - href: "https://dune.com/liquity/liquity-v2", - }, - }} - /> -
- - )} - subtitle={ - <> -
TVL
-
- -
- -
-
Total amount of BOLD deposited in the sBOLD pool.
-
-
Pools weight:
- {stats.data?.weights.map((weight, index) => { - if (!isBranchId(index)) { - return null; - } - const branch = getBranch(index); - return ( -
- - -
- ); - })} -
-
-
- - } - infoItems={[ - !active ? null : { - label: "sBOLD Balance", - content: ( - <> -
- {active && fmtnum(sboldPosition?.sbold)} - -
- {prevSboldPosition && ( -
- {fmtnum(prevSboldPosition.sbold)} - -
- )} - - ), - }, - { - label: "BOLD Deposit", - content: ( - <> -
- {active && fmtnum(sboldPosition?.bold)} - -
- {prevSboldPosition && ( -
- {fmtnum(prevSboldPosition.bold)} - -
- )} - - ), - }, - ].filter((item) => item !== null)} - /> - ); -} diff --git a/frontend/app/src/comps/Positions/PositionCardSbold.tsx b/frontend/app/src/comps/Positions/PositionCardSbold.tsx deleted file mode 100644 index 0948c6929..000000000 --- a/frontend/app/src/comps/Positions/PositionCardSbold.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import type { PositionSbold } from "@/src/types"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { css } from "@/styled-system/css"; -import { HFlex, IconEarn, TokenIcon } from "@liquity2/uikit"; -import { PositionCard } from "./PositionCard"; -import { CardRow, CardRows } from "./shared"; - -export function PositionCardSbold({ - bold, - sbold, -}: Pick< - PositionSbold, - | "bold" - | "sbold" ->) { - return ( - - sBOLD position - , - ]} - contextual={ -
- -
- } - main={{ - value: ( - - - - - ), - label: ( - - BOLD deposited - - ), - }} - secondary={ - - -
-
- APR -
-
- -
-
-
-
- 7d APR -
-
- -
-
- - } - /> - -
- sBOLD balance -
-
- - -
- - } - /> -
- } - /> - ); -} diff --git a/frontend/app/src/comps/Positions/Positions.tsx b/frontend/app/src/comps/Positions/Positions.tsx index bec21f93d..2502ebdb0 100644 --- a/frontend/app/src/comps/Positions/Positions.tsx +++ b/frontend/app/src/comps/Positions/Positions.tsx @@ -5,17 +5,14 @@ import { useBreakpointName } from "@/src/breakpoints"; import { ActionCard } from "@/src/comps/ActionCard/ActionCard"; import content from "@/src/content"; import { useEarnPositionsByAccount, useLoansByAccount } from "@/src/liquity-utils"; -import { useSboldPosition } from "@/src/sbold"; import { css } from "@/styled-system/css"; import { a, useSpring, useTransition } from "@react-spring/web"; -import * as dn from "dnum"; import { useEffect, useRef, useState } from "react"; import { match, P } from "ts-pattern"; import { NewPositionCard } from "./NewPositionCard"; import { PositionCard } from "./PositionCard"; import { PositionCardEarn } from "./PositionCardEarn"; import { PositionCardLoan } from "./PositionCardLoan"; -import { PositionCardSbold } from "./PositionCardSbold"; type Mode = "positions" | "loading" | "actions"; @@ -44,22 +41,17 @@ export function Positions({ }) { const loans = useLoansByAccount(address); const earnPositions = useEarnPositionsByAccount(address); - const sboldPosition = useSboldPosition(address); const isPositionsPending = Boolean( address && ( loans.isPending || earnPositions.isPending - || sboldPosition.isPending ), ); - const hasSboldPosition = sboldPosition.data && dn.gt(sboldPosition.data.sbold, 0); - const positions = isPositionsPending ? [] : [ ...(loans.data ?? []), ...(earnPositions.data ?? []), - ...(sboldPosition.data && hasSboldPosition ? [sboldPosition.data] : []), ]; let mode: Mode = address && positions && positions.length > 0 @@ -137,10 +129,6 @@ function PositionsGroup({ index, , ]) - .with({ type: "sbold" }, (p) => [ - index, - , - ]) .exhaustive() )) ?? [], ); diff --git a/frontend/app/src/comps/SboldInfo/SboldInfo.tsx b/frontend/app/src/comps/SboldInfo/SboldInfo.tsx deleted file mode 100644 index d21bf16a0..000000000 --- a/frontend/app/src/comps/SboldInfo/SboldInfo.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import type { Dnum } from "@/src/types"; - -import { TokenAmount } from "@/src/comps/Amount/TokenAmount"; -import { ValueUpdate } from "@/src/comps/ValueUpdate/ValueUpdate"; -import { css } from "@/styled-system/css"; -import { TokenIcon } from "@liquity2/uikit"; -import { a, useInView, useTransition } from "@react-spring/web"; - -const infoItems = [{ - icon: "sbold", - text: "sBOLD is an ERC-20 token", -}, { - icon: "managed", - text: "Managed by K3 Capital", -}, { - icon: "compounding", - text: "Auto-compounding", -}] as const; - -const iconComponents = { - sbold: () => , - managed: ManagedIcon, - compounding: CompoundingIcon, -} as const; - -export function SboldInfo({ - conversion, - loading, -}: { - conversion: { - mode: "deposit"; - boldAmount: Dnum; - sboldAmount: Dnum | null; - } | { - mode: "redeem"; - boldAmount: Dnum | null; - sboldAmount: Dnum; - }; - loading: boolean; -}) { - const [ref, inView] = useInView({ once: true }); - - const iconsTrail = useTransition( - infoItems.map((item) => ({ ...item, inView })), - { - keys: ({ text, inView }) => `${text}-${inView}`, - from: { - opacity: 0, - transform: ` - scale3d(0.2, 0.2, 1) - rotate3d(0, 0, 1, -180deg) - `, - }, - enter: { - opacity: 1, - transform: ` - scale3d(1, 1, 1) - rotate3d(0, 0, 1, 0deg) - `, - }, - trail: 100, - delay: 50, - config: { - mass: 1, - tension: 800, - friction: 60, - }, - }, - ); - - const bold = ( - - ); - - const sbold = ( - - ); - - return ( -
-
-
Conversion
-
- -
-
-
    - {iconsTrail((props, item, _, index) => { - const Icon = iconComponents[item.icon]; - return ( -
  • -
    - - - -
    -
    {item.text}
    -
  • - ); - })} -
-
- ); -} - -function ManagedIcon() { - return ( - - - - - - - ); -} - -function CompoundingIcon() { - return ( - - - - - - ); -} diff --git a/frontend/app/src/env.ts b/frontend/app/src/env.ts index c09d557ef..818bbc1e6 100644 --- a/frontend/app/src/env.ts +++ b/frontend/app/src/env.ts @@ -175,7 +175,6 @@ export const EnvSchema = v.pipe( LIQUITY_STATS_URL: v.optional(v.pipe(v.string(), v.url())), LIQUITY_GOVERNANCE_URL: v.optional(v.union([v.pipe(v.string(), v.url()), v.literal("")])), SAFE_API_URL: v.optional(v.union([v.pipe(v.string(), v.url()), v.literal("")])), - SBOLD: v.optional(v.union([vAddress(), v.literal("")])), SUBGRAPH_URL: v.pipe(v.string(), v.url()), VERCEL_ANALYTICS: v.optional(vEnvFlag(), "false"), WALLET_CONNECT_PROJECT_ID: v.pipe( @@ -329,7 +328,6 @@ const parsedEnv = v.safeParse(EnvSchema, { LIQUITY_STATS_URL: process.env.NEXT_PUBLIC_LIQUITY_STATS_URL, LIQUITY_GOVERNANCE_URL: process.env.NEXT_PUBLIC_LIQUITY_GOVERNANCE_URL, SAFE_API_URL: process.env.NEXT_PUBLIC_SAFE_API_URL, - SBOLD: process.env.NEXT_PUBLIC_SBOLD, SUBGRAPH_URL: process.env.NEXT_PUBLIC_SUBGRAPH_URL, VERCEL_ANALYTICS: process.env.NEXT_PUBLIC_VERCEL_ANALYTICS, WALLET_CONNECT_PROJECT_ID: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID, @@ -415,7 +413,6 @@ export const { LIQUITY_STATS_URL, LIQUITY_GOVERNANCE_URL, SAFE_API_URL, - SBOLD, SUBGRAPH_URL, VERCEL_ANALYTICS, WALLET_CONNECT_PROJECT_ID, diff --git a/frontend/app/src/sbold.ts b/frontend/app/src/sbold.ts deleted file mode 100644 index e8022258a..000000000 --- a/frontend/app/src/sbold.ts +++ /dev/null @@ -1,201 +0,0 @@ -import type { Address, Dnum, PositionSbold } from "@/src/types"; - -import { dnum18, DNUM_0 } from "@/src/dnum-utils"; -import { SBOLD } from "@/src/env"; -import { getBranch, getBranchesCount, useLiquityStats } from "@/src/liquity-utils"; -import { isBranchId } from "@/src/types"; -import { useQuery } from "@tanstack/react-query"; -import * as dn from "dnum"; -import { erc20Abi, parseAbi, zeroAddress } from "viem"; -import { useConfig as useWagmiConfig, useReadContracts } from "wagmi"; -import { readContract } from "wagmi/actions"; - -// if the fee is below this % of the deposit, we consider it negligible -const NEGLIGIBLE_FEE_THRESHOLD = 0.0001; // 0.01% - -export const SboldContract = { - abi: [ - ...erc20Abi, - ...parseAbi([ - "function calcFragments() view returns (uint256, uint256, uint256, uint256)", - "function convertToAssets(uint256 shares) view returns (uint256)", - "function deposit(uint256 assets, address receiver) returns (uint256)", - "function getSBoldRate() view returns (uint256)", - "function maxWithdraw(address owner) view returns (uint256)", - "function previewDeposit(uint256 assets) view returns (uint256)", - "function previewRedeem(uint256 shares) view returns (uint256)", - "function previewWithdraw(uint256 assets) view returns (uint256)", - "function redeem(uint256 shares, address receiver, address owner) returns (uint256)", - "function sps(uint256 index) view returns (address sp, uint256 weight)", - "function withdraw(uint256 assets, address receiver, address owner) returns (uint256)", - ]), - ] as const, - address: SBOLD || zeroAddress, -}; - -export function isSboldEnabled() { - return Boolean(SBOLD); -} - -export function useSboldPosition(address: Address | null) { - return useReadContracts({ - contracts: [{ - ...SboldContract, - functionName: "balanceOf", - args: [address ?? zeroAddress], - }, { - ...SboldContract, - functionName: "maxWithdraw", - args: [address ?? zeroAddress], - }], - query: { - enabled: Boolean(isSboldEnabled() && address), - select: ([balance, maxWithdraw]): PositionSbold => { - if (!address) { - throw new Error(); // should never happen (see enabled) - } - return { - type: "sbold", - bold: dnum18(maxWithdraw), - owner: address, - sbold: dnum18(balance), - }; - }, - initialData: [0n, 0n], - }, - allowFailure: false, - }); -} - -export function usePreviewDeposit(bold: Dnum | null) { - const config = useWagmiConfig(); - return useQuery({ - queryKey: ["sbold", "previewDeposit", String(bold?.[0])], - queryFn: async () => { - if (!bold || bold[0] === 0n) { - return null; - } - - const [bold_] = bold; - - const sboldFromDeposit = await readContract(config, { - ...SboldContract, - functionName: "previewDeposit", - args: [bold_], - }); - - const boldMinusFee = await readContract(config, { - ...SboldContract, - functionName: "convertToAssets", - args: [sboldFromDeposit], - }); - - const boldFee = dnum18(bold_ - boldMinusFee); - const sbold = dnum18(sboldFromDeposit); - const isFeeNegligible = dn.lt( - dn.div(boldFee, bold), - NEGLIGIBLE_FEE_THRESHOLD, - ); - - return { bold, boldFee, isFeeNegligible, sbold }; - }, - }); -} - -export function usePreviewRedeem(sbold: Dnum | null) { - const config = useWagmiConfig(); - return useQuery({ - queryKey: ["sbold", "previewRedeem", String(sbold?.[0])], - queryFn: async () => { - if (!sbold || sbold[0] === 0n) { - return null; - } - return dnum18( - await readContract(config, { - ...SboldContract, - functionName: "previewRedeem", - args: [sbold[0]], - }), - ); - }, - }); -} - -function calculateSboldApr(spData: Array<[apr: Dnum | null, weight: Dnum]>) { - let weightedAprSum = DNUM_0; - let totalWeight = DNUM_0; - for (const [apr, weight] of spData) { - if (!apr) continue; - weightedAprSum = dn.add(weightedAprSum, dn.mul(weight, apr)); - totalWeight = dn.add(totalWeight, weight); - } - return dn.eq(totalWeight, 0) - ? DNUM_0 - : dn.div(weightedAprSum, totalWeight); -} - -export function useSboldStats() { - const liquityStats = useLiquityStats(); - - type SpsCall = typeof SboldContract & { - functionName: "sps"; - args: [bigint]; - }; - - // max is 9 branches so this should be fine - type SpsCalls = [ - SpsCall, - SpsCall, - SpsCall, - SpsCall, - SpsCall, - SpsCall, - SpsCall, - SpsCall, - SpsCall, - ]; - - return useReadContracts({ - contracts: [ - { ...SboldContract, functionName: "totalSupply" }, - { ...SboldContract, functionName: "calcFragments" }, - ...(Array.from({ length: getBranchesCount() }, (_, index) => ({ - ...SboldContract, - functionName: "sps", - args: [BigInt(index)], - })) as SpsCalls), - ], - allowFailure: false, - query: { - enabled: isSboldEnabled() && liquityStats.isSuccess, - select: ([totalSupply_, [totalBold_], ...sps]) => { - const totalSupply = dnum18(totalSupply_); - const totalBold = dnum18(totalBold_); - - const sboldRate = totalSupply_ === 0n - ? DNUM_0 - : dn.div(totalBold, totalSupply); - - const spAprs = sps.map(([_, weight], index) => { - if (!isBranchId(index)) { - throw new Error(`Invalid branch index: ${index}`); - } - const branch = getBranch(index); - const statsBranch = liquityStats.data?.branch[branch.symbol]; - return { - apr: statsBranch?.spApyAvg1d ?? null, - apr7d: statsBranch?.spApyAvg7d ?? null, - weight: dn.div(dn.from(weight, 18), 100_00), // from basis points - }; - }); - return { - apr: calculateSboldApr(spAprs.map((sp) => [sp.apr, sp.weight])), - apr7d: calculateSboldApr(spAprs.map((sp) => [sp.apr7d, sp.weight])), - sboldRate, - totalBold, - weights: spAprs.map((sp) => sp.weight), - }; - }, - }, - }); -} diff --git a/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx b/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx index 4ea96bc81..cf73fc5f4 100644 --- a/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx +++ b/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx @@ -11,7 +11,7 @@ import { InterestRateField } from "@/src/comps/InterestRateField/InterestRateFie import { LinkTextButton } from "@/src/comps/LinkTextButton/LinkTextButton"; import { RedemptionInfo } from "@/src/comps/RedemptionInfo/RedemptionInfo"; import { Screen } from "@/src/comps/Screen/Screen"; -import { DEBT_SUGGESTIONS, ETH_MAX_RESERVE, MAX_COLLATERAL_DEPOSITS, MIN_DEBT } from "@/src/constants"; +import { DEBT_SUGGESTIONS, MAX_COLLATERAL_DEPOSITS, MIN_DEBT } from "@/src/constants"; import content from "@/src/content"; import { dnum18, dnumMax, dnumMin } from "@/src/dnum-utils"; import { useInputFieldValue } from "@/src/form-utils"; diff --git a/frontend/app/src/screens/EarnPoolScreen/SboldPoolScreen.tsx b/frontend/app/src/screens/EarnPoolScreen/SboldPoolScreen.tsx deleted file mode 100644 index 492e17283..000000000 --- a/frontend/app/src/screens/EarnPoolScreen/SboldPoolScreen.tsx +++ /dev/null @@ -1,417 +0,0 @@ -"use client"; - -import type { Dnum, PositionSbold } from "@/src/types"; - -import { useBreakpointName } from "@/src/breakpoints"; -import { Amount } from "@/src/comps/Amount/Amount"; -import { SboldPositionSummary } from "@/src/comps/EarnPositionSummary/SboldPositionSummary"; -import { Field } from "@/src/comps/Field/Field"; -import { FlowButton } from "@/src/comps/FlowButton/FlowButton"; -import { InputTokenBadge } from "@/src/comps/InputTokenBadge/InputTokenBadge"; -import { SboldInfo } from "@/src/comps/SboldInfo/SboldInfo"; -import { Screen } from "@/src/comps/Screen/Screen"; -import { ScreenCard } from "@/src/comps/Screen/ScreenCard"; -import { Spinner } from "@/src/comps/Spinner/Spinner"; -import content from "@/src/content"; -import { DNUM_0, dnumMax } from "@/src/dnum-utils"; -import { parseInputFloat } from "@/src/form-utils"; -import { fmtnum } from "@/src/formatting"; -import { useWait } from "@/src/react-utils"; -import { isSboldEnabled, usePreviewDeposit, usePreviewRedeem, useSboldPosition, useSboldStats } from "@/src/sbold"; -import { infoTooltipProps } from "@/src/uikit-utils"; -import { useAccount, useBalance } from "@/src/wagmi-utils"; -import { css } from "@/styled-system/css"; -import { HFlex, IconEarn, InfoTooltip, InputField, Tabs, TextButton, TokenIcon } from "@liquity2/uikit"; -import { a, useTransition } from "@react-spring/web"; -import * as dn from "dnum"; -import { notFound } from "next/navigation"; -import { useEffect, useState } from "react"; -import { match } from "ts-pattern"; - -export function SboldPoolScreen() { - if (!isSboldEnabled()) { - notFound(); - } - - // scroll to top - useEffect(() => { - window.scroll({ - top: 0, - left: 0, - behavior: "smooth", - }); - }, []); - - const account = useAccount(); - const sboldPosition = useSboldPosition(account.address ?? null); - const sboldStats = useSboldStats(); - const ready = useWait(500); - const breakpointName = useBreakpointName(); - - const loadingState = !ready || sboldPosition.isLoading || sboldStats.isLoading ? "loading" : "success"; - - const loadingTransition = useTransition(loadingState, { - from: { opacity: 0 }, - enter: { opacity: 1 }, - leave: { opacity: 0 }, - config: { - mass: 1, - tension: 2000, - friction: 120, - }, - }); - - const boldBalance = useBalance(account.address, "BOLD"); - - return ( - () - .with("success", () => "ready") - .with("loading", () => "loading") - .exhaustive()} - finalHeight={breakpointName === "large" ? 140 : 194} - > - {loadingState === "success" - ? ( - - ) - : ( - <> -
-
- -
- - Fetching sBOLD data… - - -
- - )} - - } - > - {loadingTransition((style, item) => ( - item === "success" && ( - - - - ) - ))} -
- ); -} - -type ValueUpdateMode = "deposit" | "redeem"; - -export function PanelUpdate({ - boldBalance, - sboldPosition, - sboldStats, -}: { - boldBalance: Dnum; - sboldPosition: PositionSbold | null; - sboldStats: ReturnType; -}) { - const account = useAccount(); - - const [mode, setMode] = useState("deposit"); - const [value, setValue] = useState(""); - const [focused, setFocused] = useState(false); - - const parsedValue = parseInputFloat(value); - - const hasAnyBoldDeposited = dn.gt(sboldPosition?.bold ?? DNUM_0, 0); - const depositDifference = dn.mul( - parsedValue ?? DNUM_0, - mode === "redeem" ? -1 : 1, - ); - - const value_ = (focused || !parsedValue || dn.lte(parsedValue, 0)) - ? value - : `${fmtnum(parsedValue, "full")}`; - - const updatedDeposit = dnumMax( - dn.add(sboldPosition?.bold ?? DNUM_0, depositDifference), - DNUM_0, - ); - - const totalDepositedBoldUpdated = dn.add( - sboldStats.data?.totalBold ?? DNUM_0, - depositDifference, - ); - - const updatedPoolShare = dn.gt(totalDepositedBoldUpdated, 0) - ? dn.div(updatedDeposit, totalDepositedBoldUpdated) - : DNUM_0; - - const insufficientBalance = mode === "deposit" - && parsedValue - && dn.lt(boldBalance, parsedValue); - - const withdrawAboveDeposit = mode === "redeem" - && parsedValue - && dn.gt(parsedValue, sboldPosition?.bold ?? DNUM_0); - - const depositPreview = usePreviewDeposit( - mode === "deposit" && parsedValue ? parsedValue : null, - ); - - const redeemPreview = usePreviewRedeem( - mode === "redeem" && parsedValue ? parsedValue : null, - ); - - const allowSubmit = account.isConnected - && parsedValue - && dn.gt(parsedValue, 0) - && !insufficientBalance - && !withdrawAboveDeposit - && !depositPreview.isFetching - && !redeemPreview.isFetching; - - return ( -
-
- } - label={mode === "deposit" ? "BOLD" : "sBOLD"} - /> - } - id="input-deposit-change" - label={{ - start: mode === "redeem" - ? "Redeem sBOLD" - : "Deposit BOLD", - end: ( - { - setMode(index === 1 ? "redeem" : "deposit"); - setValue(""); - if (origin !== "keyboard") { - event.preventDefault(); - (event.target as HTMLElement).focus(); - } - }} - selected={mode === "redeem" ? 1 : 0} - /> - ), - }} - labelHeight={32} - onFocus={() => setFocused(true)} - onChange={setValue} - onBlur={() => setFocused(false)} - value={value_} - placeholder="0.00" - secondary={{ - start: ( - -
{content.earnScreen.depositPanel.shareLabel}
-
- -
- -
- ), - end: mode === "deposit" - ? ( - dn.gt(boldBalance, 0) && ( - { - setValue(dn.toString(boldBalance)); - }} - /> - ) - ) - : sboldPosition?.sbold && dn.gt(sboldPosition.sbold, 0) && ( - { - setValue(dn.toString(sboldPosition.sbold)); - }} - /> - ), - }} - /> - } - /> - - -
- -
- { - if (redeemPreview.isError) { - throw redeemPreview.error; - } - if (depositPreview.isError) { - throw depositPreview.error; - } - if ( - !account.address - || !parsedValue - || (mode === "deposit" && (depositPreview.isFetching || !depositPreview.data)) - || (mode === "redeem" && (redeemPreview.isFetching || !redeemPreview.data)) - ) { - return null; - } - - const prevSboldPosition = sboldPosition ?? { - type: "sbold" as const, - owner: account.address, - bold: DNUM_0, - sbold: DNUM_0, - }; - - const [newBoldDeposit, newSboldBalance] = mode === "deposit" - ? [ - dn.add(prevSboldPosition.bold, parsedValue), - dn.add(prevSboldPosition.sbold, depositPreview.data?.sbold ?? DNUM_0), - ] - : [ - dnumMax(dn.sub(prevSboldPosition.bold, redeemPreview.data ?? DNUM_0), DNUM_0), - dn.sub(prevSboldPosition.sbold, parsedValue), - ]; - - const newSboldPosition = { - ...prevSboldPosition, - bold: newBoldDeposit, - sbold: newSboldBalance, - }; - - const depositFee = mode === "deposit" - && depositPreview.data?.isFeeNegligible === false - ? depositPreview.data.boldFee - : DNUM_0; - - if (mode === "redeem") { - return { - flowId: "sboldRedeem", - backLink: ["/earn/sbold", "Back to editing"], - successLink: ["/earn/sbold", "Go to the sBOLD Pool"], - successMessage: "The sBOLD has been redeemed successfully.", - sboldPosition: newSboldPosition, - prevSboldPosition, - }; - } - - return { - flowId: "sboldDeposit", - backLink: ["/earn/sbold", "Back to editing"], - successLink: ["/earn/sbold", "Go to the sBOLD Pool"], - successMessage: "The deposit has been processed successfully.", - depositFee, - sboldPosition: newSboldPosition, - prevSboldPosition, - }; - }} - /> -
-
- ); -} diff --git a/frontend/app/src/screens/EarnPoolsListScreen/EarnPoolsListScreen.tsx b/frontend/app/src/screens/EarnPoolsListScreen/EarnPoolsListScreen.tsx index db4497622..2e92932d2 100644 --- a/frontend/app/src/screens/EarnPoolsListScreen/EarnPoolsListScreen.tsx +++ b/frontend/app/src/screens/EarnPoolsListScreen/EarnPoolsListScreen.tsx @@ -3,18 +3,16 @@ import type { BranchId } from "@/src/types"; import { EarnPositionSummary } from "@/src/comps/EarnPositionSummary/EarnPositionSummary"; -import { SboldPositionSummary } from "@/src/comps/EarnPositionSummary/SboldPositionSummary"; import { LinkTextButton } from "@/src/comps/LinkTextButton/LinkTextButton"; import { Screen } from "@/src/comps/Screen/Screen"; import content from "@/src/content"; import { getBranches, useEarnPosition } from "@/src/liquity-utils"; -import { isSboldEnabled, useSboldPosition } from "@/src/sbold"; import { useAccount } from "@/src/wagmi-utils"; import { css } from "@/styled-system/css"; import { TokenIcon } from "@liquity2/uikit"; import { a, useTransition } from "@react-spring/web"; -type PoolId = BranchId | "sbold"; +type PoolId = BranchId; export function EarnPoolsListScreen() { const branches = getBranches(); @@ -22,10 +20,6 @@ export function EarnPoolsListScreen() { const pools: PoolId[] = branches.map((b) => b.branchId); - if (isSboldEnabled()) { - pools.push("sbold"); - } - const poolsTransition = useTransition(pools, { from: { opacity: 0, transform: "scale(1.1) translateY(64px)" }, enter: { opacity: 1, transform: "scale(1) translateY(0px)" }, @@ -84,9 +78,7 @@ export function EarnPoolsListScreen() { > {poolsTransition((style, poolId) => ( - {poolId === "sbold" - ? - : } + ))} @@ -109,14 +101,3 @@ function EarnPool({ /> ); } - -function SboldPool() { - const account = useAccount(); - const sboldPosition = useSboldPosition(account.address ?? null); - return ( - - ); -} diff --git a/frontend/app/src/screens/HomeScreen/HomeScreen.tsx b/frontend/app/src/screens/HomeScreen/HomeScreen.tsx index 747ac09c2..e6396d391 100644 --- a/frontend/app/src/screens/HomeScreen/HomeScreen.tsx +++ b/frontend/app/src/screens/HomeScreen/HomeScreen.tsx @@ -18,7 +18,6 @@ import { useBranchDebt, useEarnPool, } from "@/src/liquity-utils"; -import { useSboldStats } from "@/src/sbold"; import { useAccount } from "@/src/wagmi-utils"; import { css } from "@/styled-system/css"; import { IconBorrow, IconEarn, TokenIcon } from "@liquity2/uikit"; @@ -268,12 +267,11 @@ function EarnRewardsRow({ symbol, }: { compact: boolean; - symbol: CollateralSymbol | "SBOLD"; + symbol: CollateralSymbol; }) { - const branch = symbol === "SBOLD" ? null : getBranch(symbol); + const branch = getBranch(symbol); const token = getToken(symbol); const earnPool = useEarnPool(branch?.id ?? null); - const sboldStats = useSboldStats(); return ( @@ -285,25 +283,21 @@ function EarnRewardsRow({ })} > - {symbol === "SBOLD" ? "sBOLD by K3 Capital" : token?.name} + {token?.name} @@ -311,9 +305,7 @@ function EarnRewardsRow({ fallback="…" format="compact" prefix="$" - value={symbol === "SBOLD" - ? sboldStats.data?.totalBold - : earnPool.data?.totalDeposited} + value={earnPool.data?.totalDeposited} /> {!compact && ( @@ -332,15 +324,7 @@ function EarnRewardsRow({ Earn - {symbol === "SBOLD" - ? ( -
- ) - : } +
} diff --git a/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx b/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx index 397bc10d9..b66251997 100644 --- a/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx +++ b/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx @@ -8,7 +8,7 @@ import { Field } from "@/src/comps/Field/Field"; import { FlowButton } from "@/src/comps/FlowButton/FlowButton"; import { InputTokenBadge } from "@/src/comps/InputTokenBadge/InputTokenBadge"; import { UpdateBox } from "@/src/comps/UpdateBox/UpdateBox"; -import { ETH_MAX_RESERVE, MIN_DEBT } from "@/src/constants"; +import { MIN_DEBT } from "@/src/constants"; import { dnum18, dnumMax, dnumMin } from "@/src/dnum-utils"; import { useInputFieldValue } from "@/src/form-utils"; import { fmtnum, formatRisk } from "@/src/formatting"; diff --git a/frontend/app/src/services/TransactionFlow.tsx b/frontend/app/src/services/TransactionFlow.tsx index 8a7b50dd2..c8caa5e56 100644 --- a/frontend/app/src/services/TransactionFlow.tsx +++ b/frontend/app/src/services/TransactionFlow.tsx @@ -31,8 +31,6 @@ import { earnClaimRewards, type EarnClaimRewardsRequest } from "@/src/tx-flows/e import { earnUpdate, type EarnUpdateRequest } from "@/src/tx-flows/earnUpdate"; import { openBorrowPosition, type OpenBorrowPositionRequest } from "@/src/tx-flows/openBorrowPosition"; import { redeemCollateral, type RedeemCollateralRequest } from "@/src/tx-flows/redeemCollateral"; -import { sboldDeposit, type SboldDepositRequest } from "@/src/tx-flows/sboldDeposit"; -import { sboldRedeem, type SboldRedeemRequest } from "@/src/tx-flows/sboldRedeem"; import { updateBorrowPosition, type UpdateBorrowPositionRequest } from "@/src/tx-flows/updateBorrowPosition"; import { updateLoanInterestRate, type UpdateLoanInterestRateRequest } from "@/src/tx-flows/updateLoanInterestRate"; @@ -44,8 +42,6 @@ export type FlowRequestMap = { "earnUpdate": EarnUpdateRequest; "openBorrowPosition": OpenBorrowPositionRequest; "redeemCollateral": RedeemCollateralRequest; - "sboldDeposit": SboldDepositRequest; - "sboldRedeem": SboldRedeemRequest; "updateBorrowPosition": UpdateBorrowPositionRequest; "updateLoanInterestRate": UpdateLoanInterestRateRequest; }; @@ -58,10 +54,7 @@ const FlowIdSchema = v.union([ v.literal("earnUpdate"), v.literal("openBorrowPosition"), v.literal("redeemCollateral"), - v.literal("sboldDeposit"), - v.literal("sboldRedeem"), v.literal("updateBorrowPosition"), - v.literal("updateLeveragePosition"), v.literal("updateLoanInterestRate"), ]); @@ -73,8 +66,6 @@ export const flows: FlowsMap = { earnUpdate, openBorrowPosition, redeemCollateral, - sboldDeposit, - sboldRedeem, updateBorrowPosition, updateLoanInterestRate, }; diff --git a/frontend/app/src/tx-flows/openBorrowPosition.tsx b/frontend/app/src/tx-flows/openBorrowPosition.tsx index 8701818e1..018850f8a 100644 --- a/frontend/app/src/tx-flows/openBorrowPosition.tsx +++ b/frontend/app/src/tx-flows/openBorrowPosition.tsx @@ -20,9 +20,8 @@ import { getIndexedTroveById } from "@/src/subgraph"; import { sleep } from "@/src/utils"; import { vAddress, vBranchId, vDnum } from "@/src/valibot-utils"; import { css } from "@/styled-system/css"; -import { ADDRESS_ZERO, COLLATERALS, InfoTooltip, TOKENS_BY_SYMBOL } from "@liquity2/uikit"; +import { InfoTooltip } from "@liquity2/uikit"; import * as dn from "dnum"; -import { match } from "ts-pattern"; import * as v from "valibot"; import { maxUint256, parseEventLogs } from "viem"; import { readContract } from "wagmi/actions"; diff --git a/frontend/app/src/tx-flows/sboldDeposit.tsx b/frontend/app/src/tx-flows/sboldDeposit.tsx deleted file mode 100644 index fe8e405ff..000000000 --- a/frontend/app/src/tx-flows/sboldDeposit.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { SboldPositionSummary } from "@/src/comps/EarnPositionSummary/SboldPositionSummary"; -import { SboldContract } from "@/src/sbold"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { vDnum, vPositionSbold } from "@/src/valibot-utils"; -import { css } from "@/styled-system/css"; -import { InfoTooltip } from "@liquity2/uikit"; -import * as dn from "dnum"; -import * as v from "valibot"; -import { maxUint256 } from "viem"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "sboldDeposit", - { - depositFee: vDnum(), - prevSboldPosition: vPositionSbold(), - sboldPosition: vPositionSbold(), - }, -); - -export type SboldDepositRequest = v.InferOutput; - -export const sboldDeposit: FlowDeclaration = { - title: "Review & Send Transaction", - - Summary({ request }) { - const { prevSboldPosition, sboldPosition } = request; - return ( - - ); - }, - - Details({ request }) { - const { sboldPosition, prevSboldPosition, depositFee } = request; - const depositChange = dn.sub( - sboldPosition.bold, - prevSboldPosition.bold, - ); - - const sboldPosition_ = { ...sboldPosition }; - if (dn.lt(depositFee, 0.0001)) { - sboldPosition_.bold = dn.add( - sboldPosition.bold, - depositFee, - ); - } - - return ( - <> - , - dn.gt(depositFee, 0) && ( -
- - - This fee is charged when you deposit BOLD for sBOLD shares, and has been deducted from the deposit - amount. - -
- ), - ]} - /> - , -
- Will be slightly less due to yield accrual - - The final amount of sBOLD you receive will be slightly less than this, due to the yield that keeps - accruing until the transaction is confirmed. - -
, - ]} - /> - - ); - }, - - steps: { - approveBold: { - name: () => "Approve BOLD", - Status: (props) => ( - - ), - async commit(ctx) { - const depositChange = dn.sub( - ctx.request.sboldPosition.bold, - ctx.request.prevSboldPosition.bold, - ); - return ctx.writeContract({ - ...ctx.contracts.BoldToken, - functionName: "approve", - args: [ - SboldContract.address, - ctx.preferredApproveMethod === "approve-infinite" - ? maxUint256 // infinite approval - : dn.abs(depositChange)[0], // exact amount - ], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe, false); - }, - }, - deposit: { - name: () => "Deposit", - Status: TransactionStatus, - async commit({ request, writeContract, account }) { - const { sboldPosition, prevSboldPosition } = request; - const boldChange = sboldPosition.bold[0] - prevSboldPosition.bold[0]; - return writeContract({ - ...SboldContract, - functionName: "deposit", - args: [boldChange, account], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe, false); - }, - }, - }, - - async getSteps(ctx) { - const { prevSboldPosition, sboldPosition } = ctx.request; - - const depositChange = sboldPosition.bold[0] - prevSboldPosition.bold[0]; - - const allowance = await ctx.readContract({ - ...ctx.contracts.BoldToken, - functionName: "allowance", - args: [ctx.account, SboldContract.address], - }); - - return allowance >= depositChange - ? ["deposit"] - : ["approveBold", "deposit"]; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; diff --git a/frontend/app/src/tx-flows/sboldRedeem.tsx b/frontend/app/src/tx-flows/sboldRedeem.tsx deleted file mode 100644 index 0d7f4c6ff..000000000 --- a/frontend/app/src/tx-flows/sboldRedeem.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; - -import { Amount } from "@/src/comps/Amount/Amount"; -import { SboldPositionSummary } from "@/src/comps/EarnPositionSummary/SboldPositionSummary"; -import { SboldContract } from "@/src/sbold"; -import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; -import { TransactionStatus } from "@/src/screens/TransactionsScreen/TransactionStatus"; -import { vPositionSbold } from "@/src/valibot-utils"; -import * as dn from "dnum"; -import * as v from "valibot"; -import { createRequestSchema, verifyTransaction } from "./shared"; - -const RequestSchema = createRequestSchema( - "sboldRedeem", - { - prevSboldPosition: vPositionSbold(), - sboldPosition: vPositionSbold(), - }, -); - -export type SboldRedeemRequest = v.InferOutput; - -export const sboldRedeem: FlowDeclaration = { - title: "Review & Send Transaction", - - Summary({ request }) { - const { prevSboldPosition, sboldPosition } = request; - return ( - - ); - }, - - Details({ request }) { - const { sboldPosition, prevSboldPosition } = request; - const redeemAmount = dn.sub(sboldPosition.sbold, prevSboldPosition.sbold); - const boldAmount = dn.sub(sboldPosition.bold, prevSboldPosition.bold); - return ( - <> - , - ]} - /> - , - ]} - /> - - ); - }, - - steps: { - redeem: { - name: () => "Redeem", - Status: TransactionStatus, - async commit({ account, request, writeContract }) { - const { sboldPosition, prevSboldPosition } = request; - const redeemAmount = (sboldPosition.sbold[0] - prevSboldPosition.sbold[0]) * -1n; - if (redeemAmount <= 0n) { - throw new Error("Invalid redeem amount"); - } - return writeContract({ - ...SboldContract, - functionName: "redeem", - args: [redeemAmount, account, account], - }); - }, - async verify(ctx, hash) { - await verifyTransaction(ctx.wagmiConfig, hash, ctx.isSafe, false); - }, - }, - }, - - async getSteps() { - return ["redeem"]; - }, - - parseRequest(request) { - return v.parse(RequestSchema, request); - }, -}; diff --git a/frontend/app/src/types.ts b/frontend/app/src/types.ts index 493bfb138..b147b34dd 100644 --- a/frontend/app/src/types.ts +++ b/frontend/app/src/types.ts @@ -115,17 +115,9 @@ export type PositionEarn = { }; }; -export type PositionSbold = { - type: "sbold"; - bold: Dnum; - owner: Address; - sbold: Dnum; -}; - export type Position = | PositionEarn - | PositionLoan - | PositionSbold; + | PositionLoan; export type Delegate = { address: Address; diff --git a/frontend/app/src/valibot-utils.ts b/frontend/app/src/valibot-utils.ts index c4119e0df..3f887f106 100644 --- a/frontend/app/src/valibot-utils.ts +++ b/frontend/app/src/valibot-utils.ts @@ -237,15 +237,6 @@ export function vPositionEarn() { }); } -export function vPositionSbold() { - return v.object({ - type: v.literal("sbold"), - owner: vAddress(), - bold: vDnum(), - sbold: vDnum(), - }); -} - export function vVote() { return v.union([ v.literal("for"), @@ -275,8 +266,6 @@ export function vTokenSymbol() { return v.union([ vCollateralSymbol(), v.literal("BOLD"), - v.literal("LEGACY_BOLD"), - v.literal("SBOLD"), ]); } diff --git a/frontend/app/src/wagmi-utils.ts b/frontend/app/src/wagmi-utils.ts index 5b03f2d87..51bfd6538 100644 --- a/frontend/app/src/wagmi-utils.ts +++ b/frontend/app/src/wagmi-utils.ts @@ -10,7 +10,7 @@ import { useQuery } from "@tanstack/react-query"; import { useModal as useConnectKitModal } from "connectkit"; import { match } from "ts-pattern"; import { erc20Abi } from "viem"; -import { useAccount as useWagmiAccount, useBalance as useWagmiBalance, useEnsName, useReadContracts } from "wagmi"; +import { useAccount as useWagmiAccount, useEnsName, useReadContracts } from "wagmi"; export function useBalance( address: Address | undefined, @@ -48,7 +48,6 @@ export function useBalances( }; }); - const ethTokens = tokenConfigs.filter((config) => config.isEth); const erc20Tokens = tokenConfigs.filter((config) => !config.isEth && config.tokenAddress); const erc20Balances = useReadContracts({ @@ -63,13 +62,6 @@ export function useBalances( }, }); - const ethBalance = useWagmiBalance({ - address, - query: { - enabled: Boolean(address && ethTokens.length > 0), - }, - }); - // combine results return tokens.reduce((result, token) => { const erc20Index = erc20Tokens.findIndex((config) => config.token === token); diff --git a/frontend/uikit/src/tokens.ts b/frontend/uikit/src/tokens.ts index ad05f79cd..fb53e1e84 100644 --- a/frontend/uikit/src/tokens.ts +++ b/frontend/uikit/src/tokens.ts @@ -1,6 +1,5 @@ import tokenBold from "./token-icons/bold.svg"; import tokenEth from "./token-icons/eth.svg"; -import tokenSbold from "./token-icons/sbold.svg"; // any external token, without a known symbol export type ExternalToken = { @@ -18,7 +17,6 @@ export type Token = ExternalToken & { export type TokenSymbol = | "BOLD" - | "SBOLD" | "WANKR" | "USN"; @@ -55,12 +53,6 @@ export const BOLD: Token = { symbol: "BOLD" as const, } as const; -export const SBOLD: Token = { - icon: tokenSbold, - name: "sBOLD", - symbol: "SBOLD" as const, -} as const; - export const WANKR: CollateralToken = { collateralRatio: 1.1, icon: tokenEth, @@ -84,5 +76,4 @@ export const TOKENS_BY_SYMBOL = { BOLD, WANKR, USN, - SBOLD, } as const; From 6ef8d679079d39285f66a43ad53092ed8da7bfb0 Mon Sep 17 00:00:00 2001 From: Artur Korotenko Date: Fri, 5 Sep 2025 17:21:14 +0500 Subject: [PATCH 06/19] fix: show new position card --- frontend/app/src/comps/Positions/Positions.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/app/src/comps/Positions/Positions.tsx b/frontend/app/src/comps/Positions/Positions.tsx index 2502ebdb0..b20fce39d 100644 --- a/frontend/app/src/comps/Positions/Positions.tsx +++ b/frontend/app/src/comps/Positions/Positions.tsx @@ -25,7 +25,7 @@ const actionCards = [ export function Positions({ address, columns, - showNewPositionCard = false, + showNewPositionCard = true, title = (mode) => ( mode === "loading" ? " " @@ -113,10 +113,6 @@ function PositionsGroup({ .with("positions", () => { let cards: Array<[number, ReactNode]> = []; - if (showNewPositionCard) { - cards.push([positions.length ?? -1, ]); - } - cards = cards.concat( positions.map((position, index) => ( match(position) From 26a1d2508b853915e5ee8e9b98654be677462d5c Mon Sep 17 00:00:00 2001 From: Artur Korotenko Date: Fri, 5 Sep 2025 17:23:56 +0500 Subject: [PATCH 07/19] fix: show new position card --- frontend/app/src/comps/Positions/Positions.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/app/src/comps/Positions/Positions.tsx b/frontend/app/src/comps/Positions/Positions.tsx index b20fce39d..7b30190b7 100644 --- a/frontend/app/src/comps/Positions/Positions.tsx +++ b/frontend/app/src/comps/Positions/Positions.tsx @@ -9,7 +9,6 @@ import { css } from "@/styled-system/css"; import { a, useSpring, useTransition } from "@react-spring/web"; import { useEffect, useRef, useState } from "react"; import { match, P } from "ts-pattern"; -import { NewPositionCard } from "./NewPositionCard"; import { PositionCard } from "./PositionCard"; import { PositionCardEarn } from "./PositionCardEarn"; import { PositionCardLoan } from "./PositionCardLoan"; From abe3d9b8d025b4c58b4eb479f0210bdfae05d232 Mon Sep 17 00:00:00 2001 From: tenshou Date: Fri, 5 Sep 2025 19:41:13 +0700 Subject: [PATCH 08/19] feat: ALIGN-70 add new theme --- frontend/app/public/favicon.svg | 18 +- frontend/app/src/comps/About/About.tsx | 4 +- .../app/src/comps/AppLayout/AboutButton.tsx | 2 +- .../app/src/comps/AppLayout/AppLayout.tsx | 3 +- frontend/app/src/comps/AppLayout/TopBar.tsx | 2 +- frontend/app/src/comps/Logo/Logo.tsx | 58 ++-- frontend/app/src/content.tsx | 6 +- frontend/uikit/src/Theme/Theme.tsx | 269 ++++++++++-------- frontend/uikit/src/index.css | 4 + 9 files changed, 198 insertions(+), 168 deletions(-) diff --git a/frontend/app/public/favicon.svg b/frontend/app/public/favicon.svg index ad0efd403..426e51dd8 100644 --- a/frontend/app/public/favicon.svg +++ b/frontend/app/public/favicon.svg @@ -1,16 +1,4 @@ - - - - - - - - - - - - - - - + + + diff --git a/frontend/app/src/comps/About/About.tsx b/frontend/app/src/comps/About/About.tsx index 1fd3c8037..d683cd0a4 100644 --- a/frontend/app/src/comps/About/About.tsx +++ b/frontend/app/src/comps/About/About.tsx @@ -289,7 +289,7 @@ export function About({ children }: { children: ReactNode }) { - Liquity V2 contracts ({env.CONTRACTS_COMMIT_URL + AlignMint contracts ({env.CONTRACTS_COMMIT_URL ? ( -
Liquity V2 App
+
AlignMint App
); } diff --git a/frontend/app/src/comps/AppLayout/AboutButton.tsx b/frontend/app/src/comps/AppLayout/AboutButton.tsx index 70faeca8e..ff4c086fc 100644 --- a/frontend/app/src/comps/AppLayout/AboutButton.tsx +++ b/frontend/app/src/comps/AppLayout/AboutButton.tsx @@ -11,7 +11,7 @@ export function AboutButton({ return ( { about.openModal(); onClick?.(); diff --git a/frontend/app/src/comps/AppLayout/AppLayout.tsx b/frontend/app/src/comps/AppLayout/AppLayout.tsx index 8bfa489d6..3e4ba5fc4 100644 --- a/frontend/app/src/comps/AppLayout/AppLayout.tsx +++ b/frontend/app/src/comps/AppLayout/AppLayout.tsx @@ -24,7 +24,7 @@ export function AppLayout({ minHeight: "100vh", minWidth: "fit-content", height: "100%", - background: "background", + background: 'linear-gradient(180deg, #011837 0%, #03459D 134.73%)', })} >
- - - - - + + + + + + + + + + + +
); diff --git a/frontend/app/src/content.tsx b/frontend/app/src/content.tsx index 6b9523110..ff47c865f 100644 --- a/frontend/app/src/content.tsx +++ b/frontend/app/src/content.tsx @@ -6,9 +6,9 @@ import { css } from "@/styled-system/css"; export default { // Used in the top bar and other places - appName: "Liquity V2", + appName: "AlignMint", appDescription: ` - Liquity V2 is a new borrowing protocol that lets users + AlignMint is a new borrowing protocol that lets users deposit LSTs as collateral and mint the stablecoin BOLD. `, appUrl: typeof window === "undefined" @@ -225,7 +225,7 @@ export default { learnMore: { url: "https://docs.liquity.org/v2-documentation/friendly-fork-program", label: "Learn more", - title: "Learn more about the Liquity V2 Friendly Fork Program", + title: "Learn more about the AlignMint Friendly Fork Program", }, }, }, diff --git a/frontend/uikit/src/Theme/Theme.tsx b/frontend/uikit/src/Theme/Theme.tsx index 7ebcee57b..03fb8198c 100644 --- a/frontend/uikit/src/Theme/Theme.tsx +++ b/frontend/uikit/src/Theme/Theme.tsx @@ -1,13 +1,9 @@ "use client"; import type { ReactNode } from "react"; - import { createContext, useContext, useState } from "react"; -// The Liquity V2 base color palette, meant -// to be used by themes rather than directly. export const colors = { - // Blue "blue:50": "#F0F3FE", "blue:100": "#DEE4FB", "blue:200": "#C4D0F9", @@ -20,7 +16,6 @@ export const colors = { "blue:900": "#272A81", "blue:950": "#1C1D4F", - // Gray "gray:50": "#F5F6F8", "gray:100": "#EDEFF2", "gray:200": "#DDE0E8", @@ -33,7 +28,6 @@ export const colors = { "gray:900": "#50525F", "gray:950": "#2F3037", - // Yellow "yellow:50": "#FDFBE9", "yellow:100": "#FCF8C5", "yellow:200": "#FAEE8E", @@ -46,7 +40,6 @@ export const colors = { "yellow:900": "#6D4016", "yellow:950": "#402108", - // Green "green:50": "#F1FCF2", "green:100": "#DEFAE4", "green:200": "#BFF3CA", @@ -59,7 +52,6 @@ export const colors = { "green:900": "#194E27", "green:950": "#082B12", - // Red "red:50": "#FEF5F2", "red:100": "#FFE7E1", "red:200": "#FFD5C9", @@ -72,18 +64,40 @@ export const colors = { "red:900": "#82301A", "red:950": "#471608", - // brown "brown:50": "#F8F6F4", - // desert "desert:50": "#FAF9F7", "desert:100": "#EFECE5", "desert:950": "#2C231E", - // White "white": "#FFFFFF", - // Brand colors + "primary:green": "#88FD9D", + "primary:hover": "#2EFF54", + "primary:disabled": "#88FD9D80", + "primary:20": "#88FD9D33", + + "navy": "#011837", + + "white:100": "#FFFFFF", + "white:70": "#FFFFFFB3", + "white:40": "#FFFFFF66", + "white:20": "#FFFFFF33", + "white:10": "#FFFFFF1A", + + "error": "#FF6560", + "error:20": "#FF656033", + + "primary:grad:start": "#88FD9D", + "primary:grad:end": "#2EFF54", + + "risk:1": "#2EFF54", + "risk:2": "#88FD9D", + "risk:3": "#FFFFFFB3", + "risk:4": "#FF6560", + "risk:5": "#FF6560", + + // Legacy Liquity V2 brand colors for backward compatibility "brand:blue": "#405AE5", "brand:lightBlue": "#6D8AED", "brand:darkBlue": "#121B44", @@ -92,91 +106,99 @@ export const colors = { "brand:cyan": "#95CBF3", "brand:coral": "#FB7C59", "brand:brown": "#DBB79B", -}; - -// The light theme, which is the only theme for now. These -// colors are meant to be used by components via useTheme(), -// so that the theme can be changed at runtime. +} as const; -// Some notes about naming conventions: -// - "xContent" is the color used over a "x" background (text, icons or outlines). -// - "xHint" is the color used to hint that "x" is interactive (generally on hover). -// - "xActive" is the color used to indicate that "x" is being interacted with (generally on press). -// - "xSurface" is the color used for the surface of "x" (generally the background). export const lightTheme = { name: "light" as const, colors: { - accent: "blue:500", - accentActive: "blue:600", - accentContent: "white", - accentHint: "blue:400", - background: "white", - backgroundActive: "gray:50", - border: "gray:200", - borderSoft: "gray:100", - content: "gray:950", - contentAlt: "gray:600", - contentAlt2: "gray:500", - controlBorder: "gray:300", - controlBorderStrong: "blue:950", - controlSurface: "white", - controlSurfaceAlt: "gray:200", - hint: "brown:50", - infoSurface: "desert:50", - infoSurfaceBorder: "desert:100", - infoSurfaceContent: "desert:950", - dimmed: "gray:400", - fieldBorder: "gray:100", - fieldBorderFocused: "gray:300", - fieldSurface: "gray:50", - focused: "blue:500", - focusedSurface: "blue:50", - focusedSurfaceActive: "blue:100", - strongSurface: "blue:950", - strongSurfaceContent: "white", - strongSurfaceContentAlt: "gray:500", - strongSurfaceContentAlt2: "gray:100", - position: "#2E2E3D", - positionContent: "white", - positionContentAlt: "gray:500", - interactive: "blue:950", - negative: "red:500", - negativeStrong: "red:600", - negativeActive: "red:600", - negativeContent: "white", - negativeHint: "red:400", - negativeSurface: "red:50", - negativeSurfaceBorder: "red:100", - negativeSurfaceContent: "red:900", - negativeSurfaceContentAlt: "red:400", - negativeInfoSurface: "red:50", - negativeInfoSurfaceBorder: "red:200", - negativeInfoSurfaceContent: "red:950", - negativeInfoSurfaceContentAlt: "gray:600", - positive: "green:500", - positiveAlt: "green:400", - positiveActive: "green:600", - positiveContent: "white", - positiveHint: "green:400", - secondary: "blue:50", - secondaryActive: "blue:200", - secondaryContent: "blue:500", - secondaryHint: "blue:100", - selected: "blue:500", - separator: "gray:50", - surface: "white", - tableBorder: "gray:100", - warning: "yellow:400", - warningAlt: "yellow:300", - warningAltContent: "blue:950", - disabledBorder: "gray:200", - disabledContent: "gray:500", - disabledSurface: "gray:50", + accent: "primary:green", + accentActive: "primary:hover", + accentHint: "primary:hover", + accentContent: "navy", + + background: "navy", + backgroundActive: "white:10", + + content: "white:100", + contentAlt: "white:70", + contentAlt2: "white:40", + + surface: "white:10", + controlSurface: "white:10", + controlSurfaceAlt: "white:20", + strongSurface: "white:20", + strongSurfaceContent: "white:100", + strongSurfaceContentAlt: "white:70", + strongSurfaceContentAlt2: "white:40", + + border: "white:20", + borderSoft: "white:10", + controlBorder: "white:20", + controlBorderStrong: "white:40", + tableBorder: "white:20", + separator: "white:10", + + fieldSurface: "white:10", + fieldBorder: "white:10", + fieldBorderFocused: "white:40", + focused: "primary:green", + focusedSurface: "white:20", + focusedSurfaceActive: "white:40", + + hint: "white:20", + infoSurface: "white:10", + infoSurfaceBorder: "white:20", + infoSurfaceContent: "white:100", + + dimmed: "white:40", + interactive: "primary:green", + + positive: "primary:green", + positiveAlt: "primary:hover", + positiveActive: "primary:hover", + positiveHint: "primary:hover", + positiveContent: "navy", + + negative: "error", + negativeStrong: "error", + negativeActive: "error", + negativeHint: "error", + negativeContent: "white:100", + negativeSurface: "white:10", + negativeSurfaceBorder: "white:20", + negativeSurfaceContent: "error", + negativeSurfaceContentAlt: "white:70", + negativeInfoSurface: "white:10", + negativeInfoSurfaceBorder: "white:20", + negativeInfoSurfaceContent: "error", + negativeInfoSurfaceContentAlt: "white:70", + + secondary: "white:10", + secondaryActive: "white:20", + secondaryContent: "white:100", + secondaryHint: "white:20", + selected: "primary:green", + + disabledSurface: "white:10", + disabledBorder: "white:10", + disabledContent: "white:40", + + warning: "error", + warningAlt: "error", + warningAltContent: "white:100", + + brandPrimary: "primary:green", + brandPrimaryHover: "primary:hover", + brandNavy: "navy", + brandError: "error", + brandWhite: "white:100", + + // Legacy brand colors from Liquity V2 theme for backward compatibility brandBlue: "brand:blue", - brandBlueContent: "white", + brandBlueContent: "white:100", brandBlueContentAlt: "blue:50", brandDarkBlue: "brand:darkBlue", - brandDarkBlueContent: "white", + brandDarkBlueContent: "white:100", brandDarkBlueContentAlt: "gray:50", brandLightBlue: "brand:lightBlue", brandGolden: "brand:golden", @@ -185,47 +207,54 @@ export const lightTheme = { brandGreen: "brand:green", brandGreenContent: "green:950", brandGreenContentAlt: "green:800", + brandCyan: "brand:cyan", + brandCoral: "brand:coral", + brandBrown: "brand:brown", - // colors are resolved so we can animate them - riskGradient1: "#63D77D", // green:400 - riskGradient2: "#B8E549", - riskGradient3: "#F1C91E", // yellow:400 - riskGradient4: "#FFA12B", - riskGradient5: "#FB7C59", // red:400 + primaryGradientStart: "primary:grad:start", + primaryGradientEnd: "primary:grad:end", - riskGradientDimmed1: "red:100", - riskGradientDimmed2: "yellow:100", - riskGradientDimmed3: "green:100", + position: "navy", + positionContent: "white:100", + positionContentAlt: "white:40", + riskGradientDimmed1: "error:20", + riskGradientDimmed2: "white:20", + riskGradientDimmed3: "primary:20", + + // Legacy risk gradients from Liquity V2 theme for backward compatibility + riskGradient1: "green:400", + riskGradient2: "green:300", + riskGradient3: "yellow:400", + riskGradient4: "red:400", + riskGradient5: "red:500", + + // Legacy loading gradients from Liquity V2 theme for backward compatibility loadingGradient1: "blue:50", loadingGradient2: "blue:100", loadingGradientContent: "blue:400", - - // not used yet - brandCyan: "brand:cyan", - brandCoral: "brand:coral", - brandBrown: "brand:brown", - } satisfies Record, + } satisfies Record, } as const; export type ThemeDescriptor = { - name: "light"; // will be "light" | "dark" once dark mode is added - colors: typeof lightTheme.colors; // lightTheme acts as a reference for types + name: "light"; + colors: typeof lightTheme.colors; }; export type ThemeColorName = keyof ThemeDescriptor["colors"]; -export function themeColor(theme: ThemeDescriptor, name: ThemeColorName) { - const themeColor = theme.colors[name]; - - if (themeColor.startsWith("#")) { - return themeColor; - } +export const colorsWithDashKeys = Object.fromEntries( + Object.entries(colors).map(([k, v]) => [k.replaceAll(":", "-"), v as string]) +); - if (themeColor in colors) { - return colors[themeColor as keyof typeof colors]; - } +export const pandaColorTokens = Object.fromEntries( + Object.entries(colorsWithDashKeys).map(([k, v]) => [k, { value: v }]) +); - throw new Error(`Color ${themeColor} not found in theme`); +export function themeColor(theme: ThemeDescriptor, name: ThemeColorName) { + const ref = theme.colors[name]; + const hex = colors[ref]; + if (!hex) throw new Error(`Color ${String(ref)} not found in theme palette`); + return hex; } const ThemeContext = createContext({ @@ -242,11 +271,7 @@ export function useTheme() { }; } -export function Theme({ - children, -}: { - children: ReactNode; -}) { +export function Theme({ children }: { children: ReactNode }) { const [theme, setTheme] = useState(lightTheme); return ( diff --git a/frontend/uikit/src/index.css b/frontend/uikit/src/index.css index e27a23b77..a5084985b 100644 --- a/frontend/uikit/src/index.css +++ b/frontend/uikit/src/index.css @@ -1 +1,5 @@ @layer reset, base, tokens, recipes, utilities; + +.bdr_8 { + border-radius: 28px !important; +} From cc4bf8d008c71a25a2a10932bd77191e1815ad7e Mon Sep 17 00:00:00 2001 From: tenshou Date: Fri, 5 Sep 2025 20:34:29 +0700 Subject: [PATCH 09/19] feat: ALIGN-70 add new theme --- .../app/src/comps/ActionCard/ActionCard.tsx | 16 +- .../app/src/comps/ActionCard/ActionIcon.tsx | 224 ++---------------- frontend/app/src/comps/AppLayout/Menu.tsx | 6 +- frontend/app/src/comps/AppLayout/MenuItem.tsx | 1 - frontend/app/src/comps/AppLayout/TopBar.tsx | 1 - .../src/comps/Positions/NewPositionCard.tsx | 4 +- .../app/src/screens/HomeScreen/HomeTable.tsx | 1 + frontend/uikit/src/Dropdown/Dropdown.tsx | 6 +- frontend/uikit/src/InputField/InputField.tsx | 3 +- frontend/uikit/src/Theme/Theme.tsx | 6 +- frontend/uikit/src/icons/IconBorrow.tsx | 36 ++- frontend/uikit/src/icons/IconEarn.tsx | 40 +++- frontend/uikit/src/icons/svg/borrow.svg | 13 +- frontend/uikit/src/icons/svg/earn.svg | 13 +- 14 files changed, 133 insertions(+), 237 deletions(-) diff --git a/frontend/app/src/comps/ActionCard/ActionCard.tsx b/frontend/app/src/comps/ActionCard/ActionCard.tsx index bd9ec1f05..be9b5f3d1 100644 --- a/frontend/app/src/comps/ActionCard/ActionCard.tsx +++ b/frontend/app/src/comps/ActionCard/ActionCard.tsx @@ -36,7 +36,8 @@ export function ActionCard({ const { description, path, title, colors } = match(type) .with("borrow", () => ({ colors: { - background: token("colors.brandDarkBlue"), + title: token("colors.brandPrimary"), + background: 'linear-gradient(93deg, rgba(255, 255, 255, 0.06) 1.81%, rgba(255, 255, 255, 0.14) 97.23%)', foreground: token("colors.brandDarkBlueContent"), foregroundAlt: token("colors.brandDarkBlueContentAlt"), }, @@ -46,7 +47,8 @@ export function ActionCard({ })) .with("multiply", () => ({ colors: { - background: token("colors.brandGreen"), + title: token("colors.brandPrimary"), + background: 'linear-gradient(93deg, rgba(255, 255, 255, 0.06) 1.81%, rgba(255, 255, 255, 0.14) 97.23%)', foreground: token("colors.brandGreenContent"), foregroundAlt: token("colors.brandGreenContentAlt"), }, @@ -56,7 +58,8 @@ export function ActionCard({ })) .with("earn", () => ({ colors: { - background: token("colors.brandBlue"), + title: token("colors.brandPrimary"), + background: 'linear-gradient(93deg, rgba(255, 255, 255, 0.06) 1.81%, rgba(255, 255, 255, 0.14) 97.23%)', foreground: token("colors.brandBlueContent"), foregroundAlt: token("colors.brandBlueContentAlt"), }, @@ -108,15 +111,12 @@ export function ActionCard({ ...hintSpring, }} > -

{title}

+

{title}

{description}

@@ -129,7 +129,7 @@ export function ActionCard({
diff --git a/frontend/app/src/comps/ActionCard/ActionIcon.tsx b/frontend/app/src/comps/ActionCard/ActionIcon.tsx index bb7c699cf..fe7fc262d 100644 --- a/frontend/app/src/comps/ActionCard/ActionIcon.tsx +++ b/frontend/app/src/comps/ActionCard/ActionIcon.tsx @@ -1,8 +1,3 @@ -import type { ReactNode } from "react"; - -import { a, useSpring } from "@react-spring/web"; -import { match } from "ts-pattern"; - type IconProps = { background: string; foreground: string; @@ -15,11 +10,8 @@ export const springConfig = { friction: 40, }; -export function ActionIcon({ - colors, - iconType, - state, -}: { +// @ts-ignore +export function ActionIcon(props: { colors: { background: string; foreground: string; @@ -27,199 +19,27 @@ export function ActionIcon({ iconType: "borrow" | "multiply" | "earn" | "stake"; state: IconProps["state"]; }) { - const Icon = match(iconType) - .with("borrow", () => ActionIconBorrow) - .with("multiply", () => ActionIconLeverage) - .with("earn", () => ActionIconEarn) - .with("stake", () => ActionIconStake) - .exhaustive(); - - return ( - - ); -} - -function ActionIconBorrow({ foreground, state }: IconProps) { - const { t1, t2 } = useSpring({ - t1: state === "active" - ? "translate(36 0) scale(1.1)" - : "translate(0 0) scale(1)", - t2: state === "active" - ? "translate(-36 0) scale(1.1)" - : "translate(0 0) scale(1)", - config: springConfig, - }); - return ( - - - - - ); -} - -function ActionIconLeverage({ foreground, state }: IconProps) { - const { t1, t2 } = useSpring({ - t1: state === "active" - ? "translate(0 56) scale(0)" - : "translate(0 36) scale(2)", - t2: state === "active" - ? "translate(-6 -6) scale(6.8)" - : "translate(20 0) scale(3.6)", - config: springConfig, - }); - - return ( - - - - - ); -} - -function ActionIconEarn({ foreground, background, state }: IconProps) { - const { squareT, squareRadius, circleT } = useSpring({ - squareT: state === "active" ? "scale(1.4)" : "scale(1)", - squareRadius: state === "active" ? "28" : "0", - circleT: state === "active" ? "scale(0.8)" : "scale(1)", - config: springConfig, - }); - return ( - - - - - ); -} - -export function ActionIconStake({ foreground, state }: IconProps) { - const active = state === "active"; - - // style transform - const tr = (x: number, y: number, w: number = 1, h: number = 1) => ` - translate(${x * 56 / 3}px, ${y * 56 / 3}px) - scale(${w}, ${h}) - `; - - const { sq1, sq2, sq3, sq4 } = useSpring({ - sq1: active ? tr(2, 0, 1, 3) : tr(1, 0), - sq2: active ? tr(0, 0, 3, 1) : tr(2, 1), - sq3: active ? tr(0, 0, 1, 3) : tr(1, 2), - sq4: active ? tr(0, 2, 3, 1) : tr(0, 1), - config: springConfig, - }); - - // square - // const { sq1, sq2, sq3, sq4 } = useSpring({ - // sq1: active ? tr(0, 0) : tr(1, 0), - // sq2: active ? tr(2, 0) : tr(2, 1), - // sq3: active ? tr(2, 2) : tr(1, 2), - // sq4: active ? tr(0, 2) : tr(0, 1), - // config: springConfig, - // }); - - // arrow compact - // const { sq1, sq2, sq3, sq4 } = useSpring({ - // sq1: active ? pos(0.5, 0.5) : pos(1, 0), - // sq2: active ? pos(1.5, 0.5) : pos(2, 1), - // sq3: active ? pos(1.5, 1.5) : pos(1, 2), - // sq4: active ? pos(0, 2) : pos(0, 1), - // config: springConfig, - // }); - - // arrow wide - // const { sq1, sq2, sq3, sq4 } = useSpring({ - // sq1: active ? tr(1, 0, 1) : tr(1, 0, 1), - // sq2: active ? tr(2, 0, 1) : tr(2, 1, 1), - // sq3: active ? tr(2, 1, 1) : tr(1, 2, 1), - // sq4: active ? tr(0, 2, 1) : tr(0, 1, 1), - // config: springConfig, - // }); - - return ( - - {[sq1, sq2, sq3, sq4].map((transform, index) => ( - - ))} - - ); -} -function IconBase({ children }: { children: ReactNode }) { return ( - - {children} - + + + + + + + + + + + + ); } diff --git a/frontend/app/src/comps/AppLayout/Menu.tsx b/frontend/app/src/comps/AppLayout/Menu.tsx index 05ed0ff08..8ec64c527 100644 --- a/frontend/app/src/comps/AppLayout/Menu.tsx +++ b/frontend/app/src/comps/AppLayout/Menu.tsx @@ -32,11 +32,11 @@ export function Menu({ position: "relative", zIndex: 2, display: "flex", - gap: 8, + gap: 2, height: "100%", })} > - {menuItems.map(([label, href, Icon]) => { + {menuItems.map(([label, href]) => { const selected = href === "/" ? pathname === "/" : pathname.startsWith(href); return (
  • @@ -61,7 +61,7 @@ export function Menu({ }} > } + icon={null} label={label} selected={selected} /> diff --git a/frontend/app/src/comps/AppLayout/MenuItem.tsx b/frontend/app/src/comps/AppLayout/MenuItem.tsx index 73036510c..290d2eb19 100644 --- a/frontend/app/src/comps/AppLayout/MenuItem.tsx +++ b/frontend/app/src/comps/AppLayout/MenuItem.tsx @@ -18,7 +18,6 @@ export function MenuItem({ className={css({ display: "flex", alignItems: "center", - gap: 12, width: "100%", height: "100%", color: "content", diff --git a/frontend/app/src/comps/AppLayout/TopBar.tsx b/frontend/app/src/comps/AppLayout/TopBar.tsx index f2d48278f..db62c5c4d 100644 --- a/frontend/app/src/comps/AppLayout/TopBar.tsx +++ b/frontend/app/src/comps/AppLayout/TopBar.tsx @@ -97,7 +97,6 @@ export function TopBar() { whiteSpace: "nowrap", })} > -
    {content.appName}
    {DEPLOYMENT_FLAVOR && (
    diff --git a/frontend/app/src/screens/HomeScreen/HomeTable.tsx b/frontend/app/src/screens/HomeScreen/HomeTable.tsx index 031ccb2e4..2618f23e9 100644 --- a/frontend/app/src/screens/HomeScreen/HomeTable.tsx +++ b/frontend/app/src/screens/HomeScreen/HomeTable.tsx @@ -46,6 +46,7 @@ export function HomeTable({ display: "flex", alignItems: "flex-start", justifyContent: "space-between", + color: 'primary:green', gap: 8, fontSize: { base: 16, diff --git a/frontend/uikit/src/Dropdown/Dropdown.tsx b/frontend/uikit/src/Dropdown/Dropdown.tsx index 496609920..1611cbce8 100644 --- a/frontend/uikit/src/Dropdown/Dropdown.tsx +++ b/frontend/uikit/src/Dropdown/Dropdown.tsx @@ -242,9 +242,9 @@ export function Dropdown({ padding: "0 10px 0 16px", height: "100%", whiteSpace: "nowrap", - borderWidth: "1px 1px 0 1px", + borderWidth: "2px", borderStyle: "solid", - borderColor: "#F5F6F8", + borderColor: "token(colors.brandGreen)", boxShadow: ` 0 2px 2px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(18, 27, 68, 0.05), @@ -329,7 +329,7 @@ export function Dropdown({ display: "flex", flexDirection: "column", gap: 12, - background: "controlSurface", + background: "#011837", borderRadius: 20, border: "1px solid token(colors.border)", boxShadow: ` diff --git a/frontend/uikit/src/InputField/InputField.tsx b/frontend/uikit/src/InputField/InputField.tsx index 6d0191502..f06496250 100644 --- a/frontend/uikit/src/InputField/InputField.tsx +++ b/frontend/uikit/src/InputField/InputField.tsx @@ -376,7 +376,8 @@ export function InputFieldBadge({ height: 40, padding: "0 16px", paddingLeft: icon ? 8 : 16, - background: "#FFF", + background: "rgba(255, 255, 255, 0.20)", + border: '2px solid rgba(136, 253, 157, 0.50)', borderRadius: 20, userSelect: "none", }} diff --git a/frontend/uikit/src/Theme/Theme.tsx b/frontend/uikit/src/Theme/Theme.tsx index 03fb8198c..417671a71 100644 --- a/frontend/uikit/src/Theme/Theme.tsx +++ b/frontend/uikit/src/Theme/Theme.tsx @@ -4,10 +4,10 @@ import type { ReactNode } from "react"; import { createContext, useContext, useState } from "react"; export const colors = { - "blue:50": "#F0F3FE", - "blue:100": "#DEE4FB", + "blue:50": "rgba(255, 255, 255, 0.10)", + "blue:100": "rgba(255, 255, 255, 0.10)", "blue:200": "#C4D0F9", - "blue:300": "#9CB1F4", + "blue:300": "#1C1D4F", "blue:400": "#6D8AED", "blue:500": "#405AE5", "blue:600": "#3544DB", diff --git a/frontend/uikit/src/icons/IconBorrow.tsx b/frontend/uikit/src/icons/IconBorrow.tsx index e9e0e59b5..8ccd348ab 100644 --- a/frontend/uikit/src/icons/IconBorrow.tsx +++ b/frontend/uikit/src/icons/IconBorrow.tsx @@ -7,9 +7,39 @@ export function IconBorrow({ size?: number; }) { return ( - - - + + + + + + + + + + + ); } diff --git a/frontend/uikit/src/icons/IconEarn.tsx b/frontend/uikit/src/icons/IconEarn.tsx index b289c1470..0f5135a5d 100644 --- a/frontend/uikit/src/icons/IconEarn.tsx +++ b/frontend/uikit/src/icons/IconEarn.tsx @@ -7,13 +7,39 @@ export function IconEarn({ size?: number; }) { return ( - - + + + + + + + + + + + ); } diff --git a/frontend/uikit/src/icons/svg/borrow.svg b/frontend/uikit/src/icons/svg/borrow.svg index 360b2fad6..0bc05ccbf 100644 --- a/frontend/uikit/src/icons/svg/borrow.svg +++ b/frontend/uikit/src/icons/svg/borrow.svg @@ -1 +1,12 @@ - \ No newline at end of file + + + + + + + + + + + + diff --git a/frontend/uikit/src/icons/svg/earn.svg b/frontend/uikit/src/icons/svg/earn.svg index 03988ffb1..0bc05ccbf 100644 --- a/frontend/uikit/src/icons/svg/earn.svg +++ b/frontend/uikit/src/icons/svg/earn.svg @@ -1 +1,12 @@ - \ No newline at end of file + + + + + + + + + + + + From 9533928c46bedb99a81dea4dec56d529c4ad003b Mon Sep 17 00:00:00 2001 From: tenshou Date: Fri, 5 Sep 2025 20:37:03 +0700 Subject: [PATCH 10/19] feat: ALIGN-70 fix logo --- frontend/app/src/comps/AppLayout/MenuDrawer.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/frontend/app/src/comps/AppLayout/MenuDrawer.tsx b/frontend/app/src/comps/AppLayout/MenuDrawer.tsx index 61ddd7b2e..ad1de44e4 100644 --- a/frontend/app/src/comps/AppLayout/MenuDrawer.tsx +++ b/frontend/app/src/comps/AppLayout/MenuDrawer.tsx @@ -132,16 +132,7 @@ function MenuDrawer({ flexShrink: 0, })} > - -
    -
    - {content.appName} +
    Date: Fri, 5 Sep 2025 20:37:15 +0700 Subject: [PATCH 11/19] feat: ALIGN-70 fix logo --- frontend/app/src/comps/AppLayout/MenuDrawer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/app/src/comps/AppLayout/MenuDrawer.tsx b/frontend/app/src/comps/AppLayout/MenuDrawer.tsx index ad1de44e4..efd570784 100644 --- a/frontend/app/src/comps/AppLayout/MenuDrawer.tsx +++ b/frontend/app/src/comps/AppLayout/MenuDrawer.tsx @@ -2,7 +2,6 @@ import type { ReactNode } from "react"; import type { MenuItem } from "./Menu"; import { Logo } from "@/src/comps/Logo/Logo"; -import content from "@/src/content"; import { css } from "@/styled-system/css"; import { token } from "@/styled-system/tokens"; import { Root } from "@liquity2/uikit"; From 77be33bad704b24280e192009aa51e74778b8185 Mon Sep 17 00:00:00 2001 From: tenshou Date: Tue, 9 Sep 2025 17:06:53 +0700 Subject: [PATCH 12/19] feat: ALIGN-70 fix token --- .../app/src/comps/ActionCard/ActionCard.tsx | 11 +- .../app/src/comps/ActionCard/ActionIcon.tsx | 187 +++++++++++++++--- .../app/src/comps/AppLayout/BottomBar.tsx | 8 +- .../EarnPositionSummary.tsx | 8 +- .../comps/InterestRateField/DelegateBox.tsx | 2 +- .../InterestRateField/InterestRateField.tsx | 4 +- .../src/comps/LeverageField/LeverageField.tsx | 2 +- .../comps/Positions/PositionCardBorrow.tsx | 4 +- frontend/app/src/content.tsx | 46 ++--- .../screens/AccountScreen/AccountScreen.tsx | 2 +- .../src/screens/BorrowScreen/BorrowScreen.tsx | 8 +- .../EarnPoolScreen/PanelClaimRewards.tsx | 6 +- .../EarnPoolScreen/PanelUpdateDeposit.tsx | 12 +- .../app/src/screens/HomeScreen/HomeScreen.tsx | 4 +- .../app/src/screens/LoanScreen/LoanScreen.tsx | 2 +- .../src/screens/LoanScreen/LoanScreenCard.tsx | 6 +- .../screens/LoanScreen/PanelClosePosition.tsx | 8 +- .../screens/LoanScreen/PanelInterestRate.tsx | 8 +- .../LoanScreen/PanelUpdateBorrowPosition.tsx | 14 +- .../src/screens/RedeemScreen/RedeemScreen.tsx | 4 +- .../components/LoadingCard/LoadingCard.tsx | 2 +- .../components/TotalDebt/TotalDebt.tsx | 4 +- frontend/app/src/tx-flows/claimBribes.tsx | 2 +- .../app/src/tx-flows/closeLoanPosition.tsx | 2 +- frontend/app/src/tx-flows/earnUpdate.tsx | 2 +- .../app/src/tx-flows/openBorrowPosition.tsx | 10 +- .../app/src/tx-flows/updateBorrowPosition.tsx | 2 +- .../src/tx-flows/updateLoanInterestRate.tsx | 2 +- frontend/uikit/src/Dropdown/Dropdown.tsx | 2 +- frontend/uikit/src/InputField/InputField.tsx | 4 +- frontend/uikit/src/token-icons/bold.svg | 13 +- frontend/uikit/src/tokens.ts | 3 +- 32 files changed, 278 insertions(+), 116 deletions(-) diff --git a/frontend/app/src/comps/ActionCard/ActionCard.tsx b/frontend/app/src/comps/ActionCard/ActionCard.tsx index be9b5f3d1..26fa2d1d6 100644 --- a/frontend/app/src/comps/ActionCard/ActionCard.tsx +++ b/frontend/app/src/comps/ActionCard/ActionCard.tsx @@ -96,11 +96,14 @@ export function ActionCard({ gap: 16, width: "100%", padding: "20px 24px", - borderRadius: 8, + borderRadius: 28, + border: '2px solid var(--colors-table-border)', + _groupFocusVisible: { outline: "2px solid token(colors.focused)", outlineOffset: 2, }, + _groupHover: { transform: "scale(1.05)", }, @@ -111,11 +114,15 @@ export function ActionCard({ ...hintSpring, }} > -

    {title}

    +

    {title}

    {description} diff --git a/frontend/app/src/comps/ActionCard/ActionIcon.tsx b/frontend/app/src/comps/ActionCard/ActionIcon.tsx index fe7fc262d..5bb03da53 100644 --- a/frontend/app/src/comps/ActionCard/ActionIcon.tsx +++ b/frontend/app/src/comps/ActionCard/ActionIcon.tsx @@ -1,3 +1,8 @@ +import type { ReactNode } from "react"; + +import { a, useSpring } from "@react-spring/web"; +import { match } from "ts-pattern"; + type IconProps = { background: string; foreground: string; @@ -10,8 +15,11 @@ export const springConfig = { friction: 40, }; -// @ts-ignore -export function ActionIcon(props: { +export function ActionIcon({ + colors, + iconType, + state, +}: { colors: { background: string; foreground: string; @@ -19,27 +27,162 @@ export function ActionIcon(props: { iconType: "borrow" | "multiply" | "earn" | "stake"; state: IconProps["state"]; }) { + const Icon = match(iconType) + .with("borrow", () => ActionIconBorrow) + .with("multiply", () => ActionIconLeverage) + .with("earn", () => ActionIconEarn) + .with("stake", () => ActionIconStake) + .exhaustive(); + + return ( + + ); +} + +function ActionIconBorrow(_: IconProps) { + return ( + + + + + + + + ); +} + +function ActionIconLeverage({ foreground, state }: IconProps) { + const { t1, t2 } = useSpring({ + t1: state === 'active' + ? 'translate(0 56) scale(0)' + : 'translate(0 36) scale(2)', + t2: state === 'active' + ? 'translate(-6 -6) scale(6.8)' + : 'translate(20 0) scale(3.6)', + config: springConfig, + }); + + return ( + + + + + ); +} + +function ActionIconEarn(_: IconProps) { + return ( + + + + + + + + + + + ); +} + +export function ActionIconStake({ foreground, state }: IconProps) { + const active = state === "active"; + + // style transform + const tr = (x: number, y: number, w: number = 1, h: number = 1) => ` + translate(${x * 56 / 3}px, ${y * 56 / 3}px) + scale(${w}, ${h}) + `; + + const { sq1, sq2, sq3, sq4 } = useSpring({ + sq1: active ? tr(2, 0, 1, 3) : tr(1, 0), + sq2: active ? tr(0, 0, 3, 1) : tr(2, 1), + sq3: active ? tr(0, 0, 1, 3) : tr(1, 2), + sq4: active ? tr(0, 2, 3, 1) : tr(0, 1), + config: springConfig, + }); + + // square + // const { sq1, sq2, sq3, sq4 } = useSpring({ + // sq1: active ? tr(0, 0) : tr(1, 0), + // sq2: active ? tr(2, 0) : tr(2, 1), + // sq3: active ? tr(2, 2) : tr(1, 2), + // sq4: active ? tr(0, 2) : tr(0, 1), + // config: springConfig, + // }); + + // arrow compact + // const { sq1, sq2, sq3, sq4 } = useSpring({ + // sq1: active ? pos(0.5, 0.5) : pos(1, 0), + // sq2: active ? pos(1.5, 0.5) : pos(2, 1), + // sq3: active ? pos(1.5, 1.5) : pos(1, 2), + // sq4: active ? pos(0, 2) : pos(0, 1), + // config: springConfig, + // }); + + // arrow wide + // const { sq1, sq2, sq3, sq4 } = useSpring({ + // sq1: active ? tr(1, 0, 1) : tr(1, 0, 1), + // sq2: active ? tr(2, 0, 1) : tr(2, 1, 1), + // sq3: active ? tr(2, 1, 1) : tr(1, 2, 1), + // sq4: active ? tr(0, 2, 1) : tr(0, 1, 1), + // config: springConfig, + // }); + + return ( + + {[sq1, sq2, sq3, sq4].map((transform, index) => ( + + ))} + + ); +} +function IconBase({ children }: { children: ReactNode }) { return ( - - - - - - - - - - - - + + {children} + ); } diff --git a/frontend/app/src/comps/AppLayout/BottomBar.tsx b/frontend/app/src/comps/AppLayout/BottomBar.tsx index f78855e6a..9319f931d 100644 --- a/frontend/app/src/comps/AppLayout/BottomBar.tsx +++ b/frontend/app/src/comps/AppLayout/BottomBar.tsx @@ -14,7 +14,7 @@ import { blo } from "blo"; import Image from "next/image"; import { AboutButton } from "./AboutButton"; -const DISPLAYED_PRICES = ["BOLD", "WANKR"] as const; +const DISPLAYED_PRICES = ["MINT", "WANKR"] as const; const ENABLE_REDEEM = false; export function BottomBar() { @@ -103,7 +103,7 @@ export function BottomBar() {

    )} @@ -150,7 +150,7 @@ export function BottomBar() { whiteSpace: "nowrap", })} > - Redeem BOLD + Redeem MINT
    } className={css({ diff --git a/frontend/app/src/comps/EarnPositionSummary/EarnPositionSummary.tsx b/frontend/app/src/comps/EarnPositionSummary/EarnPositionSummary.tsx index fa00bfc4b..110487332 100644 --- a/frontend/app/src/comps/EarnPositionSummary/EarnPositionSummary.tsx +++ b/frontend/app/src/comps/EarnPositionSummary/EarnPositionSummary.tsx @@ -146,7 +146,7 @@ export function EarnPositionSummary({ />
  • - Total amount of BOLD deposited in this stability pool. + Total amount of MINT deposited in this stability pool. } @@ -157,7 +157,7 @@ export function EarnPositionSummary({ <>
    {prevEarnPosition && (
    {boldInterestPerYear && (mode === "manual" || delegate !== null) ? fmtnum(boldInterestPerYear, breakpoint === "small" ? "compact" : "2z") - : "−"} BOLD / year + : "−"} MINT / year
    @@ -330,7 +330,7 @@ export const InterestRateField = memo( (mode === "manual" || delegate !== null) ? fmtnum(bracket?.debtInFront, "compact") : "−" - } BOLD`} + } MINT`} className={css({ overflow: "hidden", whiteSpace: "nowrap", diff --git a/frontend/app/src/comps/LeverageField/LeverageField.tsx b/frontend/app/src/comps/LeverageField/LeverageField.tsx index 25a282c2a..df19eb487 100644 --- a/frontend/app/src/comps/LeverageField/LeverageField.tsx +++ b/frontend/app/src/comps/LeverageField/LeverageField.tsx @@ -91,7 +91,7 @@ export function LeverageField({ > {fmtnum(debt)} - {" BOLD"} + {" MINT"} )} diff --git a/frontend/app/src/comps/Positions/PositionCardBorrow.tsx b/frontend/app/src/comps/Positions/PositionCardBorrow.tsx index fbc9cc594..070f4fbc5 100644 --- a/frontend/app/src/comps/Positions/PositionCardBorrow.tsx +++ b/frontend/app/src/comps/Positions/PositionCardBorrow.tsx @@ -53,7 +53,7 @@ export function PositionCardBorrow({ const title = token ? [ `Loan ID: ${shortenTroveId(troveId)}…`, - `Debt: ${fmtnum(debt, "full")} BOLD`, + `Debt: ${fmtnum(debt, "full")} MINT`, `Collateral: ${fmtnum(deposit, "full")} ${token.name}`, `Interest rate: ${fmtnum(interestRate, "pctfull")}%`, ] @@ -73,7 +73,7 @@ export function PositionCardBorrow({ color: "positionContent", })} > -
    BOLD loan
    +
    MINT loan
    {statusTag} } diff --git a/frontend/app/src/content.tsx b/frontend/app/src/content.tsx index ff47c865f..28ddc1382 100644 --- a/frontend/app/src/content.tsx +++ b/frontend/app/src/content.tsx @@ -9,7 +9,7 @@ export default { appName: "AlignMint", appDescription: ` AlignMint is a new borrowing protocol that lets users - deposit LSTs as collateral and mint the stablecoin BOLD. + deposit LSTs as collateral and mint the stablecoin MINT. `, appUrl: typeof window === "undefined" ? "https://www.liquity.org/" @@ -43,20 +43,20 @@ export default { loanRedemptionRisk: [ "Redemption risk", <> - Users paying the lowest interest rate can get redeemed, if the price of BOLD falls below $1. By raising your + Users paying the lowest interest rate can get redeemed, if the price of MINT falls below $1. By raising your interest rate, you reduce this risk. , ], loanLtv: [ "Loan-to-value ratio", <> - The ratio between the amount of BOLD borrowed and the deposited collateral (in USD). + The ratio between the amount of MINT borrowed and the deposited collateral (in USD). , ], loanMaxLtv: [ "Maximum Loan-To-Value (LTV) Ratio", <> - The maximum ratio between the USD value of a loan (in BOLD) and the collateral backing it. The LTV will + The maximum ratio between the USD value of a loan (in MINT) and the collateral backing it. The LTV will fluctuate as the price of the collateral changes. To decrease the LTV add more colateral or reduce debt. , ], @@ -74,7 +74,7 @@ export default { interestRateBoldPerYear: [ "Interest rate", <> - The annualized interest amount in BOLD for the selected interest rate. The accumulated interest is added to the + The annualized interest amount in MINT for the selected interest rate. The accumulated interest is added to the loan. , ], @@ -89,7 +89,7 @@ export default { heading: "Your collateral and debt are reduced by the same value.", body: ( <> - When BOLD trades for under $1, anyone can redeem positions to get BOLD back at $1. Positions with the lowest + When MINT trades for under $1, anyone can redeem positions to get MINT back at $1. Positions with the lowest interest rate get redeemed first. ), @@ -105,14 +105,14 @@ export default { title: "Redemptions in a nutshell", subtitle: ( <> - Redemptions help maintain BOLD’s peg in a decentralized way. If a user is redeemed, their collateral and debt + Redemptions help maintain MINT’s peg in a decentralized way. If a user is redeemed, their collateral and debt are reduced equally, resulting in no net loss. ), infoItems: [ { icon: "bold", - text: "Redemptions occur when BOLD drops below $1.", + text: "Redemptions occur when MINT drops below $1.", }, { icon: "redemption", @@ -201,20 +201,20 @@ export default { actions: { borrow: { title: "Borrow", - description: "Mint BOLD against your collateral at whatever interest rate you want", + description: "Mint MINT against your collateral at whatever interest rate you want", }, multiply: { title: "Multiply", description: "Increase your exposure to ANKR and its staking yield with a single click", }, earn: { - title: "Earn with BOLD", - description: "Deposit BOLD to earn protocol revenues and liquidation proceeds", + title: "Earn with MINT", + description: "Deposit MINT to earn protocol revenues and liquidation proceeds", }, }, earnTable: { - title: "Earn rewards with BOLD", - subtitle: "Earn BOLD & (staked) ANKR rewards by depositing your BOLD in a stability pool", + title: "Earn rewards with MINT", + subtitle: "Earn MINT & (staked) ANKR rewards by depositing your MINT in a stability pool", forksInfo: { text: ( <> @@ -250,7 +250,7 @@ export default { ], spTvl: [ "Total Value Locked", - "The total amount of BOLD deposited in each stability pool.", + "The total amount of MINT deposited in each stability pool.", ], borrowTvl: [ "Total Value Locked", @@ -281,7 +281,7 @@ export default { action: "Next: Summary", infoTooltips: { interestRateSuggestions: [ - "Positions with lower interest rates are the first to be redeemed by BOLD holders.", + "Positions with lower interest rates are the first to be redeemed by MINT holders.", ], }, }, @@ -313,7 +313,7 @@ export default { ], interestRateSuggestions: [ <> - Positions with lower interest rates are the first to be redeemed by BOLD holders. + Positions with lower interest rates are the first to be redeemed by MINT holders. , ], exposure: [ @@ -330,13 +330,13 @@ export default { headline: (rewards: N, bold: N) => ( <> Deposit - {bold} BOLD + {bold} MINT to earn rewards {rewards} ), subheading: ( <> - A BOLD deposit in a stability pool earns rewards from the fees that users pay on their loans. Also, the BOLD may + A MINT deposit in a stability pool earns rewards from the fees that users pay on their loans. Also, the BOLD may be swapped to collateral in case the system needs to liquidate positions. ), @@ -348,7 +348,7 @@ export default { }, infoTooltips: { tvl: (collateral: N) => [ - <>Total BOLD covering {collateral}-backed position liquidations, + <>Total MINT covering {collateral}-backed position liquidations, ], }, }, @@ -396,10 +396,10 @@ export default { }, infoTooltips: { tvl: (collateral: N) => [ - <>Total BOLD covering {collateral}-backed position liquidations., + <>Total MINT covering {collateral}-backed position liquidations., ], depositPoolShare: [ - "Percentage of your BOLD deposit compared to the total deposited in this stability pool.", + "Percentage of your MINT deposit compared to the total deposited in this stability pool.", ], alsoClaimRewardsDeposit: [ <> @@ -414,14 +414,14 @@ export default { , ], currentApr: [ - "Average annualized return for BOLD deposits over the past 7 days.", + "Average annualized return for MINT deposits over the past 7 days.", ], rewardsEth: [ "ANKR rewards", "Your proceeds from liquidations conducted by this stability pool.", ], rewardsBold: [ - "BOLD rewards", + "MINT rewards", "Your earnings from protocol revenue distributions to this stability pool.", ], }, diff --git a/frontend/app/src/screens/AccountScreen/AccountScreen.tsx b/frontend/app/src/screens/AccountScreen/AccountScreen.tsx index 52064054b..77c73de18 100644 --- a/frontend/app/src/screens/AccountScreen/AccountScreen.tsx +++ b/frontend/app/src/screens/AccountScreen/AccountScreen.tsx @@ -106,7 +106,7 @@ export function AccountScreen({ gridTemplateColumns: `repeat(3, 1fr)`, }} > - + - {NBSP}BOLD + {NBSP}MINT , )} @@ -276,12 +276,12 @@ export function BorrowScreen() { contextual={ } - label="BOLD" + label="MINT" /> } drawer={debt.isFocused || !isBelowMinDebt ? null : { mode: "error", - message: `You must borrow at least ${fmtnum(MIN_DEBT, 2)} BOLD.`, + message: `You must borrow at least ${fmtnum(MIN_DEBT, 2)} MINT.`, }} label={content.borrowScreen.borrowField.label} placeholder="0.00" @@ -420,7 +420,7 @@ export function BorrowScreen() { fontSize: 16, color: "content", background: "infoSurface", - border: "1px solid token(colors.infoSurfaceBorder)", + border: "2px solid token(colors.infoSurfaceBorder)", borderRadius: 8, })} > diff --git a/frontend/app/src/screens/EarnPoolScreen/PanelClaimRewards.tsx b/frontend/app/src/screens/EarnPoolScreen/PanelClaimRewards.tsx index b8715c0b8..9c4056412 100644 --- a/frontend/app/src/screens/EarnPoolScreen/PanelClaimRewards.tsx +++ b/frontend/app/src/screens/EarnPoolScreen/PanelClaimRewards.tsx @@ -170,14 +170,14 @@ export function PanelClaimRewards({ checked={compound} onChange={setCompound} /> - Compound BOLD rewards + Compound MINT rewards - When enabled, your BOLD rewards will be automatically added back to your stability pool deposit, + When enabled, your MINT rewards will be automatically added back to your stability pool deposit, earning you more rewards over time. Collateral rewards will still be claimed normally. ), diff --git a/frontend/app/src/screens/EarnPoolScreen/PanelUpdateDeposit.tsx b/frontend/app/src/screens/EarnPoolScreen/PanelUpdateDeposit.tsx index 990b92238..b529f321f 100644 --- a/frontend/app/src/screens/EarnPoolScreen/PanelUpdateDeposit.tsx +++ b/frontend/app/src/screens/EarnPoolScreen/PanelUpdateDeposit.tsx @@ -87,21 +87,21 @@ export function PanelUpdateDeposit({ drawer={insufficientBalance ? { mode: "error", - message: `Insufficient balance. You have ${fmtnum(boldBalance.data ?? 0)} BOLD.`, + message: `Insufficient balance. You have ${fmtnum(boldBalance.data ?? 0)} MINT.`, } : withdrawAboveDeposit ? { mode: "error", message: hasDeposit ? `You can’t withdraw more than you have deposited.` - : `No BOLD deposited.`, + : `No MINT deposited.`, } : null} contextual={ } - label="BOLD" + label="MINT" /> } id="input-deposit-change" @@ -152,7 +152,7 @@ export function PanelUpdateDeposit({ ? boldBalance.data && ( { if (boldBalance.data) { @@ -163,7 +163,7 @@ export function PanelUpdateDeposit({ ) : position?.deposit && dn.gt(position.deposit, 0) && ( { setValue(dn.toString(position.deposit)); setClaimRewards(true); @@ -231,7 +231,7 @@ export function PanelUpdateDeposit({ color: "contentAlt", })} > - BOLD + MINT
    diff --git a/frontend/app/src/screens/HomeScreen/HomeScreen.tsx b/frontend/app/src/screens/HomeScreen/HomeScreen.tsx index e6396d391..9e1142c4d 100644 --- a/frontend/app/src/screens/HomeScreen/HomeScreen.tsx +++ b/frontend/app/src/screens/HomeScreen/HomeScreen.tsx @@ -110,7 +110,7 @@ function BorrowTable({ return (
    } columns={columns} @@ -328,7 +328,7 @@ function EarnRewardsRow({
    } - title={`Earn BOLD with ${token?.name}`} + title={`Earn MINT with ${token?.name}`} /> )} diff --git a/frontend/app/src/screens/LoanScreen/LoanScreen.tsx b/frontend/app/src/screens/LoanScreen/LoanScreen.tsx index 8ce4277cf..43e3a3b32 100644 --- a/frontend/app/src/screens/LoanScreen/LoanScreen.tsx +++ b/frontend/app/src/screens/LoanScreen/LoanScreen.tsx @@ -271,7 +271,7 @@ export function LoanScreen() { {" "} repaid in exchange for{" "} { - const title = mode === "multiply" ? "Multiply" : "BOLD loan"; + const title = mode === "multiply" ? "Multiply" : "MINT loan"; return (
    { @@ -167,7 +167,7 @@ export function PanelClosePosition({ symbol="BOLD" size={24} /> -
    BOLD
    +
    MINT
    } diff --git a/frontend/app/src/screens/LoanScreen/PanelInterestRate.tsx b/frontend/app/src/screens/LoanScreen/PanelInterestRate.tsx index d80e49a6b..87f257595 100644 --- a/frontend/app/src/screens/LoanScreen/PanelInterestRate.tsx +++ b/frontend/app/src/screens/LoanScreen/PanelInterestRate.tsx @@ -34,7 +34,7 @@ export function PanelInterestRate({ const deposit = useInputFieldValue((value) => `${fmtnum(value, "full")} ${collToken.symbol}`, { defaultValue: dn.toString(loan.deposit), }); - const debt = useInputFieldValue((value) => `${fmtnum(value, "full")} BOLD`, { + const debt = useInputFieldValue((value) => `${fmtnum(value, "full")} MINT`, { defaultValue: dn.toString(loan.borrowed), }); @@ -222,12 +222,12 @@ export function PanelInterestRate({ { label: ( <> -
    BOLD interest per year
    +
    MINT interest per year
    ), - before: , - after: , + before: , + after: , }, ]} /> diff --git a/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx b/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx index b66251997..4b3a4bf47 100644 --- a/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx +++ b/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx @@ -280,13 +280,13 @@ export function PanelUpdateBorrowPosition({ } - label="BOLD" + label="MINT" /> } drawer={!debtChange.isFocused && isBelowMinDebt - ? { mode: "error", message: `You must borrow at least ${fmtnum(MIN_DEBT, 2)} BOLD.` } + ? { mode: "error", message: `You must borrow at least ${fmtnum(MIN_DEBT, 2)} MINT.` } : insufficientBold - ? { mode: "error", message: "Insufficient BOLD balance." } + ? { mode: "error", message: "Insufficient MINT balance." } : null} label={{ start: debtMode === "remove" @@ -318,7 +318,7 @@ export function PanelUpdateBorrowPosition({ end: ( boldMax && ( { debtChange.setValue(dn.toString(boldMax)); }} @@ -349,15 +349,15 @@ export function PanelUpdateBorrowPosition({ >
    - Before: + Before:
    - After: + After:
    diff --git a/frontend/app/src/screens/RedeemScreen/RedeemScreen.tsx b/frontend/app/src/screens/RedeemScreen/RedeemScreen.tsx index 02cd30ccb..a3cdeb702 100644 --- a/frontend/app/src/screens/RedeemScreen/RedeemScreen.tsx +++ b/frontend/app/src/screens/RedeemScreen/RedeemScreen.tsx @@ -91,7 +91,7 @@ export function RedeemScreen() { contextual={ } - label="BOLD" + label="MINT" /> } drawer={amount.isFocused @@ -101,7 +101,7 @@ export function RedeemScreen() { && dn.gt(amount.parsed, boldBalance.data) ? { mode: "error", - message: `Insufficient BOLD balance. You have ${fmtnum(boldBalance.data)} BOLD.`, + message: `Insufficient MINT balance. You have ${fmtnum(boldBalance.data)} MINT.`, } : null} label="Redeeming" diff --git a/frontend/app/src/screens/TransactionsScreen/LoanCard/components/LoadingCard/LoadingCard.tsx b/frontend/app/src/screens/TransactionsScreen/LoanCard/components/LoadingCard/LoadingCard.tsx index e8acafa01..68bef04dd 100644 --- a/frontend/app/src/screens/TransactionsScreen/LoanCard/components/LoadingCard/LoadingCard.tsx +++ b/frontend/app/src/screens/TransactionsScreen/LoanCard/components/LoadingCard/LoadingCard.tsx @@ -25,7 +25,7 @@ export const LoadingCard: FC = ({ onRetry, children, }) => { - const title = leverage ? "Multiply" : "BOLD loan"; + const title = leverage ? "Multiply" : "MINT loan"; const spring = useSpring({ to: match(loadingState) diff --git a/frontend/app/src/screens/TransactionsScreen/LoanCard/components/components/TotalDebt/TotalDebt.tsx b/frontend/app/src/screens/TransactionsScreen/LoanCard/components/components/TotalDebt/TotalDebt.tsx index c695b95cf..05a50319a 100644 --- a/frontend/app/src/screens/TransactionsScreen/LoanCard/components/components/TotalDebt/TotalDebt.tsx +++ b/frontend/app/src/screens/TransactionsScreen/LoanCard/components/components/TotalDebt/TotalDebt.tsx @@ -31,7 +31,7 @@ export const TotalDebt: FC = ({ positive, loan, prevLoan }) => ( })} >
    = ({ positive, loan, prevLoan }) => (
    {prevLoan && !dn.eq(prevLoan.borrowed, loan.borrowed) && ( - + {fmtnum(prevLoan.borrowed)} )} diff --git a/frontend/app/src/tx-flows/claimBribes.tsx b/frontend/app/src/tx-flows/claimBribes.tsx index 03e5cbb11..464049dd4 100644 --- a/frontend/app/src/tx-flows/claimBribes.tsx +++ b/frontend/app/src/tx-flows/claimBribes.tsx @@ -63,7 +63,7 @@ export const claimBribes: FlowDeclaration = { epoch{totalEpochs > 1 ? "s" : ""}. = { , ]} /> diff --git a/frontend/app/src/tx-flows/earnUpdate.tsx b/frontend/app/src/tx-flows/earnUpdate.tsx index dda9bde31..43b05decc 100644 --- a/frontend/app/src/tx-flows/earnUpdate.tsx +++ b/frontend/app/src/tx-flows/earnUpdate.tsx @@ -36,7 +36,7 @@ export const earnUpdate: FlowDeclaration = { earnPosition={{ ...request.earnPosition, - // compound BOLD rewards if not claiming + // compound MINT rewards if not claiming deposit: dn.add( request.earnPosition.deposit, request.claimRewards diff --git a/frontend/app/src/tx-flows/openBorrowPosition.tsx b/frontend/app/src/tx-flows/openBorrowPosition.tsx index 018850f8a..8b5f8e138 100644 --- a/frontend/app/src/tx-flows/openBorrowPosition.tsx +++ b/frontend/app/src/tx-flows/openBorrowPosition.tsx @@ -123,7 +123,7 @@ export const openBorrowPosition: FlowDeclaration = { key="start" fallback="…" value={boldAmountWithFee} - suffix=" BOLD" + suffix=" MINT" />,
    = { fallback="…" prefix="Incl. " value={upfrontFee.data} - suffix=" BOLD creation fee" + suffix=" MINT creation fee" /> - + This fee is charged when you open a new loan or increase your debt. It corresponds to 7 days of average interest for the respective collateral asset. @@ -177,7 +177,7 @@ export const openBorrowPosition: FlowDeclaration = { @@ -202,7 +202,7 @@ export const openBorrowPosition: FlowDeclaration = { boldAmountWithFee, request.annualInterestRate, )} - suffix=" BOLD per year" + suffix=" MINT per year" />, ]} /> diff --git a/frontend/app/src/tx-flows/updateBorrowPosition.tsx b/frontend/app/src/tx-flows/updateBorrowPosition.tsx index 58568fd60..96ab5a482 100644 --- a/frontend/app/src/tx-flows/updateBorrowPosition.tsx +++ b/frontend/app/src/tx-flows/updateBorrowPosition.tsx @@ -109,7 +109,7 @@ export const updateBorrowPosition: FlowDeclaration key="start" fallback="…" value={debtChangeWithFee && dn.abs(debtChangeWithFee)} - suffix=" BOLD" + suffix=" MINT" />, upfrontFeeData.data?.upfrontFee && dn.gt(upfrontFeeData.data.upfrontFee, 0n) && ( diff --git a/frontend/uikit/src/Dropdown/Dropdown.tsx b/frontend/uikit/src/Dropdown/Dropdown.tsx index 1611cbce8..8deb764b8 100644 --- a/frontend/uikit/src/Dropdown/Dropdown.tsx +++ b/frontend/uikit/src/Dropdown/Dropdown.tsx @@ -263,7 +263,7 @@ export function Dropdown({ boxShadow: `0 1px 1px rgba(0, 0, 0, 0.1)`, }, _groupFocusVisible: { - outline: "2px solid token(colors.focused)", + outline: "1px solid token(colors.focused)", }, })} style={{ diff --git a/frontend/uikit/src/InputField/InputField.tsx b/frontend/uikit/src/InputField/InputField.tsx index f06496250..374bd43bb 100644 --- a/frontend/uikit/src/InputField/InputField.tsx +++ b/frontend/uikit/src/InputField/InputField.tsx @@ -269,7 +269,7 @@ function InputField({ display: "none", position: "absolute", inset: -1, - border: "2px solid token(colors.fieldBorderFocused)", + border: "1px solid token(colors.fieldBorderFocused)", borderRadius: 8, pointerEvents: "none", })} @@ -377,7 +377,7 @@ export function InputFieldBadge({ padding: "0 16px", paddingLeft: icon ? 8 : 16, background: "rgba(255, 255, 255, 0.20)", - border: '2px solid rgba(136, 253, 157, 0.50)', + border: '1px solid rgba(136, 253, 157, 0.50)', borderRadius: 20, userSelect: "none", }} diff --git a/frontend/uikit/src/token-icons/bold.svg b/frontend/uikit/src/token-icons/bold.svg index 2bf854dd8..1e5a766f1 100644 --- a/frontend/uikit/src/token-icons/bold.svg +++ b/frontend/uikit/src/token-icons/bold.svg @@ -1 +1,12 @@ - + + + + + + + + + + + + diff --git a/frontend/uikit/src/tokens.ts b/frontend/uikit/src/tokens.ts index fb53e1e84..755fe9c4f 100644 --- a/frontend/uikit/src/tokens.ts +++ b/frontend/uikit/src/tokens.ts @@ -49,7 +49,7 @@ export type CollateralToken = Token & { export const BOLD: Token = { icon: tokenBold, - name: "BOLD", + name: "MINT", symbol: "BOLD" as const, } as const; @@ -74,6 +74,7 @@ export const COLLATERALS: CollateralToken[] = [ export const TOKENS_BY_SYMBOL = { BOLD, + MINT: BOLD, WANKR, USN, } as const; From 9e6b21062e3f58c055a8d5103691b37e52bc9027 Mon Sep 17 00:00:00 2001 From: Artur Korotenko Date: Tue, 9 Sep 2025 18:43:32 +0500 Subject: [PATCH 13/19] chore: change env --- frontend/app/.env | 50 +++++++++---------- .../app/src/comps/AppLayout/BottomBar.tsx | 7 +-- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/frontend/app/.env b/frontend/app/.env index def80de10..6e60d5a1a 100644 --- a/frontend/app/.env +++ b/frontend/app/.env @@ -17,31 +17,31 @@ NEXT_PUBLIC_LIQUITY_STATS_URL=https://api.liquity.org/v2/testnet/sepolia.json NEXT_PUBLIC_COLL_0_TOKEN_ID=WANKR NEXT_PUBLIC_COLL_1_TOKEN_ID=USN -NEXT_PUBLIC_COLL_0_CONTRACT_ACTIVE_POOL=0xe9786d98c6203ca0746061abfbfdeeb02e0fe5da -NEXT_PUBLIC_COLL_0_CONTRACT_ADDRESSES_REGISTRY=0x9cdc1c9582136916362d84a1dc03f5aa25e318d1 -NEXT_PUBLIC_COLL_0_CONTRACT_BORROWER_OPERATIONS=0x1ca56ac150e747074114a33223262f979ffd026c -NEXT_PUBLIC_COLL_0_CONTRACT_COLL_SURPLUS_POOL=0xed8614f48a494a8fca53978f1183516a093006a8 +NEXT_PUBLIC_COLL_0_CONTRACT_ACTIVE_POOL=0x47ef48feb760f49b0e1d4cb63f648814a0adfc2d +NEXT_PUBLIC_COLL_0_CONTRACT_ADDRESSES_REGISTRY=0x0926df4209b9c6f7613f6f9deba1ad5753c26119 +NEXT_PUBLIC_COLL_0_CONTRACT_BORROWER_OPERATIONS=0xd96c70aadf9b6e1b768f7a3313b91c31349f00dc +NEXT_PUBLIC_COLL_0_CONTRACT_COLL_SURPLUS_POOL=0x8824b9cec6cb9611577d578705fcda3a0077c60d NEXT_PUBLIC_COLL_0_CONTRACT_COLL_TOKEN=0xBd833b6eCC30CAEaBf81dB18BB0f1e00C6997E7a -NEXT_PUBLIC_COLL_0_CONTRACT_DEFAULT_POOL=0x2c6853ee6d9311f1c9aebad7f003999ac2f011c2 -NEXT_PUBLIC_COLL_0_CONTRACT_PRICE_FEED=0x6f2c76f8105f723df8847a4ed97d3fc2fd335b7a -NEXT_PUBLIC_COLL_0_CONTRACT_SORTED_TROVES=0xc383b06357a330d1f326d8f3e0a92c8e5d918343 -NEXT_PUBLIC_COLL_0_CONTRACT_STABILITY_POOL=0x0c8a5ebc5b8bac0dfdad159ca61fd0906bf6f53e -NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_MANAGER=0x4d2044a26fde2876db02e19e4eac2e75ecb8d510 -NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_NFT=0xf92ea65bd8b2c038693e385a5b2965483b1ad60f -NEXT_PUBLIC_COLL_1_CONTRACT_ACTIVE_POOL=0x9e00f9c4b769e11f1768da12a255e2b6e3ca9969 -NEXT_PUBLIC_COLL_1_CONTRACT_ADDRESSES_REGISTRY=0x9869a3ce15587b32dec8049aabd0572fffc9a5fc -NEXT_PUBLIC_COLL_1_CONTRACT_BORROWER_OPERATIONS=0x53ca33f16a98c877f19bf64a3ecc3411d049fe9f -NEXT_PUBLIC_COLL_1_CONTRACT_COLL_SURPLUS_POOL=0xbc94877d8ecd73f329ecf38fc3c032d8b82c5fbd -NEXT_PUBLIC_COLL_1_CONTRACT_COLL_TOKEN=0xe95455FEc4Be722122401B5dCAbb2428171AfF0c -NEXT_PUBLIC_COLL_1_CONTRACT_DEFAULT_POOL=0xd7662a952fb043a444ffe52115cfcf133ed238d5 -NEXT_PUBLIC_COLL_1_CONTRACT_PRICE_FEED=0x4abf5e742d9b44d4b0f0309cd3928b2e78ebd187 -NEXT_PUBLIC_COLL_1_CONTRACT_SORTED_TROVES=0xf6371a5c497170dbae162c830564fac462f206d6 -NEXT_PUBLIC_COLL_1_CONTRACT_STABILITY_POOL=0x54ccb90ef683a7256fbd600db3d64d6d1015586c -NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_MANAGER=0xb048a13fad98f35470ce962ddfce8c8450822d33 -NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_NFT=0x23197d6c5199fdcddd441fc4924b5356313dc936 +NEXT_PUBLIC_COLL_0_CONTRACT_DEFAULT_POOL=0xa5e1bbe66aa3e049c3ec3ea247c94a71ef25f589 +NEXT_PUBLIC_COLL_0_CONTRACT_PRICE_FEED=0xb82c7e4a542369f1f8003ccc34c6262b2d6ffcfb +NEXT_PUBLIC_COLL_0_CONTRACT_SORTED_TROVES=0xf8e35faf176b844b3345a498207babe504b2ea61 +NEXT_PUBLIC_COLL_0_CONTRACT_STABILITY_POOL=0xcaddac2df4e27b268b48e6a682534904b76bc222 +NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_MANAGER=0x27192a33dbbbb22f6908c5988be48b118c90c6cf +NEXT_PUBLIC_COLL_0_CONTRACT_TROVE_NFT=0x066f05af1eca2911da83b66f15b56bc5a0306e49 +NEXT_PUBLIC_COLL_1_CONTRACT_ACTIVE_POOL=0x5ca398adab0f6975ff93d48fb5fc5a58e9b91100 +NEXT_PUBLIC_COLL_1_CONTRACT_ADDRESSES_REGISTRY=0x8b517b01493dd2338c2a20ceb46a0c50cc8131b8 +NEXT_PUBLIC_COLL_1_CONTRACT_BORROWER_OPERATIONS=0xf3ea136875f5b001685da862fd4ed25fc66fe2e7 +NEXT_PUBLIC_COLL_1_CONTRACT_COLL_SURPLUS_POOL=0xf2d8d9366a69eaa3d6cb042f2b749965aa5edb76 +NEXT_PUBLIC_COLL_1_CONTRACT_COLL_TOKEN=0xF22870a514b0c288A8E0cAf341146fc9E0f3D982 +NEXT_PUBLIC_COLL_1_CONTRACT_DEFAULT_POOL=0x82f09a4f9ff368a1a54c668bc24b1dcafbcc1773 +NEXT_PUBLIC_COLL_1_CONTRACT_PRICE_FEED=0x2de10459a65359942e74e8c75a6bfecd797a3179 +NEXT_PUBLIC_COLL_1_CONTRACT_SORTED_TROVES=0x20ed8bec7dd9ac84a2bf4ae74a76492fd912d319 +NEXT_PUBLIC_COLL_1_CONTRACT_STABILITY_POOL=0xf955b3b8c8aaad5b13dd6ff3a3ca2271accde3fb +NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_MANAGER=0x3cecf1d1d80b2045514620a43ebbd5e438a46feb +NEXT_PUBLIC_COLL_1_CONTRACT_TROVE_NFT=0x6385f95c28e23f53d479f86ad11c53d7589225ae -NEXT_PUBLIC_CONTRACT_BOLD_TOKEN=0xc13441f42e0aca71ea246aba87bdcdf646a456dd -NEXT_PUBLIC_CONTRACT_COLLATERAL_REGISTRY=0x39e982e327bb27549c447cb91ae220ad8a566afa -NEXT_PUBLIC_CONTRACT_HINT_HELPERS=0x7375ff5c5e993d2dfbff6b1d099d16971fae16b8 -NEXT_PUBLIC_CONTRACT_MULTI_TROVE_GETTER=0x580d8e94b61a2c3132b9b1f50d944ff4add3c3bb +NEXT_PUBLIC_CONTRACT_BOLD_TOKEN=0x2d65ae4a0a4945922f71b71f379e8e937ac84863 +NEXT_PUBLIC_CONTRACT_COLLATERAL_REGISTRY=0x0705fea127ce6a3fc2256f28dedc32e48dd02a98 +NEXT_PUBLIC_CONTRACT_HINT_HELPERS=0xb8858f3739bf477911f4e21d10a55b1d40f0bdd2 +NEXT_PUBLIC_CONTRACT_MULTI_TROVE_GETTER=0x517651465787885d2de7315cfa513d29680ea31c NEXT_PUBLIC_CONTRACT_WETH=0xBd833b6eCC30CAEaBf81dB18BB0f1e00C6997E7a diff --git a/frontend/app/src/comps/AppLayout/BottomBar.tsx b/frontend/app/src/comps/AppLayout/BottomBar.tsx index 9319f931d..1a7a19135 100644 --- a/frontend/app/src/comps/AppLayout/BottomBar.tsx +++ b/frontend/app/src/comps/AppLayout/BottomBar.tsx @@ -9,12 +9,12 @@ import { useLiquityStats } from "@/src/liquity-utils"; import { usePrice } from "@/src/services/Prices"; import { useAccount } from "@/src/wagmi-utils"; import { css } from "@/styled-system/css"; -import { shortenAddress, TokenIcon } from "@liquity2/uikit"; +import { shortenAddress, TokenIcon, TOKENS_BY_SYMBOL } from "@liquity2/uikit"; import { blo } from "blo"; import Image from "next/image"; import { AboutButton } from "./AboutButton"; -const DISPLAYED_PRICES = ["MINT", "WANKR"] as const; +const DISPLAYED_PRICES = ["BOLD", "WANKR"] as const; const ENABLE_REDEEM = false; export function BottomBar() { @@ -231,6 +231,7 @@ function Price({ symbol }: { symbol: TokenSymbol }) { const price = usePrice(symbol); const tokenAddress = getTokenAddress(symbol); const tokenUrl = tokenAddress && getTokenLink(tokenAddress); + const tokenName = TOKENS_BY_SYMBOL[symbol].name; const token = (
    - {symbol} + {tokenName}
    ); return ( From 64372372eb80a3b689629ce096097ebd9f479763 Mon Sep 17 00:00:00 2001 From: tenshou Date: Tue, 9 Sep 2025 21:22:00 +0700 Subject: [PATCH 14/19] feat: ALIGN-70 fix ui --- .../app/src/comps/AppLayout/AccountButton.tsx | 22 ++--- frontend/app/src/comps/AppLayout/Menu.tsx | 11 +-- frontend/app/src/comps/AppLayout/MenuItem.tsx | 13 +-- frontend/app/src/comps/AppLayout/TopBar.tsx | 2 +- .../EarnPositionSummaryBase.tsx | 6 +- .../InterestRateField/InterestRateField.tsx | 2 +- .../app/src/comps/Positions/PositionCard.tsx | 4 +- .../comps/RedemptionInfo/RedemptionInfo.tsx | 69 +++++--------- frontend/app/src/content.tsx | 16 ++-- .../src/screens/BorrowScreen/BorrowScreen.tsx | 13 +-- .../screens/BorrowScreen/assets/tokens.svg | 92 +++++++++++++++++++ .../EarnPoolsListScreen.tsx | 5 +- .../YieldSourceTable/components/Hint/Hint.tsx | 4 +- .../src/screens/RedeemScreen/RedeemScreen.tsx | 4 +- frontend/app/src/tx-flows/claimBribes.tsx | 2 +- .../app/src/tx-flows/closeLoanPosition.tsx | 2 +- .../app/src/tx-flows/earnClaimRewards.tsx | 4 +- frontend/app/src/tx-flows/earnUpdate.tsx | 6 +- .../app/src/tx-flows/redeemCollateral.tsx | 8 +- .../app/src/tx-flows/updateBorrowPosition.tsx | 12 +-- .../src/tx-flows/updateLoanInterestRate.tsx | 6 +- frontend/uikit/src/Dropdown/Dropdown.tsx | 10 +- frontend/uikit/src/InputField/InputField.tsx | 17 ++-- frontend/uikit/src/Radio/Radio.tsx | 6 +- frontend/uikit/src/Theme/Theme.tsx | 6 +- frontend/uikit/src/token-icons/eth.svg | 6 +- frontend/uikit/src/token-icons/wankr.svg | 4 + frontend/uikit/src/tokens.ts | 4 +- 28 files changed, 206 insertions(+), 150 deletions(-) create mode 100644 frontend/app/src/screens/BorrowScreen/assets/tokens.svg create mode 100644 frontend/uikit/src/token-icons/wankr.svg diff --git a/frontend/app/src/comps/AppLayout/AccountButton.tsx b/frontend/app/src/comps/AppLayout/AccountButton.tsx index 2a3daff79..2fd9c1ad9 100644 --- a/frontend/app/src/comps/AppLayout/AccountButton.tsx +++ b/frontend/app/src/comps/AppLayout/AccountButton.tsx @@ -2,7 +2,7 @@ import type { ComponentPropsWithRef } from "react"; import content from "@/src/content"; import { css } from "@/styled-system/css"; -import { Button, IconAccount, shortenAddress, ShowAfter } from "@liquity2/uikit"; +import { Button, shortenAddress, ShowAfter } from "@liquity2/uikit"; import { a, useTransition } from "@react-spring/web"; import { ConnectKitButton } from "connectkit"; import { match, P } from "ts-pattern"; @@ -149,8 +149,11 @@ function ButtonConnected({ className={css({ display: "grid", width: "100%", - height: "100%", - padding: 0, + minWidth: 136, + padding: "0 24px", + height: "32px", + border: "2px solid token(colors.interactive)", + borderRadius: 12, whiteSpace: "nowrap", textAlign: "center", _active: { @@ -176,16 +179,6 @@ function ButtonConnected({ color: "interactive", })} > -
    - -
    {label} diff --git a/frontend/app/src/comps/AppLayout/Menu.tsx b/frontend/app/src/comps/AppLayout/Menu.tsx index 8ec64c527..f5c10bf2a 100644 --- a/frontend/app/src/comps/AppLayout/Menu.tsx +++ b/frontend/app/src/comps/AppLayout/Menu.tsx @@ -32,12 +32,13 @@ export function Menu({ position: "relative", zIndex: 2, display: "flex", - gap: 2, + gap: 32, height: "100%", })} > {menuItems.map(([label, href]) => { const selected = href === "/" ? pathname === "/" : pathname.startsWith(href); + console.log('selected', selected); return (
  • -
    - {icon} -
    - {breakpoint === "large" && {" BOLD"}} + {breakpoint === "large" && {" MINT"}} ) diff --git a/frontend/app/src/comps/Positions/PositionCard.tsx b/frontend/app/src/comps/Positions/PositionCard.tsx index e63aa3eac..5a28016ac 100644 --- a/frontend/app/src/comps/Positions/PositionCard.tsx +++ b/frontend/app/src/comps/Positions/PositionCard.tsx @@ -84,7 +84,9 @@ export function PositionCard({ padding: "12px 16px", borderRadius: 8, outline: "none", - "--background": "token(colors.position)", + "--background": "token(colors.brandBlueContentAlt)", + border: "2px solid token(colors.border)", + _focusVisible: { outline: "2px solid token(colors.focused)", }, diff --git a/frontend/app/src/comps/RedemptionInfo/RedemptionInfo.tsx b/frontend/app/src/comps/RedemptionInfo/RedemptionInfo.tsx index 4ce647038..dcc3b9dff 100644 --- a/frontend/app/src/comps/RedemptionInfo/RedemptionInfo.tsx +++ b/frontend/app/src/comps/RedemptionInfo/RedemptionInfo.tsx @@ -9,8 +9,8 @@ const { title, subtitle, infoItems, learnMore } = content.redemptionInfo; const iconComponents = { bold: BoldIcon, - redemption: RedemptionIcon, - interest: InterestIcon, + redemption: BoldIcon, + interest: BoldIcon, } as const; export const RedemptionInfo = memo(function RedemptionInfo() { @@ -147,7 +147,7 @@ export const RedemptionInfo = memo(function RedemptionInfo() { })} -
    + {learnMore?.href && (
    } /> -
    +
    )} ); }); function BoldIcon() { return ( - - - - - ); -} - -function RedemptionIcon() { - return ( - - - - - ); -} - -function InterestIcon() { - return ( - - - - - + + + + + + + + + ); } diff --git a/frontend/app/src/content.tsx b/frontend/app/src/content.tsx index 28ddc1382..2abae03bb 100644 --- a/frontend/app/src/content.tsx +++ b/frontend/app/src/content.tsx @@ -12,7 +12,7 @@ export default { deposit LSTs as collateral and mint the stablecoin MINT. `, appUrl: typeof window === "undefined" - ? "https://www.liquity.org/" + ? "https://alignmint.io/" : window.location.origin, appIcon: ( typeof window === "undefined" ? "" : window.location.origin @@ -93,10 +93,6 @@ export default { interest rate get redeemed first. ), - footerLink: { - href: "https://docs.liquity.org/v2-faq/redemptions-and-delegation", - label: "Learn more", - }, }, }, @@ -125,7 +121,7 @@ export default { ], learnMore: { text: "Learn more about redemptions", - href: "https://docs.liquity.org/v2-faq/redemptions-and-delegation", + href: "", }, }, @@ -223,7 +219,7 @@ export default { ), titleAttr: "Stability Pool depositors earn additional rewards from forks.", learnMore: { - url: "https://docs.liquity.org/v2-documentation/friendly-fork-program", + url: "", label: "Learn more", title: "Learn more about the AlignMint Friendly Fork Program", }, @@ -233,7 +229,7 @@ export default { title: "Top 3 external yield opportunities", hint: { title: "All yield sources on Dune", - url: "https://dune.com/liquity/liquity-v2-yields", + url: "", label: "Learn more", }, }, @@ -336,11 +332,11 @@ export default { ), subheading: ( <> - A MINT deposit in a stability pool earns rewards from the fees that users pay on their loans. Also, the BOLD may + A MINT deposit in a stability pool earns rewards from the fees that users pay on their loans. Also, the MINT may be swapped to collateral in case the system needs to liquidate positions. ), - learnMore: ["https://docs.liquity.org/v2-faq/bold-and-earn", "Learn more"], + learnMore: ["", "Learn more"], poolsColumns: { pool: "Pool", apr: "APR", diff --git a/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx b/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx index 0693fc750..ef93a7cf3 100644 --- a/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx +++ b/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx @@ -43,9 +43,11 @@ import { TokenIcon, } from "@liquity2/uikit"; import * as dn from "dnum"; +import Image from 'next/image'; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; import { maxUint256 } from "viem"; +import tokensIcon from "./assets/tokens.svg"; const KNOWN_COLLATERAL_SYMBOLS = KNOWN_COLLATERALS.map(({ symbol }) => symbol); @@ -179,22 +181,17 @@ export function BorrowScreen() { })} > - {collaterals.map(({ symbol }) => ( - - ))} + - {NBSP}ANKR
    , +
    - + {NBSP}MINT
    , )} diff --git a/frontend/app/src/screens/BorrowScreen/assets/tokens.svg b/frontend/app/src/screens/BorrowScreen/assets/tokens.svg new file mode 100644 index 000000000..8dddf99cd --- /dev/null +++ b/frontend/app/src/screens/BorrowScreen/assets/tokens.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/app/src/screens/EarnPoolsListScreen/EarnPoolsListScreen.tsx b/frontend/app/src/screens/EarnPoolsListScreen/EarnPoolsListScreen.tsx index 2e92932d2..889f33b4e 100644 --- a/frontend/app/src/screens/EarnPoolsListScreen/EarnPoolsListScreen.tsx +++ b/frontend/app/src/screens/EarnPoolsListScreen/EarnPoolsListScreen.tsx @@ -61,11 +61,12 @@ export function EarnPoolsListScreen() { subtitle: ( <> {content.earnHome.subheading}{" "} - + />)} ), }} diff --git a/frontend/app/src/screens/HomeScreen/YieldSourceTable/components/Hint/Hint.tsx b/frontend/app/src/screens/HomeScreen/YieldSourceTable/components/Hint/Hint.tsx index 6c06a5d8d..a03449681 100644 --- a/frontend/app/src/screens/HomeScreen/YieldSourceTable/components/Hint/Hint.tsx +++ b/frontend/app/src/screens/HomeScreen/YieldSourceTable/components/Hint/Hint.tsx @@ -30,7 +30,7 @@ export const Hint: FC = () => { > {content.home.yieldTable.hint.title} - { })} > Learn more - + )}
    } diff --git a/frontend/app/src/screens/RedeemScreen/RedeemScreen.tsx b/frontend/app/src/screens/RedeemScreen/RedeemScreen.tsx index a3cdeb702..8342b51ae 100644 --- a/frontend/app/src/screens/RedeemScreen/RedeemScreen.tsx +++ b/frontend/app/src/screens/RedeemScreen/RedeemScreen.tsx @@ -62,7 +62,7 @@ export function RedeemScreen() { heading={{ title: ( - Redeem BOLD for + Redeem MINT for {branches.map((b) => getCollToken(b.branchId)).map(({ symbol }) => ( { if (boldBalance.data) { amount.setValue(dn.toString(boldBalance.data)); diff --git a/frontend/app/src/tx-flows/claimBribes.tsx b/frontend/app/src/tx-flows/claimBribes.tsx index 464049dd4..76d1ac381 100644 --- a/frontend/app/src/tx-flows/claimBribes.tsx +++ b/frontend/app/src/tx-flows/claimBribes.tsx @@ -67,7 +67,7 @@ export const claimBribes: FlowDeclaration = { value={[
    = { steps: { approveBold: { - name: () => "Approve BOLD", + name: () => "Approve MINT", Status: (props) => ( = { return ( <> , = { value={[ , = { /> {dn.gt(rewards.bold, 0) && ( , = { ]} /> , Estimated BOLD that will be redeemed. @@ -87,7 +87,7 @@ export const redeemCollateral: FlowDeclaration = { }, steps: { approve: { - name: () => "Approve BOLD", + name: () => "Approve MINT", Status: TransactionStatus, async commit({ request, writeContract }) { const CollateralRegistry = getProtocolContract("CollateralRegistry"); @@ -104,7 +104,7 @@ export const redeemCollateral: FlowDeclaration = { }, }, redeemCollateral: { - name: () => "Redeem BOLD", + name: () => "Redeem MINT", Status: TransactionStatus, async commit({ request, writeContract }) { const CollateralRegistry = getProtocolContract("CollateralRegistry"); diff --git a/frontend/app/src/tx-flows/updateBorrowPosition.tsx b/frontend/app/src/tx-flows/updateBorrowPosition.tsx index 96ab5a482..a26989bc3 100644 --- a/frontend/app/src/tx-flows/updateBorrowPosition.tsx +++ b/frontend/app/src/tx-flows/updateBorrowPosition.tsx @@ -117,7 +117,7 @@ export const updateBorrowPosition: FlowDeclaration fallback="…" prefix="Incl. " value={upfrontFeeData.data.upfrontFee} - suffix=" BOLD interest rate adjustment fee" + suffix=" MINT interest rate adjustment fee" /> ), ]} @@ -129,7 +129,7 @@ export const updateBorrowPosition: FlowDeclaration steps: { approveBold: { - name: () => "Approve BOLD", + name: () => "Approve MINT", Status: (props) => ( if (!dn.eq(collChange, 0) && !dn.eq(debtChange, 0)) return "Update Position"; if (dn.gt(collChange, 0)) return "Deposit Collateral"; if (dn.lt(collChange, 0)) return "Withdraw Collateral"; - if (dn.gt(debtChange, 0)) return "Borrow BOLD"; - if (dn.lt(debtChange, 0)) return "Repay BOLD"; + if (dn.gt(debtChange, 0)) return "Borrow MINT"; + if (dn.lt(debtChange, 0)) return "Repay MINT"; throw new Error("Invalid request"); }, @@ -277,7 +277,7 @@ export const updateBorrowPosition: FlowDeclaration }, depositBold: { - name: () => "Repay BOLD", + name: () => "Repay MINT", Status: TransactionStatus, async commit(ctx) { @@ -321,7 +321,7 @@ export const updateBorrowPosition: FlowDeclaration }, withdrawBold: { - name: () => "Borrow BOLD", + name: () => "Borrow MINT", Status: TransactionStatus, async commit(ctx) { diff --git a/frontend/app/src/tx-flows/updateLoanInterestRate.tsx b/frontend/app/src/tx-flows/updateLoanInterestRate.tsx index a9f11dc7d..3a41a2f02 100644 --- a/frontend/app/src/tx-flows/updateLoanInterestRate.tsx +++ b/frontend/app/src/tx-flows/updateLoanInterestRate.tsx @@ -139,7 +139,7 @@ export const updateLoanInterestRate: FlowDeclaration,
    {fmtnum(yearlyBoldInterest, { digits: 4, @@ -184,7 +184,7 @@ export const updateLoanInterestRate: FlowDeclaration
    , @@ -223,7 +223,7 @@ export const updateLoanInterestRate: FlowDeclaration, ]} /> diff --git a/frontend/uikit/src/Dropdown/Dropdown.tsx b/frontend/uikit/src/Dropdown/Dropdown.tsx index 8deb764b8..14e041aec 100644 --- a/frontend/uikit/src/Dropdown/Dropdown.tsx +++ b/frontend/uikit/src/Dropdown/Dropdown.tsx @@ -229,7 +229,8 @@ export function Dropdown({ )} style={customButton_ ? {} : { height: size === "small" ? 32 : 40, - fontSize: size === "small" ? 16 : 24, + fontSize: size === "small" ? 16 : 20, + fontWeight: 400, }} > {customButton_ ?? ( @@ -244,13 +245,14 @@ export function Dropdown({ whiteSpace: "nowrap", borderWidth: "2px", borderStyle: "solid", - borderColor: "token(colors.brandGreen)", + borderColor: "token(colors.accent)", boxShadow: ` 0 2px 2px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(18, 27, 68, 0.05), inset 0 -1px 4px rgba(0, 0, 0, 0.05) `, - borderRadius: 90, + background: 'transparent', + borderRadius: 20, cursor: "pointer", "--color-normal": "token(colors.content)", @@ -269,7 +271,7 @@ export function Dropdown({ style={{ gap: size === "small" ? 6 : 8, color: `var(--color-${buttonItem === placeholder ? "placeholder" : "normal"})`, - background: `var(--background-${buttonItem === placeholder ? "placeholder" : "normal"})`, + background: `transparent`, } as CSSProperties} > {buttonItem.icon && buttonDisplay !== "label-only" && ( diff --git a/frontend/uikit/src/InputField/InputField.tsx b/frontend/uikit/src/InputField/InputField.tsx index 374bd43bb..93c2bdd5a 100644 --- a/frontend/uikit/src/InputField/InputField.tsx +++ b/frontend/uikit/src/InputField/InputField.tsx @@ -369,24 +369,23 @@ export function InputFieldBadge({ }) { return (
    {icon}
    {label} diff --git a/frontend/uikit/src/Radio/Radio.tsx b/frontend/uikit/src/Radio/Radio.tsx index 98125583c..129574b0b 100644 --- a/frontend/uikit/src/Radio/Radio.tsx +++ b/frontend/uikit/src/Radio/Radio.tsx @@ -63,12 +63,12 @@ export function Radio({ friction: 100, }, initial: { - tickColor: color(disabled ? "disabledBorder" : "controlSurface"), + tickColor: color(disabled ? "disabledBorder" : "accentContent"), ringColor: color("accent"), tickProgress: 0, }, from: { - tickColor: color(disabled ? "disabledBorder" : "controlSurface"), + tickColor: color(disabled ? "disabledBorder" : "accentContent"), ringColor: color("accent"), tickProgress: 1, }, @@ -76,7 +76,7 @@ export function Radio({ tickProgress: 0, }, leave: { - tickColor: color(disabled ? "disabledBorder" : "controlSurface"), + tickColor: color(disabled ? "disabledBorder" : "accentContent"), ringColor: color("controlBorder"), tickProgress: 1, }, diff --git a/frontend/uikit/src/Theme/Theme.tsx b/frontend/uikit/src/Theme/Theme.tsx index 417671a71..8d804d5a5 100644 --- a/frontend/uikit/src/Theme/Theme.tsx +++ b/frontend/uikit/src/Theme/Theme.tsx @@ -73,7 +73,7 @@ export const colors = { "white": "#FFFFFF", "primary:green": "#88FD9D", - "primary:hover": "#2EFF54", + "primary:hover": "#88FD9D", "primary:disabled": "#88FD9D80", "primary:20": "#88FD9D33", @@ -121,7 +121,7 @@ export const lightTheme = { content: "white:100", contentAlt: "white:70", - contentAlt2: "white:40", + contentAlt2: "white:70", surface: "white:10", controlSurface: "white:10", @@ -216,7 +216,7 @@ export const lightTheme = { position: "navy", positionContent: "white:100", - positionContentAlt: "white:40", + positionContentAlt: "white:70", riskGradientDimmed1: "error:20", riskGradientDimmed2: "white:20", diff --git a/frontend/uikit/src/token-icons/eth.svg b/frontend/uikit/src/token-icons/eth.svg index 5491ba804..b1e3b3846 100644 --- a/frontend/uikit/src/token-icons/eth.svg +++ b/frontend/uikit/src/token-icons/eth.svg @@ -1 +1,5 @@ - + + + + + diff --git a/frontend/uikit/src/token-icons/wankr.svg b/frontend/uikit/src/token-icons/wankr.svg new file mode 100644 index 000000000..dd7062b7a --- /dev/null +++ b/frontend/uikit/src/token-icons/wankr.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/uikit/src/tokens.ts b/frontend/uikit/src/tokens.ts index 755fe9c4f..6dc4668fc 100644 --- a/frontend/uikit/src/tokens.ts +++ b/frontend/uikit/src/tokens.ts @@ -1,5 +1,6 @@ import tokenBold from "./token-icons/bold.svg"; import tokenEth from "./token-icons/eth.svg"; +import tokenWankr from "./token-icons/wankr.svg"; // any external token, without a known symbol export type ExternalToken = { @@ -17,6 +18,7 @@ export type Token = ExternalToken & { export type TokenSymbol = | "BOLD" + | "MINT" | "WANKR" | "USN"; @@ -55,7 +57,7 @@ export const BOLD: Token = { export const WANKR: CollateralToken = { collateralRatio: 1.1, - icon: tokenEth, + icon: tokenWankr, name: "wANKR", symbol: "WANKR" as const, } as const; From 78f40b1f52e074bfe4d4ae9e070ac628a77dc05a Mon Sep 17 00:00:00 2001 From: tenshou Date: Tue, 9 Sep 2025 22:13:41 +0700 Subject: [PATCH 15/19] feat: ALIGN-70 fix ui --- frontend/app/src/comps/AppLayout/BottomBar.tsx | 4 ++-- frontend/app/src/comps/Screen/ScreenCard.tsx | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/app/src/comps/AppLayout/BottomBar.tsx b/frontend/app/src/comps/AppLayout/BottomBar.tsx index 1a7a19135..e97c01ce0 100644 --- a/frontend/app/src/comps/AppLayout/BottomBar.tsx +++ b/frontend/app/src/comps/AppLayout/BottomBar.tsx @@ -84,10 +84,10 @@ export function BottomBar() { className={css({ display: "flex", alignItems: "center", - gap: 4, + gap: 12, })} > - + TVL{" "} {tvl && ( diff --git a/frontend/app/src/comps/Screen/ScreenCard.tsx b/frontend/app/src/comps/Screen/ScreenCard.tsx index d27e4fe4d..e430c1a04 100644 --- a/frontend/app/src/comps/Screen/ScreenCard.tsx +++ b/frontend/app/src/comps/Screen/ScreenCard.tsx @@ -84,8 +84,7 @@ export function ScreenCard({ overflow: "hidden", position: "absolute", inset: 0, - color: "loadingGradientContent", - background: "loadingGradient1", + color: "content", borderRadius: 8, })} style={{ From 889ff883df76f7250d97efdd8f55766830a13c08 Mon Sep 17 00:00:00 2001 From: tenshou Date: Tue, 9 Sep 2025 23:47:44 +0700 Subject: [PATCH 16/19] feat: ALIGN-70 fix error --- frontend/uikit/src/InputField/InputField.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/uikit/src/InputField/InputField.tsx b/frontend/uikit/src/InputField/InputField.tsx index 93c2bdd5a..e30dcd243 100644 --- a/frontend/uikit/src/InputField/InputField.tsx +++ b/frontend/uikit/src/InputField/InputField.tsx @@ -333,12 +333,12 @@ function Drawer({ className={css({ display: "flex", alignItems: "center", - padding: "8px 16px 0", + padding: "0 16px", height: 48, fontSize: 14, borderWidth: 1, borderStyle: "solid", - borderRadius: "0 0 8px 8px", + borderRadius: 28, })} style={{ marginTop, From d7ce03cd9edaf5478347c73547b796231b118171 Mon Sep 17 00:00:00 2001 From: tenshou Date: Tue, 9 Sep 2025 23:50:16 +0700 Subject: [PATCH 17/19] feat: ALIGN-70 fix top --- frontend/uikit/src/InputField/InputField.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/uikit/src/InputField/InputField.tsx b/frontend/uikit/src/InputField/InputField.tsx index e30dcd243..f4288eb5f 100644 --- a/frontend/uikit/src/InputField/InputField.tsx +++ b/frontend/uikit/src/InputField/InputField.tsx @@ -296,7 +296,7 @@ function Drawer({ }, enter: { contentOpacity: 1, - marginTop: 0, + marginTop: 20, }, leave: { contentOpacity: 0, From ffa2491cd57efcff316b6bdec9e4eaf5a45bfe1d Mon Sep 17 00:00:00 2001 From: Artur Korotenko Date: Wed, 10 Sep 2025 12:58:37 +0500 Subject: [PATCH 18/19] chore: update subgraph --- frontend/app/.env | 2 +- subgraph/networks.json | 4 ++-- subgraph/subgraph.yaml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/app/.env b/frontend/app/.env index 6e60d5a1a..d15a07f6c 100644 --- a/frontend/app/.env +++ b/frontend/app/.env @@ -11,7 +11,7 @@ NEXT_PUBLIC_CHAIN_ID=267 NEXT_PUBLIC_CHAIN_NAME=NeuraTestnet NEXT_PUBLIC_CHAIN_RPC_URL=https://testnet.rpc.neuraprotocol.io/ -NEXT_PUBLIC_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_cmevjjq0rde9e01ubb66qg6es/subgraphs/liquity-fork/0.0.1/gn +NEXT_PUBLIC_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_cmfdntx4sbnt901x20jdxhcwz/subgraphs/align-mint-cdp-testnet/1.0.0/gn NEXT_PUBLIC_LIQUITY_STATS_URL=https://api.liquity.org/v2/testnet/sepolia.json NEXT_PUBLIC_COLL_0_TOKEN_ID=WANKR diff --git a/subgraph/networks.json b/subgraph/networks.json index 04ec42d61..083170796 100644 --- a/subgraph/networks.json +++ b/subgraph/networks.json @@ -18,8 +18,8 @@ }, "neura-testnet": { "BoldToken": { - "address": "0xc13441f42e0aca71ea246aba87bdcdf646a456dd", - "startBlock": 100000 + "address": "0x2d65ae4a0a4945922f71b71f379e8e937ac84863", + "startBlock": 4188567 } } } diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 274274356..b5c6e0214 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -6,8 +6,8 @@ dataSources: name: BoldToken source: abi: BoldToken - address: "0xc13441f42e0aca71ea246aba87bdcdf646a456dd" - startBlock: 100000 + address: "0x2d65ae4a0a4945922f71b71f379e8e937ac84863" + startBlock: 4188567 mapping: kind: ethereum/events apiVersion: 0.0.9 @@ -88,4 +88,4 @@ templates: file: ../contracts/out/TroveNFT.sol/TroveNFT.json eventHandlers: - event: Transfer(indexed address,indexed address,indexed uint256) - handler: handleTransfer \ No newline at end of file + handler: handleTransfer From 20a6abcc5faa8164bfa8fa3002e85e511332d317 Mon Sep 17 00:00:00 2001 From: tenshou Date: Wed, 10 Sep 2025 20:14:32 +0700 Subject: [PATCH 19/19] feat: ALIGN-70 add prefix $, fix title --- frontend/app/src/app/layout.tsx | 2 +- .../EarnPositionSummary.tsx | 6 +-- frontend/app/src/content.tsx | 38 +++++++++---------- frontend/uikit/src/Tooltip/InfoTooltip.tsx | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/frontend/app/src/app/layout.tsx b/frontend/app/src/app/layout.tsx index 0774ddd85..3978b2ac9 100644 --- a/frontend/app/src/app/layout.tsx +++ b/frontend/app/src/app/layout.tsx @@ -20,7 +20,7 @@ import { Analytics } from "@vercel/analytics/react"; import { GeistSans } from "geist/font/sans"; export const metadata: Metadata = { - title: content.appName, + title: `${content.appName} | CDP`, icons: "/favicon.svg", }; diff --git a/frontend/app/src/comps/EarnPositionSummary/EarnPositionSummary.tsx b/frontend/app/src/comps/EarnPositionSummary/EarnPositionSummary.tsx index 110487332..28a4ac7f9 100644 --- a/frontend/app/src/comps/EarnPositionSummary/EarnPositionSummary.tsx +++ b/frontend/app/src/comps/EarnPositionSummary/EarnPositionSummary.tsx @@ -94,7 +94,7 @@ export function EarnPositionSummary({ + "deposits earned over the last 24 hours.", footerLink: { label: "Check Dune for more details", - href: "https://dune.com/liquity/liquity-v2", + href: "", }, }} /> @@ -127,7 +127,7 @@ export function EarnPositionSummary({ + "deposits earned over the past 7 days.", footerLink: { label: "Check Dune for more details", - href: "https://dune.com/liquity/liquity-v2", + href: "", }, }} /> @@ -146,7 +146,7 @@ export function EarnPositionSummary({ />
    - Total amount of MINT deposited in this stability pool. + Total amount of $MINT deposited in this stability pool. } diff --git a/frontend/app/src/content.tsx b/frontend/app/src/content.tsx index 2abae03bb..32317fd23 100644 --- a/frontend/app/src/content.tsx +++ b/frontend/app/src/content.tsx @@ -43,20 +43,20 @@ export default { loanRedemptionRisk: [ "Redemption risk", <> - Users paying the lowest interest rate can get redeemed, if the price of MINT falls below $1. By raising your + Users paying the lowest interest rate can get redeemed, if the price of $MINT falls below $1. By raising your interest rate, you reduce this risk. , ], loanLtv: [ "Loan-to-value ratio", <> - The ratio between the amount of MINT borrowed and the deposited collateral (in USD). + The ratio between the amount of $MINT borrowed and the deposited collateral (in USD). , ], loanMaxLtv: [ "Maximum Loan-To-Value (LTV) Ratio", <> - The maximum ratio between the USD value of a loan (in MINT) and the collateral backing it. The LTV will + The maximum ratio between the USD value of a loan (in $MINT) and the collateral backing it. The LTV will fluctuate as the price of the collateral changes. To decrease the LTV add more colateral or reduce debt. , ], @@ -67,14 +67,14 @@ export default { ethPrice: [ "ANKR Price", <> - The current price of ANKR, as reported by the oracle. The ANKR price is used to calculate the Loan-To-Value + The current price of $ANKR, as reported by the oracle. The $ANKR price is used to calculate the Loan-To-Value (LTV) ratio of a loan. , ], interestRateBoldPerYear: [ "Interest rate", <> - The annualized interest amount in MINT for the selected interest rate. The accumulated interest is added to the + The annualized interest amount in $MINT for the selected interest rate. The accumulated interest is added to the loan. , ], @@ -89,7 +89,7 @@ export default { heading: "Your collateral and debt are reduced by the same value.", body: ( <> - When MINT trades for under $1, anyone can redeem positions to get MINT back at $1. Positions with the lowest + When $MINT trades for under $1, anyone can redeem positions to get $MINT back at $1. Positions with the lowest interest rate get redeemed first. ), @@ -101,14 +101,14 @@ export default { title: "Redemptions in a nutshell", subtitle: ( <> - Redemptions help maintain MINT’s peg in a decentralized way. If a user is redeemed, their collateral and debt + Redemptions help maintain $MINT’s peg in a decentralized way. If a user is redeemed, their collateral and debt are reduced equally, resulting in no net loss. ), infoItems: [ { icon: "bold", - text: "Redemptions occur when MINT drops below $1.", + text: "Redemptions occur when $MINT drops below $1.", }, { icon: "redemption", @@ -197,7 +197,7 @@ export default { actions: { borrow: { title: "Borrow", - description: "Mint MINT against your collateral at whatever interest rate you want", + description: "Mint $MINT against your collateral at whatever interest rate you want", }, multiply: { title: "Multiply", @@ -205,12 +205,12 @@ export default { }, earn: { title: "Earn with MINT", - description: "Deposit MINT to earn protocol revenues and liquidation proceeds", + description: "Deposit $MINT to earn protocol revenues and liquidation proceeds", }, }, earnTable: { title: "Earn rewards with MINT", - subtitle: "Earn MINT & (staked) ANKR rewards by depositing your MINT in a stability pool", + subtitle: "Earn $MINT & (staked) $ANKR rewards by depositing your $MINT in a stability pool", forksInfo: { text: ( <> @@ -246,7 +246,7 @@ export default { ], spTvl: [ "Total Value Locked", - "The total amount of MINT deposited in each stability pool.", + "The total amount of $MINT deposited in each stability pool.", ], borrowTvl: [ "Total Value Locked", @@ -277,7 +277,7 @@ export default { action: "Next: Summary", infoTooltips: { interestRateSuggestions: [ - "Positions with lower interest rates are the first to be redeemed by MINT holders.", + "Positions with lower interest rates are the first to be redeemed by $MINT holders.", ], }, }, @@ -309,7 +309,7 @@ export default { ], interestRateSuggestions: [ <> - Positions with lower interest rates are the first to be redeemed by MINT holders. + Positions with lower interest rates are the first to be redeemed by $MINT holders. , ], exposure: [ @@ -332,7 +332,7 @@ export default { ), subheading: ( <> - A MINT deposit in a stability pool earns rewards from the fees that users pay on their loans. Also, the MINT may + A $MINT deposit in a stability pool earns rewards from the fees that users pay on their loans. Also, the $MINT may be swapped to collateral in case the system needs to liquidate positions. ), @@ -344,7 +344,7 @@ export default { }, infoTooltips: { tvl: (collateral: N) => [ - <>Total MINT covering {collateral}-backed position liquidations, + <>Total $MINT covering {collateral}-backed position liquidations, ], }, }, @@ -392,10 +392,10 @@ export default { }, infoTooltips: { tvl: (collateral: N) => [ - <>Total MINT covering {collateral}-backed position liquidations., + <>Total $MINT covering {collateral}-backed position liquidations., ], depositPoolShare: [ - "Percentage of your MINT deposit compared to the total deposited in this stability pool.", + "Percentage of your $MINT deposit compared to the total deposited in this stability pool.", ], alsoClaimRewardsDeposit: [ <> @@ -410,7 +410,7 @@ export default { , ], currentApr: [ - "Average annualized return for MINT deposits over the past 7 days.", + "Average annualized return for $MINT deposits over the past 7 days.", ], rewardsEth: [ "ANKR rewards", diff --git a/frontend/uikit/src/Tooltip/InfoTooltip.tsx b/frontend/uikit/src/Tooltip/InfoTooltip.tsx index 6669369d6..694db7972 100644 --- a/frontend/uikit/src/Tooltip/InfoTooltip.tsx +++ b/frontend/uikit/src/Tooltip/InfoTooltip.tsx @@ -54,7 +54,7 @@ export function InfoTooltip( })} > {props.content.body} - {props.content.footerLink && ( + {props.content.footerLink?.href && (