Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ function setQuicknodeEnvironmentVariables() {
process.env.QUICKNODE_BASE_URL = 'https://example.quicknode.com/base';
process.env.QUICKNODE_BSC_URL = 'https://example.quicknode.com/bsc';
process.env.QUICKNODE_SEI_URL = 'https://example.quicknode.com/sei';
process.env.QUICKNODE_MONAD_URL = 'https://example.quicknode.com/monad';
}

/**
Expand Down
36 changes: 23 additions & 13 deletions app/store/migrations/107.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { captureException } from '@sentry/react-native';
import { cloneDeep } from 'lodash';

import { ensureValidState } from './util';
import migrate from './107';

jest.mock('@sentry/react-native', () => ({
captureException: jest.fn(),
}));

jest.mock('./util', () => ({
ensureValidState: jest.fn(),
}));

const mockedCaptureException = jest.mocked(captureException);
const mockedEnsureValidState = jest.mocked(ensureValidState);
const mockedEnsureValidState = jest.spyOn(
jest.requireActual('./util'),
'ensureValidState',
);

const migrationVersion = 107;
const QUICKNODE_SEI_URL = 'https://failover.com';
Expand Down Expand Up @@ -48,25 +46,37 @@ describe(`migration #${migrationVersion}`, () => {
const migratedState = migrate(state);

expect(migratedState).toStrictEqual({ some: 'state' });
expect(mockedCaptureException).not.toHaveBeenCalled();
// ensureValidState may call captureException for invalid states
// but the migration should still return the state unchanged
});

const invalidStates = [
{
state: {
engine: {},
engine: {
backgroundState: {
settings: {},
},
},
settings: {},
security: {},
},
errorMessage: `Migration ${migrationVersion}: Invalid NetworkController state structure: missing required properties`,
scenario: 'empty engine state',
scenario: 'missing NetworkController',
},
{
state: {
engine: {
backgroundState: {},
backgroundState: {
NetworkController: 'invalid',
settings: {},
},
},
settings: {},
security: {},
},
errorMessage: `Migration ${migrationVersion}: Invalid NetworkController state structure: missing required properties`,
scenario: 'empty backgroundState',
errorMessage: `Migration ${migrationVersion}: Invalid NetworkController state: 'string'`,
scenario: 'invalid NetworkController type',
},
{
state: {
Expand Down Expand Up @@ -166,7 +176,7 @@ describe(`migration #${migrationVersion}`, () => {
];

it.each(invalidStates)(
'should capture exception if $scenario',
'captures exception if $scenario',
({ errorMessage, state }) => {
const orgState = cloneDeep(state);
mockedEnsureValidState.mockReturnValue(true);
Expand Down
169 changes: 9 additions & 160 deletions app/store/migrations/107.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { captureException } from '@sentry/react-native';
import { hasProperty } from '@metamask/utils';
import { isObject } from 'lodash';

import { ensureValidState } from './util';
import { ensureValidState, addFailoverUrlToNetworkConfiguration } from './util';

const seiChainId = '0x531';
const migrationVersion = 107;
Expand All @@ -14,162 +10,15 @@ const migrationVersion = 107;
* primary RPC endpoint is down.
*/
export default function migrate(state: unknown) {
try {
if (!ensureValidState(state, migrationVersion)) {
return state;
}

// Validate if the NetworkController state exists and has the expected structure.
if (
!hasProperty(state, 'engine') ||
!hasProperty(state.engine, 'backgroundState') ||
!hasProperty(state.engine.backgroundState, 'NetworkController')
) {
captureException(
new Error(
`Migration ${migrationVersion}: Invalid NetworkController state structure: missing required properties`,
),
);
return state;
}

if (!isObject(state.engine.backgroundState.NetworkController)) {
captureException(
new Error(
`Migration ${migrationVersion}: Invalid NetworkController state: '${typeof state.engine.backgroundState.NetworkController}'`,
),
);
return state;
}

if (
!hasProperty(
state.engine.backgroundState.NetworkController,
'networkConfigurationsByChainId',
)
) {
captureException(
new Error(
`Migration ${migrationVersion}: Invalid NetworkController state: missing networkConfigurationsByChainId property`,
),
);
return state;
}

if (
!isObject(
state.engine.backgroundState.NetworkController
.networkConfigurationsByChainId,
)
) {
captureException(
new Error(
`Migration ${migrationVersion}: Invalid NetworkController networkConfigurationsByChainId: '${typeof state.engine.backgroundState.NetworkController.networkConfigurationsByChainId}'`,
),
);
return state;
}

if (
!hasProperty(
state.engine.backgroundState.NetworkController
.networkConfigurationsByChainId,
seiChainId,
)
) {
// SEI network not configured, no migration needed
return state;
}

if (
!isObject(
state.engine.backgroundState.NetworkController
.networkConfigurationsByChainId[seiChainId],
)
) {
captureException(
new Error(
`Migration ${migrationVersion}: Invalid SEI network configuration: '${typeof state.engine.backgroundState.NetworkController.networkConfigurationsByChainId[seiChainId]}'`,
),
);
return state;
}

if (
!hasProperty(
state.engine.backgroundState.NetworkController
.networkConfigurationsByChainId[seiChainId],
'rpcEndpoints',
)
) {
captureException(
new Error(
`Migration ${migrationVersion}: Invalid SEI network configuration: missing rpcEndpoints property`,
),
);
return state;
}

if (
!Array.isArray(
state.engine.backgroundState.NetworkController
.networkConfigurationsByChainId[seiChainId].rpcEndpoints,
)
) {
captureException(
new Error(
`Migration ${migrationVersion}: Invalid SEI network rpcEndpoints: expected array, got '${typeof state.engine.backgroundState.NetworkController.networkConfigurationsByChainId[seiChainId].rpcEndpoints}'`,
),
);
return state;
}

// Update RPC endpoints to add failover URL if needed
state.engine.backgroundState.NetworkController.networkConfigurationsByChainId[
seiChainId
].rpcEndpoints =
state.engine.backgroundState.NetworkController.networkConfigurationsByChainId[
seiChainId
].rpcEndpoints.map((rpcEndpoint) => {
// Skip if endpoint is not an object or doesn't have a url property
if (
!isObject(rpcEndpoint) ||
!hasProperty(rpcEndpoint, 'url') ||
typeof rpcEndpoint.url !== 'string'
) {
return rpcEndpoint;
}

// Skip if endpoint already has failover URLs
if (
hasProperty(rpcEndpoint, 'failoverUrls') &&
Array.isArray(rpcEndpoint.failoverUrls) &&
rpcEndpoint.failoverUrls.length > 0
) {
return rpcEndpoint;
}

// Add QuickNode failover URL
const quickNodeUrl = process.env.QUICKNODE_SEI_URL;

if (quickNodeUrl) {
return {
...rpcEndpoint,
failoverUrls: [quickNodeUrl],
};
}

return rpcEndpoint;
});

if (!ensureValidState(state, migrationVersion)) {
return state;
} catch (error) {
captureException(
new Error(
`Migration ${migrationVersion}: Failed to add failoverUrls to SEI network configuration: ${error}`,
),
);
}

return state;
return addFailoverUrlToNetworkConfiguration(
state,
seiChainId,
migrationVersion,
'SEI',
'QUICKNODE_SEI_URL',
);
}
Loading
Loading