@toruslabs/ethereum-controllers 4.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/dist/ethereumControllers.cjs.js +6153 -0
- package/dist/ethereumControllers.cjs.js.map +1 -0
- package/dist/ethereumControllers.esm.js +5570 -0
- package/dist/ethereumControllers.esm.js.map +1 -0
- package/dist/ethereumControllers.umd.min.js +3 -0
- package/dist/ethereumControllers.umd.min.js.LICENSE.txt +38 -0
- package/dist/ethereumControllers.umd.min.js.map +1 -0
- package/dist/types/Account/AccountTrackerController.d.ts +35 -0
- package/dist/types/Block/PollingBlockTracker.d.ts +14 -0
- package/dist/types/Currency/CurrencyController.d.ts +30 -0
- package/dist/types/Gas/GasFeeController.d.ts +64 -0
- package/dist/types/Gas/IGasFeeController.d.ts +49 -0
- package/dist/types/Gas/gasUtil.d.ts +21 -0
- package/dist/types/Keyring/KeyringController.d.ts +20 -0
- package/dist/types/Message/AbstractMessageController.d.ts +36 -0
- package/dist/types/Message/DecryptMessageController.d.ts +20 -0
- package/dist/types/Message/EncryptionPublicKeyController.d.ts +20 -0
- package/dist/types/Message/MessageController.d.ts +20 -0
- package/dist/types/Message/PersonalMessageController.d.ts +20 -0
- package/dist/types/Message/TypedMessageController.d.ts +21 -0
- package/dist/types/Message/utils.d.ts +10 -0
- package/dist/types/Network/NetworkController.d.ts +40 -0
- package/dist/types/Network/createEthereumMiddleware.d.ts +66 -0
- package/dist/types/Network/createJsonRpcClient.d.ts +9 -0
- package/dist/types/Nfts/INftsController.d.ts +10 -0
- package/dist/types/Nfts/NftHandler.d.ts +35 -0
- package/dist/types/Nfts/NftsController.d.ts +40 -0
- package/dist/types/Preferences/PreferencesController.d.ts +53 -0
- package/dist/types/Tokens/ITokensController.d.ts +10 -0
- package/dist/types/Tokens/TokenHandler.d.ts +20 -0
- package/dist/types/Tokens/TokenRatesController.d.ts +42 -0
- package/dist/types/Tokens/TokensController.d.ts +42 -0
- package/dist/types/Transaction/NonceTracker.d.ts +37 -0
- package/dist/types/Transaction/PendingTransactionTracker.d.ts +32 -0
- package/dist/types/Transaction/TransactionController.d.ts +67 -0
- package/dist/types/Transaction/TransactionGasUtil.d.ts +21 -0
- package/dist/types/Transaction/TransactionStateHistoryHelper.d.ts +16 -0
- package/dist/types/Transaction/TransactionStateManager.d.ts +30 -0
- package/dist/types/Transaction/TransactionUtils.d.ts +70 -0
- package/dist/types/index.d.ts +43 -0
- package/dist/types/utils/abiDecoder.d.ts +17 -0
- package/dist/types/utils/abis.d.ts +84 -0
- package/dist/types/utils/constants.d.ts +81 -0
- package/dist/types/utils/contractAddresses.d.ts +1 -0
- package/dist/types/utils/conversionUtils.d.ts +42 -0
- package/dist/types/utils/helpers.d.ts +24 -0
- package/dist/types/utils/interfaces.d.ts +384 -0
- package/package.json +71 -0
- package/src/Account/AccountTrackerController.ts +157 -0
- package/src/Block/PollingBlockTracker.ts +89 -0
- package/src/Currency/CurrencyController.ts +117 -0
- package/src/Gas/GasFeeController.ts +254 -0
- package/src/Gas/IGasFeeController.ts +56 -0
- package/src/Gas/gasUtil.ts +163 -0
- package/src/Keyring/KeyringController.ts +118 -0
- package/src/Message/AbstractMessageController.ts +136 -0
- package/src/Message/DecryptMessageController.ts +81 -0
- package/src/Message/EncryptionPublicKeyController.ts +83 -0
- package/src/Message/MessageController.ts +74 -0
- package/src/Message/PersonalMessageController.ts +74 -0
- package/src/Message/TypedMessageController.ts +112 -0
- package/src/Message/utils.ts +107 -0
- package/src/Network/NetworkController.ts +184 -0
- package/src/Network/createEthereumMiddleware.ts +307 -0
- package/src/Network/createJsonRpcClient.ts +59 -0
- package/src/Nfts/INftsController.ts +13 -0
- package/src/Nfts/NftHandler.ts +191 -0
- package/src/Nfts/NftsController.ts +230 -0
- package/src/Preferences/PreferencesController.ts +409 -0
- package/src/Tokens/ITokensController.ts +13 -0
- package/src/Tokens/TokenHandler.ts +60 -0
- package/src/Tokens/TokenRatesController.ts +134 -0
- package/src/Tokens/TokensController.ts +278 -0
- package/src/Transaction/NonceTracker.ts +152 -0
- package/src/Transaction/PendingTransactionTracker.ts +235 -0
- package/src/Transaction/TransactionController.ts +558 -0
- package/src/Transaction/TransactionGasUtil.ts +74 -0
- package/src/Transaction/TransactionStateHistoryHelper.ts +41 -0
- package/src/Transaction/TransactionStateManager.ts +315 -0
- package/src/Transaction/TransactionUtils.ts +333 -0
- package/src/index.ts +45 -0
- package/src/utils/abiDecoder.ts +195 -0
- package/src/utils/abis.ts +677 -0
- package/src/utils/constants.ts +379 -0
- package/src/utils/contractAddresses.ts +21 -0
- package/src/utils/conversionUtils.ts +269 -0
- package/src/utils/helpers.ts +177 -0
- package/src/utils/interfaces.ts +454 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { BaseController, PreferencesState } from "@toruslabs/base-controllers";
|
|
2
|
+
import { SafeEventEmitterProvider } from "@toruslabs/openlogin-jrpc";
|
|
3
|
+
import { BrowserProvider } from "ethers";
|
|
4
|
+
import { merge } from "lodash";
|
|
5
|
+
import log from "loglevel";
|
|
6
|
+
|
|
7
|
+
import NetworkController from "../Network/NetworkController";
|
|
8
|
+
import PreferencesController from "../Preferences/PreferencesController";
|
|
9
|
+
import { SIMPLEHASH_SUPPORTED_CHAINS } from "../utils/constants";
|
|
10
|
+
import { idleTimeTracker } from "../utils/helpers";
|
|
11
|
+
import { CustomNftInfo, CustomNftItemInfo, EthereumNetworkState, ExtendedAddressPreferences } from "../utils/interfaces";
|
|
12
|
+
import { NftsControllerConfig, NftsControllerState } from "./INftsController";
|
|
13
|
+
import { NftHandler } from "./NftHandler";
|
|
14
|
+
|
|
15
|
+
export interface INftsControllerOptions {
|
|
16
|
+
config?: Partial<NftsControllerConfig>;
|
|
17
|
+
state?: Partial<NftsControllerState>;
|
|
18
|
+
provider: SafeEventEmitterProvider;
|
|
19
|
+
getNetworkIdentifier: NetworkController["getNetworkIdentifier"];
|
|
20
|
+
getCustomNfts?: PreferencesController["getCustomNfts"];
|
|
21
|
+
getSimpleHashNfts: PreferencesController["getSimpleHashNfts"];
|
|
22
|
+
onPreferencesStateChange: (listener: (preferencesState: PreferencesState<ExtendedAddressPreferences>) => void) => void;
|
|
23
|
+
onNetworkStateChange: (listener: (networkState: EthereumNetworkState) => void) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEFAULT_INTERVAL = 180 * 1000;
|
|
27
|
+
|
|
28
|
+
export class NftsController extends BaseController<NftsControllerConfig, NftsControllerState> {
|
|
29
|
+
name = "NftsController";
|
|
30
|
+
|
|
31
|
+
private provider: SafeEventEmitterProvider;
|
|
32
|
+
|
|
33
|
+
private ethersProvider: BrowserProvider;
|
|
34
|
+
|
|
35
|
+
private _timer: number;
|
|
36
|
+
|
|
37
|
+
private getNetworkIdentifier: NetworkController["getNetworkIdentifier"];
|
|
38
|
+
|
|
39
|
+
private getCustomNfts: PreferencesController["getCustomNfts"];
|
|
40
|
+
|
|
41
|
+
private getSimpleHashNfts: PreferencesController["getSimpleHashNfts"];
|
|
42
|
+
|
|
43
|
+
constructor({
|
|
44
|
+
config,
|
|
45
|
+
state,
|
|
46
|
+
provider,
|
|
47
|
+
getNetworkIdentifier,
|
|
48
|
+
getCustomNfts,
|
|
49
|
+
getSimpleHashNfts,
|
|
50
|
+
onPreferencesStateChange,
|
|
51
|
+
onNetworkStateChange,
|
|
52
|
+
}: INftsControllerOptions) {
|
|
53
|
+
super({ config, state });
|
|
54
|
+
|
|
55
|
+
this.provider = provider;
|
|
56
|
+
this.ethersProvider = new BrowserProvider(this.provider, "any");
|
|
57
|
+
this.getNetworkIdentifier = getNetworkIdentifier;
|
|
58
|
+
|
|
59
|
+
this.getCustomNfts = getCustomNfts;
|
|
60
|
+
this.getSimpleHashNfts = getSimpleHashNfts;
|
|
61
|
+
|
|
62
|
+
this.defaultConfig = {
|
|
63
|
+
interval: DEFAULT_INTERVAL,
|
|
64
|
+
selectedAddress: "",
|
|
65
|
+
chainId: "",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
this.defaultState = {
|
|
69
|
+
nfts: {},
|
|
70
|
+
};
|
|
71
|
+
this.initialize();
|
|
72
|
+
onPreferencesStateChange((preferencesState) => {
|
|
73
|
+
if (preferencesState.selectedAddress !== this.config.selectedAddress) {
|
|
74
|
+
this.configure({ selectedAddress: preferencesState.selectedAddress });
|
|
75
|
+
this.restartNftDetection();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
onNetworkStateChange((networkState) => {
|
|
80
|
+
const { chainId } = networkState.providerConfig;
|
|
81
|
+
if (chainId !== this.config.chainId) {
|
|
82
|
+
this.configure({ chainId });
|
|
83
|
+
this.restartNftDetection();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get userSelectedAddress(): string {
|
|
89
|
+
return this.config.selectedAddress;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get userNfts() {
|
|
93
|
+
if (!this.userSelectedAddress) return [];
|
|
94
|
+
return this.state.nfts[this.userSelectedAddress] ?? [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get interval(): number {
|
|
98
|
+
return this.config.interval;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
set interval(interval: number) {
|
|
102
|
+
if (this._timer) window.clearInterval(this._timer);
|
|
103
|
+
if (!interval) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this._timer = window.setInterval(() => {
|
|
107
|
+
if (!idleTimeTracker.checkIfIdle()) {
|
|
108
|
+
this.detectNewNfts();
|
|
109
|
+
this.refreshNftBalances();
|
|
110
|
+
}
|
|
111
|
+
}, interval);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public startNftDetection(selectedAddress: string) {
|
|
115
|
+
this.configure({ selectedAddress });
|
|
116
|
+
this.restartNftDetection();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Restart nft detection polling period and call detectNewNfts
|
|
121
|
+
* in case of address change or user session initialization.
|
|
122
|
+
*
|
|
123
|
+
*/
|
|
124
|
+
public restartNftDetection() {
|
|
125
|
+
if (!this.userSelectedAddress) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.detectNewNfts();
|
|
129
|
+
this.refreshNftBalances();
|
|
130
|
+
this.config.interval = DEFAULT_INTERVAL;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public detectNewNfts() {
|
|
134
|
+
const userAddress = this.userSelectedAddress;
|
|
135
|
+
if (!userAddress) return;
|
|
136
|
+
const currentChainId = this.getNetworkIdentifier();
|
|
137
|
+
const nftsToDetect: CustomNftInfo[] = []; // object[]
|
|
138
|
+
if (!currentChainId) {
|
|
139
|
+
this.update({ nfts: { [userAddress]: [...nftsToDetect] } });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (this.getCustomNfts) {
|
|
144
|
+
const customNfts = this.getCustomNfts(userAddress);
|
|
145
|
+
|
|
146
|
+
const reducedNfts = customNfts.reduce(
|
|
147
|
+
(acc, x) => {
|
|
148
|
+
// first aggregate by contract address
|
|
149
|
+
if (x.network === currentChainId) {
|
|
150
|
+
const newAsset: CustomNftItemInfo = {
|
|
151
|
+
description: "",
|
|
152
|
+
image: "",
|
|
153
|
+
name: "",
|
|
154
|
+
tokenBalance: "",
|
|
155
|
+
tokenId: x.nft_id,
|
|
156
|
+
customNftId: x.id.toString(),
|
|
157
|
+
};
|
|
158
|
+
if (acc[x.nft_address]) {
|
|
159
|
+
acc[x.nft_address].assets.push(newAsset);
|
|
160
|
+
} else {
|
|
161
|
+
const objToPush: CustomNftInfo = {
|
|
162
|
+
assets: [newAsset],
|
|
163
|
+
chainId: x.network,
|
|
164
|
+
contractAddress: x.nft_address,
|
|
165
|
+
contractName: "",
|
|
166
|
+
contractSymbol: "",
|
|
167
|
+
contractImage: "",
|
|
168
|
+
nftStandard: x.nft_contract_standard,
|
|
169
|
+
contractDescription: "",
|
|
170
|
+
} as CustomNftInfo;
|
|
171
|
+
acc[x.nft_address] = objToPush;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return acc;
|
|
175
|
+
},
|
|
176
|
+
{} as Record<string, CustomNftInfo>
|
|
177
|
+
);
|
|
178
|
+
nftsToDetect.push(...Object.values(reducedNfts));
|
|
179
|
+
}
|
|
180
|
+
this.update({ nfts: { [userAddress]: [...nftsToDetect] } });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async refreshNftBalances() {
|
|
184
|
+
const userAddress = this.userSelectedAddress;
|
|
185
|
+
if (userAddress === "") return;
|
|
186
|
+
const oldNfts = [...this.userNfts];
|
|
187
|
+
const nonZeroNfts: CustomNftInfo[] = [];
|
|
188
|
+
try {
|
|
189
|
+
const currentChainId = this.getNetworkIdentifier();
|
|
190
|
+
if (SIMPLEHASH_SUPPORTED_CHAINS.includes(currentChainId)) {
|
|
191
|
+
const simpleHashBalances = await this.getSimpleHashNfts(userAddress, currentChainId);
|
|
192
|
+
nonZeroNfts.push(...simpleHashBalances);
|
|
193
|
+
this.update({ nfts: { [userAddress]: nonZeroNfts } });
|
|
194
|
+
}
|
|
195
|
+
if (oldNfts.length > 0) {
|
|
196
|
+
this.getNftBalancesUsingHandler(oldNfts);
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
log.error(error, "unable to fetch nft balances");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async getNftBalancesUsingHandler(customNfts: CustomNftInfo[]) {
|
|
204
|
+
if (!this.userSelectedAddress) return;
|
|
205
|
+
const userAddress = this.userSelectedAddress;
|
|
206
|
+
const currentNetworkNfts = customNfts;
|
|
207
|
+
const promiseSettledResult = await Promise.allSettled(
|
|
208
|
+
currentNetworkNfts.map(async (x) => {
|
|
209
|
+
try {
|
|
210
|
+
const tokenInstance = new NftHandler({
|
|
211
|
+
...x,
|
|
212
|
+
provider: this.ethersProvider,
|
|
213
|
+
});
|
|
214
|
+
const contractData = await tokenInstance.getContractMetadata();
|
|
215
|
+
const assetData = await Promise.allSettled(x.assets.map((y) => tokenInstance.getNftMetadata(userAddress, y)));
|
|
216
|
+
return {
|
|
217
|
+
...contractData,
|
|
218
|
+
assets: assetData.filter((z) => z.status === "fulfilled").map((z) => (z as PromiseFulfilledResult<CustomNftItemInfo>).value),
|
|
219
|
+
} as CustomNftInfo;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
log.warn("Invalid contract address while fetching", error);
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
const nonZeroTokens = promiseSettledResult.filter((x) => x.status === "fulfilled").map((x) => (x as PromiseFulfilledResult<CustomNftInfo>).value);
|
|
227
|
+
|
|
228
|
+
this.update({ nfts: { [userAddress]: merge(this.userNfts, nonZeroTokens) } });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACTIVITY_ACTION_RECEIVE,
|
|
3
|
+
ACTIVITY_ACTION_SEND,
|
|
4
|
+
ACTIVITY_ACTION_TOPUP,
|
|
5
|
+
BasePreferencesController,
|
|
6
|
+
CustomNft,
|
|
7
|
+
CustomToken,
|
|
8
|
+
IPreferencesController,
|
|
9
|
+
PreferencesConfig,
|
|
10
|
+
PreferencesState,
|
|
11
|
+
TransactionStatus,
|
|
12
|
+
UserInfo,
|
|
13
|
+
} from "@toruslabs/base-controllers";
|
|
14
|
+
import { get, patch, post, remove } from "@toruslabs/http-helpers";
|
|
15
|
+
import { SafeEventEmitterProvider } from "@toruslabs/openlogin-jrpc";
|
|
16
|
+
import { Mutex } from "async-mutex";
|
|
17
|
+
import log from "loglevel";
|
|
18
|
+
|
|
19
|
+
import PollingBlockTracker from "../Block/PollingBlockTracker";
|
|
20
|
+
import KeyringController from "../Keyring/KeyringController";
|
|
21
|
+
import NetworkController from "../Network/NetworkController";
|
|
22
|
+
import { SUPPORTED_NETWORKS } from "../utils/constants";
|
|
23
|
+
import { formatDate, formatPastTx, formatTime, getEthTxStatus } from "../utils/helpers";
|
|
24
|
+
import type {
|
|
25
|
+
CustomNetworkPayload,
|
|
26
|
+
CustomNetworks,
|
|
27
|
+
CustomNftInfo,
|
|
28
|
+
CustomTokenInfo,
|
|
29
|
+
EthereumUser,
|
|
30
|
+
ExtendedAddressPreferences,
|
|
31
|
+
FetchCommonTransaction,
|
|
32
|
+
FetchedTransaction,
|
|
33
|
+
FormattedTransactionActivity,
|
|
34
|
+
TransactionPayload,
|
|
35
|
+
} from "../utils/interfaces";
|
|
36
|
+
|
|
37
|
+
interface IPreferencesControllerOptions {
|
|
38
|
+
config?: Partial<PreferencesConfig> & Pick<PreferencesConfig, "api" | "commonApiHost" | "signInPrefix">;
|
|
39
|
+
state?: Partial<PreferencesState<ExtendedAddressPreferences>>;
|
|
40
|
+
provider: SafeEventEmitterProvider;
|
|
41
|
+
// TODO: Require later
|
|
42
|
+
blockTracker?: PollingBlockTracker;
|
|
43
|
+
signAuthMessage?: KeyringController["signAuthMessage"];
|
|
44
|
+
getProviderConfig?: NetworkController["getProviderConfig"];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default class PreferencesController
|
|
48
|
+
extends BasePreferencesController<ExtendedAddressPreferences, PreferencesConfig, PreferencesState<ExtendedAddressPreferences>>
|
|
49
|
+
implements IPreferencesController<ExtendedAddressPreferences, PreferencesConfig, PreferencesState<ExtendedAddressPreferences>>
|
|
50
|
+
{
|
|
51
|
+
private _handle?: number;
|
|
52
|
+
|
|
53
|
+
private _mutex: Mutex = new Mutex();
|
|
54
|
+
|
|
55
|
+
private getProviderConfig: NetworkController["getProviderConfig"];
|
|
56
|
+
|
|
57
|
+
private provider: SafeEventEmitterProvider;
|
|
58
|
+
|
|
59
|
+
private blockTracker: PollingBlockTracker;
|
|
60
|
+
|
|
61
|
+
constructor({ config, state, provider, blockTracker, signAuthMessage, getProviderConfig }: IPreferencesControllerOptions) {
|
|
62
|
+
super({ config, state, defaultPreferences: { formattedPastTransactions: [], fetchedPastTx: [], paymentTx: [] }, signAuthMessage });
|
|
63
|
+
this.provider = provider;
|
|
64
|
+
this.getProviderConfig = getProviderConfig;
|
|
65
|
+
this.blockTracker = blockTracker;
|
|
66
|
+
log.info(this.blockTracker);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public async poll(interval?: number): Promise<void> {
|
|
70
|
+
const releaseLock = await this._mutex.acquire();
|
|
71
|
+
if (interval) this.configure({ pollInterval: interval });
|
|
72
|
+
if (this._handle) window.clearTimeout(this._handle);
|
|
73
|
+
// call here
|
|
74
|
+
const storeSelectedAddress = this.state.selectedAddress;
|
|
75
|
+
if (!storeSelectedAddress) return;
|
|
76
|
+
if (!this.getAddressState(storeSelectedAddress)?.jwtToken) return;
|
|
77
|
+
// This should never throw
|
|
78
|
+
await this.sync(storeSelectedAddress);
|
|
79
|
+
releaseLock();
|
|
80
|
+
this._handle = window.setTimeout(() => {
|
|
81
|
+
this.poll(this.config.pollInterval);
|
|
82
|
+
}, this.config.pollInterval);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async initPreferences(params: {
|
|
86
|
+
address: string;
|
|
87
|
+
jwtToken?: string;
|
|
88
|
+
calledFromEmbed?: boolean;
|
|
89
|
+
userInfo?: UserInfo;
|
|
90
|
+
rehydrate?: boolean;
|
|
91
|
+
locale?: string;
|
|
92
|
+
}): Promise<void> {
|
|
93
|
+
const { address, jwtToken, calledFromEmbed, userInfo, rehydrate, locale = "en-US" } = params;
|
|
94
|
+
await super.init(address, userInfo, jwtToken);
|
|
95
|
+
const { aggregateVerifier, verifier, verifierId } = userInfo || {};
|
|
96
|
+
const userExists = await this.sync(address);
|
|
97
|
+
if (!userExists) {
|
|
98
|
+
const accountState = this.getAddressState(address);
|
|
99
|
+
await this.createUser({
|
|
100
|
+
selectedCurrency: accountState.selectedCurrency,
|
|
101
|
+
theme: accountState.theme,
|
|
102
|
+
verifier: aggregateVerifier || verifier,
|
|
103
|
+
verifierId,
|
|
104
|
+
locale,
|
|
105
|
+
address,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (!rehydrate)
|
|
109
|
+
await this.storeUserLogin({ verifier: aggregateVerifier || verifier, verifierId, options: { calledFromEmbed, rehydrate }, address });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public getSelectedAddress(): string {
|
|
113
|
+
return this.state.selectedAddress;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async sync(address: string): Promise<boolean> {
|
|
117
|
+
try {
|
|
118
|
+
const user = await this.getUser<EthereumUser>(address);
|
|
119
|
+
if (user) {
|
|
120
|
+
const {
|
|
121
|
+
default_currency: defaultCurrency,
|
|
122
|
+
contacts,
|
|
123
|
+
theme,
|
|
124
|
+
locale,
|
|
125
|
+
public_address: userPublicAddress,
|
|
126
|
+
default_public_address: defaultPublicAddress,
|
|
127
|
+
customNetworks,
|
|
128
|
+
customTokens,
|
|
129
|
+
customNfts,
|
|
130
|
+
} = user || {};
|
|
131
|
+
|
|
132
|
+
// update latest data in state.
|
|
133
|
+
this.updateState(
|
|
134
|
+
{
|
|
135
|
+
contacts,
|
|
136
|
+
theme,
|
|
137
|
+
selectedCurrency: defaultCurrency,
|
|
138
|
+
locale,
|
|
139
|
+
defaultPublicAddress: defaultPublicAddress || userPublicAddress,
|
|
140
|
+
customTokens,
|
|
141
|
+
customNfts,
|
|
142
|
+
customNetworks,
|
|
143
|
+
},
|
|
144
|
+
address
|
|
145
|
+
);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
log.error(error);
|
|
151
|
+
return false;
|
|
152
|
+
} finally {
|
|
153
|
+
Promise.all([
|
|
154
|
+
this.getWalletOrders<FetchedTransaction>(address).catch((error) => {
|
|
155
|
+
log.error("unable to fetch wallet orders", error);
|
|
156
|
+
}),
|
|
157
|
+
this.getTopUpOrders<FetchCommonTransaction>(address).catch((error) => {
|
|
158
|
+
log.error("unable to fetch top up orders", error);
|
|
159
|
+
}),
|
|
160
|
+
])
|
|
161
|
+
.then((data) => {
|
|
162
|
+
const [walletTx, paymentTx] = data;
|
|
163
|
+
if (paymentTx) {
|
|
164
|
+
this.calculatePaymentTx(paymentTx, address);
|
|
165
|
+
}
|
|
166
|
+
// eslint-disable-next-line promise/always-return
|
|
167
|
+
if (walletTx && walletTx.length > 0) {
|
|
168
|
+
this.updateState({ fetchedPastTx: [...walletTx] }, address);
|
|
169
|
+
this.calculatePastTx(walletTx, address);
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
.catch((error) => log.error(error));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public async patchNewTx(tx: TransactionPayload, address: string): Promise<void> {
|
|
177
|
+
const formattedTx = formatPastTx(tx);
|
|
178
|
+
const storePastTx = this.getAddressState(address)?.formattedPastTransactions;
|
|
179
|
+
const duplicateIndex = storePastTx.findIndex((x) => x.transaction_hash === tx.transaction_hash && x.networkType === tx.network);
|
|
180
|
+
if (tx.status === TransactionStatus.submitted || tx.status === TransactionStatus.confirmed) {
|
|
181
|
+
if (duplicateIndex === -1) {
|
|
182
|
+
// No duplicate found
|
|
183
|
+
|
|
184
|
+
const finalTx = this.cancelTxCalculate([...storePastTx, formattedTx]);
|
|
185
|
+
tx.is_cancel = formattedTx.is_cancel;
|
|
186
|
+
tx.to = tx.to.toLowerCase();
|
|
187
|
+
tx.from = tx.from.toLowerCase();
|
|
188
|
+
|
|
189
|
+
this.updateState({ formattedPastTransactions: finalTx }, address);
|
|
190
|
+
this.postPastTx<TransactionPayload>(tx, address);
|
|
191
|
+
} else {
|
|
192
|
+
// avoid overriding is_cancel
|
|
193
|
+
formattedTx.is_cancel = storePastTx[duplicateIndex].is_cancel;
|
|
194
|
+
storePastTx[duplicateIndex] = formattedTx;
|
|
195
|
+
this.updateState({ formattedPastTransactions: this.cancelTxCalculate([...storePastTx]) }, address);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public recalculatePastTx(address?: string) {
|
|
201
|
+
// This triggers store update which calculates past Tx status for that network
|
|
202
|
+
const selectedAddress = address || this.state.selectedAddress;
|
|
203
|
+
const state = this.getAddressState(selectedAddress);
|
|
204
|
+
if (!state?.fetchedPastTx) return;
|
|
205
|
+
this.calculatePastTx(state.fetchedPastTx, selectedAddress);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public async refetchEtherscanTx(address?: string) {
|
|
209
|
+
const selectedAddress = address || this.state.selectedAddress;
|
|
210
|
+
if (this.getAddressState(selectedAddress)?.jwtToken) {
|
|
211
|
+
// const selectedNetwork = this.getProviderConfig().rpcTarget;
|
|
212
|
+
// if (ETHERSCAN_SUPPORTED_NETWORKS.has(selectedNetwork)) {
|
|
213
|
+
// const data = await this.fetchEtherscanTx({ selectedAddress, selectedNetwork });
|
|
214
|
+
// if (data.length) {
|
|
215
|
+
// this.emit("addEtherscanTransactions", data, selectedNetwork);
|
|
216
|
+
// }
|
|
217
|
+
// }
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public async getEtherScanTokens(address: string, chainId: string): Promise<CustomTokenInfo[]> {
|
|
222
|
+
const selectedAddress = address;
|
|
223
|
+
const apiUrl = new URL(this.config.api);
|
|
224
|
+
apiUrl.pathname = `/tokens`;
|
|
225
|
+
apiUrl.searchParams.append("chainId", chainId);
|
|
226
|
+
apiUrl.searchParams.append("address", selectedAddress);
|
|
227
|
+
const result = await get<{ data: CustomTokenInfo[] }>(apiUrl.href, this.headers(this.state.selectedAddress));
|
|
228
|
+
return result.data;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public async getSimpleHashNfts(address: string, chainId: string): Promise<CustomNftInfo[]> {
|
|
232
|
+
const selectedAddress = address;
|
|
233
|
+
const apiUrl = new URL(this.config.api);
|
|
234
|
+
apiUrl.pathname = `/nfts`;
|
|
235
|
+
apiUrl.searchParams.append("chainId", chainId);
|
|
236
|
+
apiUrl.searchParams.append("address", selectedAddress);
|
|
237
|
+
const result = await get<{ data: CustomNftInfo[] }>(apiUrl.href, this.headers(this.state.selectedAddress));
|
|
238
|
+
return result.data;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
public getCustomTokens(address?: string): CustomToken[] {
|
|
242
|
+
return this.getAddressState(address)?.customTokens ?? [];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
public getCustomNfts(address?: string): CustomNft[] {
|
|
246
|
+
return this.getAddressState(address)?.customNfts ?? [];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Custom Network methods
|
|
250
|
+
public async addCustomNetwork({ type, network }: { type: string; network: CustomNetworkPayload }): Promise<number> {
|
|
251
|
+
try {
|
|
252
|
+
const apiUrl = new URL(this.config.api);
|
|
253
|
+
apiUrl.pathname = `/customnetwork/${type}`;
|
|
254
|
+
const { selectedAddress } = this.state;
|
|
255
|
+
const payload = {
|
|
256
|
+
network_name: network.displayName,
|
|
257
|
+
rpc_url: network.rpcTarget,
|
|
258
|
+
chain_id: network.chainId,
|
|
259
|
+
symbol: network.ticker,
|
|
260
|
+
block_explorer_url: network.blockExplorerUrl || undefined,
|
|
261
|
+
is_test_net: network.isTestNet || false,
|
|
262
|
+
};
|
|
263
|
+
const res = await post<{ data: CustomNetworks }>(apiUrl.href, payload, this.headers(selectedAddress), { useAPIKey: true });
|
|
264
|
+
await this.sync(selectedAddress);
|
|
265
|
+
|
|
266
|
+
return res.data.id;
|
|
267
|
+
} catch {
|
|
268
|
+
log.error("error adding custom network");
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async deleteCustomNetwork(id: number) {
|
|
274
|
+
try {
|
|
275
|
+
const { selectedAddress } = this.state;
|
|
276
|
+
const apiUrl = new URL(this.config.api);
|
|
277
|
+
apiUrl.pathname = `/customnetwork/${id}`;
|
|
278
|
+
await remove(apiUrl.href, {}, this.headers(selectedAddress), { useAPIKey: true });
|
|
279
|
+
await this.sync(selectedAddress);
|
|
280
|
+
return true;
|
|
281
|
+
} catch {
|
|
282
|
+
log.error("error deleting custom network");
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async editCustomNetwork({ network, id }: { network: CustomNetworkPayload; id: number | null }) {
|
|
288
|
+
try {
|
|
289
|
+
const { selectedAddress } = this.state;
|
|
290
|
+
const apiUrl = new URL(this.config.api);
|
|
291
|
+
apiUrl.pathname = `/customnetwork/${id}`;
|
|
292
|
+
|
|
293
|
+
const payload = {
|
|
294
|
+
network_name: network.displayName,
|
|
295
|
+
rpc_url: network.rpcTarget,
|
|
296
|
+
chain_id: network.chainId,
|
|
297
|
+
symbol: network.ticker || undefined,
|
|
298
|
+
block_explorer_url: network.blockExplorerUrl || undefined,
|
|
299
|
+
is_test_net: network.isTestNet || false,
|
|
300
|
+
};
|
|
301
|
+
await patch(apiUrl.href, payload, this.headers(selectedAddress), { useAPIKey: true });
|
|
302
|
+
await this.sync(selectedAddress);
|
|
303
|
+
return true;
|
|
304
|
+
} catch {
|
|
305
|
+
log.error("error editing custom network");
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private calculatePaymentTx(txs: FetchCommonTransaction[], address: string) {
|
|
311
|
+
const accumulator: FetchCommonTransaction[] = [];
|
|
312
|
+
for (const x of txs) {
|
|
313
|
+
let action = "";
|
|
314
|
+
const lowerCaseAction = x.action.toLowerCase();
|
|
315
|
+
if (ACTIVITY_ACTION_TOPUP.includes(lowerCaseAction)) action = ACTIVITY_ACTION_TOPUP;
|
|
316
|
+
else if (ACTIVITY_ACTION_SEND.includes(lowerCaseAction)) action = ACTIVITY_ACTION_SEND;
|
|
317
|
+
else if (ACTIVITY_ACTION_RECEIVE.includes(lowerCaseAction)) action = ACTIVITY_ACTION_RECEIVE;
|
|
318
|
+
|
|
319
|
+
accumulator.push({
|
|
320
|
+
id: x.id,
|
|
321
|
+
date: new Date(x.date).toDateString(),
|
|
322
|
+
from: x.from,
|
|
323
|
+
slicedFrom: x.slicedFrom,
|
|
324
|
+
action,
|
|
325
|
+
to: x.to,
|
|
326
|
+
slicedTo: x.slicedTo,
|
|
327
|
+
totalAmount: x.totalAmount,
|
|
328
|
+
totalAmountString: x.totalAmountString,
|
|
329
|
+
currencyAmount: x.currencyAmount,
|
|
330
|
+
currencyAmountString: x.currencyAmountString,
|
|
331
|
+
amount: x.amount,
|
|
332
|
+
ethRate: x.ethRate,
|
|
333
|
+
status: x.status.toLowerCase(),
|
|
334
|
+
etherscanLink: x.etherscanLink || "",
|
|
335
|
+
blockExplorerLink: "",
|
|
336
|
+
currencyUsed: x.currencyUsed,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
this.updateState({ paymentTx: accumulator }, address);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private async calculatePastTx(txs: FetchedTransaction[], address: string) {
|
|
343
|
+
const pastTx = [];
|
|
344
|
+
const pendingTx = [];
|
|
345
|
+
const lowerCaseSelectedAddress = address.toLowerCase();
|
|
346
|
+
for (const x of txs) {
|
|
347
|
+
if (
|
|
348
|
+
x.network === SUPPORTED_NETWORKS[this.getProviderConfig().chainId].chainId &&
|
|
349
|
+
x.to &&
|
|
350
|
+
x.from &&
|
|
351
|
+
(lowerCaseSelectedAddress === x.from.toLowerCase() || lowerCaseSelectedAddress === x.to.toLowerCase())
|
|
352
|
+
) {
|
|
353
|
+
if (x.status !== "confirmed") {
|
|
354
|
+
pendingTx.push(x);
|
|
355
|
+
} else {
|
|
356
|
+
const finalObject = formatPastTx(x, lowerCaseSelectedAddress);
|
|
357
|
+
pastTx.push(finalObject);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const pendingTxPromises = pendingTx.map((x) => getEthTxStatus(x.transaction_hash, this.provider).catch((error) => log.error(error)));
|
|
362
|
+
const resolvedTxStatuses = await Promise.all(pendingTxPromises);
|
|
363
|
+
for (const [index, element] of pendingTx.entries()) {
|
|
364
|
+
const finalObject = formatPastTx(element, lowerCaseSelectedAddress);
|
|
365
|
+
finalObject.status = resolvedTxStatuses[index] || TransactionStatus.submitted;
|
|
366
|
+
pastTx.push(finalObject);
|
|
367
|
+
if (lowerCaseSelectedAddress === element.from.toLowerCase() && finalObject.status && finalObject.status !== element.status)
|
|
368
|
+
this.patchPastTx({ id: element.id, status: finalObject.status, updated_at: new Date().toISOString() }, address);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const finalTx = this.cancelTxCalculate(pastTx);
|
|
372
|
+
|
|
373
|
+
this.updateState({ formattedPastTransactions: [...finalTx] }, address);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private cancelTxCalculate(pastTx: FormattedTransactionActivity[]) {
|
|
377
|
+
const nonceMap: Record<string, FormattedTransactionActivity[]> = {};
|
|
378
|
+
for (const x of pastTx) {
|
|
379
|
+
if (!nonceMap[x.nonce]) nonceMap[x.nonce] = [x];
|
|
380
|
+
else {
|
|
381
|
+
nonceMap[x.nonce].push(x);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
for (const [, value] of Object.entries(nonceMap)) {
|
|
386
|
+
// has duplicate
|
|
387
|
+
if (value.length > 1) {
|
|
388
|
+
// get latest and mark it as is_cancel
|
|
389
|
+
const latestTxs = value.sort((a, b) => {
|
|
390
|
+
const aDate = new Date(a.date).getTime();
|
|
391
|
+
const bDate = new Date(b.date).getTime();
|
|
392
|
+
return bDate - aDate;
|
|
393
|
+
});
|
|
394
|
+
const latestCancelTx = latestTxs[0];
|
|
395
|
+
latestCancelTx.is_cancel = true;
|
|
396
|
+
latestTxs.slice(1).forEach((x) => {
|
|
397
|
+
x.hasCancel = true;
|
|
398
|
+
x.status = latestCancelTx.status === "confirmed" ? TransactionStatus.cancelled : TransactionStatus.cancelling;
|
|
399
|
+
x.cancelDateInitiated = `${formatTime(new Date(latestCancelTx.date).getTime())} - ${formatDate(latestCancelTx.date)}`;
|
|
400
|
+
x.etherscanLink = latestCancelTx.etherscanLink;
|
|
401
|
+
x.cancelGas = latestCancelTx.gas;
|
|
402
|
+
x.cancelGasPrice = latestCancelTx.gasPrice;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return pastTx;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseConfig, BaseState } from "@toruslabs/base-controllers";
|
|
2
|
+
|
|
3
|
+
import { CustomTokenInfo } from "../utils/interfaces";
|
|
4
|
+
|
|
5
|
+
export interface TokensControllerConfig extends BaseConfig {
|
|
6
|
+
interval?: number;
|
|
7
|
+
selectedAddress: string;
|
|
8
|
+
chainId: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TokensControllerState extends BaseState {
|
|
12
|
+
tokens: Record<string, CustomTokenInfo[]>;
|
|
13
|
+
}
|