@exodus/web3-ethereum 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/provider/index.d.ts +2 -0
- package/lib/provider/index.js +2 -0
- package/lib/provider/provider.d.ts +22 -0
- package/lib/provider/provider.js +236 -0
- package/lib/provider/storage.d.ts +3 -0
- package/lib/provider/storage.js +28 -0
- package/lib/provider/types.d.ts +44 -0
- package/lib/provider/types.js +1 -0
- package/lib/provider/wallet-standard/account.d.ts +11 -0
- package/lib/provider/wallet-standard/account.js +36 -0
- package/lib/provider/wallet-standard/chains.d.ts +9 -0
- package/lib/provider/wallet-standard/chains.js +11 -0
- package/lib/provider/wallet-standard/index.d.ts +1 -0
- package/lib/provider/wallet-standard/index.js +1 -0
- package/lib/provider/wallet-standard/wallet.d.ts +37 -0
- package/lib/provider/wallet-standard/wallet.js +123 -0
- package/lib/types.d.ts +13 -0
- package/lib/types.js +1 -0
- package/package.json +7 -3
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BaseProvider } from '@exodus/web3-provider';
|
|
2
|
+
import type { ChainId, Deps, JsonRpcCallback, JsonRpcRequest, JsonRpcResult, ProviderConnectInfo, RequestArguments } from './types.js';
|
|
3
|
+
interface EthereumProviderEvents {
|
|
4
|
+
accountsChanged(accounts: string[]): void;
|
|
5
|
+
chainChanged(chainId: ChainId): void;
|
|
6
|
+
connect(providerConnectInfo: ProviderConnectInfo): void;
|
|
7
|
+
disconnect(): void;
|
|
8
|
+
message(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare class EthereumProvider extends BaseProvider<EthereumProviderEvents> {
|
|
11
|
+
#private;
|
|
12
|
+
constructor({ accountsObservable, storage, supportedChainIds, transport, getDefaultChainId, }: Deps);
|
|
13
|
+
protected _callRpcMethod<Params, Response>(method: string, params: Params): Promise<Response>;
|
|
14
|
+
isConnected(): boolean;
|
|
15
|
+
request(args: RequestArguments): Promise<unknown>;
|
|
16
|
+
get chainId(): ChainId | null;
|
|
17
|
+
get selectedAddress(): string | null;
|
|
18
|
+
enable(): Promise<string[]>;
|
|
19
|
+
send(methodOrPayload: string | JsonRpcRequest, paramsOrCallback: unknown[] | JsonRpcCallback): Promise<void | JsonRpcResult>;
|
|
20
|
+
sendAsync(payload: JsonRpcRequest, callback: JsonRpcCallback): void;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { BaseProvider } from '@exodus/web3-provider';
|
|
2
|
+
import { ethErrors } from 'eth-rpc-errors';
|
|
3
|
+
import { createStorageWrapper } from './storage.js';
|
|
4
|
+
export class EthereumProvider extends BaseProvider {
|
|
5
|
+
#storage;
|
|
6
|
+
#supportedChainIds = [];
|
|
7
|
+
#getDefaultChainId;
|
|
8
|
+
#initializePromise = null;
|
|
9
|
+
#initialized = false;
|
|
10
|
+
#isConnected = false;
|
|
11
|
+
#chainId = null;
|
|
12
|
+
#accounts = [];
|
|
13
|
+
#selectedAddress = null;
|
|
14
|
+
constructor({ accountsObservable, storage, supportedChainIds, transport, getDefaultChainId, }) {
|
|
15
|
+
if (!supportedChainIds?.length) {
|
|
16
|
+
throw new Error('At least one supported chain must be provided');
|
|
17
|
+
}
|
|
18
|
+
super({ transport });
|
|
19
|
+
this.#storage = createStorageWrapper(storage);
|
|
20
|
+
this.#supportedChainIds = supportedChainIds.map((chainId) => chainId.toLowerCase());
|
|
21
|
+
// Observe account changes.
|
|
22
|
+
this.on('connect', () => {
|
|
23
|
+
const unobserve = accountsObservable.observe((accounts) => {
|
|
24
|
+
this.#handleAccountsChanged(accounts);
|
|
25
|
+
});
|
|
26
|
+
this.on('disconnect', unobserve);
|
|
27
|
+
});
|
|
28
|
+
this.on('connect', () => {
|
|
29
|
+
this.#isConnected = true;
|
|
30
|
+
});
|
|
31
|
+
this.#initializePromise = this.#initialize();
|
|
32
|
+
this.#getDefaultChainId =
|
|
33
|
+
typeof getDefaultChainId === 'function'
|
|
34
|
+
? getDefaultChainId
|
|
35
|
+
: async () => null;
|
|
36
|
+
}
|
|
37
|
+
_callRpcMethod(method, params) {
|
|
38
|
+
const prefix = this.#chainId;
|
|
39
|
+
return super._callRpcMethod(`${prefix}_${method}`, params);
|
|
40
|
+
}
|
|
41
|
+
#assertConnected() {
|
|
42
|
+
if (!this.isConnected()) {
|
|
43
|
+
throw ethErrors.provider.disconnected({
|
|
44
|
+
message: 'The Provider is disconnected from all chains.',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
#assertChainSupported(chainId) {
|
|
49
|
+
if (!this.#supportedChainIds.includes(chainId)) {
|
|
50
|
+
throw ethErrors.provider.custom({
|
|
51
|
+
message: 'The Provider does not support the requested chain.',
|
|
52
|
+
code: 4902,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async #getChainId() {
|
|
57
|
+
const storedChainId = await this.#storage.getChainId();
|
|
58
|
+
const defaultChainId = this.#supportedChainIds[0];
|
|
59
|
+
return storedChainId ?? defaultChainId;
|
|
60
|
+
}
|
|
61
|
+
async #getAccounts() {
|
|
62
|
+
return this._callRpcMethod('eth_accounts', []);
|
|
63
|
+
}
|
|
64
|
+
async #initialize() {
|
|
65
|
+
const chainId = await this.#getChainId();
|
|
66
|
+
this.#handleConnect(chainId);
|
|
67
|
+
this.#handleChainChanged(chainId);
|
|
68
|
+
const accounts = await this.#getAccounts();
|
|
69
|
+
this.#handleAccountsChanged(accounts);
|
|
70
|
+
this.#initialized = true;
|
|
71
|
+
this.#initializePromise = null;
|
|
72
|
+
}
|
|
73
|
+
async #awaitInitialized() {
|
|
74
|
+
if (this.#initializePromise) {
|
|
75
|
+
await this.#initializePromise;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
#handleConnect(chainId) {
|
|
79
|
+
if (!this.#isConnected) {
|
|
80
|
+
this.#isConnected = true;
|
|
81
|
+
this.emit('connect', { chainId });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
#handleChainChanged(chainId) {
|
|
85
|
+
if (!chainId || typeof chainId !== 'string' || !chainId.startsWith('0x')) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (chainId !== this.#chainId) {
|
|
89
|
+
this.#chainId = chainId;
|
|
90
|
+
if (this.#initialized) {
|
|
91
|
+
this.emit('chainChanged', chainId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
#handleAccountsChanged(accounts) {
|
|
96
|
+
// `accounts` is always going to have a single account (or none) for the time being.
|
|
97
|
+
if (this.#accounts[0] !== accounts[0]) {
|
|
98
|
+
this.#accounts = accounts;
|
|
99
|
+
this.#selectedAddress = accounts[0] ?? null;
|
|
100
|
+
if (this.#initialized) {
|
|
101
|
+
this.emit('accountsChanged', accounts);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async #requestAccounts() {
|
|
106
|
+
const permissions = { eth_accounts: {} };
|
|
107
|
+
await this._callRpcMethod('wallet_requestPermissions', [permissions]);
|
|
108
|
+
const accounts = await this._callRpcMethod('eth_accounts', []);
|
|
109
|
+
this.#handleAccountsChanged(accounts);
|
|
110
|
+
if (!(await this.#storage.getChainId())) {
|
|
111
|
+
// Get default chain Id from remote config
|
|
112
|
+
const defaultChainId = await this.#getDefaultChainId();
|
|
113
|
+
if (defaultChainId) {
|
|
114
|
+
this.#handleChainChanged(defaultChainId);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return accounts;
|
|
118
|
+
}
|
|
119
|
+
async #addChain(chainId) {
|
|
120
|
+
// Exodus does not support the addition of custom chains.
|
|
121
|
+
// If the requested chain is supported, the provider will switch to that chain.
|
|
122
|
+
// This matches MetaMask's behavior.
|
|
123
|
+
// See: https://github.com/MetaMask/metamask-extension/blob/d92936475a942fed060bb36f810ee6bfd901f4f4/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js#L167-L183.
|
|
124
|
+
return this.#switchChain(chainId);
|
|
125
|
+
}
|
|
126
|
+
async #switchChain(chainId) {
|
|
127
|
+
const lowerCaseChainId = chainId.toLowerCase();
|
|
128
|
+
this.#assertChainSupported(lowerCaseChainId);
|
|
129
|
+
this.#handleChainChanged(lowerCaseChainId);
|
|
130
|
+
await this.#storage.setChainId(lowerCaseChainId);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
isConnected() {
|
|
134
|
+
return this.#isConnected;
|
|
135
|
+
}
|
|
136
|
+
async request(args) {
|
|
137
|
+
await this.#awaitInitialized();
|
|
138
|
+
this.#assertConnected();
|
|
139
|
+
const { method, params = [] } = args;
|
|
140
|
+
if (typeof method !== 'string' || method.length === 0) {
|
|
141
|
+
throw ethErrors.rpc.invalidRequest({
|
|
142
|
+
message: "'args.method' must be a non-empty string.",
|
|
143
|
+
data: args,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (!Array.isArray(params) &&
|
|
147
|
+
(typeof params !== 'object' || params === null)) {
|
|
148
|
+
throw ethErrors.rpc.invalidRequest({
|
|
149
|
+
message: "'args.params' must be an object or array if provided.",
|
|
150
|
+
data: args,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Intercepted requests.
|
|
155
|
+
*/
|
|
156
|
+
// EIP-695
|
|
157
|
+
// See: https://eips.ethereum.org/EIPS/eip-695.
|
|
158
|
+
if (method === 'eth_chainId') {
|
|
159
|
+
return this.#chainId;
|
|
160
|
+
}
|
|
161
|
+
// EIP-2255.
|
|
162
|
+
// See: https://eips.ethereum.org/EIPS/eip-2255.
|
|
163
|
+
if (method === 'eth_requestAccounts') {
|
|
164
|
+
return this.#requestAccounts();
|
|
165
|
+
}
|
|
166
|
+
// EIP-3085
|
|
167
|
+
// See: https://eips.ethereum.org/EIPS/eip-3085.
|
|
168
|
+
if (method === 'wallet_addEthereumChain') {
|
|
169
|
+
const { chainId } = params[0];
|
|
170
|
+
return this.#addChain(chainId);
|
|
171
|
+
}
|
|
172
|
+
// EIP-3326.
|
|
173
|
+
// See: https://eips.ethereum.org/EIPS/eip-3326.
|
|
174
|
+
if (method === 'wallet_switchEthereumChain') {
|
|
175
|
+
const { chainId } = params[0];
|
|
176
|
+
return this.#switchChain(chainId);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Request.
|
|
180
|
+
*/
|
|
181
|
+
let revisedParams = params;
|
|
182
|
+
if (method === 'personal_sign') {
|
|
183
|
+
// Omit `password` parameter if passed.
|
|
184
|
+
// See: https://github.com/ethereum/go-ethereum/pull/2940.
|
|
185
|
+
revisedParams = params.slice(0, 2);
|
|
186
|
+
}
|
|
187
|
+
return this._callRpcMethod(method, revisedParams);
|
|
188
|
+
}
|
|
189
|
+
/*
|
|
190
|
+
* Legacy API.
|
|
191
|
+
*/
|
|
192
|
+
get chainId() {
|
|
193
|
+
console.warn('Exodus: "ethereum.chainId" is deprecated and may be removed in the future. Please use "ethereum.request({ method: \'eth_chainId\' })" instead.');
|
|
194
|
+
return this.#chainId;
|
|
195
|
+
}
|
|
196
|
+
get selectedAddress() {
|
|
197
|
+
console.warn('Exodus: "ethereum.selectedAddress" is deprecated and may be removed in the future. Please use "ethereum.request({ method: \'eth_accounts\' })" instead.');
|
|
198
|
+
return this.#selectedAddress;
|
|
199
|
+
}
|
|
200
|
+
async enable() {
|
|
201
|
+
console.warn('Exodus: "ethereum.enable()" is deprecated and may be removed in the future. Please use "ethereum.request({ method: \'eth_requestAccounts\' })" instead.\nFor more information, see: https://eips.ethereum.org/EIPS/eip-1102.');
|
|
202
|
+
this.#assertConnected();
|
|
203
|
+
return this.#requestAccounts();
|
|
204
|
+
}
|
|
205
|
+
async send(methodOrPayload, paramsOrCallback) {
|
|
206
|
+
console.warn('Exodus: "ethereum.send()" is deprecated and may be removed in the future. Please use "ethereum.request()" instead.');
|
|
207
|
+
const hasMethod = typeof methodOrPayload === 'string';
|
|
208
|
+
const hasParams = Array.isArray(paramsOrCallback);
|
|
209
|
+
if (hasMethod && (!paramsOrCallback || hasParams)) {
|
|
210
|
+
const method = methodOrPayload;
|
|
211
|
+
const params = paramsOrCallback;
|
|
212
|
+
return new Promise((resolve, reject) => this.request({ method, params })
|
|
213
|
+
.then((result) => resolve({ id: '1', jsonrpc: '2.0', result }))
|
|
214
|
+
.catch((err) => reject({ id: '1', jsonrpc: '2.0', error: err })));
|
|
215
|
+
}
|
|
216
|
+
const payload = methodOrPayload;
|
|
217
|
+
const callback = paramsOrCallback;
|
|
218
|
+
this.sendAsync(payload, callback);
|
|
219
|
+
}
|
|
220
|
+
sendAsync(payload, callback) {
|
|
221
|
+
console.warn('Exodus: "ethereum.sendAsync()" is deprecated and may be removed in the future. Please use "ethereum.request()" instead.');
|
|
222
|
+
const { id, jsonrpc, method, params } = payload;
|
|
223
|
+
const hasCallback = callback && typeof callback === 'function';
|
|
224
|
+
this.request({ method, params })
|
|
225
|
+
.then((result) => {
|
|
226
|
+
if (hasCallback) {
|
|
227
|
+
callback(null, { id, jsonrpc, result });
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
.catch((err) => {
|
|
231
|
+
if (hasCallback) {
|
|
232
|
+
callback(err, { id, jsonrpc, error: err });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const chainIdKey = 'exodus:ethereum:chainId';
|
|
2
|
+
export function createStorageWrapper(storage) {
|
|
3
|
+
async function getChainId() {
|
|
4
|
+
try {
|
|
5
|
+
return await storage.getItem(chainIdKey);
|
|
6
|
+
}
|
|
7
|
+
catch (err) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
async function setChainId(chainId) {
|
|
12
|
+
try {
|
|
13
|
+
await storage.setItem(chainIdKey, chainId);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
// Ignore error.
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function clearChainId() {
|
|
20
|
+
try {
|
|
21
|
+
await storage.removeItem(chainIdKey);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
// Ignore error.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { getChainId, setChainId, clearChainId };
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Deps as BaseProviderDeps } from '@exodus/web3-provider';
|
|
2
|
+
import type { Observable, Storage } from '@exodus/web3-types';
|
|
3
|
+
export declare type ChainId = string;
|
|
4
|
+
export interface ProviderConnectInfo {
|
|
5
|
+
chainId: ChainId;
|
|
6
|
+
}
|
|
7
|
+
export interface ChainParameter {
|
|
8
|
+
chainId: ChainId;
|
|
9
|
+
}
|
|
10
|
+
export interface RequestArguments {
|
|
11
|
+
method: string;
|
|
12
|
+
params?: unknown[] | Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
declare type JsonRpcId = string | undefined;
|
|
15
|
+
export interface JsonRpcRequest {
|
|
16
|
+
id: JsonRpcId;
|
|
17
|
+
jsonrpc: '2.0';
|
|
18
|
+
method: string;
|
|
19
|
+
params?: unknown[];
|
|
20
|
+
}
|
|
21
|
+
declare type JsonRpcSuccess = {
|
|
22
|
+
id: JsonRpcId;
|
|
23
|
+
jsonrpc: '2.0';
|
|
24
|
+
result: unknown;
|
|
25
|
+
};
|
|
26
|
+
declare type JsonRpcFailure = {
|
|
27
|
+
id: JsonRpcId;
|
|
28
|
+
jsonrpc: '2.0';
|
|
29
|
+
error: Error;
|
|
30
|
+
};
|
|
31
|
+
export declare type JsonRpcResult = JsonRpcSuccess | JsonRpcFailure;
|
|
32
|
+
export declare type JsonRpcCallback = (error: Error | null, result?: JsonRpcResult) => unknown;
|
|
33
|
+
export declare type Deps = BaseProviderDeps & {
|
|
34
|
+
accountsObservable: Observable;
|
|
35
|
+
storage: Storage;
|
|
36
|
+
supportedChainIds: string[];
|
|
37
|
+
getDefaultChainId?: () => Promise<string | null>;
|
|
38
|
+
};
|
|
39
|
+
export interface ProviderStorage {
|
|
40
|
+
getChainId: () => Promise<ChainId | null>;
|
|
41
|
+
setChainId: (chainId: ChainId) => Promise<void>;
|
|
42
|
+
clearChainId: () => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { WalletStandardAccountParams } from '../../types.js';
|
|
2
|
+
export declare class ExodusEthereumWalletAccount {
|
|
3
|
+
#private;
|
|
4
|
+
get address(): string;
|
|
5
|
+
get publicKey(): string | undefined;
|
|
6
|
+
get chains(): string[] | undefined;
|
|
7
|
+
get features(): string[];
|
|
8
|
+
get label(): string | undefined;
|
|
9
|
+
get icon(): string | undefined;
|
|
10
|
+
constructor({ address, publicKey, label, icon, chains, }: WalletStandardAccountParams);
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// This file is copied with modification from @wallet-standard/util
|
|
2
|
+
const features = ['ethereum:request'];
|
|
3
|
+
export class ExodusEthereumWalletAccount {
|
|
4
|
+
#address;
|
|
5
|
+
#publicKey;
|
|
6
|
+
#chains;
|
|
7
|
+
#features;
|
|
8
|
+
#label;
|
|
9
|
+
#icon;
|
|
10
|
+
get address() {
|
|
11
|
+
return this.#address;
|
|
12
|
+
}
|
|
13
|
+
get publicKey() {
|
|
14
|
+
return this.#publicKey?.slice();
|
|
15
|
+
}
|
|
16
|
+
get chains() {
|
|
17
|
+
return this.#chains?.slice();
|
|
18
|
+
}
|
|
19
|
+
get features() {
|
|
20
|
+
return this.#features?.slice();
|
|
21
|
+
}
|
|
22
|
+
get label() {
|
|
23
|
+
return this.#label;
|
|
24
|
+
}
|
|
25
|
+
get icon() {
|
|
26
|
+
return this.#icon;
|
|
27
|
+
}
|
|
28
|
+
constructor({ address, publicKey, label, icon, chains, }) {
|
|
29
|
+
this.#address = address;
|
|
30
|
+
this.#publicKey = publicKey;
|
|
31
|
+
this.#chains = chains;
|
|
32
|
+
this.#features = features;
|
|
33
|
+
this.#label = label;
|
|
34
|
+
this.#icon = icon;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChainId } from '../../types.js';
|
|
2
|
+
/** Ethereum Mainnet, e.g. https://ethereum.publicnode.com */
|
|
3
|
+
export declare const ETHEREUM_MAINNET = "ethereum:1";
|
|
4
|
+
/** Array of Ethereum chains supported by Exodus */
|
|
5
|
+
export declare const ETHEREUM_CHAINS: string[];
|
|
6
|
+
/**
|
|
7
|
+
* Converts a hexadecimal chain ID to a [human-readable format](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md)
|
|
8
|
+
*/
|
|
9
|
+
export declare function convertChainIdToCanonicalBlockchainIdentifier(chainId: ChainId): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Ethereum Mainnet, e.g. https://ethereum.publicnode.com */
|
|
2
|
+
export const ETHEREUM_MAINNET = 'ethereum:1';
|
|
3
|
+
/** Array of Ethereum chains supported by Exodus */
|
|
4
|
+
export const ETHEREUM_CHAINS = [ETHEREUM_MAINNET];
|
|
5
|
+
/**
|
|
6
|
+
* Converts a hexadecimal chain ID to a [human-readable format](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md)
|
|
7
|
+
*/
|
|
8
|
+
export function convertChainIdToCanonicalBlockchainIdentifier(chainId) {
|
|
9
|
+
chainId = chainId.replace('0x', '');
|
|
10
|
+
return `ethereum:${chainId}`;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ExodusEthereumWallet } from './wallet.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ExodusEthereumWallet } from './wallet.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ExodusEthereumWalletAccount } from './account.js';
|
|
2
|
+
import type { ChainId, ExodusProvider, UnknownFunction } from '../../types.js';
|
|
3
|
+
import type { EthereumProvider } from '../provider.js';
|
|
4
|
+
export declare class ExodusEthereumWallet {
|
|
5
|
+
#private;
|
|
6
|
+
get version(): string;
|
|
7
|
+
get name(): string;
|
|
8
|
+
get icon(): string;
|
|
9
|
+
get chains(): string[];
|
|
10
|
+
get features(): {
|
|
11
|
+
'standard:connect': {
|
|
12
|
+
version: string;
|
|
13
|
+
connect: ({ silent }?: {
|
|
14
|
+
silent: boolean;
|
|
15
|
+
}) => Promise<{
|
|
16
|
+
accounts: ExodusEthereumWalletAccount[];
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
'standard:disconnect': {
|
|
20
|
+
version: string;
|
|
21
|
+
disconnect: () => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
'standard:events': {
|
|
24
|
+
version: string;
|
|
25
|
+
on: (event: string, listener: UnknownFunction) => () => void;
|
|
26
|
+
};
|
|
27
|
+
'ethereum:provider': {
|
|
28
|
+
version: string;
|
|
29
|
+
provider: EthereumProvider | null;
|
|
30
|
+
};
|
|
31
|
+
'exodus:': {
|
|
32
|
+
exodus: ExodusProvider | null;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
get accounts(): ExodusEthereumWalletAccount[];
|
|
36
|
+
constructor(exodusProvider: ExodusProvider, supportedChainIds: ChainId[]);
|
|
37
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { EXODUS_ICON } from '@exodus/web3-constants';
|
|
2
|
+
import { ExodusEthereumWalletAccount } from './account.js';
|
|
3
|
+
import { convertChainIdToCanonicalBlockchainIdentifier } from './chains.js';
|
|
4
|
+
export class ExodusEthereumWallet {
|
|
5
|
+
#exodus = null;
|
|
6
|
+
#provider = null;
|
|
7
|
+
#listeners = {};
|
|
8
|
+
#version = '1.0.0';
|
|
9
|
+
#name = 'Exodus';
|
|
10
|
+
#icon = EXODUS_ICON;
|
|
11
|
+
#account = null;
|
|
12
|
+
#chains;
|
|
13
|
+
get version() {
|
|
14
|
+
return this.#version;
|
|
15
|
+
}
|
|
16
|
+
get name() {
|
|
17
|
+
return this.#name;
|
|
18
|
+
}
|
|
19
|
+
get icon() {
|
|
20
|
+
return this.#icon;
|
|
21
|
+
}
|
|
22
|
+
get chains() {
|
|
23
|
+
return this.#chains?.slice() ?? [];
|
|
24
|
+
}
|
|
25
|
+
get features() {
|
|
26
|
+
return {
|
|
27
|
+
'standard:connect': {
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
connect: this.#connect,
|
|
30
|
+
},
|
|
31
|
+
'standard:disconnect': {
|
|
32
|
+
version: '1.0.0',
|
|
33
|
+
disconnect: this.#disconnect,
|
|
34
|
+
},
|
|
35
|
+
'standard:events': {
|
|
36
|
+
version: '1.0.0',
|
|
37
|
+
on: this.#on,
|
|
38
|
+
},
|
|
39
|
+
'ethereum:provider': {
|
|
40
|
+
version: '1.0.0',
|
|
41
|
+
provider: this.#provider,
|
|
42
|
+
},
|
|
43
|
+
'exodus:': {
|
|
44
|
+
exodus: this.#exodus,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
get accounts() {
|
|
49
|
+
return this.#account ? [this.#account] : [];
|
|
50
|
+
}
|
|
51
|
+
constructor(exodusProvider, supportedChainIds) {
|
|
52
|
+
if (!exodusProvider) {
|
|
53
|
+
throw new Error('Exodus Provider is required!');
|
|
54
|
+
}
|
|
55
|
+
if (!exodusProvider.ethereum) {
|
|
56
|
+
throw new Error('Exodus Provider must include Ethereum Provider!');
|
|
57
|
+
}
|
|
58
|
+
if (!supportedChainIds?.length) {
|
|
59
|
+
throw new Error('At least one supported chain must be provided!');
|
|
60
|
+
}
|
|
61
|
+
this.#exodus = exodusProvider;
|
|
62
|
+
this.#provider = exodusProvider.ethereum;
|
|
63
|
+
this.#chains = supportedChainIds.map(convertChainIdToCanonicalBlockchainIdentifier);
|
|
64
|
+
this.#provider.on('accountsChanged', this.#reconnected, this);
|
|
65
|
+
this.#connected();
|
|
66
|
+
}
|
|
67
|
+
#on = (event, listener) => {
|
|
68
|
+
this.#listeners[event]?.push(listener) ||
|
|
69
|
+
(this.#listeners[event] = [listener]);
|
|
70
|
+
return () => this.#off(event, listener);
|
|
71
|
+
};
|
|
72
|
+
#emit = (event, ...args) => {
|
|
73
|
+
// eslint-disable-next-line no-unused-expressions,prefer-spread
|
|
74
|
+
this.#listeners[event]?.forEach((listener) => listener.apply(null, args));
|
|
75
|
+
};
|
|
76
|
+
#off = (event, listener) => {
|
|
77
|
+
this.#listeners[event] = this.#listeners[event]?.filter((existingListener) => listener !== existingListener);
|
|
78
|
+
};
|
|
79
|
+
#connected = async () => {
|
|
80
|
+
const address = await this.#getAddress();
|
|
81
|
+
if (address) {
|
|
82
|
+
const account = this.#account;
|
|
83
|
+
if (!account || account.address !== address) {
|
|
84
|
+
this.#account = new ExodusEthereumWalletAccount({ address });
|
|
85
|
+
this.#emit('change', { accounts: this.accounts });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
#disconnected = () => {
|
|
90
|
+
if (this.#account) {
|
|
91
|
+
this.#account = null;
|
|
92
|
+
this.#emit('change', { accounts: this.accounts });
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
#reconnected = async () => {
|
|
96
|
+
const address = await this.#getAddress();
|
|
97
|
+
if (address) {
|
|
98
|
+
this.#connected();
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
this.#disconnected();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
#connect = async ({ silent } = { silent: false }) => {
|
|
105
|
+
if (!silent) {
|
|
106
|
+
await this.#requestAddress();
|
|
107
|
+
}
|
|
108
|
+
await this.#connected();
|
|
109
|
+
return { accounts: this.accounts };
|
|
110
|
+
};
|
|
111
|
+
#disconnect = async () => {
|
|
112
|
+
this.#disconnected();
|
|
113
|
+
};
|
|
114
|
+
#requestAddress = () => {
|
|
115
|
+
return this.#provider.request({ method: 'eth_requestAccounts' });
|
|
116
|
+
};
|
|
117
|
+
#getAddress = async () => {
|
|
118
|
+
const [address] = (await this.#provider.request({
|
|
119
|
+
method: 'eth_accounts',
|
|
120
|
+
}));
|
|
121
|
+
return address;
|
|
122
|
+
};
|
|
123
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { EthereumProvider } from './provider/provider.js';
|
|
2
|
+
export interface WalletStandardAccountParams {
|
|
3
|
+
address: string;
|
|
4
|
+
chains?: ChainId[];
|
|
5
|
+
publicKey?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ExodusProvider {
|
|
10
|
+
ethereum: EthereumProvider;
|
|
11
|
+
}
|
|
12
|
+
export declare type ChainId = string;
|
|
13
|
+
export declare type UnknownFunction = (...args: unknown[]) => unknown;
|
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/web3-ethereum",
|
|
3
3
|
"description": "Apps connectivity library for EVM-compatible chains.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"module": "lib/index.js",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"./package.json": "./package.json",
|
|
12
12
|
"./provider": "./lib/provider/index.js",
|
|
13
13
|
"./rpc": "./lib/rpc-handlers/index.js",
|
|
14
|
+
"./simulation": "./lib/simulation/index.js",
|
|
14
15
|
"./utils": "./lib/rpc-handlers/index.js"
|
|
15
16
|
},
|
|
16
17
|
"react-native": "./lib/index.js",
|
|
@@ -31,7 +32,10 @@
|
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"@exodus/currency": "^2.1.2",
|
|
33
34
|
"@exodus/ethereum-api": "3.2.4",
|
|
34
|
-
"@exodus/web3-
|
|
35
|
+
"@exodus/web3-constants": "^1.13.1",
|
|
36
|
+
"@exodus/web3-ethereum-utils": "1.7.0",
|
|
37
|
+
"@exodus/web3-provider": "^2.9.0",
|
|
38
|
+
"eth-rpc-errors": "^4.0.3"
|
|
35
39
|
},
|
|
36
40
|
"devDependencies": {
|
|
37
41
|
"@exodus/assets": "^8.0.72",
|
|
@@ -41,5 +45,5 @@
|
|
|
41
45
|
"peerDependencies": {
|
|
42
46
|
"@exodus/assets": "^8.0.72"
|
|
43
47
|
},
|
|
44
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "c220026347ce27cfc25bacc083d61b1aae5c876b"
|
|
45
49
|
}
|