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'