diff --git a/app/components/Base/RemoteImage/index.test.tsx b/app/components/Base/RemoteImage/index.test.tsx index 429dfeb7ca5..65691852ebe 100644 --- a/app/components/Base/RemoteImage/index.test.tsx +++ b/app/components/Base/RemoteImage/index.test.tsx @@ -6,7 +6,7 @@ jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), useSelector: jest .fn() - .mockImplementation(() => 'https://gateway.pinata.cloud/ipfs/'), + .mockImplementation(() => 'https://dweb.link/ipfs/'), })); jest.mock('../../../components/hooks/useIpfsGateway', () => jest.fn()); diff --git a/app/components/UI/AssetIcon/index.test.tsx b/app/components/UI/AssetIcon/index.test.tsx index 84c0941722a..b6541f8ffa8 100644 --- a/app/components/UI/AssetIcon/index.test.tsx +++ b/app/components/UI/AssetIcon/index.test.tsx @@ -10,7 +10,7 @@ const mockInitialState = { ...backgroundState, PreferencesController: { featureFlags: {}, - ipfsGateway: 'https://gateway.pinata.cloud/ipfs/', + ipfsGateway: 'https://dweb.link/ipfs/', lostIdentities: {}, selectedAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', useTokenDetection: true, @@ -25,7 +25,7 @@ const mockInitialState = { _W: { featureFlags: {}, frequentRpcList: [], - ipfsGateway: 'https://gateway.pinata.cloud/ipfs/', + ipfsGateway: 'https://dweb.link/ipfs/', lostIdentities: {}, selectedAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', useTokenDetection: true, diff --git a/app/components/UI/Swaps/components/TokenIcon.test.js b/app/components/UI/Swaps/components/TokenIcon.test.js index f8f0bc4adb3..476343f4329 100644 --- a/app/components/UI/Swaps/components/TokenIcon.test.js +++ b/app/components/UI/Swaps/components/TokenIcon.test.js @@ -13,7 +13,7 @@ describe('TokenIcon component', () => { const icon = shallow( , ); expect(icon).toMatchSnapshot(); @@ -27,7 +27,7 @@ describe('TokenIcon component', () => { , ); expect(iconMedium).toMatchSnapshot(); diff --git a/app/components/UI/Swaps/components/TokenSelectButton.test.js b/app/components/UI/Swaps/components/TokenSelectButton.test.js index 328501a798a..376f482d497 100644 --- a/app/components/UI/Swaps/components/TokenSelectButton.test.js +++ b/app/components/UI/Swaps/components/TokenSelectButton.test.js @@ -19,7 +19,7 @@ describe('TokenSelectButton component', () => { , ); expect(icon).toMatchSnapshot(); @@ -27,7 +27,7 @@ describe('TokenSelectButton component', () => { , ); diff --git a/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap b/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap index 8b00e7564fd..3ebab929b29 100644 --- a/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap +++ b/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap @@ -57,7 +57,7 @@ exports[`TokenIcon component should Render correctly 4`] = ` onError={[Function]} source={ { - "uri": "https://gateway.pinata.cloud/ipfs/QmNYVMm3iC7HEoxfvxsZbRoapdjDHj9EREFac4BPeVphSJ", + "uri": "https://dweb.link/ipfs/QmNYVMm3iC7HEoxfvxsZbRoapdjDHj9EREFac4BPeVphSJ", } } style={ @@ -146,7 +146,7 @@ exports[`TokenIcon component should Render correctly 8`] = ` onError={[Function]} source={ { - "uri": "https://gateway.pinata.cloud/ipfs/QmNYVMm3iC7HEoxfvxsZbRoapdjDHj9EREFac4BPeVphSJ", + "uri": "https://dweb.link/ipfs/QmNYVMm3iC7HEoxfvxsZbRoapdjDHj9EREFac4BPeVphSJ", } } style={ diff --git a/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap b/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap index 8c6472bfbac..1c01f4d06e1 100644 --- a/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap +++ b/app/components/UI/Swaps/components/__snapshots__/TokenSelectButton.test.js.snap @@ -71,7 +71,7 @@ exports[`TokenSelectButton component should Render correctly 4`] = ` } > @@ -95,7 +95,7 @@ exports[`TokenSelectButton component should Render correctly 5`] = ` } > diff --git a/app/components/Views/confirmations/Send/index.test.tsx b/app/components/Views/confirmations/Send/index.test.tsx index a1d650918ef..297c420cb78 100644 --- a/app/components/Views/confirmations/Send/index.test.tsx +++ b/app/components/Views/confirmations/Send/index.test.tsx @@ -65,7 +65,7 @@ const initialState: DeepPartial = { }, PreferencesController: { featureFlags: {}, - ipfsGateway: 'https://gateway.pinata.cloud/ipfs/', + ipfsGateway: 'https://dweb.link/ipfs/', lostIdentities: {}, selectedAddress: MOCK_ADDRESS_2, useTokenDetection: true, diff --git a/app/constants/network.js b/app/constants/network.js index 5a804714de8..90e7925de06 100644 --- a/app/constants/network.js +++ b/app/constants/network.js @@ -11,7 +11,7 @@ export const RPC = NetworkType.rpc; export const NO_RPC_BLOCK_EXPLORER = 'NO_BLOCK_EXPLORER'; export const PRIVATENETWORK = 'PRIVATENETWORK'; export const DEFAULT_MAINNET_CUSTOM_NAME = 'Ethereum Main Custom'; -export const IPFS_DEFAULT_GATEWAY_URL = 'https://gateway.pinata.cloud/ipfs/'; +export const IPFS_DEFAULT_GATEWAY_URL = 'https://dweb.link/ipfs/'; /** * @enum {string} diff --git a/app/core/AppConstants.ts b/app/core/AppConstants.ts index 8aea9a7cb72..85d04769bcf 100644 --- a/app/core/AppConstants.ts +++ b/app/core/AppConstants.ts @@ -12,7 +12,7 @@ export default { DEFAULT_SEARCH_ENGINE: 'DuckDuckGo', TX_CHECK_BACKGROUND_FREQUENCY: 30000, IPFS_OVERRIDE_PARAM: 'mm_override', - IPFS_DEFAULT_GATEWAY_URL: 'https://gateway.pinata.cloud/ipfs/', + IPFS_DEFAULT_GATEWAY_URL: 'https://dweb.link/ipfs/', IPNS_DEFAULT_GATEWAY_URL: 'https://gateway.pinata.cloud/ipns/', SWARM_DEFAULT_GATEWAY_URL: 'https://swarm-gateways.net/bzz:/', supportedTLDs: ['eth', 'xyz', 'test'], diff --git a/app/store/migrations/056.test.ts b/app/store/migrations/056.test.ts new file mode 100644 index 00000000000..775de63d6f6 --- /dev/null +++ b/app/store/migrations/056.test.ts @@ -0,0 +1,119 @@ +import migrate from './056'; +import { merge } from 'lodash'; +import { captureException } from '@sentry/react-native'; +import initialRootState from '../../util/test/initial-root-state'; +import mockedEngine from '../../core/__mocks__/MockedEngine'; + +jest.mock('@sentry/react-native', () => ({ + captureException: jest.fn(), +})); +const mockedCaptureException = jest.mocked(captureException); + +jest.mock('../../core/Engine', () => ({ + init: () => mockedEngine.init(), +})); + +describe('Migration #56', () => { + beforeEach(() => { + jest.restoreAllMocks(); + jest.resetAllMocks(); + }); + + const invalidStates = [ + { + state: null, + errorMessage: "FATAL ERROR: Migration 56: Invalid state error: 'object'", + scenario: 'state is invalid', + }, + { + state: merge({}, initialRootState, { + engine: null, + }), + errorMessage: + "FATAL ERROR: Migration 56: Invalid engine state error: 'object'", + scenario: 'engine state is invalid', + }, + { + state: merge({}, initialRootState, { + engine: { + backgroundState: null, + }, + }), + errorMessage: + "FATAL ERROR: Migration 56: Invalid engine backgroundState error: 'object'", + scenario: 'backgroundState is invalid', + }, + { + state: merge({}, initialRootState, { + engine: { + backgroundState: { PreferencesController: null }, + }, + }), + errorMessage: + "FATAL ERROR: Migration 56: Invalid PreferencesController state error: 'object'", + scenario: 'PreferencesController is invalid', + }, + ]; + + for (const { errorMessage, scenario, state } of invalidStates) { + it(`should capture exception if ${scenario}`, () => { + const newState = migrate(state); + + expect(newState).toStrictEqual(state); + expect(mockedCaptureException).toHaveBeenCalledWith(expect.any(Error)); + expect(mockedCaptureException.mock.calls[0][0].message).toBe( + errorMessage, + ); + }); + } + + it('updates decomissioned ipfsGateway to new default', () => { + const oldState = { + engine: { + backgroundState: { + PreferencesController: { + ipfsGateway: 'https://cloudflare-ipfs.com/ipfs/', + }, + }, + }, + }; + + const expectedState = { + engine: { + backgroundState: { + PreferencesController: { + ipfsGateway: 'https://dweb.link/ipfs/', + }, + }, + }, + }; + + const migratedState = migrate(oldState); + expect(migratedState).toStrictEqual(expectedState); + }); + + it('does not change ipfsGateway if not decomissioned', () => { + const oldState = { + engine: { + backgroundState: { + PreferencesController: { + ipfsGateway: 'https://ipfs.io/ipfs/', + }, + }, + }, + }; + + const expectedState = { + engine: { + backgroundState: { + PreferencesController: { + ipfsGateway: 'https://ipfs.io/ipfs/', + }, + }, + }, + }; + + const migratedState = migrate(oldState); + expect(migratedState).toStrictEqual(expectedState); + }); +}); diff --git a/app/store/migrations/056.ts b/app/store/migrations/056.ts new file mode 100644 index 00000000000..df29f5ba5e6 --- /dev/null +++ b/app/store/migrations/056.ts @@ -0,0 +1,31 @@ +import { captureException } from '@sentry/react-native'; +import { isObject } from '@metamask/utils'; +import { ensureValidState } from './util'; + +export default function migrate(state: unknown) { + if (!ensureValidState(state, 56)) { + // Increment the migration number as appropriate + return state; + } + + const preferencesController = + state.engine.backgroundState.PreferencesController; + + if (!isObject(preferencesController)) { + captureException( + new Error( + `FATAL ERROR: Migration 56: Invalid PreferencesController state error: '${typeof preferencesController}'`, + ), + ); + return state; + } + + const decommisionedIpfsGateway = 'https://cloudflare-ipfs.com/ipfs/'; + const newDefaultIpfsGateway = 'https://dweb.link/ipfs/'; + + if (decommisionedIpfsGateway === preferencesController?.ipfsGateway) { + preferencesController.ipfsGateway = newDefaultIpfsGateway; + } + // Return the modified state + return state; +} diff --git a/app/store/migrations/index.ts b/app/store/migrations/index.ts index c767d71edf6..4f8e9e30ee9 100644 --- a/app/store/migrations/index.ts +++ b/app/store/migrations/index.ts @@ -56,6 +56,7 @@ import migration52 from './052'; import migration53 from './053'; import migration54 from './054'; import migration55 from './055'; +import migration56 from './056'; type MigrationFunction = (state: unknown) => unknown; type AsyncMigrationFunction = (state: unknown) => Promise; @@ -124,6 +125,7 @@ export const migrationList: MigrationsList = { 53: migration53, 54: migration54, 55: migration55, + 56: migration56, }; // Enable both synchronous and asynchronous migrations diff --git a/app/util/ipfs-gateways.json b/app/util/ipfs-gateways.json index 20e6d4f8b11..7e4d103f836 100644 --- a/app/util/ipfs-gateways.json +++ b/app/util/ipfs-gateways.json @@ -18,5 +18,10 @@ "value": "https://gateway.pinata.cloud/ipfs/", "key": 24, "label": "https://gateway.pinata.cloud/ipfs/" + }, + { + "value": "https://dweb.link/ipfs/", + "key": 30, + "label": "https://dweb.link/ipfs/" } ] diff --git a/app/util/sentry/utils.test.ts b/app/util/sentry/utils.test.ts index a80ca2868f7..34486095c9f 100644 --- a/app/util/sentry/utils.test.ts +++ b/app/util/sentry/utils.test.ts @@ -252,7 +252,7 @@ describe('captureSentryFeedback', () => { name: 'Account 1', }, }, - ipfsGateway: 'https://gateway.pinata.cloud/ipfs/', + ipfsGateway: 'https://dweb.link/ipfs/', isIpfsGatewayEnabled: true, isMultiAccountBalancesEnabled: true, lostIdentities: {}, diff --git a/app/util/test/initial-background-state.json b/app/util/test/initial-background-state.json index 87e02448df0..529ccf1750b 100644 --- a/app/util/test/initial-background-state.json +++ b/app/util/test/initial-background-state.json @@ -156,7 +156,7 @@ "PreferencesController": { "featureFlags": {}, "identities": {}, - "ipfsGateway": "https://gateway.pinata.cloud/ipfs/", + "ipfsGateway": "https://dweb.link/ipfs/", "lostIdentities": {}, "selectedAddress": "", "useTokenDetection": true, diff --git a/e2e/fixtures/fixture-builder.js b/e2e/fixtures/fixture-builder.js index c4e987b3f01..a2a26ad1b07 100644 --- a/e2e/fixtures/fixture-builder.js +++ b/e2e/fixtures/fixture-builder.js @@ -277,7 +277,7 @@ class FixtureBuilder { importTime: 1684232000456, }, }, - ipfsGateway: 'https://gateway.pinata.cloud/ipfs/', + ipfsGateway: 'https://dweb.link/ipfs/', lostIdentities: {}, selectedAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', useTokenDetection: true, @@ -298,7 +298,7 @@ class FixtureBuilder { importTime: 1684232000456, }, }, - ipfsGateway: 'https://gateway.pinata.cloud/ipfs/', + ipfsGateway: 'https://dweb.link/ipfs/', lostIdentities: {}, selectedAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', useTokenDetection: true,