1- export interface IEthereumRequestAccountsResult {
2- accounts : string [ ]
3- selectedAddress : string | null
4- info : {
5- extensionFound : boolean
6- uniqueChainName ?: UNIQUE_CHAIN ,
7- chainId ?: string
8- chainIdNumber ?: number
9- userRejected ?: boolean
10- error ?: Error
11- }
1+ import { documentReadyPromiseAndWindowIsOk } from './utils'
2+
3+ export const windowIsOkSync = ( ) : boolean => {
4+ return typeof window !== 'undefined' && ! ! ( window as any ) . ethereum ;
5+ }
6+
7+ type IEthereumExtensionError = Error & {
8+ extensionFound : boolean
9+ isUserRejected : boolean
1210}
1311
1412export interface AddEthereumChainParameter {
@@ -25,77 +23,101 @@ export interface AddEthereumChainParameter {
2523}
2624
2725type UNIQUE_CHAIN = 'unique' | 'quartz' | 'opal' | 'sapphire'
28- const UNIQUE_CHAIN_IDS : Record < UNIQUE_CHAIN , number > = {
26+ const chainNameToChainId : Record < UNIQUE_CHAIN , number > = {
2927 unique : 8880 ,
3028 quartz : 8881 ,
3129 opal : 8882 ,
3230 sapphire : 8883
3331}
34- const chainNameByChainId : Record < number , UNIQUE_CHAIN > = {
32+ const chainIdToChainName : Record < number , UNIQUE_CHAIN > = {
3533 8880 : 'unique' ,
3634 8881 : 'quartz' ,
3735 8882 : 'opal' ,
3836 8883 : 'sapphire'
3937}
4038
39+ const isUniqueChainFactory = ( chainName : UNIQUE_CHAIN ) => ( ) : boolean => {
40+ if ( ! windowIsOkSync ( ) ) {
41+ return false
42+ }
43+ const chainId = parseInt ( ( window as any ) . ethereum . chainId , 16 )
44+ return chainId === chainNameToChainId [ chainName ]
45+ }
46+ const currentChainIs : Record < UNIQUE_CHAIN , ( ) => boolean > & { anyUniqueChain : ( chainId : string | number ) => boolean } = {
47+ unique : isUniqueChainFactory ( 'unique' ) ,
48+ quartz : isUniqueChainFactory ( 'quartz' ) ,
49+ opal : isUniqueChainFactory ( 'opal' ) ,
50+ sapphire : isUniqueChainFactory ( 'sapphire' ) ,
51+ anyUniqueChain : ( chainId : string | number | null | undefined ) : boolean => {
52+ if ( ! chainId ) return false
4153
42- export const requestAccounts = async ( ) : Promise < IEthereumRequestAccountsResult > => {
43- if ( typeof window === 'undefined' || ! ( window as any ) . ethereum ) {
44- return { accounts : [ ] , selectedAddress : null , info : { extensionFound : false } }
54+ const chainName : UNIQUE_CHAIN | undefined =
55+ chainIdToChainName [ typeof chainId === "number" ? chainId : parseInt ( chainId , 16 ) ]
56+
57+ return ( typeof chainName === 'string' )
58+ }
59+ }
60+
61+
62+ export type IEthereumAccountResult = {
63+ address : string
64+ chainId : number
65+ error : null
66+ } | {
67+ address : null
68+ chainId : number | null
69+ error : IEthereumExtensionError
70+ }
71+
72+ const getOrRequestAccounts = async ( requestInsteadOfGet : boolean = false ) : Promise < IEthereumAccountResult > => {
73+ const windowIsOk = await documentReadyPromiseAndWindowIsOk ( )
74+ if ( ! windowIsOk || ! ( window as any ) . ethereum ) {
75+ const error = new Error ( 'No extension found' ) as IEthereumExtensionError
76+ error . extensionFound = false
77+ error . isUserRejected = false
78+
79+ return {
80+ address : null ,
81+ chainId : null ,
82+ error
83+ }
4584 }
85+
4686 const ethereum = ( window as any ) . ethereum
4787 let accounts : string [ ] = [ ]
4888 try {
49- accounts = await ethereum . request ( { method : 'eth_requestAccounts' } )
50- const chainIdNumber = parseInt ( ethereum . chainId , 16 )
89+ accounts = await ethereum . request ( { method : requestInsteadOfGet ? 'eth_requestAccounts' : 'eth_accounts '} )
90+ const chainId = parseInt ( ethereum . chainId , 16 )
5191
5292 return {
53- accounts,
54- selectedAddress : ethereum . selectedAddress ,
55- info : {
56- extensionFound : true ,
57- chainId : ethereum . chainId ,
58- chainIdNumber,
59- uniqueChainName : chainNameByChainId [ chainIdNumber ] ,
60- } ,
93+ address : accounts [ 0 ] ,
94+ chainId,
95+ error : null
6196 }
62- } catch ( error : any ) {
97+ } catch ( _error : any ) {
6398 // EIP-1193 userRejectedRequest error code is 4001
6499 // If this happens, the user rejected the connection request.
65100
101+ const error = _error as IEthereumExtensionError
102+ error . isUserRejected = _error . code === 4001
103+ error . extensionFound = true
104+
105+ const chainIdStr = ( window as any ) . ethereum ?. chainId
106+ const chainId = typeof chainIdStr === 'string' ? parseInt ( chainIdStr , 16 ) : NaN
107+
66108 return {
67- accounts : [ ] ,
68- selectedAddress : null ,
69- info : { extensionFound : true , userRejected : error . code === 4001 , error } ,
109+ address : null ,
110+ chainId : isNaN ( chainId ) ? null : chainId ,
111+ error,
70112 }
71113 }
72114}
73115
74- export const getAccounts = async ( ) : Promise < IEthereumRequestAccountsResult > => {
75- if ( typeof window === 'undefined' || ! ( window as any ) . ethereum ) {
76- return { accounts : [ ] , selectedAddress : null , info : { extensionFound : false } }
77- }
78- const ethereum = ( window as any ) . ethereum
79116
80- const accounts : string [ ] = await ethereum . request ( { method : 'eth_accounts' } )
81- const chainIdNumber = parseInt ( ethereum . chainId , 16 )
82- return {
83- accounts,
84- selectedAddress : ethereum . selectedAddress ,
85- info : {
86- extensionFound : true ,
87- chainId : ethereum . chainId ,
88- chainIdNumber,
89- uniqueChainName : chainNameByChainId [ chainIdNumber ] ,
90- } ,
91- }
92- }
117+ export const addChainToExtension = async ( chainData : AddEthereumChainParameter ) : Promise < void > => {
118+ const windowIsOk = await documentReadyPromiseAndWindowIsOk ( )
119+ if ( ! windowIsOk ) return
93120
94- export const addChainToMetamask = async ( chainData : AddEthereumChainParameter ) : Promise < void > => {
95- const safeGetAccountsResult = await getAccounts ( )
96- if ( ! safeGetAccountsResult . info . extensionFound ) {
97- throw new Error ( `No browser extension found` )
98- }
99121 const ethereum = ( window as any ) . ethereum
100122
101123 if ( ethereum . chainId === chainData . chainId ) {
@@ -115,7 +137,7 @@ export const addChainToMetamask = async (chainData: AddEthereumChainParameter):
115137}
116138
117139
118- export const UNIQUE_CHAINS_DATA_FOR_EXTENSIONS : Record < UNIQUE_CHAIN , AddEthereumChainParameter > = {
140+ const UNIQUE_CHAINS_DATA_FOR_EXTENSIONS : Record < UNIQUE_CHAIN , AddEthereumChainParameter > = {
119141 unique : {
120142 chainId : "0x22b0" ,
121143 chainName : 'Unique' ,
@@ -128,8 +150,6 @@ export const UNIQUE_CHAINS_DATA_FOR_EXTENSIONS: Record<UNIQUE_CHAIN, AddEthereum
128150 iconUrls : [ `https://ipfs.unique.network/ipfs/QmbJ7CGZ2GxWMp7s6jy71UGzRsMe4w3KANKXDAExYWdaFR` ] ,
129151 blockExplorerUrls : [ 'https://uniquescan.io/unique/' ] ,
130152 } ,
131-
132-
133153 quartz : {
134154 chainId : "0x22b1" ,
135155 chainName : "Quartz by Unique" ,
@@ -168,20 +188,79 @@ export const UNIQUE_CHAINS_DATA_FOR_EXTENSIONS: Record<UNIQUE_CHAIN, AddEthereum
168188 } ,
169189}
170190
171- const AddUniqueChainToMetamask : Record < UNIQUE_CHAIN , ( ) => Promise < void > > = {
172- unique : ( ) => addChainToMetamask ( UNIQUE_CHAINS_DATA_FOR_EXTENSIONS . unique ) ,
173- quartz : ( ) => addChainToMetamask ( UNIQUE_CHAINS_DATA_FOR_EXTENSIONS . quartz ) ,
174- opal : ( ) => addChainToMetamask ( UNIQUE_CHAINS_DATA_FOR_EXTENSIONS . opal ) ,
175- sapphire : ( ) => addChainToMetamask ( UNIQUE_CHAINS_DATA_FOR_EXTENSIONS . sapphire ) ,
191+ const addChain : Record < UNIQUE_CHAIN , ( ) => Promise < void > > & { anyChain : ( chainData : AddEthereumChainParameter ) => Promise < void > } = {
192+ unique : ( ) => addChainToExtension ( UNIQUE_CHAINS_DATA_FOR_EXTENSIONS . unique ) ,
193+ quartz : ( ) => addChainToExtension ( UNIQUE_CHAINS_DATA_FOR_EXTENSIONS . quartz ) ,
194+ opal : ( ) => addChainToExtension ( UNIQUE_CHAINS_DATA_FOR_EXTENSIONS . opal ) ,
195+ sapphire : ( ) => addChainToExtension ( UNIQUE_CHAINS_DATA_FOR_EXTENSIONS . sapphire ) ,
196+ anyChain : ( chainData : AddEthereumChainParameter ) => addChainToExtension ( chainData ) ,
197+ }
198+
199+ const switchToChain = async ( chainId : number | string ) : Promise < void > => {
200+ const windowIsOk = await documentReadyPromiseAndWindowIsOk ( )
201+ if ( ! windowIsOk ) return
202+
203+ const parsedChainId : string = typeof chainId === 'string' ? chainId : '0x' + chainId . toString ( 16 )
204+
205+ await ( window as any ) . ethereum . request ( { method : 'wallet_switchEthereumChain' , params : [ { chainId : parsedChainId } ] } )
206+ }
207+ const switchChainTo : Record < UNIQUE_CHAIN , ( ) => Promise < void > > & { anyChain : ( chainId : number | string ) => Promise < void > } = {
208+ unique : ( ) => switchToChain ( chainNameToChainId . unique ) ,
209+ quartz : ( ) => switchToChain ( chainNameToChainId . quartz ) ,
210+ opal : ( ) => switchToChain ( chainNameToChainId . opal ) ,
211+ sapphire : ( ) => switchToChain ( chainNameToChainId . sapphire ) ,
212+ anyChain : ( chainId ) => switchToChain ( chainId )
213+ }
214+
215+ export type UpdateReason = 'account' | 'chain'
216+
217+ const subscribeOnChanges = ( cb : ( result : { reason : UpdateReason , chainId : number | null , address : string | null } ) => void ) : ( ( ) => void ) => {
218+ if ( typeof window === 'undefined' || ! ( window as any ) . ethereum ) {
219+ return ( ) => undefined
220+ }
221+
222+ const ethereum = ( window as any ) . ethereum
223+
224+ const getAccounts = ( reason : UpdateReason ) => {
225+ if ( ethereum . chainId && ethereum . selectedAddress ) {
226+ cb ( { reason, address : ethereum . selectedAddress , chainId : parseInt ( ethereum . chainId , 16 ) } )
227+ } else {
228+ getOrRequestAccounts ( ) . then ( ( { chainId, address, error} ) => {
229+ if ( error ) {
230+ throw error
231+ }
232+
233+ cb ( { reason, chainId, address} )
234+ } )
235+ }
236+ }
237+
238+ ethereum . on ( 'accountsChanged' , ( ) => {
239+ getAccounts ( 'account' )
240+ } )
241+ ethereum . on ( 'chainChanged' , ( ) => {
242+ getAccounts ( 'chain' )
243+ } )
244+ return ( ) => {
245+ ethereum . removeListener ( 'accountsChanged' , getAccounts )
246+ ethereum . removeListener ( 'networkChanged' , getAccounts )
247+ }
176248}
177249
178250export type { UNIQUE_CHAIN }
179251
180252export const Ethereum = {
181- requestAccounts,
182- getAccounts,
183- addChainToMetamask,
184- UNIQUE_CHAIN_IDS ,
253+ getOrRequestAccounts,
254+ requestAccounts : ( ) => getOrRequestAccounts ( true ) ,
255+ getAccounts : ( ) => getOrRequestAccounts ( ) ,
256+ subscribeOnChanges,
257+
258+ chainNameToChainId,
259+ chainIdToChainName,
260+
261+ currentChainIs,
262+ addChain,
263+ switchChainTo,
264+
185265 UNIQUE_CHAINS_DATA_FOR_EXTENSIONS ,
186- AddUniqueChainToMetamask,
187266}
0 commit comments