From ed2fc6c0f51d99989c068efcb405b7db279d6d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=B3vis=20Neto?= Date: Tue, 24 Dec 2024 10:30:53 +0100 Subject: [PATCH] feat: create account management functionality (#60) * feat: create useSign hook * feat: add react-native-quick-crypto back * feat: create chainsDisplay component to show the grouped chains images * chore: move redux provider to the top of the react three to make the bottomSheet provider able to read values from redux selectors * feat: create badge variant for using the badge component in the chainsDisplay component according to figma * feat: add testIDs in the chainsDisplay component * feat: allow user to provider a footer to the dropdown component * chore: make the logo size dynamically * feat: create AccountCard component * feat: create AccountItem component to handle the dropdown events and specific layout * feat: replace the image component by the chainsDisplay component in the Balances component * feat: change the color of the active chain in the chain selection dropdown * feat: create a footer for MyAccounts dropdown * feat: remove unnecessary tests * feat: add accounts management feature inside the navbar dropdown * feat: create useInfiniteScroll hook to handle infinite scroll functionality * feat: use activeChain information into the tokens container * feat: make the Identicon component to be more extensible * feat: add mocked constants inside the store folder * feat: create safes slice to store all safes added into the app * feat: add possibility to get all supported chains ids and get them also by id * chore: auto generated types * chore: remove unused types * feat: create MyAccounts container * feat: use isFetching instead isLoading to avoid cached result while query is being revalidated * chore: memoize the chains manipulation in the chainsDisplay component * chore: create an useMyAccountsService hook to handle pos-fetch logic outside the container (cherry picked from commit be1a137b30984b8fcc866fd6767824f5f67ec659) --- apps/mobile/.gitignore | 3 +- apps/mobile/app/_layout.tsx | 16 +- apps/mobile/src/components/Badge/Badge.tsx | 5 +- apps/mobile/src/components/Badge/theme.ts | 8 + .../ChainsDisplay/ChainsDisplay.stories.tsx | 45 + .../ChainsDisplay/ChainsDisplay.test.tsx | 30 + .../ChainsDisplay/ChainsDisplay.tsx | 34 + .../src/components/ChainsDisplay/index.ts | 1 + .../src/components/Dropdown/Dropdown.tsx | 28 +- apps/mobile/src/components/Logo/Logo.tsx | 11 +- .../Card/AccountCard/AccountCard.stories.tsx | 46 + .../Card/AccountCard/AccountCard.test.tsx | 45 + .../Card/AccountCard/AccountCard.tsx | 52 + .../Card/AccountCard/index.ts | 1 + apps/mobile/src/config/constants.ts | 6 +- .../AccountItem/AccountItem.stories.tsx | 64 + .../AccountItem/AccountItem.test.tsx | 59 + .../components/AccountItem/AccountItem.tsx | 48 + .../Assets/components/AccountItem/index.ts | 2 + .../components/Balance/Balance.container.tsx | 12 +- .../Assets/components/Balance/Balance.tsx | 9 +- .../Assets/components/Balance/ChainItems.tsx | 2 +- .../MyAccounts/MyAccounts.container.tsx | 46 + .../MyAccounts/MyAccountsFooter.test.tsx | 12 + .../MyAccounts/MyAccountsFooter.tsx | 58 + .../MyAccounts/hooks/useMyAccountsService.ts | 45 + .../Assets/components/MyAccounts/index.ts | 2 + .../Assets/components/NFTs/NFTs.container.tsx | 38 +- .../Assets/components/Navbar/Navbar.tsx | 35 +- .../components/Tokens/Tokens.container.tsx | 12 +- .../IdenticonWithBadge/IdenticonWithBadge.tsx | 10 +- .../src/hooks/useInfiniteScroll/index.ts | 1 + .../useInfiniteScroll/useInfiniteScroll.ts | 39 + apps/mobile/src/hooks/usePendingTxs/index.ts | 40 +- apps/mobile/src/store/activeChainSlice.ts | 3 +- apps/mobile/src/store/activeSafeSlice.ts | 12 +- apps/mobile/src/store/chains/index.ts | 14 + apps/mobile/src/store/constants.ts | 262 +++ apps/mobile/src/store/index.ts | 2 + apps/mobile/src/store/safesSlice.ts | 44 + apps/mobile/src/tests/jest.setup.tsx | 2 + apps/mobile/src/types/address.ts | 5 + apps/mobile/src/utils/formatters.ts | 2 + packages/store/scripts/api-schema/schema.json | 1568 ++++++++++++++--- .../src/gateway/AUTO_GENERATED/chains.ts | 1 + .../gateway/AUTO_GENERATED/notifications.ts | 85 + .../store/src/gateway/AUTO_GENERATED/safes.ts | 4 +- .../gateway/AUTO_GENERATED/transactions.ts | 3 +- packages/store/src/gateway/chains/index.ts | 2 +- 49 files changed, 2491 insertions(+), 383 deletions(-) create mode 100644 apps/mobile/src/components/ChainsDisplay/ChainsDisplay.stories.tsx create mode 100644 apps/mobile/src/components/ChainsDisplay/ChainsDisplay.test.tsx create mode 100644 apps/mobile/src/components/ChainsDisplay/ChainsDisplay.tsx create mode 100644 apps/mobile/src/components/ChainsDisplay/index.ts create mode 100644 apps/mobile/src/components/transactions-list/Card/AccountCard/AccountCard.stories.tsx create mode 100644 apps/mobile/src/components/transactions-list/Card/AccountCard/AccountCard.test.tsx create mode 100644 apps/mobile/src/components/transactions-list/Card/AccountCard/AccountCard.tsx create mode 100644 apps/mobile/src/components/transactions-list/Card/AccountCard/index.ts create mode 100644 apps/mobile/src/features/Assets/components/AccountItem/AccountItem.stories.tsx create mode 100644 apps/mobile/src/features/Assets/components/AccountItem/AccountItem.test.tsx create mode 100644 apps/mobile/src/features/Assets/components/AccountItem/AccountItem.tsx create mode 100644 apps/mobile/src/features/Assets/components/AccountItem/index.ts create mode 100644 apps/mobile/src/features/Assets/components/MyAccounts/MyAccounts.container.tsx create mode 100644 apps/mobile/src/features/Assets/components/MyAccounts/MyAccountsFooter.test.tsx create mode 100644 apps/mobile/src/features/Assets/components/MyAccounts/MyAccountsFooter.tsx create mode 100644 apps/mobile/src/features/Assets/components/MyAccounts/hooks/useMyAccountsService.ts create mode 100644 apps/mobile/src/features/Assets/components/MyAccounts/index.ts create mode 100644 apps/mobile/src/hooks/useInfiniteScroll/index.ts create mode 100644 apps/mobile/src/hooks/useInfiniteScroll/useInfiniteScroll.ts create mode 100644 apps/mobile/src/store/constants.ts create mode 100644 apps/mobile/src/store/safesSlice.ts diff --git a/apps/mobile/.gitignore b/apps/mobile/.gitignore index bb70681e48..91a9f56902 100644 --- a/apps/mobile/.gitignore +++ b/apps/mobile/.gitignore @@ -36,10 +36,11 @@ expo-env.d.ts # Native *.orig.* *. +*.jks *.p8 *.p12 *.key -*. +*.mobileprovision # Metro .metro-health-check* diff --git a/apps/mobile/app/_layout.tsx b/apps/mobile/app/_layout.tsx index 88dc2f152c..027e159c24 100644 --- a/apps/mobile/app/_layout.tsx +++ b/apps/mobile/app/_layout.tsx @@ -26,10 +26,10 @@ function RootLayout() { store.dispatch(apiSliceWithChainsConfig.endpoints.getChainsConfig.initiate()) return ( - - - - + + + + @@ -63,10 +63,10 @@ function RootLayout() { - - - - + + + + ) } diff --git a/apps/mobile/src/components/Badge/Badge.tsx b/apps/mobile/src/components/Badge/Badge.tsx index 4db8b7b31e..b42d9612b8 100644 --- a/apps/mobile/src/components/Badge/Badge.tsx +++ b/apps/mobile/src/components/Badge/Badge.tsx @@ -15,6 +15,7 @@ interface BadgeProps { circleProps?: Partial textContentProps?: Partial circular?: boolean + testID?: string } export const Badge = ({ @@ -25,6 +26,7 @@ export const Badge = ({ circular = true, circleProps, textContentProps, + testID, }: BadgeProps) => { let contentToRender = content if (typeof content === 'string') { @@ -38,7 +40,7 @@ export const Badge = ({ if (circular) { return ( - + {contentToRender} @@ -47,6 +49,7 @@ export const Badge = ({ return ( = { + title: 'ChainsDisplay', + component: ChainsDisplay, + argTypes: {}, +} + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + chains: mockedChains as unknown as Chain[], + max: 3, + }, + parameters: { + layout: 'fullscreen', + }, +} + +export const Truncated: Story = { + args: { + chains: mockedChains as unknown as Chain[], + max: 1, + }, + parameters: { + layout: 'fullscreen', + }, +} + +export const ActiveChain: Story = { + args: { + chains: mockedChains as unknown as Chain[], + activeChainId: mockedChains[1].chainId, + max: 1, + }, + parameters: { + layout: 'fullscreen', + }, +} diff --git a/apps/mobile/src/components/ChainsDisplay/ChainsDisplay.test.tsx b/apps/mobile/src/components/ChainsDisplay/ChainsDisplay.test.tsx new file mode 100644 index 0000000000..29c093dc0b --- /dev/null +++ b/apps/mobile/src/components/ChainsDisplay/ChainsDisplay.test.tsx @@ -0,0 +1,30 @@ +import { mockedChains } from '@/src/store/constants' +import { ChainsDisplay } from './ChainsDisplay' +import { render } from '@testing-library/react-native' +import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains' + +describe('ChainsDisplay', () => { + it('should render all chains next each other', () => { + const container = render() + + expect(container.getAllByTestId('chain-display')).toHaveLength(3) + }) + it('should truncate the chains when the provided chains length is greatter than the max', () => { + const container = render() + const moreChainsBadge = container.getByTestId('more-chains-badge') + + expect(container.getAllByTestId('chain-display')).toHaveLength(2) + expect(moreChainsBadge).toBeVisible() + expect(moreChainsBadge).toHaveTextContent('+1') + }) + + it('should always show the selected chain as the first column of the row', () => { + const container = render( + , + ) + + expect(container.getAllByTestId('chain-display')[0].children[0].props.accessibilityLabel).toBe( + mockedChains[2].chainName, + ) + }) +}) diff --git a/apps/mobile/src/components/ChainsDisplay/ChainsDisplay.tsx b/apps/mobile/src/components/ChainsDisplay/ChainsDisplay.tsx new file mode 100644 index 0000000000..d7a2731ec5 --- /dev/null +++ b/apps/mobile/src/components/ChainsDisplay/ChainsDisplay.tsx @@ -0,0 +1,34 @@ +import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains' +import React, { useMemo } from 'react' +import { View } from 'tamagui' +import { Logo } from '../Logo' +import { Badge } from '../Badge' + +interface ChainsDisplayProps { + chains: Chain[] + max?: number + activeChainId?: string +} + +export function ChainsDisplay({ chains, activeChainId, max }: ChainsDisplayProps) { + const orderedChains = useMemo( + () => [...chains].sort((a, b) => (a.chainId === activeChainId ? -1 : b.chainId === activeChainId ? 1 : 0)), + [chains], + ) + const slicedChains = max ? orderedChains.slice(0, max) : chains + const showBadge = max && chains.length > max + + return ( + + {slicedChains.map(({ chainLogoUri, chainName, chainId }, index) => ( + + + + ))} + + {showBadge && ( + + )} + + ) +} diff --git a/apps/mobile/src/components/ChainsDisplay/index.ts b/apps/mobile/src/components/ChainsDisplay/index.ts new file mode 100644 index 0000000000..5c0ef338de --- /dev/null +++ b/apps/mobile/src/components/ChainsDisplay/index.ts @@ -0,0 +1 @@ +export { ChainsDisplay } from './ChainsDisplay' diff --git a/apps/mobile/src/components/Dropdown/Dropdown.tsx b/apps/mobile/src/components/Dropdown/Dropdown.tsx index 45952f63f2..160673638a 100644 --- a/apps/mobile/src/components/Dropdown/Dropdown.tsx +++ b/apps/mobile/src/components/Dropdown/Dropdown.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useRef } from 'react' -import { H5, ScrollView, Text, View } from 'tamagui' +import { GetThemeValueForKey, H5, ScrollView, Text, View } from 'tamagui' import { SafeFontIcon } from '@/src/components/SafeFontIcon/SafeFontIcon' -import { BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet' +import { BottomSheetFooterProps, BottomSheetModal, BottomSheetModalProps, BottomSheetView } from '@gorhom/bottom-sheet' import { StyleSheet } from 'react-native' import { BackdropComponent, BackgroundComponent } from './sheetComponents' @@ -11,18 +11,32 @@ interface DropdownProps { children?: React.ReactNode dropdownTitle?: string items?: T[] + snapPoints?: BottomSheetModalProps['snapPoints'] + labelProps?: { + fontSize?: '$4' | '$5' | GetThemeValueForKey<'fontSize'> + fontWeight: 400 | 500 | 600 + } + footerComponent?: React.FC renderItem?: React.FC<{ item: T; onClose: () => void }> keyExtractor?: ({ item, index }: { item: T; index: number }) => string } +const defaultLabelProps = { + fontSize: '$4', + fontWeight: 400, +} as const + export function Dropdown({ label, leftNode, children, dropdownTitle, items, + snapPoints = [600, '90%'], keyExtractor, renderItem: Render, + labelProps = defaultLabelProps, + footerComponent, }: DropdownProps) { const bottomSheetModalRef = useRef(null) @@ -44,10 +58,11 @@ export function Dropdown({ onPress={handlePresentModalPress} flexDirection="row" marginBottom="$3" + columnGap="$2" > {leftNode} - + {label} @@ -56,19 +71,20 @@ export function Dropdown({ {dropdownTitle && ( -
+
{dropdownTitle}
)} @@ -95,6 +111,6 @@ export function Dropdown({ const styles = StyleSheet.create({ contentContainer: { paddingHorizontal: 20, - flex: 1, + justifyContent: 'space-around', }, }) diff --git a/apps/mobile/src/components/Logo/Logo.tsx b/apps/mobile/src/components/Logo/Logo.tsx index 9409ac759d..15bc1a4274 100644 --- a/apps/mobile/src/components/Logo/Logo.tsx +++ b/apps/mobile/src/components/Logo/Logo.tsx @@ -7,12 +7,19 @@ interface LogoProps { accessibilityLabel?: string fallbackIcon?: IconProps['name'] imageBackground?: string + size?: string } -export function Logo({ logoUri, accessibilityLabel, imageBackground = '$color', fallbackIcon = 'nft' }: LogoProps) { +export function Logo({ + logoUri, + accessibilityLabel, + size = '$10', + imageBackground = '$color', + fallbackIcon = 'nft', +}: LogoProps) { return ( - + {logoUri && ( = { + title: 'TransactionsList/AccountCard', + component: AccountCard, + argTypes: {}, +} + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + name: 'This is my account', + chains: mockedChains as unknown as Chain[], + owners: 5, + balance: mockedActiveSafeInfo.fiatTotal, + address: mockedActiveSafeInfo.address.value as Address, + threshold: 2, + }, + parameters: { + layout: 'fullscreen', + }, + render: ({ ...args }) => } />, +} + +export const TruncatedAccount: Story = { + args: { + name: 'This is my account with a very long text in one more test', + chains: mockedChains as unknown as Chain[], + owners: 5, + balance: mockedActiveSafeInfo.fiatTotal, + address: mockedActiveSafeInfo.address.value as Address, + threshold: 2, + }, + parameters: { + layout: 'fullscreen', + }, + render: ({ ...args }) => } />, +} diff --git a/apps/mobile/src/components/transactions-list/Card/AccountCard/AccountCard.test.tsx b/apps/mobile/src/components/transactions-list/Card/AccountCard/AccountCard.test.tsx new file mode 100644 index 0000000000..3a5243a62e --- /dev/null +++ b/apps/mobile/src/components/transactions-list/Card/AccountCard/AccountCard.test.tsx @@ -0,0 +1,45 @@ +import { render } from '@/src/tests/test-utils' +import { AccountCard } from './AccountCard' +import { mockedActiveSafeInfo, mockedChains } from '@/src/store/constants' +import { Address } from '@/src/types/address' +import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains' +import { ellipsis } from '@/src/utils/formatters' + +describe('AccountCard', () => { + it('should render the account card with only one chain provided', () => { + const accountName = 'This is my account' + const container = render( + , + ) + expect(container.getByTestId('threshold-info-badge')).toBeVisible() + expect(container.getByText('2/5')).toBeDefined() + expect(container.getByText(`$${mockedActiveSafeInfo.fiatTotal}`)).toBeDefined() + expect(container.getByText(accountName)).toBeDefined() + }) + + it('should truncate the account information when they are very long', () => { + const longAccountName = 'This is my account with a very very long text' + const longBalance = '21312321312213213121221312321312312' + const container = render( + , + ) + expect(container.getByTestId('threshold-info-badge')).toBeVisible() + expect(container.getByText('2/5')).toBeDefined() + expect(container.getByText(`$${ellipsis(longBalance, 14)}`)).toBeDefined() + expect(container.getByText(ellipsis(longAccountName, 18))).toBeDefined() + }) +}) diff --git a/apps/mobile/src/components/transactions-list/Card/AccountCard/AccountCard.tsx b/apps/mobile/src/components/transactions-list/Card/AccountCard/AccountCard.tsx new file mode 100644 index 0000000000..7409501c45 --- /dev/null +++ b/apps/mobile/src/components/transactions-list/Card/AccountCard/AccountCard.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import { Text, View } from 'tamagui' +import { SafeListItem } from '@/src/components/SafeListItem' +import { ellipsis } from '@/src/utils/formatters' +import { IdenticonWithBadge } from '@/src/features/Settings/components/IdenticonWithBadge' +import { Address } from '@/src/types/address' +import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains' +import { ChainsDisplay } from '@/src/components/ChainsDisplay' + +interface AccountCardProps { + name: string | Address + balance: string + address: Address + owners: number + threshold: number + rightNode?: string | React.ReactNode + chains: Chain[] +} + +export function AccountCard({ name, chains, owners, balance, address, threshold, rightNode }: AccountCardProps) { + return ( + + + {ellipsis(name, 18)} + + + ${ellipsis(balance, 14)} + +
+ } + leftNode={ + + + + } + rightNode={ + + + {rightNode} + + } + transparent + /> + ) +} diff --git a/apps/mobile/src/components/transactions-list/Card/AccountCard/index.ts b/apps/mobile/src/components/transactions-list/Card/AccountCard/index.ts new file mode 100644 index 0000000000..d692abb08d --- /dev/null +++ b/apps/mobile/src/components/transactions-list/Card/AccountCard/index.ts @@ -0,0 +1 @@ +export { AccountCard } from './AccountCard' diff --git a/apps/mobile/src/config/constants.ts b/apps/mobile/src/config/constants.ts index 47b9bee8ee..158774b6b7 100644 --- a/apps/mobile/src/config/constants.ts +++ b/apps/mobile/src/config/constants.ts @@ -1,7 +1,9 @@ import Constants from 'expo-constants' import { Platform } from 'react-native' -export const isProduction = process.env.NODE_ENV !== 'production' +// export const isProduction = process.env.NODE_ENV === 'production' +// TODO: put it to get from process.env.NODE_ENV once we remove the mocks for the user account. +export const isProduction = true export const isAndroid = Platform.OS === 'android' export const isTestingEnv = process.env.NODE_ENV === 'test' export const isStorybookEnv = Constants?.expoConfig?.extra?.storybookEnabled === 'true' @@ -10,4 +12,4 @@ export const POLLING_INTERVAL = 15_000 export const GATEWAY_URL_PRODUCTION = process.env.NEXT_PUBLIC_GATEWAY_URL_PRODUCTION || 'https://safe-client.safe.global' export const GATEWAY_URL_STAGING = process.env.NEXT_PUBLIC_GATEWAY_URL_STAGING || 'https://safe-client.staging.5afe.dev' -export const GATEWAY_URL = process.env.NODE_ENV !== 'production' ? GATEWAY_URL_STAGING : GATEWAY_URL_PRODUCTION +export const GATEWAY_URL = isProduction ? GATEWAY_URL_PRODUCTION : GATEWAY_URL_STAGING diff --git a/apps/mobile/src/features/Assets/components/AccountItem/AccountItem.stories.tsx b/apps/mobile/src/features/Assets/components/AccountItem/AccountItem.stories.tsx new file mode 100644 index 0000000000..26b24209d4 --- /dev/null +++ b/apps/mobile/src/features/Assets/components/AccountItem/AccountItem.stories.tsx @@ -0,0 +1,64 @@ +import { AccountItem } from './AccountItem' +import { Meta, StoryObj } from '@storybook/react/*' +import { mockedActiveSafeInfo, mockedChains } from '@/src/store/constants' +import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains' +import { action } from '@storybook/addon-actions' +import { Address } from '@/src/types/address' + +const meta: Meta = { + title: 'Assets/AccountItem', + component: AccountItem, + argTypes: {}, +} + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + account: mockedActiveSafeInfo, + chains: mockedChains as unknown as Chain[], + activeAccount: '0x123', + onSelect: action('onSelect'), + }, + parameters: { + layout: 'fullscreen', + }, +} + +export const ActiveAccount: Story = { + args: { + account: mockedActiveSafeInfo, + chains: mockedChains as unknown as Chain[], + activeAccount: mockedActiveSafeInfo.address.value as Address, + onSelect: action('onSelect'), + }, + parameters: { + layout: 'fullscreen', + }, +} + +export const TruncatedAccountChains: Story = { + args: { + account: mockedActiveSafeInfo, + chains: [...mockedChains, ...mockedChains, ...mockedChains] as unknown as Chain[], + activeAccount: '0x12312', + onSelect: action('onSelect'), + }, + parameters: { + layout: 'fullscreen', + }, +} + +export const TruncatedActiveAccountChains: Story = { + args: { + account: mockedActiveSafeInfo, + chains: [...mockedChains, ...mockedChains, ...mockedChains] as unknown as Chain[], + activeAccount: mockedActiveSafeInfo.address.value as Address, + onSelect: action('onSelect'), + }, + parameters: { + layout: 'fullscreen', + }, +} diff --git a/apps/mobile/src/features/Assets/components/AccountItem/AccountItem.test.tsx b/apps/mobile/src/features/Assets/components/AccountItem/AccountItem.test.tsx new file mode 100644 index 0000000000..9f3573e921 --- /dev/null +++ b/apps/mobile/src/features/Assets/components/AccountItem/AccountItem.test.tsx @@ -0,0 +1,59 @@ +import { render, userEvent } from '@/src/tests/test-utils' +import AccountItem from './AccountItem' +import { mockedActiveSafeInfo, mockedChains } from '@/src/store/constants' +import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains' +import { shortenAddress } from '@/src/utils/formatters' +import { Address } from '@/src/types/address' + +describe('AccountItem', () => { + it('should render a unselected AccountItem', () => { + const container = render( + , + ) + + expect(container.getByTestId('account-item-wrapper')).toHaveStyle({ backgroundColor: 'transparent' }) + expect(container.getByText(shortenAddress(mockedActiveSafeInfo.address.value))).toBeDefined() + expect(container.getByText(`${mockedActiveSafeInfo.threshold}/${mockedActiveSafeInfo.owners.length}`)).toBeDefined() + expect(container.getByText(`$${mockedActiveSafeInfo.fiatTotal}`)).toBeVisible() + expect(container.getAllByTestId('chain-display')).toHaveLength(mockedChains.length) + }) + + it('should render a selected AccountItem', () => { + const container = render( + , + ) + + expect(container.getByTestId('account-item-wrapper')).toHaveStyle({ backgroundColor: '#DCDEE0' }) + expect(container.getByText(shortenAddress(mockedActiveSafeInfo.address.value))).toBeDefined() + expect(container.getByText(`${mockedActiveSafeInfo.threshold}/${mockedActiveSafeInfo.owners.length}`)).toBeDefined() + expect(container.getByText(`$${mockedActiveSafeInfo.fiatTotal}`)).toBeVisible() + expect(container.getAllByTestId('chain-display')).toHaveLength(mockedChains.length) + }) + + it('should trigger an event when user clicks in the account item', async () => { + const spyFn = jest.fn() + const user = userEvent.setup() + const container = render( + , + ) + + await user.press(container.getByTestId('account-item-wrapper')) + + expect(spyFn).toHaveBeenNthCalledWith(1, mockedActiveSafeInfo.address.value) + }) +}) diff --git a/apps/mobile/src/features/Assets/components/AccountItem/AccountItem.tsx b/apps/mobile/src/features/Assets/components/AccountItem/AccountItem.tsx new file mode 100644 index 0000000000..2d3c2d7cec --- /dev/null +++ b/apps/mobile/src/features/Assets/components/AccountItem/AccountItem.tsx @@ -0,0 +1,48 @@ +import React from 'react' +import { TouchableOpacity } from 'react-native' +import { View } from 'tamagui' +import { SafeFontIcon } from '@/src/components/SafeFontIcon' +import { AccountCard } from '@/src/components/transactions-list/Card/AccountCard' +import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains' +import { Address } from '@/src/types/address' +import { SafeOverview } from '@safe-global/store/gateway/AUTO_GENERATED/safes' +import { shortenAddress } from '@/src/utils/formatters' + +interface AccountItemProps { + chains: Chain[] + account: SafeOverview + activeAccount: Address + onSelect: (accountAddress: string) => void +} + +// TODO: These props needs to come from the AccountItem.container component +// remove this comment once it is done +export function AccountItem({ account, chains, activeAccount, onSelect }: AccountItemProps) { + const isActive = activeAccount === account.address.value + + const handleChainSelect = () => { + onSelect(account.address.value) + } + + return ( + + + } + /> + + + ) +} + +export default AccountItem diff --git a/apps/mobile/src/features/Assets/components/AccountItem/index.ts b/apps/mobile/src/features/Assets/components/AccountItem/index.ts new file mode 100644 index 0000000000..1a0911a316 --- /dev/null +++ b/apps/mobile/src/features/Assets/components/AccountItem/index.ts @@ -0,0 +1,2 @@ +import { AccountItem } from './AccountItem' +export { AccountItem } diff --git a/apps/mobile/src/features/Assets/components/Balance/Balance.container.tsx b/apps/mobile/src/features/Assets/components/Balance/Balance.container.tsx index f9997d588c..23c8b5cb9c 100644 --- a/apps/mobile/src/features/Assets/components/Balance/Balance.container.tsx +++ b/apps/mobile/src/features/Assets/components/Balance/Balance.container.tsx @@ -4,16 +4,20 @@ import { useSafesGetSafeOverviewV1Query } from '@safe-global/store/gateway/AUTO_ import { selectActiveSafe } from '@/src/store/activeSafeSlice' import { SafeOverviewResult } from '@safe-global/store/gateway/types' import { POLLING_INTERVAL } from '@/src/config/constants' -import { selectAllChains } from '@/src/store/chains' +import { getChainsByIds, selectAllChains } from '@/src/store/chains' import { Balance } from './Balance' - -const makeSafeId = (chainId: string, address: string) => `${chainId}:${address}` as `${number}:0x${string}` +import { makeSafeId } from '@/src/utils/formatters' +import { RootState } from '@/src/store' +import { selectActiveSafeInfo } from '@/src/store/safesSlice' export function BalanceContainer() { const activeChain = useSelector(selectActiveChain) const chains = useSelector(selectAllChains) const activeSafe = useSelector(selectActiveSafe) const dispatch = useDispatch() + const activeSafeInfo = useSelector((state: RootState) => selectActiveSafeInfo(state, activeSafe.address)) + const activeSafeChains = useSelector((state: RootState) => getChainsByIds(state, activeSafeInfo.chains)) + const { data, isLoading } = useSafesGetSafeOverviewV1Query( { safes: chains.map((chain) => makeSafeId(chain.chainId, activeSafe.address)).join(','), @@ -34,7 +38,7 @@ export function BalanceContainer() { return ( label={activeChain?.chainName} dropdownTitle="Select network:" - leftNode={ - activeChain?.chainLogoUri && ( - - ) - } + leftNode={} items={data} keyExtractor={({ item }) => item.chainId} renderItem={({ item, onClose }) => ( diff --git a/apps/mobile/src/features/Assets/components/Balance/ChainItems.tsx b/apps/mobile/src/features/Assets/components/Balance/ChainItems.tsx index 2bf3fd6514..14af89b06f 100644 --- a/apps/mobile/src/features/Assets/components/Balance/ChainItems.tsx +++ b/apps/mobile/src/features/Assets/components/Balance/ChainItems.tsx @@ -32,7 +32,7 @@ export function ChainItems({ chainId, chains, activeChain, fiatTotal, onSelect } name={chain.chainName} logoUri={chain.chainLogoUri} description={`${fiatTotal}`} - rightNode={isActive && } + rightNode={isActive && } />
diff --git a/apps/mobile/src/features/Assets/components/MyAccounts/MyAccounts.container.tsx b/apps/mobile/src/features/Assets/components/MyAccounts/MyAccounts.container.tsx new file mode 100644 index 0000000000..aac4ed881d --- /dev/null +++ b/apps/mobile/src/features/Assets/components/MyAccounts/MyAccounts.container.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import { AccountItem } from '../AccountItem' +import { SafesSliceItem } from '@/src/store/safesSlice' +import { Address } from '@/src/types/address' +import { useDispatch, useSelector } from 'react-redux' +import { selectActiveSafe, setActiveSafe } from '@/src/store/activeSafeSlice' +import { getChainsByIds } from '@/src/store/chains' +import { RootState } from '@/src/store' +import { switchActiveChain } from '@/src/store/activeChainSlice' +import { useMyAccountsService } from './hooks/useMyAccountsService' + +interface MyAccountsContainerProps { + item: SafesSliceItem + onClose: () => void +} + +export function MyAccountsContainer({ item, onClose }: MyAccountsContainerProps) { + useMyAccountsService(item) + + const dispatch = useDispatch() + const activeSafe = useSelector(selectActiveSafe) + const filteredChains = useSelector((state: RootState) => getChainsByIds(state, item.chains)) + + const handleAccountSelected = () => { + const chainId = item.chains[0] + + dispatch( + setActiveSafe({ + address: item.SafeInfo.address.value as Address, + chainId, + }), + ) + dispatch(switchActiveChain({ id: chainId })) + + onClose() + } + + return ( + + ) +} diff --git a/apps/mobile/src/features/Assets/components/MyAccounts/MyAccountsFooter.test.tsx b/apps/mobile/src/features/Assets/components/MyAccounts/MyAccountsFooter.test.tsx new file mode 100644 index 0000000000..76b422a0ff --- /dev/null +++ b/apps/mobile/src/features/Assets/components/MyAccounts/MyAccountsFooter.test.tsx @@ -0,0 +1,12 @@ +import { render } from '@/src/tests/test-utils' +import { MyAccountsFooter } from './MyAccountsFooter' +import { SharedValue } from 'react-native-reanimated' + +describe('MyAccountsFooter', () => { + it('should render the defualt template', () => { + const container = render(} />) + + expect(container.getByText('Add Existing Account')).toBeDefined() + expect(container.getByText('Join New Account')).toBeDefined() + }) +}) diff --git a/apps/mobile/src/features/Assets/components/MyAccounts/MyAccountsFooter.tsx b/apps/mobile/src/features/Assets/components/MyAccounts/MyAccountsFooter.tsx new file mode 100644 index 0000000000..7d930ce395 --- /dev/null +++ b/apps/mobile/src/features/Assets/components/MyAccounts/MyAccountsFooter.tsx @@ -0,0 +1,58 @@ +import { Badge } from '@/src/components/Badge' +import { SafeFontIcon } from '@/src/components/SafeFontIcon' +import { BottomSheetFooter, BottomSheetFooterProps } from '@gorhom/bottom-sheet' +import React from 'react' +import { TouchableOpacity } from 'react-native' +import { styled, Text, View } from 'tamagui' + +const MyAccountsFooterContainer = styled(View, { + borderTopWidth: 1, + borderTopColor: '$colorSecondary', + paddingVertical: '$7', + paddingHorizontal: '$5', + backgroundColor: '$backgroundPaper', +}) + +const MyAccountsButton = styled(View, { + columnGap: '$3', + alignItems: 'center', + flexDirection: 'row', + marginBottom: '$7', +}) + +interface CustomFooterProps extends BottomSheetFooterProps {} + +export function MyAccountsFooter({ animatedFooterPosition }: CustomFooterProps) { + const onAddAccountClick = () => null + const onJoinAccountClick = () => null + + return ( + + + + + } + /> + + + Add Existing Account + + + + + + + } /> + + + Join New Account + + + + + + ) +} diff --git a/apps/mobile/src/features/Assets/components/MyAccounts/hooks/useMyAccountsService.ts b/apps/mobile/src/features/Assets/components/MyAccounts/hooks/useMyAccountsService.ts new file mode 100644 index 0000000000..d590807f76 --- /dev/null +++ b/apps/mobile/src/features/Assets/components/MyAccounts/hooks/useMyAccountsService.ts @@ -0,0 +1,45 @@ +import { useSafesGetSafeOverviewV1Query } from '@safe-global/store/gateway/AUTO_GENERATED/safes' +import { SafeOverviewResult } from '@safe-global/store/gateway/types' +import { useEffect, useMemo } from 'react' +import { useDispatch, useSelector } from 'react-redux' + +import { selectAllChainsIds } from '@/src/store/chains' +import { SafesSliceItem, updateSafeInfo } from '@/src/store/safesSlice' +import { Address } from '@/src/types/address' +import { makeSafeId } from '@/src/utils/formatters' + +export const useMyAccountsService = (item: SafesSliceItem) => { + const dispatch = useDispatch() + const chainIds = useSelector(selectAllChainsIds) + const safes = useMemo( + () => chainIds.map((chainId: string) => makeSafeId(chainId, item.SafeInfo.address.value)).join(','), + [chainIds, item.SafeInfo.address.value], + ) + const { data } = useSafesGetSafeOverviewV1Query({ + safes, + currency: 'usd', + trusted: true, + excludeSpam: true, + }) + + useEffect(() => { + if (!data) { + return + } + + const safe = data[0] + + dispatch( + updateSafeInfo({ + address: safe.address.value as Address, + item: { + chains: data.map((safeInfo) => safeInfo.chainId), + SafeInfo: { + ...safe, + fiatTotal: data.reduce((prev, { fiatTotal }) => parseFloat(fiatTotal) + prev, 0).toString(), + }, + }, + }), + ) + }, [data, dispatch]) +} diff --git a/apps/mobile/src/features/Assets/components/MyAccounts/index.ts b/apps/mobile/src/features/Assets/components/MyAccounts/index.ts new file mode 100644 index 0000000000..e1d3d5f3b1 --- /dev/null +++ b/apps/mobile/src/features/Assets/components/MyAccounts/index.ts @@ -0,0 +1,2 @@ +export { MyAccountsFooter } from './MyAccountsFooter' +export { MyAccountsContainer } from './MyAccounts.container' diff --git a/apps/mobile/src/features/Assets/components/NFTs/NFTs.container.tsx b/apps/mobile/src/features/Assets/components/NFTs/NFTs.container.tsx index 6ee3ca8e13..d201de1cfe 100644 --- a/apps/mobile/src/features/Assets/components/NFTs/NFTs.container.tsx +++ b/apps/mobile/src/features/Assets/components/NFTs/NFTs.container.tsx @@ -1,5 +1,5 @@ import { safelyDecodeURIComponent } from 'expo-router/build/fork/getStateFromPath-forks' -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' import { useSelector } from 'react-redux' import { SafeTab } from '@/src/components/SafeTab' @@ -13,15 +13,17 @@ import { import { Fallback } from '../Fallback' import { NFTItem } from './NFTItem' +import { selectActiveChain } from '@/src/store/activeChainSlice' +import { useInfiniteScroll } from '@/src/hooks/useInfiniteScroll' export function NFTsContainer() { + const activeChain = useSelector(selectActiveChain) const activeSafe = useSelector(selectActiveSafe) const [pageUrl, setPageUrl] = useState() - const [list, setList] = useState() - const { data, isLoading, error, refetch } = useCollectiblesGetCollectiblesV2Query( + const { data, isFetching, error, refetch } = useCollectiblesGetCollectiblesV2Query( { - chainId: activeSafe.chainId, + chainId: activeChain.chainId, safeAddress: activeSafe.address, cursor: pageUrl && safelyDecodeURIComponent(pageUrl?.split('cursor=')[1]), }, @@ -29,26 +31,14 @@ export function NFTsContainer() { pollingInterval: POLLING_INTERVAL, }, ) - - useEffect(() => { - if (!data?.results) { - return - } - - setList((prev) => (prev ? [...prev, ...data.results] : data.results)) - }, [data]) - - const onEndReached = () => { - if (!data?.next) { - return - } - - setPageUrl(data.next) - refetch() - } - - if (isLoading || !list?.length || error) { - return + const { list, onEndReached } = useInfiniteScroll({ + refetch, + setPageUrl, + data, + }) + + if (isFetching || !list?.length || error) { + return } return ( diff --git a/apps/mobile/src/features/Assets/components/Navbar/Navbar.tsx b/apps/mobile/src/features/Assets/components/Navbar/Navbar.tsx index 0700470f2e..f67f461720 100644 --- a/apps/mobile/src/features/Assets/components/Navbar/Navbar.tsx +++ b/apps/mobile/src/features/Assets/components/Navbar/Navbar.tsx @@ -1,31 +1,42 @@ import { useSelector } from 'react-redux' import { selectActiveSafe } from '@/src/store/activeSafeSlice' -import { Text, View } from 'tamagui' +import { View } from 'tamagui' import { BlurredIdenticonBackground } from '@/src/components/BlurredIdenticonBackground' import { SafeAreaView } from 'react-native-safe-area-context' import { Identicon } from '@/src/components/Identicon' import { shortenAddress } from '@/src/utils/formatters' import { SafeFontIcon } from '@/src/components/SafeFontIcon' import { StyleSheet, TouchableOpacity } from 'react-native' -import React from 'react' +import React, { useMemo } from 'react' import { Address } from '@/src/types/address' +import { Dropdown } from '@/src/components/Dropdown' +import { MyAccountsContainer, MyAccountsFooter } from '../MyAccounts' +import { SafesSliceItem, selectAllSafes } from '@/src/store/safesSlice' + +const dropdownLabelProps = { + fontSize: '$5', + fontWeight: 600, +} as const export const Navbar = () => { const activeSafe = useSelector(selectActiveSafe) + const safes = useSelector(selectAllSafes) + const memoizedSafes = useMemo(() => Object.values(safes), [safes]) + return ( - - - - - - {shortenAddress(activeSafe.address)} - - - - + + label={shortenAddress(activeSafe.address)} + labelProps={dropdownLabelProps} + dropdownTitle="My accounts" + leftNode={} + items={memoizedSafes} + keyExtractor={({ item }) => item.SafeInfo.address.value} + footerComponent={MyAccountsFooter} + renderItem={MyAccountsContainer} + /> diff --git a/apps/mobile/src/features/Assets/components/Tokens/Tokens.container.tsx b/apps/mobile/src/features/Assets/components/Tokens/Tokens.container.tsx index bba93e7497..29b5ca90c5 100644 --- a/apps/mobile/src/features/Assets/components/Tokens/Tokens.container.tsx +++ b/apps/mobile/src/features/Assets/components/Tokens/Tokens.container.tsx @@ -9,17 +9,17 @@ import { POLLING_INTERVAL } from '@/src/config/constants' import { selectActiveSafe } from '@/src/store/activeSafeSlice' import { Balance, useBalancesGetBalancesV1Query } from '@safe-global/store/gateway/AUTO_GENERATED/balances' import { formatValue } from '@/src/utils/formatters' -// import { selectActiveChain } from '@/src/store/activeChainSlice' +import { selectActiveChain } from '@/src/store/activeChainSlice' import { Fallback } from '../Fallback' export function TokensContainer() { const activeSafe = useSelector(selectActiveSafe) - // const activeChain = useSelector(selectActiveChain) + const activeChain = useSelector(selectActiveChain) - const { data, isLoading, error } = useBalancesGetBalancesV1Query( + const { data, isFetching, error } = useBalancesGetBalancesV1Query( { - chainId: activeSafe.chainId, + chainId: activeChain.chainId, fiatCode: 'USD', safeAddress: activeSafe.address, excludeSpam: false, @@ -45,8 +45,8 @@ export function TokensContainer() { ) }, []) - if (isLoading || !data?.items.length || error) { - return + if (isFetching || !data?.items.length || error) { + return } return ( diff --git a/apps/mobile/src/features/Settings/components/IdenticonWithBadge/IdenticonWithBadge.tsx b/apps/mobile/src/features/Settings/components/IdenticonWithBadge/IdenticonWithBadge.tsx index 43052e96d8..7e4ed2c009 100644 --- a/apps/mobile/src/features/Settings/components/IdenticonWithBadge/IdenticonWithBadge.tsx +++ b/apps/mobile/src/features/Settings/components/IdenticonWithBadge/IdenticonWithBadge.tsx @@ -6,15 +6,17 @@ import React from 'react' import { StyleSheet } from 'react-native' import { Address } from '@/src/types/address' -type Props = { +type IdenticonWithBadgeProps = { address: Address badgeContent?: string + size?: number + testID?: string } -export const IdenticonWithBadge = ({ address, badgeContent }: Props) => { +export const IdenticonWithBadge = ({ address, testID, badgeContent, size = 56 }: IdenticonWithBadgeProps) => { return ( - - + + {badgeContent && ( diff --git a/apps/mobile/src/hooks/useInfiniteScroll/index.ts b/apps/mobile/src/hooks/useInfiniteScroll/index.ts new file mode 100644 index 0000000000..02e13ff6f4 --- /dev/null +++ b/apps/mobile/src/hooks/useInfiniteScroll/index.ts @@ -0,0 +1 @@ +export { useInfiniteScroll } from './useInfiniteScroll' diff --git a/apps/mobile/src/hooks/useInfiniteScroll/useInfiniteScroll.ts b/apps/mobile/src/hooks/useInfiniteScroll/useInfiniteScroll.ts new file mode 100644 index 0000000000..fc2290b3e1 --- /dev/null +++ b/apps/mobile/src/hooks/useInfiniteScroll/useInfiniteScroll.ts @@ -0,0 +1,39 @@ +import { selectActiveSafe } from '@/src/store/activeSafeSlice' +import { useCallback, useEffect, useState } from 'react' +import { useSelector } from 'react-redux' + +type TUseInfiniteScrollData = { results: J[]; next?: string | null } + +type TUseInfiniteScrollConfig = { + refetch: () => void + setPageUrl: (nextUrl?: string) => void + data: (T & TUseInfiniteScrollData) | undefined +} + +export const useInfiniteScroll = ({ refetch, setPageUrl, data }: TUseInfiniteScrollConfig) => { + const activeSafe = useSelector(selectActiveSafe) + const [list, setList] = useState([]) + + useEffect(() => { + setList([]) + }, [activeSafe]) + + useEffect(() => { + if (!data?.results) { + return + } + + setList((prev) => (prev ? [...prev, ...data.results] : data.results)) + }, [data]) + + const onEndReached = useCallback(() => { + if (!data?.next) { + return + } + + setPageUrl(data.next) + refetch() + }, [data, refetch, setPageUrl]) + + return { list, onEndReached } +} diff --git a/apps/mobile/src/hooks/usePendingTxs/index.ts b/apps/mobile/src/hooks/usePendingTxs/index.ts index ec803e2a06..16ac8c0d78 100644 --- a/apps/mobile/src/hooks/usePendingTxs/index.ts +++ b/apps/mobile/src/hooks/usePendingTxs/index.ts @@ -1,43 +1,39 @@ import { useGetPendingTxsQuery } from '@safe-global/store/gateway' -import { useEffect, useMemo, useState } from 'react' +import { useMemo, useState } from 'react' import { useSelector } from 'react-redux' -import { QueuedItemPage } from '@safe-global/store/gateway/AUTO_GENERATED/transactions' +import { + ConflictHeaderQueuedItem, + LabelQueuedItem, + QueuedItemPage, + TransactionQueuedItem, +} from '@safe-global/store/gateway/AUTO_GENERATED/transactions' import { groupPendingTxs } from '@/src/features/PendingTx/utils' import { selectActiveSafe } from '@/src/store/activeSafeSlice' +import { safelyDecodeURIComponent } from 'expo-router/build/fork/getStateFromPath-forks' +import { useInfiniteScroll } from '../useInfiniteScroll' const usePendingTxs = () => { const activeSafe = useSelector(selectActiveSafe) - const [list, setList] = useState([]) const [pageUrl, setPageUrl] = useState() const { data, isLoading, isFetching, refetch, isUninitialized } = useGetPendingTxsQuery( { chainId: activeSafe.chainId, safeAddress: activeSafe.address, - cursor: pageUrl, + cursor: pageUrl && safelyDecodeURIComponent(pageUrl?.split('cursor=')[1]), }, { skip: !activeSafe.chainId, }, ) - - useEffect(() => { - if (!data?.results) { - return - } - - setList((prev) => [...prev, ...data.results]) - }, [data]) - - const fetchMoreTx = async () => { - if (!data?.next) { - return - } - - setPageUrl(data.next) - - refetch() - } + const { list, onEndReached: fetchMoreTx } = useInfiniteScroll< + QueuedItemPage, + ConflictHeaderQueuedItem | LabelQueuedItem | TransactionQueuedItem + >({ + refetch, + setPageUrl, + data, + }) const pendingTxs = useMemo(() => groupPendingTxs(list || []), [list]) diff --git a/apps/mobile/src/store/activeChainSlice.ts b/apps/mobile/src/store/activeChainSlice.ts index fc5dc964e6..db064e6a39 100644 --- a/apps/mobile/src/store/activeChainSlice.ts +++ b/apps/mobile/src/store/activeChainSlice.ts @@ -1,8 +1,9 @@ import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit' import { RootState } from '.' import { selectChainById } from './chains' +import { mockedActiveAccount } from './constants' -const initialState = { id: '1' } +const initialState = { id: mockedActiveAccount.chainId } const activeChainSlice = createSlice({ name: 'activeChain', diff --git a/apps/mobile/src/store/activeSafeSlice.ts b/apps/mobile/src/store/activeSafeSlice.ts index f600545480..adaf98cb14 100644 --- a/apps/mobile/src/store/activeSafeSlice.ts +++ b/apps/mobile/src/store/activeSafeSlice.ts @@ -1,15 +1,11 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import { Address } from '@/src/types/address' import { RootState } from '.' - -interface SafeInfo { - address: Address - chainId: string -} +import { mockedActiveAccount } from './constants' +import { SafeInfo } from '../types/address' const initialState: SafeInfo = { - address: '0xA77DE01e157f9f57C7c4A326eeE9C4874D0598b6', - chainId: '1', + address: mockedActiveAccount.address, + chainId: mockedActiveAccount.chainId, } const activeSafeSlice = createSlice({ diff --git a/apps/mobile/src/store/chains/index.ts b/apps/mobile/src/store/chains/index.ts index ed675e21b7..5872031662 100644 --- a/apps/mobile/src/store/chains/index.ts +++ b/apps/mobile/src/store/chains/index.ts @@ -1,6 +1,7 @@ import { apiSliceWithChainsConfig, chainsAdapter, initialState } from '@safe-global/store/gateway/chains' import { createSelector } from '@reduxjs/toolkit' import { RootState } from '..' +import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains' const selectChainsResult = apiSliceWithChainsConfig.endpoints.getChainsConfig.select() @@ -11,5 +12,18 @@ const selectChainsData = createSelector(selectChainsResult, (result) => { const { selectAll: selectAllChains, selectById } = chainsAdapter.getSelectors(selectChainsData) export const selectChainById = (state: RootState, chainId: string) => selectById(state, chainId) +export const selectAllChainsIds = createSelector([selectAllChains], (chains: Chain[]) => + chains.map((chain) => chain.chainId), +) + +export const getChainsByIds = createSelector( + [ + // Pass the root state and chainIds array as dependencies + (state: RootState) => state, + (_state: RootState, chainIds: string[]) => chainIds, + ], + (state, chainIds) => chainIds.map((chainId) => selectById(state, chainId)), +) + export const { useGetChainsConfigQuery } = apiSliceWithChainsConfig export { selectAllChains } diff --git a/apps/mobile/src/store/constants.ts b/apps/mobile/src/store/constants.ts new file mode 100644 index 0000000000..c71e0af9e3 --- /dev/null +++ b/apps/mobile/src/store/constants.ts @@ -0,0 +1,262 @@ +import { SafeOverview } from '@safe-global/store/gateway/AUTO_GENERATED/safes' +import { SafeInfo } from '../types/address' + +export const mockedActiveAccount: SafeInfo = { + address: '0xA77DE01e157f9f57C7c4A326eeE9C4874D0598b6', + chainId: '1', +} + +export const mockedActiveSafeInfo: SafeOverview = { + address: { value: '0xA77DE01e157f9f57C7c4A326eeE9C4874D0598b6', name: null, logoUri: null }, + awaitingConfirmation: null, + chainId: mockedActiveAccount.chainId, + fiatTotal: '758.926', + owners: [{ value: '0xA77DE01e157f9f57C7c4A326eeE9C4874D0598b6', name: null, logoUri: null }], + queued: 1, + threshold: 1, +} + +export const mockedAccounts = [ + mockedActiveSafeInfo, + { + address: { value: '0xc7c2E116A3027D0BFd9817781c717A81a8bC5518', name: null, logoUri: null }, + awaitingConfirmation: null, + chainId: '42161', + fiatTotal: '0', + owners: [{ value: '0xc7c2E116A3027D0BFd9817781c717A81a8bC5518', name: null, logoUri: null }], + queued: 1, + threshold: 1, + }, +] + +export const mockedChains = [ + { + balancesProvider: { chainName: 'xdai', enabled: true }, + beaconChainExplorerUriTemplate: { publicKey: null }, + blockExplorerUriTemplate: { + address: 'https://gnosisscan.io/address/{{address}}', + api: 'https://api.gnosisscan.io/api?module={{module}}&action={{action}}&address={{address}}&apiKey={{apiKey}}', + txHash: 'https://gnosisscan.io/tx/{{txHash}}/', + }, + chainId: '100', + chainLogoUri: 'https://safe-transaction-assets.safe.global/chains/100/chain_logo.png', + chainName: 'Gnosis Chain', + contractAddresses: { + createCallAddress: null, + fallbackHandlerAddress: null, + multiSendAddress: null, + multiSendCallOnlyAddress: null, + safeProxyFactoryAddress: null, + safeSingletonAddress: null, + safeWebAuthnSignerFactoryAddress: null, + signMessageLibAddress: null, + simulateTxAccessorAddress: null, + }, + description: '', + disabledWallets: [ + 'keystone', + 'ledger_v2', + 'NONE', + 'opera', + 'operaTouch', + 'pk', + 'safeMobile', + 'tally', + 'trust', + 'walletConnect', + ], + ensRegistryAddress: null, + features: [ + 'COUNTERFACTUAL', + 'DEFAULT_TOKENLIST', + 'DELETE_TX', + 'EIP1271', + 'EIP1559', + 'ERC721', + 'MULTI_CHAIN_SAFE_ADD_NETWORK', + 'MULTI_CHAIN_SAFE_CREATION', + 'NATIVE_SWAPS', + 'NATIVE_SWAPS_FEE_ENABLED', + 'NATIVE_WALLETCONNECT', + 'PROPOSERS', + 'PUSH_NOTIFICATIONS', + 'RECOVERY', + 'RELAYING', + 'RELAYING_MOBILE', + 'RISK_MITIGATION', + 'SAFE_141', + 'SAFE_APPS', + 'SPEED_UP_TX', + 'SPENDING_LIMIT', + 'TX_SIMULATION', + 'ZODIAC_ROLES', + ], + gasPrice: [], + isTestnet: false, + l2: true, + nativeCurrency: { + decimals: 18, + logoUri: 'https://safe-transaction-assets.safe.global/chains/100/currency_logo.png', + name: 'xDai', + symbol: 'XDAI', + }, + publicRpcUri: { authentication: 'NO_AUTHENTICATION', value: 'https://rpc.gnosischain.com/' }, + rpcUri: { authentication: 'NO_AUTHENTICATION', value: 'https://rpc.gnosischain.com/' }, + safeAppsRpcUri: { authentication: 'NO_AUTHENTICATION', value: 'https://rpc.gnosischain.com/' }, + shortName: 'gno', + theme: { backgroundColor: '#48A9A6', textColor: '#ffffff' }, + transactionService: 'https://safe-transaction-gnosis-chain.safe.global', + }, + { + balancesProvider: { chainName: 'polygon', enabled: true }, + beaconChainExplorerUriTemplate: { publicKey: null }, + blockExplorerUriTemplate: { + address: 'https://polygonscan.com/address/{{address}}', + api: 'https://api.polygonscan.com/api?module={{module}}&action={{action}}&address={{address}}&apiKey={{apiKey}}', + txHash: 'https://polygonscan.com/tx/{{txHash}}', + }, + chainId: '137', + chainLogoUri: 'https://safe-transaction-assets.safe.global/chains/137/chain_logo.png', + chainName: 'Polygon', + contractAddresses: { + createCallAddress: null, + fallbackHandlerAddress: null, + multiSendAddress: null, + multiSendCallOnlyAddress: null, + safeProxyFactoryAddress: null, + safeSingletonAddress: null, + safeWebAuthnSignerFactoryAddress: null, + signMessageLibAddress: null, + simulateTxAccessorAddress: null, + }, + description: 'L2 chain', + disabledWallets: [ + 'keystone', + 'ledger_v2', + 'NONE', + 'opera', + 'operaTouch', + 'pk', + 'safeMobile', + 'socialSigner', + 'tally', + 'trezor', + 'trust', + 'walletConnect', + ], + ensRegistryAddress: null, + features: [ + 'COUNTERFACTUAL', + 'DEFAULT_TOKENLIST', + 'DELETE_TX', + 'EIP1271', + 'EIP1559', + 'ERC721', + 'MOONPAY_MOBILE', + 'MULTI_CHAIN_SAFE_ADD_NETWORK', + 'MULTI_CHAIN_SAFE_CREATION', + 'NATIVE_WALLETCONNECT', + 'PROPOSERS', + 'PUSH_NOTIFICATIONS', + 'RECOVERY', + 'RELAYING', + 'RISK_MITIGATION', + 'SAFE_141', + 'SAFE_APPS', + 'SPEED_UP_TX', + 'SPENDING_LIMIT', + 'TX_SIMULATION', + 'ZODIAC_ROLES', + ], + gasPrice: [], + isTestnet: false, + l2: true, + nativeCurrency: { + decimals: 18, + logoUri: 'https://safe-transaction-assets.safe.global/chains/137/currency_logo.png', + name: 'POL (ex-MATIC)', + symbol: 'POL', + }, + publicRpcUri: { authentication: 'NO_AUTHENTICATION', value: 'https://polygon-rpc.com' }, + rpcUri: { authentication: 'API_KEY_PATH', value: 'https://polygon-mainnet.infura.io/v3/' }, + safeAppsRpcUri: { authentication: 'API_KEY_PATH', value: 'https://polygon-mainnet.infura.io/v3/' }, + shortName: 'matic', + theme: { backgroundColor: '#8248E5', textColor: '#ffffff' }, + transactionService: 'https://safe-transaction-polygon.safe.global', + }, + { + balancesProvider: { chainName: 'arbitrum', enabled: true }, + beaconChainExplorerUriTemplate: { publicKey: null }, + blockExplorerUriTemplate: { + address: 'https://arbiscan.io/address/{{address}}', + api: 'https://api.arbiscan.io/api?module={{module}}&action={{action}}&address={{address}}&apiKey={{apiKey}}', + txHash: 'https://arbiscan.io/tx/{{txHash}}', + }, + chainId: '42161', + chainLogoUri: 'https://safe-transaction-assets.safe.global/chains/42161/chain_logo.png', + chainName: 'Arbitrum', + contractAddresses: { + createCallAddress: null, + fallbackHandlerAddress: null, + multiSendAddress: null, + multiSendCallOnlyAddress: null, + safeProxyFactoryAddress: null, + safeSingletonAddress: null, + safeWebAuthnSignerFactoryAddress: null, + signMessageLibAddress: null, + simulateTxAccessorAddress: null, + }, + description: '', + disabledWallets: [ + 'keystone', + 'ledger_v2', + 'NONE', + 'opera', + 'operaTouch', + 'pk', + 'safeMobile', + 'socialSigner', + 'tally', + 'trust', + 'walletConnect', + ], + ensRegistryAddress: null, + features: [ + 'COUNTERFACTUAL', + 'DEFAULT_TOKENLIST', + 'DELETE_TX', + 'EIP1271', + 'ERC721', + 'MOONPAY_MOBILE', + 'MULTI_CHAIN_SAFE_ADD_NETWORK', + 'MULTI_CHAIN_SAFE_CREATION', + 'NATIVE_SWAPS', + 'NATIVE_SWAPS_FEE_ENABLED', + 'NATIVE_WALLETCONNECT', + 'PROPOSERS', + 'PUSH_NOTIFICATIONS', + 'RECOVERY', + 'RISK_MITIGATION', + 'SAFE_141', + 'SAFE_APPS', + 'SPEED_UP_TX', + 'TX_SIMULATION', + 'ZODIAC_ROLES', + ], + gasPrice: [], + isTestnet: false, + l2: true, + nativeCurrency: { + decimals: 18, + logoUri: 'https://safe-transaction-assets.safe.global/chains/42161/currency_logo.png', + name: 'AETH', + symbol: 'AETH', + }, + publicRpcUri: { authentication: 'NO_AUTHENTICATION', value: 'https://arb1.arbitrum.io/rpc' }, + rpcUri: { authentication: 'NO_AUTHENTICATION', value: 'https://arb1.arbitrum.io/rpc' }, + safeAppsRpcUri: { authentication: 'NO_AUTHENTICATION', value: 'https://arb1.arbitrum.io/rpc' }, + shortName: 'arb1', + theme: { backgroundColor: '#28A0F0', textColor: '#ffffff' }, + transactionService: 'https://safe-transaction-arbitrum.safe.global', + }, +] diff --git a/apps/mobile/src/store/index.ts b/apps/mobile/src/store/index.ts index 4f078cd2e3..7c580238b2 100644 --- a/apps/mobile/src/store/index.ts +++ b/apps/mobile/src/store/index.ts @@ -4,6 +4,7 @@ import { reduxStorage } from './storage' import txHistory from './txHistorySlice' import activeChain from './activeChainSlice' import activeSafe from './activeSafeSlice' +import safes from './safesSlice' import { cgwClient, setBaseUrl } from '@safe-global/store/gateway/cgwClient' import devToolsEnhancer from 'redux-devtools-expo-dev-plugin' import { GATEWAY_URL, isTestingEnv } from '../config/constants' @@ -17,6 +18,7 @@ const persistConfig = { } export const rootReducer = combineReducers({ txHistory, + safes, activeChain, activeSafe, [cgwClient.reducerPath]: cgwClient.reducer, diff --git a/apps/mobile/src/store/safesSlice.ts b/apps/mobile/src/store/safesSlice.ts new file mode 100644 index 0000000000..d78641779c --- /dev/null +++ b/apps/mobile/src/store/safesSlice.ts @@ -0,0 +1,44 @@ +import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit' +import { RootState } from '.' +import { mockedAccounts, mockedActiveAccount, mockedActiveSafeInfo } from './constants' +import { Address } from '@/src/types/address' +import { SafeOverview } from '@safe-global/store/gateway/AUTO_GENERATED/safes' + +export type SafesSliceItem = { + SafeInfo: SafeOverview + chains: string[] +} + +export type SafesSlice = Record + +const initialState: SafesSlice = { + [mockedActiveAccount.address]: { + SafeInfo: mockedActiveSafeInfo, + chains: [mockedActiveAccount.chainId], + }, + [mockedAccounts[1].address.value]: { + SafeInfo: mockedAccounts[1], + chains: [mockedAccounts[1].chainId], + }, +} + +const activeSafeSlice = createSlice({ + name: 'safes', + initialState, + reducers: { + updateSafeInfo: (state, action: PayloadAction<{ address: Address; item: SafesSliceItem }>) => { + state[action.payload.address] = action.payload.item + return state + }, + }, +}) + +export const { updateSafeInfo } = activeSafeSlice.actions + +export const selectAllSafes = (state: RootState) => state.safes +export const selectActiveSafeInfo = createSelector( + [selectAllSafes, (_state, activeSafeAddress: Address) => activeSafeAddress], + (safes: SafesSlice, activeSafeAddress: Address) => safes[activeSafeAddress], +) + +export default activeSafeSlice.reducer diff --git a/apps/mobile/src/tests/jest.setup.tsx b/apps/mobile/src/tests/jest.setup.tsx index 70c19757e4..f7eed09035 100644 --- a/apps/mobile/src/tests/jest.setup.tsx +++ b/apps/mobile/src/tests/jest.setup.tsx @@ -115,6 +115,8 @@ jest.mock('@gorhom/bottom-sheet', () => { return { __esModule: true, default: View, + BottomSheetFooter: View, + BottomSheetFooterContainer: View, BottomSheetModal: MockBottomSheetComponent, BottomSheetModalProvider: View, BottomSheetView: View, diff --git a/apps/mobile/src/types/address.ts b/apps/mobile/src/types/address.ts index 816b1b8638..2125eb77f6 100644 --- a/apps/mobile/src/types/address.ts +++ b/apps/mobile/src/types/address.ts @@ -1 +1,6 @@ +export interface SafeInfo { + address: Address + chainId: string +} + export type Address = `0x${string}` diff --git a/apps/mobile/src/utils/formatters.ts b/apps/mobile/src/utils/formatters.ts index 4ef155a16a..86db4980a9 100644 --- a/apps/mobile/src/utils/formatters.ts +++ b/apps/mobile/src/utils/formatters.ts @@ -2,6 +2,8 @@ export const ellipsis = (str: string, length: number): string => { return str.length > length ? `${str.slice(0, length)}...` : str } +export const makeSafeId = (chainId: string, address: string) => `${chainId}:${address}` as `${number}:0x${string}` + export const shortenAddress = (address: string, length = 4): string => { if (!address) { return '' diff --git a/packages/store/scripts/api-schema/schema.json b/packages/store/scripts/api-schema/schema.json index 2d6dcabaa0..291d62f0b6 100644 --- a/packages/store/scripts/api-schema/schema.json +++ b/packages/store/scripts/api-schema/schema.json @@ -17,7 +17,9 @@ } } }, - "tags": ["about"] + "tags": [ + "about" + ] } }, "/v1/accounts": { @@ -46,7 +48,9 @@ } } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] } }, "/v1/accounts/data-types": { @@ -68,7 +72,9 @@ } } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] } }, "/v1/accounts/{address}/data-settings": { @@ -99,7 +105,9 @@ } } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] }, "put": { "operationId": "accountsUpsertAccountDataSettingsV1", @@ -138,7 +146,9 @@ } } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] } }, "/v1/accounts/{address}": { @@ -166,7 +176,9 @@ } } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] }, "delete": { "operationId": "accountsDeleteAccountV1", @@ -185,7 +197,9 @@ "description": "" } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] } }, "/v1/accounts/{address}/address-books/{chainId}": { @@ -221,7 +235,9 @@ } } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] }, "post": { "operationId": "addressBooksCreateAddressBookItemV1", @@ -265,7 +281,9 @@ } } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] }, "delete": { "operationId": "addressBooksDeleteAddressBookV1", @@ -292,7 +310,9 @@ "description": "" } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] } }, "/v1/accounts/{address}/address-books/{chainId}/{addressBookItemId}": { @@ -329,7 +349,9 @@ "description": "" } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] } }, "/v1/accounts/{address}/counterfactual-safes/{chainId}/{predictedAddress}": { @@ -373,7 +395,9 @@ } } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] }, "delete": { "operationId": "counterfactualSafesDeleteCounterfactualSafeV1", @@ -408,7 +432,9 @@ "description": "" } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] } }, "/v1/accounts/{address}/counterfactual-safes": { @@ -439,7 +465,9 @@ } } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] }, "put": { "operationId": "counterfactualSafesCreateCounterfactualSafeV1", @@ -475,7 +503,9 @@ } } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] }, "delete": { "operationId": "counterfactualSafesDeleteCounterfactualSafesV1", @@ -494,7 +524,9 @@ "description": "" } }, - "tags": ["accounts"] + "tags": [ + "accounts" + ] } }, "/v1/auth/nonce": { @@ -513,7 +545,9 @@ } } }, - "tags": ["auth"] + "tags": [ + "auth" + ] } }, "/v1/auth/verify": { @@ -535,7 +569,9 @@ "description": "Empty response body. JWT token is set as response cookie." } }, - "tags": ["auth"] + "tags": [ + "auth" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/balances/{fiatCode}": { @@ -595,7 +631,9 @@ } } }, - "tags": ["balances"] + "tags": [ + "balances" + ] } }, "/v1/balances/supported-fiat-codes": { @@ -607,7 +645,9 @@ "description": "" } }, - "tags": ["balances"] + "tags": [ + "balances" + ] } }, "/v1/chains": { @@ -635,7 +675,9 @@ } } }, - "tags": ["chains"] + "tags": [ + "chains" + ] } }, "/v1/chains/{chainId}": { @@ -663,7 +705,9 @@ } } }, - "tags": ["chains"] + "tags": [ + "chains" + ] } }, "/v1/chains/{chainId}/about": { @@ -691,7 +735,9 @@ } } }, - "tags": ["chains"] + "tags": [ + "chains" + ] } }, "/v1/chains/{chainId}/about/backbone": { @@ -719,7 +765,9 @@ } } }, - "tags": ["chains"] + "tags": [ + "chains" + ] } }, "/v1/chains/{chainId}/about/master-copies": { @@ -750,7 +798,9 @@ } } }, - "tags": ["chains"] + "tags": [ + "chains" + ] } }, "/v1/chains/{chainId}/about/indexing": { @@ -778,7 +828,9 @@ } } }, - "tags": ["chains"] + "tags": [ + "chains" + ] } }, "/v2/chains/{chainId}/safes/{safeAddress}/collectibles": { @@ -838,7 +890,9 @@ } } }, - "tags": ["collectibles"] + "tags": [ + "collectibles" + ] } }, "/v1/community/campaigns": { @@ -866,7 +920,9 @@ } } }, - "tags": ["community"] + "tags": [ + "community" + ] } }, "/v1/community/campaigns/{resourceId}": { @@ -894,7 +950,9 @@ } } }, - "tags": ["community"] + "tags": [ + "community" + ] } }, "/v1/community/campaigns/{resourceId}/activities": { @@ -931,7 +989,9 @@ "description": "" } }, - "tags": ["community"] + "tags": [ + "community" + ] } }, "/v1/community/campaigns/{resourceId}/leaderboard": { @@ -967,7 +1027,9 @@ } } }, - "tags": ["community"] + "tags": [ + "community" + ] } }, "/v1/community/campaigns/{resourceId}/leaderboard/{safeAddress}": { @@ -1003,7 +1065,9 @@ } } }, - "tags": ["community"] + "tags": [ + "community" + ] } }, "/v1/community/eligibility": { @@ -1032,7 +1096,9 @@ } } }, - "tags": ["community"] + "tags": [ + "community" + ] } }, "/v1/community/locking/leaderboard": { @@ -1060,7 +1126,9 @@ } } }, - "tags": ["community"] + "tags": [ + "community" + ] } }, "/v1/community/locking/{safeAddress}/rank": { @@ -1088,7 +1156,9 @@ } } }, - "tags": ["community"] + "tags": [ + "community" + ] } }, "/v1/community/locking/{safeAddress}/history": { @@ -1124,7 +1194,9 @@ } } }, - "tags": ["community"] + "tags": [ + "community" + ] } }, "/v1/chains/{chainId}/contracts/{contractAddress}": { @@ -1160,7 +1232,9 @@ } } }, - "tags": ["contracts"] + "tags": [ + "contracts" + ] } }, "/v1/chains/{chainId}/data-decoder": { @@ -1198,7 +1272,9 @@ } } }, - "tags": ["data-decoded"] + "tags": [ + "data-decoded" + ] } }, "/v1/chains/{chainId}/delegates": { @@ -1268,7 +1344,9 @@ } }, "summary": "", - "tags": ["delegates"] + "tags": [ + "delegates" + ] }, "post": { "deprecated": true, @@ -1299,7 +1377,9 @@ } }, "summary": "", - "tags": ["delegates"] + "tags": [ + "delegates" + ] } }, "/v1/chains/{chainId}/delegates/{delegateAddress}": { @@ -1340,7 +1420,9 @@ } }, "summary": "", - "tags": ["delegates"] + "tags": [ + "delegates" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/delegates/{delegateAddress}": { @@ -1373,7 +1455,9 @@ } }, "summary": "", - "tags": ["delegates"] + "tags": [ + "delegates" + ] } }, "/v2/chains/{chainId}/delegates": { @@ -1441,7 +1525,9 @@ } } }, - "tags": ["delegates"] + "tags": [ + "delegates" + ] }, "post": { "operationId": "delegatesPostDelegateV2", @@ -1470,7 +1556,9 @@ "description": "" } }, - "tags": ["delegates"] + "tags": [ + "delegates" + ] } }, "/v2/chains/{chainId}/delegates/{delegateAddress}": { @@ -1509,7 +1597,89 @@ "description": "" } }, - "tags": ["delegates"] + "tags": [ + "delegates" + ] + } + }, + "/v1/chains/{chainId}/safes/{safeAddress}/recovery": { + "post": { + "operationId": "recoveryAddRecoveryModuleV1", + "parameters": [ + { + "name": "chainId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "safeAddress", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddRecoveryModuleDto" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "recovery" + ] + } + }, + "/v1/chains/{chainId}/safes/{safeAddress}/recovery/{moduleAddress}": { + "delete": { + "operationId": "recoveryDeleteRecoveryModuleV1", + "parameters": [ + { + "name": "chainId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "moduleAddress", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "safeAddress", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "" + } + }, + "tags": [ + "recovery" + ] } }, "/v2/chains/{chainId}/safes/{address}/multisig-transactions/estimations": { @@ -1555,7 +1725,140 @@ } } }, - "tags": ["estimations"] + "tags": [ + "estimations" + ] + } + }, + "/v2/register/notifications": { + "post": { + "operationId": "notificationsUpsertSubscriptionsV2", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertSubscriptionsDto" + } + } + } + }, + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "notifications" + ] + } + }, + "/v2/chains/{chainId}/notifications/devices/{deviceUuid}/safes/{safeAddress}": { + "get": { + "operationId": "notificationsGetSafeSubscriptionV2", + "parameters": [ + { + "name": "deviceUuid", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "chainId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "safeAddress", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "notifications" + ] + }, + "delete": { + "operationId": "notificationsDeleteSubscriptionV2", + "parameters": [ + { + "name": "deviceUuid", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "chainId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "safeAddress", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "notifications" + ] + } + }, + "/v2/chains/{chainId}/notifications/devices/{deviceUuid}": { + "delete": { + "operationId": "notificationsDeleteDeviceV2", + "parameters": [ + { + "name": "chainId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "deviceUuid", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "notifications" + ] } }, "/v1/chains/{chainId}/messages/{messageHash}": { @@ -1591,7 +1894,9 @@ } } }, - "tags": ["messages"] + "tags": [ + "messages" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/messages": { @@ -1635,7 +1940,9 @@ } } }, - "tags": ["messages"] + "tags": [ + "messages" + ] }, "post": { "operationId": "messagesCreateMessageV1", @@ -1672,7 +1979,9 @@ "description": "" } }, - "tags": ["messages"] + "tags": [ + "messages" + ] } }, "/v1/chains/{chainId}/messages/{messageHash}/signatures": { @@ -1711,7 +2020,9 @@ "description": "" } }, - "tags": ["messages"] + "tags": [ + "messages" + ] } }, "/v1/register/notifications": { @@ -1733,7 +2044,9 @@ "description": "" } }, - "tags": ["notifications"] + "tags": [ + "notifications" + ] } }, "/v1/chains/{chainId}/notifications/devices/{uuid}": { @@ -1762,7 +2075,9 @@ "description": "" } }, - "tags": ["notifications"] + "tags": [ + "notifications" + ] } }, "/v1/chains/{chainId}/notifications/devices/{uuid}/safes/{safeAddress}": { @@ -1799,7 +2114,9 @@ "description": "" } }, - "tags": ["notifications"] + "tags": [ + "notifications" + ] } }, "/v1/chains/{chainId}/owners/{ownerAddress}/safes": { @@ -1835,7 +2152,9 @@ } } }, - "tags": ["owners"] + "tags": [ + "owners" + ] } }, "/v1/owners/{ownerAddress}/safes": { @@ -1863,7 +2182,9 @@ } } }, - "tags": ["owners"] + "tags": [ + "owners" + ] } }, "/v1/chains/{chainId}/relay": { @@ -1894,7 +2215,9 @@ "description": "" } }, - "tags": ["relay"] + "tags": [ + "relay" + ] } }, "/v1/chains/{chainId}/relay/{safeAddress}": { @@ -1923,7 +2246,9 @@ "description": "" } }, - "tags": ["relay"] + "tags": [ + "relay" + ] } }, "/v1/chains/{chainId}/safe-apps": { @@ -1970,7 +2295,9 @@ } } }, - "tags": ["safe-apps"] + "tags": [ + "safe-apps" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}": { @@ -2006,7 +2333,9 @@ } } }, - "tags": ["safes"] + "tags": [ + "safes" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/nonces": { @@ -2042,7 +2371,9 @@ } } }, - "tags": ["safes"] + "tags": [ + "safes" + ] } }, "/v1/safes": { @@ -2105,7 +2436,9 @@ } } }, - "tags": ["safes"] + "tags": [ + "safes" + ] } }, "/v1/targeted-messaging/outreaches/{outreachId}/chains/{chainId}/safes/{safeAddress}/signers/{signerAddress}/submissions": { @@ -2157,7 +2490,9 @@ } } }, - "tags": ["targeted-messaging"] + "tags": [ + "targeted-messaging" + ] }, "post": { "operationId": "targetedMessagingCreateSubmissionV1", @@ -2217,7 +2552,9 @@ } } }, - "tags": ["targeted-messaging"] + "tags": [ + "targeted-messaging" + ] } }, "/v1/chains/{chainId}/transactions/{id}": { @@ -2253,7 +2590,9 @@ } } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/multisig-transactions": { @@ -2345,7 +2684,9 @@ } } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/transactions/{safeTxHash}": { @@ -2384,7 +2725,9 @@ "description": "" } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/module-transactions": { @@ -2452,7 +2795,9 @@ } } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/transactions/{safeTxHash}/confirmations": { @@ -2498,7 +2843,9 @@ } } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/incoming-transfers": { @@ -2590,7 +2937,9 @@ } } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/transactions/{safeAddress}/preview": { @@ -2636,7 +2985,9 @@ } } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/transactions/queued": { @@ -2688,7 +3039,9 @@ } } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/transactions/history": { @@ -2765,7 +3118,9 @@ } } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/transactions/{safeAddress}/propose": { @@ -2811,7 +3166,9 @@ } } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/transactions/creation": { @@ -2847,7 +3204,9 @@ } } }, - "tags": ["transactions"] + "tags": [ + "transactions" + ] } }, "/v1/chains/{chainId}/safes/{safeAddress}/views/transaction-confirmation": { @@ -2885,28 +3244,6 @@ }, "responses": { "200": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/BaselineConfirmationView" - }, - { - "$ref": "#/components/schemas/CowSwapConfirmationView" - }, - { - "$ref": "#/components/schemas/CowSwapTwapConfirmationView" - }, - { - "$ref": "#/components/schemas/NativeStakingDepositConfirmationView" - }, - { - "$ref": "#/components/schemas/NativeStakingValidatorsExitConfirmationView" - }, - { - "$ref": "#/components/schemas/NativeStakingWithdrawConfirmationView" - } - ] - }, "description": "", "content": { "application/json": { @@ -2937,7 +3274,9 @@ } }, "summary": "", - "tags": ["transactions"] + "tags": [ + "transactions" + ] } } }, @@ -2966,7 +3305,9 @@ "nullable": true } }, - "required": ["name"] + "required": [ + "name" + ] }, "CreateAccountDto": { "type": "object", @@ -2978,7 +3319,10 @@ "type": "string" } }, - "required": ["address", "name"] + "required": [ + "address", + "name" + ] }, "Account": { "type": "object", @@ -2997,7 +3341,11 @@ "type": "string" } }, - "required": ["id", "address", "name"] + "required": [ + "id", + "address", + "name" + ] }, "AccountDataType": { "type": "object", @@ -3016,7 +3364,11 @@ "type": "boolean" } }, - "required": ["id", "name", "isActive"] + "required": [ + "id", + "name", + "isActive" + ] }, "AccountDataSetting": { "type": "object", @@ -3028,7 +3380,10 @@ "type": "boolean" } }, - "required": ["dataTypeId", "enabled"] + "required": [ + "dataTypeId", + "enabled" + ] }, "UpsertAccountDataSettingDto": { "type": "object", @@ -3040,7 +3395,10 @@ "type": "boolean" } }, - "required": ["dataTypeId", "enabled"] + "required": [ + "dataTypeId", + "enabled" + ] }, "UpsertAccountDataSettingsDto": { "type": "object", @@ -3052,7 +3410,9 @@ } } }, - "required": ["accountDataSettings"] + "required": [ + "accountDataSettings" + ] }, "AddressBookItem": { "type": "object", @@ -3067,7 +3427,11 @@ "type": "string" } }, - "required": ["id", "name", "address"] + "required": [ + "id", + "name", + "address" + ] }, "AddressBook": { "type": "object", @@ -3088,7 +3452,12 @@ } } }, - "required": ["id", "accountId", "chainId", "data"] + "required": [ + "id", + "accountId", + "chainId", + "data" + ] }, "CreateAddressBookItemDto": { "type": "object", @@ -3100,7 +3469,10 @@ "type": "string" } }, - "required": ["name", "address"] + "required": [ + "name", + "address" + ] }, "CounterfactualSafe": { "type": "object", @@ -3189,7 +3561,9 @@ "type": "string" } }, - "required": ["nonce"] + "required": [ + "nonce" + ] }, "SiweDto": { "type": "object", @@ -3201,7 +3575,10 @@ "type": "string" } }, - "required": ["message", "signature"] + "required": [ + "message", + "signature" + ] }, "Token": { "type": "object", @@ -3224,10 +3601,21 @@ }, "type": { "type": "string", - "enum": ["ERC721", "ERC20", "NATIVE_TOKEN", "UNKNOWN"] + "enum": [ + "ERC721", + "ERC20", + "NATIVE_TOKEN", + "UNKNOWN" + ] } }, - "required": ["address", "logoUri", "name", "symbol", "type"] + "required": [ + "address", + "logoUri", + "name", + "symbol", + "type" + ] }, "Balance": { "type": "object", @@ -3245,7 +3633,12 @@ "$ref": "#/components/schemas/Token" } }, - "required": ["balance", "fiatBalance", "fiatConversion", "tokenInfo"] + "required": [ + "balance", + "fiatBalance", + "fiatConversion", + "tokenInfo" + ] }, "Balances": { "type": "object", @@ -3264,7 +3657,10 @@ } } }, - "required": ["fiatTotal", "items"] + "required": [ + "fiatTotal", + "items" + ] }, "GasPriceOracle": { "type": "object", @@ -3282,7 +3678,12 @@ "type": "string" } }, - "required": ["type", "gasParameter", "gweiFactor", "uri"] + "required": [ + "type", + "gasParameter", + "gweiFactor", + "uri" + ] }, "GasPriceFixed": { "type": "object", @@ -3294,7 +3695,10 @@ "type": "string" } }, - "required": ["type", "weiValue"] + "required": [ + "type", + "weiValue" + ] }, "GasPriceFixedEIP1559": { "type": "object", @@ -3309,7 +3713,11 @@ "type": "string" } }, - "required": ["type", "maxFeePerGas", "maxPriorityFeePerGas"] + "required": [ + "type", + "maxFeePerGas", + "maxPriorityFeePerGas" + ] }, "NativeCurrency": { "type": "object", @@ -3327,7 +3735,12 @@ "type": "string" } }, - "required": ["decimals", "logoUri", "name", "symbol"] + "required": [ + "decimals", + "logoUri", + "name", + "symbol" + ] }, "BlockExplorerUriTemplate": { "type": "object", @@ -3342,7 +3755,11 @@ "type": "string" } }, - "required": ["address", "api", "txHash"] + "required": [ + "address", + "api", + "txHash" + ] }, "BalancesProvider": { "type": "object", @@ -3355,7 +3772,9 @@ "type": "boolean" } }, - "required": ["enabled"] + "required": [ + "enabled" + ] }, "ContractAddresses": { "type": "object", @@ -3403,13 +3822,20 @@ "properties": { "authentication": { "type": "string", - "enum": ["API_KEY_PATH", "NO_AUTHENTICATION", "UNKNOWN"] + "enum": [ + "API_KEY_PATH", + "NO_AUTHENTICATION", + "UNKNOWN" + ] }, "value": { "type": "string" } }, - "required": ["authentication", "value"] + "required": [ + "authentication", + "value" + ] }, "Theme": { "type": "object", @@ -3421,7 +3847,10 @@ "type": "string" } }, - "required": ["backgroundColor", "textColor"] + "required": [ + "backgroundColor", + "textColor" + ] }, "Chain": { "type": "object", @@ -3509,6 +3938,10 @@ }, "theme": { "$ref": "#/components/schemas/Theme" + }, + "recommendedMasterCopyVersion": { + "type": "string", + "nullable": true } }, "required": [ @@ -3555,7 +3988,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "AboutChain": { "type": "object", @@ -3573,7 +4008,12 @@ "type": "string" } }, - "required": ["transactionServiceBaseUri", "name", "version", "buildNumber"] + "required": [ + "transactionServiceBaseUri", + "name", + "version", + "buildNumber" + ] }, "Backbone": { "type": "object", @@ -3602,7 +4042,14 @@ "type": "string" } }, - "required": ["api_version", "host", "name", "secure", "settings", "version"] + "required": [ + "api_version", + "host", + "name", + "secure", + "settings", + "version" + ] }, "MasterCopy": { "type": "object", @@ -3614,7 +4061,10 @@ "type": "string" } }, - "required": ["address", "version"] + "required": [ + "address", + "version" + ] }, "IndexingStatus": { "type": "object", @@ -3626,7 +4076,10 @@ "type": "boolean" } }, - "required": ["lastSync", "synced"] + "required": [ + "lastSync", + "synced" + ] }, "Collectible": { "type": "object", @@ -3667,7 +4120,13 @@ "nullable": true } }, - "required": ["address", "tokenName", "tokenSymbol", "logoUri", "id"] + "required": [ + "address", + "tokenName", + "tokenSymbol", + "logoUri", + "id" + ] }, "CollectiblePage": { "type": "object", @@ -3691,7 +4150,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "ActivityMetadata": { "type": "object", @@ -3706,7 +4167,11 @@ "type": "number" } }, - "required": ["name", "description", "maxPoints"] + "required": [ + "name", + "description", + "maxPoints" + ] }, "Campaign": { "type": "object", @@ -3761,7 +4226,14 @@ "type": "boolean" } }, - "required": ["resourceId", "name", "description", "startDate", "endDate", "isPromoted"] + "required": [ + "resourceId", + "name", + "description", + "startDate", + "endDate", + "isPromoted" + ] }, "CampaignPage": { "type": "object", @@ -3785,7 +4257,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "CampaignRank": { "type": "object", @@ -3806,7 +4280,13 @@ "type": "number" } }, - "required": ["holder", "position", "boost", "totalPoints", "totalBoostedPoints"] + "required": [ + "holder", + "position", + "boost", + "totalPoints", + "totalBoostedPoints" + ] }, "CampaignRankPage": { "type": "object", @@ -3830,7 +4310,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "EligibilityRequest": { "type": "object", @@ -3842,7 +4324,10 @@ "type": "string" } }, - "required": ["requestId", "sealedData"] + "required": [ + "requestId", + "sealedData" + ] }, "Eligibility": { "type": "object", @@ -3857,7 +4342,11 @@ "type": "boolean" } }, - "required": ["requestId", "isAllowed", "isVpn"] + "required": [ + "requestId", + "isAllowed", + "isVpn" + ] }, "LockingRank": { "type": "object", @@ -3878,7 +4367,13 @@ "type": "string" } }, - "required": ["holder", "position", "lockedAmount", "unlockedAmount", "withdrawnAmount"] + "required": [ + "holder", + "position", + "lockedAmount", + "unlockedAmount", + "withdrawnAmount" + ] }, "LockingRankPage": { "type": "object", @@ -3902,14 +4397,18 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "LockEventItem": { "type": "object", "properties": { "eventType": { "type": "string", - "enum": ["LOCKED"] + "enum": [ + "LOCKED" + ] }, "executionDate": { "type": "string" @@ -3927,14 +4426,23 @@ "type": "string" } }, - "required": ["eventType", "executionDate", "transactionHash", "holder", "amount", "logIndex"] + "required": [ + "eventType", + "executionDate", + "transactionHash", + "holder", + "amount", + "logIndex" + ] }, "UnlockEventItem": { "type": "object", "properties": { "eventType": { "type": "string", - "enum": ["UNLOCKED"] + "enum": [ + "UNLOCKED" + ] }, "executionDate": { "type": "string" @@ -3955,14 +4463,24 @@ "type": "string" } }, - "required": ["eventType", "executionDate", "transactionHash", "holder", "amount", "logIndex", "unlockIndex"] + "required": [ + "eventType", + "executionDate", + "transactionHash", + "holder", + "amount", + "logIndex", + "unlockIndex" + ] }, "WithdrawEventItem": { "type": "object", "properties": { "eventType": { "type": "string", - "enum": ["WITHDRAWN"] + "enum": [ + "WITHDRAWN" + ] }, "executionDate": { "type": "string" @@ -3983,7 +4501,15 @@ "type": "string" } }, - "required": ["eventType", "executionDate", "transactionHash", "holder", "amount", "logIndex", "unlockIndex"] + "required": [ + "eventType", + "executionDate", + "transactionHash", + "holder", + "amount", + "logIndex", + "unlockIndex" + ] }, "LockingEventPage": { "type": "object", @@ -4017,7 +4543,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "Contract": { "type": "object", @@ -4042,7 +4570,13 @@ "type": "boolean" } }, - "required": ["address", "name", "displayName", "logoUri", "trustedForDelegateCall"] + "required": [ + "address", + "name", + "displayName", + "logoUri", + "trustedForDelegateCall" + ] }, "TransactionDataDto": { "type": "object", @@ -4060,7 +4594,9 @@ "description": "The wei amount being sent to a payable function" } }, - "required": ["data"] + "required": [ + "data" + ] }, "DataDecodedParameter": { "type": "object", @@ -4089,7 +4625,11 @@ "nullable": true } }, - "required": ["name", "type", "value"] + "required": [ + "name", + "type", + "value" + ] }, "DataDecoded": { "type": "object", @@ -4105,7 +4645,9 @@ } } }, - "required": ["method"] + "required": [ + "method" + ] }, "Delegate": { "type": "object", @@ -4124,7 +4666,11 @@ "type": "string" } }, - "required": ["delegate", "delegator", "label"] + "required": [ + "delegate", + "delegator", + "label" + ] }, "DelegatePage": { "type": "object", @@ -4148,7 +4694,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "CreateDelegateDto": { "type": "object", @@ -4170,7 +4718,12 @@ "type": "string" } }, - "required": ["delegate", "delegator", "signature", "label"] + "required": [ + "delegate", + "delegator", + "signature", + "label" + ] }, "DeleteDelegateDto": { "type": "object", @@ -4185,7 +4738,11 @@ "type": "string" } }, - "required": ["delegate", "delegator", "signature"] + "required": [ + "delegate", + "delegator", + "signature" + ] }, "DeleteSafeDelegateDto": { "type": "object", @@ -4200,7 +4757,11 @@ "type": "string" } }, - "required": ["delegate", "safe", "signature"] + "required": [ + "delegate", + "safe", + "signature" + ] }, "DeleteDelegateV2Dto": { "type": "object", @@ -4217,7 +4778,20 @@ "type": "string" } }, - "required": ["signature"] + "required": [ + "signature" + ] + }, + "AddRecoveryModuleDto": { + "type": "object", + "properties": { + "moduleAddress": { + "type": "string" + } + }, + "required": [ + "moduleAddress" + ] }, "GetEstimationDto": { "type": "object", @@ -4236,7 +4810,11 @@ "type": "number" } }, - "required": ["to", "value", "operation"] + "required": [ + "to", + "value", + "operation" + ] }, "EstimationResponse": { "type": "object", @@ -4251,7 +4829,83 @@ "type": "string" } }, - "required": ["currentNonce", "recommendedNonce", "safeTxGas"] + "required": [ + "currentNonce", + "recommendedNonce", + "safeTxGas" + ] + }, + "NotificationType": { + "type": "string", + "enum": [ + "CONFIRMATION_REQUEST", + "DELETED_MULTISIG_TRANSACTION", + "EXECUTED_MULTISIG_TRANSACTION", + "INCOMING_ETHER", + "INCOMING_TOKEN", + "MESSAGE_CONFIRMATION_REQUEST", + "MODULE_TRANSACTION" + ] + }, + "UpsertSubscriptionsSafesDto": { + "type": "object", + "properties": { + "chainId": { + "type": "string" + }, + "address": { + "type": "string" + }, + "notificationTypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NotificationType" + } + } + }, + "required": [ + "chainId", + "address", + "notificationTypes" + ] + }, + "DeviceType": { + "type": "string", + "enum": [ + "ANDROID", + "IOS", + "WEB" + ] + }, + "UpsertSubscriptionsDto": { + "type": "object", + "properties": { + "cloudMessagingToken": { + "type": "string" + }, + "safes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UpsertSubscriptionsSafesDto" + } + }, + "deviceType": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceType" + } + ] + }, + "deviceUuid": { + "type": "string", + "nullable": true + } + }, + "required": [ + "cloudMessagingToken", + "safes", + "deviceType" + ] }, "AddressInfo": { "type": "object", @@ -4268,7 +4922,9 @@ "nullable": true } }, - "required": ["value"] + "required": [ + "value" + ] }, "Message": { "type": "object", @@ -4403,13 +5059,18 @@ "properties": { "type": { "type": "string", - "enum": ["DATE_LABEL"] + "enum": [ + "DATE_LABEL" + ] }, "timestamp": { "type": "number" } }, - "required": ["type", "timestamp"] + "required": [ + "type", + "timestamp" + ] }, "MessagePage": { "type": "object", @@ -4440,7 +5101,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "CreateMessageDto": { "type": "object", @@ -4461,7 +5124,10 @@ "nullable": true } }, - "required": ["message", "signature"] + "required": [ + "message", + "signature" + ] }, "UpdateMessageSignatureDto": { "type": "object", @@ -4470,7 +5136,9 @@ "type": "string" } }, - "required": ["signature"] + "required": [ + "signature" + ] }, "SafeRegistration": { "type": "object", @@ -4491,7 +5159,11 @@ } } }, - "required": ["chainId", "safes", "signatures"] + "required": [ + "chainId", + "safes", + "signatures" + ] }, "RegisterDeviceDto": { "type": "object", @@ -4526,7 +5198,14 @@ } } }, - "required": ["cloudMessagingToken", "buildNumber", "bundle", "deviceType", "version", "safeRegistrations"] + "required": [ + "cloudMessagingToken", + "buildNumber", + "bundle", + "deviceType", + "version", + "safeRegistrations" + ] }, "SafeList": { "type": "object", @@ -4538,7 +5217,9 @@ } } }, - "required": ["safes"] + "required": [ + "safes" + ] }, "RelayDto": { "type": "object", @@ -4558,7 +5239,11 @@ "description": "If specified, a gas buffer of 150k will be added on top of the expected gas usage for the transaction.\n This is for the \n Gelato Relay execution overhead, reducing the chance of the task cancelling before it is executed on-chain." } }, - "required": ["version", "to", "data"] + "required": [ + "version", + "to", + "data" + ] }, "SafeAppProvider": { "type": "object", @@ -4570,7 +5255,10 @@ "type": "string" } }, - "required": ["url", "name"] + "required": [ + "url", + "name" + ] }, "SafeAppAccessControl": { "type": "object", @@ -4586,20 +5274,30 @@ } } }, - "required": ["type"] + "required": [ + "type" + ] }, "SafeAppSocialProfile": { "type": "object", "properties": { "platform": { "type": "string", - "enum": ["DISCORD", "GITHUB", "TWITTER", "UNKNOWN"] + "enum": [ + "DISCORD", + "GITHUB", + "TWITTER", + "UNKNOWN" + ] }, "url": { "type": "string" } }, - "required": ["platform", "url"] + "required": [ + "platform", + "url" + ] }, "SafeApp": { "type": "object", @@ -4692,9 +5390,10 @@ "type": "number" }, "owners": { + "nullable": false, "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/AddressInfo" } }, "implementation": { @@ -4729,7 +5428,11 @@ }, "implementationVersionState": { "type": "string", - "enum": ["UP_TO_DATE", "OUTDATED", "UNKNOWN"] + "enum": [ + "UP_TO_DATE", + "OUTDATED", + "UNKNOWN" + ] }, "collectiblesTag": { "type": "string", @@ -4768,7 +5471,10 @@ "type": "number" } }, - "required": ["currentNonce", "recommendedNonce"] + "required": [ + "currentNonce", + "recommendedNonce" + ] }, "SafeOverview": { "type": "object", @@ -4783,9 +5489,10 @@ "type": "number" }, "owners": { + "nullable": false, "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/AddressInfo" } }, "fiatTotal": { @@ -4799,7 +5506,14 @@ "nullable": true } }, - "required": ["address", "chainId", "threshold", "owners", "fiatTotal", "queued"] + "required": [ + "address", + "chainId", + "threshold", + "owners", + "fiatTotal", + "queued" + ] }, "Submission": { "type": "object", @@ -4819,7 +5533,11 @@ "nullable": true } }, - "required": ["outreachId", "targetedSafeId", "signerAddress"] + "required": [ + "outreachId", + "targetedSafeId", + "signerAddress" + ] }, "CreateSubmissionDto": { "type": "object", @@ -4828,7 +5546,9 @@ "type": "boolean" } }, - "required": ["completed"] + "required": [ + "completed" + ] }, "TransactionInfo": { "type": "object", @@ -4853,7 +5573,9 @@ "nullable": true } }, - "required": ["type"] + "required": [ + "type" + ] }, "TransactionData": { "type": "object", @@ -4889,14 +5611,19 @@ "nullable": true } }, - "required": ["to", "operation"] + "required": [ + "to", + "operation" + ] }, "MultisigExecutionDetails": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["MULTISIG"] + "enum": [ + "MULTISIG" + ] }, "submittedAt": { "type": "number" @@ -5001,13 +5728,18 @@ "properties": { "type": { "type": "string", - "enum": ["MODULE"] + "enum": [ + "MODULE" + ] }, "address": { "$ref": "#/components/schemas/AddressInfo" } }, - "required": ["type", "address"] + "required": [ + "type", + "address" + ] }, "SafeAppInfo": { "type": "object", @@ -5023,7 +5755,10 @@ "nullable": true } }, - "required": ["name", "url"] + "required": [ + "name", + "url" + ] }, "TransactionDetails": { "type": "object", @@ -5040,7 +5775,13 @@ }, "txStatus": { "type": "string", - "enum": ["SUCCESS", "FAILED", "CANCELLED", "AWAITING_CONFIRMATIONS", "AWAITING_EXECUTION"] + "enum": [ + "SUCCESS", + "FAILED", + "CANCELLED", + "AWAITING_CONFIRMATIONS", + "AWAITING_EXECUTION" + ] }, "txInfo": { "nullable": true, @@ -5082,14 +5823,21 @@ ] } }, - "required": ["safeAddress", "txId", "txStatus", "txInfo"] + "required": [ + "safeAddress", + "txId", + "txStatus", + "txInfo" + ] }, "CreationTransactionInfo": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["Creation"] + "enum": [ + "Creation" + ] }, "humanDescription": { "type": "string", @@ -5117,14 +5865,20 @@ "nullable": true } }, - "required": ["type", "creator", "transactionHash"] + "required": [ + "type", + "creator", + "transactionHash" + ] }, "CustomTransactionInfo": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["Custom"] + "enum": [ + "Custom" + ] }, "humanDescription": { "type": "string", @@ -5152,7 +5906,12 @@ "nullable": true } }, - "required": ["type", "to", "dataSize", "isCancellation"] + "required": [ + "type", + "to", + "dataSize", + "isCancellation" + ] }, "SettingsChange": { "type": "object", @@ -5173,14 +5932,18 @@ ] } }, - "required": ["type"] + "required": [ + "type" + ] }, "SettingsChangeTransaction": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["SettingsChange"] + "enum": [ + "SettingsChange" + ] }, "humanDescription": { "type": "string", @@ -5198,14 +5961,19 @@ ] } }, - "required": ["type", "dataDecoded"] + "required": [ + "type", + "dataDecoded" + ] }, "Erc20Transfer": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["ERC20"] + "enum": [ + "ERC20" + ] }, "tokenAddress": { "type": "string" @@ -5237,14 +6005,21 @@ "type": "boolean" } }, - "required": ["type", "tokenAddress", "value", "imitation"] + "required": [ + "type", + "tokenAddress", + "value", + "imitation" + ] }, "Erc721Transfer": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["ERC721"] + "enum": [ + "ERC721" + ] }, "tokenAddress": { "type": "string" @@ -5269,38 +6044,54 @@ "nullable": true } }, - "required": ["type", "tokenAddress", "tokenId"] + "required": [ + "type", + "tokenAddress", + "tokenId" + ] }, "NativeCoinTransfer": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["NATIVE_COIN"] + "enum": [ + "NATIVE_COIN" + ] }, "value": { "type": "string", "nullable": true } }, - "required": ["type"] + "required": [ + "type" + ] }, "Transfer": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["NATIVE_COIN", "ERC20", "ERC721"] + "enum": [ + "NATIVE_COIN", + "ERC20", + "ERC721" + ] } }, - "required": ["type"] + "required": [ + "type" + ] }, "TransferTransactionInfo": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["Transfer"] + "enum": [ + "Transfer" + ] }, "humanDescription": { "type": "string", @@ -5314,7 +6105,11 @@ }, "direction": { "type": "string", - "enum": ["INCOMING", "OUTGOING", "UNKNOWN"] + "enum": [ + "INCOMING", + "OUTGOING", + "UNKNOWN" + ] }, "transferInfo": { "oneOf": [ @@ -5335,27 +6130,40 @@ ] } }, - "required": ["type", "sender", "recipient", "direction", "transferInfo"] + "required": [ + "type", + "sender", + "recipient", + "direction", + "transferInfo" + ] }, "ModuleExecutionInfo": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["MODULE"] + "enum": [ + "MODULE" + ] }, "address": { "$ref": "#/components/schemas/AddressInfo" } }, - "required": ["type", "address"] + "required": [ + "type", + "address" + ] }, "MultisigExecutionInfo": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["MULTISIG"] + "enum": [ + "MULTISIG" + ] }, "nonce": { "type": "number" @@ -5374,7 +6182,12 @@ } } }, - "required": ["type", "nonce", "confirmationsRequired", "confirmationsSubmitted"] + "required": [ + "type", + "nonce", + "confirmationsRequired", + "confirmationsSubmitted" + ] }, "TokenInfo": { "type": "object", @@ -5405,14 +6218,22 @@ "description": "The token trusted status" } }, - "required": ["address", "decimals", "name", "symbol", "trusted"] + "required": [ + "address", + "decimals", + "name", + "symbol", + "trusted" + ] }, "SwapOrderTransactionInfo": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["SwapOrder"] + "enum": [ + "SwapOrder" + ] }, "humanDescription": { "type": "string", @@ -5424,15 +6245,31 @@ }, "status": { "type": "string", - "enum": ["presignaturePending", "open", "fulfilled", "cancelled", "expired", "unknown"] + "enum": [ + "presignaturePending", + "open", + "fulfilled", + "cancelled", + "expired", + "unknown" + ] }, "kind": { "type": "string", - "enum": ["buy", "sell", "unknown"] + "enum": [ + "buy", + "sell", + "unknown" + ] }, "orderClass": { "type": "string", - "enum": ["market", "limit", "liquidity", "unknown"] + "enum": [ + "market", + "limit", + "liquidity", + "unknown" + ] }, "validUntil": { "type": "number", @@ -5515,7 +6352,9 @@ "properties": { "type": { "type": "string", - "enum": ["SwapTransfer"] + "enum": [ + "SwapTransfer" + ] }, "humanDescription": { "type": "string", @@ -5554,15 +6393,31 @@ }, "status": { "type": "string", - "enum": ["presignaturePending", "open", "fulfilled", "cancelled", "expired", "unknown"] + "enum": [ + "presignaturePending", + "open", + "fulfilled", + "cancelled", + "expired", + "unknown" + ] }, "kind": { "type": "string", - "enum": ["buy", "sell", "unknown"] + "enum": [ + "buy", + "sell", + "unknown" + ] }, "orderClass": { "type": "string", - "enum": ["market", "limit", "liquidity", "unknown"] + "enum": [ + "market", + "limit", + "liquidity", + "unknown" + ] }, "validUntil": { "type": "number", @@ -5649,7 +6504,9 @@ "properties": { "type": { "type": "string", - "enum": ["TwapOrder"] + "enum": [ + "TwapOrder" + ] }, "humanDescription": { "type": "string", @@ -5658,15 +6515,31 @@ "status": { "type": "string", "description": "The TWAP status", - "enum": ["presignaturePending", "open", "fulfilled", "cancelled", "expired", "unknown"] + "enum": [ + "presignaturePending", + "open", + "fulfilled", + "cancelled", + "expired", + "unknown" + ] }, "kind": { "type": "string", - "enum": ["buy", "sell", "unknown"] + "enum": [ + "buy", + "sell", + "unknown" + ] }, "class": { "type": "string", - "enum": ["market", "limit", "liquidity", "unknown"] + "enum": [ + "market", + "limit", + "liquidity", + "unknown" + ] }, "activeOrderUid": { "type": "string", @@ -5777,7 +6650,9 @@ "properties": { "type": { "type": "string", - "enum": ["NativeStakingDeposit"] + "enum": [ + "NativeStakingDeposit" + ] }, "humanDescription": { "type": "string", @@ -5867,7 +6742,9 @@ "properties": { "type": { "type": "string", - "enum": ["NativeStakingValidatorsExit"] + "enum": [ + "NativeStakingValidatorsExit" + ] }, "humanDescription": { "type": "string", @@ -5924,7 +6801,9 @@ "properties": { "type": { "type": "string", - "enum": ["NativeStakingWithdraw"] + "enum": [ + "NativeStakingWithdraw" + ] }, "humanDescription": { "type": "string", @@ -5943,7 +6822,12 @@ } } }, - "required": ["type", "value", "tokenInfo", "validators"] + "required": [ + "type", + "value", + "tokenInfo", + "validators" + ] }, "Transaction": { "type": "object", @@ -5960,7 +6844,13 @@ }, "txStatus": { "type": "string", - "enum": ["SUCCESS", "FAILED", "CANCELLED", "AWAITING_CONFIRMATIONS", "AWAITING_EXECUTION"] + "enum": [ + "SUCCESS", + "FAILED", + "CANCELLED", + "AWAITING_CONFIRMATIONS", + "AWAITING_EXECUTION" + ] }, "txInfo": { "oneOf": [ @@ -6021,24 +6911,39 @@ ] } }, - "required": ["id", "timestamp", "txStatus", "txInfo"] + "required": [ + "id", + "timestamp", + "txStatus", + "txInfo" + ] }, "MultisigTransaction": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["TRANSACTION"] + "enum": [ + "TRANSACTION" + ] }, "transaction": { "$ref": "#/components/schemas/Transaction" }, "conflictType": { "type": "string", - "enum": ["None", "HasNext", "End"] + "enum": [ + "None", + "HasNext", + "End" + ] } }, - "required": ["type", "transaction", "conflictType"] + "required": [ + "type", + "transaction", + "conflictType" + ] }, "MultisigTransactionPage": { "type": "object", @@ -6062,7 +6967,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "DeleteTransactionDto": { "type": "object", @@ -6071,24 +6978,34 @@ "type": "string" } }, - "required": ["signature"] + "required": [ + "signature" + ] }, "ModuleTransaction": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["TRANSACTION"] + "enum": [ + "TRANSACTION" + ] }, "transaction": { "$ref": "#/components/schemas/Transaction" }, "conflictType": { "type": "string", - "enum": ["None"] + "enum": [ + "None" + ] } }, - "required": ["type", "transaction", "conflictType"] + "required": [ + "type", + "transaction", + "conflictType" + ] }, "ModuleTransactionPage": { "type": "object", @@ -6112,7 +7029,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "AddConfirmationDto": { "type": "object", @@ -6121,24 +7040,34 @@ "type": "string" } }, - "required": ["signedSafeTxHash"] + "required": [ + "signedSafeTxHash" + ] }, "IncomingTransfer": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["TRANSACTION"] + "enum": [ + "TRANSACTION" + ] }, "transaction": { "$ref": "#/components/schemas/Transaction" }, "conflictType": { "type": "string", - "enum": ["None"] + "enum": [ + "None" + ] } }, - "required": ["type", "transaction", "conflictType"] + "required": [ + "type", + "transaction", + "conflictType" + ] }, "IncomingTransferPage": { "type": "object", @@ -6162,7 +7091,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "PreviewTransactionDto": { "type": "object", @@ -6181,7 +7112,11 @@ "type": "number" } }, - "required": ["to", "value", "operation"] + "required": [ + "to", + "value", + "operation" + ] }, "TransactionPreview": { "type": "object", @@ -6226,50 +7161,73 @@ "$ref": "#/components/schemas/TransactionData" } }, - "required": ["txInfo", "txData"] + "required": [ + "txInfo", + "txData" + ] }, "ConflictHeaderQueuedItem": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["CONFLICT_HEADER"] + "enum": [ + "CONFLICT_HEADER" + ] }, "nonce": { "type": "number" } }, - "required": ["type", "nonce"] + "required": [ + "type", + "nonce" + ] }, "LabelQueuedItem": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["LABEL"] + "enum": [ + "LABEL" + ] }, "label": { "type": "string" } }, - "required": ["type", "label"] + "required": [ + "type", + "label" + ] }, "TransactionQueuedItem": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["TRANSACTION"] + "enum": [ + "TRANSACTION" + ] }, "transaction": { "$ref": "#/components/schemas/Transaction" }, "conflictType": { "type": "string", - "enum": ["None", "HasNext", "End"] + "enum": [ + "None", + "HasNext", + "End" + ] } }, - "required": ["type", "transaction", "conflictType"] + "required": [ + "type", + "transaction", + "conflictType" + ] }, "QueuedItemPage": { "type": "object", @@ -6303,24 +7261,34 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "TransactionItem": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["TRANSACTION"] + "enum": [ + "TRANSACTION" + ] }, "transaction": { "$ref": "#/components/schemas/Transaction" }, "conflictType": { "type": "string", - "enum": ["None"] + "enum": [ + "None" + ] } }, - "required": ["type", "transaction", "conflictType"] + "required": [ + "type", + "transaction", + "conflictType" + ] }, "TransactionItemPage": { "type": "object", @@ -6351,7 +7319,9 @@ } } }, - "required": ["results"] + "required": [ + "results" + ] }, "ProposeTransactionDto": { "type": "object", @@ -6453,14 +7423,21 @@ ] } }, - "required": ["created", "creator", "transactionHash", "factoryAddress"] + "required": [ + "created", + "creator", + "transactionHash", + "factoryAddress" + ] }, "BaselineConfirmationView": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["GENERIC"] + "enum": [ + "GENERIC" + ] }, "method": { "type": "string" @@ -6473,14 +7450,19 @@ } } }, - "required": ["type", "method"] + "required": [ + "type", + "method" + ] }, "CowSwapConfirmationView": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["COW_SWAP_ORDER"] + "enum": [ + "COW_SWAP_ORDER" + ] }, "method": { "type": "string" @@ -6498,15 +7480,31 @@ }, "status": { "type": "string", - "enum": ["presignaturePending", "open", "fulfilled", "cancelled", "expired", "unknown"] + "enum": [ + "presignaturePending", + "open", + "fulfilled", + "cancelled", + "expired", + "unknown" + ] }, "kind": { "type": "string", - "enum": ["buy", "sell", "unknown"] + "enum": [ + "buy", + "sell", + "unknown" + ] }, "orderClass": { "type": "string", - "enum": ["market", "limit", "liquidity", "unknown"] + "enum": [ + "market", + "limit", + "liquidity", + "unknown" + ] }, "validUntil": { "type": "number", @@ -6590,7 +7588,9 @@ "properties": { "type": { "type": "string", - "enum": ["COW_SWAP_TWAP_ORDER"] + "enum": [ + "COW_SWAP_TWAP_ORDER" + ] }, "method": { "type": "string" @@ -6604,16 +7604,32 @@ }, "status": { "type": "string", - "enum": ["presignaturePending", "open", "fulfilled", "cancelled", "expired", "unknown"], + "enum": [ + "presignaturePending", + "open", + "fulfilled", + "cancelled", + "expired", + "unknown" + ], "description": "The TWAP status" }, "kind": { "type": "string", - "enum": ["buy", "sell", "unknown"] + "enum": [ + "buy", + "sell", + "unknown" + ] }, "class": { "type": "string", - "enum": ["market", "limit", "liquidity", "unknown"] + "enum": [ + "market", + "limit", + "liquidity", + "unknown" + ] }, "activeOrderUid": { "type": "null", @@ -6726,7 +7742,9 @@ "properties": { "type": { "type": "string", - "enum": ["KILN_NATIVE_STAKING_DEPOSIT"] + "enum": [ + "KILN_NATIVE_STAKING_DEPOSIT" + ] }, "status": { "type": "string", @@ -6815,7 +7833,9 @@ "properties": { "type": { "type": "string", - "enum": ["KILN_NATIVE_STAKING_VALIDATORS_EXIT"] + "enum": [ + "KILN_NATIVE_STAKING_VALIDATORS_EXIT" + ] }, "status": { "type": "string", @@ -6879,7 +7899,9 @@ "properties": { "type": { "type": "string", - "enum": ["KILN_NATIVE_STAKING_WITHDRAW"] + "enum": [ + "KILN_NATIVE_STAKING_WITHDRAW" + ] }, "method": { "type": "string" @@ -6904,7 +7926,13 @@ } } }, - "required": ["type", "method", "value", "tokenInfo", "validators"] + "required": [ + "type", + "method", + "value", + "tokenInfo", + "validators" + ] } } } diff --git a/packages/store/src/gateway/AUTO_GENERATED/chains.ts b/packages/store/src/gateway/AUTO_GENERATED/chains.ts index f1d9929dee..e01d846347 100644 --- a/packages/store/src/gateway/AUTO_GENERATED/chains.ts +++ b/packages/store/src/gateway/AUTO_GENERATED/chains.ts @@ -134,6 +134,7 @@ export type Chain = { safeAppsRpcUri: RpcUri shortName: string theme: Theme + recommendedMasterCopyVersion?: string | null } export type ChainPage = { count?: number | null diff --git a/packages/store/src/gateway/AUTO_GENERATED/notifications.ts b/packages/store/src/gateway/AUTO_GENERATED/notifications.ts index fb66aa5205..f93f99582d 100644 --- a/packages/store/src/gateway/AUTO_GENERATED/notifications.ts +++ b/packages/store/src/gateway/AUTO_GENERATED/notifications.ts @@ -6,6 +6,46 @@ const injectedRtkApi = api }) .injectEndpoints({ endpoints: (build) => ({ + notificationsUpsertSubscriptionsV2: build.mutation< + NotificationsUpsertSubscriptionsV2ApiResponse, + NotificationsUpsertSubscriptionsV2ApiArg + >({ + query: (queryArg) => ({ + url: `/v2/register/notifications`, + method: 'POST', + body: queryArg.upsertSubscriptionsDto, + }), + invalidatesTags: ['notifications'], + }), + notificationsGetSafeSubscriptionV2: build.query< + NotificationsGetSafeSubscriptionV2ApiResponse, + NotificationsGetSafeSubscriptionV2ApiArg + >({ + query: (queryArg) => ({ + url: `/v2/chains/${queryArg.chainId}/notifications/devices/${queryArg.deviceUuid}/safes/${queryArg.safeAddress}`, + }), + providesTags: ['notifications'], + }), + notificationsDeleteSubscriptionV2: build.mutation< + NotificationsDeleteSubscriptionV2ApiResponse, + NotificationsDeleteSubscriptionV2ApiArg + >({ + query: (queryArg) => ({ + url: `/v2/chains/${queryArg.chainId}/notifications/devices/${queryArg.deviceUuid}/safes/${queryArg.safeAddress}`, + method: 'DELETE', + }), + invalidatesTags: ['notifications'], + }), + notificationsDeleteDeviceV2: build.mutation< + NotificationsDeleteDeviceV2ApiResponse, + NotificationsDeleteDeviceV2ApiArg + >({ + query: (queryArg) => ({ + url: `/v2/chains/${queryArg.chainId}/notifications/devices/${queryArg.deviceUuid}`, + method: 'DELETE', + }), + invalidatesTags: ['notifications'], + }), notificationsRegisterDeviceV1: build.mutation< NotificationsRegisterDeviceV1ApiResponse, NotificationsRegisterDeviceV1ApiArg @@ -37,6 +77,27 @@ const injectedRtkApi = api overrideExisting: false, }) export { injectedRtkApi as cgwApi } +export type NotificationsUpsertSubscriptionsV2ApiResponse = unknown +export type NotificationsUpsertSubscriptionsV2ApiArg = { + upsertSubscriptionsDto: UpsertSubscriptionsDto +} +export type NotificationsGetSafeSubscriptionV2ApiResponse = unknown +export type NotificationsGetSafeSubscriptionV2ApiArg = { + deviceUuid: string + chainId: string + safeAddress: string +} +export type NotificationsDeleteSubscriptionV2ApiResponse = unknown +export type NotificationsDeleteSubscriptionV2ApiArg = { + deviceUuid: string + chainId: string + safeAddress: string +} +export type NotificationsDeleteDeviceV2ApiResponse = unknown +export type NotificationsDeleteDeviceV2ApiArg = { + chainId: string + deviceUuid: string +} export type NotificationsRegisterDeviceV1ApiResponse = unknown export type NotificationsRegisterDeviceV1ApiArg = { registerDeviceDto: RegisterDeviceDto @@ -52,6 +113,26 @@ export type NotificationsUnregisterSafeV1ApiArg = { uuid: string safeAddress: string } +export type NotificationType = + | 'CONFIRMATION_REQUEST' + | 'DELETED_MULTISIG_TRANSACTION' + | 'EXECUTED_MULTISIG_TRANSACTION' + | 'INCOMING_ETHER' + | 'INCOMING_TOKEN' + | 'MESSAGE_CONFIRMATION_REQUEST' + | 'MODULE_TRANSACTION' +export type UpsertSubscriptionsSafesDto = { + chainId: string + address: string + notificationTypes: NotificationType[] +} +export type DeviceType = 'ANDROID' | 'IOS' | 'WEB' +export type UpsertSubscriptionsDto = { + cloudMessagingToken: string + safes: UpsertSubscriptionsSafesDto[] + deviceType: DeviceType + deviceUuid?: string | null +} export type SafeRegistration = { chainId: string safes: string[] @@ -68,6 +149,10 @@ export type RegisterDeviceDto = { safeRegistrations: SafeRegistration[] } export const { + useNotificationsUpsertSubscriptionsV2Mutation, + useNotificationsGetSafeSubscriptionV2Query, + useNotificationsDeleteSubscriptionV2Mutation, + useNotificationsDeleteDeviceV2Mutation, useNotificationsRegisterDeviceV1Mutation, useNotificationsUnregisterDeviceV1Mutation, useNotificationsUnregisterSafeV1Mutation, diff --git a/packages/store/src/gateway/AUTO_GENERATED/safes.ts b/packages/store/src/gateway/AUTO_GENERATED/safes.ts index ff3cf1b5f7..059e249054 100644 --- a/packages/store/src/gateway/AUTO_GENERATED/safes.ts +++ b/packages/store/src/gateway/AUTO_GENERATED/safes.ts @@ -59,7 +59,7 @@ export type SafeState = { chainId: string nonce: number threshold: number - owners: string[] + owners: AddressInfo[] implementation: AddressInfo modules?: AddressInfo[] | null fallbackHandler?: AddressInfo | null @@ -79,7 +79,7 @@ export type SafeOverview = { address: AddressInfo chainId: string threshold: number - owners: string[] + owners: AddressInfo[] fiatTotal: string queued: number awaitingConfirmation?: number | null diff --git a/packages/store/src/gateway/AUTO_GENERATED/transactions.ts b/packages/store/src/gateway/AUTO_GENERATED/transactions.ts index d17095c601..1f45d6454a 100644 --- a/packages/store/src/gateway/AUTO_GENERATED/transactions.ts +++ b/packages/store/src/gateway/AUTO_GENERATED/transactions.ts @@ -245,8 +245,7 @@ export type TransactionsGetCreationTransactionV1ApiArg = { chainId: string safeAddress: string } -export type TransactionsViewGetTransactionConfirmationViewV1ApiResponse = - /** status 200 */ +export type TransactionsViewGetTransactionConfirmationViewV1ApiResponse = /** status 200 */ | BaselineConfirmationView | CowSwapConfirmationView | CowSwapTwapConfirmationView diff --git a/packages/store/src/gateway/chains/index.ts b/packages/store/src/gateway/chains/index.ts index 2316641354..d47946706b 100644 --- a/packages/store/src/gateway/chains/index.ts +++ b/packages/store/src/gateway/chains/index.ts @@ -1,5 +1,5 @@ import { type Chain as ChainInfo } from '../AUTO_GENERATED/chains' -import { createEntityAdapter, createSelector, EntityState } from '@reduxjs/toolkit' +import { createEntityAdapter, EntityState } from '@reduxjs/toolkit' import { cgwClient, getBaseUrl } from '../cgwClient' import { QueryReturnValue, FetchBaseQueryError, FetchBaseQueryMeta } from '@reduxjs/toolkit/dist/query'