From 3c9140d807d72e4c47149a3a43a8b8de52a941c5 Mon Sep 17 00:00:00 2001 From: Matt Holtzman Date: Thu, 16 Mar 2023 08:45:59 -0400 Subject: [PATCH] Pylon option (#1483) * remove infura and alchemy connections in favor of Pylon * add ability to mute migrate to Pylon notice * add notification * add default connection, just in case --- @types/frame/state.d.ts | 13 +++ app/tray/Notify/index.js | 52 +++++++++ app/tray/index.js | 3 + main/store/actions/index.js | 25 +++++ main/store/migrate/migrations/35.ts | 66 ++++++----- main/store/state/index.js | 1 + test/main/store/migrate/migrations/35.test.js | 105 ++++++++---------- test/main/store/migrate/setup.js | 1 + 8 files changed, 177 insertions(+), 89 deletions(-) diff --git a/@types/frame/state.d.ts b/@types/frame/state.d.ts index ab1653b21..af9851f05 100644 --- a/@types/frame/state.d.ts +++ b/@types/frame/state.d.ts @@ -7,6 +7,7 @@ type State = { networksMeta: { ethereum: Record } + mute: Record } } @@ -72,6 +73,18 @@ interface NativeCurrency { usd?: Rate } +type MutableNotificationType = + | 'alphaWarning' + | 'welcomeWarning' + | 'externalLinkWarning' + | 'explorerWarning' + | 'signerRelockChange' + | 'gasFeeWarning' + | 'betaDisclosure' + | 'onboardingWindow' + | 'migrateToPylon' + | 'signerCompatibilityWarning' + interface GasData { fees: GasFees price: { diff --git a/app/tray/Notify/index.js b/app/tray/Notify/index.js index 3ebca5528..0eefceb67 100644 --- a/app/tray/Notify/index.js +++ b/app/tray/Notify/index.js @@ -11,6 +11,52 @@ import ExtensionConnectNotification from './ExtensionConnect' const FEE_WARNING_THRESHOLD_USD = 50 +const MigrateToPylon = ({ store }) => { + return ( +
e.stopPropagation()}> +
+
+
+ +
+
New RPCs
+
+
+
+ We're migrating our built-in Infura and Alchemy connection presets to our internal RPC + infrastructure called Pylon. If you'd prefer not to have your existing Infura and Alchemy + connections migrated to use Pylon, you will need to obtain an API key from these providers and + set the corresponding URL using the "custom" preset for these connections. +
+
+
+
+
{ + link.send('tray:action', 'migrateToPylonConnections') + link.send('tray:action', 'mutePylonMigrationNotice') + store.notify() + }} + > +
Enable Pylon
+
+
{ + link.send('tray:action', 'mutePylonMigrationNotice') + store.notify() + }} + > +
Use custom connections
+
+
+
+
+
+ ) +} + class Notify extends React.Component { mainnet() { return ( @@ -509,6 +555,12 @@ class Notify extends React.Component { ) } else if (notify === 'betaDisclosure') { return
{this.betaDisclosure()}
+ } else if (notify === 'migrateToPylon') { + return ( +
+ +
+ ) } else if (notify === 'updateOriginChain') { return (
this.store.notify()}> diff --git a/app/tray/index.js b/app/tray/index.js index 15b1f6af5..66fbd7262 100644 --- a/app/tray/index.js +++ b/app/tray/index.js @@ -28,6 +28,9 @@ link.rpc('getState', (err, state) => { const store = appStore(state) link.send('tray:ready') // turn on api link.send('tray:refreshMain') + + if (!store('main.mute.migrateToPylon')) store.notify('migrateToPylon') + store.observer(() => { document.body.classList.remove('dark', 'light') document.body.classList.add('clip', store('main.colorway')) diff --git a/main/store/actions/index.js b/main/store/actions/index.js index a6c48c903..65a6ce2f9 100644 --- a/main/store/actions/index.js +++ b/main/store/actions/index.js @@ -779,6 +779,31 @@ module.exports = { }) u('windows.dash.showing', () => true) }, + mutePylonMigrationNotice: (u) => { + u('main.mute.migrateToPylon', () => true) + }, + migrateToPylonConnections: (u) => { + const pylonChains = ['1', '5', '10', '137', '42161', '11155111'] + + const switchToPylon = (connection = {}) => { + if (connection.current === 'custom' && connection.custom === '') { + connection.current = 'pylon' + } + } + + u('main.networks.ethereum', (chains) => { + Object.entries(chains).forEach(([id, chain]) => { + if (pylonChains.includes(id)) { + const { primary, secondary } = chain.connection + + switchToPylon(primary) + switchToPylon(secondary) + } + }) + + return chains + }) + }, completeOnboarding: (u) => { u('main.mute.onboardingWindow', () => true) u('windows.onboard.showing', () => false) diff --git a/main/store/migrate/migrations/35.ts b/main/store/migrate/migrations/35.ts index b4b9b03db..e7c792387 100644 --- a/main/store/migrate/migrations/35.ts +++ b/main/store/migrate/migrations/35.ts @@ -1,43 +1,49 @@ -function removeRpcConnection(connection: Connection, replaceWithPylon = true) { - const isServiceRpc = connection.current === 'infura' || connection.current === 'alchemy' - - return { - ...connection, - // turn off existing connections to Infura or Alchemy if they're not being replaced by Pylon - on: connection.on && (!isServiceRpc || replaceWithPylon), - current: isServiceRpc ? (replaceWithPylon ? 'pylon' : 'custom') : connection.current - } -} +const pylonChainIds = ['1', '5', '10', '137', '42161', '11155111'] +const retiredChainIds = ['3', '4', '42'] +const chainsToMigrate = [...pylonChainIds, ...retiredChainIds] + +export default function (initial: State) { + // disable all Infura and Alchemy connections; these may later be + // replaced by connections to Pylon if the user opts in, otherwise they will be left as + // custom connections to be specified by the user -function updateChain(chain: Network, replaceWithPylon = true) { - const { primary, secondary } = chain.connection + let showMigrationWarning = false - const updatedChain = { - ...chain, - connection: { - ...chain.connection, - primary: removeRpcConnection(primary, replaceWithPylon), - secondary: removeRpcConnection(secondary, replaceWithPylon) + const removeRpcConnection = (connection: Connection) => { + const isServiceRpc = connection.current === 'infura' || connection.current === 'alchemy' + + showMigrationWarning = showMigrationWarning || isServiceRpc + + return { + ...connection, + current: isServiceRpc ? 'custom' : connection.current, + custom: isServiceRpc ? '' : connection.custom } } - return updatedChain -} + const updateChain = (chain: Network) => { + const { primary, secondary } = chain.connection + + const updatedChain = { + ...chain, + connection: { + ...chain.connection, + primary: removeRpcConnection(primary), + secondary: removeRpcConnection(secondary) + } + } + + return updatedChain + } -export default function (initial: State) { const chains = Object.entries(initial.main.networks.ethereum) - // migrate existing Infura and Alchemy connections to use Pylon where applicable - const pylonChains = chains - .filter(([id]) => ['1', '5', '10', '137', '42161', '11155111'].includes(id)) + const migratedChains = chains + .filter(([id]) => chainsToMigrate.includes(id)) .map(([id, chain]) => [id, updateChain(chain)]) - // these connections previously used Infura and Alchemy and are not supported by Pylon - const retiredChains = chains - .filter(([id]) => ['3', '4', '42'].includes(id)) - .map(([id, chain]) => [id, updateChain(chain, false)]) - - initial.main.networks.ethereum = Object.fromEntries([...chains, ...pylonChains, ...retiredChains]) + initial.main.networks.ethereum = Object.fromEntries([...chains, ...migratedChains]) + initial.main.mute.migrateToPylon = !showMigrationWarning return initial } diff --git a/main/store/state/index.js b/main/store/state/index.js index df9332d09..aba23413e 100644 --- a/main/store/state/index.js +++ b/main/store/state/index.js @@ -186,6 +186,7 @@ const initial = { gasFeeWarning: main('mute.gasFeeWarning', false), betaDisclosure: main('mute.betaDisclosure', false), onboardingWindow: main('mute.onboardingWindow', false), + migrateToPylon: main('mute.migrateToPylon', false), signerCompatibilityWarning: main('mute.signerCompatibilityWarning', false) }, shortcuts: { diff --git a/test/main/store/migrate/migrations/35.test.js b/test/main/store/migrate/migrations/35.test.js index 9ba44066c..c5c5804dd 100644 --- a/test/main/store/migrate/migrations/35.test.js +++ b/test/main/store/migrate/migrations/35.test.js @@ -3,10 +3,13 @@ import { createState, initChainState } from '../setup' const providers = ['infura', 'alchemy'] -const pylonChains = [ +const migratedChains = [ [1, 'Mainnet'], + [3, 'Ropsten'], + [4, 'Rinkeby'], [5, 'Goerli'], [10, 'Optimism'], + [42, 'Kovan'], [137, 'Polygon'], [42161, 'Arbitrum'], [11155111, 'Sepolia'] @@ -18,13 +21,14 @@ beforeEach(() => { state = createState() }) -pylonChains.forEach(([id, chainName]) => { +migratedChains.forEach(([id, chainName]) => { providers.forEach((provider) => { - it(`should migrate a primary ${chainName} ${provider} connection to use Pylon`, () => { + it(`should remove the RPC for a primary ${chainName} ${provider} connection`, () => { initChainState(state, id) + state.main.networks.ethereum[id].connection = { - primary: { current: provider, on: true, connected: false }, - secondary: { current: 'custom', on: false, connected: false } + primary: { current: provider }, + secondary: { current: 'custom', custom: 'myrpc' } } const updatedState = migrate(state) @@ -33,15 +37,18 @@ pylonChains.forEach(([id, chainName]) => { connection: { primary, secondary } } = updatedState.main.networks.ethereum[id] - expect(primary.current).toBe('pylon') + expect(primary.current).toBe('custom') + expect(primary.custom).toBe('') expect(secondary.current).toBe('custom') + expect(secondary.custom).toBe('myrpc') }) - it(`should migrate a secondary ${chainName} ${provider} connection to use Pylon`, () => { + it(`should remove the RPC for a secondary ${chainName} ${provider} connection`, () => { initChainState(state, id) + state.main.networks.ethereum[id].connection = { - primary: { current: 'local', on: true, connected: false }, - secondary: { current: provider, on: false, connected: false } + primary: { current: 'local', on: true }, + secondary: { current: provider, on: false } } const updatedState = migrate(state) @@ -51,66 +58,21 @@ pylonChains.forEach(([id, chainName]) => { } = updatedState.main.networks.ethereum[id] expect(primary.current).toBe('local') - expect(secondary.current).toBe('pylon') - }) - }) -}) - -// these chains will not be supported by Pylon -const retiredChains = [ - [3, 'Ropsten'], - [4, 'Rinkeby'], - [42, 'Kovan'] -] - -retiredChains.forEach(([id, chainName]) => { - providers.forEach((provider) => { - it(`should remove a primary ${chainName} ${provider} connection`, () => { - initChainState(state, id) - state.main.networks.ethereum[id].connection = { - primary: { current: provider, on: true, connected: false }, - secondary: { current: 'custom', on: false, connected: false } - } - - const updatedState = migrate(state) - - const { - connection: { primary } - } = updatedState.main.networks.ethereum[id] - - expect(primary.current).toBe('custom') - expect(primary.on).toBe(false) - }) - - it(`should remove a secondary ${chainName} ${provider} connection`, () => { - initChainState(state, id) - state.main.networks.ethereum[id].connection = { - primary: { current: 'local', on: true, connected: false }, - secondary: { current: provider, on: false, connected: false } - } - - const updatedState = migrate(state) - - const { - connection: { secondary } - } = updatedState.main.networks.ethereum[id] - expect(secondary.current).toBe('custom') - expect(secondary.on).toBe(false) + expect(secondary.custom).toBe('') }) }) }) it('should not migrate an existing custom infura connection on a Pylon chain', () => { initChainState(state, 10) + state.main.networks.ethereum[10].connection = { primary: { current: 'custom', - custom: 'https://optimism-mainnet.infura.io/v3/myapikey', - on: true, - connected: false + custom: 'https://optimism-mainnet.infura.io/v3/myapikey' }, - secondary: { current: 'custom', on: false, connected: false } + secondary: { current: 'custom' } } const updatedState = migrate(state) @@ -120,7 +82,32 @@ it('should not migrate an existing custom infura connection on a Pylon chain', ( } = updatedState.main.networks.ethereum[10] expect(primary.current).toBe('custom') - expect(primary.on).toBe(true) expect(primary.custom).toBe('https://optimism-mainnet.infura.io/v3/myapikey') expect(secondary.current).toBe('custom') }) + +it('should show the migration warning if any Infura or Alchemy connections were updated', () => { + initChainState(state, 1) + + state.main.networks.ethereum[1].connection = { + primary: { current: 'infura', on: true }, + secondary: { current: 'custom', custom: 'myrpc', on: false } + } + + const updatedState = migrate(state) + + expect(updatedState.main.mute.migrateToPylon).toBe(false) +}) + +it('should not show the migration warning if the user has no Infura or Alchemy connections', () => { + initChainState(state, 1) + + state.main.networks.ethereum[1].connection = { + primary: { current: 'local', on: true }, + secondary: { current: 'custom', custom: 'myrpc', on: false } + } + + const updatedState = migrate(state) + + expect(updatedState.main.mute.migrateToPylon).toBe(true) +}) diff --git a/test/main/store/migrate/setup.js b/test/main/store/migrate/setup.js index 51bcfaefa..375dd0ca8 100644 --- a/test/main/store/migrate/setup.js +++ b/test/main/store/migrate/setup.js @@ -5,6 +5,7 @@ export const createState = () => ({ networksMeta: { ethereum: {} }, accounts: {}, balances: {}, + mute: {}, tokens: { known: {} } } })