Skip to content

Commit

Permalink
Pylon option (#1483)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mholtzman authored Mar 16, 2023
1 parent e76e0b4 commit 3c9140d
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 89 deletions.
13 changes: 13 additions & 0 deletions @types/frame/state.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type State = {
networksMeta: {
ethereum: Record<number, NetworkMetadata>
}
mute: Record<MutableNotificationType, boolean>
}
}

Expand Down Expand Up @@ -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: {
Expand Down
52 changes: 52 additions & 0 deletions app/tray/Notify/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,52 @@ import ExtensionConnectNotification from './ExtensionConnect'

const FEE_WARNING_THRESHOLD_USD = 50

const MigrateToPylon = ({ store }) => {
return (
<div className='notifyBoxWrap' onMouseDown={(e) => e.stopPropagation()}>
<div className='notifyBoxSlide'>
<div className='notifyBox'>
<div className='notifyFrameIcon'>
<img src={frameIcon} />
</div>
<div className='notifyTitle'>New RPCs</div>
<div className='notifyBody'>
<div className='notifyBodyBlock'>
<div className='notifySection'>
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.
</div>
</div>
</div>
<div className='notifyInput'>
<div
className='notifyInputOption notifyInputSingleButton'
onMouseDown={() => {
link.send('tray:action', 'migrateToPylonConnections')
link.send('tray:action', 'mutePylonMigrationNotice')
store.notify()
}}
>
<div className='notifyInputOptionText notifyBetaGo'>Enable Pylon</div>
</div>
<div
className='notifyInputOption notifyInputSingleButton'
onMouseDown={() => {
link.send('tray:action', 'mutePylonMigrationNotice')
store.notify()
}}
>
<div className='notifyInputOptionText notifyBetaGo'>Use custom connections</div>
</div>
</div>
</div>
</div>
</div>
)
}

class Notify extends React.Component {
mainnet() {
return (
Expand Down Expand Up @@ -509,6 +555,12 @@ class Notify extends React.Component {
)
} else if (notify === 'betaDisclosure') {
return <div className='notify cardShow'>{this.betaDisclosure()}</div>
} else if (notify === 'migrateToPylon') {
return (
<div className='notify cardShow'>
<MigrateToPylon store={this.store} />
</div>
)
} else if (notify === 'updateOriginChain') {
return (
<div className='notify cardShow' onMouseDown={() => this.store.notify()}>
Expand Down
3 changes: 3 additions & 0 deletions app/tray/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down
25 changes: 25 additions & 0 deletions main/store/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
66 changes: 36 additions & 30 deletions main/store/migrate/migrations/35.ts
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions main/store/state/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
105 changes: 46 additions & 59 deletions test/main/store/migrate/migrations/35.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
})
1 change: 1 addition & 0 deletions test/main/store/migrate/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const createState = () => ({
networksMeta: { ethereum: {} },
accounts: {},
balances: {},
mute: {},
tokens: { known: {} }
}
})
Expand Down

0 comments on commit 3c9140d

Please sign in to comment.