@exodus/web3-ethereum 0.1.0 → 1.1.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 +3 -1
- package/lib/index.js +3 -1
- 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/simulation/constant.d.ts +1 -5
- package/lib/simulation/constant.js +1 -32
- package/lib/simulation/index.d.ts +1 -16
- package/lib/simulation/index.js +1 -70
- package/lib/simulation/simulation.d.ts +16 -0
- package/lib/simulation/simulation.js +67 -0
- package/lib/simulation/types.d.ts +0 -4
- package/lib/simulation/utils.d.ts +0 -6
- package/lib/simulation/utils.js +0 -7
- package/lib/wallet-standard/icon.d.ts +2 -0
- package/lib/wallet-standard/icon.js +2 -0
- package/lib/wallet-standard/index.d.ts +1 -0
- package/lib/wallet-standard/index.js +1 -0
- package/lib/wallet-standard/wallet.d.ts +17 -0
- package/lib/wallet-standard/wallet.js +35 -0
- package/package.json +10 -5
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 {};
|
|
@@ -1,32 +1 @@
|
|
|
1
|
-
export const
|
|
2
|
-
avalanchec: 43114,
|
|
3
|
-
bsc: 56,
|
|
4
|
-
ethereum: 1,
|
|
5
|
-
ethereumclassic: 61,
|
|
6
|
-
ethereumgoerli: 5,
|
|
7
|
-
fantommainnet: 250,
|
|
8
|
-
harmonymainnet: 1666600000,
|
|
9
|
-
matic: 137,
|
|
10
|
-
};
|
|
11
|
-
export const ENABLED_EVMS = [
|
|
12
|
-
{
|
|
13
|
-
assetName: 'ethereum',
|
|
14
|
-
shouldSimulate: true,
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
assetName: 'matic',
|
|
18
|
-
shouldSimulate: false,
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
assetName: 'bsc',
|
|
22
|
-
shouldSimulate: false,
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
assetName: 'avalanchec',
|
|
26
|
-
shouldSimulate: false,
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
assetName: 'fantommainnet',
|
|
30
|
-
shouldSimulate: false,
|
|
31
|
-
},
|
|
32
|
-
];
|
|
1
|
+
export const SIMULATION_SUPPORTED_ASSET_NAMES = ['ethereum']; // just ethereum for now
|
|
@@ -1,16 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { Asset, EthBalanceChanges } from './types.js';
|
|
3
|
-
export declare const simulateTransaction: (assets: Record<string, Asset>, transaction: EthTransaction, network: string) => Promise<{
|
|
4
|
-
assetName: string;
|
|
5
|
-
advancedDetails: never[];
|
|
6
|
-
balanceChanges: EthBalanceChanges;
|
|
7
|
-
displayInfo: {
|
|
8
|
-
title: string;
|
|
9
|
-
};
|
|
10
|
-
txFee: unknown;
|
|
11
|
-
txFeeDetails: {
|
|
12
|
-
gasLimit: number;
|
|
13
|
-
maxFeePerGas: number | undefined;
|
|
14
|
-
gasPrice: number;
|
|
15
|
-
};
|
|
16
|
-
}>;
|
|
1
|
+
export { simulateTransaction } from './simulation.js';
|
package/lib/simulation/index.js
CHANGED
|
@@ -1,70 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { createCurrency, formatAccounts, getEthereumDisplayInfo, supportedNetworkEvms, hexToBN, isEthTransferTx, } from './utils.js';
|
|
3
|
-
const computeBalanceChanges = async (asset, shouldSimulate, transaction) => {
|
|
4
|
-
const assetName = asset.name;
|
|
5
|
-
let balanceChanges = {
|
|
6
|
-
willReceive: [],
|
|
7
|
-
willSend: [],
|
|
8
|
-
willApprove: [],
|
|
9
|
-
};
|
|
10
|
-
if (isEthTransferTx(transaction)) {
|
|
11
|
-
const txValue = hexToBN(transaction.value).toString();
|
|
12
|
-
balanceChanges.willSend.push({
|
|
13
|
-
balance: createCurrency({
|
|
14
|
-
amount: txValue,
|
|
15
|
-
denominator: asset.currency.defaultUnit.power,
|
|
16
|
-
symbol: asset.displayTicker,
|
|
17
|
-
}),
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
try {
|
|
22
|
-
const sideEffect = await retrieveSideEffects({
|
|
23
|
-
assetName,
|
|
24
|
-
shouldSimulate,
|
|
25
|
-
transaction,
|
|
26
|
-
});
|
|
27
|
-
if (sideEffect) {
|
|
28
|
-
balanceChanges = formatAccounts(sideEffect);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
console.error('Ethereum transaction simulation failed.', error);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return balanceChanges;
|
|
36
|
-
};
|
|
37
|
-
const getTxFeeDetails = (asset, transaction) => {
|
|
38
|
-
const maxFeePerGasBn = transaction.maxFeePerGas
|
|
39
|
-
? hexToBN(transaction.maxFeePerGas)
|
|
40
|
-
: undefined;
|
|
41
|
-
const gasPriceBn = hexToBN(transaction.gasPrice);
|
|
42
|
-
const gasBn = hexToBN(transaction.gas);
|
|
43
|
-
return {
|
|
44
|
-
txFee: asset.currency.baseUnit(gasBn.mul(maxFeePerGasBn || gasPriceBn).toString()),
|
|
45
|
-
txFeeDetails: {
|
|
46
|
-
gasLimit: gasBn.toNumber(),
|
|
47
|
-
maxFeePerGas: maxFeePerGasBn?.toNumber(),
|
|
48
|
-
gasPrice: gasPriceBn.toNumber(),
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
export const simulateTransaction = async (assets, transaction, network) => {
|
|
53
|
-
const evm = supportedNetworkEvms.get(network);
|
|
54
|
-
if (!evm) {
|
|
55
|
-
throw new Error(`unsupported network, ${network}`);
|
|
56
|
-
}
|
|
57
|
-
const { assetName, shouldSimulate } = evm;
|
|
58
|
-
const asset = assets[assetName];
|
|
59
|
-
const balanceChanges = await computeBalanceChanges(asset, shouldSimulate, transaction);
|
|
60
|
-
const { txFee, txFeeDetails } = getTxFeeDetails(asset, transaction);
|
|
61
|
-
const displayInfo = getEthereumDisplayInfo(balanceChanges);
|
|
62
|
-
return {
|
|
63
|
-
assetName,
|
|
64
|
-
advancedDetails: [],
|
|
65
|
-
balanceChanges,
|
|
66
|
-
displayInfo,
|
|
67
|
-
txFee,
|
|
68
|
-
txFeeDetails,
|
|
69
|
-
};
|
|
70
|
-
};
|
|
1
|
+
export { simulateTransaction } from './simulation.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EthTransaction } from '@exodus/web3-ethereum-utils';
|
|
2
|
+
import type { Asset, EthBalanceChanges } from './types.js';
|
|
3
|
+
export declare const simulateTransaction: (asset: Asset, transaction: EthTransaction) => Promise<{
|
|
4
|
+
assetName: string;
|
|
5
|
+
advancedDetails: never[];
|
|
6
|
+
balanceChanges: EthBalanceChanges;
|
|
7
|
+
displayInfo: {
|
|
8
|
+
title: string;
|
|
9
|
+
};
|
|
10
|
+
txFee: unknown;
|
|
11
|
+
txFeeDetails: {
|
|
12
|
+
gasLimit: number;
|
|
13
|
+
maxFeePerGas: number | undefined;
|
|
14
|
+
gasPrice: number;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { retrieveSideEffects } from '@exodus/ethereum-api';
|
|
2
|
+
import { SIMULATION_SUPPORTED_ASSET_NAMES } from './constant.js';
|
|
3
|
+
import { createCurrency, formatAccounts, getEthereumDisplayInfo, hexToBN, isEthTransferTx, } from './utils.js';
|
|
4
|
+
const computeBalanceChanges = async (asset, shouldSimulate, transaction) => {
|
|
5
|
+
const assetName = asset.name;
|
|
6
|
+
let balanceChanges = {
|
|
7
|
+
willReceive: [],
|
|
8
|
+
willSend: [],
|
|
9
|
+
willApprove: [],
|
|
10
|
+
};
|
|
11
|
+
if (isEthTransferTx(transaction)) {
|
|
12
|
+
const txValue = hexToBN(transaction.value).toString();
|
|
13
|
+
balanceChanges.willSend.push({
|
|
14
|
+
balance: createCurrency({
|
|
15
|
+
amount: txValue,
|
|
16
|
+
denominator: asset.currency.defaultUnit.power,
|
|
17
|
+
symbol: asset.displayTicker,
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
try {
|
|
23
|
+
const sideEffect = await retrieveSideEffects({
|
|
24
|
+
assetName,
|
|
25
|
+
shouldSimulate,
|
|
26
|
+
transaction,
|
|
27
|
+
});
|
|
28
|
+
if (sideEffect) {
|
|
29
|
+
balanceChanges = formatAccounts(sideEffect);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error('Ethereum transaction simulation failed.', error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return balanceChanges;
|
|
37
|
+
};
|
|
38
|
+
const getTxFeeDetails = (asset, transaction) => {
|
|
39
|
+
const maxFeePerGasBn = transaction.maxFeePerGas
|
|
40
|
+
? hexToBN(transaction.maxFeePerGas)
|
|
41
|
+
: undefined;
|
|
42
|
+
const gasPriceBn = hexToBN(transaction.gasPrice);
|
|
43
|
+
const gasBn = hexToBN(transaction.gas);
|
|
44
|
+
return {
|
|
45
|
+
txFee: asset.currency.baseUnit(gasBn.mul(maxFeePerGasBn || gasPriceBn).toString()),
|
|
46
|
+
txFeeDetails: {
|
|
47
|
+
gasLimit: gasBn.toNumber(),
|
|
48
|
+
maxFeePerGas: maxFeePerGasBn?.toNumber(),
|
|
49
|
+
gasPrice: gasPriceBn.toNumber(),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export const simulateTransaction = async (asset, transaction) => {
|
|
54
|
+
const assetName = asset.name;
|
|
55
|
+
const shouldSimulate = SIMULATION_SUPPORTED_ASSET_NAMES.includes(assetName);
|
|
56
|
+
const balanceChanges = await computeBalanceChanges(asset, shouldSimulate, transaction);
|
|
57
|
+
const { txFee, txFeeDetails } = getTxFeeDetails(asset, transaction);
|
|
58
|
+
const displayInfo = getEthereumDisplayInfo(balanceChanges);
|
|
59
|
+
return {
|
|
60
|
+
assetName,
|
|
61
|
+
advancedDetails: [],
|
|
62
|
+
balanceChanges,
|
|
63
|
+
displayInfo,
|
|
64
|
+
txFee,
|
|
65
|
+
txFeeDetails,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -2,12 +2,6 @@ import BN from 'bn.js';
|
|
|
2
2
|
import type { CurrencyParams, EthBalanceChange, EthBalanceChanges } from './types.js';
|
|
3
3
|
import type { EthTransaction } from '@exodus/web3-ethereum-utils';
|
|
4
4
|
export declare const hexToBN: (hexValue?: string) => BN;
|
|
5
|
-
export declare const supportedNetworkEvms: Map<string, {
|
|
6
|
-
chainId: string;
|
|
7
|
-
network: string;
|
|
8
|
-
assetName: string;
|
|
9
|
-
shouldSimulate: boolean;
|
|
10
|
-
}>;
|
|
11
5
|
export declare const isEthTransferTx: (transaction: EthTransaction) => boolean;
|
|
12
6
|
export declare function getEthereumDisplayInfo(balanceChanges: EthBalanceChanges): {
|
|
13
7
|
title: string;
|
package/lib/simulation/utils.js
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import { UnitType } from '@exodus/currency';
|
|
2
2
|
import BN from 'bn.js';
|
|
3
|
-
import { CHAIN_IDS, ENABLED_EVMS } from './constant.js';
|
|
4
3
|
export const hexToBN = (hexValue) => new BN((hexValue || '').slice(2), 'hex');
|
|
5
|
-
const getEvmNetwork = (chainId) => `evm:${chainId}`;
|
|
6
|
-
export const supportedNetworkEvms = new Map(ENABLED_EVMS.map((constant) => {
|
|
7
|
-
const chainId = '0x' + CHAIN_IDS[constant.assetName].toString(16);
|
|
8
|
-
const network = getEvmNetwork(chainId);
|
|
9
|
-
return [network, { ...constant, chainId, network }];
|
|
10
|
-
}));
|
|
11
4
|
export const isEthTransferTx = (transaction) => {
|
|
12
5
|
return (['0x', null, undefined].includes(transaction.data) &&
|
|
13
6
|
[undefined, '0x5208'].includes(transaction.gas));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ExodusEthereumWallet } from './wallet.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ExodusEthereumWallet } from './wallet.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Wallet } from '@wallet-standard/base';
|
|
2
|
+
import type { EthereumProvider } from '../provider/provider.js';
|
|
3
|
+
export declare type EthereumProviderFeature = {
|
|
4
|
+
'ethereum:provider': {
|
|
5
|
+
provider: EthereumProvider;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export declare class ExodusEthereumWallet implements Wallet {
|
|
9
|
+
#private;
|
|
10
|
+
get version(): "1.0.0";
|
|
11
|
+
get name(): "Exodus";
|
|
12
|
+
get icon(): `data:image/svg+xml;base64,${string}` | `data:image/webp;base64,${string}` | `data:image/png;base64,${string}` | `data:image/gif;base64,${string}`;
|
|
13
|
+
get chains(): never[];
|
|
14
|
+
get features(): EthereumProviderFeature;
|
|
15
|
+
get accounts(): never[];
|
|
16
|
+
constructor(ethereumProvider: EthereumProvider);
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { icon } from './icon.js';
|
|
2
|
+
export class ExodusEthereumWallet {
|
|
3
|
+
#version = '1.0.0';
|
|
4
|
+
#name = 'Exodus';
|
|
5
|
+
#icon = icon;
|
|
6
|
+
#provider = null;
|
|
7
|
+
get version() {
|
|
8
|
+
return this.#version;
|
|
9
|
+
}
|
|
10
|
+
get name() {
|
|
11
|
+
return this.#name;
|
|
12
|
+
}
|
|
13
|
+
get icon() {
|
|
14
|
+
return this.#icon;
|
|
15
|
+
}
|
|
16
|
+
get chains() {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
get features() {
|
|
20
|
+
return {
|
|
21
|
+
'ethereum:provider': {
|
|
22
|
+
provider: this.#provider,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
get accounts() {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
constructor(ethereumProvider) {
|
|
30
|
+
if (!ethereumProvider) {
|
|
31
|
+
throw new Error('Ethereum Provider is required!');
|
|
32
|
+
}
|
|
33
|
+
this.#provider = ethereumProvider;
|
|
34
|
+
}
|
|
35
|
+
}
|
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.1.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"module": "lib/index.js",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"./package.json": "./package.json",
|
|
12
12
|
"./provider": "./lib/provider/index.js",
|
|
13
13
|
"./rpc": "./lib/rpc-handlers/index.js",
|
|
14
|
-
"./
|
|
14
|
+
"./simulation": "./lib/simulation/index.js",
|
|
15
|
+
"./wallet-standard": "./lib/wallet-standard/index.js"
|
|
15
16
|
},
|
|
16
17
|
"react-native": "./lib/index.js",
|
|
17
18
|
"files": [
|
|
@@ -31,15 +32,19 @@
|
|
|
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",
|
|
38
42
|
"@exodus/schemasafe": "^1.0.0-rc.7",
|
|
39
|
-
"@exodus/web3-types": "^1.6.0"
|
|
43
|
+
"@exodus/web3-types": "^1.6.0",
|
|
44
|
+
"@wallet-standard/base": "^1.0.1"
|
|
40
45
|
},
|
|
41
46
|
"peerDependencies": {
|
|
42
47
|
"@exodus/assets": "^8.0.72"
|
|
43
48
|
},
|
|
44
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "75254d697e3ffd2d95759e30425c30c7fb7fa654"
|
|
45
50
|
}
|