diff --git a/abi/TopUp/AllowedTokensRegistry.abi.json b/abi/TopUp/AllowedTokensRegistry.abi.json new file mode 100644 index 00000000..748702b4 --- /dev/null +++ b/abi/TopUp/AllowedTokensRegistry.abi.json @@ -0,0 +1,259 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_admin", "type": "address" }, + { + "internalType": "address[]", + "name": "_addTokenToAllowedListRoleHolders", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "_removeTokenFromAllowedListRoleHolders", + "type": "address[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_token", + "type": "address" + } + ], + "name": "TokenAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_token", + "type": "address" + } + ], + "name": "TokenRemoved", + "type": "event" + }, + { + "inputs": [], + "name": "ADD_TOKEN_TO_ALLOWED_LIST_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "addToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "allowedTokens", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getAllowedTokens", + "outputs": [ + { "internalType": "address[]", "name": "", "type": "address[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" } + ], + "name": "getRoleAdmin", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "hasRole", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "isTokenAllowed", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_tokenAmount", "type": "uint256" }, + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "normalizeAmount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "removeToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } + ], + "name": "supportsInterface", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/abi/TopUp/TopUpWithLimitsStables.abi.json b/abi/TopUp/TopUpWithLimitsStables.abi.json new file mode 100644 index 00000000..a5314a6b --- /dev/null +++ b/abi/TopUp/TopUpWithLimitsStables.abi.json @@ -0,0 +1,103 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_trustedCaller", + "type": "address" + }, + { + "internalType": "address", + "name": "_allowedRecipientsRegistry", + "type": "address" + }, + { + "internalType": "address", + "name": "_allowedTokensRegistry", + "type": "address" + }, + { "internalType": "address", "name": "_finance", "type": "address" }, + { "internalType": "address", "name": "_easyTrack", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "allowedRecipientsRegistry", + "outputs": [ + { + "internalType": "contract IAllowedRecipientsRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allowedTokensRegistry", + "outputs": [ + { + "internalType": "contract IAllowedTokensRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_creator", "type": "address" }, + { "internalType": "bytes", "name": "_evmScriptCallData", "type": "bytes" } + ], + "name": "createEVMScript", + "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes", "name": "_evmScriptCallData", "type": "bytes" } + ], + "name": "decodeEVMScriptCallData", + "outputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { + "internalType": "address[]", + "name": "recipients", + "type": "address[]" + }, + { "internalType": "uint256[]", "name": "amounts", "type": "uint256[]" } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "easyTrack", + "outputs": [ + { "internalType": "contract IEasyTrack", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "finance", + "outputs": [ + { "internalType": "contract IFinance", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "trustedCaller", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/modules/blockChain/constants.ts b/modules/blockChain/constants.ts new file mode 100644 index 00000000..70c2ceed --- /dev/null +++ b/modules/blockChain/constants.ts @@ -0,0 +1 @@ +export const DEFAULT_DECIMALS = 18 diff --git a/modules/blockChain/contractAddresses.ts b/modules/blockChain/contractAddresses.ts index 6ea1e18f..71ea30a3 100644 --- a/modules/blockChain/contractAddresses.ts +++ b/modules/blockChain/contractAddresses.ts @@ -71,17 +71,17 @@ export const LegoDAIRegistry: ChainAddressMap = { [CHAINS.Goerli]: '0x5884f5849414D4317d175fEc144e2F87f699B082', } -export const RccDAIRegistry: ChainAddressMap = { +export const RccStablesRegistry: ChainAddressMap = { [CHAINS.Mainnet]: '0xDc1A0C7849150f466F07d48b38eAA6cE99079f80', [CHAINS.Goerli]: '0x1440E8aDbE3a42a9EDB4b30059df8F6c35205a15', } -export const PmlDAIRegistry: ChainAddressMap = { +export const PmlStablesRegistry: ChainAddressMap = { [CHAINS.Mainnet]: '0xDFfCD3BF14796a62a804c1B16F877Cf7120379dB', [CHAINS.Goerli]: '0xAadfBd1ADE92d85c967f4aE096141F0F802F46Db', } -export const AtcDAIRegistry: ChainAddressMap = { +export const AtcStablesRegistry: ChainAddressMap = { [CHAINS.Mainnet]: '0xe07305F43B11F230EaA951002F6a55a16419B707', [CHAINS.Goerli]: '0xedD3B813275e1A88c2283FAfa5bf5396938ef59e', } @@ -123,3 +123,7 @@ export const SDVTRegistry: ChainAddressMap = { [CHAINS.Goerli]: '0x6370FA71b9Fd83aFC4196ee189a0d348C90E93b0', [CHAINS.Holesky]: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', } + +export const AllowedTokensRegistry: ChainAddressMap = { + [CHAINS.Goerli]: '0xeda5a9F02a580B4A879aEA65E2a7B7fEc0956b0E', +} diff --git a/modules/blockChain/contracts.ts b/modules/blockChain/contracts.ts index 8ff5b56d..3eba6d68 100644 --- a/modules/blockChain/contracts.ts +++ b/modules/blockChain/contracts.ts @@ -124,31 +124,25 @@ export const ContractEvmLegoDAITopUp = createContractHelpers({ address: EvmAddressesByType[MotionType.LegoDAITopUp], }) -export const ContractRccDAIRegistry = createContractHelpers({ - factory: TypeChain.RegistryWithLimitsAbi__factory, - address: CONTRACT_ADDRESSES.RccDAIRegistry, -}) - +/** + * @deprecated + */ export const ContractEvmRccDAITopUp = createContractHelpers({ factory: TypeChain.TopUpWithLimitsAbi__factory, address: EvmAddressesByType[MotionType.RccDAITopUp], }) -export const ContractPmlDAIRegistry = createContractHelpers({ - factory: TypeChain.RegistryWithLimitsAbi__factory, - address: CONTRACT_ADDRESSES.PmlDAIRegistry, -}) - +/** + * @deprecated + */ export const ContractEvmPmlDAITopUp = createContractHelpers({ factory: TypeChain.TopUpWithLimitsAbi__factory, address: EvmAddressesByType[MotionType.PmlDAITopUp], }) -export const ContractAtcDAIRegistry = createContractHelpers({ - factory: TypeChain.RegistryWithLimitsAbi__factory, - address: CONTRACT_ADDRESSES.AtcDAIRegistry, -}) - +/** + * @deprecated + */ export const ContractEvmAtcDAITopUp = createContractHelpers({ factory: TypeChain.TopUpWithLimitsAbi__factory, address: EvmAddressesByType[MotionType.AtcDAITopUp], @@ -329,3 +323,38 @@ export const ContractSDVTTargetValidatorLimitsUpdate = createContractHelpers({ factory: TypeChain.UpdateTargetValidatorLimitsAbi__factory, address: EvmAddressesByType[MotionType.SDVTTargetValidatorLimitsUpdate], }) + +export const ContractAllowedTokensRegistry = createContractHelpers({ + factory: TypeChain.AllowedTokensRegistryAbi__factory, + address: CONTRACT_ADDRESSES.AllowedTokensRegistry, +}) + +export const ContractRccStablesRegistry = createContractHelpers({ + factory: TypeChain.RegistryWithLimitsAbi__factory, + address: CONTRACT_ADDRESSES.RccStablesRegistry, +}) + +export const ContractEvmRccStablesTopUp = createContractHelpers({ + factory: TypeChain.TopUpWithLimitsStablesAbi__factory, + address: EvmAddressesByType[MotionType.RccStablesTopUp], +}) + +export const ContractPmlStablesRegistry = createContractHelpers({ + factory: TypeChain.RegistryWithLimitsAbi__factory, + address: CONTRACT_ADDRESSES.PmlStablesRegistry, +}) + +export const ContractEvmPmlStablesTopUp = createContractHelpers({ + factory: TypeChain.TopUpWithLimitsStablesAbi__factory, + address: EvmAddressesByType[MotionType.PmlStablesTopUp], +}) + +export const ContractAtcStablesRegistry = createContractHelpers({ + factory: TypeChain.RegistryWithLimitsAbi__factory, + address: CONTRACT_ADDRESSES.AtcStablesRegistry, +}) + +export const ContractEvmAtcStablesTopUp = createContractHelpers({ + factory: TypeChain.TopUpWithLimitsStablesAbi__factory, + address: EvmAddressesByType[MotionType.AtcStablesTopUp], +}) diff --git a/modules/motions/evmAddresses.ts b/modules/motions/evmAddresses.ts index 024a1807..0f6c738c 100644 --- a/modules/motions/evmAddresses.ts +++ b/modules/motions/evmAddresses.ts @@ -88,6 +88,9 @@ export const EvmAddressesByChain: EvmAddresses = { [MotionType.RccDAITopUp]: '0xd0411e7c4A24E7d4509D5F13AEd19aeb8e5644AB', [MotionType.PmlDAITopUp]: '0xc749aD24572263887Bc888d3Cb854FCD50eCCB61', [MotionType.AtcDAITopUp]: '0xF4b8b5760EE4b5c5Cb154edd0f0841465d821006', + [MotionType.RccStablesTopUp]: '0xd50eE42B31Bc500409B7caD99A2D16FB1Bfecdc6', + [MotionType.PmlStablesTopUp]: '0x5F379512158A46ab7a91f8b799A97691eC498b9a', + [MotionType.AtcStablesTopUp]: '0xB87300405050e7f1dBC35c6C9ce9ea4417D3Ad81', [MotionType.StethRewardProgramAdd]: '0x785A8B1CDC03Bb191670Ed4696e9ED5B11Af910A', [MotionType.StethRewardProgramRemove]: @@ -159,9 +162,6 @@ export const EvmAddressesByChain: EvmAddresses = { [MotionType.AllowedRecipientTopUpTrpLdo]: '', [MotionType.LegoLDOTopUp]: '', [MotionType.LegoDAITopUp]: '', - [MotionType.RccDAITopUp]: '', - [MotionType.PmlDAITopUp]: '', - [MotionType.AtcDAITopUp]: '', [MotionType.StethRewardProgramAdd]: '', [MotionType.StethRewardProgramRemove]: '', [MotionType.StethRewardProgramTopUp]: '', diff --git a/modules/motions/hooks/useAllowedTokensRegistry.ts b/modules/motions/hooks/useAllowedTokensRegistry.ts new file mode 100644 index 00000000..7ebeb4d9 --- /dev/null +++ b/modules/motions/hooks/useAllowedTokensRegistry.ts @@ -0,0 +1,72 @@ +import { ContractAllowedTokensRegistry } from 'modules/blockChain/contracts' +import { useSWR } from 'modules/network/hooks/useSwr' +import { useWeb3 } from 'modules/blockChain/hooks/useWeb3' +import { AllowedTokensRegistryAbi } from 'generated' +import { DAI } from 'modules/blockChain/contractAddresses' +import { connectERC20Contract } from '../utils/connectTokenContract' + +export function useAllowedTokens() { + const { chainId, library } = useWeb3() + + const { data, initialLoading } = useSWR( + `allowed-tokens-${chainId}`, + async () => { + if (!library) { + return + } + + let registry: AllowedTokensRegistryAbi | undefined + try { + registry = ContractAllowedTokensRegistry.connectRpc({ chainId }) + } catch (error) { + // Fallback for motions without registry support + + const address = DAI[chainId] + if (!address) { + return + } + + const daiContract = connectERC20Contract(address, chainId) + const decimals = await daiContract.decimals() + + return { + allowedTokens: [ + { + address, + label: 'DAI', + decimals, + }, + ], + decimalsMap: { [address]: decimals }, + } + } + const tokensAddresses = await registry.getAllowedTokens() + + const allowedTokens = await Promise.all( + tokensAddresses.map(async tokenAddress => { + const tokenContract = connectERC20Contract(tokenAddress, chainId) + + const label = await tokenContract.symbol() + const decimals = await tokenContract.decimals() + + return { address: tokenAddress, label, decimals } + }), + ) + + const tokensDecimalsMap: Record = {} + + for (const token of allowedTokens) { + tokensDecimalsMap[token.address] = token.decimals + } + + return { allowedTokens, tokensDecimalsMap } + }, + { revalidateOnFocus: false, revalidateOnReconnect: false }, + ) + + return { + allowedTokens: data?.allowedTokens, + tokensDecimalsMap: data?.tokensDecimalsMap, + initialLoading, + } +} diff --git a/modules/motions/hooks/useContractEvmScript.ts b/modules/motions/hooks/useContractEvmScript.ts index 22a971cc..3a7f61ca 100644 --- a/modules/motions/hooks/useContractEvmScript.ts +++ b/modules/motions/hooks/useContractEvmScript.ts @@ -64,6 +64,9 @@ export const EVM_CONTRACTS = { CONTRACTS.ContractSDVTNodeOperatorNamesSet, [MotionType.SDVTNodeOperatorManagerChange]: CONTRACTS.ContractSDVTNodeOperatorManagerChange, + [MotionType.RccStablesTopUp]: CONTRACTS.ContractEvmRccStablesTopUp, + [MotionType.PmlStablesTopUp]: CONTRACTS.ContractEvmPmlStablesTopUp, + [MotionType.AtcStablesTopUp]: CONTRACTS.ContractEvmAtcStablesTopUp, } as const export function useContractEvmScript( diff --git a/modules/motions/hooks/useEVMScriptDecoder.ts b/modules/motions/hooks/useEVMScriptDecoder.ts index a9ba20e4..0683faa9 100644 --- a/modules/motions/hooks/useEVMScriptDecoder.ts +++ b/modules/motions/hooks/useEVMScriptDecoder.ts @@ -35,9 +35,9 @@ export function useEVMScriptDecoder() { abis.AllowedRecipientsRegistryAbi__factory.abi, [KEYS.LegoLDORegistry]: abis.RegistryWithLimitsAbi__factory.abi, [KEYS.LegoDAIRegistry]: abis.RegistryWithLimitsAbi__factory.abi, - [KEYS.RccDAIRegistry]: abis.RegistryWithLimitsAbi__factory.abi, - [KEYS.PmlDAIRegistry]: abis.RegistryWithLimitsAbi__factory.abi, - [KEYS.AtcDAIRegistry]: abis.RegistryWithLimitsAbi__factory.abi, + [KEYS.RccStablesRegistry]: abis.RegistryWithLimitsAbi__factory.abi, + [KEYS.PmlStablesRegistry]: abis.RegistryWithLimitsAbi__factory.abi, + [KEYS.AtcStablesRegistry]: abis.RegistryWithLimitsAbi__factory.abi, [KEYS.gasFunderETHRegistry]: abis.RegistryWithLimitsAbi__factory.abi, [KEYS.StethRewardProgramRegistry]: abis.RegistryWithLimitsAbi__factory.abi, diff --git a/modules/motions/hooks/useMotionTokenData.ts b/modules/motions/hooks/useMotionTokenData.ts new file mode 100644 index 00000000..2fe4e642 --- /dev/null +++ b/modules/motions/hooks/useMotionTokenData.ts @@ -0,0 +1,32 @@ +import { DEFAULT_DECIMALS } from 'modules/blockChain/constants' +import { useWeb3 } from 'modules/blockChain/hooks/useWeb3' +import { useSWR } from 'modules/network/hooks/useSwr' +import { connectERC20Contract } from '../utils/connectTokenContract' + +export function useMotionTokenData(tokenAddress: string | null | undefined) { + const { chainId } = useWeb3() + const { data: tokenData, initialLoading: isTokenDataLoading } = useSWR( + tokenAddress?.length ? `token-data-${tokenAddress}-${chainId}` : null, + async () => { + if (!tokenAddress?.length) return null + try { + const tokenContract = connectERC20Contract(tokenAddress, chainId) + const label = await tokenContract.symbol() + const decimals = await tokenContract.decimals() + + return { + label, + address: tokenAddress, + decimals, + } + } catch (error) { + return { + label: 'Unknown token', + address: tokenAddress, + decimals: DEFAULT_DECIMALS, + } + } + }, + ) + return { tokenData, isTokenDataLoading } +} diff --git a/modules/motions/hooks/usePeriodLimitsInfo.ts b/modules/motions/hooks/usePeriodLimitsInfo.ts index 3861f2f0..f1f0f268 100644 --- a/modules/motions/hooks/usePeriodLimitsInfo.ts +++ b/modules/motions/hooks/usePeriodLimitsInfo.ts @@ -7,10 +7,10 @@ import { ContractAllowedRecipientReferralDaiRegistry, ContractAllowedRecipientTrpLdoRegistry, ContractLegoLDORegistry, - ContractRccDAIRegistry, - ContractAtcDAIRegistry, + ContractRccStablesRegistry, + ContractAtcStablesRegistry, ContractGasFunderETHRegistry, - ContractPmlDAIRegistry, + ContractPmlStablesRegistry, ContractStethRewardProgramRegistry, } from 'modules/blockChain/contracts' import { useWeb3 } from 'modules/blockChain/hooks/useWeb3' @@ -103,9 +103,9 @@ const registryByMotionType: { } = { [MotionType.LegoLDOTopUp]: ContractLegoLDORegistry, [MotionType.LegoDAITopUp]: ContractLegoDAIRegistry, - [MotionType.RccDAITopUp]: ContractRccDAIRegistry, - [MotionType.PmlDAITopUp]: ContractPmlDAIRegistry, - [MotionType.AtcDAITopUp]: ContractAtcDAIRegistry, + [MotionType.RccStablesTopUp]: ContractRccStablesRegistry, + [MotionType.PmlStablesTopUp]: ContractPmlStablesRegistry, + [MotionType.AtcStablesTopUp]: ContractAtcStablesRegistry, [MotionType.GasFunderETHTopUp]: ContractGasFunderETHRegistry, [MotionType.AllowedRecipientTopUp]: ContractAllowedRecipientRegistry, [MotionType.AllowedRecipientTopUpReferralDai]: diff --git a/modules/motions/hooks/useRegistryWithLimits.ts b/modules/motions/hooks/useRegistryWithLimits.ts index f05fb835..0da049be 100644 --- a/modules/motions/hooks/useRegistryWithLimits.ts +++ b/modules/motions/hooks/useRegistryWithLimits.ts @@ -4,9 +4,8 @@ import { useWeb3 } from 'modules/blockChain/hooks/useWeb3' import { ContractLegoLDORegistry, ContractLegoDAIRegistry, - ContractRccDAIRegistry, - ContractPmlDAIRegistry, - ContractAtcDAIRegistry, + ContractPmlStablesRegistry, + ContractAtcStablesRegistry, ContractGasFunderETHRegistry, ContractAllowedRecipientRegistry, ContractAllowedRecipientReferralDaiRegistry, @@ -14,6 +13,7 @@ import { ContractStethRewardProgramRegistry, ContractStethGasSupplyRegistry, ContractRewardsShareProgramRegistry, + ContractRccStablesRegistry, } from 'modules/blockChain/contracts' import { getEventsRecipientAdded } from 'modules/motions/utils' import { MotionType } from 'modules/motions/types' @@ -28,9 +28,9 @@ type AllowedRecipient = { export const REGISTRY_WITH_LIMITS_BY_MOTION_TYPE = { [MotionType.LegoLDOTopUp]: ContractLegoLDORegistry, [MotionType.LegoDAITopUp]: ContractLegoDAIRegistry, - [MotionType.RccDAITopUp]: ContractRccDAIRegistry, - [MotionType.PmlDAITopUp]: ContractPmlDAIRegistry, - [MotionType.AtcDAITopUp]: ContractAtcDAIRegistry, + [MotionType.RccDAITopUp]: ContractRccStablesRegistry, + [MotionType.PmlDAITopUp]: ContractPmlStablesRegistry, + [MotionType.AtcDAITopUp]: ContractAtcStablesRegistry, [MotionType.GasFunderETHTopUp]: ContractGasFunderETHRegistry, [MotionType.AllowedRecipientTopUp]: ContractAllowedRecipientRegistry, [MotionType.AllowedRecipientRemove]: ContractAllowedRecipientRegistry, @@ -52,6 +52,9 @@ export const REGISTRY_WITH_LIMITS_BY_MOTION_TYPE = { [MotionType.RewardsShareProgramAdd]: ContractRewardsShareProgramRegistry, [MotionType.RewardsShareProgramRemove]: ContractRewardsShareProgramRegistry, [MotionType.RewardsShareProgramTopUp]: ContractRewardsShareProgramRegistry, + [MotionType.RccStablesTopUp]: ContractRccStablesRegistry, + [MotionType.PmlStablesTopUp]: ContractPmlStablesRegistry, + [MotionType.AtcStablesTopUp]: ContractAtcStablesRegistry, } as const type HookArgs = { diff --git a/modules/motions/providers/motionDetailed.tsx b/modules/motions/providers/motionDetailed.tsx index 9daab59a..94fcd21a 100644 --- a/modules/motions/providers/motionDetailed.tsx +++ b/modules/motions/providers/motionDetailed.tsx @@ -1,5 +1,5 @@ import { FC, createContext, useMemo, useCallback } from 'react' -import { formatEther } from 'ethers/lib/utils' +import { formatEther, formatUnits } from 'ethers/lib/utils' import invariant from 'tiny-invariant' import { useSWR } from 'modules/network/hooks/useSwr' @@ -23,6 +23,29 @@ import { useTransactionSender } from 'modules/blockChain/hooks/useTransactionSen import { EvmUnrecognized } from 'modules/motions/evmAddresses' import { Motion, MotionStatus } from 'modules/motions/types' import { ContractEasyTrack } from 'modules/blockChain/contracts' +import { useMotionTokenData } from '../hooks/useMotionTokenData' +import { BigNumber } from 'ethers' +import { DEFAULT_DECIMALS } from 'modules/blockChain/constants' + +const getTopUpAmount = (callData: any, tokenDecimals = DEFAULT_DECIMALS) => { + if (!callData) { + return 0 + } + + if (callData[1]?.[0]?._isBigNumber) { + return Number(formatEther(callData[1][0])) || 0 + } + + if (Array.isArray(callData.amounts)) { + const amountsSum = (callData.amounts as BigNumber[]).reduce((acc, amount) => + acc.add(amount), + ) + + return Number(formatUnits(amountsSum, tokenDecimals)) + } + + return 0 +} export type MotionDetailedValue = { isArchived: boolean @@ -87,13 +110,19 @@ export const MotionDetailedProvider: FC = props => { }, ) + const { tokenData } = useMotionTokenData( + callData?.token ?? topUpToken.address, + ) + const isArchived = motion.status !== MotionStatus.ACTIVE && motion.status !== MotionStatus.PENDING - const motionTopUpAmount: number = - (callData?.[1]?.[0]?._isBigNumber && Number(formatEther(callData[1][0]))) || - 0 // TODO: refactor - const motionTopUpToken = topUpToken.label || '' + + const motionTopUpAmount = getTopUpAmount( + callData, + tokenData?.decimals ?? DEFAULT_DECIMALS, + ) + const motionTopUpToken = tokenData?.label ?? '' const pending = isCallDataLoading || isEventLoading || isPeriodLimitsDataLoading diff --git a/modules/motions/types.ts b/modules/motions/types.ts index 73a59df5..d5a749d2 100644 --- a/modules/motions/types.ts +++ b/modules/motions/types.ts @@ -10,9 +10,9 @@ export const MotionTypeForms = { AllowedRecipientTopUpTrpLdo: 'AllowedRecipientTopUpTrpLdo', LegoLDOTopUp: 'LegoLDOTopUp', LegoDAITopUp: 'LegoDAITopUp', - RccDAITopUp: 'RccDAITopUp', - PmlDAITopUp: 'PmlDAITopUp', - AtcDAITopUp: 'AtcDAITopUp', + RccStablesTopUp: 'RccStablesTopUp', + PmlStablesTopUp: 'PmlStablesTopUp', + AtcStablesTopUp: 'AtcStablesTopUp', StethRewardProgramAdd: 'StethRewardProgramAdd', StethRewardProgramRemove: 'StethRewardProgramRemove', StethRewardProgramTopUp: 'StethRewardProgramTopUp', @@ -54,6 +54,9 @@ export const MotionTypeDisplayOnly = { AllowedRecipientAddReferralDai: 'AllowedRecipientAddReferralDai', AllowedRecipientRemoveReferralDai: 'AllowedRecipientRemoveReferralDai', AllowedRecipientTopUpReferralDai: 'AllowedRecipientTopUpReferralDai', + RccDAITopUp: 'RccDAITopUp', + PmlDAITopUp: 'PmlDAITopUp', + AtcDAITopUp: 'AtcDAITopUp', } as const // intentionally // eslint-disable-next-line @typescript-eslint/no-redeclare diff --git a/modules/motions/ui/MotionDescription/DescTopUpWithLimitsAndCustomToken.tsx b/modules/motions/ui/MotionDescription/DescTopUpWithLimitsAndCustomToken.tsx new file mode 100644 index 00000000..a9c022e6 --- /dev/null +++ b/modules/motions/ui/MotionDescription/DescTopUpWithLimitsAndCustomToken.tsx @@ -0,0 +1,49 @@ +import { + useRecipientMapAll, + REGISTRY_WITH_LIMITS_BY_MOTION_TYPE, +} from 'modules/motions/hooks' + +import { AddressInlineWithPop } from 'modules/shared/ui/Common/AddressInlineWithPop' + +import { formatUnits } from 'ethers/lib/utils' +import { TopUpWithLimitsStablesAbi } from 'generated' +import { NestProps } from './types' +import { useMotionTokenData } from 'modules/motions/hooks/useMotionTokenData' + +type Props = NestProps & { + registryType: keyof typeof REGISTRY_WITH_LIMITS_BY_MOTION_TYPE +} + +export const DescTopUpWithLimitsAndCustomToken = ({ + callData, + registryType, +}: Props) => { + const { data: allowedRecipientMap, initialLoading: isRecipientDataLoading } = + useRecipientMapAll({ registryType }) + + const { tokenData, isTokenDataLoading } = useMotionTokenData(callData.token) + + if (isRecipientDataLoading || !allowedRecipientMap || isTokenDataLoading) { + return
Loading...
+ } + + return ( +
+ Top up single allowed recipient: + {callData.recipients.map((address, i) => { + const recipientName = allowedRecipientMap[address] + const formattedAmount = Number( + formatUnits(callData.amounts[i], tokenData?.decimals), + ).toLocaleString('en-EN') + + return ( +
+ {recipientName} + with {formattedAmount}{' '} + {tokenData ? {tokenData.label} : null} +
+ ) + })} +
+ ) +} diff --git a/modules/motions/ui/MotionDescription/MotionDescription.tsx b/modules/motions/ui/MotionDescription/MotionDescription.tsx index 00efcb8d..207baa24 100644 --- a/modules/motions/ui/MotionDescription/MotionDescription.tsx +++ b/modules/motions/ui/MotionDescription/MotionDescription.tsx @@ -22,6 +22,7 @@ import { } from './DescAllowedRecipient' import { DescTopUpWithLimits } from './DescTopUpWithLimits' +import { DescTopUpWithLimitsAndCustomToken } from './DescTopUpWithLimitsAndCustomToken' import { TopUpWithLimitsAbi, @@ -52,6 +53,10 @@ type DescAllowedRecipientAddProps = NestProps< AddAllowedRecipientAbi['decodeEVMScriptCallData'] > +type GenericDescProps = { + callData: any +} + const MOTION_DESCRIPTIONS = { [MotionType.NodeOperatorIncreaseLimit]: DescNodeOperatorIncreaseLimit, [MotionType.LEGOTopUp]: DescLEGOTopUp, @@ -61,7 +66,7 @@ const MOTION_DESCRIPTIONS = { [MotionType.ReferralPartnerAdd]: DescReferralPartnerAdd, [MotionType.ReferralPartnerTopUp]: DescReferralPartnerTopUp, [MotionType.ReferralPartnerRemove]: DescReferralPartnerRemove, - [MotionType.AllowedRecipientAdd]: (props: DescAllowedRecipientAddProps) => ( + [MotionType.AllowedRecipientAdd]: (props: GenericDescProps) => ( ( + + ), + [MotionType.PmlStablesTopUp]: (props: GenericDescProps) => ( + + ), + [MotionType.AtcStablesTopUp]: (props: GenericDescProps) => ( + + ), } as const type Props = { @@ -242,7 +265,8 @@ export function MotionDescription({ motion }: Props) { return <>Loading... } - const Desc = MOTION_DESCRIPTIONS[motionType] + const Desc: React.FunctionComponent = + MOTION_DESCRIPTIONS[motionType] return ( diff --git a/modules/motions/ui/MotionFormStartNew/Parts/StartNewAllowedRecipientTopUp.tsx b/modules/motions/ui/MotionFormStartNew/Parts/StartNewAllowedRecipientTopUp.tsx index 55f31891..87fe8d56 100644 --- a/modules/motions/ui/MotionFormStartNew/Parts/StartNewAllowedRecipientTopUp.tsx +++ b/modules/motions/ui/MotionFormStartNew/Parts/StartNewAllowedRecipientTopUp.tsx @@ -227,9 +227,9 @@ export const formParts = ({ rules={{ required: 'Field is required', validate: value => { - const check1 = validateToken(value) - if (typeof check1 === 'string') { - return check1 + const tokenError = validateToken(value) + if (tokenError) { + return tokenError } if ( transitionLimit && diff --git a/modules/motions/ui/MotionFormStartNew/Parts/StartNewTopUpWithLimits.tsx b/modules/motions/ui/MotionFormStartNew/Parts/StartNewTopUpWithLimits.tsx index a57fe10c..d8a15684 100644 --- a/modules/motions/ui/MotionFormStartNew/Parts/StartNewTopUpWithLimits.tsx +++ b/modules/motions/ui/MotionFormStartNew/Parts/StartNewTopUpWithLimits.tsx @@ -31,9 +31,6 @@ import { import { ContractEvmLegoLDOTopUp, ContractEvmLegoDAITopUp, - ContractEvmRccDAITopUp, - ContractEvmPmlDAITopUp, - ContractEvmAtcDAITopUp, ContractEvmGasFunderETHTopUp, } from 'modules/blockChain/contracts' import { MotionType } from 'modules/motions/types' @@ -54,18 +51,6 @@ export const TOPUP_WITH_LIMITS_MAP = { evmContract: ContractEvmLegoDAITopUp, motionType: MotionType.LegoDAITopUp, }, - [MotionType.RccDAITopUp]: { - evmContract: ContractEvmRccDAITopUp, - motionType: MotionType.RccDAITopUp, - }, - [MotionType.PmlDAITopUp]: { - evmContract: ContractEvmPmlDAITopUp, - motionType: MotionType.PmlDAITopUp, - }, - [MotionType.AtcDAITopUp]: { - evmContract: ContractEvmAtcDAITopUp, - motionType: MotionType.AtcDAITopUp, - }, [MotionType.GasFunderETHTopUp]: { evmContract: ContractEvmGasFunderETHTopUp, motionType: MotionType.GasFunderETHTopUp, @@ -237,9 +222,9 @@ export const formParts = ({ rules={{ required: 'Field is required', validate: value => { - const check1 = validateToken(value) - if (typeof check1 === 'string') { - return check1 + const tokenError = validateToken(value) + if (tokenError) { + return tokenError } if ( transitionLimit && diff --git a/modules/motions/ui/MotionFormStartNew/Parts/StartNewTopUpWithLimitsAndCustomToken.tsx b/modules/motions/ui/MotionFormStartNew/Parts/StartNewTopUpWithLimitsAndCustomToken.tsx new file mode 100644 index 00000000..06466a86 --- /dev/null +++ b/modules/motions/ui/MotionFormStartNew/Parts/StartNewTopUpWithLimitsAndCustomToken.tsx @@ -0,0 +1,333 @@ +import { utils } from 'ethers' + +import { Fragment, useEffect, useMemo } from 'react' +import { useFieldArray, useFormContext } from 'react-hook-form' +import { Plus, ButtonIcon } from '@lidofinance/lido-ui' +import { useWeb3 } from 'modules/blockChain/hooks/useWeb3' +import { useRecipientActual, usePeriodLimitsData } from 'modules/motions/hooks' +import { useTransitionLimits } from 'modules/motions/hooks/useTransitionLimits' +import { + MotionLimitProgress, + MotionLimitProgressWrapper, +} from 'modules/motions/ui/MotionLimitProgress' + +import { PageLoader } from 'modules/shared/ui/Common/PageLoader' +import { InputNumberControl } from 'modules/shared/ui/Controls/InputNumber' +import { SelectControl, Option } from 'modules/shared/ui/Controls/Select' +import { MotionInfoBox } from 'modules/shared/ui/Common/MotionInfoBox' +import { + Fieldset, + MessageBox, + RemoveItemButton, + FieldsWrapper, + FieldsHeader, + FieldsHeaderDesc, +} from '../CreateMotionFormStyle' + +import { + ContractEvmRccStablesTopUp, + ContractEvmPmlStablesTopUp, + ContractEvmAtcStablesTopUp, +} from 'modules/blockChain/contracts' +import { MotionType } from 'modules/motions/types' +import { createMotionFormPart } from './createMotionFormPart' +import { validateToken } from 'modules/tokens/utils/validateToken' +import { + estimateGasFallback, + checkInputsGreaterThanLimit, +} from 'modules/motions/utils' +import { tokenLimitError, periodLimitError } from 'modules/motions/constants' +import { useAllowedTokens } from 'modules/motions/hooks/useAllowedTokensRegistry' +import { Text } from 'modules/shared/ui/Common/Text' +import { AddressInlineWithPop } from 'modules/shared/ui/Common/AddressInlineWithPop' +import { DEFAULT_DECIMALS } from 'modules/blockChain/constants' + +export const TOPUP_WITH_LIMITS_MAP = { + [MotionType.RccStablesTopUp]: { + evmContract: ContractEvmRccStablesTopUp, + motionType: MotionType.RccStablesTopUp, + }, + [MotionType.PmlStablesTopUp]: { + evmContract: ContractEvmPmlStablesTopUp, + motionType: MotionType.PmlStablesTopUp, + }, + [MotionType.AtcStablesTopUp]: { + evmContract: ContractEvmAtcStablesTopUp, + motionType: MotionType.AtcStablesTopUp, + }, +} + +type Program = { + address: string + amount: string +} + +export const formParts = ({ + registryType, +}: { + registryType: keyof typeof TOPUP_WITH_LIMITS_MAP +}) => + createMotionFormPart({ + motionType: TOPUP_WITH_LIMITS_MAP[registryType].motionType, + populateTx: async ({ evmScriptFactory, formData, contract }) => { + const encodedCallData = new utils.AbiCoder().encode( + ['address', 'address[]', 'uint256[]'], + [ + utils.getAddress(formData.tokenAddress), + formData.programs.map(p => utils.getAddress(p.address)), + formData.programs.map(p => + utils.parseUnits(p.amount, formData.tokenDecimals), + ), + ], + ) + const gasLimit = await estimateGasFallback( + contract.estimateGas.createMotion(evmScriptFactory, encodedCallData), + ) + const tx = await contract.populateTransaction.createMotion( + evmScriptFactory, + encodedCallData, + { gasLimit }, + ) + return tx + }, + getDefaultFormData: () => ({ + tokenAddress: '', + tokenDecimals: DEFAULT_DECIMALS, + programs: [{ address: '', amount: '' }] as Program[], + }), + Component: function StartNewMotionMotionFormLego({ + fieldNames, + submitAction, + }) { + const { walletAddress } = useWeb3() + const trustedCaller = TOPUP_WITH_LIMITS_MAP[ + registryType + ].evmContract.useSwrWeb3('trustedCaller', []) + const isTrustedCallerConnected = trustedCaller.data === walletAddress + + const { + allowedTokens, + tokensDecimalsMap, + initialLoading: isTokensDataLoading, + } = useAllowedTokens() + const { + data: periodLimitsData, + initialLoading: isPeriodLimitsDataLoading, + } = usePeriodLimitsData({ registryType }) + + const { + data: actualRecipients, + initialLoading: isRecipientsDataLoading, + } = useRecipientActual({ registryType }) + + const fieldsArr = useFieldArray({ name: fieldNames.programs }) + + const handleAddProgram = () => + fieldsArr.append({ address: '', amount: '' }) + + const { watch, setValue } = useFormContext() + const selectedPrograms: Program[] = watch(fieldNames.programs) + const selectedTokenAddress = watch(fieldNames.tokenAddress) + + const selectedTokenLabel = useMemo(() => { + if (!selectedTokenAddress || !allowedTokens?.length) { + return '' + } + return allowedTokens.find( + ({ address }) => address === selectedTokenAddress, + )?.label + }, [allowedTokens, selectedTokenAddress]) + + const newAmount = selectedPrograms.reduce( + (acc, program) => acc + Number(program.amount), + 0, + ) + + const getFilteredOptions = (fieldIdx: number) => { + if (!actualRecipients) return [] + const thatAddress = selectedPrograms[fieldIdx]?.address + const selectedAddresses = selectedPrograms.map(({ address }) => address) + return actualRecipients.filter( + ({ address }) => + !selectedAddresses.includes(address) || address === thatAddress, + ) + } + + useEffect(() => { + const recipientsCount = actualRecipients?.length || 0 + const isMoreThanOne = recipientsCount > 1 + + if (isMoreThanOne) return + + const recipientAddress = actualRecipients?.[0].address || '' + setValue(fieldNames.programs, [ + { address: recipientAddress, amount: '' }, + ]) + }, [fieldNames.programs, setValue, actualRecipients]) + + const { data: limits } = useTransitionLimits() + const transitionLimit = + selectedTokenAddress && limits?.[utils.getAddress(selectedTokenAddress)] + + if ( + trustedCaller.initialLoading || + isRecipientsDataLoading || + isPeriodLimitsDataLoading || + isTokensDataLoading + ) { + return + } + + if (!isTrustedCallerConnected) { + return ( + You should be connected as trusted caller + ) + } + + return ( + <> +
+ { + const tokenDecimals = tokensDecimalsMap?.[value] + if (tokenDecimals) { + setValue(fieldNames.tokenDecimals, tokenDecimals) + } + }} + > + {allowedTokens?.map((token, j) => ( + +
+ {selectedTokenAddress && ( + + + {selectedTokenLabel || 'Token'} address:{' '} + + + + )} + {periodLimitsData?.periodData && selectedTokenAddress && ( + + + + )} + + {fieldsArr.fields.map((item, fieldIdx) => ( + + + + Recipient #{fieldIdx + 1} + {fieldsArr.fields.length > 1 && ( + fieldsArr.remove(fieldIdx)} + > + Remove recipient {fieldIdx + 1} + + )} + + {periodLimitsData?.isEndInNextPeriod && ( + + The motion is ending in the next period. The transfer limit + would be replenished by that time. + + )} +
+ + {getFilteredOptions(fieldIdx).map((program, j) => ( + +
+ +
+ { + const tokenError = validateToken(value) + if (tokenError) { + return tokenError + } + if ( + transitionLimit && + Number(value) > transitionLimit + ) { + return tokenLimitError( + selectedTokenLabel, + transitionLimit, + ) + } + + const isLargeThenPeriodLimit = + checkInputsGreaterThanLimit({ + inputValues: selectedPrograms, + spendableBalanceInPeriod: Number( + periodLimitsData?.periodData + .spendableBalanceInPeriod, + ), + currentValue: { value, index: fieldIdx }, + }) + + if ( + periodLimitsData?.periodData + .spendableBalanceInPeriod && + isLargeThenPeriodLimit + ) { + return periodLimitError() + } + return true + }, + }} + /> +
+
+
+ ))} + + {actualRecipients && + fieldsArr.fields.length < actualRecipients.length && ( +
+ } + color="secondary" + > + One more recipient + +
+ )} + + {submitAction} + + ) + }, + }) diff --git a/modules/motions/ui/MotionFormStartNew/Parts/index.ts b/modules/motions/ui/MotionFormStartNew/Parts/index.ts index 7b93d4e4..f6798c13 100644 --- a/modules/motions/ui/MotionFormStartNew/Parts/index.ts +++ b/modules/motions/ui/MotionFormStartNew/Parts/index.ts @@ -5,6 +5,7 @@ import * as formAllowedRecipientAdd from './StartNewAllowedRecipientAdd' import * as formAllowedRecipientRemove from './StartNewAllowedRecipientRemove' import * as formAllowedRecipientTopUp from './StartNewAllowedRecipientTopUp' import * as StartNewTopUpWithLimits from './StartNewTopUpWithLimits' +import * as StartNewTopUpWithLimitsAndCustomToken from './StartNewTopUpWithLimitsAndCustomToken' import * as StartSDVTNodeOperatorsAdd from './StartSDVTNodeOperatorsAdd' import * as StartNewSDVTNodeOperatorsActivate from './StartNewSDVTNodeOperatorsActivate' import * as StartNewSDVTNodeOperatorsDeactivate from './StartNewSDVTNodeOperatorsDeactivate' @@ -26,15 +27,18 @@ export const formParts = { [MotionTypeForms.LegoDAITopUp]: StartNewTopUpWithLimits.formParts({ registryType: MotionTypeForms.LegoDAITopUp, }), - [MotionTypeForms.RccDAITopUp]: StartNewTopUpWithLimits.formParts({ - registryType: MotionTypeForms.RccDAITopUp, - }), - [MotionTypeForms.PmlDAITopUp]: StartNewTopUpWithLimits.formParts({ - registryType: MotionTypeForms.PmlDAITopUp, - }), - [MotionTypeForms.AtcDAITopUp]: StartNewTopUpWithLimits.formParts({ - registryType: MotionTypeForms.AtcDAITopUp, - }), + [MotionTypeForms.RccStablesTopUp]: + StartNewTopUpWithLimitsAndCustomToken.formParts({ + registryType: MotionTypeForms.RccStablesTopUp, + }), + [MotionTypeForms.PmlStablesTopUp]: + StartNewTopUpWithLimitsAndCustomToken.formParts({ + registryType: MotionTypeForms.PmlStablesTopUp, + }), + [MotionTypeForms.AtcStablesTopUp]: + StartNewTopUpWithLimitsAndCustomToken.formParts({ + registryType: MotionTypeForms.AtcStablesTopUp, + }), [MotionTypeForms.StethRewardProgramAdd]: formAllowedRecipientAdd.formParts({ registryType: MotionTypeForms.StethRewardProgramAdd, }), diff --git a/modules/motions/utils/connectTokenContract.ts b/modules/motions/utils/connectTokenContract.ts new file mode 100644 index 00000000..910a974f --- /dev/null +++ b/modules/motions/utils/connectTokenContract.ts @@ -0,0 +1,10 @@ +import { CHAINS } from '@lido-sdk/constants' +import { getStaticRpcBatchProvider } from '@lido-sdk/providers' +import { Erc20Abi__factory } from 'generated' +import { getBackendRpcUrl } from 'modules/blockChain/utils/getBackendRpcUrl' + +export function connectERC20Contract(contractAddress: string, chainId: CHAINS) { + const library = getStaticRpcBatchProvider(chainId, getBackendRpcUrl(chainId)) + + return Erc20Abi__factory.connect(contractAddress, library) +} diff --git a/modules/motions/utils/getMotionTypeDisplayName.ts b/modules/motions/utils/getMotionTypeDisplayName.ts index 88a2e57d..03a3f16d 100644 --- a/modules/motions/utils/getMotionTypeDisplayName.ts +++ b/modules/motions/utils/getMotionTypeDisplayName.ts @@ -10,9 +10,9 @@ export const MotionTypeDisplayNames: Record< [MotionType.AllowedRecipientTopUpTrpLdo]: 'Top up LDO TRP', [MotionType.LegoLDOTopUp]: 'Top up LEGO LDO', [MotionType.LegoDAITopUp]: 'Top up LEGO DAI', - [MotionType.RccDAITopUp]: 'Top up RCC DAI', - [MotionType.PmlDAITopUp]: 'Top up PML DAI', - [MotionType.AtcDAITopUp]: 'Top up ATC DAI', + [MotionType.RccStablesTopUp]: 'Top up RCC', + [MotionType.PmlStablesTopUp]: 'Top up PML', + [MotionType.AtcStablesTopUp]: 'Top up ATC', [MotionType.StethRewardProgramAdd]: 'Add stETH reward program', [MotionType.StethRewardProgramRemove]: 'Remove stETH reward program', [MotionType.StethRewardProgramTopUp]: 'Top up stETH reward programs', @@ -57,6 +57,9 @@ export const MotionTypeDisplayNames: Record< [MotionType.AllowedRecipientAddReferralDai]: 'Add DAI referral partner', [MotionType.AllowedRecipientRemoveReferralDai]: 'Remove DAI referral partner', [MotionType.AllowedRecipientTopUpReferralDai]: 'Top up DAI referral partner', + [MotionType.RccDAITopUp]: 'Top up RCC DAI', + [MotionType.PmlDAITopUp]: 'Top up PML DAI', + [MotionType.AtcDAITopUp]: 'Top up ATC DAI', } as const export function getMotionTypeDisplayName( diff --git a/modules/shared/ui/Common/AddressInlineWithPop/AddressInlineWithPop.tsx b/modules/shared/ui/Common/AddressInlineWithPop/AddressInlineWithPop.tsx index 18486a56..89ba6c4e 100644 --- a/modules/shared/ui/Common/AddressInlineWithPop/AddressInlineWithPop.tsx +++ b/modules/shared/ui/Common/AddressInlineWithPop/AddressInlineWithPop.tsx @@ -4,12 +4,13 @@ import { Wrap } from './AddressInlineWithPopStyle' type Props = { address: string + trim?: boolean } -export function AddressInlineWithPop({ address }: Props) { +export function AddressInlineWithPop({ address, trim = true }: Props) { return ( - + ) } diff --git a/modules/tokens/utils/validateToken.ts b/modules/tokens/utils/validateToken.ts index 94be5495..b41d50a4 100644 --- a/modules/tokens/utils/validateToken.ts +++ b/modules/tokens/utils/validateToken.ts @@ -6,7 +6,7 @@ export const validateToken = (value: string) => { } try { utils.parseEther(value) - return true + return null } catch (_) { return 'Unable to parse' }