Repository navigation
Expand file tree
/
Copy pathEIP6963.ts
More file actions
210 lines (191 loc) · 6.25 KB
/
Copy pathEIP6963.ts
File metadata and controls
210 lines (191 loc) · 6.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import { isObject } from '@metamask/utils';
import { BaseProvider } from './BaseProvider';
/**
* Describes the possible EIP-6963 event names
*/
enum EIP6963EventNames {
Announce = 'eip6963:announceProvider',
Request = 'eip6963:requestProvider', // eslint-disable-line @typescript-eslint/no-shadow
}
declare global {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface WindowEventMap {
[EIP6963EventNames.Request]: EIP6963RequestProviderEvent;
[EIP6963EventNames.Announce]: EIP6963AnnounceProviderEvent;
}
}
/**
* Represents the assets needed to display and identify a wallet.
*
* @type EIP6963ProviderInfo
* @property uuid - A locally unique identifier for the wallet. MUST be a v4 UUID.
* @property name - The name of the wallet.
* @property icon - The icon for the wallet. MUST be data URI.
* @property rdns - The reverse syntax domain name identifier for the wallet.
*/
export type EIP6963ProviderInfo = {
uuid: string;
name: string;
icon: string;
rdns: string;
};
/**
* Represents a provider and the information relevant for the dapp.
*
* @type EIP6963ProviderDetail
* @property info - The EIP6963ProviderInfo object.
* @property provider - The provider instance.
*/
export type EIP6963ProviderDetail = {
info: EIP6963ProviderInfo;
provider: BaseProvider;
};
/**
* Event for requesting an EVM provider.
*
* @type EIP6963RequestProviderEvent
* @property type - The name of the event.
*/
export type EIP6963RequestProviderEvent = Event & {
type: EIP6963EventNames.Request;
};
/**
* Event for announcing an EVM provider.
*
* @type EIP6963RequestProviderEvent
* @property type - The name of the event.
* @property detail - The detail object of the event.
*/
export type EIP6963AnnounceProviderEvent = CustomEvent & {
type: EIP6963EventNames.Announce;
detail: EIP6963ProviderDetail;
};
// https://github.com/thenativeweb/uuidv4/blob/bdcf3a3138bef4fb7c51f389a170666f9012c478/lib/uuidv4.ts#L5
const UUID_V4_REGEX =
/(?:^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}$)|(?:^0{8}-0{4}-0{4}-0{4}-0{12}$)/u;
// https://stackoverflow.com/a/20204811
const FQDN_REGEX =
/(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/u;
/**
* Intended to be used by a dapp. Forwards every announced provider to the
* provided handler by listening for * {@link EIP6963AnnounceProviderEvent},
* and dispatches an {@link EIP6963RequestProviderEvent}.
*
* @param handleProvider - A function that handles an announced provider.
*/
export function requestProvider<HandlerReturnType>(
handleProvider: (providerDetail: EIP6963ProviderDetail) => HandlerReturnType,
): void {
window.addEventListener(
EIP6963EventNames.Announce,
(event: EIP6963AnnounceProviderEvent) => {
if (!isValidAnnounceProviderEvent(event)) {
throwErrorEIP6963(
`Invalid EIP-6963 AnnounceProviderEvent object received from ${EIP6963EventNames.Announce} event.`,
);
}
handleProvider(event.detail);
},
);
window.dispatchEvent(new Event(EIP6963EventNames.Request));
}
/**
* Intended to be used by a wallet. Announces a provider by dispatching
* an {@link EIP6963AnnounceProviderEvent}, and listening for
* {@link EIP6963RequestProviderEvent} to re-announce.
*
* @throws If the {@link EIP6963ProviderDetail} is invalid.
* @param providerDetail - The {@link EIP6963ProviderDetail} to announce.
* @param providerDetail.info - The {@link EIP6963ProviderInfo} to announce.
* @param providerDetail.provider - The provider to announce.
*/
export function announceProvider(providerDetail: EIP6963ProviderDetail): void {
if (!isValidProviderDetail(providerDetail)) {
throwErrorEIP6963('Invalid EIP-6963 ProviderDetail object.');
}
const { info, provider } = providerDetail;
const _announceProvider = () =>
window.dispatchEvent(
new CustomEvent(EIP6963EventNames.Announce, {
detail: Object.freeze({ info: { ...info }, provider }),
}),
);
_announceProvider();
window.addEventListener(
EIP6963EventNames.Request,
(event: EIP6963RequestProviderEvent) => {
if (!isValidRequestProviderEvent(event)) {
throwErrorEIP6963(
`Invalid EIP-6963 RequestProviderEvent object received from ${EIP6963EventNames.Request} event.`,
);
}
_announceProvider();
},
);
}
/**
* Validates an {@link EIP6963RequestProviderEvent} object.
*
* @param event - The {@link EIP6963RequestProviderEvent} to validate.
* @returns Whether the {@link EIP6963RequestProviderEvent} is valid.
*/
function isValidRequestProviderEvent(
event: unknown,
): event is EIP6963RequestProviderEvent {
return event instanceof Event && event.type === EIP6963EventNames.Request;
}
/**
* Validates an {@link EIP6963AnnounceProviderEvent} object.
*
* @param event - The {@link EIP6963AnnounceProviderEvent} to validate.
* @returns Whether the {@link EIP6963AnnounceProviderEvent} is valid.
*/
function isValidAnnounceProviderEvent(
event: unknown,
): event is EIP6963AnnounceProviderEvent {
return (
event instanceof CustomEvent &&
event.type === EIP6963EventNames.Announce &&
Object.isFrozen(event.detail) &&
isValidProviderDetail(event.detail)
);
}
/**
* Validates an {@link EIP6963ProviderDetail} object.
*
* @param providerDetail - The {@link EIP6963ProviderDetail} to validate.
* @returns Whether the {@link EIP6963ProviderDetail} is valid.
*/
function isValidProviderDetail(
providerDetail: unknown,
): providerDetail is EIP6963ProviderDetail {
if (
!isObject(providerDetail) ||
!isObject(providerDetail.info) ||
!isObject(providerDetail.provider)
) {
return false;
}
const { info } = providerDetail;
return (
typeof info.uuid === 'string' &&
UUID_V4_REGEX.test(info.uuid) &&
typeof info.name === 'string' &&
Boolean(info.name) &&
typeof info.icon === 'string' &&
info.icon.startsWith('data:image') &&
typeof info.rdns === 'string' &&
FQDN_REGEX.test(info.rdns)
);
}
/**
* Throws an error with link to EIP-6963 specifications.
*
* @param message - The message to include.
* @throws a friendly error with a link to EIP-6963.
*/
function throwErrorEIP6963(message: string) {
throw new Error(
`${message} See https://eips.ethereum.org/EIPS/eip-6963 for requirements.`,
);
}