Skip to content

Commit 5a55262

Browse files
authored
[core-v2.1.0] - Feature: Update Wallets without Re-Initialization (#906)
* Add update wallet module action to API * Increment version * Update core version in react package
1 parent b28cfc0 commit 5a55262

File tree

13 files changed

+142
-45
lines changed

13 files changed

+142
-45
lines changed

packages/core/README.md

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,6 @@ Putting it all together, here is an example initialization with the injected wal
9595
import Onboard from '@web3-onboard/core'
9696
import injectedModule from '@web3-onboard/injected-wallets'
9797

98-
const ETH_MAINNET_RPC = `https://mainnet.infura.io/v3/${INFURA_KEY}`
99-
const ETH_RINKEBY_RPC = `https://rinkeby.infura.io/v3/${INFURA_KEY}`
100-
const MATIC_MAINNET_RPC = 'https://matic-mainnet.chainstacklabs.com'
101-
10298
const injected = injectedModule()
10399

104100
const onboard = Onboard({
@@ -239,11 +235,13 @@ Onboard currently keeps track of the following state:
239235

240236
- `wallets`: The wallets connected to Onboard
241237
- `chains`: The chains that Onboard has been initialized with
238+
- `walletModules`: The wallet modules that are currently set and will be rendered in the wallet selection modal
242239

243240
```typescript
244241
type AppState = {
245242
chains: Chain[]
246243
wallets: WalletState[]
244+
walletModules: WalletModule[]
247245
}
248246

249247
type WalletState = {
@@ -273,14 +271,24 @@ type ConnectedChain = {
273271

274272
type ChainId = string
275273
type TokenSymbol = string
274+
275+
type WalletModule {
276+
label: string
277+
getIcon: () => Promise<string>
278+
getInterface: (helpers: GetInterfaceHelpers) => Promise<WalletInterface>
279+
}
276280
```
277281

282+
### Get Current State
283+
278284
The current state of Onboard can be accessed at any time using the `state.get()` method:
279285

280286
```javascript
281287
const currentState = onboard.state.get()
282288
```
283289

290+
### Subscribe to State Updates
291+
284292
State can also be subscribed to using the `state.select()` method. The `select` method will return an [RXJS Observable](https://rxjs.dev/guide/observable). Understanding of RXJS observables is not necessary to subscribe to state updates, but allows for composable functionality if wanted. The key point to understand is that if you subscribe for updates, remember to unsubscribe when you are finished to prevent memory leaks.
285293

286294
To subscribe to all state updates, call the `select` method with no arguments:
@@ -307,6 +315,43 @@ const { unsubscribe } = wallets.subscribe(update =>
307315
unsubscribe()
308316
```
309317

318+
### Actions to Modify State
319+
320+
A limited subset of internal actions are exposed to update the Onboard state.
321+
322+
**setWalletModules**
323+
For updating the wallets that are displayed in the wallet selection modal. This can be used if the wallets you want to support is conditional on another user action within your app. The `setWalletModules` action is called with an updated array of wallets (the same wallets that are passed in on initialization)
324+
325+
```typescript
326+
import Onboard from '@web3-onboard/core'
327+
import injectedModule from '@web3-onboard/injected-wallets'
328+
import ledgerModule from '@web3-onboard/ledger'
329+
import trezorModule from '@web3-onboard/trezor'
330+
331+
const injected = injectedModule()
332+
const ledger = ledgerModule()
333+
const trezor = trezorModule({
334+
email: '<EMAIL_CONTACT>',
335+
appUrl: '<APP_URL>'
336+
})
337+
338+
// initialize with injected and hardware wallets
339+
const onboard = Onboard({
340+
wallets: [injected, trezor, ledger],
341+
chains: [
342+
{
343+
id: '0x1',
344+
token: 'ETH',
345+
label: 'Ethereum Mainnet',
346+
rpcUrl: `https://mainnet.infura.io/v3/${INFURA_ID}`
347+
}
348+
]
349+
})
350+
351+
// then after a user action, you may decide to only display hardware wallets on the next call to onboard.connectWallet
352+
onboard.state.actions.setWalletModules([ledger, trezor])
353+
```
354+
310355
## Setting the User's Chain/Network
311356

312357
When initializing Onboard you define a list of chains/networks that your app supports. If you would like to prompt the user to switch to one of those chains, you can use the `setChain` method on an initialized instance of Onboard:

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@web3-onboard/core",
3-
"version": "2.0.11",
3+
"version": "2.1.0",
44
"scripts": {
55
"build": "rollup -c",
66
"dev": "rollup -c -w",

packages/core/src/constants.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { AppState } from './types'
22

33
export const APP_INITIAL_STATE: AppState = {
4-
wallets: [],
5-
chains: []
4+
chains: [],
5+
walletModules: [],
6+
wallets: []
67
}
78

89
export const STORAGE_KEYS = {

packages/core/src/index.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import type { WalletModule } from '@web3-onboard/common'
21
import { SofiaProRegular } from '@web3-onboard/common'
32
import connectWallet from './connect'
43
import disconnectWallet from './disconnect'
54
import setChain from './chain'
65
import { state } from './store'
7-
import { addChains } from './store/actions'
6+
import { addChains, setWalletModules } from './store/actions'
87
import { reset$, internalState$ } from './streams'
98
import { validateInitOptions } from './validation'
109
import initI18N from './i18n'
@@ -17,7 +16,13 @@ const API = {
1716
connectWallet,
1817
disconnectWallet,
1918
setChain,
20-
state
19+
state: {
20+
get: state.get,
21+
select: state.select,
22+
actions: {
23+
setWalletModules
24+
}
25+
}
2126
}
2227

2328
export type {
@@ -56,27 +61,16 @@ function init(options: InitOptions): OnboardAPI {
5661
}
5762

5863
const device = getDeviceInfo()
59-
60-
const walletModules = wallets.reduce((acc, walletInit) => {
61-
const initialized = walletInit({ device })
62-
63-
if (initialized) {
64-
// injected wallets is an array of wallets
65-
acc.push(...(Array.isArray(initialized) ? initialized : [initialized]))
66-
}
67-
68-
return acc
69-
}, [] as WalletModule[])
70-
7164
const app = svelteInstance || mountApp()
7265

7366
internalState$.next({
7467
appMetadata,
7568
svelteInstance: app,
76-
walletModules,
7769
device
7870
})
7971

72+
setWalletModules(wallets)
73+
8074
return API
8175
}
8276

packages/core/src/store/actions.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
1-
import type { Chain } from '@web3-onboard/common'
1+
import type { Chain, WalletInit } from '@web3-onboard/common'
2+
import { internalState$ } from '../streams'
23

34
import type {
45
Account,
56
AddChainsAction,
67
AddWalletAction,
78
RemoveWalletAction,
89
ResetStoreAction,
10+
SetWalletModulesAction,
911
UpdateAccountAction,
1012
UpdateWalletAction,
1113
WalletState
1214
} from '../types'
15+
import { initializeWalletModules } from '../utils'
1316

14-
import { validateString, validateWallet } from '../validation'
17+
import {
18+
validateString,
19+
validateWallet,
20+
validateWalletInit
21+
} from '../validation'
1522

1623
import {
1724
ADD_CHAINS,
1825
UPDATE_WALLET,
1926
RESET_STORE,
2027
ADD_WALLET,
2128
REMOVE_WALLET,
22-
UPDATE_ACCOUNT
29+
UPDATE_ACCOUNT,
30+
SET_WALLET_MODULES
2331
} from './constants'
2432
import { dispatch } from './index'
2533

@@ -112,3 +120,23 @@ export function resetStore(): void {
112120

113121
dispatch(action as ResetStoreAction)
114122
}
123+
124+
export function setWalletModules(wallets: WalletInit[]): void {
125+
const error = validateWalletInit(wallets)
126+
127+
if (error) {
128+
throw error
129+
}
130+
131+
const modules = initializeWalletModules(
132+
wallets,
133+
internalState$.getValue().device
134+
)
135+
136+
const action = {
137+
type: SET_WALLET_MODULES,
138+
payload: modules
139+
}
140+
141+
dispatch(action as SetWalletModulesAction)
142+
}

packages/core/src/store/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export const ADD_WALLET = 'add_wallet'
44
export const UPDATE_WALLET = 'update_wallet'
55
export const REMOVE_WALLET = 'remove_wallet'
66
export const UPDATE_ACCOUNT = 'update_account'
7+
export const SET_WALLET_MODULES = 'set_wallet_modules'

packages/core/src/store/index.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { BehaviorSubject, Subject, Observable } from 'rxjs'
22
import { distinctUntilKeyChanged, pluck, filter } from 'rxjs/operators'
33

4-
import type { Chain } from '@web3-onboard/common'
4+
import type { Chain, WalletModule } from '@web3-onboard/common'
55

66
import type {
77
AppState,
@@ -18,18 +18,13 @@ import {
1818
UPDATE_WALLET,
1919
REMOVE_WALLET,
2020
RESET_STORE,
21-
UPDATE_ACCOUNT
21+
UPDATE_ACCOUNT,
22+
SET_WALLET_MODULES
2223
} from './constants'
2324

2425
import { APP_INITIAL_STATE } from '../constants'
2526
import { notNullish } from '../utils'
2627

27-
// observable to log actions or do sideeffects after every state change
28-
export const actions$ = new Subject<{
29-
action: Action
30-
state: AppState
31-
}>()
32-
3328
function reducer(state: AppState, action: Action): AppState {
3429
const { type, payload } = action
3530

@@ -103,6 +98,13 @@ function reducer(state: AppState, action: Action): AppState {
10398
}
10499
}
105100

101+
case SET_WALLET_MODULES: {
102+
return {
103+
...state,
104+
walletModules: payload as WalletModule[]
105+
}
106+
}
107+
106108
case RESET_STORE:
107109
return APP_INITIAL_STATE
108110

@@ -118,8 +120,6 @@ _stateUpdates.subscribe(_store)
118120

119121
export function dispatch(action: Action): void {
120122
const state = _store.getValue()
121-
actions$.next({ action, state })
122-
123123
_stateUpdates.next(reducer(state, action))
124124
}
125125

packages/core/src/streams.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export const disconnectWallet$ = new Subject<WalletState['label']>()
1919

2020
export const internalState$ = new BehaviorSubject<InternalState>({
2121
svelteInstance: null,
22-
walletModules: [],
2322
appMetadata: null,
2423
device: null
2524
})

packages/core/src/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ export type Address = string
9090

9191
export interface AppState {
9292
chains: Chain[]
93+
walletModules: WalletModule[]
9394
wallets: WalletState[]
9495
}
9596

9697
export type InternalState = {
9798
svelteInstance: SvelteComponent | null
98-
walletModules: WalletModule[]
9999
appMetadata: AppMetadata | null
100100
device: Device | null
101101
}
@@ -112,6 +112,7 @@ export type Action =
112112
| RemoveWalletAction
113113
| ResetStoreAction
114114
| UpdateAccountAction
115+
| SetWalletModulesAction
115116

116117
export type AddChainsAction = { type: 'add_chains'; payload: Chain[] }
117118
export type AddWalletAction = { type: 'add_wallet'; payload: WalletState }
@@ -135,3 +136,8 @@ export type UpdateAccountAction = {
135136
type: 'update_account'
136137
payload: { id: string; address: string } & Partial<Account>
137138
}
139+
140+
export type SetWalletModulesAction = {
141+
type: 'set_wallet_modules'
142+
payload: WalletModule[]
143+
}

packages/core/src/utils.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import type {
55
DeviceBrowser,
66
DeviceOS,
77
DeviceType,
8-
ChainId
8+
ChainId,
9+
WalletInit,
10+
WalletModule
911
} from '@web3-onboard/common'
1012

1113
export const notNullish = <T>(value: T | null | undefined): value is T =>
@@ -39,3 +41,19 @@ export function validEnsChain(chainId: ChainId): boolean {
3941
export function isSVG(str: string): boolean {
4042
return str.includes('<svg')
4143
}
44+
45+
export function initializeWalletModules(
46+
modules: WalletInit[],
47+
device: Device
48+
): WalletModule[] {
49+
return modules.reduce((acc, walletInit) => {
50+
const initialized = walletInit({ device })
51+
52+
if (initialized) {
53+
// injected wallets is an array of wallets
54+
acc.push(...(Array.isArray(initialized) ? initialized : [initialized]))
55+
}
56+
57+
return acc
58+
}, [] as WalletModule[])
59+
}

packages/core/src/validation.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Joi from 'joi'
2-
import type { ChainId, WalletModule } from '@web3-onboard/common'
2+
import type { ChainId, WalletInit, WalletModule } from '@web3-onboard/common'
33

44
import type {
55
InitOptions,
@@ -92,10 +92,10 @@ const walletModule = Joi.object({
9292
getInterface: Joi.function().arity(1).required()
9393
})
9494

95-
const walletModules = Joi.array().items(Joi.function()).required()
95+
const walletInit = Joi.array().items(Joi.function()).required()
9696

9797
const initOptions = Joi.object({
98-
wallets: walletModules,
98+
wallets: walletInit,
9999
chains: chains.required(),
100100
appMetadata: appMetadata,
101101
i18n: Joi.object().unknown()
@@ -163,3 +163,7 @@ export function validateSetChainOptions(data: {
163163
}): ValidateReturn {
164164
return validate(setChainOptions, data)
165165
}
166+
167+
export function validateWalletInit(data: WalletInit[]): ValidateReturn {
168+
return validate(walletInit, data)
169+
}

packages/core/src/views/connect/Index.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
3535
export let autoSelect: ConnectOptions['autoSelect']
3636
37-
const { walletModules, appMetadata } = internalState$.getValue()
37+
const { appMetadata } = internalState$.getValue()
38+
const { walletModules } = state.get()
3839
3940
let connectionRejected = false
4041
let wallets: WalletWithLoadingIcon[] = []

0 commit comments

Comments
 (0)