diff --git a/.github/workflows/e2e-tests-linux.yml b/.github/workflows/e2e-tests-linux.yml index 71eec19cf4..47f32d5fc3 100644 --- a/.github/workflows/e2e-tests-linux.yml +++ b/.github/workflows/e2e-tests-linux.yml @@ -50,6 +50,9 @@ jobs: uses: ./.github/shared/build with: LACE_EXTENSION_KEY: ${{ secrets.MANIFEST_PUBLIC_KEY }} + CARDANO_SERVICES_URL_MAINNET: ${{ secrets.CARDANO_SERVICES_DEV_URL_MAINNET }} + CARDANO_SERVICES_URL_PREPROD: ${{ secrets.CARDANO_SERVICES_DEV_URL_PREPROD }} + CARDANO_SERVICES_URL_PREVIEW: ${{ secrets.CARDANO_SERVICES_DEV_URL_PREVIEW }} - name: Start XVFB run: | Xvfb :99 & diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 5c2526ad4b..5066293a93 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -26,6 +26,9 @@ jobs: uses: ./.github/shared/build with: LACE_EXTENSION_KEY: ${{ secrets.MANIFEST_PUBLIC_KEY }} + CARDANO_SERVICES_URL_MAINNET: ${{ secrets.CARDANO_SERVICES_DEV_URL_MAINNET }} + CARDANO_SERVICES_URL_PREPROD: ${{ secrets.CARDANO_SERVICES_DEV_URL_PREPROD }} + CARDANO_SERVICES_URL_PREVIEW: ${{ secrets.CARDANO_SERVICES_DEV_URL_PREVIEW }} - name: Start XVFB run: | Xvfb :99 & diff --git a/apps/browser-extension-wallet/.env.defaults b/apps/browser-extension-wallet/.env.defaults index fd8968c01a..0ebb89d0bc 100644 --- a/apps/browser-extension-wallet/.env.defaults +++ b/apps/browser-extension-wallet/.env.defaults @@ -4,7 +4,7 @@ DEFAULT_CHAIN=Mainnet WALLET_SYNC_TIMEOUT_IN_SEC=60 WALLET_INTERVAL_IN_SEC=30 DROP_CONSOLE_IN_PRODUCTION=false -AVAILABLE_CHAINS=Preprod,Preview,Mainnet +AVAILABLE_CHAINS=Preprod,Preview,Mainnet,Sanchonet ADA_PRICE_POLLING_IN_SEC=60 TOKEN_PRICE_POLLING_IN_SEC=300 SAVED_PRICE_DURATION_IN_MINUTES=720 diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx index e17e48769d..03980ee8bb 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import React, { useMemo, useState } from 'react'; import cn from 'classnames'; import { Button, PostHogAction } from '@lace/common'; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx index 03fea860f4..65379c1058 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx @@ -1,8 +1,10 @@ +/* eslint-disable complexity */ import React from 'react'; import { Skeleton } from 'antd'; +import { Wallet } from '@lace/cardano'; +import { SignTxData } from './types'; import { ConfirmDRepRegistrationContainer } from './ConfirmDRepRegistrationContainer'; import { DappTransactionContainer } from './DappTransactionContainer'; -import { SignTxData } from './types'; import { ConfirmDRepRetirementContainer } from './ConfirmDRepRetirementContainer'; import { ConfirmVoteDelegationContainer } from './ConfirmVoteDelegationContainer'; import { VotingProceduresContainer } from './VotingProceduresContainer'; @@ -11,7 +13,7 @@ import { ConfirmVoteRegistrationDelegationContainer } from './ConfirmVoteRegistr import { ConfirmStakeRegistrationDelegationContainer } from './ConfirmStakeRegistrationDelegationContainer'; import { ConfirmStakeVoteRegistrationDelegationContainer } from './ConfirmStakeVoteRegistrationDelegationContainer'; import { ConfirmStakeVoteDelegationContainer } from './ConfirmStakeVoteDelegationContainer'; -import { Wallet } from '@lace/cardano'; +import { ProposalProceduresContainer } from './ProposalProceduresContainer'; interface Props { txType?: Wallet.Cip30TxType; @@ -51,6 +53,9 @@ export const ConfirmTransactionContent = ({ txType, signTxData, onError, errorMe if (txType === Wallet.Cip30TxType.StakeVoteDelegation) { return ; } + if (txType === Wallet.Cip30TxType.ProposalProcedures) { + return ; + } return ; }; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ProposalProceduresContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ProposalProceduresContainer.tsx new file mode 100644 index 0000000000..7c3df2b028 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ProposalProceduresContainer.tsx @@ -0,0 +1,86 @@ +/* eslint-disable consistent-return */ +/* eslint-disable complexity */ +import React, { useMemo } from 'react'; +import { Wallet } from '@lace/cardano'; +import { SignTxData } from './types'; +import { proposalProceduresInspector } from './utils'; +import { HardForkInitiationActionContainer } from './proposal-procedures/HardForkInitiationActionContainer'; +import { InfoActionContainer } from './proposal-procedures/InfoActionContainer'; +import { NewConstitutionActionContainer } from './proposal-procedures/NewConstitutionActionContainer'; +import { NoConfidenceActionContainer } from './proposal-procedures/NoConfidenceActionContainer'; +import { ParameterChangeActionContainer } from './proposal-procedures/ParameterChangeActionContainer'; +import { TreasuryWithdrawalsActionContainer } from './proposal-procedures/TreasuryWithdrawalsActionContainer'; +import { UpdateCommitteeActionContainer } from './proposal-procedures/UpdateCommitteeActionContainer'; + +interface Props { + signTxData: SignTxData; + errorMessage?: string; +} + +export const ProposalProceduresContainer = ({ + signTxData: { dappInfo, tx }, + errorMessage +}: Props): React.ReactElement => { + const proposalProcedures = proposalProceduresInspector(tx); + + const props = useMemo(() => ({ dappInfo, errorMessage }), [dappInfo, errorMessage]); + + return ( + <> + {proposalProcedures.map(({ deposit, rewardAccount, anchor, governanceAction }) => { + const key = `${governanceAction.__typename}_${anchor.dataHash}`; + if (governanceAction.__typename === Wallet.Cardano.GovernanceActionType.hard_fork_initiation_action) { + return ( + + ); + } + if (governanceAction.__typename === Wallet.Cardano.GovernanceActionType.info_action) { + return ; + } + if (governanceAction.__typename === Wallet.Cardano.GovernanceActionType.new_constitution) { + return ( + + ); + } + if (governanceAction.__typename === Wallet.Cardano.GovernanceActionType.no_confidence) { + return ( + + ); + } + if (governanceAction.__typename === Wallet.Cardano.GovernanceActionType.parameter_change_action) { + return ( + + ); + } + if (governanceAction.__typename === Wallet.Cardano.GovernanceActionType.treasury_withdrawals_action) { + return ( + + ); + } + if (governanceAction.__typename === Wallet.Cardano.GovernanceActionType.update_committee) { + return ( + + ); + } + })} + + ); +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/VotingProceduresContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/VotingProceduresContainer.tsx index 0ca9fc967d..8ebeefc4da 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/VotingProceduresContainer.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/VotingProceduresContainer.tsx @@ -1,11 +1,10 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import { VotingProcedures } from '@lace/core'; import { SignTxData } from './types'; import { drepIDasBech32FromHash, votingProceduresInspector } from './utils'; import { Wallet } from '@lace/cardano'; -import { useWalletStore } from '@src/stores'; -import { config } from '@src/config'; +import { useCExpolorerBaseUrl } from './hooks'; interface Props { signTxData: SignTxData; @@ -53,13 +52,8 @@ export const getVote = (vote: Wallet.Cardano.Vote): Votes => { export const VotingProceduresContainer = ({ signTxData, errorMessage }: Props): React.ReactElement => { const { t } = useTranslation(); const votingProcedures = votingProceduresInspector(signTxData.tx); - const { environmentName } = useWalletStore(); - const { CEXPLORER_BASE_URL, CEXPLORER_URL_PATHS } = config(); - const explorerBaseUrl = useMemo( - () => (environmentName === 'Sanchonet' ? '' : `${CEXPLORER_BASE_URL[environmentName]}/${CEXPLORER_URL_PATHS.Tx}`), - [CEXPLORER_BASE_URL, CEXPLORER_URL_PATHS.Tx, environmentName] - ); + const explorerBaseUrl = useCExpolorerBaseUrl(); return ( { }; }); -const backgroundService = { - getBackgroundStorage: jest.fn(), - setBackgroundStorage: jest.fn() -} as unknown as BackgroundServiceAPIProviderProps['value']; - -const getWrapper = - () => - ({ children }: { children: React.ReactNode }) => - ( - - - - - - - {children} - - - - - - - ); - describe('Testing ConfirmDRepRegistrationContainer component', () => { beforeEach(() => { mockUseWalletStore.mockReset(); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx index 570bbdb16e..c7bdc6d5ae 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx @@ -21,24 +21,12 @@ import * as React from 'react'; import { cleanup, render } from '@testing-library/react'; import { ConfirmDRepRetirementContainer } from '../ConfirmDRepRetirementContainer'; import '@testing-library/jest-dom'; -import { I18nextProvider } from 'react-i18next'; -import { StoreProvider } from '@src/stores'; -import { - AnalyticsProvider, - AppSettingsProvider, - BackgroundServiceAPIProvider, - BackgroundServiceAPIProviderProps, - DatabaseProvider -} from '@src/providers'; -import { APP_MODE_BROWSER } from '@src/utils/constants'; -import i18n from '@lib/i18n'; import { BehaviorSubject } from 'rxjs'; import { act } from 'react-dom/test-utils'; -import { PostHogClientProvider } from '@providers/PostHogClientProvider'; -import { postHogClientMocks } from '@src/utils/mocks/test-helpers'; import { buildMockTx } from '@src/utils/mocks/tx'; import { Wallet } from '@lace/cardano'; import BigNumber from 'bignumber.js'; +import { getWrapper } from '../testing.utils'; const LOVELACE_VALUE = 1_000_000; const DEFAULT_DECIMALS = 2; @@ -114,30 +102,6 @@ jest.mock('react-i18next', () => { }; }); -const backgroundService = { - getBackgroundStorage: jest.fn(), - setBackgroundStorage: jest.fn() -} as unknown as BackgroundServiceAPIProviderProps['value']; - -const getWrapper = - () => - ({ children }: { children: React.ReactNode }) => - ( - - - - - - - {children} - - - - - - - ); - describe('Testing ConfirmDRepRetirementContainer component', () => { beforeEach(() => { mockUseWalletStore.mockReset(); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepUpdateContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepUpdateContainer.test.tsx index eba21c3337..62aaaec965 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepUpdateContainer.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepUpdateContainer.test.tsx @@ -9,23 +9,11 @@ import * as React from 'react'; import { cleanup, render } from '@testing-library/react'; import { ConfirmDRepUpdateContainer } from '../ConfirmDRepUpdateContainer'; import '@testing-library/jest-dom'; -import { I18nextProvider } from 'react-i18next'; -import { StoreProvider } from '@src/stores'; -import { - AnalyticsProvider, - AppSettingsProvider, - BackgroundServiceAPIProvider, - BackgroundServiceAPIProviderProps, - DatabaseProvider -} from '@src/providers'; -import { APP_MODE_BROWSER } from '@src/utils/constants'; -import i18n from '@lib/i18n'; import { BehaviorSubject } from 'rxjs'; import { act } from 'react-dom/test-utils'; -import { PostHogClientProvider } from '@providers/PostHogClientProvider'; -import { postHogClientMocks } from '@src/utils/mocks/test-helpers'; import { buildMockTx } from '@src/utils/mocks/tx'; import { Wallet } from '@lace/cardano'; +import { getWrapper } from '../testing.utils'; const { Cardano, Crypto, HexBlob } = Wallet; @@ -68,30 +56,6 @@ jest.mock('react-i18next', () => { }; }); -const backgroundService = { - getBackgroundStorage: jest.fn(), - setBackgroundStorage: jest.fn() -} as unknown as BackgroundServiceAPIProviderProps['value']; - -const getWrapper = - () => - ({ children }: { children: React.ReactNode }) => - ( - - - - - - - {children} - - - - - - - ); - describe('Testing ConfirmDRepUpdateContainer component', () => { beforeEach(() => { mockUseWalletStore.mockReset(); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmStakeRegistrationDelegationContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmStakeRegistrationDelegationContainer.test.tsx new file mode 100644 index 0000000000..f8252136c2 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmStakeRegistrationDelegationContainer.test.tsx @@ -0,0 +1,149 @@ +/* eslint-disable unicorn/no-null */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable import/imports-first */ +const mockUseWalletStore = jest.fn(); +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockConfirmStakeRegistrationDelegation = jest.fn(); +const mockLovelacesToAdaString = jest.fn(); +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { ConfirmStakeRegistrationDelegationContainer } from '../ConfirmStakeRegistrationDelegationContainer'; +import '@testing-library/jest-dom'; +import { BehaviorSubject } from 'rxjs'; +import { act } from 'react-dom/test-utils'; +import { buildMockTx } from '@src/utils/mocks/tx'; +import { Wallet } from '@lace/cardano'; +import { getWrapper } from '../testing.utils'; + +const REWARD_ACCOUNT = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const STAKE_KEY_HASH = Wallet.Cardano.RewardAccount.toHash(REWARD_ACCOUNT); + +const assetInfo$ = new BehaviorSubject(new Map()); +const available$ = new BehaviorSubject([]); + +const inMemoryWallet = { + assetInfo$, + balance: { + utxo: { + available$ + } + } +}; + +const cardanoCoinMock = { + symbol: 'cardanoCoinMockSymbol' +}; + +jest.mock('@src/stores', () => ({ + ...jest.requireActual('@src/stores'), + useWalletStore: mockUseWalletStore +})); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + ConfirmStakeRegistrationDelegation: mockConfirmStakeRegistrationDelegation + }; +}); + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/cardano', () => { + const actual = jest.requireActual('@lace/cardano'); + return { + __esModule: true, + ...actual, + Wallet: { + ...actual.Wallet, + util: { + ...actual.Wallet.util, + lovelacesToAdaString: mockLovelacesToAdaString + } + } + }; +}); + +describe('Testing ConfirmStakeRegistrationDelegationContainer component', () => { + beforeEach(() => { + mockUseWalletStore.mockReset(); + mockUseWalletStore.mockImplementation(() => ({ + inMemoryWallet, + walletUI: { cardanoCoin: cardanoCoinMock }, + walletInfo: {} + })); + mockConfirmStakeRegistrationDelegation.mockReset(); + mockConfirmStakeRegistrationDelegation.mockReturnValue(); + mockUseTranslation.mockReset(); + mockUseTranslation.mockImplementation(() => ({ t })); + mockLovelacesToAdaString.mockReset(); + mockLovelacesToAdaString.mockImplementation((val) => val); + }); + + afterEach(() => { + jest.resetModules(); + jest.resetAllMocks(); + cleanup(); + }); + + test('should render ConfirmStakeRegistrationDelegation component with proper props', async () => { + let queryByTestId: any; + + const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' + }; + const certificate: Wallet.Cardano.Certificate = { + __typename: Wallet.Cardano.CertificateType.StakeRegistrationDelegation, + poolId: Wallet.Cardano.PoolId('pool126zlx7728y7xs08s8epg9qp393kyafy9rzr89g4qkvv4cv93zem'), + stakeCredential: { + type: Wallet.Cardano.CredentialType.KeyHash, + hash: Wallet.Crypto.Hash28ByteBase16(STAKE_KEY_HASH) + }, + deposit: BigInt('100000') + }; + const tx = buildMockTx({ + certificates: [certificate] + }); + const errorMessage = 'errorMessage'; + const props = { signTxData: { dappInfo, tx }, errorMessage }; + + await act(async () => { + ({ queryByTestId } = render(, { + wrapper: getWrapper() + })); + }); + + expect(queryByTestId('ConfirmStakeRegistrationDelegation')).toBeInTheDocument(); + expect(mockConfirmStakeRegistrationDelegation).toHaveBeenLastCalledWith( + { + dappInfo, + metadata: { + poolId: certificate.poolId, + stakeKeyHash: certificate.stakeCredential.hash, + depositPaid: `${certificate.deposit.toString()} ${cardanoCoinMock.symbol}` + }, + translations: { + metadata: t('core.StakeRegistrationDelegation.metadata'), + labels: { + poolId: t('core.StakeRegistrationDelegation.poolId'), + stakeKeyHash: t('core.StakeRegistrationDelegation.stakeKeyHash'), + depositPaid: t('core.StakeRegistrationDelegation.depositPaid') + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmStakeVoteDelegationContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmStakeVoteDelegationContainer.test.tsx new file mode 100644 index 0000000000..ccd3dba70f --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmStakeVoteDelegationContainer.test.tsx @@ -0,0 +1,168 @@ +/* eslint-disable unicorn/no-null */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable import/imports-first */ +const mockUseWalletStore = jest.fn(); +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockConfirmStakeVoteDelegation = jest.fn(); +const mockIsDRepAlwaysAbstain = jest.fn(); +const mockIsDRepAlwaysNoConfidence = jest.fn(); +const mockIsDRepCredential = jest.fn(); +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { ConfirmStakeVoteDelegationContainer } from '../ConfirmStakeVoteDelegationContainer'; +import '@testing-library/jest-dom'; +import { BehaviorSubject } from 'rxjs'; +import { act } from 'react-dom/test-utils'; +import { buildMockTx } from '@src/utils/mocks/tx'; +import { Wallet } from '@lace/cardano'; +import { getWrapper } from '../testing.utils'; + +const REWARD_ACCOUNT = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const STAKE_KEY_HASH = Wallet.Cardano.RewardAccount.toHash(REWARD_ACCOUNT); + +const assetInfo$ = new BehaviorSubject(new Map()); +const available$ = new BehaviorSubject([]); + +const inMemoryWallet = { + assetInfo$, + balance: { + utxo: { + available$ + } + } +}; + +const cardanoCoinMock = { + symbol: 'cardanoCoinMockSymbol' +}; + +jest.mock('@src/stores', () => ({ + ...jest.requireActual('@src/stores'), + useWalletStore: mockUseWalletStore +})); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + ConfirmStakeVoteDelegation: mockConfirmStakeVoteDelegation + }; +}); + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/cardano', () => { + const actual = jest.requireActual('@lace/cardano'); + return { + __esModule: true, + ...actual, + Wallet: { + ...actual.Wallet, + Cardano: { + ...actual.Wallet.Cardano, + isDRepAlwaysAbstain: mockIsDRepAlwaysAbstain, + isDRepAlwaysNoConfidence: mockIsDRepAlwaysNoConfidence, + isDRepCredential: mockIsDRepCredential + } + } + }; +}); + +const isDRepAlwaysAbstainMocked = 'isDRepAlwaysAbstainMocked'; +const isDRepAlwaysNoConfidenceMocked = 'isDRepAlwaysNoConfidenceMocked'; + +describe('Testing ConfirmStakeVoteDelegationContainer component', () => { + beforeEach(() => { + mockUseWalletStore.mockReset(); + mockUseWalletStore.mockImplementation(() => ({ + inMemoryWallet, + walletUI: { cardanoCoin: cardanoCoinMock }, + walletInfo: {} + })); + mockConfirmStakeVoteDelegation.mockReset(); + mockConfirmStakeVoteDelegation.mockReturnValue(); + mockUseTranslation.mockReset(); + mockUseTranslation.mockImplementation(() => ({ t })); + mockIsDRepAlwaysAbstain.mockReset(); + mockIsDRepAlwaysAbstain.mockImplementation(() => isDRepAlwaysAbstainMocked); + mockIsDRepAlwaysNoConfidence.mockReset(); + mockIsDRepAlwaysNoConfidence.mockImplementation(() => isDRepAlwaysNoConfidenceMocked); + mockIsDRepCredential.mockReset(); + mockIsDRepCredential.mockImplementation(() => true); + }); + + afterEach(() => { + jest.resetModules(); + jest.resetAllMocks(); + cleanup(); + }); + + test('should render ConfirmStakeVoteDelegation component with proper props', async () => { + let queryByTestId: any; + + const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' + }; + const certificate: Wallet.Cardano.Certificate = { + __typename: Wallet.Cardano.CertificateType.StakeVoteDelegation, + poolId: Wallet.Cardano.PoolId('pool126zlx7728y7xs08s8epg9qp393kyafy9rzr89g4qkvv4cv93zem'), + stakeCredential: { + type: Wallet.Cardano.CredentialType.KeyHash, + hash: Wallet.Crypto.Hash28ByteBase16(STAKE_KEY_HASH) + }, + dRep: { + type: Wallet.Cardano.CredentialType.KeyHash, + hash: Wallet.Crypto.Hash28ByteBase16(Buffer.from('dRepCredentialHashdRepCreden').toString('hex')) + } + }; + const tx = buildMockTx({ + certificates: [certificate] + }); + const errorMessage = 'errorMessage'; + const props = { signTxData: { dappInfo, tx }, errorMessage }; + + await act(async () => { + ({ queryByTestId } = render(, { + wrapper: getWrapper() + })); + }); + + expect(queryByTestId('ConfirmStakeVoteDelegation')).toBeInTheDocument(); + expect(mockConfirmStakeVoteDelegation).toHaveBeenLastCalledWith( + { + dappInfo, + metadata: { + poolId: certificate.poolId, + stakeKeyHash: certificate.stakeCredential.hash, + alwaysAbstain: isDRepAlwaysAbstainMocked, + alwaysNoConfidence: isDRepAlwaysNoConfidenceMocked, + drepId: (certificate.dRep as Wallet.Cardano.Credential).hash.toString() + }, + translations: { + metadata: t('core.StakeVoteDelegation.metadata'), + option: t('core.StakeVoteDelegation.option'), + labels: { + poolId: t('core.StakeVoteDelegation.poolId'), + stakeKeyHash: t('core.StakeVoteDelegation.stakeKeyHash'), + drepId: t('core.StakeVoteDelegation.drepId'), + alwaysAbstain: t('core.StakeVoteDelegation.alwaysAbstain'), + alwaysNoConfidence: t('core.StakeVoteDelegation.alwaysNoConfidence') + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmStakeVoteRegistrationDelegationContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmStakeVoteRegistrationDelegationContainer.test.tsx new file mode 100644 index 0000000000..37aa8cb203 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmStakeVoteRegistrationDelegationContainer.test.tsx @@ -0,0 +1,180 @@ +/* eslint-disable unicorn/no-null */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable import/imports-first */ +const mockUseWalletStore = jest.fn(); +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockConfirmStakeVoteRegistrationDelegation = jest.fn(); +const mockIsDRepAlwaysAbstain = jest.fn(); +const mockIsDRepAlwaysNoConfidence = jest.fn(); +const mockIsDRepCredential = jest.fn(); +const mockLovelacesToAdaString = jest.fn(); +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { ConfirmStakeVoteRegistrationDelegationContainer } from '../ConfirmStakeVoteRegistrationDelegationContainer'; +import '@testing-library/jest-dom'; +import { BehaviorSubject } from 'rxjs'; +import { act } from 'react-dom/test-utils'; +import { buildMockTx } from '@src/utils/mocks/tx'; +import { Wallet } from '@lace/cardano'; +import { getWrapper } from '../testing.utils'; + +const REWARD_ACCOUNT = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const STAKE_KEY_HASH = Wallet.Cardano.RewardAccount.toHash(REWARD_ACCOUNT); + +const assetInfo$ = new BehaviorSubject(new Map()); +const available$ = new BehaviorSubject([]); + +const inMemoryWallet = { + assetInfo$, + balance: { + utxo: { + available$ + } + } +}; + +const cardanoCoinMock = { + symbol: 'cardanoCoinMockSymbol' +}; + +jest.mock('@src/stores', () => ({ + ...jest.requireActual('@src/stores'), + useWalletStore: mockUseWalletStore +})); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + ConfirmStakeVoteRegistrationDelegation: mockConfirmStakeVoteRegistrationDelegation + }; +}); + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/cardano', () => { + const actual = jest.requireActual('@lace/cardano'); + return { + __esModule: true, + ...actual, + Wallet: { + ...actual.Wallet, + util: { + ...actual.Wallet.util, + lovelacesToAdaString: mockLovelacesToAdaString + }, + Cardano: { + ...actual.Wallet.Cardano, + isDRepAlwaysAbstain: mockIsDRepAlwaysAbstain, + isDRepAlwaysNoConfidence: mockIsDRepAlwaysNoConfidence, + isDRepCredential: mockIsDRepCredential + } + } + }; +}); + +const isDRepAlwaysAbstainMocked = 'isDRepAlwaysAbstainMocked'; +const isDRepAlwaysNoConfidenceMocked = 'isDRepAlwaysNoConfidenceMocked'; + +describe('Testing ConfirmStakeVoteRegistrationDelegationContainer component', () => { + beforeEach(() => { + mockUseWalletStore.mockReset(); + mockUseWalletStore.mockImplementation(() => ({ + inMemoryWallet, + walletUI: { cardanoCoin: cardanoCoinMock }, + walletInfo: {} + })); + mockConfirmStakeVoteRegistrationDelegation.mockReset(); + mockConfirmStakeVoteRegistrationDelegation.mockReturnValue( + + ); + mockUseTranslation.mockReset(); + mockUseTranslation.mockImplementation(() => ({ t })); + mockLovelacesToAdaString.mockReset(); + mockLovelacesToAdaString.mockImplementation((val) => val); + mockIsDRepAlwaysAbstain.mockReset(); + mockIsDRepAlwaysAbstain.mockImplementation(() => isDRepAlwaysAbstainMocked); + mockIsDRepAlwaysNoConfidence.mockReset(); + mockIsDRepAlwaysNoConfidence.mockImplementation(() => isDRepAlwaysNoConfidenceMocked); + mockIsDRepCredential.mockReset(); + mockIsDRepCredential.mockImplementation(() => true); + }); + + afterEach(() => { + jest.resetModules(); + jest.resetAllMocks(); + cleanup(); + }); + + test('should render ConfirmStakeVoteRegistrationDelegation component with proper props', async () => { + let queryByTestId: any; + + const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' + }; + const certificate: Wallet.Cardano.Certificate = { + __typename: Wallet.Cardano.CertificateType.StakeVoteRegistrationDelegation, + poolId: Wallet.Cardano.PoolId('pool126zlx7728y7xs08s8epg9qp393kyafy9rzr89g4qkvv4cv93zem'), + stakeCredential: { + type: Wallet.Cardano.CredentialType.KeyHash, + hash: Wallet.Crypto.Hash28ByteBase16(STAKE_KEY_HASH) + }, + dRep: { + type: Wallet.Cardano.CredentialType.KeyHash, + hash: Wallet.Crypto.Hash28ByteBase16(Buffer.from('dRepCredentialHashdRepCreden').toString('hex')) + }, + deposit: BigInt('100000') + }; + const tx = buildMockTx({ + certificates: [certificate] + }); + const errorMessage = 'errorMessage'; + const props = { signTxData: { dappInfo, tx }, errorMessage }; + + await act(async () => { + ({ queryByTestId } = render(, { + wrapper: getWrapper() + })); + }); + + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegation')).toBeInTheDocument(); + expect(mockConfirmStakeVoteRegistrationDelegation).toHaveBeenLastCalledWith( + { + dappInfo, + metadata: { + poolId: certificate.poolId, + stakeKeyHash: certificate.stakeCredential.hash, + depositPaid: `${certificate.deposit.toString()} ${cardanoCoinMock.symbol}`, + alwaysAbstain: isDRepAlwaysAbstainMocked, + alwaysNoConfidence: isDRepAlwaysNoConfidenceMocked, + drepId: (certificate.dRep as Wallet.Cardano.Credential).hash.toString() + }, + translations: { + metadata: t('core.StakeVoteDelegationRegistration.metadata'), + option: t('core.StakeVoteDelegationRegistration.option'), + labels: { + poolId: t('core.StakeVoteDelegationRegistration.poolId'), + stakeKeyHash: t('core.StakeVoteDelegationRegistration.stakeKeyHash'), + drepId: t('core.StakeVoteDelegationRegistration.drepId'), + alwaysAbstain: t('core.StakeVoteDelegationRegistration.alwaysAbstain'), + alwaysNoConfidence: t('core.StakeVoteDelegationRegistration.alwaysNoConfidence'), + depositPaid: t('core.StakeVoteDelegationRegistration.depositPaid') + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransaction.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransaction.test.tsx index 6be8a2a2c9..d8b854952a 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransaction.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransaction.test.tsx @@ -18,23 +18,9 @@ import * as React from 'react'; import { cleanup, render, act, fireEvent } from '@testing-library/react'; import { ConfirmTransaction } from '../ConfirmTransaction'; import '@testing-library/jest-dom'; -import { I18nextProvider } from 'react-i18next'; -import { StoreProvider } from '@src/stores'; -import { - AnalyticsProvider, - AppSettingsProvider, - BackgroundServiceAPIProvider, - BackgroundServiceAPIProviderProps, - DatabaseProvider, - ViewFlowProvider -} from '@src/providers'; -import { APP_MODE_BROWSER } from '@src/utils/constants'; -import i18n from '@lib/i18n'; import { BehaviorSubject } from 'rxjs'; -import { sendViewsFlowState } from '../../../config'; -import { PostHogClientProvider } from '@providers/PostHogClientProvider'; -import { postHogClientMocks } from '@src/utils/mocks/test-helpers'; import { Wallet } from '@lace/cardano'; +import { getWrapper } from '../testing.utils'; const assetInfo$ = new BehaviorSubject(new Map()); const available$ = new BehaviorSubject([]); @@ -134,32 +120,6 @@ const testIds = { dappTransactionCancel: 'dapp-transaction-cancel' }; -const backgroundService = { - getBackgroundStorage: jest.fn(), - setBackgroundStorage: jest.fn() -} as unknown as BackgroundServiceAPIProviderProps['value']; - -const getWrapper = - () => - ({ children }: { children: React.ReactNode }) => - ( - - - - - - - - {children} - - - - - - - - ); - describe('Testing ConfirmTransaction component', () => { window.ResizeObserver = ResizeObserver; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransactionContent.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransactionContent.test.tsx index c2ad9259a2..3386aff6be 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransactionContent.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmTransactionContent.test.tsx @@ -9,6 +9,19 @@ const mockConfirmDRepRetirementContainer = jest.fn(() => ); const mockConfirmVoteDelegationContainer = jest.fn(() => ); const mockVotingProceduresContainer = jest.fn(() => ); +const mockProposalProceduresContainer = jest.fn(() => ); +const mockConfirmVoteRegistrationDelegationContainer = jest.fn(() => ( + +)); +const mockConfirmStakeRegistrationDelegationContainer = jest.fn(() => ( + +)); +const mockConfirmStakeVoteRegistrationDelegationContainer = jest.fn(() => ( + +)); +const mockConfirmStakeVoteDelegationContainer = jest.fn(() => ( + +)); const mockDappTransactionContainer = jest.fn(() => ); import * as React from 'react'; import { cleanup, render } from '@testing-library/react'; @@ -71,6 +84,51 @@ jest.mock('../VotingProceduresContainer', () => { }; }); +jest.mock('../ProposalProceduresContainer', () => { + const original = jest.requireActual('../ProposalProceduresContainer'); + return { + __esModule: true, + ...original, + ProposalProceduresContainer: mockProposalProceduresContainer + }; +}); + +jest.mock('../ConfirmVoteRegistrationDelegationContainer', () => { + const original = jest.requireActual('../ConfirmVoteRegistrationDelegationContainer'); + return { + __esModule: true, + ...original, + ConfirmVoteRegistrationDelegationContainer: mockConfirmVoteRegistrationDelegationContainer + }; +}); + +jest.mock('../ConfirmStakeRegistrationDelegationContainer', () => { + const original = jest.requireActual('../ConfirmStakeRegistrationDelegationContainer'); + return { + __esModule: true, + ...original, + ConfirmStakeRegistrationDelegationContainer: mockConfirmStakeRegistrationDelegationContainer + }; +}); + +jest.mock('../ConfirmStakeVoteRegistrationDelegationContainer', () => { + const original = jest.requireActual('../ConfirmStakeVoteRegistrationDelegationContainer'); + return { + __esModule: true, + ...original, + ConfirmStakeVoteRegistrationDelegationContainer: mockConfirmStakeVoteRegistrationDelegationContainer + }; +}); + +jest.mock('../ConfirmStakeVoteDelegationContainer', () => { + const original = jest.requireActual('../ConfirmStakeVoteDelegationContainer'); + return { + __esModule: true, + ...original, + ConfirmStakeVoteDelegationContainer: mockConfirmStakeVoteDelegationContainer + }; +}); + jest.mock('../DappTransactionContainer', () => { const original = jest.requireActual('../DappTransactionContainer'); return { @@ -106,6 +164,11 @@ describe('Testing ConfirmTransactionContent component', () => { expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); expect(mockSkeleton).toHaveBeenLastCalledWith({ loading: true }, {}); }); @@ -125,6 +188,11 @@ describe('Testing ConfirmTransactionContent component', () => { expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); expect(mockConfirmDRepRegistrationContainer).toHaveBeenLastCalledWith(props, {}); }); @@ -144,6 +212,11 @@ describe('Testing ConfirmTransactionContent component', () => { expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); expect(mockConfirmDRepRetirementContainer).toHaveBeenLastCalledWith(props, {}); }); @@ -163,6 +236,11 @@ describe('Testing ConfirmTransactionContent component', () => { expect(queryByTestId('ConfirmDRepUpdateContainer')).toBeInTheDocument(); expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); expect(mockConfirmDRepUpdateContainer).toHaveBeenLastCalledWith(props, {}); }); @@ -182,6 +260,11 @@ describe('Testing ConfirmTransactionContent component', () => { expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); expect(queryByTestId('ConfirmVoteDelegationContainer')).toBeInTheDocument(); expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); expect(mockConfirmVoteDelegationContainer).toHaveBeenLastCalledWith(props, {}); }); @@ -201,10 +284,135 @@ describe('Testing ConfirmTransactionContent component', () => { expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('VotingProceduresContainer')).toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); expect(mockVotingProceduresContainer).toHaveBeenLastCalledWith(props, {}); }); + test('should render ProposalProceduresContainer with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + + )); + }); + + expect(queryByTestId('skeleton')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepRegistrationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepRetirementContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); + expect(mockProposalProceduresContainer).toHaveBeenLastCalledWith(props, {}); + }); + + test('should render ConfirmVoteRegistrationDelegationContainer with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + + )); + }); + + expect(queryByTestId('skeleton')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepRegistrationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepRetirementContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); + expect(mockConfirmVoteRegistrationDelegationContainer).toHaveBeenLastCalledWith(props, {}); + }); + + test('should render ConfirmStakeRegistrationDelegationContainer with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + + )); + }); + + expect(queryByTestId('skeleton')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepRegistrationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepRetirementContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); + expect(mockConfirmStakeRegistrationDelegationContainer).toHaveBeenLastCalledWith(props, {}); + }); + + test('should render ConfirmStakeVoteRegistrationDelegationContainer with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + + )); + }); + + expect(queryByTestId('skeleton')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepRegistrationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepRetirementContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); + expect(mockConfirmStakeVoteRegistrationDelegationContainer).toHaveBeenLastCalledWith(props, {}); + }); + + test('should render ConfirmStakeVoteDelegationContainer with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + + )); + }); + + expect(queryByTestId('skeleton')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepRegistrationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepRetirementContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).toBeInTheDocument(); + expect(queryByTestId('DappTransactionContainer')).not.toBeInTheDocument(); + expect(mockConfirmStakeVoteDelegationContainer).toHaveBeenLastCalledWith(props, {}); + }); + test('should render DappTransactionContainer with proper props', async () => { let queryByTestId: any; @@ -220,6 +428,11 @@ describe('Testing ConfirmTransactionContent component', () => { expect(queryByTestId('ConfirmDRepUpdateContainer')).not.toBeInTheDocument(); expect(queryByTestId('ConfirmVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('VotingProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ProposalProceduresContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteRegistrationDelegationContainer')).not.toBeInTheDocument(); + expect(queryByTestId('ConfirmStakeVoteDelegationContainer')).not.toBeInTheDocument(); expect(queryByTestId('DappTransactionContainer')).toBeInTheDocument(); expect(mockDappTransactionContainer).toHaveBeenLastCalledWith(props, {}); }); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmVoteDelegationContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmVoteDelegationContainer.test.tsx index 3f2c77c6d7..1b4c2bdbe2 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmVoteDelegationContainer.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmVoteDelegationContainer.test.tsx @@ -9,23 +9,11 @@ import * as React from 'react'; import { cleanup, render } from '@testing-library/react'; import { ConfirmVoteDelegationContainer } from '../ConfirmVoteDelegationContainer'; import '@testing-library/jest-dom'; -import { I18nextProvider } from 'react-i18next'; -import { StoreProvider } from '@src/stores'; -import { - AnalyticsProvider, - AppSettingsProvider, - BackgroundServiceAPIProvider, - BackgroundServiceAPIProviderProps, - DatabaseProvider -} from '@src/providers'; -import { APP_MODE_BROWSER } from '@src/utils/constants'; -import i18n from '@lib/i18n'; import { BehaviorSubject } from 'rxjs'; import { act } from 'react-dom/test-utils'; -import { PostHogClientProvider } from '@providers/PostHogClientProvider'; -import { postHogClientMocks } from '@src/utils/mocks/test-helpers'; import { buildMockTx } from '@src/utils/mocks/tx'; import { Wallet } from '@lace/cardano'; +import { getWrapper } from '../testing.utils'; const REWARD_ACCOUNT = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); const STAKE_KEY_HASH = Wallet.Cardano.RewardAccount.toHash(REWARD_ACCOUNT); @@ -69,30 +57,6 @@ jest.mock('react-i18next', () => { }; }); -const backgroundService = { - getBackgroundStorage: jest.fn(), - setBackgroundStorage: jest.fn() -} as unknown as BackgroundServiceAPIProviderProps['value']; - -const getWrapper = - () => - ({ children }: { children: React.ReactNode }) => - ( - - - - - - - {children} - - - - - - - ); - describe('Testing ConfirmVoteDelegationContainer component', () => { beforeEach(() => { mockUseWalletStore.mockReset(); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmVoteRegistrationDelegationContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmVoteRegistrationDelegationContainer.test.tsx new file mode 100644 index 0000000000..ffc8debe9f --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmVoteRegistrationDelegationContainer.test.tsx @@ -0,0 +1,175 @@ +/* eslint-disable unicorn/no-null */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable import/imports-first */ +const mockUseWalletStore = jest.fn(); +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockConfirmVoteRegistrationDelegation = jest.fn(); +const mockIsDRepAlwaysAbstain = jest.fn(); +const mockIsDRepAlwaysNoConfidence = jest.fn(); +const mockIsDRepCredential = jest.fn(); +const mockLovelacesToAdaString = jest.fn(); +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { ConfirmVoteRegistrationDelegationContainer } from '../ConfirmVoteRegistrationDelegationContainer'; +import '@testing-library/jest-dom'; +import { BehaviorSubject } from 'rxjs'; +import { act } from 'react-dom/test-utils'; +import { buildMockTx } from '@src/utils/mocks/tx'; +import { Wallet } from '@lace/cardano'; +import { getWrapper } from '../testing.utils'; + +const REWARD_ACCOUNT = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const STAKE_KEY_HASH = Wallet.Cardano.RewardAccount.toHash(REWARD_ACCOUNT); + +const assetInfo$ = new BehaviorSubject(new Map()); +const available$ = new BehaviorSubject([]); + +const inMemoryWallet = { + assetInfo$, + balance: { + utxo: { + available$ + } + } +}; + +const cardanoCoinMock = { + symbol: 'cardanoCoinMockSymbol' +}; + +jest.mock('@src/stores', () => ({ + ...jest.requireActual('@src/stores'), + useWalletStore: mockUseWalletStore +})); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + ConfirmVoteRegistrationDelegation: mockConfirmVoteRegistrationDelegation + }; +}); + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/cardano', () => { + const actual = jest.requireActual('@lace/cardano'); + return { + __esModule: true, + ...actual, + Wallet: { + ...actual.Wallet, + util: { + ...actual.Wallet.util, + lovelacesToAdaString: mockLovelacesToAdaString + }, + Cardano: { + ...actual.Wallet.Cardano, + isDRepAlwaysAbstain: mockIsDRepAlwaysAbstain, + isDRepAlwaysNoConfidence: mockIsDRepAlwaysNoConfidence, + isDRepCredential: mockIsDRepCredential + } + } + }; +}); + +const isDRepAlwaysAbstainMocked = 'isDRepAlwaysAbstainMocked'; +const isDRepAlwaysNoConfidenceMocked = 'isDRepAlwaysNoConfidenceMocked'; + +describe('Testing ConfirmVoteRegistrationDelegationContainer component', () => { + beforeEach(() => { + mockUseWalletStore.mockReset(); + mockUseWalletStore.mockImplementation(() => ({ + inMemoryWallet, + walletUI: { cardanoCoin: cardanoCoinMock }, + walletInfo: {} + })); + mockConfirmVoteRegistrationDelegation.mockReset(); + mockConfirmVoteRegistrationDelegation.mockReturnValue(); + mockUseTranslation.mockReset(); + mockUseTranslation.mockImplementation(() => ({ t })); + mockLovelacesToAdaString.mockReset(); + mockLovelacesToAdaString.mockImplementation((val) => val); + mockIsDRepAlwaysAbstain.mockReset(); + mockIsDRepAlwaysAbstain.mockImplementation(() => isDRepAlwaysAbstainMocked); + mockIsDRepAlwaysNoConfidence.mockReset(); + mockIsDRepAlwaysNoConfidence.mockImplementation(() => isDRepAlwaysNoConfidenceMocked); + mockIsDRepCredential.mockReset(); + mockIsDRepCredential.mockImplementation(() => true); + }); + + afterEach(() => { + jest.resetModules(); + jest.resetAllMocks(); + cleanup(); + }); + + test('should render ConfirmVoteRegistrationDelegation component with proper props', async () => { + let queryByTestId: any; + + const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' + }; + const certificate: Wallet.Cardano.Certificate = { + __typename: Wallet.Cardano.CertificateType.VoteRegistrationDelegation, + stakeCredential: { + type: Wallet.Cardano.CredentialType.KeyHash, + hash: Wallet.Crypto.Hash28ByteBase16(STAKE_KEY_HASH) + }, + dRep: { + type: Wallet.Cardano.CredentialType.KeyHash, + hash: Wallet.Crypto.Hash28ByteBase16(Buffer.from('dRepCredentialHashdRepCreden').toString('hex')) + }, + deposit: BigInt('100000') + }; + const tx = buildMockTx({ + certificates: [certificate] + }); + const errorMessage = 'errorMessage'; + const props = { signTxData: { dappInfo, tx }, errorMessage }; + + await act(async () => { + ({ queryByTestId } = render(, { + wrapper: getWrapper() + })); + }); + + expect(queryByTestId('ConfirmVoteRegistrationDelegation')).toBeInTheDocument(); + expect(mockConfirmVoteRegistrationDelegation).toHaveBeenLastCalledWith( + { + dappInfo, + metadata: { + depositPaid: `${certificate.deposit.toString()} ${cardanoCoinMock.symbol}`, + stakeKeyHash: certificate.stakeCredential.hash, + alwaysAbstain: isDRepAlwaysAbstainMocked, + alwaysNoConfidence: isDRepAlwaysNoConfidenceMocked, + drepId: (certificate.dRep as Wallet.Cardano.Credential).hash.toString() + }, + translations: { + metadata: t('core.VoteRegistrationDelegation.metadata'), + option: t('core.VoteRegistrationDelegation.option'), + labels: { + drepId: t('core.VoteRegistrationDelegation.drepId'), + alwaysAbstain: t('core.VoteRegistrationDelegation.alwaysAbstain'), + alwaysNoConfidence: t('core.VoteRegistrationDelegation.alwaysNoConfidence'), + depositPaid: t('core.VoteRegistrationDelegation.depositPaid'), + stakeKeyHash: t('core.VoteRegistrationDelegation.stakeKeyHash') + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/DappTransactionContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/DappTransactionContainer.test.tsx index 4f58f3f937..2f6eca12f0 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/DappTransactionContainer.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/DappTransactionContainer.test.tsx @@ -19,24 +19,12 @@ import * as React from 'react'; import { cleanup, render } from '@testing-library/react'; import { DappTransactionContainer } from '../DappTransactionContainer'; import '@testing-library/jest-dom'; -import { I18nextProvider } from 'react-i18next'; -import { StoreProvider } from '@src/stores'; -import { - AnalyticsProvider, - AppSettingsProvider, - BackgroundServiceAPIProvider, - BackgroundServiceAPIProviderProps, - DatabaseProvider -} from '@src/providers'; -import { APP_MODE_BROWSER } from '@src/utils/constants'; -import i18n from '@lib/i18n'; import { BehaviorSubject } from 'rxjs'; import { act } from 'react-dom/test-utils'; -import { PostHogClientProvider } from '@providers/PostHogClientProvider'; -import { postHogClientMocks } from '@src/utils/mocks/test-helpers'; import { buildMockTx } from '@src/utils/mocks/tx'; import { Wallet } from '@lace/cardano'; import { SignTxData } from '../types'; +import { getWrapper } from '../testing.utils'; const { Cardano, Crypto } = Wallet; @@ -114,30 +102,6 @@ jest.mock('antd', () => { }; }); -const backgroundService = { - getBackgroundStorage: jest.fn(), - setBackgroundStorage: jest.fn() -} as unknown as BackgroundServiceAPIProviderProps['value']; - -const getWrapper = - () => - ({ children }: { children: React.ReactNode }) => - ( - - - - - - - {children} - - - - - - - ); - describe('Testing DappTransactionContainer component', () => { beforeEach(() => { mockUseWalletStore.mockReset(); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ProposalProceduresContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ProposalProceduresContainer.test.tsx new file mode 100644 index 0000000000..8dd390fba9 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ProposalProceduresContainer.test.tsx @@ -0,0 +1,179 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable import/imports-first */ +const mockHardForkInitiationActionContainer = jest.fn(() => ); +const mockInfoActionContainer = jest.fn(() => ); +const mockNewConstitutionActionContainer = jest.fn(() => ); +const mockNoConfidenceActionContainer = jest.fn(() => ); +const mockParameterChangeActionContainer = jest.fn(() => ); +const mockTreasuryWithdrawalsActionContainer = jest.fn(() => ); +const mockUpdateCommitteeActionContainer = jest.fn(() => ); +import { Wallet } from '@lace/cardano'; +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { ProposalProceduresContainer } from '../ProposalProceduresContainer'; +import '@testing-library/jest-dom'; +import { act } from 'react-dom/test-utils'; +import { buildMockTx } from '@src/utils/mocks/tx'; + +jest.mock('../proposal-procedures/HardForkInitiationActionContainer', () => { + const original = jest.requireActual('../proposal-procedures/HardForkInitiationActionContainer'); + return { + __esModule: true, + ...original, + HardForkInitiationActionContainer: mockHardForkInitiationActionContainer + }; +}); + +jest.mock('../proposal-procedures/InfoActionContainer', () => { + const original = jest.requireActual('../proposal-procedures/InfoActionContainer'); + return { + __esModule: true, + ...original, + InfoActionContainer: mockInfoActionContainer + }; +}); + +jest.mock('../proposal-procedures/NewConstitutionActionContainer', () => { + const original = jest.requireActual('../proposal-procedures/NewConstitutionActionContainer'); + return { + __esModule: true, + ...original, + NewConstitutionActionContainer: mockNewConstitutionActionContainer + }; +}); + +jest.mock('../proposal-procedures/NoConfidenceActionContainer', () => { + const original = jest.requireActual('../proposal-procedures/NoConfidenceActionContainer'); + return { + __esModule: true, + ...original, + NoConfidenceActionContainer: mockNoConfidenceActionContainer + }; +}); + +jest.mock('../proposal-procedures/ParameterChangeActionContainer', () => { + const original = jest.requireActual('../proposal-procedures/ParameterChangeActionContainer'); + return { + __esModule: true, + ...original, + ParameterChangeActionContainer: mockParameterChangeActionContainer + }; +}); + +jest.mock('../proposal-procedures/TreasuryWithdrawalsActionContainer', () => { + const original = jest.requireActual('../proposal-procedures/TreasuryWithdrawalsActionContainer'); + return { + __esModule: true, + ...original, + TreasuryWithdrawalsActionContainer: mockTreasuryWithdrawalsActionContainer + }; +}); + +jest.mock('../proposal-procedures/UpdateCommitteeActionContainer', () => { + const original = jest.requireActual('../proposal-procedures/UpdateCommitteeActionContainer'); + return { + __esModule: true, + ...original, + UpdateCommitteeActionContainer: mockUpdateCommitteeActionContainer + }; +}); + +const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' +}; +const errorMessage = 'errorMessage'; +const tx = buildMockTx(); +const deposit = BigInt('10000'); +const rewardAccount = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const anchor = { + url: 'anchorUrl', + dataHash: Wallet.Crypto.Hash32ByteBase16(Buffer.from('anchorDataHashanchorDataHashanch').toString('hex')) +}; + +const hardForkInitiationAction = { + __typename: Wallet.Cardano.GovernanceActionType.hard_fork_initiation_action +} as Wallet.Cardano.HardForkInitiationAction; +const infoAction = { + __typename: Wallet.Cardano.GovernanceActionType.info_action +} as Wallet.Cardano.InfoAction; +const newConstitution = { + __typename: Wallet.Cardano.GovernanceActionType.new_constitution +} as Wallet.Cardano.NewConstitution; +const noConfidence = { + __typename: Wallet.Cardano.GovernanceActionType.no_confidence +} as Wallet.Cardano.NoConfidence; +const parameterChangeAction = { + __typename: Wallet.Cardano.GovernanceActionType.parameter_change_action +} as Wallet.Cardano.ParameterChangeAction; +const treasuryWithdrawalsAction = { + __typename: Wallet.Cardano.GovernanceActionType.treasury_withdrawals_action +} as Wallet.Cardano.TreasuryWithdrawalsAction; +const updateCommittee = { + __typename: Wallet.Cardano.GovernanceActionType.update_committee +} as Wallet.Cardano.UpdateCommittee; + +const proposalProcedures = [ + { deposit, rewardAccount, anchor, governanceAction: hardForkInitiationAction }, + { deposit, rewardAccount, anchor, governanceAction: infoAction }, + { deposit, rewardAccount, anchor, governanceAction: newConstitution }, + { deposit, rewardAccount, anchor, governanceAction: noConfidence }, + { deposit, rewardAccount, anchor, governanceAction: parameterChangeAction }, + { deposit, rewardAccount, anchor, governanceAction: treasuryWithdrawalsAction }, + { deposit, rewardAccount, anchor, governanceAction: updateCommittee } +]; +const signTxData = { dappInfo, tx: { ...tx, body: { ...tx.body, proposalProcedures } } }; +const props = { signTxData, errorMessage }; + +describe('Testing ProposalProceduresContainer component', () => { + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('should render proper procedure', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render()); + }); + + expect(queryByTestId('HardForkInitiationActionContainer')).toBeInTheDocument(); + expect(queryByTestId('InfoActionContainer')).toBeInTheDocument(); + expect(queryByTestId('NewConstitutionActionContainer')).toBeInTheDocument(); + expect(queryByTestId('NoConfidenceActionContainer')).toBeInTheDocument(); + expect(queryByTestId('ParameterChangeActionContainer')).toBeInTheDocument(); + expect(queryByTestId('TreasuryWithdrawalsActionContainer')).toBeInTheDocument(); + expect(queryByTestId('UpdateCommitteeActionContainer')).toBeInTheDocument(); + + const expectedProps = { dappInfo: signTxData.dappInfo, errorMessage, deposit, rewardAccount, anchor }; + + expect(mockHardForkInitiationActionContainer).toHaveBeenLastCalledWith( + { ...expectedProps, governanceAction: hardForkInitiationAction }, + {} + ); + expect(mockInfoActionContainer).toHaveBeenLastCalledWith({ ...expectedProps, governanceAction: infoAction }, {}); + expect(mockNewConstitutionActionContainer).toHaveBeenLastCalledWith( + { ...expectedProps, governanceAction: newConstitution }, + {} + ); + expect(mockNoConfidenceActionContainer).toHaveBeenLastCalledWith( + { ...expectedProps, governanceAction: noConfidence }, + {} + ); + expect(mockParameterChangeActionContainer).toHaveBeenLastCalledWith( + { ...expectedProps, governanceAction: parameterChangeAction }, + {} + ); + expect(mockTreasuryWithdrawalsActionContainer).toHaveBeenLastCalledWith( + { ...expectedProps, governanceAction: treasuryWithdrawalsAction }, + {} + ); + expect(mockUpdateCommitteeActionContainer).toHaveBeenLastCalledWith( + { ...expectedProps, governanceAction: updateCommittee }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/VotingProceduresContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/VotingProceduresContainer.test.tsx index d1ddb7fdcc..fd1c35b40e 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/VotingProceduresContainer.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/VotingProceduresContainer.test.tsx @@ -13,22 +13,10 @@ import * as React from 'react'; import { cleanup, render } from '@testing-library/react'; import { VoterType, Votes, VotingProceduresContainer, getVote, getVoterType } from '../VotingProceduresContainer'; import '@testing-library/jest-dom'; -import { I18nextProvider } from 'react-i18next'; -import { StoreProvider } from '@src/stores'; -import { - AnalyticsProvider, - AppSettingsProvider, - BackgroundServiceAPIProvider, - BackgroundServiceAPIProviderProps, - DatabaseProvider -} from '@src/providers'; -import { APP_MODE_BROWSER } from '@src/utils/constants'; -import i18n from '@lib/i18n'; import { act } from 'react-dom/test-utils'; -import { PostHogClientProvider } from '@providers/PostHogClientProvider'; -import { postHogClientMocks } from '@src/utils/mocks/test-helpers'; import { buildMockTx } from '@src/utils/mocks/tx'; import { Wallet } from '@lace/cardano'; +import { getWrapper } from '../testing.utils'; jest.mock('@src/stores', () => ({ ...jest.requireActual('@src/stores'), @@ -65,30 +53,6 @@ jest.mock('react-i18next', () => { }; }); -const backgroundService = { - getBackgroundStorage: jest.fn(), - setBackgroundStorage: jest.fn() -} as unknown as BackgroundServiceAPIProviderProps['value']; - -const getWrapper = - () => - ({ children }: { children: React.ReactNode }) => - ( - - - - - - - {children} - - - - - - - ); - describe('Testing VotingProceduresContainer component', () => { beforeEach(() => { mockUseWalletStore.mockReset(); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/utils.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/utils.test.tsx index d998977a9f..a43ecc32b0 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/utils.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/utils.test.tsx @@ -102,37 +102,66 @@ describe('Testing utils', () => { const txInspectorCurriedFnPayload = { minted: [], burned: [] } as unknown as any; const createTxInspectorSpy = jest .spyOn(Core, 'createTxInspector') - .mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload, votingProcedures: true })); - expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.VotingProcedures); + .mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload, proposalProcedures: true })); + expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.ProposalProcedures); expect(createTxInspectorSpy).toHaveBeenCalledTimes(1); + createTxInspectorSpy.mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload, votingProcedures: true })); + expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.VotingProcedures); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(2); + createTxInspectorSpy.mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload, minted: { length: 1 } })); expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.Mint); - expect(createTxInspectorSpy).toHaveBeenCalledTimes(2); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(3); createTxInspectorSpy.mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload, burned: { length: 1 } })); expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.Burn); - expect(createTxInspectorSpy).toHaveBeenCalledTimes(3); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(4); createTxInspectorSpy.mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload, dRepRegistration: true })); expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.DRepRegistration); - expect(createTxInspectorSpy).toHaveBeenCalledTimes(4); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(5); createTxInspectorSpy.mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload, dRepRetirement: true })); expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.DRepRetirement); - expect(createTxInspectorSpy).toHaveBeenCalledTimes(5); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(6); createTxInspectorSpy.mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload, voteDelegation: true })); expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.VoteDelegation); - expect(createTxInspectorSpy).toHaveBeenCalledTimes(6); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(7); createTxInspectorSpy.mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload, dRepUpdate: true })); expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.DRepUpdate); - expect(createTxInspectorSpy).toHaveBeenCalledTimes(7); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(8); + + createTxInspectorSpy.mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload, stakeVoteDelegation: true })); + expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.StakeVoteDelegation); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(9); + + createTxInspectorSpy.mockReturnValueOnce(() => ({ + ...txInspectorCurriedFnPayload, + voteRegistrationDelegation: true + })); + expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.VoteRegistrationDelegation); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(10); + + createTxInspectorSpy.mockReturnValueOnce(() => ({ + ...txInspectorCurriedFnPayload, + stakeRegistrationDelegation: true + })); + expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.StakeRegistrationDelegation); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(11); + + createTxInspectorSpy.mockReturnValueOnce(() => ({ + ...txInspectorCurriedFnPayload, + stakeVoteDelegationRegistration: true + })); + expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.StakeVoteDelegationRegistration); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(12); createTxInspectorSpy.mockReturnValueOnce(() => ({ ...txInspectorCurriedFnPayload })); expect(getTxType(tx)).toEqual(Wallet.Cip30TxType.Send); - expect(createTxInspectorSpy).toHaveBeenCalledTimes(8); + expect(createTxInspectorSpy).toHaveBeenCalledTimes(13); }); test('testing drepIDasBech32FromHash', () => { diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts index 602b689441..105b686672 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts @@ -12,6 +12,7 @@ import { dAppRoutePaths } from '@routes'; import { Wallet } from '@lace/cardano'; import { useRedirection } from '@hooks'; import { CardanoTxOut, WalletInfo } from '@src/types'; +import { config } from '@src/config'; import { TokenInfo, getAssetsInformation } from '@src/utils/get-assets-information'; import { getTransactionAssetsId } from '@src/stores/slices'; import { AddressListType } from '@src/views/browser-view/features/activity'; @@ -288,3 +289,20 @@ export const useGetOwnPubDRepKeyHash = (): UseGetOwnPubDRepKeyHash => { // TODO consider using Zustand or at least some common abstraction e.g. https://github.com/streamich/react-use/blob/master/src/useAsync.ts return { loading: ownPubDRepKeyHash === undefined, ownPubDRepKeyHash }; }; + +export const useCExpolorerBaseUrl = (): string => { + const [explorerBaseUrl, setExplorerBaseUrl] = useState(''); + const { environmentName } = useWalletStore(); + + const { CEXPLORER_BASE_URL, CEXPLORER_URL_PATHS } = config(); + + useEffect(() => { + const newUrl = + environmentName === 'Sanchonet' ? '' : `${CEXPLORER_BASE_URL[environmentName]}/${CEXPLORER_URL_PATHS.Tx}`; + if (newUrl !== explorerBaseUrl) { + setExplorerBaseUrl(newUrl); + } + }, [CEXPLORER_BASE_URL, CEXPLORER_URL_PATHS.Tx, environmentName, explorerBaseUrl]); + + return explorerBaseUrl; +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/HardForkInitiationActionContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/HardForkInitiationActionContainer.tsx new file mode 100644 index 0000000000..30b937804e --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/HardForkInitiationActionContainer.tsx @@ -0,0 +1,93 @@ +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Wallet } from '@lace/cardano'; +import { HardForkInitiationAction } from '@lace/core'; +import { useWalletStore } from '@src/stores'; +import { SignTxData } from '../types'; +import { useCExpolorerBaseUrl } from '../hooks'; + +interface Props { + dappInfo: SignTxData['dappInfo']; + governanceAction: Wallet.Cardano.HardForkInitiationAction; + deposit: Wallet.Cardano.ProposalProcedure['deposit']; + rewardAccount: Wallet.Cardano.ProposalProcedure['rewardAccount']; + anchor: Wallet.Cardano.ProposalProcedure['anchor']; + errorMessage?: string; +} + +export const HardForkInitiationActionContainer = ({ + dappInfo, + governanceAction, + deposit, + rewardAccount, + anchor, + errorMessage +}: Props): React.ReactElement => { + const { t } = useTranslation(); + const { + walletUI: { cardanoCoin } + } = useWalletStore(); + + const explorerBaseUrl = useCExpolorerBaseUrl(); + + const translations = useMemo[0]['translations']>( + () => ({ + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + }, + protocolVersion: { + major: t('core.ProposalProcedure.governanceAction.hardForkInitiation.protocolVersion.major'), + minor: t('core.ProposalProcedure.governanceAction.hardForkInitiation.protocolVersion.minor'), + patch: t('core.ProposalProcedure.governanceAction.hardForkInitiation.protocolVersion.patch') + }, + actionId: { + title: t('core.ProposalProcedure.governanceAction.actionId.title'), + index: t('core.ProposalProcedure.governanceAction.actionId.index'), + txId: t('core.ProposalProcedure.governanceAction.actionId.txId') + } + }), + [t] + ); + + const { governanceActionId, protocolVersion } = governanceAction; + + const data: Parameters[0]['data'] = { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.hardForkInitiation.title'), + deposit: `${Wallet.util.lovelacesToAdaString(deposit.toString())} ${cardanoCoin.symbol}`, + rewardAccount + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + ...(explorerBaseUrl && { txHashUrl: `${explorerBaseUrl}/${anchor.dataHash}` }) + } + }, + protocolVersion: { + major: protocolVersion.major.toString(), + minor: protocolVersion.minor.toString(), + patch: protocolVersion.patch?.toString() + }, + ...(governanceActionId && { + actionId: { + index: governanceActionId.actionIndex.toString(), + id: governanceActionId.id || '' + } + }) + }; + + return ( + + ); +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/InfoActionContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/InfoActionContainer.tsx new file mode 100644 index 0000000000..de46a3b62f --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/InfoActionContainer.tsx @@ -0,0 +1,50 @@ +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Wallet } from '@lace/cardano'; +import { InfoAction } from '@lace/core'; +import { SignTxData } from '../types'; +import { useCExpolorerBaseUrl } from '../hooks'; + +interface Props { + dappInfo: SignTxData['dappInfo']; + anchor: Wallet.Cardano.ProposalProcedure['anchor']; + errorMessage?: string; +} + +export const InfoActionContainer = ({ dappInfo, anchor, errorMessage }: Props): React.ReactElement => { + const { t } = useTranslation(); + + const explorerBaseUrl = useCExpolorerBaseUrl(); + + const translations = useMemo[0]['translations']>( + () => ({ + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + } + }), + [t] + ); + + const data: Parameters[0]['data'] = { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.infoAction.title') + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + ...(explorerBaseUrl && { txHashUrl: `${explorerBaseUrl}/${anchor.dataHash}` }) + } + } + }; + + return ; +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/NewConstitutionActionContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/NewConstitutionActionContainer.tsx new file mode 100644 index 0000000000..de84864942 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/NewConstitutionActionContainer.tsx @@ -0,0 +1,98 @@ +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Wallet } from '@lace/cardano'; +import { NewConstitutionAction } from '@lace/core'; +import { useWalletStore } from '@src/stores'; +import { SignTxData } from '../types'; +import { useCExpolorerBaseUrl } from '../hooks'; + +interface Props { + dappInfo: SignTxData['dappInfo']; + governanceAction: Wallet.Cardano.NewConstitution; + deposit: Wallet.Cardano.ProposalProcedure['deposit']; + rewardAccount: Wallet.Cardano.ProposalProcedure['rewardAccount']; + anchor: Wallet.Cardano.ProposalProcedure['anchor']; + errorMessage?: string; +} + +export const NewConstitutionActionContainer = ({ + dappInfo, + governanceAction, + deposit, + rewardAccount, + anchor, + errorMessage +}: Props): React.ReactElement => { + const { t } = useTranslation(); + const { + walletUI: { cardanoCoin } + } = useWalletStore(); + + const explorerBaseUrl = useCExpolorerBaseUrl(); + + const translations = useMemo[0]['translations']>( + () => ({ + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + }, + constitution: { + title: t('core.ProposalProcedure.governanceAction.newConstitutionAction.constitution.title'), + anchor: { + dataHash: t('core.ProposalProcedure.governanceAction.newConstitutionAction.constitution.anchor.dataHash'), + url: t('core.ProposalProcedure.governanceAction.newConstitutionAction.constitution.anchor.url') + }, + scriptHash: t('core.ProposalProcedure.governanceAction.newConstitutionAction.constitution.scriptHash') + }, + actionId: { + title: t('core.ProposalProcedure.governanceAction.actionId.title'), + index: t('core.ProposalProcedure.governanceAction.actionId.index'), + txId: t('core.ProposalProcedure.governanceAction.actionId.txId') + } + }), + [t] + ); + + const { governanceActionId, constitution } = governanceAction; + + const data: Parameters[0]['data'] = { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.newConstitutionAction.title'), + deposit: `${Wallet.util.lovelacesToAdaString(deposit.toString())} ${cardanoCoin.symbol}`, + rewardAccount + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + ...(explorerBaseUrl && { txHashUrl: `${explorerBaseUrl}/${anchor.dataHash}` }) + } + }, + ...(governanceActionId && { + actionId: { + index: governanceActionId.actionIndex.toString(), + id: governanceActionId.id || '' + } + }), + constitution: { + anchor: { + dataHash: constitution.anchor.dataHash.toString(), + url: constitution.anchor.url.toString() + }, + ...(constitution.scriptHash && { scriptHash: constitution.scriptHash.toString() }) + } + }; + + return ( + + ); +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/NoConfidenceActionContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/NoConfidenceActionContainer.tsx new file mode 100644 index 0000000000..7605b54424 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/NoConfidenceActionContainer.tsx @@ -0,0 +1,81 @@ +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Wallet } from '@lace/cardano'; +import { NoConfidenceAction } from '@lace/core'; +import { useWalletStore } from '@src/stores'; +import { SignTxData } from '../types'; +import { useCExpolorerBaseUrl } from '../hooks'; + +interface Props { + dappInfo: SignTxData['dappInfo']; + governanceAction: Wallet.Cardano.NoConfidence; + deposit: Wallet.Cardano.ProposalProcedure['deposit']; + rewardAccount: Wallet.Cardano.ProposalProcedure['rewardAccount']; + anchor: Wallet.Cardano.ProposalProcedure['anchor']; + errorMessage?: string; +} + +export const NoConfidenceActionContainer = ({ + dappInfo, + governanceAction, + deposit, + rewardAccount, + anchor, + errorMessage +}: Props): React.ReactElement => { + const { t } = useTranslation(); + const { + walletUI: { cardanoCoin } + } = useWalletStore(); + + const explorerBaseUrl = useCExpolorerBaseUrl(); + + const translations = useMemo[0]['translations']>( + () => ({ + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + }, + actionId: { + title: t('core.ProposalProcedure.governanceAction.actionId.title'), + index: t('core.ProposalProcedure.governanceAction.actionId.index'), + txId: t('core.ProposalProcedure.governanceAction.actionId.txId') + } + }), + [t] + ); + + const { governanceActionId } = governanceAction; + + const data: Parameters[0]['data'] = { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.noConfidenceAction.title'), + deposit: `${Wallet.util.lovelacesToAdaString(deposit.toString())} ${cardanoCoin.symbol}`, + rewardAccount + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + ...(explorerBaseUrl && { txHashUrl: `${explorerBaseUrl}/${anchor.dataHash}` }) + } + }, + ...(governanceActionId && { + actionId: { + index: governanceActionId.actionIndex.toString(), + id: governanceActionId.id || '' + } + }) + }; + + return ; +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/ParameterChangeActionContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/ParameterChangeActionContainer.tsx new file mode 100644 index 0000000000..31264a6be2 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/ParameterChangeActionContainer.tsx @@ -0,0 +1,341 @@ +/* eslint-disable unicorn/no-array-reduce */ +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { formatPercentages } from '@lace/common'; +import { Wallet } from '@lace/cardano'; +import { ParameterChangeAction } from '@lace/core'; +import { useWalletStore } from '@src/stores'; +import { SignTxData } from '../types'; +import { useCExpolorerBaseUrl } from '../hooks'; + +interface Props { + dappInfo: SignTxData['dappInfo']; + governanceAction: Wallet.Cardano.ParameterChangeAction; + deposit: Wallet.Cardano.ProposalProcedure['deposit']; + rewardAccount: Wallet.Cardano.ProposalProcedure['rewardAccount']; + anchor: Wallet.Cardano.ProposalProcedure['anchor']; + errorMessage?: string; +} + +export const ParameterChangeActionContainer = ({ + dappInfo, + governanceAction, + deposit, + rewardAccount, + anchor, + errorMessage +}: Props): React.ReactElement => { + const { t } = useTranslation(); + const { + walletUI: { cardanoCoin } + } = useWalletStore(); + + const explorerBaseUrl = useCExpolorerBaseUrl(); + + const translations = useMemo[0]['translations']>( + () => ({ + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + }, + memory: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.memory'), + step: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.step'), + networkGroup: { + title: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.title'), + maxBBSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxBBSize'), + maxTxSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxTxSize'), + maxBHSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxBHSize'), + maxValSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxValSize'), + maxTxExUnits: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxTxExUnits'), + maxBlockExUnits: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxBlockExUnits'), + maxCollateralInputs: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxCollateralInputs' + ), + coinsByUTXOByte: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.coinsByUTXOByte'), + tooltip: { + maxBBSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxBBSize'), + maxTxSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxTxSize'), + maxBHSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxBHSize'), + maxValSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxValSize'), + maxTxExUnits: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxTxExUnits' + ), + maxBlockExUnits: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxBlockExUnits' + ), + maxCollateralInputs: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxCollateralInputs' + ), + coinsByUTXOByte: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.coinsByUTXOByte' + ) + } + }, + economicGroup: { + title: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.title'), + minFeeA: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.minFeeA'), + minFeeB: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.minFeeB'), + keyDeposit: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.keyDeposit'), + poolDeposit: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.poolDeposit'), + rho: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.rho'), + tau: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tau'), + minPoolCost: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.minPoolCost'), + coinsPerUTxOByte: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.coinsPerUTxOByte' + ), + prices: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.prices'), + tooltip: { + minFeeA: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.minFeeA'), + minFeeB: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.minFeeB'), + keyDeposit: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.keyDeposit'), + poolDeposit: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.poolDeposit' + ), + rho: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.rho'), + tau: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.tau'), + minPoolCost: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.minPoolCost' + ), + coinsPerUTxOByte: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.coinsPerUTxOByte' + ), + prices: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.prices') + } + }, + technicalGroup: { + title: t('core.ProposalProcedure.governanceAction.technicalGroup.title'), + a0: t('core.ProposalProcedure.governanceAction.technicalGroup.a0'), + eMax: t('core.ProposalProcedure.governanceAction.technicalGroup.eMax'), + nOpt: t('core.ProposalProcedure.governanceAction.technicalGroup.nOpt'), + costModels: t('core.ProposalProcedure.governanceAction.technicalGroup.costModels'), + collateralPercentage: t('core.ProposalProcedure.governanceAction.technicalGroup.collateralPercentage'), + tooltip: { + a0: t('core.ProposalProcedure.governanceAction.technicalGroup.tooltip.a0'), + eMax: t('core.ProposalProcedure.governanceAction.technicalGroup.tooltip.eMax'), + nOpt: t('core.ProposalProcedure.governanceAction.technicalGroup.tooltip.nOpt'), + costModels: t('core.ProposalProcedure.governanceAction.technicalGroup.tooltip.costModels'), + collateralPercentage: t('core.ProposalProcedure.governanceAction.technicalGroup.tooltip.collateralPercentage') + } + }, + governanceGroup: { + title: t('core.ProposalProcedure.governanceAction.governanceGroup.title'), + govActionLifetime: t('core.ProposalProcedure.governanceAction.governanceGroup.govActionLifetime'), + govActionDeposit: t('core.ProposalProcedure.governanceAction.governanceGroup.govActionDeposit'), + drepDeposit: t('core.ProposalProcedure.governanceAction.governanceGroup.drepDeposit'), + drepActivity: t('core.ProposalProcedure.governanceAction.governanceGroup.drepActivity'), + ccMinSize: t('core.ProposalProcedure.governanceAction.governanceGroup.ccMinSize'), + ccMaxTermLength: t('core.ProposalProcedure.governanceAction.governanceGroup.ccMaxTermLength'), + dRepVotingThresholds: { + title: t('core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.title'), + motionNoConfidence: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.motionNoConfidence' + ), + committeeNormal: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.committeeNormal' + ), + committeeNoConfidence: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.committeeNoConfidence' + ), + updateConstitution: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.updateConstitution' + ), + hardForkInitiation: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.hardForkInitiation' + ), + ppNetworkGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.ppNetworkGroup' + ), + ppEconomicGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.ppEconomicGroup' + ), + ppTechnicalGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.ppTechnicalGroup' + ), + ppGovernanceGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.ppGovernanceGroup' + ), + treasuryWithdrawal: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.treasuryWithdrawal' + ) + }, + tooltip: { + govActionLifetime: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.govActionLifetime'), + govActionDeposit: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.govActionDeposit'), + drepDeposit: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.drepDeposit'), + drepActivity: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.drepActivity'), + ccMinSize: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.ccMinSize'), + ccMaxTermLength: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.ccMaxTermLength'), + dRepVotingThresholds: { + title: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.title'), + motionNoConfidence: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.motionNoConfidence' + ), + committeeNormal: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.committeeNormal' + ), + committeeNoConfidence: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.committeeNoConfidence' + ), + updateConstitution: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.updateConstitution' + ), + hardForkInitiation: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.hardForkInitiation' + ), + ppNetworkGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.ppNetworkGroup' + ), + ppEconomicGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.ppEconomicGroup' + ), + ppTechnicalGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.ppTechnicalGroup' + ), + ppGovernanceGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.ppGovernanceGroup' + ), + treasuryWithdrawal: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.treasuryWithdrawal' + ) + } + } + } + }), + [t] + ); + + const { + protocolParamUpdate: { + maxBlockBodySize, + maxTxSize, + maxBlockHeaderSize, + maxValueSize, + maxExecutionUnitsPerTransaction, + maxExecutionUnitsPerBlock, + maxCollateralInputs, + stakeKeyDeposit, + poolDeposit, + minFeeCoefficient, + minFeeConstant, + treasuryExpansion, + monetaryExpansion, + minPoolCost, + coinsPerUtxoByte, + prices, + poolInfluence, + poolRetirementEpochBound, + desiredNumberOfPools, + costModels, + collateralPercentage, + governanceActionDeposit, + dRepDeposit, + governanceActionValidityPeriod, + dRepInactivityPeriod, + minCommitteeSize, + committeeTermLimit, + dRepVotingThresholds: { + motionNoConfidence, + committeeNormal, + commiteeNoConfidence, + updateConstitution, + hardForkInitiation, + ppNetworkGroup, + ppEconomicGroup, + ppTechnicalGroup, + ppGovernanceGroup, + treasuryWithdrawal + } + } + } = governanceAction; + + const data: Parameters[0]['data'] = { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.title'), + deposit: `${Wallet.util.lovelacesToAdaString(deposit.toString())} ${cardanoCoin.symbol}`, + rewardAccount + }, + anchor: { + url: anchor.url, + hash: anchor.dataHash, + ...(explorerBaseUrl && { txHashUrl: `${explorerBaseUrl}/${anchor.dataHash}` }) + }, + protocolParamUpdate: { + maxTxExUnits: { + memory: maxExecutionUnitsPerTransaction.memory.toString(), + step: maxExecutionUnitsPerTransaction.steps.toString() + }, + maxBlockExUnits: { + memory: maxExecutionUnitsPerBlock.memory.toString(), + step: maxExecutionUnitsPerBlock.steps.toString() + }, + networkGroup: { + maxBBSize: maxBlockBodySize.toString(), + maxTxSize: maxTxSize.toString(), + maxBHSize: maxBlockHeaderSize.toString(), + maxValSize: maxValueSize.toString(), + maxCollateralInputs: maxCollateralInputs.toString() + }, + economicGroup: { + minFeeA: minFeeCoefficient.toString(), + minFeeB: minFeeConstant.toString(), + keyDeposit: stakeKeyDeposit.toString(), + poolDeposit: poolDeposit.toString(), + rho: monetaryExpansion, + tau: treasuryExpansion, + minPoolCost: minPoolCost.toString(), + coinsPerUTxOByte: coinsPerUtxoByte.toString(), + price: { + memory: prices.memory.toString(), + step: prices.steps.toString() + } + }, + technicalGroup: { + a0: poolInfluence, + eMax: poolRetirementEpochBound.toString(), + nOpt: desiredNumberOfPools.toString(), + // TODO: review cost model syntax/display + costModels: { + PlutusV1: Object.entries(costModels.get(Wallet.Cardano.PlutusLanguageVersion.V1)).reduce( + (acc, cur) => ({ ...acc, [cur[0]]: cur[1] }), + {} + ), + PlutusV2: Object.entries(costModels.get(Wallet.Cardano.PlutusLanguageVersion.V2)).reduce( + (acc, cur) => ({ ...acc, [cur[0]]: cur[1] }), + {} + ) + }, + collateralPercentage: collateralPercentage.toString() + }, + governanceGroup: { + govActionLifetime: governanceActionValidityPeriod.toString(), + govActionDeposit: governanceActionDeposit.toString(), + drepDeposit: dRepDeposit.toString(), + drepActivity: dRepInactivityPeriod.toString(), + ccMinSize: minCommitteeSize.toString(), + ccMaxTermLength: committeeTermLimit.toString(), + dRepVotingThresholds: { + motionNoConfidence: formatPercentages(motionNoConfidence.numerator / motionNoConfidence.denominator), + committeeNormal: formatPercentages(committeeNormal.numerator / committeeNormal.denominator), + committeeNoConfidence: formatPercentages(commiteeNoConfidence.numerator / commiteeNoConfidence.denominator), + updateToConstitution: formatPercentages(updateConstitution.numerator / updateConstitution.denominator), + hardForkInitiation: formatPercentages(hardForkInitiation.numerator / hardForkInitiation.denominator), + ppNetworkGroup: formatPercentages(ppNetworkGroup.numerator / ppNetworkGroup.denominator), + ppEconomicGroup: formatPercentages(ppEconomicGroup.numerator / ppEconomicGroup.denominator), + ppTechnicalGroup: formatPercentages(ppTechnicalGroup.numerator / ppTechnicalGroup.denominator), + ppGovGroup: formatPercentages(ppGovernanceGroup.numerator / ppGovernanceGroup.denominator), + treasuryWithdrawal: formatPercentages(treasuryWithdrawal.numerator / treasuryWithdrawal.denominator) + } + } + } + }; + + return ( + + ); +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/TreasuryWithdrawalsActionContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/TreasuryWithdrawalsActionContainer.tsx new file mode 100644 index 0000000000..6cb2d0d385 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/TreasuryWithdrawalsActionContainer.tsx @@ -0,0 +1,91 @@ +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Wallet } from '@lace/cardano'; +import { TreasuryWithdrawalsAction } from '@lace/core'; +import { useWalletStore } from '@src/stores'; +import { SignTxData } from '../types'; +import { useCExpolorerBaseUrl } from '../hooks'; + +interface Props { + dappInfo: SignTxData['dappInfo']; + governanceAction: Wallet.Cardano.TreasuryWithdrawalsAction; + deposit: Wallet.Cardano.ProposalProcedure['deposit']; + rewardAccount: Wallet.Cardano.ProposalProcedure['rewardAccount']; + anchor: Wallet.Cardano.ProposalProcedure['anchor']; + errorMessage?: string; +} + +export const TreasuryWithdrawalsActionContainer = ({ + dappInfo, + governanceAction, + deposit, + rewardAccount, + anchor, + errorMessage +}: Props): React.ReactElement => { + const { t } = useTranslation(); + const { + walletUI: { cardanoCoin } + } = useWalletStore(); + + const explorerBaseUrl = useCExpolorerBaseUrl(); + + const translations = useMemo[0]['translations']>( + () => ({ + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + }, + actionId: { + title: t('core.ProposalProcedure.governanceAction.actionId.title'), + index: t('core.ProposalProcedure.governanceAction.actionId.index'), + txId: t('core.ProposalProcedure.governanceAction.actionId.txId') + }, + withdrawals: { + title: t('core.ProposalProcedure.governanceAction.treasuryWithdrawals.title'), + rewardAccount: t('core.ProposalProcedure.governanceAction.treasuryWithdrawals.withdrawals.rewardAccount'), + lovelace: t('core.ProposalProcedure.governanceAction.treasuryWithdrawals.withdrawals.lovelace') + } + }), + [t] + ); + + const { withdrawals } = governanceAction; + + const data: Parameters[0]['data'] = { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.treasuryWithdrawals.title'), + deposit: `${Wallet.util.lovelacesToAdaString(deposit.toString())} ${cardanoCoin.symbol}`, + rewardAccount + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + ...(explorerBaseUrl && { txHashUrl: `${explorerBaseUrl}/${anchor.dataHash}` }) + } + }, + withdrawals: [...withdrawals].map((withdrawal) => ({ + rewardAccount: withdrawal.rewardAccount.toString(), + lovelace: `${Wallet.util.lovelacesToAdaString(withdrawal.coin.toString())} ${cardanoCoin.symbol}` + })) + }; + + return ( + + ); +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/UpdateCommitteeActionContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/UpdateCommitteeActionContainer.tsx new file mode 100644 index 0000000000..0a01112d1f --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/UpdateCommitteeActionContainer.tsx @@ -0,0 +1,107 @@ +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Wallet } from '@lace/cardano'; +import { UpdateCommitteeAction } from '@lace/core'; +import { useWalletStore } from '@src/stores'; +import { SignTxData } from '../types'; +import { useCExpolorerBaseUrl } from '../hooks'; + +interface Props { + dappInfo: SignTxData['dappInfo']; + governanceAction: Wallet.Cardano.UpdateCommittee; + deposit: Wallet.Cardano.ProposalProcedure['deposit']; + rewardAccount: Wallet.Cardano.ProposalProcedure['rewardAccount']; + anchor: Wallet.Cardano.ProposalProcedure['anchor']; + errorMessage?: string; +} + +export const UpdateCommitteeActionContainer = ({ + dappInfo, + governanceAction, + deposit, + rewardAccount, + anchor, + errorMessage +}: Props): React.ReactElement => { + const { t } = useTranslation(); + const { + walletUI: { cardanoCoin } + } = useWalletStore(); + + const explorerBaseUrl = useCExpolorerBaseUrl(); + + const translations = useMemo[0]['translations']>( + () => ({ + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + }, + actionId: { + title: t('core.ProposalProcedure.governanceAction.actionId.title'), + index: t('core.ProposalProcedure.governanceAction.actionId.index'), + txId: t('core.ProposalProcedure.governanceAction.actionId.txId') + }, + membersToBeAdded: { + title: t('core.ProposalProcedure.governanceAction.updateCommitteeAction.membersToBeAdded.title'), + coldCredential: { + hash: t('core.ProposalProcedure.governanceAction.updateCommitteeAction.membersToBeAdded.coldCredential.hash'), + epoch: t( + 'core.ProposalProcedure.governanceAction.updateCommitteeAction.membersToBeAdded.coldCredential.epoch' + ) + } + }, + membersToBeRemoved: { + title: t('core.ProposalProcedure.governanceAction.updateCommitteeAction.membersToBeRemoved.title'), + hash: t('core.ProposalProcedure.governanceAction.updateCommitteeAction.membersToBeRemoved.hash') + } + }), + [t] + ); + + const { membersToBeAdded, membersToBeRemoved, governanceActionId } = governanceAction; + + const data: Parameters[0]['data'] = { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.updateCommitteeAction.title'), + deposit: `${Wallet.util.lovelacesToAdaString(deposit.toString())} ${cardanoCoin.symbol}`, + rewardAccount + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + ...(explorerBaseUrl && { txHashUrl: `${explorerBaseUrl}/${anchor.dataHash}` }) + } + }, + ...(governanceActionId && { + actionId: { + index: governanceActionId.actionIndex.toString(), + id: governanceActionId.id || '' + } + }), + membersToBeAdded: [...membersToBeAdded].map(({ coldCredential: { hash }, epoch }) => ({ + coldCredential: { + // TODO: use bech32 in future revision + hash: hash.toString() + }, + epoch: epoch.toString() + })), + membersToBeRemoved: [...membersToBeRemoved].map(({ hash }) => ({ + // TODO: use bech32 in future revision + hash: hash.toString() + })) + }; + + return ( + + ); +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/HardForkInitiationActionContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/HardForkInitiationActionContainer.test.tsx new file mode 100644 index 0000000000..3ff6302ac4 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/HardForkInitiationActionContainer.test.tsx @@ -0,0 +1,175 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable import/imports-first */ +const cardanoCoinMock = { + symbol: 'cardanoCoinMockSymbol' +}; +const mockUseWalletStore = jest.fn(() => ({ + walletUI: { cardanoCoin: cardanoCoinMock }, + walletInfo: {} +})); +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockHardForkInitiationAction = jest.fn(() => ); +const mockLovelacesToAdaString = jest.fn((val) => val); +const mockedCExpolorerBaseUrl = 'mockedCExpolorerBaseUrl'; +const mockUseCExpolorerBaseUrl = jest.fn(() => mockedCExpolorerBaseUrl); +import { Wallet } from '@lace/cardano'; +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { act } from 'react-dom/test-utils'; +import { HardForkInitiationActionContainer } from '../HardForkInitiationActionContainer'; +import { getWrapper } from '../../testing.utils'; + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + HardForkInitiationAction: mockHardForkInitiationAction + }; +}); + +jest.mock('@src/stores', () => ({ + ...jest.requireActual('@src/stores'), + useWalletStore: mockUseWalletStore +})); + +jest.mock('../../hooks', () => { + const original = jest.requireActual('../../hooks'); + return { + __esModule: true, + ...original, + useCExpolorerBaseUrl: mockUseCExpolorerBaseUrl + }; +}); + +jest.mock('@lace/cardano', () => { + const actual = jest.requireActual('@lace/cardano'); + return { + __esModule: true, + ...actual, + Wallet: { + ...actual.Wallet, + util: { + ...actual.Wallet.util, + lovelacesToAdaString: mockLovelacesToAdaString + } + } + }; +}); + +const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' +}; +const errorMessage = 'errorMessage'; +const deposit = BigInt('10000'); +const rewardAccount = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const anchor = { + url: 'anchorUrl', + dataHash: Wallet.Crypto.Hash32ByteBase16(Buffer.from('anchorDataHashanchorDataHashanch').toString('hex')) +}; + +const hardForkInitiationAction = { + protocolVersion: { + major: 123, + minor: 234, + patch: 456 + }, + governanceActionId: { + actionIndex: 123, + id: Wallet.Cardano.TransactionId('724a0a88b9470a714fc5bf84daf5851fa259a9b89e1a5453f6f5cd6595ad9821') + }, + __typename: Wallet.Cardano.GovernanceActionType.hard_fork_initiation_action +} as Wallet.Cardano.HardForkInitiationAction; + +describe('Testing ProposalProceduresContainer component', () => { + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('should render HardForkInitiationAction component with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + , + { + wrapper: getWrapper() + } + )); + }); + + expect(queryByTestId('HardForkInitiationAction')).toBeInTheDocument(); + expect(mockHardForkInitiationAction).toHaveBeenLastCalledWith( + { + dappInfo, + data: { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.hardForkInitiation.title'), + deposit: `${deposit.toString()} ${cardanoCoinMock.symbol}`, + rewardAccount + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + txHashUrl: `${mockedCExpolorerBaseUrl}/${anchor.dataHash}` + } + }, + protocolVersion: { + major: hardForkInitiationAction.protocolVersion.major.toString(), + minor: hardForkInitiationAction.protocolVersion.minor.toString(), + patch: hardForkInitiationAction.protocolVersion.patch?.toString() + }, + actionId: { + index: hardForkInitiationAction.governanceActionId.actionIndex.toString(), + id: hardForkInitiationAction.governanceActionId.id || '' + } + }, + translations: { + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + }, + protocolVersion: { + major: t('core.ProposalProcedure.governanceAction.hardForkInitiation.protocolVersion.major'), + minor: t('core.ProposalProcedure.governanceAction.hardForkInitiation.protocolVersion.minor'), + patch: t('core.ProposalProcedure.governanceAction.hardForkInitiation.protocolVersion.patch') + }, + actionId: { + title: t('core.ProposalProcedure.governanceAction.actionId.title'), + index: t('core.ProposalProcedure.governanceAction.actionId.index'), + txId: t('core.ProposalProcedure.governanceAction.actionId.txId') + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/InfoActionContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/InfoActionContainer.test.tsx new file mode 100644 index 0000000000..e4b11d214c --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/InfoActionContainer.test.tsx @@ -0,0 +1,115 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable import/imports-first */ +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockInfoAction = jest.fn(() => ); +const mockedCExpolorerBaseUrl = 'mockedCExpolorerBaseUrl'; +const mockUseCExpolorerBaseUrl = jest.fn(() => mockedCExpolorerBaseUrl); +import { Wallet } from '@lace/cardano'; +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { act } from 'react-dom/test-utils'; +import { InfoActionContainer } from '../InfoActionContainer'; +import { getWrapper } from '../../testing.utils'; + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + InfoAction: mockInfoAction + }; +}); + +jest.mock('../../hooks', () => { + const original = jest.requireActual('../../hooks'); + return { + __esModule: true, + ...original, + useCExpolorerBaseUrl: mockUseCExpolorerBaseUrl + }; +}); + +const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' +}; +const errorMessage = 'errorMessage'; +const deposit = BigInt('10000'); +const rewardAccount = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const anchor = { + url: 'anchorUrl', + dataHash: Wallet.Crypto.Hash32ByteBase16(Buffer.from('anchorDataHashanchorDataHashanch').toString('hex')) +}; + +const infoAction = { + __typename: Wallet.Cardano.GovernanceActionType.info_action +} as Wallet.Cardano.InfoAction; + +describe('Testing ProposalProceduresContainer component', () => { + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('should render InfoAction component with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + , + { + wrapper: getWrapper() + } + )); + }); + + expect(queryByTestId('InfoAction')).toBeInTheDocument(); + expect(mockInfoAction).toHaveBeenLastCalledWith( + { + dappInfo, + data: { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.infoAction.title') + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + txHashUrl: `${mockedCExpolorerBaseUrl}/${anchor.dataHash}` + } + } + }, + translations: { + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/NewConstitutionActionContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/NewConstitutionActionContainer.test.tsx new file mode 100644 index 0000000000..468233c9b3 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/NewConstitutionActionContainer.test.tsx @@ -0,0 +1,179 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable import/imports-first */ +const cardanoCoinMock = { + symbol: 'cardanoCoinMockSymbol' +}; +const mockUseWalletStore = jest.fn(() => ({ + walletUI: { cardanoCoin: cardanoCoinMock }, + walletInfo: {} +})); +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockNewConstitutionAction = jest.fn(() => ); +const mockLovelacesToAdaString = jest.fn((val) => val); +const mockedCExpolorerBaseUrl = 'mockedCExpolorerBaseUrl'; +const mockUseCExpolorerBaseUrl = jest.fn(() => mockedCExpolorerBaseUrl); +import { Wallet } from '@lace/cardano'; +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { act } from 'react-dom/test-utils'; +import { NewConstitutionActionContainer } from '../NewConstitutionActionContainer'; +import { getWrapper } from '../../testing.utils'; + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + NewConstitutionAction: mockNewConstitutionAction + }; +}); + +jest.mock('@src/stores', () => ({ + ...jest.requireActual('@src/stores'), + useWalletStore: mockUseWalletStore +})); + +jest.mock('../../hooks', () => { + const original = jest.requireActual('../../hooks'); + return { + __esModule: true, + ...original, + useCExpolorerBaseUrl: mockUseCExpolorerBaseUrl + }; +}); + +jest.mock('@lace/cardano', () => { + const actual = jest.requireActual('@lace/cardano'); + return { + __esModule: true, + ...actual, + Wallet: { + ...actual.Wallet, + util: { + ...actual.Wallet.util, + lovelacesToAdaString: mockLovelacesToAdaString + } + } + }; +}); + +const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' +}; +const errorMessage = 'errorMessage'; +const deposit = BigInt('10000'); +const rewardAccount = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const anchor = { + url: 'anchorUrl', + dataHash: Wallet.Crypto.Hash32ByteBase16(Buffer.from('anchorDataHashanchorDataHashanch').toString('hex')) +}; + +const newConstitution = { + __typename: Wallet.Cardano.GovernanceActionType.new_constitution, + constitution: { + anchor, + scriptHash: Wallet.Crypto.Hash28ByteBase16(Buffer.from('newConstitutionscriptHashnew').toString('hex')) + }, + governanceActionId: { + actionIndex: 123, + id: Wallet.Cardano.TransactionId('724a0a88b9470a714fc5bf84daf5851fa259a9b89e1a5453f6f5cd6595ad9821') + } +} as Wallet.Cardano.NewConstitution; + +describe('Testing ProposalProceduresContainer component', () => { + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('should render NewConstitutionAction component with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + , + { + wrapper: getWrapper() + } + )); + }); + + expect(queryByTestId('NewConstitutionAction')).toBeInTheDocument(); + expect(mockNewConstitutionAction).toHaveBeenLastCalledWith( + { + dappInfo, + data: { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.newConstitutionAction.title'), + deposit: `${deposit.toString()} ${cardanoCoinMock.symbol}`, + rewardAccount + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + txHashUrl: `${mockedCExpolorerBaseUrl}/${anchor.dataHash}` + } + }, + actionId: { + index: newConstitution.governanceActionId.actionIndex.toString(), + id: newConstitution.governanceActionId.id || '' + }, + constitution: { + anchor: { + dataHash: newConstitution.constitution.anchor.dataHash.toString(), + url: newConstitution.constitution.anchor.url.toString() + }, + scriptHash: newConstitution.constitution.scriptHash.toString() + } + }, + translations: { + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + }, + constitution: { + title: t('core.ProposalProcedure.governanceAction.newConstitutionAction.constitution.title'), + anchor: { + dataHash: t('core.ProposalProcedure.governanceAction.newConstitutionAction.constitution.anchor.dataHash'), + url: t('core.ProposalProcedure.governanceAction.newConstitutionAction.constitution.anchor.url') + }, + scriptHash: t('core.ProposalProcedure.governanceAction.newConstitutionAction.constitution.scriptHash') + }, + actionId: { + title: t('core.ProposalProcedure.governanceAction.actionId.title'), + index: t('core.ProposalProcedure.governanceAction.actionId.index'), + txId: t('core.ProposalProcedure.governanceAction.actionId.txId') + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/NoConfidenceActionContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/NoConfidenceActionContainer.test.tsx new file mode 100644 index 0000000000..5d0d1ee411 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/NoConfidenceActionContainer.test.tsx @@ -0,0 +1,160 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable import/imports-first */ +const cardanoCoinMock = { + symbol: 'cardanoCoinMockSymbol' +}; +const mockUseWalletStore = jest.fn(() => ({ + walletUI: { cardanoCoin: cardanoCoinMock }, + walletInfo: {} +})); +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockNoConfidenceAction = jest.fn(() => ); +const mockLovelacesToAdaString = jest.fn((val) => val); +const mockedCExpolorerBaseUrl = 'mockedCExpolorerBaseUrl'; +const mockUseCExpolorerBaseUrl = jest.fn(() => mockedCExpolorerBaseUrl); +import { Wallet } from '@lace/cardano'; +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { act } from 'react-dom/test-utils'; +import { NoConfidenceActionContainer } from '../NoConfidenceActionContainer'; +import { getWrapper } from '../../testing.utils'; + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + NoConfidenceAction: mockNoConfidenceAction + }; +}); + +jest.mock('@src/stores', () => ({ + ...jest.requireActual('@src/stores'), + useWalletStore: mockUseWalletStore +})); + +jest.mock('../../hooks', () => { + const original = jest.requireActual('../../hooks'); + return { + __esModule: true, + ...original, + useCExpolorerBaseUrl: mockUseCExpolorerBaseUrl + }; +}); + +jest.mock('@lace/cardano', () => { + const actual = jest.requireActual('@lace/cardano'); + return { + __esModule: true, + ...actual, + Wallet: { + ...actual.Wallet, + util: { + ...actual.Wallet.util, + lovelacesToAdaString: mockLovelacesToAdaString + } + } + }; +}); + +const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' +}; +const errorMessage = 'errorMessage'; +const deposit = BigInt('10000'); +const rewardAccount = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const anchor = { + url: 'anchorUrl', + dataHash: Wallet.Crypto.Hash32ByteBase16(Buffer.from('anchorDataHashanchorDataHashanch').toString('hex')) +}; + +const noConfidence = { + __typename: Wallet.Cardano.GovernanceActionType.no_confidence, + governanceActionId: { + actionIndex: 123, + id: Wallet.Cardano.TransactionId('724a0a88b9470a714fc5bf84daf5851fa259a9b89e1a5453f6f5cd6595ad9821') + } +} as Wallet.Cardano.NoConfidence; + +describe('Testing ProposalProceduresContainer component', () => { + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('should render NoConfidenceAction component with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + , + { + wrapper: getWrapper() + } + )); + }); + + expect(queryByTestId('NoConfidenceAction')).toBeInTheDocument(); + expect(mockNoConfidenceAction).toHaveBeenLastCalledWith( + { + dappInfo, + data: { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.noConfidenceAction.title'), + deposit: `${deposit.toString()} ${cardanoCoinMock.symbol}`, + rewardAccount + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + txHashUrl: `${mockedCExpolorerBaseUrl}/${anchor.dataHash}` + } + }, + actionId: { + index: noConfidence.governanceActionId.actionIndex.toString(), + id: noConfidence.governanceActionId.id || '' + } + }, + translations: { + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + }, + actionId: { + title: t('core.ProposalProcedure.governanceAction.actionId.title'), + index: t('core.ProposalProcedure.governanceAction.actionId.index'), + txId: t('core.ProposalProcedure.governanceAction.actionId.txId') + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/ParameterChangeActionContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/ParameterChangeActionContainer.test.tsx new file mode 100644 index 0000000000..b0f46f2293 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/ParameterChangeActionContainer.test.tsx @@ -0,0 +1,504 @@ +/* eslint-disable unicorn/no-array-reduce */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable import/imports-first */ +const cardanoCoinMock = { + symbol: 'cardanoCoinMockSymbol' +}; +const mockUseWalletStore = jest.fn(() => ({ + walletUI: { cardanoCoin: cardanoCoinMock }, + walletInfo: {} +})); +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockParameterChangeAction = jest.fn(() => ); +const mockLovelacesToAdaString = jest.fn((val) => val); +const mockedCExpolorerBaseUrl = 'mockedCExpolorerBaseUrl'; +const mockUseCExpolorerBaseUrl = jest.fn(() => mockedCExpolorerBaseUrl); +import { Wallet } from '@lace/cardano'; +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { act } from 'react-dom/test-utils'; +import { ParameterChangeActionContainer } from '../ParameterChangeActionContainer'; +import { formatPercentages } from '@lace/common'; +import { getWrapper } from '../../testing.utils'; + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + ParameterChangeAction: mockParameterChangeAction + }; +}); + +jest.mock('@src/stores', () => ({ + ...jest.requireActual('@src/stores'), + useWalletStore: mockUseWalletStore +})); + +jest.mock('../../hooks', () => { + const original = jest.requireActual('../../hooks'); + return { + __esModule: true, + ...original, + useCExpolorerBaseUrl: mockUseCExpolorerBaseUrl + }; +}); + +jest.mock('@lace/cardano', () => { + const actual = jest.requireActual('@lace/cardano'); + return { + __esModule: true, + ...actual, + Wallet: { + ...actual.Wallet, + util: { + ...actual.Wallet.util, + lovelacesToAdaString: mockLovelacesToAdaString + } + } + }; +}); + +const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' +}; +const errorMessage = 'errorMessage'; +const deposit = BigInt('10000'); +const rewardAccount = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const anchor = { + url: 'anchorUrl', + dataHash: Wallet.Crypto.Hash32ByteBase16(Buffer.from('anchorDataHashanchorDataHashanch').toString('hex')) +}; + +const parameterChangeAction = { + protocolParamUpdate: { + maxBlockBodySize: 1, + maxTxSize: 2, + maxBlockHeaderSize: 3, + maxValueSize: 4, + maxExecutionUnitsPerTransaction: { + memory: 5, + steps: 6 + }, + maxExecutionUnitsPerBlock: { + memory: 7, + steps: 8 + }, + maxCollateralInputs: 9, + stakeKeyDeposit: 10, + poolDeposit: 11, + minFeeCoefficient: 12, + minFeeConstant: 13, + treasuryExpansion: '14', + monetaryExpansion: '15', + minPoolCost: 16, + coinsPerUtxoByte: 17, + prices: { + memory: 18, + steps: 19 + }, + poolInfluence: '20', + poolRetirementEpochBound: 21, + desiredNumberOfPools: 22, + costModels: new Map([ + [0, [23, 24]], + [1, [25, 26]] + ]), + collateralPercentage: 27, + governanceActionDeposit: 28, + dRepDeposit: 29, + governanceActionValidityPeriod: 30, + dRepInactivityPeriod: 31, + minCommitteeSize: 32, + committeeTermLimit: 33, + dRepVotingThresholds: { + motionNoConfidence: { + numerator: 34, + denominator: 35 + }, + committeeNormal: { + numerator: 36, + denominator: 37 + }, + commiteeNoConfidence: { + numerator: 38, + denominator: 39 + }, + updateConstitution: { + numerator: 40, + denominator: 41 + }, + hardForkInitiation: { + numerator: 42, + denominator: 43 + }, + ppNetworkGroup: { + numerator: 44, + denominator: 45 + }, + ppEconomicGroup: { + numerator: 46, + denominator: 47 + }, + ppTechnicalGroup: { + numerator: 48, + denominator: 49 + }, + ppGovernanceGroup: { + numerator: 50, + denominator: 51 + }, + treasuryWithdrawal: { + numerator: 55, + denominator: 56 + } + } + }, + governanceActionId: { + actionIndex: 123, + id: Wallet.Cardano.TransactionId('724a0a88b9470a714fc5bf84daf5851fa259a9b89e1a5453f6f5cd6595ad9821') + }, + __typename: Wallet.Cardano.GovernanceActionType.parameter_change_action +} as Wallet.Cardano.ParameterChangeAction; + +describe('Testing ProposalProceduresContainer component', () => { + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('should render ParameterChangeAction component with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + , + { + wrapper: getWrapper() + } + )); + }); + + expect(queryByTestId('ParameterChangeAction')).toBeInTheDocument(); + expect(mockParameterChangeAction).toHaveBeenLastCalledWith( + { + dappInfo, + data: { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.title'), + deposit: `${deposit.toString()} ${cardanoCoinMock.symbol}`, + rewardAccount + }, + anchor: { + url: anchor.url, + hash: anchor.dataHash, + txHashUrl: `${mockedCExpolorerBaseUrl}/${anchor.dataHash}` + }, + protocolParamUpdate: { + maxTxExUnits: { + memory: parameterChangeAction.protocolParamUpdate.maxExecutionUnitsPerTransaction.memory.toString(), + step: parameterChangeAction.protocolParamUpdate.maxExecutionUnitsPerTransaction.steps.toString() + }, + maxBlockExUnits: { + memory: parameterChangeAction.protocolParamUpdate.maxExecutionUnitsPerBlock.memory.toString(), + step: parameterChangeAction.protocolParamUpdate.maxExecutionUnitsPerBlock.steps.toString() + }, + networkGroup: { + maxBBSize: parameterChangeAction.protocolParamUpdate.maxBlockBodySize.toString(), + maxTxSize: parameterChangeAction.protocolParamUpdate.maxTxSize.toString(), + maxBHSize: parameterChangeAction.protocolParamUpdate.maxBlockHeaderSize.toString(), + maxValSize: parameterChangeAction.protocolParamUpdate.maxValueSize.toString(), + maxCollateralInputs: parameterChangeAction.protocolParamUpdate.maxCollateralInputs.toString() + }, + economicGroup: { + minFeeA: parameterChangeAction.protocolParamUpdate.minFeeCoefficient.toString(), + minFeeB: parameterChangeAction.protocolParamUpdate.minFeeConstant.toString(), + keyDeposit: parameterChangeAction.protocolParamUpdate.stakeKeyDeposit.toString(), + poolDeposit: parameterChangeAction.protocolParamUpdate.poolDeposit.toString(), + rho: parameterChangeAction.protocolParamUpdate.monetaryExpansion, + tau: parameterChangeAction.protocolParamUpdate.treasuryExpansion, + minPoolCost: parameterChangeAction.protocolParamUpdate.minPoolCost.toString(), + coinsPerUTxOByte: parameterChangeAction.protocolParamUpdate.coinsPerUtxoByte.toString(), + price: { + memory: parameterChangeAction.protocolParamUpdate.prices.memory.toString(), + step: parameterChangeAction.protocolParamUpdate.prices.steps.toString() + } + }, + technicalGroup: { + a0: parameterChangeAction.protocolParamUpdate.poolInfluence, + eMax: parameterChangeAction.protocolParamUpdate.poolRetirementEpochBound.toString(), + nOpt: parameterChangeAction.protocolParamUpdate.desiredNumberOfPools.toString(), + costModels: { + PlutusV1: Object.entries( + parameterChangeAction.protocolParamUpdate.costModels.get(Wallet.Cardano.PlutusLanguageVersion.V1) + ).reduce((acc, cur) => ({ ...acc, [cur[0]]: cur[1] }), {}), + PlutusV2: Object.entries( + parameterChangeAction.protocolParamUpdate.costModels.get(Wallet.Cardano.PlutusLanguageVersion.V2) + ).reduce((acc, cur) => ({ ...acc, [cur[0]]: cur[1] }), {}) + }, + collateralPercentage: parameterChangeAction.protocolParamUpdate.collateralPercentage.toString() + }, + governanceGroup: { + govActionLifetime: parameterChangeAction.protocolParamUpdate.governanceActionValidityPeriod.toString(), + govActionDeposit: parameterChangeAction.protocolParamUpdate.governanceActionDeposit.toString(), + drepDeposit: parameterChangeAction.protocolParamUpdate.dRepDeposit.toString(), + drepActivity: parameterChangeAction.protocolParamUpdate.dRepInactivityPeriod.toString(), + ccMinSize: parameterChangeAction.protocolParamUpdate.minCommitteeSize.toString(), + ccMaxTermLength: parameterChangeAction.protocolParamUpdate.committeeTermLimit.toString(), + dRepVotingThresholds: { + motionNoConfidence: formatPercentages( + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.motionNoConfidence.numerator / + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.motionNoConfidence.denominator + ), + committeeNormal: formatPercentages( + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.committeeNormal.numerator / + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.committeeNormal.denominator + ), + committeeNoConfidence: formatPercentages( + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.commiteeNoConfidence.numerator / + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.commiteeNoConfidence.denominator + ), + updateToConstitution: formatPercentages( + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.updateConstitution.numerator / + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.updateConstitution.denominator + ), + hardForkInitiation: formatPercentages( + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.hardForkInitiation.numerator / + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.hardForkInitiation.denominator + ), + ppNetworkGroup: formatPercentages( + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.ppNetworkGroup.numerator / + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.ppNetworkGroup.denominator + ), + ppEconomicGroup: formatPercentages( + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.ppEconomicGroup.numerator / + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.ppEconomicGroup.denominator + ), + ppTechnicalGroup: formatPercentages( + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.ppTechnicalGroup.numerator / + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.ppTechnicalGroup.denominator + ), + ppGovGroup: formatPercentages( + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.ppGovernanceGroup.numerator / + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.ppGovernanceGroup.denominator + ), + treasuryWithdrawal: formatPercentages( + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.treasuryWithdrawal.numerator / + parameterChangeAction.protocolParamUpdate.dRepVotingThresholds.treasuryWithdrawal.denominator + ) + } + } + } + }, + translations: { + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + }, + memory: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.memory'), + step: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.step'), + networkGroup: { + title: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.title'), + maxBBSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxBBSize'), + maxTxSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxTxSize'), + maxBHSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxBHSize'), + maxValSize: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxValSize'), + maxTxExUnits: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxTxExUnits'), + maxBlockExUnits: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxBlockExUnits' + ), + maxCollateralInputs: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.maxCollateralInputs' + ), + coinsByUTXOByte: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.coinsByUTXOByte' + ), + tooltip: { + maxBBSize: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxBBSize' + ), + maxTxSize: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxTxSize' + ), + maxBHSize: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxBHSize' + ), + maxValSize: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxValSize' + ), + maxTxExUnits: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxTxExUnits' + ), + maxBlockExUnits: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxBlockExUnits' + ), + maxCollateralInputs: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.maxCollateralInputs' + ), + coinsByUTXOByte: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.networkGroup.tooltip.coinsByUTXOByte' + ) + } + }, + economicGroup: { + title: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.title'), + minFeeA: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.minFeeA'), + minFeeB: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.minFeeB'), + keyDeposit: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.keyDeposit'), + poolDeposit: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.poolDeposit'), + rho: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.rho'), + tau: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tau'), + minPoolCost: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.minPoolCost'), + coinsPerUTxOByte: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.coinsPerUTxOByte' + ), + prices: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.prices'), + tooltip: { + minFeeA: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.minFeeA'), + minFeeB: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.minFeeB'), + keyDeposit: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.keyDeposit' + ), + poolDeposit: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.poolDeposit' + ), + rho: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.rho'), + tau: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.tau'), + minPoolCost: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.minPoolCost' + ), + coinsPerUTxOByte: t( + 'core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.coinsPerUTxOByte' + ), + prices: t('core.ProposalProcedure.governanceAction.protocolParamUpdate.economicGroup.tooltip.prices') + } + }, + technicalGroup: { + title: t('core.ProposalProcedure.governanceAction.technicalGroup.title'), + a0: t('core.ProposalProcedure.governanceAction.technicalGroup.a0'), + eMax: t('core.ProposalProcedure.governanceAction.technicalGroup.eMax'), + nOpt: t('core.ProposalProcedure.governanceAction.technicalGroup.nOpt'), + costModels: t('core.ProposalProcedure.governanceAction.technicalGroup.costModels'), + collateralPercentage: t('core.ProposalProcedure.governanceAction.technicalGroup.collateralPercentage'), + tooltip: { + a0: t('core.ProposalProcedure.governanceAction.technicalGroup.tooltip.a0'), + eMax: t('core.ProposalProcedure.governanceAction.technicalGroup.tooltip.eMax'), + nOpt: t('core.ProposalProcedure.governanceAction.technicalGroup.tooltip.nOpt'), + costModels: t('core.ProposalProcedure.governanceAction.technicalGroup.tooltip.costModels'), + collateralPercentage: t( + 'core.ProposalProcedure.governanceAction.technicalGroup.tooltip.collateralPercentage' + ) + } + }, + governanceGroup: { + title: t('core.ProposalProcedure.governanceAction.governanceGroup.title'), + govActionLifetime: t('core.ProposalProcedure.governanceAction.governanceGroup.govActionLifetime'), + govActionDeposit: t('core.ProposalProcedure.governanceAction.governanceGroup.govActionDeposit'), + drepDeposit: t('core.ProposalProcedure.governanceAction.governanceGroup.drepDeposit'), + drepActivity: t('core.ProposalProcedure.governanceAction.governanceGroup.drepActivity'), + ccMinSize: t('core.ProposalProcedure.governanceAction.governanceGroup.ccMinSize'), + ccMaxTermLength: t('core.ProposalProcedure.governanceAction.governanceGroup.ccMaxTermLength'), + dRepVotingThresholds: { + title: t('core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.title'), + motionNoConfidence: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.motionNoConfidence' + ), + committeeNormal: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.committeeNormal' + ), + committeeNoConfidence: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.committeeNoConfidence' + ), + updateConstitution: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.updateConstitution' + ), + hardForkInitiation: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.hardForkInitiation' + ), + ppNetworkGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.ppNetworkGroup' + ), + ppEconomicGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.ppEconomicGroup' + ), + ppTechnicalGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.ppTechnicalGroup' + ), + ppGovernanceGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.ppGovernanceGroup' + ), + treasuryWithdrawal: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.dRepVotingThresholds.treasuryWithdrawal' + ) + }, + tooltip: { + govActionLifetime: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.govActionLifetime'), + govActionDeposit: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.govActionDeposit'), + drepDeposit: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.drepDeposit'), + drepActivity: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.drepActivity'), + ccMinSize: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.ccMinSize'), + ccMaxTermLength: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.ccMaxTermLength'), + dRepVotingThresholds: { + title: t('core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.title'), + motionNoConfidence: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.motionNoConfidence' + ), + committeeNormal: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.committeeNormal' + ), + committeeNoConfidence: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.committeeNoConfidence' + ), + updateConstitution: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.updateConstitution' + ), + hardForkInitiation: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.hardForkInitiation' + ), + ppNetworkGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.ppNetworkGroup' + ), + ppEconomicGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.ppEconomicGroup' + ), + ppTechnicalGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.ppTechnicalGroup' + ), + ppGovernanceGroup: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.ppGovernanceGroup' + ), + treasuryWithdrawal: t( + 'core.ProposalProcedure.governanceAction.governanceGroup.tooltip.dRepVotingThresholds.treasuryWithdrawal' + ) + } + } + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/TreasuryWithdrawalsActionContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/TreasuryWithdrawalsActionContainer.test.tsx new file mode 100644 index 0000000000..701b0c2be3 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/TreasuryWithdrawalsActionContainer.test.tsx @@ -0,0 +1,165 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable import/imports-first */ +const cardanoCoinMock = { + symbol: 'cardanoCoinMockSymbol' +}; +const mockUseWalletStore = jest.fn(() => ({ + walletUI: { cardanoCoin: cardanoCoinMock }, + walletInfo: {} +})); +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockTreasuryWithdrawalsAction = jest.fn(() => ); +const mockLovelacesToAdaString = jest.fn((val) => val); +const mockedCExpolorerBaseUrl = 'mockedCExpolorerBaseUrl'; +const mockUseCExpolorerBaseUrl = jest.fn(() => mockedCExpolorerBaseUrl); +import { Wallet } from '@lace/cardano'; +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { act } from 'react-dom/test-utils'; +import { TreasuryWithdrawalsActionContainer } from '../TreasuryWithdrawalsActionContainer'; +import { getWrapper } from '../../testing.utils'; + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + TreasuryWithdrawalsAction: mockTreasuryWithdrawalsAction + }; +}); + +jest.mock('@src/stores', () => ({ + ...jest.requireActual('@src/stores'), + useWalletStore: mockUseWalletStore +})); + +jest.mock('../../hooks', () => { + const original = jest.requireActual('../../hooks'); + return { + __esModule: true, + ...original, + useCExpolorerBaseUrl: mockUseCExpolorerBaseUrl + }; +}); + +jest.mock('@lace/cardano', () => { + const actual = jest.requireActual('@lace/cardano'); + return { + __esModule: true, + ...actual, + Wallet: { + ...actual.Wallet, + util: { + ...actual.Wallet.util, + lovelacesToAdaString: mockLovelacesToAdaString + } + } + }; +}); + +const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' +}; +const errorMessage = 'errorMessage'; +const deposit = BigInt('10000'); +const rewardAccount = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const anchor = { + url: 'anchorUrl', + dataHash: Wallet.Crypto.Hash32ByteBase16(Buffer.from('anchorDataHashanchorDataHashanch').toString('hex')) +}; + +const treasuryWithdrawalsAction = { + withdrawals: new Set([ + { rewardAccount, coin: BigInt('10000000') }, + { rewardAccount, coin: BigInt('10000001') } + ]), + __typename: Wallet.Cardano.GovernanceActionType.treasury_withdrawals_action +} as Wallet.Cardano.TreasuryWithdrawalsAction; + +describe('Testing ProposalProceduresContainer component', () => { + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('should render TreasuryWithdrawalsAction component with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + , + { + wrapper: getWrapper() + } + )); + }); + + expect(queryByTestId('TreasuryWithdrawalsAction')).toBeInTheDocument(); + expect(mockTreasuryWithdrawalsAction).toHaveBeenLastCalledWith( + { + dappInfo, + data: { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.treasuryWithdrawals.title'), + deposit: `${deposit.toString()} ${cardanoCoinMock.symbol}`, + rewardAccount + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + txHashUrl: `${mockedCExpolorerBaseUrl}/${anchor.dataHash}` + } + }, + withdrawals: [...treasuryWithdrawalsAction.withdrawals].map((withdrawal) => ({ + rewardAccount: withdrawal.rewardAccount.toString(), + lovelace: `${withdrawal.coin.toString()} ${cardanoCoinMock.symbol}` + })) + }, + translations: { + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + }, + actionId: { + title: t('core.ProposalProcedure.governanceAction.actionId.title'), + index: t('core.ProposalProcedure.governanceAction.actionId.index'), + txId: t('core.ProposalProcedure.governanceAction.actionId.txId') + }, + withdrawals: { + title: t('core.ProposalProcedure.governanceAction.treasuryWithdrawals.title'), + rewardAccount: t('core.ProposalProcedure.governanceAction.treasuryWithdrawals.withdrawals.rewardAccount'), + lovelace: t('core.ProposalProcedure.governanceAction.treasuryWithdrawals.withdrawals.lovelace') + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/UpdateCommitteeActionContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/UpdateCommitteeActionContainer.test.tsx new file mode 100644 index 0000000000..5c99335f63 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/proposal-procedures/__tests__/UpdateCommitteeActionContainer.test.tsx @@ -0,0 +1,208 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable import/imports-first */ +const cardanoCoinMock = { + symbol: 'cardanoCoinMockSymbol' +}; +const mockUseWalletStore = jest.fn(() => ({ + walletUI: { cardanoCoin: cardanoCoinMock }, + walletInfo: {} +})); +const t = jest.fn().mockImplementation((res) => res); +const mockUseTranslation = jest.fn(() => ({ t })); +const mockUpdateCommitteeAction = jest.fn(() => ); +const mockLovelacesToAdaString = jest.fn((val) => val); +const mockedCExpolorerBaseUrl = 'mockedCExpolorerBaseUrl'; +const mockUseCExpolorerBaseUrl = jest.fn(() => mockedCExpolorerBaseUrl); +import { Wallet } from '@lace/cardano'; +import * as React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { act } from 'react-dom/test-utils'; +import { UpdateCommitteeActionContainer } from '../UpdateCommitteeActionContainer'; +import { getWrapper } from '../../testing.utils'; + +jest.mock('react-i18next', () => { + const original = jest.requireActual('react-i18next'); + return { + __esModule: true, + ...original, + useTranslation: mockUseTranslation + }; +}); + +jest.mock('@lace/core', () => { + const original = jest.requireActual('@lace/core'); + return { + __esModule: true, + ...original, + UpdateCommitteeAction: mockUpdateCommitteeAction + }; +}); + +jest.mock('@src/stores', () => ({ + ...jest.requireActual('@src/stores'), + useWalletStore: mockUseWalletStore +})); + +jest.mock('../../hooks', () => { + const original = jest.requireActual('../../hooks'); + return { + __esModule: true, + ...original, + useCExpolorerBaseUrl: mockUseCExpolorerBaseUrl + }; +}); + +jest.mock('@lace/cardano', () => { + const actual = jest.requireActual('@lace/cardano'); + return { + __esModule: true, + ...actual, + Wallet: { + ...actual.Wallet, + util: { + ...actual.Wallet.util, + lovelacesToAdaString: mockLovelacesToAdaString + } + } + }; +}); + +const dappInfo = { + name: 'dappName', + logo: 'dappLogo', + url: 'dappUrl' +}; +const errorMessage = 'errorMessage'; +const deposit = BigInt('10000'); +const rewardAccount = Wallet.Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj'); +const anchor = { + url: 'anchorUrl', + dataHash: Wallet.Crypto.Hash32ByteBase16(Buffer.from('anchorDataHashanchorDataHashanch').toString('hex')) +}; + +const updateCommittee = { + membersToBeAdded: new Set([ + { + coldCredential: { + type: 0, + hash: Wallet.Crypto.Hash28ByteBase16(Buffer.from('updateCommitteecoldCredenti1').toString('hex')) + }, + epoch: 1 + }, + { + coldCredential: { + type: 1, + hash: Wallet.Crypto.Hash28ByteBase16(Buffer.from('updateCommitteecoldCredenti2').toString('hex')) + }, + epoch: 2 + } + ]), + membersToBeRemoved: new Set([ + { + type: 0, + hash: Wallet.Crypto.Hash28ByteBase16(Buffer.from('updateCommitteecoldCredenti2').toString('hex')) + }, + { + type: 1, + hash: Wallet.Crypto.Hash28ByteBase16(Buffer.from('updateCommitteecoldCredenti3').toString('hex')) + } + ]), + governanceActionId: { + actionIndex: 123, + id: Wallet.Cardano.TransactionId('724a0a88b9470a714fc5bf84daf5851fa259a9b89e1a5453f6f5cd6595ad9821') + }, + __typename: Wallet.Cardano.GovernanceActionType.update_committee +} as Wallet.Cardano.UpdateCommittee; + +describe('Testing ProposalProceduresContainer component', () => { + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('should render UpdateCommitteeAction component with proper props', async () => { + let queryByTestId: any; + + await act(async () => { + ({ queryByTestId } = render( + , + { + wrapper: getWrapper() + } + )); + }); + + expect(queryByTestId('UpdateCommitteeAction')).toBeInTheDocument(); + expect(mockUpdateCommitteeAction).toHaveBeenLastCalledWith( + { + dappInfo, + data: { + txDetails: { + txType: t('core.ProposalProcedure.governanceAction.updateCommitteeAction.title'), + deposit: `${deposit.toString()} ${cardanoCoinMock.symbol}`, + rewardAccount + }, + procedure: { + anchor: { + url: anchor.url, + hash: anchor.dataHash, + txHashUrl: `${mockedCExpolorerBaseUrl}/${anchor.dataHash}` + } + }, + actionId: { + index: updateCommittee.governanceActionId.actionIndex.toString(), + id: updateCommittee.governanceActionId.id || '' + }, + membersToBeAdded: [...updateCommittee.membersToBeAdded].map(({ coldCredential: { hash }, epoch }) => ({ + coldCredential: { + hash: hash.toString() + }, + epoch: epoch.toString() + })), + membersToBeRemoved: [...updateCommittee.membersToBeRemoved].map(({ hash }) => ({ hash: hash.toString() })) + }, + translations: { + txDetails: { + title: t('core.ProposalProcedure.txDetails.title'), + txType: t('core.ProposalProcedure.txDetails.txType'), + deposit: t('core.ProposalProcedure.txDetails.deposit'), + rewardAccount: t('core.ProposalProcedure.txDetails.rewardAccount') + }, + procedure: { + title: t('core.ProposalProcedure.procedure.title'), + anchor: { + url: t('core.ProposalProcedure.procedure.anchor.url'), + hash: t('core.ProposalProcedure.procedure.anchor.hash') + } + }, + actionId: { + title: t('core.ProposalProcedure.governanceAction.actionId.title'), + index: t('core.ProposalProcedure.governanceAction.actionId.index'), + txId: t('core.ProposalProcedure.governanceAction.actionId.txId') + }, + membersToBeAdded: { + title: t('core.ProposalProcedure.governanceAction.updateCommitteeAction.membersToBeAdded.title'), + coldCredential: { + hash: t( + 'core.ProposalProcedure.governanceAction.updateCommitteeAction.membersToBeAdded.coldCredential.hash' + ), + epoch: t( + 'core.ProposalProcedure.governanceAction.updateCommitteeAction.membersToBeAdded.coldCredential.epoch' + ) + } + }, + membersToBeRemoved: { + title: t('core.ProposalProcedure.governanceAction.updateCommitteeAction.membersToBeRemoved.title'), + hash: t('core.ProposalProcedure.governanceAction.updateCommitteeAction.membersToBeRemoved.hash') + } + }, + errorMessage + }, + {} + ); + }); +}); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/testing.utils.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/testing.utils.tsx new file mode 100644 index 0000000000..4d4cff1643 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/testing.utils.tsx @@ -0,0 +1,38 @@ +import { I18nextProvider } from 'react-i18next'; +import { StoreProvider } from '@src/stores'; +import { + AnalyticsProvider, + AppSettingsProvider, + BackgroundServiceAPIProvider, + BackgroundServiceAPIProviderProps, + DatabaseProvider +} from '@src/providers'; +import { APP_MODE_BROWSER } from '@src/utils/constants'; +import i18n from '@lib/i18n'; +import { PostHogClientProvider } from '@providers/PostHogClientProvider'; +import { postHogClientMocks } from '@src/utils/mocks/test-helpers'; +import React from 'react'; + +const backgroundService = { + getBackgroundStorage: jest.fn(), + setBackgroundStorage: jest.fn() +} as unknown as BackgroundServiceAPIProviderProps['value']; + +export const getWrapper = + () => + ({ children }: { children: React.ReactNode }): React.ReactElement => + ( + + + + + + + {children} + + + + + + + ); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts index a92dc323c5..664bfe32d7 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts @@ -67,11 +67,15 @@ export const votingProceduresInspector = (tx: Wallet.Cardano.Tx): Wallet.Cardano tx?.body?.votingProcedures; // eslint-disable-next-line complexity +export const proposalProceduresInspector = (tx: Wallet.Cardano.Tx): Wallet.Cardano.ProposalProcedure[] | undefined => + tx?.body?.proposalProcedures; + export const getTxType = (tx: Wallet.Cardano.Tx): Wallet.Cip30TxType => { const inspector = createTxInspector({ minted: assetsMintedInspector, burned: assetsBurnedInspector, votingProcedures: votingProceduresInspector, + proposalProcedures: proposalProceduresInspector, dRepRegistration: certificateInspectorFactory(CertificateType.RegisterDelegateRepresentative), dRepRetirement: certificateInspectorFactory(CertificateType.UnregisterDelegateRepresentative), dRepUpdate: certificateInspectorFactory(CertificateType.UpdateDelegateRepresentative), @@ -93,11 +97,16 @@ export const getTxType = (tx: Wallet.Cardano.Tx): Wallet.Cip30TxType => { stakeVoteDelegation, voteRegistrationDelegation, stakeRegistrationDelegation, - stakeVoteDelegationRegistration + stakeVoteDelegationRegistration, + proposalProcedures } = inspector(tx as Wallet.Cardano.HydratedTx); const isMintTransaction = minted.length > 0; const isBurnTransaction = burned.length > 0; + if (proposalProcedures) { + return Wallet.Cip30TxType.ProposalProcedures; + } + if (votingProcedures) { return Wallet.Cip30TxType.VotingProcedures; } diff --git a/apps/browser-extension-wallet/src/lib/translations/en.json b/apps/browser-extension-wallet/src/lib/translations/en.json index 23463cdad6..26e7307368 100644 --- a/apps/browser-extension-wallet/src/lib/translations/en.json +++ b/apps/browser-extension-wallet/src/lib/translations/en.json @@ -1130,6 +1130,186 @@ "tryingToUseAssetNotInWallet": "This DApp is trying to use token not held in your wallet.", "noCollateral": "Wallet should not be able to sign dapp txs without collateral." }, + "ProposalProcedure": { + "dRepId": "DRep ID", + "txDetails": { + "deposit": "Deposit", + "rewardAccount": "Reward account", + "title": "Transaction Details", + "txType": "Transaction Type" + }, + "procedure": { + "anchor": { + "hash": "Anchor Hash", + "url": "Anchor URL" + }, + "title": "Procedure", + "dRepId": "DRep ID" + }, + "governanceAction": { + "actionId": { + "title": "Action ID", + "index": "Index", + "txId": "TX ID" + }, + "hardForkInitiation": { + "title": "Hard Fork Initiation", + "protocolVersion": { + "major": "Protocol Version Major", + "minor": "Protocol Version Minor", + "patch": "Protocol Version Patch" + } + }, + "newConstitutionAction": { + "title": "New Constitution Action", + "constitution": { + "title": "Constitution Details", + "anchor": { + "dataHash": "Anchor Data Hash", + "url": "Constitution Anchor URL" + }, + "scriptHash": "Constitution Script Hash" + } + }, + "infoAction": { + "title": "Info Action" + }, + "noConfidenceAction": { + "title": "No Confidence" + }, + "protocolParamUpdate": { + "title": "Protocol Parameter Update", + "memory": "Memory", + "step": "Step", + "networkGroup": { + "title": "Network group", + "maxBBSize": "Max BB Size", + "maxTxSize": "Max Tx Size", + "maxBHSize": "Max BH Size", + "maxValSize": "Max Val Size", + "maxTxExUnits": "Max TX Ex Units", + "maxBlockExUnits": "Max Blk Ex Units", + "maxCollateralInputs": "Max Coll Inputs", + "tooltip": { + "maxBBSize": "Max block body size", + "maxTxSize": "Max transaction size", + "maxBHSize": "Max block header size", + "maxValSize": "Max size of a serialized asset value", + "maxTxExUnits": "Max script execution units in a single transaction", + "maxBlockExUnits": "Max script execution units in a single block", + "maxCollateralInputs": "Max number of collateral inputs" + } + }, + "economicGroup": { + "title": "Economic group", + "minFeeA": "Min Fee A", + "minFeeB": "Min Fee B", + "keyDeposit": "Key Deposit", + "poolDeposit": "Pool Deposit", + "rho": "Rho", + "tau": "Tau", + "minPoolCost": "Min Pool Cost", + "coinsPerUTxOByte": "Coins/UTxO Byte", + "prices": "Price", + "tooltip": { + "minFeeA": "Min fee coefficient", + "minFeeB": "Min fee constant", + "keyDeposit": "Delegation key Lovelace deposit", + "poolDeposit": "Pool registration Lovelace deposit", + "rho": "Monetary expansion", + "tau": "Treasury expansion", + "minPoolCost": "Min fixed rewards cut for pools", + "coinsPerUTxOByte": "Min Lovelace deposit per byte of serialized UTxO", + "prices": "Prices of Plutus execution units" + } + }, + "technicalGroup": { + "title": "Technical group", + "a0": "A0", + "eMax": "EMax", + "nOpt": "NOpt", + "costModels": "Cost Models", + "collateralPercentage": "Coll Percentage", + "tooltip": { + "a0": "Pool pledge influence", + "eMax": "Pool retirement maximum epoch", + "nOpt": "Desired number of pools", + "costModels": "Plutus execution cost models", + "collateralPercentage": "Proportion of collateral needed for scripts" + } + }, + "governanceGroup": { + "title": "Governance group", + "govActionLifetime": "Gov Act Lifetime", + "govActionDeposit": "Gov Act Deposit", + "drepDeposit": "DRep Deposit", + "drepActivity": "DRep Activity", + "ccMinSize": "CC Min Size", + "ccMaxTermLength": "CC Max Term Length", + "dRepVotingThresholds": { + "title": "Governance voting thresholds", + "motionNoConfidence": "Motion No Conf", + "committeeNormal": "Comm Normal", + "committeeNoConfidence": "Comm No Conf", + "updateConstitution": "Update Const", + "hardForkInitiation": "Hard Fork Init", + "ppNetworkGroup": "PP Network Grp", + "ppEconomicGroup": "PP Economic Grp", + "ppTechnicalGroup": "PP Technical Grp", + "ppGovernanceGroup": "PP Governance Grp", + "treasuryWithdrawal": "Treasury Withdraw" + }, + "tooltip": { + "govActionLifetime": "governance action maximum lifetime in epochs", + "govActionDeposit": "governance action deposit", + "drepDeposit": "DRep deposit amount", + "drepActivity": "DRep activity period in epochs", + "ccMinSize": "Min constitutional committee size", + "ccMaxTermLength": "Max term length (in epochs) for the constitutional committee members", + "dRepVotingThresholds": { + "title": "Governance voting thresholds", + "motionNoConfidence": "1. Motion of no-confidence", + "committeeNormal": "2a. New committee/threshold (normal state)", + "committeeNoConfidence": "2b. New committee/threshold (state of no-confidence)", + "updateConstitution": "3. Update to the Constitution or proposal policy", + "hardForkInitiation": "4. Hard-fork initiation", + "ppNetworkGroup": "5a. Protocol parameter changes, network group", + "ppEconomicGroup": "5b. Protocol parameter changes, economic group", + "ppTechnicalGroup": "5c. Protocol parameter changes, technical group", + "ppGovernanceGroup": "5d. Protocol parameter changes, governance group", + "treasuryWithdrawal": "6. Treasury withdrawal" + } + } + } + }, + "treasuryWithdrawals": { + "title": "Treasury Withdrawals", + "withdrawals": { + "lovelace": "Withdrawal Amount", + "rewardAccount": "Withdrawal Reward Account" + } + }, + "updateCommitteeAction": { + "title": "Update Committee Action", + "membersToBeAdded": { + "title": "Members To Be Added", + "coldCredential": { + "hash": "Cold Credential Hash", + "epoch": "Epoch" + } + }, + "membersToBeRemoved": { + "title": "Members To Be Removed", + "hash": "Hash" + }, + "newQuorumThreshold": { + "title": "New Quorum Threshold", + "denominator": "Denominator", + "numerator": "Numerator" + } + } + } + }, "VotingProcedures": { "title": "Confirm Vote", "voterType": "Voter type", diff --git a/packages/cardano/src/wallet/types.ts b/packages/cardano/src/wallet/types.ts index 7a4f78c0ad..9c2091494f 100644 --- a/packages/cardano/src/wallet/types.ts +++ b/packages/cardano/src/wallet/types.ts @@ -40,7 +40,8 @@ export enum Cip30TxType { VoteRegistrationDelegation = 'VoteRegistrationDelegation', StakeRegistrationDelegation = 'StakeRegistrationDelegation', StakeVoteDelegationRegistration = 'StakeVoteDelegationRegistration', - StakeVoteDelegation = 'StakeVoteDelegation' + StakeVoteDelegation = 'StakeVoteDelegation', + ProposalProcedures = 'ProposalProcedures' } export type Cip30SignTxOutput = { diff --git a/packages/core/.storybook/preview.js b/packages/core/.storybook/preview.js index 7ffa549eec..4a32e4e72b 100644 --- a/packages/core/.storybook/preview.js +++ b/packages/core/.storybook/preview.js @@ -4,6 +4,16 @@ import 'normalize.css'; import './theme.scss'; import { ThemeColorScheme, ThemeProvider } from '@lace/ui'; +export const customViewports = { + popup: { + name: 'Popup', + styles: { + width: '360px', + height: '600' + } + } +}; + export const preview = { parameters: { actions: { argTypesRegex: '^on[A-Z].*' }, @@ -12,6 +22,10 @@ export const preview = { color: /(background|color)$/i, date: /Date$/ } + }, + viewport: { + viewports: customViewports, + defaultViewport: 'Popup' } } }; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 70019dfd2c..d8cc6891e4 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -37,3 +37,4 @@ export * from '@ui/components/ConfirmStakeRegistrationDelegation'; export * from '@ui/components/ConfirmStakeVoteRegistrationDelegation'; export * from '@ui/components/ConfirmVoteRegistrationDelegation'; export * from '@ui/components/VotingProcedures'; +export * from '@ui/components/ProposalProcedures'; diff --git a/packages/core/src/ui/components/ConfirmStakeRegistrationDelegation/ConfirmStakeRegistrationDelegation.stories.ts b/packages/core/src/ui/components/ConfirmStakeRegistrationDelegation/ConfirmStakeRegistrationDelegation.stories.tsx similarity index 100% rename from packages/core/src/ui/components/ConfirmStakeRegistrationDelegation/ConfirmStakeRegistrationDelegation.stories.ts rename to packages/core/src/ui/components/ConfirmStakeRegistrationDelegation/ConfirmStakeRegistrationDelegation.stories.tsx diff --git a/packages/core/src/ui/components/ConfirmStakeVoteDelegation/ConfirmStakeVoteDelegation.stories.ts b/packages/core/src/ui/components/ConfirmStakeVoteDelegation/ConfirmStakeVoteDelegation.stories.tsx similarity index 100% rename from packages/core/src/ui/components/ConfirmStakeVoteDelegation/ConfirmStakeVoteDelegation.stories.ts rename to packages/core/src/ui/components/ConfirmStakeVoteDelegation/ConfirmStakeVoteDelegation.stories.tsx diff --git a/packages/core/src/ui/components/ConfirmStakeVoteRegistrationDelegation/ConfirmStakeVoteRegistrationDelegation.stories.ts b/packages/core/src/ui/components/ConfirmStakeVoteRegistrationDelegation/ConfirmStakeVoteRegistrationDelegation.stories.tsx similarity index 100% rename from packages/core/src/ui/components/ConfirmStakeVoteRegistrationDelegation/ConfirmStakeVoteRegistrationDelegation.stories.ts rename to packages/core/src/ui/components/ConfirmStakeVoteRegistrationDelegation/ConfirmStakeVoteRegistrationDelegation.stories.tsx diff --git a/packages/core/src/ui/components/ConfirmVoteRegistrationDelegation/ConfirmVoteRegistrationDelegation.stories.ts b/packages/core/src/ui/components/ConfirmVoteRegistrationDelegation/ConfirmVoteRegistrationDelegation.stories.tsx similarity index 100% rename from packages/core/src/ui/components/ConfirmVoteRegistrationDelegation/ConfirmVoteRegistrationDelegation.stories.ts rename to packages/core/src/ui/components/ConfirmVoteRegistrationDelegation/ConfirmVoteRegistrationDelegation.stories.tsx diff --git a/packages/core/src/ui/components/DappTransaction/DappTransaction.stories.tsx b/packages/core/src/ui/components/DappTransaction/DappTransaction.stories.tsx index 56f29d0531..07525eac9c 100644 --- a/packages/core/src/ui/components/DappTransaction/DappTransaction.stories.tsx +++ b/packages/core/src/ui/components/DappTransaction/DappTransaction.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { DappTransaction } from './DappTransaction'; import { ComponentProps } from 'react'; +import { Wallet } from '@lace/cardano'; const meta: Meta = { title: 'DappTransaction', @@ -20,13 +21,6 @@ const data: ComponentProps = { name: 'Mint', url: 'https://preprod.mint.handle.me' }, - translations: { - recipient: 'Recipient', - amount: 'Amount', - adaFollowingNumericValue: 'ADA', - fee: 'Fee', - transaction: 'Transaction' - }, transaction: { fee: '0.17', outputs: [ @@ -36,7 +30,7 @@ const data: ComponentProps = { 'addr_test1qrl0s3nqfljv8dfckn7c4wkzu5rl6wn4hakkddcz2mczt3szlqss933x0aag07qcgspcaglmay6ufl4y4lalmlpe02mqhl0fx2' } ], - type: 'Mint' + type: Wallet.Cip30TxType.Mint } }; diff --git a/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/HardForkInitiationAction.stories.tsx b/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/HardForkInitiationAction.stories.tsx new file mode 100644 index 0000000000..b50685d593 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/HardForkInitiationAction.stories.tsx @@ -0,0 +1,98 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { HardForkInitiationAction } from './HardForkInitiationAction'; +import { ComponentProps } from 'react'; + +const customViewports = { + popup: { + name: 'Popup', + styles: { + width: '360px', + height: '600' + } + } +}; + +const meta: Meta = { + title: 'ProposalProcedure/HardForkInitiationAction', + component: HardForkInitiationAction, + parameters: { + layout: 'centered', + viewport: { + viewports: customViewports, + defaultViewport: 'popup' + } + } +}; + +export default meta; +type Story = StoryObj; + +const data: ComponentProps = { + dappInfo: { + logo: 'https://cdn.mint.handle.me/favicon.png', + name: 'Mint', + url: 'https://preprod.mint.handle.me' + }, + data: { + txDetails: { + txType: 'Hard Fork Initiation', + deposit: '2000', + rewardAccount: 'stake1u89sasnfyjtmgk8ydqfv3fdl52f36x3djedfnzfc9rkgzrcss5vgr' + }, + procedure: { + anchor: { + hash: '26bfdcc75a7f4d0cd8c71f0189bc5ca5ad2f4a3db6240c82b5a0edac7f9203e0', + url: 'https://www.someurl.io', + txHashUrl: 'https://www.someurl.io' + } + }, + actionId: { + index: '0', + id: '26bfdcc75a7f4d0cd8c71f0189bc5ca5ad2f4a3db6240c82b5a0edac7f9203e0' + }, + protocolVersion: { + major: '5', + minor: '1', + patch: '1' + } + }, + translations: { + txDetails: { + title: 'Transaction Details', + txType: 'Transaction Type', + deposit: 'Deposit', + rewardAccount: 'Reward account' + }, + procedure: { + anchor: { + hash: 'Anchor Hash', + url: 'Anchor URL' + }, + title: 'Procedure' + }, + actionId: { + title: 'Action ID', + index: 'Index', + txId: 'TX ID' + }, + protocolVersion: { + major: 'Protocol Version Major', + minor: 'Protocol Version Minor', + patch: 'Protocol Version Patch' + } + } +}; + +export const Overview: Story = { + args: { + ...data + } +}; + +export const WithError: Story = { + args: { + ...data, + errorMessage: 'Something went wrong' + } +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/HardForkInitiationAction.tsx b/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/HardForkInitiationAction.tsx new file mode 100644 index 0000000000..6a722f1018 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/HardForkInitiationAction.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { Box, Grid, Flex, Divider, Metadata, Cell } from '@lace/ui'; +import { DappInfo, DappInfoProps } from '../../DappInfo'; +import { ErrorPane } from '@lace/common'; +import * as Types from './HardForkInitiationActionTypes'; +import { TransactionDetails } from '../components/TransactionDetails'; +import { Procedure } from '../components/Procedure'; +import { ActionId } from '../components/ActionId'; + +export interface HardForkInitiationActionProps { + dappInfo: Omit; + errorMessage?: string; + data: Types.Data; + translations: Types.Translations; +} + +export const HardForkInitiationAction = ({ + dappInfo, + errorMessage, + data: { procedure, txDetails, actionId, protocolVersion }, + translations +}: HardForkInitiationActionProps): JSX.Element => ( + + + + + {errorMessage && ( + + + + )} + + {/* tx details section */} + + + + + {/* procedure section */} + + + + + + + + {protocolVersion.patch && ( + + + + )} + {/* action id section*/} + {actionId && ( + <> + + + + + + )} + + +); diff --git a/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/HardForkInitiationActionTypes.ts b/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/HardForkInitiationActionTypes.ts new file mode 100644 index 0000000000..bd8ae61b7d --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/HardForkInitiationActionTypes.ts @@ -0,0 +1,25 @@ +import * as ProcedureTypes from '../components/ProcedureTypes'; +import * as ActionIdTypes from '../components/ActionIdTypes'; +import * as TxDetailsTypes from '../components/TransactionDetailsTypes'; + +export interface Data { + txDetails: TxDetailsTypes.TxDetails; + procedure: ProcedureTypes.Procedure; + actionId?: ActionIdTypes.Data; + protocolVersion: { + major: string; + minor: string; + patch?: string; + }; +} + +export interface Translations { + txDetails: TxDetailsTypes.Translations; + procedure: ProcedureTypes.Translations; + actionId?: ActionIdTypes.Translations; + protocolVersion: { + major: string; + minor: string; + patch: string; + }; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/index.ts b/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/index.ts new file mode 100644 index 0000000000..94f3dd74c4 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/HardForkInitiationAction/index.ts @@ -0,0 +1 @@ +export { HardForkInitiationAction } from './HardForkInitiationAction'; diff --git a/packages/core/src/ui/components/ProposalProcedures/InfoAction/InfoAction.stories.tsx b/packages/core/src/ui/components/ProposalProcedures/InfoAction/InfoAction.stories.tsx new file mode 100644 index 0000000000..3f97136d22 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/InfoAction/InfoAction.stories.tsx @@ -0,0 +1,75 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { InfoAction } from './InfoAction'; +import { ComponentProps } from 'react'; + +const customViewports = { + popup: { + name: 'Popup', + styles: { + width: '360px', + height: '600' + } + } +}; + +const meta: Meta = { + title: 'ProposalProcedure/InfoAction', + component: InfoAction, + parameters: { + layout: 'centered', + viewport: { + viewports: customViewports, + defaultViewport: 'popup' + } + } +}; + +export default meta; +type Story = StoryObj; + +const data: ComponentProps = { + dappInfo: { + logo: 'https://cdn.mint.handle.me/favicon.png', + name: 'Mint', + url: 'https://preprod.mint.handle.me' + }, + data: { + txDetails: { + txType: 'Info action' + }, + procedure: { + anchor: { + hash: '26bfdcc75a7f4d0cd8c71f0189bc5ca5ad2f4a3db6240c82b5a0edac7f9203e0', + url: 'https://www.someurl.io', + txHashUrl: 'https://www.someurl.io' + } + } + }, + translations: { + txDetails: { + title: 'Transaction Details', + txType: 'Transaction Type' + }, + procedure: { + anchor: { + hash: 'Anchor Hash', + url: 'Anchor URL' + }, + title: 'Procedure' + } + } +}; + +export const Overview: Story = { + args: { + ...data + } +}; + +export const WithError: Story = { + args: { + ...data, + errorMessage: 'Something went wrong' + } +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/InfoAction/InfoAction.tsx b/packages/core/src/ui/components/ProposalProcedures/InfoAction/InfoAction.tsx new file mode 100644 index 0000000000..b84260bd9a --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/InfoAction/InfoAction.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Box, Grid, Flex, Divider, Cell } from '@lace/ui'; +import { DappInfo, DappInfoProps } from '../../DappInfo'; +import { ErrorPane } from '@lace/common'; +import * as Types from './InfoActionTypes'; +import { TransactionDetails } from '../components/TransactionDetails'; +import { Procedure } from '../components/Procedure'; + +export interface InfoActionProps { + dappInfo: Omit; + errorMessage?: string; + data: Types.Data; + translations: Types.Translations; +} + +export const InfoAction = ({ + dappInfo, + errorMessage, + data: { procedure, txDetails }, + translations +}: InfoActionProps): JSX.Element => ( + + + + + {errorMessage && ( + + + + )} + + + + + + {/* procedure section */} + + + +); diff --git a/packages/core/src/ui/components/ProposalProcedures/InfoAction/InfoActionTypes.ts b/packages/core/src/ui/components/ProposalProcedures/InfoAction/InfoActionTypes.ts new file mode 100644 index 0000000000..2f8853991e --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/InfoAction/InfoActionTypes.ts @@ -0,0 +1,11 @@ +import * as ProcedureTypes from '../components/ProcedureTypes'; +import * as TxDetailsTypes from '../components/TransactionDetailsTypes'; +export interface Data { + txDetails: TxDetailsTypes.TxDetails; + procedure: ProcedureTypes.Procedure; +} + +export interface Translations { + txDetails: TxDetailsTypes.Translations; + procedure: ProcedureTypes.Translations; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/InfoAction/index.ts b/packages/core/src/ui/components/ProposalProcedures/InfoAction/index.ts new file mode 100644 index 0000000000..f6fae454f1 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/InfoAction/index.ts @@ -0,0 +1 @@ +export { InfoAction } from './InfoAction'; diff --git a/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/NewConstitutionAction.stories.tsx b/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/NewConstitutionAction.stories.tsx new file mode 100644 index 0000000000..d6fc007a4c --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/NewConstitutionAction.stories.tsx @@ -0,0 +1,103 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { NewConstitutionAction } from './NewConstitutionAction'; +import { ComponentProps } from 'react'; + +const customViewports = { + popup: { + name: 'Popup', + styles: { + width: '360px', + height: '600' + } + } +}; + +const meta: Meta = { + title: 'ProposalProcedure/NewConstitutionAction', + component: NewConstitutionAction, + parameters: { + layout: 'centered', + viewport: { + viewports: customViewports, + defaultViewport: 'popup' + } + } +}; + +export default meta; +type Story = StoryObj; + +const data: ComponentProps = { + dappInfo: { + logo: 'https://cdn.mint.handle.me/favicon.png', + name: 'Mint', + url: 'https://preprod.mint.handle.me' + }, + data: { + txDetails: { + txType: 'New Constitution', + deposit: '2000', + rewardAccount: 'stake1u89sasnfyjtmgk8ydqfv3fdl52f36x3djedfnzfc9rkgzrcss5vgr' + }, + procedure: { + anchor: { + hash: '0000000000000000000000000000000000000000000000000000000000000000', + url: 'https://www.someurl.io', + txHashUrl: 'https://www.someurl.io/' + } + }, + actionId: { + index: '0', + id: '26bfdcc75a7f4d0cd8c71f0189bc5ca5ad2f4a3db6240c82b5a0edac7f9203e0' + }, + constitution: { + anchor: { + dataHash: '0000000000000000000000000000000000000000000000000000000000000000', + url: 'https://www.someurl.io' + }, + scriptHash: 'cb0ec2692497b458e46812c8a5bfa2931d1a2d965a99893828ec810f' + } + }, + translations: { + txDetails: { + title: 'Transaction Details', + txType: 'Transaction Type', + deposit: 'Deposit', + rewardAccount: 'Reward account' + }, + procedure: { + anchor: { + hash: 'Anchor Hash', + url: 'Anchor URL' + }, + title: 'Procedure' + }, + actionId: { + title: 'Action ID', + index: 'Index', + txId: 'TX ID' + }, + constitution: { + title: 'Constitution Details', + anchor: { + dataHash: 'Anchor Data Hash', + url: 'Anchor URL' + }, + scriptHash: 'Script Hash' + } + } +}; + +export const Overview: Story = { + args: { + ...data + } +}; + +export const WithError: Story = { + args: { + ...data, + errorMessage: 'Something went wrong' + } +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/NewConstitutionAction.tsx b/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/NewConstitutionAction.tsx new file mode 100644 index 0000000000..7fb710ab03 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/NewConstitutionAction.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Box, Grid, Flex, Divider, Metadata, MetadataLink, Cell } from '@lace/ui'; +import { DappInfo, DappInfoProps } from '../../DappInfo'; +import { ErrorPane } from '@lace/common'; +import * as Types from './NewConstitutionActionTypes'; +import { TransactionDetails } from '../components/TransactionDetails'; +import { Procedure } from '../components/Procedure'; +import { ActionId } from '../components/ActionId'; + +export interface NewConstitutionActionProps { + dappInfo: Omit; + errorMessage?: string; + data: Types.Data; + translations: Types.Translations; +} + +export const NewConstitutionAction = ({ + dappInfo, + errorMessage, + data: { txDetails, procedure, constitution, actionId }, + translations +}: NewConstitutionActionProps): JSX.Element => ( + + + + + {errorMessage && ( + + + + )} + + {/* txDetails section */} + + + + + {/* procedure section */} + + + + + {constitution.scriptHash && ( + + + + )} + {/* action id section*/} + {actionId && ( + <> + + + + + + )} + + +); diff --git a/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/NewConstitutionActionTypes.ts b/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/NewConstitutionActionTypes.ts new file mode 100644 index 0000000000..4d53001a2d --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/NewConstitutionActionTypes.ts @@ -0,0 +1,30 @@ +import * as ProcedureTypes from '../components/ProcedureTypes'; +import * as ActionIdTypes from '../components/ActionIdTypes'; +import * as TxDetailsTypes from '../components/TransactionDetailsTypes'; + +export interface Data { + procedure: ProcedureTypes.Procedure; + actionId?: ActionIdTypes.Data; + txDetails: TxDetailsTypes.TxDetails; + constitution: { + anchor: { + dataHash: string; + url: string; + }; + scriptHash: string; + }; +} + +export interface Translations { + procedure: ProcedureTypes.Translations; + actionId?: ActionIdTypes.Translations; + txDetails: TxDetailsTypes.Translations; + constitution: { + title: string; + anchor: { + dataHash: string; + url: string; + }; + scriptHash: string; + }; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/index.ts b/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/index.ts new file mode 100644 index 0000000000..2e50ba16ea --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/NewConstitutionAction/index.ts @@ -0,0 +1 @@ +export { NewConstitutionAction } from './NewConstitutionAction'; diff --git a/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/NoConfidenceAction.stories.tsx b/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/NoConfidenceAction.stories.tsx new file mode 100644 index 0000000000..81092e9bfe --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/NoConfidenceAction.stories.tsx @@ -0,0 +1,88 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { NoConfidenceAction } from './NoConfidenceAction'; +import { ComponentProps } from 'react'; + +const customViewports = { + popup: { + name: 'Popup', + styles: { + width: '360px', + height: '600' + } + } +}; + +const meta: Meta = { + title: 'ProposalProcedure/NoConfidenceAction', + component: NoConfidenceAction, + parameters: { + layout: 'centered', + viewport: { + viewports: customViewports, + defaultViewport: 'popup' + } + } +}; + +export default meta; +type Story = StoryObj; + +const data: ComponentProps = { + dappInfo: { + logo: 'https://cdn.mint.handle.me/favicon.png', + name: 'Mint', + url: 'https://preprod.mint.handle.me' + }, + data: { + txDetails: { + txType: 'No Confidence', + deposit: '2000', + rewardAccount: 'stake1u89sasnfyjtmgk8ydqfv3fdl52f36x3djedfnzfc9rkgzrcss5vgr' + }, + procedure: { + anchor: { + hash: '0000000000000000000000000000000000000000000000000000000000000000', + url: 'https://www.someurl.io', + txHashUrl: 'https://www.someurl.io/' + } + }, + actionId: { + index: '0', + id: '26bfdcc75a7f4d0cd8c71f0189bc5ca5ad2f4a3db6240c82b5a0edac7f9203e0' + } + }, + translations: { + txDetails: { + title: 'Transaction Details', + txType: 'Transaction Type', + deposit: 'Deposit', + rewardAccount: 'Reward account' + }, + procedure: { + anchor: { + hash: 'Anchor Hash', + url: 'Anchor URL' + }, + title: 'Procedure' + }, + actionId: { + title: 'Action ID', + index: 'Index', + txId: 'TX ID' + } + } +}; + +export const Overview: Story = { + args: { + ...data + } +}; + +export const WithError: Story = { + args: { + ...data, + errorMessage: 'Something went wrong' + } +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/NoConfidenceAction.tsx b/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/NoConfidenceAction.tsx new file mode 100644 index 0000000000..73034a6fe9 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/NoConfidenceAction.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Box, Grid, Flex, Divider, Cell } from '@lace/ui'; +import { DappInfo, DappInfoProps } from '../../DappInfo'; +import { ErrorPane } from '@lace/common'; +import * as Types from './NoConfidenceActionTypes'; +import { TransactionDetails } from '../components/TransactionDetails'; +import { Procedure } from '../components/Procedure'; +import { ActionId } from '../components/ActionId'; + +export interface NoConfidenceActionProps { + dappInfo: Omit; + errorMessage?: string; + data: Types.Data; + translations: Types.Translations; +} + +export const NoConfidenceAction = ({ + dappInfo, + errorMessage, + data: { procedure, txDetails, actionId }, + translations +}: NoConfidenceActionProps): JSX.Element => ( + + + + + {errorMessage && ( + + + + )} + + {/* tx details section */} + + + + + {/* procedure section */} + + {/* action id section*/} + {actionId && ( + <> + + + + + + )} + + +); diff --git a/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/NoConfidenceActionTypes.ts b/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/NoConfidenceActionTypes.ts new file mode 100644 index 0000000000..0cdfcea7d0 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/NoConfidenceActionTypes.ts @@ -0,0 +1,15 @@ +import * as ProcedureTypes from '../components/ProcedureTypes'; +import * as ActionIdTypes from '../components/ActionIdTypes'; +import * as TxDetailsTypes from '../components/TransactionDetailsTypes'; + +export interface Data { + procedure: ProcedureTypes.Procedure; + actionId?: ActionIdTypes.Data; + txDetails: TxDetailsTypes.TxDetails; +} + +export interface Translations { + procedure: ProcedureTypes.Translations; + actionId?: ActionIdTypes.Translations; + txDetails: TxDetailsTypes.Translations; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/index.ts b/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/index.ts new file mode 100644 index 0000000000..2c9c805b7b --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/NoConfidenceAction/index.ts @@ -0,0 +1 @@ +export { NoConfidenceAction } from './NoConfidenceAction'; diff --git a/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/EconomicGroup.tsx b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/EconomicGroup.tsx new file mode 100644 index 0000000000..0e92b3b4d3 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/EconomicGroup.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Metadata, Text, sx, Cell } from '@lace/ui'; +import { Card } from '../components/Card'; +import * as Types from './ParameterChangeActionTypes'; + +interface Props { + economicGroup: Types.EconomicGroup; + translations: Types.Translations['economicGroup']; +} + +export const EconomicGroup = ({ economicGroup, translations }: Props): JSX.Element => { + const textCss = sx({ + color: '$text_primary' + }); + + return ( + <> + + + {translations.title} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/GovernanceGroup.tsx b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/GovernanceGroup.tsx new file mode 100644 index 0000000000..e0aa03098a --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/GovernanceGroup.tsx @@ -0,0 +1,144 @@ +import React from 'react'; +import { Metadata, Text, sx, Divider, Cell } from '@lace/ui'; +import * as Types from './ParameterChangeActionTypes'; + +interface Props { + governanceGroup: Types.GovernanceGroup; + translations: Types.Translations['governanceGroup']; +} + +export const GovernanceGroup = ({ governanceGroup, translations }: Props): JSX.Element => { + const textCss = sx({ + color: '$text_primary' + }); + + return ( + <> + + + {translations.title} + + + + + + + + + + + + + + + + + + + + + + + + + + {translations.dRepVotingThresholds.title} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/NetworkGroup.tsx b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/NetworkGroup.tsx new file mode 100644 index 0000000000..88605cbc76 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/NetworkGroup.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Metadata, Text, sx, Cell } from '@lace/ui'; +import * as Types from './ParameterChangeActionTypes'; + +interface Props { + networkGroup: Types.NetworkGroup; + translations: Types.Translations['networkGroup']; +} + +export const NetworkGroup = ({ networkGroup, translations }: Props): JSX.Element => { + const textCss = sx({ + color: '$text_primary' + }); + + return ( + <> + + + {translations.title} + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/ParameterChangeAction.stories.tsx b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/ParameterChangeAction.stories.tsx new file mode 100644 index 0000000000..2e4fababde --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/ParameterChangeAction.stories.tsx @@ -0,0 +1,243 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { ParameterChangeAction } from './ParameterChangeAction'; +import { ComponentProps } from 'react'; + +const customViewports = { + popup: { + name: 'Popup', + styles: { + width: '360px', + height: '600' + } + } +}; + +const meta: Meta = { + title: 'ProposalProcedure/ParameterChangeAction', + component: ParameterChangeAction, + parameters: { + layout: 'centered', + viewport: { + viewports: customViewports, + defaultViewport: 'popup' + } + } +}; + +export default meta; +type Story = StoryObj; + +const data: ComponentProps = { + dappInfo: { + logo: 'https://cdn.mint.handle.me/favicon.png', + name: 'Mint', + url: 'https://preprod.mint.handle.me' + }, + data: { + txDetails: { + txType: 'Protocol Parameter Update', + deposit: '2000', + rewardAccount: 'stake1u89sasnfyjtmgk8ydqfv3fdl52f36x3djedfnzfc9rkgzrcss5vgr' + }, + anchor: { + hash: '0000000000000000000000000000000000000000000000000000000000000000', + url: 'https://www.someurl.io', + txHashUrl: 'https://www.someurl.io/' + }, + protocolParamUpdate: { + maxBlockExUnits: { + memory: '50000000', + step: '4000000000' + }, + maxTxExUnits: { + memory: '10000000', + step: '10000000000' + }, + networkGroup: { + maxBBSize: '65536', + maxBHSize: '1100', + maxTxSize: '16384', + maxCollateralInputs: '3', + maxValSize: '5000' + }, + economicGroup: { + minFeeA: '44', + minFeeB: '155381', + keyDeposit: '2000000', + poolDeposit: '500000000', + minPoolCost: '340000000', + coinsPerUTxOByte: '34482', + price: { + memory: '0.0577', + step: '0.0000721' + }, + rho: '0.003', + tau: '0.2' + }, + technicalGroup: { + a0: '0.3', + nOpt: '150', + collateralPercentage: '150', + costModels: { + PlutusV1: { + 'addInteger-cpu-arguments-intercept': '197_209', + 'addInteger-cpu-arguments-slope': '0' + }, + PlutusV2: { + 'addInteger-cpu-arguments-intercept': '197_209', + 'addInteger-cpu-arguments-slope': '0' + } + }, + eMax: '18' + }, + governanceGroup: { + govActionLifetime: '14', + govActionDeposit: '0', + ccMaxTermLength: '60', + ccMinSize: '0', + drepActivity: '0', + drepDeposit: '0', + dRepVotingThresholds: { + motionNoConfidence: '0.51', + committeeNormal: '0.51', + committeeNoConfidence: '0.51', + updateToConstitution: '0.51', + hardForkInitiation: '0.51', + ppNetworkGroup: '0.51', + ppEconomicGroup: '0.51', + ppTechnicalGroup: '0.51', + ppGovGroup: '0.51', + treasuryWithdrawal: '0.51' + } + } + } + }, + translations: { + txDetails: { + title: 'Transaction Details', + txType: 'Transaction Type', + deposit: 'Deposit', + rewardAccount: 'Reward account' + }, + memory: 'Memory', + step: 'Step', + anchor: { + hash: 'Anchor Hash', + url: 'Anchor URL' + }, + networkGroup: { + title: 'Network group', + maxBBSize: 'Max BB Size', + maxTxSize: 'Max Tx Size', + maxBHSize: 'Max BH Size', + maxValSize: 'Max Val Size', + maxTxExUnits: 'Max TX Ex Units', + maxBlockExUnits: 'Max Blk Ex Units', + maxCollateralInputs: 'Max Coll Inputs', + tooltip: { + maxBBSize: 'Max block body size', + maxTxSize: 'Max transaction size', + maxBHSize: 'Max block header size', + maxValSize: 'Max size of a serialized asset value', + maxTxExUnits: 'Max script execution units in a single transaction', + maxBlockExUnits: 'Max script execution units in a single block', + maxCollateralInputs: 'Max number of collateral inputs' + } + }, + economicGroup: { + title: 'Economic group', + minFeeA: 'Min Fee A', + minFeeB: 'Min Fee B', + keyDeposit: 'Key Deposit', + poolDeposit: 'Pool Deposit', + rho: 'Rho', + tau: 'Tau', + minPoolCost: 'Min Pool Cost', + coinsPerUTxOByte: 'Coins/UTxO Byte', + prices: 'Price', + tooltip: { + minFeeA: 'Min fee coefficient', + minFeeB: 'Min fee constant', + keyDeposit: 'Delegation key Lovelace deposit', + poolDeposit: 'Pool registration Lovelace deposit', + rho: 'Monetary expansion', + tau: 'Treasury expansion', + minPoolCost: 'Min fixed rewards cut for pools', + coinsPerUTxOByte: 'Min Lovelace deposit per byte of serialized UTxO', + prices: 'Prices of Plutus execution units' + } + }, + technicalGroup: { + title: 'Technical group', + a0: 'A0', + eMax: 'EMax', + nOpt: 'NOpt', + costModels: 'Cost Models', + collateralPercentage: 'Coll Percentage', + tooltip: { + a0: 'Pool pledge influence', + eMax: 'Pool retirement maximum epoch', + nOpt: 'Desired number of pools', + costModels: 'Plutus execution cost models', + collateralPercentage: 'Proportion of collateral needed for scripts' + } + }, + governanceGroup: { + title: 'Governance group', + govActionLifetime: 'Gov Act Lifetime', + govActionDeposit: 'Gov Act Deposit', + drepDeposit: 'DRep Deposit', + drepActivity: 'DRep Activity', + ccMinSize: 'CC Min Size', + ccMaxTermLength: 'CC Max Term Length', + dRepVotingThresholds: { + title: 'Governance voting thresholds', + motionNoConfidence: 'Motion No Conf', + committeeNormal: 'Comm Normal', + committeeNoConfidence: 'Comm No Conf', + updateConstitution: 'Update Const', + hardForkInitiation: 'Hard Fork Init', + ppNetworkGroup: 'PP Network Grp', + ppEconomicGroup: 'PP Economic Grp', + ppTechnicalGroup: 'PP Technical Grp', + ppGovernanceGroup: 'PP Governance Grp', + treasuryWithdrawal: 'Treasury Withdraw' + }, + tooltip: { + govActionLifetime: 'governance action maximum lifetime in epochs', + govActionDeposit: 'governance action deposit', + drepDeposit: 'DRep deposit amount', + drepActivity: 'DRep activity period in epochs', + ccMinSize: 'Min constitutional committee size', + ccMaxTermLength: 'Max term length (in epochs) for the constitutional committee members', + dRepVotingThresholds: { + title: 'DRep voting thresholds', + motionNoConfidence: '1. Motion of no-confidence', + committeeNormal: '2a. New committee/threshold (normal state)', + committeeNoConfidence: '2b. New committee/threshold (state of no-confidence)', + updateConstitution: '3. Update to the Constitution or proposal policy', + hardForkInitiation: '4. Hard-fork initiation', + ppNetworkGroup: '5a. Protocol parameter changes, network group', + ppEconomicGroup: '5b. Protocol parameter changes, economic group', + ppTechnicalGroup: '5c. Protocol parameter changes, technical group', + ppGovernanceGroup: '5d. Protocol parameter changes, governance group', + treasuryWithdrawal: '6. Treasury withdrawal' + } + } + } + } +}; + +export const Overview: Story = { + args: { + ...data + } +}; + +export const WithError: Story = { + args: { + ...data, + errorMessage: 'Something went wrong' + } +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/ParameterChangeAction.tsx b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/ParameterChangeAction.tsx new file mode 100644 index 0000000000..871101f707 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/ParameterChangeAction.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { Box, Grid, Flex, Divider, Metadata, MetadataLink, Cell } from '@lace/ui'; +import { DappInfo, DappInfoProps } from '../../DappInfo'; +import { ErrorPane } from '@lace/common'; +import { TransactionDetails } from '../components/TransactionDetails'; +import * as Types from './ParameterChangeActionTypes'; +import { EconomicGroup } from './EconomicGroup'; +import { NetworkGroup } from './NetworkGroup'; +import { TechnicalGroup } from './TechnicalGroup'; +import { GovernanceGroup } from './GovernanceGroup'; +import { Card } from '../components/Card'; + +interface ParameterChangeActionProps { + dappInfo: Omit; + errorMessage?: string; + data: Types.Data; + translations: Types.Translations; +} + +export const ParameterChangeAction = ({ + dappInfo, + errorMessage, + data: { txDetails, protocolParamUpdate, anchor }, + translations +}: ParameterChangeActionProps): JSX.Element => { + const { economicGroup, governanceGroup, networkGroup, technicalGroup, maxTxExUnits, maxBlockExUnits } = + protocolParamUpdate; + + return ( + + + + + {errorMessage && ( + + + + )} + + {/* tx details section */} + + <> + + + + + {anchor.txHashUrl ? ( + + ) : ( + + )} + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/ParameterChangeActionTypes.ts b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/ParameterChangeActionTypes.ts new file mode 100644 index 0000000000..90efec01da --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/ParameterChangeActionTypes.ts @@ -0,0 +1,185 @@ +import * as ProcedureTypes from '../components/ProcedureTypes'; +import * as TxDetailsTypes from '../components/TransactionDetailsTypes'; + +export interface Data { + protocolParamUpdate: ProtocolParamUpdate; + txDetails: TxDetailsTypes.TxDetails; + anchor: ProcedureTypes.Procedure['anchor']; +} + +export interface NetworkGroup { + maxBBSize: string; + maxTxSize: string; + maxBHSize: string; + maxValSize: string; + maxCollateralInputs: string; +} + +export interface EconomicGroup { + minFeeA: string; + minFeeB: string; + keyDeposit: string; + poolDeposit: string; + rho: string; + tau: string; + minPoolCost: string; + coinsPerUTxOByte: string; + price: { + memory: string; + step: string; + }; +} + +export interface TechnicalGroup { + a0: string; + eMax: string; + nOpt: string; + costModels: { + PlutusV1: Record; + PlutusV2: Record; + }; + collateralPercentage: string; +} + +export interface GovernanceGroup { + govActionLifetime: string; + govActionDeposit: string; + drepDeposit: string; + drepActivity: string; + ccMinSize: string; + ccMaxTermLength: string; + dRepVotingThresholds: { + motionNoConfidence: string; + committeeNormal: string; + committeeNoConfidence: string; + updateToConstitution: string; + hardForkInitiation: string; + ppNetworkGroup: string; + ppEconomicGroup: string; + ppTechnicalGroup: string; + ppGovGroup: string; + treasuryWithdrawal: string; + }; +} + +interface ProtocolParamUpdate { + maxTxExUnits: { + memory: string; + step: string; + }; + maxBlockExUnits: { + memory: string; + step: string; + }; + networkGroup: NetworkGroup; + economicGroup: EconomicGroup; + technicalGroup: TechnicalGroup; + governanceGroup: GovernanceGroup; +} + +export interface Translations { + txDetails: TxDetailsTypes.Translations; + anchor: ProcedureTypes.Translations['anchor']; + memory: string; + step: string; + networkGroup: { + title: string; + maxBBSize: string; + maxTxSize: string; + maxBHSize: string; + maxValSize: string; + maxTxExUnits: string; + maxBlockExUnits: string; + maxCollateralInputs: string; + tooltip: { + maxBBSize: string; + maxTxSize: string; + maxBHSize: string; + maxValSize: string; + maxTxExUnits: string; + maxBlockExUnits: string; + maxCollateralInputs: string; + }; + }; + economicGroup: { + title: string; + minFeeA: string; + minFeeB: string; + keyDeposit: string; + poolDeposit: string; + rho: string; + tau: string; + minPoolCost: string; + coinsPerUTxOByte: string; + prices: string; + tooltip: { + minFeeA: string; + minFeeB: string; + keyDeposit: string; + poolDeposit: string; + rho: string; + tau: string; + minPoolCost: string; + coinsPerUTxOByte: string; + prices: string; + }; + }; + technicalGroup: { + title: string; + a0: string; + eMax: string; + nOpt: string; + costModels: string; + collateralPercentage: string; + tooltip: { + a0: string; + eMax: string; + nOpt: string; + costModels: string; + collateralPercentage: string; + }; + }; + governanceGroup: { + title: string; + govActionLifetime: string; + govActionDeposit: string; + drepDeposit: string; + drepActivity: string; + ccMinSize: string; + ccMaxTermLength: string; + dRepVotingThresholds: { + title: string; + motionNoConfidence: string; + committeeNormal: string; + committeeNoConfidence: string; + updateConstitution: string; + hardForkInitiation: string; + ppNetworkGroup: string; + ppEconomicGroup: string; + ppTechnicalGroup: string; + ppGovernanceGroup: string; + treasuryWithdrawal: string; + }; + tooltip: { + govActionLifetime: string; + govActionDeposit: string; + drepDeposit: string; + drepActivity: string; + ccMinSize: string; + ccMaxTermLength: string; + dRepVotingThresholds: { + title: string; + motionNoConfidence: string; + committeeNormal: string; + committeeNoConfidence: string; + updateConstitution: string; + hardForkInitiation: string; + ppNetworkGroup: string; + ppEconomicGroup: string; + ppTechnicalGroup: string; + ppGovernanceGroup: string; + treasuryWithdrawal: string; + }; + }; + }; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/TechnicalGroup.tsx b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/TechnicalGroup.tsx new file mode 100644 index 0000000000..fd3776d211 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/TechnicalGroup.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Metadata, Text, sx, Cell } from '@lace/ui'; +import * as Types from './ParameterChangeActionTypes'; + +interface Props { + technicalGroup: Types.TechnicalGroup; + translations: Types.Translations['technicalGroup']; +} + +export const TechnicalGroup = ({ technicalGroup, translations }: Props): JSX.Element => { + const textCss = sx({ + color: '$text_primary' + }); + + // TODO: review cost model syntax/display + // const costModels = Object.entries(technicalGroup.costModels).map(([key, value]) => ({ + // title: key, + // fields: Object.entries(value).map(([cKey, cValue]) => ({ + // label: cKey, + // value: cValue + // })) + // })); + + return ( + <> + + + {translations.title} + + + + + + + + + + + + + + + {/* TODO: review cost model syntax/display */} + {/* + + {translations.costModels} + + + + {costModels.map(({ title, fields }, idx) => ( + 0 ? '$24' : '$0'} mb={costModels.length === idx - 1 ? '$18' : '$0'} key={title}> + + + ))} + */} + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/index.ts b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/index.ts new file mode 100644 index 0000000000..63caf5ba15 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/ParameterChangeAction/index.ts @@ -0,0 +1 @@ +export { ParameterChangeAction } from './ParameterChangeAction'; diff --git a/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/TreasuryWithdrawalsAction.stories.tsx b/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/TreasuryWithdrawalsAction.stories.tsx new file mode 100644 index 0000000000..951be959cd --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/TreasuryWithdrawalsAction.stories.tsx @@ -0,0 +1,99 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { TreasuryWithdrawalsAction } from './TreasuryWithdrawalsAction'; +import { ComponentProps } from 'react'; + +const customViewports = { + popup: { + name: 'Popup', + styles: { + width: '360px', + height: '600' + } + } +}; + +const meta: Meta = { + title: 'ProposalProcedure/TreasuryWithdrawalsAction', + component: TreasuryWithdrawalsAction, + parameters: { + layout: 'centered', + viewport: { + viewports: customViewports, + defaultViewport: 'popup' + } + } +}; + +export default meta; +type Story = StoryObj; + +const data: ComponentProps = { + dappInfo: { + logo: 'https://cdn.mint.handle.me/favicon.png', + name: 'Mint', + url: 'https://preprod.mint.handle.me' + }, + data: { + txDetails: { + txType: 'Treasury Withdrawals', + deposit: '2000', + rewardAccount: 'stake1u89sasnfyjtmgk8ydqfv3fdl52f36x3djedfnzfc9rkgzrcss5vgr' + }, + procedure: { + anchor: { + hash: '26bfdcc75a7f4d0cd8c71f0189bc5ca5ad2f4a3db6240c82b5a0edac7f9203e0', + url: 'https://www.someurl.io', + txHashUrl: 'https://www.someurl.io' + } + }, + withdrawals: [ + { + rewardAccount: 'stake1u89sasnfyjtmgk8ydqfv3fdl52f36x3djedfnzfc9rkgzrcss5vgr', + lovelace: '1030939916423' + } + ], + actionId: { + index: '0', + id: '26bfdcc75a7f4d0cd8c71f0189bc5ca5ad2f4a3db6240c82b5a0edac7f9203e0' + } + }, + translations: { + txDetails: { + title: 'Transaction Details', + txType: 'Transaction Type', + deposit: 'Deposit', + rewardAccount: 'Reward account' + }, + procedure: { + anchor: { + hash: 'Anchor Hash', + url: 'Anchor URL' + }, + title: 'Procedure' + }, + actionId: { + title: 'Action ID', + index: 'Index', + txId: 'TX ID' + }, + withdrawals: { + title: 'Withdrawal Details', + lovelace: 'Lovelace Withdrawn', + rewardAccount: 'Reward account' + } + } +}; + +export const Overview: Story = { + args: { + ...data + } +}; + +export const WithError: Story = { + args: { + ...data, + errorMessage: 'Something went wrong' + } +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/TreasuryWithdrawalsAction.tsx b/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/TreasuryWithdrawalsAction.tsx new file mode 100644 index 0000000000..38a8d2196d --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/TreasuryWithdrawalsAction.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { Box, Grid, Flex, Divider, sx, Text, Metadata, Cell } from '@lace/ui'; +import { DappInfo, DappInfoProps } from '../../DappInfo'; +import { ErrorPane } from '@lace/common'; +import * as Types from './TreasuryWithdrawalsActionTypes'; +import { TransactionDetails } from '../components/TransactionDetails'; +import { ActionId } from '../components/ActionId'; +import { Procedure } from '../components/Procedure'; + +interface TreasuryWithdrawalsActionProps { + dappInfo: Omit; + errorMessage?: string; + data: Types.Data; + translations: Types.Translations; +} + +export const TreasuryWithdrawalsAction = ({ + dappInfo, + errorMessage, + data: { txDetails, procedure, withdrawals, actionId }, + translations +}: TreasuryWithdrawalsActionProps): JSX.Element => { + const textCss = sx({ + color: '$text_primary' + }); + + return ( + + + + + {errorMessage && ( + + + + )} + + {/* tx details section */} + + + + + {/* procedure section */} + + + + {translations.withdrawals.title} + + + {withdrawals.map((withdrawal) => ( + + + + + + + + + ))} + {actionId && ( + <> + + + + + + )} + + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/TreasuryWithdrawalsActionTypes.ts b/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/TreasuryWithdrawalsActionTypes.ts new file mode 100644 index 0000000000..7de7a0fc0d --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/TreasuryWithdrawalsActionTypes.ts @@ -0,0 +1,24 @@ +import * as ProcedureTypes from '../components/ProcedureTypes'; +import * as ActionIdTypes from '../components/ActionIdTypes'; +import * as TxDetailsTypes from '../components/TransactionDetailsTypes'; + +export interface Data { + actionId?: ActionIdTypes.Data; + txDetails: TxDetailsTypes.TxDetails; + procedure: ProcedureTypes.Procedure; + withdrawals: Array<{ + rewardAccount: string; + lovelace: string; + }>; +} + +export interface Translations { + txDetails: TxDetailsTypes.Translations; + actionId?: ActionIdTypes.Translations; + procedure: ProcedureTypes.Translations; + withdrawals: { + title: string; + rewardAccount: string; + lovelace: string; + }; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/index.ts b/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/index.ts new file mode 100644 index 0000000000..2b32267088 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/TreasuryWithdrawalsAction/index.ts @@ -0,0 +1 @@ +export { TreasuryWithdrawalsAction } from './TreasuryWithdrawalsAction'; diff --git a/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/UpdateCommitteeActionAction.stories.tsx b/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/UpdateCommitteeActionAction.stories.tsx new file mode 100644 index 0000000000..9750fa1551 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/UpdateCommitteeActionAction.stories.tsx @@ -0,0 +1,130 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { UpdateCommitteeAction } from './UpdateCommitteeActionAction'; +import { ComponentProps } from 'react'; + +const customViewports = { + popup: { + name: 'Popup', + styles: { + width: '360px', + height: '600' + } + } +}; + +const meta: Meta = { + title: 'ProposalProcedure/UpdateCommitteeAction', + component: UpdateCommitteeAction, + parameters: { + layout: 'centered', + viewport: { + viewports: customViewports, + defaultViewport: 'popup' + } + } +}; + +export default meta; +type Story = StoryObj; + +const data: ComponentProps = { + dappInfo: { + logo: 'https://cdn.mint.handle.me/favicon.png', + name: 'Mint', + url: 'https://preprod.mint.handle.me' + }, + data: { + txDetails: { + txType: 'Hard Fork Initiation', + deposit: '2000', + rewardAccount: 'stake1u89sasnfyjtmgk8ydqfv3fdl52f36x3djedfnzfc9rkgzrcss5vgr' + }, + procedure: { + anchor: { + hash: '26bfdcc75a7f4d0cd8c71f0189bc5ca5ad2f4a3db6240c82b5a0edac7f9203e0', + url: 'https://www.someurl.io', + txHashUrl: 'https://www.someurl.io' + } + }, + actionId: { + index: '0', + id: '26bfdcc75a7f4d0cd8c71f0189bc5ca5ad2f4a3db6240c82b5a0edac7f9203e0' + }, + membersToBeAdded: [ + { + coldCredential: { + hash: '30000000000000000000000000000000000000000000000000000000' + }, + epoch: '1' + }, + { + coldCredential: { + hash: '40000000000000000000000000000000000000000000000000000000' + }, + epoch: '2' + } + ], + membersToBeRemoved: [ + { + hash: '00000000000000000000000000000000000000000000000000000000' + }, + { + hash: '20000000000000000000000000000000000000000000000000000000' + } + ], + newQuorumThreshold: { + denominator: '5', + numerator: '1' + } + }, + translations: { + txDetails: { + title: 'Transaction Details', + txType: 'Transaction Type', + deposit: 'Deposit', + rewardAccount: 'Reward account' + }, + procedure: { + anchor: { + hash: 'Anchor Hash', + url: 'Anchor URL' + }, + title: 'Procedure' + }, + actionId: { + title: 'Action ID', + index: 'Index', + txId: 'TX ID' + }, + membersToBeAdded: { + title: 'Members To Be Added', + coldCredential: { + hash: 'Cold Credential Hash', + epoch: 'Epoch' + } + }, + membersToBeRemoved: { + title: 'Members To Be Removed', + hash: 'Hash' + }, + newQuorumThreshold: { + title: 'New Quorum Threshold', + denominator: 'Denominator', + numerator: 'Numerator' + } + } +}; + +export const Overview: Story = { + args: { + ...data + } +}; + +export const WithError: Story = { + args: { + ...data, + errorMessage: 'Something went wrong' + } +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/UpdateCommitteeActionAction.tsx b/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/UpdateCommitteeActionAction.tsx new file mode 100644 index 0000000000..97237316d5 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/UpdateCommitteeActionAction.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import { Box, Grid, Flex, Divider, sx, Text, Metadata, Cell } from '@lace/ui'; +import { DappInfo, DappInfoProps } from '../../DappInfo'; +import { ErrorPane } from '@lace/common'; +import * as Types from './UpdateCommitteeActionTypes'; +import { Procedure } from '../components/Procedure'; +import { TransactionDetails } from '../components/TransactionDetails'; +import { ActionId } from '../components/ActionId'; + +interface UpdateCommitteeActionProps { + dappInfo: Omit; + errorMessage?: string; + data: Types.Data; + translations: Types.Translations; +} + +export const UpdateCommitteeAction = ({ + dappInfo, + errorMessage, + data: { procedure, txDetails, membersToBeAdded, membersToBeRemoved, actionId }, + translations +}: UpdateCommitteeActionProps): JSX.Element => { + const textCss = sx({ + color: '$text_primary' + }); + + return ( + + + + + {errorMessage && ( + + + + )} + + {/* tx details section */} + + + + + {/* procedure section */} + + + + + {membersToBeAdded.length > 0 && ( + <> + + + {translations.membersToBeAdded.title} + + + {membersToBeAdded.map(({ coldCredential, epoch }) => ( + + + + + + + + + ))} + + )} + {membersToBeRemoved.length > 0 && ( + <> + + + {translations.membersToBeRemoved.title} + + + {membersToBeRemoved.map(({ hash }) => ( + + + + + + + + + ))} + + )} + {actionId && ( + <> + + + + + + )} + + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/UpdateCommitteeActionTypes.ts b/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/UpdateCommitteeActionTypes.ts new file mode 100644 index 0000000000..ce8b90745d --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/UpdateCommitteeActionTypes.ts @@ -0,0 +1,50 @@ +import * as ProcedureTypes from '../components/ProcedureTypes'; +import * as ActionIdTypes from '../components/ActionIdTypes'; +import * as TxDetailsTypes from '../components/TransactionDetailsTypes'; + +interface MembersToBeAdded { + coldCredential: { + hash: string; + }; + epoch: string; +} + +interface MembersToBeRemoved { + hash: string; +} + +interface NewQuorumThreshold { + denominator: string; + numerator: string; +} + +export interface Data { + actionId?: ActionIdTypes.Data; + txDetails: TxDetailsTypes.TxDetails; + procedure: ProcedureTypes.Procedure; + membersToBeAdded: MembersToBeAdded[]; + membersToBeRemoved: MembersToBeRemoved[]; + newQuorumThreshold?: NewQuorumThreshold; +} + +export interface Translations { + procedure: ProcedureTypes.Translations; + actionId?: ActionIdTypes.Translations; + txDetails: TxDetailsTypes.Translations; + membersToBeAdded: { + title: string; + coldCredential: { + hash: string; + epoch: string; + }; + }; + membersToBeRemoved: { + title: string; + hash: string; + }; + newQuorumThreshold?: { + title: string; + denominator: string; + numerator: string; + }; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/index.ts b/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/index.ts new file mode 100644 index 0000000000..b2b57cd254 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/UpdateCommitteeAction/index.ts @@ -0,0 +1 @@ +export { UpdateCommitteeAction } from './UpdateCommitteeActionAction'; diff --git a/packages/core/src/ui/components/ProposalProcedures/components/ActionId.tsx b/packages/core/src/ui/components/ProposalProcedures/components/ActionId.tsx new file mode 100644 index 0000000000..27112d1a52 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/components/ActionId.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Cell, sx, Metadata, Text } from '@lace/ui'; +import * as Types from './ActionIdTypes'; + +interface Props { + data: Types.Data; + translations: Types.Translations; +} + +export const ActionId = ({ data, translations }: Props): JSX.Element => { + const textCss = sx({ + color: '$text_primary' + }); + + return ( + <> + + + {translations.title} + + + + + + + + + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/components/ActionIdTypes.ts b/packages/core/src/ui/components/ProposalProcedures/components/ActionIdTypes.ts new file mode 100644 index 0000000000..3dbb0d8824 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/components/ActionIdTypes.ts @@ -0,0 +1,10 @@ +export interface Data { + index: string; + id: string; +} + +export interface Translations { + title?: string; + index: string; + txId: string; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/components/Card.module.scss b/packages/core/src/ui/components/ProposalProcedures/components/Card.module.scss new file mode 100644 index 0000000000..bf8fa31cab --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/components/Card.module.scss @@ -0,0 +1,3 @@ +.text { + word-break: break-all; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/components/Card.tsx b/packages/core/src/ui/components/ProposalProcedures/components/Card.tsx new file mode 100644 index 0000000000..44bed28c23 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/components/Card.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { Grid, Flex, Card as UICard, Box, sx, Text, TextLink, Tooltip, Cell } from '@lace/ui'; +import styles from './Card.module.scss'; + +interface Item { + label: string; + value: string; + tooltip?: string; + url?: string; +} + +interface Props { + title?: string; + tooltip?: string; + data: Item[]; +} + +export const Card = ({ title, tooltip, data }: Props): JSX.Element => { + const textCss = sx({ + color: '$text_primary' + }); + + const renderRow = (props: Item) => ( + <> + + + {props.tooltip ? ( + + + {props.label} + + + ) : ( + + {props.label} + + )} + + + + + {props.url ? ( + + + + ) : ( + + {props.value} + + )} + + + + ); + + const renderTitle = () => { + if (!title) return <>; + + if (tooltip) { + return ( + + + + {title} + + + + ); + } + + return ( + + + {title} + + + ); + }; + + return ( + + + + + {renderTitle()} + + {data.map((props) => renderRow(props))} + + + + + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/components/Procedure.tsx b/packages/core/src/ui/components/ProposalProcedures/components/Procedure.tsx new file mode 100644 index 0000000000..682fcc21ff --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/components/Procedure.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Metadata, MetadataLink, Text, sx, Cell } from '@lace/ui'; +import * as Types from './ProcedureTypes'; + +interface Props { + data: Types.Procedure; + translations: Types.Translations; +} + +export const Procedure = ({ data, translations }: Props): JSX.Element => { + const textCss = sx({ + color: '$text_primary' + }); + + return ( + <> + + + {translations.title} + + + <> + + + + + + + + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/components/ProcedureTypes.ts b/packages/core/src/ui/components/ProposalProcedures/components/ProcedureTypes.ts new file mode 100644 index 0000000000..a1ec2c00a7 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/components/ProcedureTypes.ts @@ -0,0 +1,15 @@ +export interface Procedure { + anchor: { + url: string; + hash: string; + txHashUrl: string; + }; +} + +export interface Translations { + title: string; + anchor: { + url: string; + hash: string; + }; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/components/TransactionDetails.tsx b/packages/core/src/ui/components/ProposalProcedures/components/TransactionDetails.tsx new file mode 100644 index 0000000000..8a21c6b005 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/components/TransactionDetails.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Metadata, Text, sx, Cell } from '@lace/ui'; +import * as Types from './TransactionDetailsTypes'; + +interface Props { + data: Types.TxDetails; + translations: Types.Translations; +} + +export const TransactionDetails = ({ data, translations }: Props): JSX.Element => { + const textCss = sx({ + color: '$text_primary' + }); + + return ( + <> + + + {translations.title} + + + + + + {data.rewardAccount && ( + + + + )} + {data.deposit && ( + + + + )} + + ); +}; diff --git a/packages/core/src/ui/components/ProposalProcedures/components/TransactionDetailsTypes.ts b/packages/core/src/ui/components/ProposalProcedures/components/TransactionDetailsTypes.ts new file mode 100644 index 0000000000..744b832002 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/components/TransactionDetailsTypes.ts @@ -0,0 +1,12 @@ +export interface TxDetails { + txType: string; + deposit?: string; + rewardAccount?: string; +} + +export interface Translations { + title: string; + txType: string; + deposit?: string; + rewardAccount?: string; +} diff --git a/packages/core/src/ui/components/ProposalProcedures/index.ts b/packages/core/src/ui/components/ProposalProcedures/index.ts new file mode 100644 index 0000000000..d8d0bc4008 --- /dev/null +++ b/packages/core/src/ui/components/ProposalProcedures/index.ts @@ -0,0 +1,7 @@ +export * from './HardForkInitiationAction'; +export * from './InfoAction'; +export * from './NewConstitutionAction'; +export * from './NoConfidenceAction'; +export * from './ParameterChangeAction'; +export * from './TreasuryWithdrawalsAction'; +export * from './UpdateCommitteeAction'; diff --git a/packages/staking/src/features/DelegationCard/DelegationTooltip.css.ts b/packages/staking/src/features/DelegationCard/DelegationTooltip.css.ts new file mode 100644 index 0000000000..c48a22fb36 --- /dev/null +++ b/packages/staking/src/features/DelegationCard/DelegationTooltip.css.ts @@ -0,0 +1,11 @@ +import { style } from '@vanilla-extract/css'; +import { theme } from '../theme'; + +export const tooltip = style({ + background: theme.colors.$tooltipBgColor, + borderRadius: theme.radius.$small, + boxShadow: theme.elevation.$tooltip, + margin: theme.spacing.$10, + maxWidth: theme.spacing.$214, + padding: theme.spacing.$16, +}); diff --git a/packages/ui/src/design-system/metadata/metadata-link.component.tsx b/packages/ui/src/design-system/metadata/metadata-link.component.tsx index 5eb52078cf..3d766be856 100644 --- a/packages/ui/src/design-system/metadata/metadata-link.component.tsx +++ b/packages/ui/src/design-system/metadata/metadata-link.component.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { Flex } from '../flex'; import { Grid, Cell } from '../grid'; import { TextLink } from '../text-link'; +import { Tooltip } from '../tooltip'; import * as Typography from '../typography'; import * as cx from './metadata.css'; @@ -13,20 +14,32 @@ type Props = OmitClassName<'div'> & { label: string; text: string; url: string; + tooltip?: string; }; export const MetadataLink = ({ label, text, url, + tooltip, ...props }: Readonly): JSX.Element => { return ( - - {label} - + {tooltip == undefined ? ( + + {label} + + ) : ( + +
+ + {label} + +
+
+ )}
diff --git a/packages/ui/src/design-system/metadata/metadata.component.tsx b/packages/ui/src/design-system/metadata/metadata.component.tsx index f9d9fd0aa8..6f15a58c6b 100644 --- a/packages/ui/src/design-system/metadata/metadata.component.tsx +++ b/packages/ui/src/design-system/metadata/metadata.component.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Flex } from '../flex'; import { Grid, Cell } from '../grid'; +import { Tooltip } from '../tooltip'; import * as Typography from '../typography'; import * as cx from './metadata.css'; @@ -11,19 +12,31 @@ import type { OmitClassName } from '../../types'; type Props = OmitClassName<'div'> & { label: string; text: string; + tooltip?: string; }; export const Metadata = ({ label, text, + tooltip, ...props }: Readonly): JSX.Element => { return ( - - {label} - + {tooltip == undefined ? ( + + {label} + + ) : ( + +
+ + {label} + +
+
+ )}
diff --git a/packages/ui/src/design-system/metadata/metadata.stories.tsx b/packages/ui/src/design-system/metadata/metadata.stories.tsx index a504ca41a2..d91634f4f3 100644 --- a/packages/ui/src/design-system/metadata/metadata.stories.tsx +++ b/packages/ui/src/design-system/metadata/metadata.stories.tsx @@ -42,6 +42,7 @@ const MainComponents = (): JSX.Element => ( diff --git a/packages/ui/src/design-system/tooltip/tooltip-content.css.ts b/packages/ui/src/design-system/tooltip/tooltip-content.css.ts index 29d7cd6999..803e3c84e5 100644 --- a/packages/ui/src/design-system/tooltip/tooltip-content.css.ts +++ b/packages/ui/src/design-system/tooltip/tooltip-content.css.ts @@ -15,5 +15,6 @@ export const tooltipContent = style([ }), { position: 'relative', + wordBreak: 'break-word', }, ]);