diff --git a/apps/mobile/src/tests/mocks.ts b/apps/mobile/src/tests/mocks.ts index 7a3aa42010..cf8a29a0c2 100644 --- a/apps/mobile/src/tests/mocks.ts +++ b/apps/mobile/src/tests/mocks.ts @@ -159,7 +159,6 @@ export const mockSwapTransfer: SwapTransferTransactionInfo = { sellToken: fakeToken2, buyToken: fakeToken, explorerUrl: 'http://google.com', - executedSurplusFee: '', receiver: '0xbob', owner: '0xalice', } diff --git a/apps/web/src/features/swap/helpers/swapOrderBuilder.ts b/apps/web/src/features/swap/helpers/swapOrderBuilder.ts index 30d751e1d6..9cdd71d2d7 100644 --- a/apps/web/src/features/swap/helpers/swapOrderBuilder.ts +++ b/apps/web/src/features/swap/helpers/swapOrderBuilder.ts @@ -64,7 +64,6 @@ export function swapOrderBuilder(): IBuilder { buyToken: orderTokenBuilder().build(), explorerUrl: 'https://explorer.cow.fi/orders/0x03a5d561ad2452d719a0d075573f4bed68217c696b52f151122c30e3e4426f1b05e6b5eb1d0e6aabab082057d5bb91f2ee6d11be66223d88', - executedSurplusFee: executedFee, executedFee, executedFeeToken: sellToken, fullAppData: appDataBuilder().build(), @@ -86,7 +85,6 @@ export function twapOrderBuilder(): IBuilder { executedBuyAmount: faker.string.numeric(), sellToken: orderTokenBuilder().build(), buyToken: orderTokenBuilder().build(), - executedSurplusFee: executedFee, executedFee, executedFeeToken: sellToken, fullAppData: appDataBuilder().build(), diff --git a/apps/web/src/features/targetedFeatures/constants.ts b/apps/web/src/features/targetedFeatures/constants.ts new file mode 100644 index 0000000000..49a1aa50ca --- /dev/null +++ b/apps/web/src/features/targetedFeatures/constants.ts @@ -0,0 +1,6 @@ +import { FEATURES } from '@/utils/chains' + +export const TARGETED_FEATURES = [ + { id: 3, feature: FEATURES.TARGETED_NESTED_SAFES }, + { id: 4, feature: FEATURES.TARGETED_MASS_PAYOUTS }, +] as const diff --git a/apps/web/src/features/targetedFeatures/hooks/__tests__/useIsOutreachSafe.test.ts b/apps/web/src/features/targetedFeatures/hooks/__tests__/useIsOutreachSafe.test.ts new file mode 100644 index 0000000000..ad388bbd7c --- /dev/null +++ b/apps/web/src/features/targetedFeatures/hooks/__tests__/useIsOutreachSafe.test.ts @@ -0,0 +1,140 @@ +import { faker } from '@faker-js/faker' +import * as targetedMessages from '@safe-global/store/gateway/AUTO_GENERATED/targeted-messages' + +import * as useSafeInfoHook from '@/hooks/useSafeInfo' +import { safeInfoBuilder } from '@/tests/builders/safe' +import { renderHook } from '@/tests/test-utils' +import { useIsOutreachSafe } from '../useIsOutreachSafe' + +describe('useIsOutreachSafe', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('returns true if the Safe is targeted for messaging', () => { + const safeInfo = safeInfoBuilder().build() + const outreachId = faker.number.int() + jest.spyOn(useSafeInfoHook, 'default').mockReturnValue({ + safeAddress: safeInfo.address.value, + safe: { + ...safeInfo, + deployed: true, + }, + safeLoaded: true, + safeLoading: false, + safeError: undefined, + }) + jest.spyOn(targetedMessages, 'useTargetedMessagingGetTargetedSafeV1Query').mockReturnValue({ + data: { + outreachId, + address: safeInfo.address.value, + }, + refetch: jest.fn(), + }) + + const { result } = renderHook(() => useIsOutreachSafe(outreachId)) + + expect(result.current).toBe(true) + }) + + it('returns false if the Safe is not targeted for messaging', () => { + const safeInfo = safeInfoBuilder().build() + const outreachId = faker.number.int() + jest.spyOn(useSafeInfoHook, 'default').mockReturnValue({ + safeAddress: safeInfo.address.value, + safe: { + ...safeInfo, + deployed: true, + }, + safeLoaded: true, + safeLoading: false, + safeError: undefined, + }) + jest.spyOn(targetedMessages, 'useTargetedMessagingGetTargetedSafeV1Query').mockReturnValue({ + data: undefined, + error: new Error('Safe not targeted'), + refetch: jest.fn(), + }) + + const { result } = renderHook(() => useIsOutreachSafe(outreachId)) + + expect(result.current).toBe(false) + }) + + it('returns false if the data is not available', () => { + const safeInfo = safeInfoBuilder().build() + const outreachId = faker.number.int() + jest.spyOn(useSafeInfoHook, 'default').mockReturnValue({ + safeAddress: safeInfo.address.value, + safe: { + ...safeInfo, + deployed: true, + }, + safeLoaded: true, + safeLoading: false, + safeError: undefined, + }) + jest.spyOn(targetedMessages, 'useTargetedMessagingGetTargetedSafeV1Query').mockReturnValue({ + data: undefined, // Yet to be fetched + refetch: jest.fn(), + }) + + const { result } = renderHook(() => useIsOutreachSafe(outreachId)) + + expect(result.current).toBe(false) + }) + + it('returns false if the outreachId does not match', () => { + const safeInfo = safeInfoBuilder().build() + const outreachId = faker.number.int() + const otherOutreachId = 'OTHER_FEATURE' + jest.spyOn(useSafeInfoHook, 'default').mockReturnValue({ + safeAddress: safeInfo.address.value, + safe: { + ...safeInfo, + deployed: true, + }, + safeLoaded: true, + safeLoading: false, + safeError: undefined, + }) + jest.spyOn(targetedMessages, 'useTargetedMessagingGetTargetedSafeV1Query').mockReturnValue({ + data: { + outreachId: otherOutreachId, + address: safeInfo.address.value, + }, + refetch: jest.fn(), + }) + + const { result } = renderHook(() => useIsOutreachSafe(outreachId)) + + expect(result.current).toBe(false) + }) + + it('returns false if the address does not match', () => { + const safeInfo = safeInfoBuilder().build() + const otherAddress = faker.finance.ethereumAddress() + const outreachId = faker.number.int() + jest.spyOn(useSafeInfoHook, 'default').mockReturnValue({ + safeAddress: safeInfo.address.value, + safe: { + ...safeInfo, + deployed: true, + }, + safeLoaded: true, + safeLoading: false, + safeError: undefined, + }) + jest.spyOn(targetedMessages, 'useTargetedMessagingGetTargetedSafeV1Query').mockReturnValue({ + data: { + outreachId, + address: otherAddress, + }, + refetch: jest.fn(), + }) + + const { result } = renderHook(() => useIsOutreachSafe(outreachId)) + + expect(result.current).toBe(false) + }) +}) diff --git a/apps/web/src/features/targetedFeatures/hooks/__tests__/useIsTargetedFeature.test.ts b/apps/web/src/features/targetedFeatures/hooks/__tests__/useIsTargetedFeature.test.ts new file mode 100644 index 0000000000..97ff88ea0d --- /dev/null +++ b/apps/web/src/features/targetedFeatures/hooks/__tests__/useIsTargetedFeature.test.ts @@ -0,0 +1,74 @@ +import { faker } from '@faker-js/faker' + +import * as useOutreachSafeHook from '@/features/targetedFeatures/hooks/useIsOutreachSafe' +import * as useChainsHook from '@/hooks/useChains' +import * as useLocalStorageHook from '@/services/local-storage/useLocalStorage' +import { renderHook, waitFor } from '@/tests/test-utils' +import { useIsTargetedFeature } from '../useIsTargetedFeature' +import { TARGETED_FEATURES } from '../../constants' + +const targetedFeatures = TARGETED_FEATURES.map((f) => f.feature) + +describe('useIsTargetedFeature', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('returns true if the Safe is targeted and the feature is enabled', () => { + const feature = faker.helpers.arrayElement(targetedFeatures) + jest.spyOn(useChainsHook, 'useHasFeature').mockReturnValue(true) + jest.spyOn(useOutreachSafeHook, 'useIsOutreachSafe').mockReturnValue(true) + jest.spyOn(useLocalStorageHook, 'default').mockReturnValue([[feature], jest.fn()]) + + const { result } = renderHook(() => useIsTargetedFeature(feature)) + + expect(result.current).toBe(true) + }) + + it('returns true if the the feature is unlocked and enabled', () => { + const feature = faker.helpers.arrayElement(targetedFeatures) + jest.spyOn(useChainsHook, 'useHasFeature').mockReturnValue(true) + jest.spyOn(useOutreachSafeHook, 'useIsOutreachSafe').mockReturnValue(false) + jest.spyOn(useLocalStorageHook, 'default').mockReturnValue([[feature], jest.fn()]) + + const { result } = renderHook(() => useIsTargetedFeature(feature)) + + expect(result.current).toBe(true) + }) + + it('returns false if the Safe is targeted but the feature is disabled', () => { + const feature = faker.helpers.arrayElement(targetedFeatures) + jest.spyOn(useChainsHook, 'useHasFeature').mockReturnValue(false) + jest.spyOn(useOutreachSafeHook, 'useIsOutreachSafe').mockReturnValue(true) + jest.spyOn(useLocalStorageHook, 'default').mockReturnValue([[], jest.fn()]) + + const { result } = renderHook(() => useIsTargetedFeature(feature)) + + expect(result.current).toBe(false) + }) + + it('returns false if the Safe is targeted and the feature is unlocked', () => { + const feature = faker.helpers.arrayElement(targetedFeatures) + jest.spyOn(useChainsHook, 'useHasFeature').mockReturnValue(false) + jest.spyOn(useOutreachSafeHook, 'useIsOutreachSafe').mockReturnValue(true) + jest.spyOn(useLocalStorageHook, 'default').mockReturnValue([[feature], jest.fn()]) + + const { result } = renderHook(() => useIsTargetedFeature(feature)) + + expect(result.current).toBe(false) + }) + + it('caches targeted/enabled features', () => { + const feature = faker.helpers.arrayElement(targetedFeatures) + const setLocalStorageMock = jest.fn() + jest.spyOn(useChainsHook, 'useHasFeature').mockReturnValue(true) + jest.spyOn(useOutreachSafeHook, 'useIsOutreachSafe').mockReturnValue(true) + jest.spyOn(useLocalStorageHook, 'default').mockReturnValue([[feature], jest.fn()]) + + renderHook(() => useIsTargetedFeature(feature)) + + waitFor(() => { + expect(setLocalStorageMock).toHaveBeenCalledWith([feature]) + }) + }) +}) diff --git a/apps/web/src/features/targetedFeatures/hooks/useIsOutreachSafe.ts b/apps/web/src/features/targetedFeatures/hooks/useIsOutreachSafe.ts new file mode 100644 index 0000000000..d9366bcf8e --- /dev/null +++ b/apps/web/src/features/targetedFeatures/hooks/useIsOutreachSafe.ts @@ -0,0 +1,18 @@ +import { useTargetedMessagingGetTargetedSafeV1Query } from '@safe-global/store/gateway/AUTO_GENERATED/targeted-messages' + +import useSafeInfo from '@/hooks/useSafeInfo' +import { sameAddress } from '@/utils/addresses' + +export function useIsOutreachSafe(outreachId: number): boolean { + const { safe } = useSafeInfo() + const { data } = useTargetedMessagingGetTargetedSafeV1Query( + { + outreachId, + chainId: safe.chainId, + safeAddress: safe.address.value, + }, + { skip: !safe.address.value }, + ) + + return data?.outreachId === outreachId && sameAddress(data.address, safe.address.value) +} diff --git a/apps/web/src/features/targetedFeatures/hooks/useIsTargetedFeature.ts b/apps/web/src/features/targetedFeatures/hooks/useIsTargetedFeature.ts new file mode 100644 index 0000000000..3ef0ef497a --- /dev/null +++ b/apps/web/src/features/targetedFeatures/hooks/useIsTargetedFeature.ts @@ -0,0 +1,29 @@ +import { useEffect } from 'react' + +import { useIsOutreachSafe } from '@/features/targetedFeatures/hooks/useIsOutreachSafe' +import { useHasFeature } from '@/hooks/useChains' +import useLocalStorage from '@/services/local-storage/useLocalStorage' +import { TARGETED_FEATURES } from '../constants' + +const UNLOCKED_FEATURES_LS_KEY = 'unlockedFeatures' + +type TargetedFeatures = (typeof TARGETED_FEATURES)[number]['feature'] + +export function useIsTargetedFeature(feature: TargetedFeatures): boolean { + const hasFeature = useHasFeature(feature) + + const outreachId = TARGETED_FEATURES.find((f) => f.feature === feature)!.id + const isTargeted = useIsOutreachSafe(outreachId) + + // Should a targeted Safe have been opened, we "unlock" the feature across the app + const [unlockedFeatures = [], setUnlockedFeatures] = + useLocalStorage>(UNLOCKED_FEATURES_LS_KEY) + const isUnlocked = unlockedFeatures.includes(feature) + useEffect(() => { + if (hasFeature && isTargeted && !isUnlocked) { + setUnlockedFeatures([...unlockedFeatures, feature]) + } + }, [feature, hasFeature, isTargeted, isUnlocked, setUnlockedFeatures, unlockedFeatures]) + + return !!hasFeature && (isTargeted || isUnlocked) +} diff --git a/apps/web/src/utils/chains.ts b/apps/web/src/utils/chains.ts index 8d7534b7c1..42d576ebb2 100644 --- a/apps/web/src/utils/chains.ts +++ b/apps/web/src/utils/chains.ts @@ -41,6 +41,8 @@ export enum FEATURES { BRIDGE = 'BRIDGE', RENEW_NOTIFICATIONS_TOKEN = 'RENEW_NOTIFICATIONS_TOKEN', TX_NOTES = 'TX_NOTES', + TARGETED_NESTED_SAFES = 'TARGETED_NESTED_SAFES', + TARGETED_MASS_PAYOUTS = 'TARGETED_MASS_PAYOUTS', } export const FeatureRoutes = { diff --git a/packages/store/scripts/api-schema/schema.json b/packages/store/scripts/api-schema/schema.json index 2557e7665b..769f310ebe 100644 --- a/packages/store/scripts/api-schema/schema.json +++ b/packages/store/scripts/api-schema/schema.json @@ -2011,6 +2011,508 @@ "tags": ["notifications"] } }, + "/v1/users": { + "get": { + "operationId": "usersGetWithWalletsV1", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserWithWallets" + } + } + } + }, + "401": { + "description": "Signer address not provided" + }, + "404": { + "description": "User (wallet) not found" + } + }, + "tags": ["users"] + }, + "delete": { + "operationId": "usersDeleteV1", + "parameters": [], + "responses": { + "200": { + "description": "User deleted" + }, + "401": { + "description": "Signer address not provided" + }, + "404": { + "description": "User (wallet) not found" + } + }, + "tags": ["users"] + } + }, + "/v1/users/wallet": { + "post": { + "operationId": "usersCreateWithWalletV1", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreatedUserWithWallet" + } + } + } + }, + "401": { + "description": "Signer address not provided" + }, + "409": { + "description": "Wallet already exists" + } + }, + "tags": ["users"] + } + }, + "/v1/users/wallet/{walletAddress}": { + "post": { + "operationId": "usersAddWalletToUserV1", + "parameters": [ + { + "name": "walletAddress", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddedToUser" + } + } + } + }, + "401": { + "description": "Signer address not provided" + }, + "404": { + "description": "User not found" + }, + "409": { + "description": "Wallet already exists" + } + }, + "tags": ["users"] + }, + "delete": { + "operationId": "usersDeleteWalletFromUserV1", + "parameters": [ + { + "name": "walletAddress", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Wallet removed from user and deleted" + }, + "401": { + "description": "Signer address not provided" + }, + "404": { + "description": "User OR provided wallet not found" + }, + "409": { + "description": "Cannot remove the current wallet" + } + }, + "tags": ["users"] + } + }, + "/v1/organizations": { + "post": { + "operationId": "organizationsCreateV1", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOrganizationDto" + } + } + } + }, + "responses": { + "200": { + "description": "Organizations created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOrganizationResponse" + } + } + } + }, + "401": { + "description": "Signer address not provided" + }, + "403": { + "description": "Forbidden resource" + }, + "404": { + "description": "User not found." + } + }, + "tags": ["organizations"] + }, + "get": { + "operationId": "organizationsGetV1", + "parameters": [], + "responses": { + "200": { + "description": "Organizations found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GetOrganizationResponse" + } + } + } + } + }, + "401": { + "description": "Signer address not provided" + }, + "403": { + "description": "Forbidden resource" + }, + "404": { + "description": "User not found." + } + }, + "tags": ["organizations"] + } + }, + "/v1/organizations/create-with-user": { + "post": { + "operationId": "organizationsCreateWithUserV1", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOrganizationDto" + } + } + } + }, + "responses": { + "200": { + "description": "Organizations created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOrganizationResponse" + } + } + } + }, + "401": { + "description": "Signer address not provided" + }, + "403": { + "description": "Forbidden resource" + } + }, + "tags": ["organizations"] + } + }, + "/v1/organizations/{id}": { + "get": { + "operationId": "organizationsGetOneV1", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "Organization found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetOrganizationResponse" + } + } + } + }, + "401": { + "description": "Signer address not provided" + }, + "403": { + "description": "Forbidden resource" + }, + "404": { + "description": "Organization not found. OR User not found." + } + }, + "tags": ["organizations"] + }, + "patch": { + "operationId": "organizationsUpdateV1", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateOrganizationDto" + } + } + } + }, + "responses": { + "200": { + "description": "Organization updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateOrganizationResponse" + } + } + } + }, + "401": { + "description": "Signer address not provided OR User is unauthorized" + }, + "403": { + "description": "Forbidden resource" + }, + "404": { + "description": "User not found." + } + }, + "tags": ["organizations"] + }, + "delete": { + "operationId": "organizationsDeleteV1", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "204": { + "description": "Organization deleted" + }, + "401": { + "description": "Signer address not provided OR User is unauthorized" + }, + "403": { + "description": "Forbidden resource" + }, + "404": { + "description": "User not found." + } + }, + "tags": ["organizations"] + } + }, + "/v1/organizations/{orgId}/members/invite": { + "post": { + "operationId": "userOrganizationsInviteUserV1", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "responses": { + "200": { + "description": "Users invited", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Invitation" + } + } + } + } + }, + "401": { + "description": "User not admin OR signer address not provided OR member is not active" + }, + "403": { + "description": "User not authorized" + }, + "409": { + "description": "Too many invites" + } + }, + "tags": ["organizations"] + } + }, + "/v1/organizations/{orgId}/members/accept": { + "post": { + "operationId": "userOrganizationsAcceptInviteV1", + "parameters": [], + "responses": { + "200": { + "description": "Invite accepted" + }, + "403": { + "description": "Signer not authorized" + }, + "404": { + "description": "Signer, organization or membership not found" + }, + "409": { + "description": "User invite not pending" + } + }, + "tags": ["organizations"] + } + }, + "/v1/organizations/{orgId}/members/decline": { + "post": { + "operationId": "userOrganizationsDeclineInviteV1", + "parameters": [], + "responses": { + "200": { + "description": "Invite declined" + }, + "403": { + "description": "Signer not authorized" + }, + "404": { + "description": "Signer, organization or membership not found" + }, + "409": { + "description": "User invite not pending" + } + }, + "tags": ["organizations"] + } + }, + "/v1/organizations/{orgId}/members": { + "get": { + "operationId": "userOrganizationsGetUsersV1", + "parameters": [], + "responses": { + "200": { + "description": "Organization and members list", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserOrganizationsDto" + } + } + } + }, + "403": { + "description": "Signer not authorized" + }, + "404": { + "description": "Signer or organization not found" + } + }, + "tags": ["organizations"] + } + }, + "/v1/organizations/{orgId}/members/{userId}/role": { + "patch": { + "operationId": "userOrganizationsUpdateRoleV1", + "parameters": [], + "responses": { + "200": { + "description": "Role updated" + }, + "401": { + "description": "Signer not active or admin" + }, + "403": { + "description": "Signer not authorized" + }, + "404": { + "description": "Signer, organization or signer/user-to-update membership not found" + }, + "409": { + "description": "Cannot remove last admin" + } + }, + "tags": ["organizations"] + } + }, + "/v1/organizations/{orgId}/members/{userId}": { + "delete": { + "operationId": "userOrganizationsRemoveUserV1", + "parameters": [], + "responses": { + "200": { + "description": "Membership deleted" + }, + "401": { + "description": "Signer not active or admin" + }, + "403": { + "description": "Signer not authorized" + }, + "404": { + "description": "Signer or organization not found" + }, + "409": { + "description": "Cannot remove last admin" + } + }, + "tags": ["organizations"] + } + }, "/v1/chains/{chainId}/owners/{ownerAddress}/safes": { "get": { "operationId": "ownersGetSafesByOwnerV1", @@ -2351,6 +2853,53 @@ "tags": ["safes"] } }, + "/v1/targeted-messaging/outreaches/{outreachId}/chains/{chainId}/safes/{safeAddress}": { + "get": { + "operationId": "targetedMessagingGetTargetedSafeV1", + "parameters": [ + { + "name": "outreachId", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + }, + { + "name": "chainId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "safeAddress", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TargetedSafe" + } + } + } + }, + "404": { + "description": "Safe not targeted." + } + }, + "tags": ["targeted-messaging"] + } + }, "/v1/targeted-messaging/outreaches/{outreachId}/chains/{chainId}/safes/{safeAddress}/signers/{signerAddress}/submissions": { "get": { "operationId": "targetedMessagingGetSubmissionV1", @@ -4754,6 +5303,192 @@ }, "required": ["cloudMessagingToken", "buildNumber", "bundle", "deviceType", "version", "safeRegistrations"] }, + "UserWallet": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "address": { + "type": "string" + } + }, + "required": ["id", "address"] + }, + "UserWithWallets": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "status": { + "type": "number", + "enum": [0, 1] + }, + "wallets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserWallet" + } + } + }, + "required": ["id", "status", "wallets"] + }, + "CreatedUserWithWallet": { + "type": "object", + "properties": { + "id": { + "type": "number" + } + }, + "required": ["id"] + }, + "WalletAddedToUser": { + "type": "object", + "properties": { + "id": { + "type": "number" + } + }, + "required": ["id"] + }, + "CreateOrganizationDto": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + }, + "CreateOrganizationResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "number" + } + }, + "required": ["name", "id"] + }, + "GetOrganizationResponse": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "status": { + "type": "number", + "enum": [1] + }, + "userOrganizations": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["id", "name", "status", "userOrganizations"] + }, + "UpdateOrganizationDto": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "status": { + "type": "number", + "enum": [1] + } + } + }, + "UpdateOrganizationResponse": { + "type": "object", + "properties": { + "id": { + "type": "number" + } + }, + "required": ["id"] + }, + "Invitation": { + "type": "object", + "properties": { + "userId": { + "type": "number" + }, + "orgId": { + "type": "number" + }, + "role": { + "type": "string", + "enum": ["1", "2", "ADMIN", "MEMBER"] + }, + "status": { + "type": "string", + "enum": ["0", "1", "2", "INVITED", "ACTIVE", "DECLINED"] + } + }, + "required": ["userId", "orgId", "role", "status"] + }, + "UserOrganizationUser": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "status": { + "type": "string", + "enum": ["0", "1", "PENDING", "ACTIVE"] + } + }, + "required": ["id", "status"] + }, + "UserOrganization": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "role": { + "type": "string", + "enum": ["1", "2", "ADMIN", "MEMBER"] + }, + "status": { + "type": "string", + "enum": ["0", "1", "2", "INVITED", "ACTIVE", "DECLINED"] + }, + "createdAt": { + "format": "date-time", + "type": "string" + }, + "updatedAt": { + "format": "date-time", + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/UserOrganizationUser" + } + }, + "required": ["id", "role", "status", "createdAt", "updatedAt", "user"] + }, + "UserOrganizationsDto": { + "type": "object", + "properties": { + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserOrganization" + } + } + }, + "required": ["members"] + }, "SafeList": { "type": "object", "properties": { @@ -5027,6 +5762,18 @@ }, "required": ["address", "chainId", "threshold", "owners", "fiatTotal", "queued"] }, + "TargetedSafe": { + "type": "object", + "properties": { + "outreachId": { + "type": "number" + }, + "address": { + "type": "string" + } + }, + "required": ["outreachId", "address"] + }, "Submission": { "type": "object", "properties": { @@ -5432,11 +6179,6 @@ "type": "string", "description": "The URL to the explorer page of the order" }, - "executedSurplusFee": { - "type": "string", - "description": "The amount of fees paid for this order.", - "deprecated": true - }, "executedFee": { "type": "string", "description": "The amount of fees paid for this order." @@ -5473,7 +6215,6 @@ "sellToken", "buyToken", "explorerUrl", - "executedSurplusFee", "executedFee", "executedFeeToken", "owner" @@ -5573,11 +6314,6 @@ "type": "string", "description": "The URL to the explorer page of the order" }, - "executedSurplusFee": { - "type": "string", - "description": "The amount of fees paid for this order.", - "deprecated": true - }, "executedFee": { "type": "string", "description": "The amount of fees paid for this order." @@ -5622,7 +6358,6 @@ "sellToken", "buyToken", "explorerUrl", - "executedSurplusFee", "executedFee", "executedFeeToken", "owner" @@ -5679,12 +6414,6 @@ "nullable": true, "description": "The executed buy token raw amount (no decimals), or null if there are too many parts" }, - "executedSurplusFee": { - "type": "string", - "nullable": true, - "description": "The executed surplus fee raw amount (no decimals), or null if there are too many parts", - "deprecated": true - }, "executedFee": { "type": "string", "nullable": true, diff --git a/packages/store/src/gateway/AUTO_GENERATED/owners.ts b/packages/store/src/gateway/AUTO_GENERATED/owners.ts index df23ea45f5..2a85ad5625 100644 --- a/packages/store/src/gateway/AUTO_GENERATED/owners.ts +++ b/packages/store/src/gateway/AUTO_GENERATED/owners.ts @@ -42,8 +42,9 @@ export type SafeList = { } export const { useOwnersGetSafesByOwnerV1Query, - useOwnersGetAllSafesByOwnerV2Query, useLazyOwnersGetSafesByOwnerV1Query, useOwnersGetAllSafesByOwnerV1Query, useLazyOwnersGetAllSafesByOwnerV1Query, + useOwnersGetAllSafesByOwnerV2Query, + useLazyOwnersGetAllSafesByOwnerV2Query, } = injectedRtkApi diff --git a/packages/store/src/gateway/AUTO_GENERATED/targeted-messages.ts b/packages/store/src/gateway/AUTO_GENERATED/targeted-messages.ts index 5f3ea2d2ea..26f1fa92a5 100644 --- a/packages/store/src/gateway/AUTO_GENERATED/targeted-messages.ts +++ b/packages/store/src/gateway/AUTO_GENERATED/targeted-messages.ts @@ -6,6 +6,15 @@ const injectedRtkApi = api }) .injectEndpoints({ endpoints: (build) => ({ + targetedMessagingGetTargetedSafeV1: build.query< + TargetedMessagingGetTargetedSafeV1ApiResponse, + TargetedMessagingGetTargetedSafeV1ApiArg + >({ + query: (queryArg) => ({ + url: `/v1/targeted-messaging/outreaches/${queryArg.outreachId}/chains/${queryArg.chainId}/safes/${queryArg.safeAddress}`, + }), + providesTags: ['targeted-messaging'], + }), targetedMessagingGetSubmissionV1: build.query< TargetedMessagingGetSubmissionV1ApiResponse, TargetedMessagingGetSubmissionV1ApiArg @@ -30,6 +39,12 @@ const injectedRtkApi = api overrideExisting: false, }) export { injectedRtkApi as cgwApi } +export type TargetedMessagingGetTargetedSafeV1ApiResponse = /** status 200 */ TargetedSafe +export type TargetedMessagingGetTargetedSafeV1ApiArg = { + outreachId: number + chainId: string + safeAddress: string +} export type TargetedMessagingGetSubmissionV1ApiResponse = /** status 200 */ Submission export type TargetedMessagingGetSubmissionV1ApiArg = { outreachId: number @@ -45,6 +60,10 @@ export type TargetedMessagingCreateSubmissionV1ApiArg = { signerAddress: string createSubmissionDto: CreateSubmissionDto } +export type TargetedSafe = { + outreachId: number + address: string +} export type Submission = { outreachId: number targetedSafeId: number @@ -55,6 +74,8 @@ export type CreateSubmissionDto = { completed: boolean } export const { + useTargetedMessagingGetTargetedSafeV1Query, + useLazyTargetedMessagingGetTargetedSafeV1Query, useTargetedMessagingGetSubmissionV1Query, useLazyTargetedMessagingGetSubmissionV1Query, useTargetedMessagingCreateSubmissionV1Mutation, diff --git a/packages/store/src/gateway/AUTO_GENERATED/transactions.ts b/packages/store/src/gateway/AUTO_GENERATED/transactions.ts index 41023c9987..ab6e599701 100644 --- a/packages/store/src/gateway/AUTO_GENERATED/transactions.ts +++ b/packages/store/src/gateway/AUTO_GENERATED/transactions.ts @@ -358,8 +358,6 @@ export type SwapOrderTransactionInfo = { /** The URL to the explorer page of the order */ explorerUrl: string /** The amount of fees paid for this order. */ - executedSurplusFee: string - /** The amount of fees paid for this order. */ executedFee: string /** The token in which the fee was paid, expressed by SURPLUS tokens (BUY tokens for SELL orders and SELL tokens for BUY orders). */ executedFeeToken: string @@ -398,8 +396,6 @@ export type SwapTransferTransactionInfo = { /** The URL to the explorer page of the order */ explorerUrl: string /** The amount of fees paid for this order. */ - executedSurplusFee: string - /** The amount of fees paid for this order. */ executedFee: string /** The token in which the fee was paid, expressed by SURPLUS tokens (BUY tokens for SELL orders and SELL tokens for BUY orders). */ executedFeeToken: TokenInfo @@ -429,8 +425,6 @@ export type TwapOrderTransactionInfo = { /** The executed buy token raw amount (no decimals), or null if there are too many parts */ executedBuyAmount?: string | null /** The executed surplus fee raw amount (no decimals), or null if there are too many parts */ - executedSurplusFee?: string | null - /** The executed surplus fee raw amount (no decimals), or null if there are too many parts */ executedFee?: string | null /** The token in which the fee was paid, expressed by SURPLUS tokens (BUY tokens for SELL orders and SELL tokens for BUY orders). */ executedFeeToken: string