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,