Skip to content

Commit e7cc89e

Browse files
Merge pull request #647 from blocknative/release/1.33.0
Release: 1.33.0 - Master
2 parents 5248146 + 8c6f083 commit e7cc89e

File tree

12 files changed

+493
-15
lines changed

12 files changed

+493
-15
lines changed

.prettierrc.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@ module.exports = {
55
printWidth: 80,
66
tabWidth: 2,
77
arrowParens: 'avoid',
8-
svelteSortOrder: 'options-scripts-styles-markup',
9-
plugins: ['prettier-plugin-svelte']
8+
svelteSortOrder: 'options-scripts-styles-markup'
109
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bnc-onboard",
3-
"version": "1.32.0",
3+
"version": "1.33.0",
44
"description": "Onboard users to web3 by allowing them to select a wallet, get that wallet ready to transact and have access to synced wallet state.",
55
"keywords": [
66
"ethereum",
@@ -59,6 +59,7 @@
5959
"@ethereumjs/tx": "^3.0.0",
6060
"@gnosis.pm/safe-apps-provider": "^0.5.0",
6161
"@gnosis.pm/safe-apps-sdk": "^3.0.0",
62+
"@keystonehq/eth-keyring": "0.7.2",
6263
"@ledgerhq/hw-app-eth": "^5.49.0",
6364
"@ledgerhq/hw-transport-u2f": "^5.21.0",
6465
"@ledgerhq/hw-transport-webusb": "5.53.0",

rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export default {
7373
'eth-lattice-keyring',
7474
'eth-sig-util',
7575
'@cvbb/eth-keyring',
76+
'@keystonehq/eth-keyring',
7677
'hdkey',
7778
'@ledgerhq/hw-transport-u2f',
7879
'@ledgerhq/hw-transport-webusb',

src/@types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ declare module '@ledgerhq/hw-transport-u2f'
1818
declare module '@ledgerhq/hw-transport-webusb'
1919
declare module 'eth-provider'
2020
declare module '@ensdomains/ensjs'
21+
declare module '@keystonehq/eth-keyring'
2122

2223
declare module '@shapeshiftoss/hdwallet-core'
2324
declare module '@shapeshiftoss/hdwallet-keepkey-webusb'

src/interfaces.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,12 @@ export interface LedgerOptions extends CommonWalletOptions {
252252
customNetwork?: HardwareWalletCustomNetwork
253253
}
254254

255+
export interface KeystoneOptions extends CommonWalletOptions {
256+
appName: string
257+
rpcUrl: string
258+
customNetwork?: HardwareWalletCustomNetwork
259+
}
260+
255261
export interface GnosisOptions extends CommonWalletOptions {
256262
// For default apps (cf. https://github.com/gnosis/safe-apps-list/issues/new/choose)
257263
appName?: string

src/modules/check/network.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ function network(
2222
walletCheck,
2323
exit,
2424
stateSyncStatus,
25-
stateStore
25+
stateStore,
26+
wallet
2627
} = stateAndHelpers
2728

2829
if (network === null) {
@@ -40,7 +41,14 @@ function network(
4041
})
4142
}
4243
}
43-
44+
try {
45+
await wallet?.provider?.request({
46+
method: 'wallet_switchEthereumChain',
47+
params: [{ chainId: '0x' + appNetworkId?.toString(16) }]
48+
})
49+
} catch (e) {
50+
// Could not switch networks so proceed as normal through the checks
51+
}
4452
if (stateStore.network.get() != appNetworkId) {
4553
return {
4654
heading: heading || 'You Must Change Networks',

src/modules/select/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ function getModule(name: string): Promise<{
140140
return import('./wallets/trezor')
141141
case 'lattice':
142142
return import('./wallets/lattice')
143+
case 'keystone':
144+
return import('./wallets/keystone')
143145
case 'cobovault':
144146
return import('./wallets/cobovault')
145147
case 'ledger':
Loading
Loading
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
import AirGapedKeyring from '@keystonehq/eth-keyring'
2+
import {
3+
Helpers,
4+
KeystoneOptions,
5+
WalletModule,
6+
HardwareWalletCustomNetwork
7+
} from '../../../interfaces'
8+
import keystoneIcon from '../wallet-icons/icon-keystone.png'
9+
import keystoneIcon2x from '../wallet-icons/icon-keystone@2x.png'
10+
11+
function keystone(
12+
options: KeystoneOptions & { networkId: number }
13+
): WalletModule {
14+
const {
15+
appName,
16+
rpcUrl,
17+
networkId,
18+
preferred,
19+
label,
20+
iconSrc,
21+
svg,
22+
customNetwork
23+
} = options
24+
25+
return {
26+
name: label || 'Keystone',
27+
svg: svg,
28+
iconSrc: keystoneIcon,
29+
iconSrcSet: iconSrc || keystoneIcon2x,
30+
wallet: async (helpers: Helpers) => {
31+
const { BigNumber, networkName, resetWalletState } = helpers
32+
33+
const provider = await keystoneProvider({
34+
appName,
35+
rpcUrl,
36+
networkId,
37+
BigNumber,
38+
networkName,
39+
resetWalletState,
40+
customNetwork
41+
})
42+
43+
return {
44+
provider,
45+
interface: {
46+
name: 'Keystone',
47+
connect: provider.enable,
48+
disconnect: provider.disconnect,
49+
address: {
50+
get: async () => provider.getPrimaryAddress()
51+
},
52+
network: {
53+
get: async () => networkId
54+
},
55+
balance: {
56+
get: async () => {
57+
const address = provider.getPrimaryAddress()
58+
return address && provider.getBalance(address)
59+
}
60+
}
61+
}
62+
}
63+
},
64+
type: 'hardware',
65+
desktop: true,
66+
mobile: true,
67+
osExclusions: [],
68+
preferred
69+
}
70+
}
71+
72+
async function keystoneProvider(options: {
73+
appName: string
74+
networkId: number
75+
rpcUrl: string
76+
BigNumber: any
77+
customNetwork?: HardwareWalletCustomNetwork
78+
networkName: (id: number) => string
79+
resetWalletState: (options?: {
80+
disconnected: boolean
81+
walletName: string
82+
}) => void
83+
}) {
84+
const { Transaction } = await import('@ethereumjs/tx')
85+
const { default: Common } = await import('@ethereumjs/common')
86+
const { default: createProvider } = await import('./providerEngine')
87+
88+
const BASE_PATH = "m/44'/60'/0'/0"
89+
90+
const { networkId, rpcUrl, BigNumber, networkName, customNetwork } = options
91+
92+
const keyring = AirGapedKeyring.getEmptyKeyring()
93+
94+
let dPath = ''
95+
96+
let addressList = Array.from<string>([])
97+
let enabled = false
98+
let customPath = false
99+
100+
const provider = createProvider({
101+
getAccounts: (callback: any) => {
102+
getAccounts()
103+
.then((res: Array<string | undefined>) => callback(null, res))
104+
.catch((err: any) => callback(err, null))
105+
},
106+
signTransaction: (transactionData: any, callback: any) => {
107+
signTransaction(transactionData)
108+
.then((res: string) => callback(null, res))
109+
.catch(err => callback(err, null))
110+
},
111+
processMessage: (messageData: any, callback: any) => {
112+
signMessage(messageData)
113+
.then((res: string) => callback(null, res))
114+
.catch(err => callback(err, null))
115+
},
116+
processPersonalMessage: (messageData: any, callback: any) => {
117+
signMessage(messageData)
118+
.then((res: string) => callback(null, res))
119+
.catch(err => callback(err, null))
120+
},
121+
signMessage: (messageData: any, callback: any) => {
122+
signMessage(messageData)
123+
.then((res: string) => callback(null, res))
124+
.catch(err => callback(err, null))
125+
},
126+
signPersonalMessage: (messageData: any, callback: any) => {
127+
signMessage(messageData)
128+
.then((res: string) => callback(null, res))
129+
.catch(err => callback(err, null))
130+
},
131+
rpcUrl
132+
})
133+
134+
provider.setPath = setPath
135+
provider.dPath = dPath
136+
provider.enable = enable
137+
provider.setPrimaryAccount = setPrimaryAccount
138+
provider.getPrimaryAddress = getPrimaryAddress
139+
provider.getAccounts = getAccounts
140+
provider.getMoreAccounts = getMoreAccounts
141+
provider.getBalance = getBalance
142+
provider.getBalances = getBalances
143+
provider.send = provider.sendAsync
144+
provider.disconnect = disconnect
145+
provider.isCustomPath = isCustomPath
146+
147+
function disconnect() {
148+
dPath = ''
149+
enabled = false
150+
provider.stop()
151+
}
152+
153+
async function setPath(path: string) {
154+
if (path !== BASE_PATH)
155+
throw new Error("Keystone only supports standard path: m/44'/0'/0'/0/x")
156+
customPath = false
157+
dPath = path
158+
return true
159+
}
160+
161+
function isCustomPath() {
162+
return customPath
163+
}
164+
165+
function enable() {
166+
if (enabled) {
167+
return getAccounts()
168+
}
169+
return keyring.readKeyring().then(() => {
170+
enabled = true
171+
return getAccounts()
172+
})
173+
}
174+
175+
function setPrimaryAccount(address: string) {
176+
return keyring.setCurrentAccount(
177+
addressList.findIndex(addr => addr === address) || 0
178+
)
179+
}
180+
181+
function getPrimaryAddress() {
182+
return keyring.getCurrentAddress()
183+
}
184+
185+
async function getMoreAccounts() {
186+
const accounts = await getAccounts(true)
187+
return accounts && getBalances(accounts)
188+
}
189+
190+
async function getAccounts(getMore?: boolean) {
191+
if (!enabled) {
192+
return []
193+
}
194+
195+
if (keyring.getAccounts().length > 0 && !getMore) {
196+
return keyring.getAccounts()
197+
}
198+
199+
try {
200+
addressList = await keyring.addAccounts(keyring.getAccounts().length + 5)
201+
} catch (error) {
202+
throw error
203+
}
204+
return addressList
205+
}
206+
207+
function getBalances(addresses: Array<string>) {
208+
return Promise.all(
209+
addresses.map(
210+
address =>
211+
new Promise(async resolve => {
212+
const balance = await getBalance(address)
213+
resolve({ address, balance })
214+
})
215+
)
216+
)
217+
}
218+
219+
function getBalance(address: string) {
220+
return new Promise((resolve, reject) => {
221+
provider.sendAsync(
222+
{
223+
jsonrpc: '2.0',
224+
method: 'eth_getBalance',
225+
params: [address, 'latest'],
226+
id: 42
227+
},
228+
(e: any, res: any) => {
229+
e && reject(e)
230+
const result = res && res.result
231+
232+
if (result != null) {
233+
resolve(new BigNumber(result).toString(10))
234+
} else {
235+
resolve(null)
236+
}
237+
}
238+
)
239+
})
240+
}
241+
242+
async function signTransaction(transactionData: any) {
243+
if (addressList.length === 0) {
244+
await enable()
245+
}
246+
247+
const common = new Common({
248+
chain: customNetwork || networkName(networkId)
249+
})
250+
251+
const transaction = Transaction.fromTxData(
252+
{
253+
...transactionData,
254+
gasLimit: transactionData.gas ?? transactionData.gasLimit
255+
},
256+
{ common, freeze: false }
257+
)
258+
259+
try {
260+
const signedTx = await keyring.signTransaction(
261+
getPrimaryAddress(),
262+
transaction
263+
)
264+
return `0x${signedTx.serialize().toString('hex')}`
265+
} catch (err) {
266+
throw err
267+
}
268+
}
269+
270+
async function signMessage(message: { data: string }): Promise<string> {
271+
if (addressList.length === 0) {
272+
await enable()
273+
}
274+
275+
try {
276+
return keyring.signPersonalMessage(getPrimaryAddress(), message.data)
277+
} catch (err) {
278+
throw err
279+
}
280+
}
281+
282+
return provider
283+
}
284+
285+
export default keystone

0 commit comments

Comments
 (0)