Skip to content

Commit f5469dd

Browse files
committed
Added EIP-6963 discovery to BrowserProvider.
1 parent e7165a6 commit f5469dd

File tree

4 files changed

+137
-12
lines changed

4 files changed

+137
-12
lines changed

docs.wrm/links/specs.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ link-eip-2718 [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718)
3333
link-eip-2930 [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)
3434
link-eip-4788 [EIP-4844](https://eips.ethereum.org/EIPS/eip-4788)
3535
link-eip-4844 [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)
36+
link-eip-6963 [EIP-1193](https://eips.ethereum.org/EIPS/eip-6963)
3637

3738
# Open Standards
3839
link-base58 [Base58](https://en.bitcoinwiki.org/wiki/Base58)

src.ts/ethers.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,16 @@ export type {
168168

169169
AbstractProviderOptions, BrowserProviderOptions, FallbackProviderOptions,
170170

171-
AbstractProviderPlugin, BlockParams, BlockTag, ContractRunner, DebugEventBrowserProvider,
172-
Eip1193Provider, EventFilter, Filter, FilterByBlockHash, GasCostParameters,
173-
JsonRpcApiProviderOptions, JsonRpcError, JsonRpcPayload, JsonRpcResult,
174-
JsonRpcTransactionRequest, LogParams, MinedBlock, MinedTransactionResponse, Networkish,
175-
OrphanFilter, PerformActionFilter, PerformActionRequest, PerformActionTransaction,
176-
PreparedTransactionRequest, ProviderEvent, Subscriber, Subscription, TopicFilter,
177-
TransactionReceiptParams, TransactionRequest, TransactionResponseParams,
178-
WebSocketCreator, WebSocketLike
171+
AbstractProviderPlugin, BlockParams, BlockTag, BrowserDiscoverOptions,
172+
ContractRunner, DebugEventBrowserProvider, Eip1193Provider,
173+
Eip6963ProviderInfo, EventFilter, Filter, FilterByBlockHash,
174+
GasCostParameters, JsonRpcApiProviderOptions, JsonRpcError,
175+
JsonRpcPayload, JsonRpcResult, JsonRpcTransactionRequest, LogParams,
176+
MinedBlock, MinedTransactionResponse, Networkish, OrphanFilter,
177+
PerformActionFilter, PerformActionRequest, PerformActionTransaction,
178+
PreparedTransactionRequest, ProviderEvent, Subscriber, Subscription,
179+
TopicFilter, TransactionReceiptParams, TransactionRequest,
180+
TransactionResponseParams, WebSocketCreator, WebSocketLike
179181
} from "./providers/index.js";
180182

181183
export type {

src.ts/providers/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ export type {
114114
} from "./provider.js";
115115

116116
export type {
117-
BrowserProviderOptions, DebugEventBrowserProvider, Eip1193Provider
117+
BrowserDiscoverOptions, BrowserProviderOptions, DebugEventBrowserProvider,
118+
Eip1193Provider, Eip6963ProviderInfo
118119
} from "./provider-browser.js";
119120

120121
export type { FallbackProviderOptions } from "./provider-fallback.js";

src.ts/providers/provider-browser.ts

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,69 @@ export type DebugEventBrowserProvider = {
3636
error: Error
3737
};
3838

39+
/**
40+
* Provider info provided by the [[link-eip-6963]] discovery mechanism.
41+
*/
42+
export interface Eip6963ProviderInfo {
43+
uuid: string;
44+
name: string;
45+
icon: string;
46+
rdns: string;
47+
}
48+
49+
interface Eip6963ProviderDetail {
50+
info: Eip6963ProviderInfo;
51+
provider: Eip1193Provider;
52+
}
53+
54+
interface Eip6963Announcement {
55+
type: "eip6963:announceProvider";
56+
detail: Eip6963ProviderDetail
57+
}
58+
3959
export type BrowserProviderOptions = {
4060
polling?: boolean;
4161
staticNetwork?: null | boolean | Network;
4262

4363
cacheTimeout?: number;
4464
pollingInterval?: number;
65+
66+
providerInfo?: Eip6963ProviderInfo;
4567
};
4668

69+
/**
70+
* Specifies how [[link-eip-6963]] discovery should proceed.
71+
*
72+
* See: [[BrowserProvider-discover]]
73+
*/
74+
export interface BrowserDiscoverOptions {
75+
/**
76+
* Override provider detection with this provider.
77+
*/
78+
provider?: Eip1193Provider;
79+
80+
/**
81+
* Duration to wait to detect providers. (default: 300ms)
82+
*/
83+
timeout?: number;
84+
85+
/**
86+
* Return the first detected provider. Otherwise wait for %%timeout%%
87+
* and allowing filtering before selecting the desired provider.
88+
*/
89+
anyProvider?: boolean;
90+
91+
/**
92+
* Use the provided window context. Useful in non-standard
93+
* environments or to hijack where a provider comes from.
94+
*/
95+
window?: any;
96+
}
97+
98+
// @TODO: Future, provide some sort of filter mechansm callback along
99+
// with exposing the following types
100+
101+
47102
/**
48103
* A **BrowserProvider** is intended to wrap an injected provider which
49104
* adheres to the [[link-eip-1193]] standard, which most (if not all)
@@ -52,11 +107,14 @@ export type BrowserProviderOptions = {
52107
export class BrowserProvider extends JsonRpcApiPollingProvider {
53108
#request: (method: string, params: Array<any> | Record<string, any>) => Promise<any>;
54109

110+
#providerInfo: null | Eip6963ProviderInfo;
111+
55112
/**
56113
* Connect to the %%ethereum%% provider, optionally forcing the
57114
* %%network%%.
58115
*/
59116
constructor(ethereum: Eip1193Provider, network?: Networkish, _options?: BrowserProviderOptions) {
117+
60118
// Copy the options
61119
const options: JsonRpcApiProviderOptions = Object.assign({ },
62120
((_options != null) ? _options: { }),
@@ -66,6 +124,11 @@ export class BrowserProvider extends JsonRpcApiPollingProvider {
66124

67125
super(network, options);
68126

127+
this.#providerInfo = null;
128+
if (_options && _options.providerInfo) {
129+
this.#providerInfo = _options.providerInfo;
130+
}
131+
69132
this.#request = async (method: string, params: Array<any> | Record<string, any>) => {
70133
const payload = { method, params };
71134
this.emit("debug", { action: "sendEip1193Request", payload });
@@ -84,6 +147,10 @@ export class BrowserProvider extends JsonRpcApiPollingProvider {
84147
};
85148
}
86149

150+
get providerInfo(): null | Eip6963ProviderInfo {
151+
return this.#providerInfo;
152+
}
153+
87154
async send(method: string, params: Array<any> | Record<string, any>): Promise<any> {
88155
await this._start();
89156

@@ -109,7 +176,7 @@ export class BrowserProvider extends JsonRpcApiPollingProvider {
109176
error = JSON.parse(JSON.stringify(error));
110177

111178
// EIP-1193 gives us some machine-readable error codes, so rewrite
112-
// them into
179+
// them into Ethers standard errors.
113180
switch (error.error.code || -1) {
114181
case 4001:
115182
error.error.message = `ethers-user-denied: ${ error.error.message }`;
@@ -142,9 +209,7 @@ export class BrowserProvider extends JsonRpcApiPollingProvider {
142209

143210
if (!(await this.hasSigner(address))) {
144211
try {
145-
//const resp =
146212
await this.#request("eth_requestAccounts", [ ]);
147-
//console.log("RESP", resp);
148213

149214
} catch (error: any) {
150215
const payload = error.payload;
@@ -154,4 +219,60 @@ export class BrowserProvider extends JsonRpcApiPollingProvider {
154219

155220
return await super.getSigner(address);
156221
}
222+
223+
/**
224+
* Discover and connect to a Provider in the Browser using the
225+
* [[link-eip-6963]] discovery mechanism. If no providers are
226+
* present, ``null`` is resolved.
227+
*/
228+
static async discover(options?: BrowserDiscoverOptions): Promise<null | BrowserProvider> {
229+
if (options == null) { options = { }; }
230+
231+
if (options.provider) {
232+
return new BrowserProvider(options.provider);
233+
}
234+
235+
const context = options.window ? options.window:
236+
(typeof(window) !== "undefined") ? window: null;
237+
238+
if (context == null) { return null; }
239+
240+
const anyProvider = options.anyProvider;
241+
if (anyProvider && context.ethereum) {
242+
return new BrowserProvider(context.ethereum);
243+
}
244+
245+
const timeout = options.timeout ? options.timeout: 300;
246+
if (timeout === 0) { return null; }
247+
248+
return await (new Promise((resolve) => {
249+
let found: Array<any> = [ ];
250+
251+
const addProvider = (event: Eip6963Announcement) => {
252+
found.push(event.detail);
253+
if (anyProvider) { finalize(); }
254+
};
255+
256+
const finalize = () => {
257+
clearTimeout(timer);
258+
if (found.length) {
259+
const { provider, info } = found[0];
260+
resolve(new BrowserProvider(provider, undefined, {
261+
providerInfo: info
262+
}));
263+
} else {
264+
resolve(null);
265+
}
266+
context.removeEventListener(<any>"eip6963:announceProvider",
267+
addProvider);
268+
};
269+
270+
const timer = setTimeout(() => { finalize(); }, timeout);
271+
272+
context.addEventListener(<any>"eip6963:announceProvider",
273+
addProvider);
274+
275+
context.dispatchEvent(new Event("eip6963:requestProvider"));
276+
}));
277+
}
157278
}

0 commit comments

Comments
 (0)