@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.
Files changed (88) hide show
  1. package/dist/ethereumControllers.cjs.js +6153 -0
  2. package/dist/ethereumControllers.cjs.js.map +1 -0
  3. package/dist/ethereumControllers.esm.js +5570 -0
  4. package/dist/ethereumControllers.esm.js.map +1 -0
  5. package/dist/ethereumControllers.umd.min.js +3 -0
  6. package/dist/ethereumControllers.umd.min.js.LICENSE.txt +38 -0
  7. package/dist/ethereumControllers.umd.min.js.map +1 -0
  8. package/dist/types/Account/AccountTrackerController.d.ts +35 -0
  9. package/dist/types/Block/PollingBlockTracker.d.ts +14 -0
  10. package/dist/types/Currency/CurrencyController.d.ts +30 -0
  11. package/dist/types/Gas/GasFeeController.d.ts +64 -0
  12. package/dist/types/Gas/IGasFeeController.d.ts +49 -0
  13. package/dist/types/Gas/gasUtil.d.ts +21 -0
  14. package/dist/types/Keyring/KeyringController.d.ts +20 -0
  15. package/dist/types/Message/AbstractMessageController.d.ts +36 -0
  16. package/dist/types/Message/DecryptMessageController.d.ts +20 -0
  17. package/dist/types/Message/EncryptionPublicKeyController.d.ts +20 -0
  18. package/dist/types/Message/MessageController.d.ts +20 -0
  19. package/dist/types/Message/PersonalMessageController.d.ts +20 -0
  20. package/dist/types/Message/TypedMessageController.d.ts +21 -0
  21. package/dist/types/Message/utils.d.ts +10 -0
  22. package/dist/types/Network/NetworkController.d.ts +40 -0
  23. package/dist/types/Network/createEthereumMiddleware.d.ts +66 -0
  24. package/dist/types/Network/createJsonRpcClient.d.ts +9 -0
  25. package/dist/types/Nfts/INftsController.d.ts +10 -0
  26. package/dist/types/Nfts/NftHandler.d.ts +35 -0
  27. package/dist/types/Nfts/NftsController.d.ts +40 -0
  28. package/dist/types/Preferences/PreferencesController.d.ts +53 -0
  29. package/dist/types/Tokens/ITokensController.d.ts +10 -0
  30. package/dist/types/Tokens/TokenHandler.d.ts +20 -0
  31. package/dist/types/Tokens/TokenRatesController.d.ts +42 -0
  32. package/dist/types/Tokens/TokensController.d.ts +42 -0
  33. package/dist/types/Transaction/NonceTracker.d.ts +37 -0
  34. package/dist/types/Transaction/PendingTransactionTracker.d.ts +32 -0
  35. package/dist/types/Transaction/TransactionController.d.ts +67 -0
  36. package/dist/types/Transaction/TransactionGasUtil.d.ts +21 -0
  37. package/dist/types/Transaction/TransactionStateHistoryHelper.d.ts +16 -0
  38. package/dist/types/Transaction/TransactionStateManager.d.ts +30 -0
  39. package/dist/types/Transaction/TransactionUtils.d.ts +70 -0
  40. package/dist/types/index.d.ts +43 -0
  41. package/dist/types/utils/abiDecoder.d.ts +17 -0
  42. package/dist/types/utils/abis.d.ts +84 -0
  43. package/dist/types/utils/constants.d.ts +81 -0
  44. package/dist/types/utils/contractAddresses.d.ts +1 -0
  45. package/dist/types/utils/conversionUtils.d.ts +42 -0
  46. package/dist/types/utils/helpers.d.ts +24 -0
  47. package/dist/types/utils/interfaces.d.ts +384 -0
  48. package/package.json +71 -0
  49. package/src/Account/AccountTrackerController.ts +157 -0
  50. package/src/Block/PollingBlockTracker.ts +89 -0
  51. package/src/Currency/CurrencyController.ts +117 -0
  52. package/src/Gas/GasFeeController.ts +254 -0
  53. package/src/Gas/IGasFeeController.ts +56 -0
  54. package/src/Gas/gasUtil.ts +163 -0
  55. package/src/Keyring/KeyringController.ts +118 -0
  56. package/src/Message/AbstractMessageController.ts +136 -0
  57. package/src/Message/DecryptMessageController.ts +81 -0
  58. package/src/Message/EncryptionPublicKeyController.ts +83 -0
  59. package/src/Message/MessageController.ts +74 -0
  60. package/src/Message/PersonalMessageController.ts +74 -0
  61. package/src/Message/TypedMessageController.ts +112 -0
  62. package/src/Message/utils.ts +107 -0
  63. package/src/Network/NetworkController.ts +184 -0
  64. package/src/Network/createEthereumMiddleware.ts +307 -0
  65. package/src/Network/createJsonRpcClient.ts +59 -0
  66. package/src/Nfts/INftsController.ts +13 -0
  67. package/src/Nfts/NftHandler.ts +191 -0
  68. package/src/Nfts/NftsController.ts +230 -0
  69. package/src/Preferences/PreferencesController.ts +409 -0
  70. package/src/Tokens/ITokensController.ts +13 -0
  71. package/src/Tokens/TokenHandler.ts +60 -0
  72. package/src/Tokens/TokenRatesController.ts +134 -0
  73. package/src/Tokens/TokensController.ts +278 -0
  74. package/src/Transaction/NonceTracker.ts +152 -0
  75. package/src/Transaction/PendingTransactionTracker.ts +235 -0
  76. package/src/Transaction/TransactionController.ts +558 -0
  77. package/src/Transaction/TransactionGasUtil.ts +74 -0
  78. package/src/Transaction/TransactionStateHistoryHelper.ts +41 -0
  79. package/src/Transaction/TransactionStateManager.ts +315 -0
  80. package/src/Transaction/TransactionUtils.ts +333 -0
  81. package/src/index.ts +45 -0
  82. package/src/utils/abiDecoder.ts +195 -0
  83. package/src/utils/abis.ts +677 -0
  84. package/src/utils/constants.ts +379 -0
  85. package/src/utils/contractAddresses.ts +21 -0
  86. package/src/utils/conversionUtils.ts +269 -0
  87. package/src/utils/helpers.ts +177 -0
  88. package/src/utils/interfaces.ts +454 -0
@@ -0,0 +1,307 @@
1
+ import { InPageWalletProviderState, PROVIDER_JRPC_METHODS } from "@toruslabs/base-controllers";
2
+ import {
3
+ createAsyncMiddleware,
4
+ createScaffoldMiddleware,
5
+ JRPCEngineEndCallback,
6
+ JRPCEngineNextCallback,
7
+ JRPCMiddleware,
8
+ JRPCRequest,
9
+ JRPCResponse,
10
+ mergeMiddleware,
11
+ } from "@toruslabs/openlogin-jrpc";
12
+
13
+ import { METHOD_TYPES, TRANSACTION_ENVELOPE_TYPES } from "../utils/constants";
14
+ import {
15
+ BlockParams,
16
+ EthereumTransactionMeta,
17
+ MessageParams,
18
+ TransactionParams,
19
+ TransactionRPCMeta,
20
+ TypedMessageParams,
21
+ UserRequestApprovalParams,
22
+ } from "../utils/interfaces";
23
+
24
+ export interface IProviderHandlers {
25
+ version: string;
26
+ requestAccounts?: (req: JRPCRequest<string[]>) => Promise<string[]>;
27
+ getAccounts: (req: JRPCRequest<string[]>) => Promise<string[]>;
28
+
29
+ // All operations which open popup window or modal should operate with a windowId parameter
30
+ processTransaction?: (txParams: TransactionParams, req: JRPCRequest<TransactionParams> & UserRequestApprovalParams) => Promise<string>;
31
+
32
+ processEthSignMessage?: (msgParams: MessageParams, req: JRPCRequest<MessageParams> & UserRequestApprovalParams) => Promise<string>;
33
+ processTypedMessage?: (msgParams: TypedMessageParams, req: JRPCRequest<TypedMessageParams> & UserRequestApprovalParams) => Promise<string>;
34
+ processTypedMessageV3?: (msgParams: TypedMessageParams, req: JRPCRequest<TypedMessageParams> & UserRequestApprovalParams) => Promise<string>;
35
+ processTypedMessageV4?: (msgParams: TypedMessageParams, req: JRPCRequest<TypedMessageParams> & UserRequestApprovalParams) => Promise<string>;
36
+ processPersonalMessage?: (msgParams: MessageParams, req: JRPCRequest<MessageParams> & UserRequestApprovalParams) => Promise<string>;
37
+
38
+ processEncryptionPublicKey?: (address: string, req: JRPCRequest<string> & UserRequestApprovalParams) => Promise<string>;
39
+ processDecryptMessage?: (msgParams: MessageParams, req: JRPCRequest<MessageParams> & UserRequestApprovalParams) => Promise<string>;
40
+
41
+ getPendingNonce?: (
42
+ nonceParams: { address: string; blockReference: string },
43
+ req: JRPCRequest<{ address: string; blockReference: string }>
44
+ ) => Promise<string>;
45
+ getPendingTransactionByHash?: (hash: string, req: JRPCRequest<string>) => Promise<EthereumTransactionMeta>;
46
+
47
+ getProviderState: (
48
+ req: JRPCRequest<[]>,
49
+ res: JRPCResponse<InPageWalletProviderState>,
50
+ next: JRPCEngineNextCallback,
51
+ end: JRPCEngineEndCallback
52
+ ) => void;
53
+ }
54
+
55
+ export function createGetAccountsMiddleware({ getAccounts }: { getAccounts: IProviderHandlers["getAccounts"] }): JRPCMiddleware<never, string[]> {
56
+ return createAsyncMiddleware(async (request, response, next) => {
57
+ const { method } = request;
58
+ if (method !== METHOD_TYPES.GET_ACCOUNTS) return next();
59
+
60
+ if (!getAccounts) throw new Error("WalletMiddleware - opts.getAccounts not provided");
61
+
62
+ const accounts = await getAccounts(request);
63
+ response.result = accounts;
64
+ });
65
+ }
66
+
67
+ export function createProcessTransactionMiddleware({
68
+ processTransaction,
69
+ }: {
70
+ processTransaction: IProviderHandlers["processTransaction"];
71
+ }): JRPCMiddleware<TransactionParams, string> {
72
+ return createAsyncMiddleware(async (request, response, next) => {
73
+ const { method } = request;
74
+ if (method !== METHOD_TYPES.ETH_TRANSACTION) return next();
75
+
76
+ if (!processTransaction) throw new Error("WalletMiddleware - opts.processTransaction not provided");
77
+
78
+ response.result = await processTransaction(request.params, request);
79
+ });
80
+ }
81
+
82
+ export function createProcessEthSignMessage({
83
+ processEthSignMessage,
84
+ }: {
85
+ processEthSignMessage: IProviderHandlers["processEthSignMessage"];
86
+ }): JRPCMiddleware<MessageParams, string> {
87
+ return createAsyncMiddleware(async (request, response, next) => {
88
+ const { method } = request;
89
+ if (method !== METHOD_TYPES.ETH_SIGN) return next();
90
+
91
+ if (!processEthSignMessage) throw new Error("WalletMiddleware - opts.processEthSignMessage not provided");
92
+
93
+ response.result = await processEthSignMessage(request.params, request);
94
+ });
95
+ }
96
+
97
+ export function createProcessTypedMessage({
98
+ processTypedMessage,
99
+ }: {
100
+ processTypedMessage: IProviderHandlers["processTypedMessage"];
101
+ }): JRPCMiddleware<MessageParams, string> {
102
+ return createAsyncMiddleware(async (request, response, next) => {
103
+ const { method } = request;
104
+ if (method !== METHOD_TYPES.ETH_SIGN_TYPED_DATA) return next();
105
+
106
+ if (!processTypedMessage) throw new Error("WalletMiddleware - opts.processTypedMessage not provided");
107
+ response.result = await processTypedMessage(request.params, request);
108
+ });
109
+ }
110
+
111
+ export function createProcessTypedMessageV3({
112
+ processTypedMessageV3,
113
+ }: {
114
+ processTypedMessageV3: IProviderHandlers["processTypedMessageV3"];
115
+ }): JRPCMiddleware<TypedMessageParams, string> {
116
+ return createAsyncMiddleware(async (request, response, next) => {
117
+ const { method } = request;
118
+ if (method !== METHOD_TYPES.ETH_SIGN_TYPED_DATA_V3) return next();
119
+
120
+ if (!processTypedMessageV3) throw new Error("WalletMiddleware - opts.processTypedMessageV3 is not provided");
121
+
122
+ response.result = await processTypedMessageV3(request.params, request);
123
+ });
124
+ }
125
+
126
+ export function createProcessTypedMessageV4({
127
+ processTypedMessageV4,
128
+ }: {
129
+ processTypedMessageV4: IProviderHandlers["processTypedMessageV4"];
130
+ }): JRPCMiddleware<TypedMessageParams, string> {
131
+ return createAsyncMiddleware(async (request, response, next) => {
132
+ const { method } = request;
133
+ if (method !== METHOD_TYPES.ETH_SIGN_TYPED_DATA_V4) return next();
134
+
135
+ if (!processTypedMessageV4) throw new Error("WalletMiddleware - opts.processTypedMessageV4 is not provided");
136
+
137
+ response.result = await processTypedMessageV4(request.params, request);
138
+ });
139
+ }
140
+
141
+ export function createProcessPersonalMessage({
142
+ processPersonalMessage,
143
+ }: {
144
+ processPersonalMessage: IProviderHandlers["processPersonalMessage"];
145
+ }): JRPCMiddleware<MessageParams, string> {
146
+ return createAsyncMiddleware(async (request, response, next) => {
147
+ const { method } = request;
148
+ if (method !== METHOD_TYPES.PERSONAL_SIGN) return next();
149
+
150
+ if (!processPersonalMessage) throw new Error("WalletMiddleware - opts.processPersonalMessage is not provided");
151
+
152
+ response.result = await processPersonalMessage(request.params, request);
153
+ });
154
+ }
155
+
156
+ export function createPendingNonceMiddleware({
157
+ getPendingNonce,
158
+ }: {
159
+ getPendingNonce: IProviderHandlers["getPendingNonce"];
160
+ }): JRPCMiddleware<{ address: string; blockReference: BlockParams }, string> {
161
+ return createAsyncMiddleware(async (request, response, next) => {
162
+ const { params, method } = request;
163
+ if (method !== METHOD_TYPES.ETH_GET_TRANSACTION_COUNT) return next();
164
+
165
+ const { blockReference } = params;
166
+ if (blockReference !== "pending") return next();
167
+ response.result = await getPendingNonce(params, request);
168
+ });
169
+ }
170
+
171
+ export function formatTxMetaForRpcResult(txMeta: EthereumTransactionMeta): TransactionRPCMeta {
172
+ const { r, s, v, txReceipt, transaction, transactionHash, accessList } = txMeta;
173
+ const { to, data, nonce, gas, from, value, gasPrice, maxFeePerGas, maxPriorityFeePerGas } = transaction;
174
+
175
+ const formattedTxMeta: TransactionRPCMeta = {
176
+ v,
177
+ r,
178
+ s,
179
+ to,
180
+ gas,
181
+ from,
182
+ hash: transactionHash,
183
+ nonce,
184
+ input: data || "0x",
185
+ value: value || "0x0",
186
+ accessList: accessList || null,
187
+ blockHash: txReceipt?.blockHash || null,
188
+ blockNumber: txReceipt?.blockNumber || null,
189
+ transactionIndex: txReceipt?.transactionIndex || null,
190
+ type: null,
191
+ };
192
+
193
+ if (maxFeePerGas && maxPriorityFeePerGas) {
194
+ formattedTxMeta.maxFeePerGas = maxFeePerGas;
195
+ formattedTxMeta.maxPriorityFeePerGas = maxPriorityFeePerGas;
196
+ formattedTxMeta.type = TRANSACTION_ENVELOPE_TYPES.FEE_MARKET;
197
+ } else {
198
+ formattedTxMeta.gasPrice = gasPrice;
199
+ formattedTxMeta.type = TRANSACTION_ENVELOPE_TYPES.LEGACY;
200
+ }
201
+
202
+ return formattedTxMeta;
203
+ }
204
+
205
+ export function createPendingTxMiddleware({
206
+ getPendingTransactionByHash,
207
+ }: {
208
+ getPendingTransactionByHash: IProviderHandlers["getPendingTransactionByHash"];
209
+ }): JRPCMiddleware<string, TransactionRPCMeta> {
210
+ return createAsyncMiddleware(async (request, response, next) => {
211
+ const { params, method } = request;
212
+ if (method !== METHOD_TYPES.ETH_GET_TRANSACTION_BY_HASH) return next();
213
+
214
+ if (!getPendingTransactionByHash) throw new Error("WalletMiddleware - opts.getPendingTransactionByHash not provided");
215
+
216
+ const txMeta = await getPendingTransactionByHash(params, request);
217
+ if (!txMeta) {
218
+ return next();
219
+ }
220
+ response.result = formatTxMetaForRpcResult(txMeta);
221
+ return undefined;
222
+ });
223
+ }
224
+
225
+ export function createProcessEncryptionPublicKeyMiddleware({
226
+ processEncryptionPublicKey,
227
+ }: {
228
+ processEncryptionPublicKey: IProviderHandlers["processEncryptionPublicKey"];
229
+ }): JRPCMiddleware<string, string> {
230
+ return createAsyncMiddleware(async (request, response, next) => {
231
+ const { params, method } = request;
232
+ if (method !== METHOD_TYPES.ETH_GET_ENCRYPTION_PUBLIC_KEY) return next();
233
+
234
+ if (!processEncryptionPublicKey) throw new Error("WalletMiddleware - opts.processEncryptionPublicKey not provided");
235
+
236
+ response.result = await processEncryptionPublicKey(params, request);
237
+ });
238
+ }
239
+
240
+ export function createProcessDecryptMessageMiddleware({
241
+ processDecryptMessage,
242
+ }: {
243
+ processDecryptMessage: IProviderHandlers["processDecryptMessage"];
244
+ }): JRPCMiddleware<MessageParams, string> {
245
+ return createAsyncMiddleware(async (request, response, next) => {
246
+ const { params, method } = request;
247
+ if (method !== METHOD_TYPES.ETH_DECRYPT) return next();
248
+
249
+ if (!processDecryptMessage) throw new Error("WalletMiddleware - opts.processDecryptMessage not provided");
250
+
251
+ response.result = await processDecryptMessage(params, request);
252
+ });
253
+ }
254
+
255
+ export function createRequestAccountsMiddleware({
256
+ requestAccounts,
257
+ }: {
258
+ requestAccounts: IProviderHandlers["requestAccounts"];
259
+ }): JRPCMiddleware<string[], unknown> {
260
+ return createAsyncMiddleware(async (request, response, next) => {
261
+ const { method } = request;
262
+ if (method !== "eth_requestAccounts") return next();
263
+
264
+ if (!requestAccounts) throw new Error("WalletMiddleware - opts.requestAccounts not provided");
265
+ // This calls the UI login function
266
+ const accounts = await requestAccounts(request);
267
+ response.result = accounts;
268
+ return undefined;
269
+ });
270
+ }
271
+
272
+ export function createEthereumMiddleware(providerHandlers: IProviderHandlers): JRPCMiddleware<unknown, unknown> {
273
+ const {
274
+ requestAccounts,
275
+ getAccounts,
276
+ processTransaction,
277
+ processEthSignMessage,
278
+ processTypedMessage,
279
+ processTypedMessageV3,
280
+ processTypedMessageV4,
281
+ processPersonalMessage,
282
+ getPendingNonce,
283
+ getPendingTransactionByHash,
284
+ processEncryptionPublicKey,
285
+ processDecryptMessage,
286
+ getProviderState,
287
+ version,
288
+ } = providerHandlers;
289
+ return mergeMiddleware([
290
+ createScaffoldMiddleware({
291
+ version,
292
+ [PROVIDER_JRPC_METHODS.GET_PROVIDER_STATE]: getProviderState as JRPCMiddleware<unknown, unknown>,
293
+ }),
294
+ createRequestAccountsMiddleware({ requestAccounts }) as JRPCMiddleware<unknown, unknown>,
295
+ createGetAccountsMiddleware({ getAccounts }) as JRPCMiddleware<unknown, unknown>,
296
+ createProcessTransactionMiddleware({ processTransaction }) as JRPCMiddleware<unknown, unknown>,
297
+ createProcessEthSignMessage({ processEthSignMessage }) as JRPCMiddleware<unknown, unknown>,
298
+ createProcessTypedMessage({ processTypedMessage }) as JRPCMiddleware<unknown, unknown>,
299
+ createProcessTypedMessageV3({ processTypedMessageV3 }) as JRPCMiddleware<unknown, unknown>,
300
+ createProcessTypedMessageV4({ processTypedMessageV4 }) as JRPCMiddleware<unknown, unknown>,
301
+ createProcessPersonalMessage({ processPersonalMessage }) as JRPCMiddleware<unknown, unknown>,
302
+ createPendingNonceMiddleware({ getPendingNonce }) as JRPCMiddleware<unknown, unknown>,
303
+ createPendingTxMiddleware({ getPendingTransactionByHash }) as JRPCMiddleware<unknown, unknown>,
304
+ createProcessEncryptionPublicKeyMiddleware({ processEncryptionPublicKey }) as JRPCMiddleware<unknown, unknown>,
305
+ createProcessDecryptMessageMiddleware({ processDecryptMessage }) as JRPCMiddleware<unknown, unknown>,
306
+ ]);
307
+ }
@@ -0,0 +1,59 @@
1
+ import { createFetchMiddleware, ProviderConfig } from "@toruslabs/base-controllers";
2
+ import {
3
+ JRPCEngineEndCallback,
4
+ JRPCEngineNextCallback,
5
+ JRPCMiddleware,
6
+ JRPCRequest,
7
+ JRPCResponse,
8
+ mergeMiddleware,
9
+ providerFromMiddleware,
10
+ } from "@toruslabs/openlogin-jrpc";
11
+
12
+ import PollingBlockTracker from "../Block/PollingBlockTracker";
13
+
14
+ export function createChainIdMiddleware(chainId: string): JRPCMiddleware<unknown, string> {
15
+ return (req: JRPCRequest<unknown>, res: JRPCResponse<string>, next: JRPCEngineNextCallback, end: JRPCEngineEndCallback) => {
16
+ if (req.method === "eth_chainId") {
17
+ res.result = chainId;
18
+ return end();
19
+ }
20
+ if (req.method === "net_version") {
21
+ // convert to decimal
22
+ res.result = Number.parseInt(chainId, 16).toString(10);
23
+ return end();
24
+ }
25
+ return next();
26
+ };
27
+ }
28
+
29
+ export function createProviderConfigMiddleware(providerConfig: ProviderConfig): JRPCMiddleware<unknown, ProviderConfig> {
30
+ return (req: JRPCRequest<unknown>, res: JRPCResponse<ProviderConfig>, next: JRPCEngineNextCallback, end: JRPCEngineEndCallback) => {
31
+ if (req.method === "eth_provider_config") {
32
+ res.result = providerConfig;
33
+ return end();
34
+ }
35
+ return next();
36
+ };
37
+ }
38
+
39
+ export function createJsonRpcClient(providerConfig: ProviderConfig): {
40
+ networkMiddleware: JRPCMiddleware<unknown, unknown>;
41
+ blockTracker: PollingBlockTracker;
42
+ } {
43
+ const { chainId, rpcTarget } = providerConfig;
44
+ const fetchMiddleware = createFetchMiddleware({ rpcTarget });
45
+ const blockProvider = providerFromMiddleware(fetchMiddleware as JRPCMiddleware<unknown, unknown>);
46
+ const blockTracker = new PollingBlockTracker({ config: { provider: blockProvider }, state: {} });
47
+
48
+ const networkMiddleware = mergeMiddleware([
49
+ createChainIdMiddleware(chainId) as JRPCMiddleware<unknown, unknown>,
50
+ createProviderConfigMiddleware(providerConfig) as JRPCMiddleware<unknown, unknown>,
51
+ // No need for the following middlewares for web because all browser sessions are quite short lived and each session is limited to scope of a window/tab
52
+ // createBlockRefRewriteMiddleware({ blockTracker }),
53
+ // createBlockCacheMiddleware({ blockTracker }),
54
+ // createInflightCacheMiddleware(),
55
+ // createBlockTrackerInspectorMiddleware({ blockTracker }),
56
+ fetchMiddleware as JRPCMiddleware<unknown, unknown>,
57
+ ]);
58
+ return { networkMiddleware, blockTracker };
59
+ }
@@ -0,0 +1,13 @@
1
+ import { BaseConfig, BaseState } from "@toruslabs/base-controllers";
2
+
3
+ import { CustomNftInfo } from "../utils/interfaces";
4
+
5
+ export interface NftsControllerConfig extends BaseConfig {
6
+ interval?: number;
7
+ selectedAddress: string;
8
+ chainId: string;
9
+ }
10
+
11
+ export interface NftsControllerState extends BaseState {
12
+ nfts: Record<string, CustomNftInfo[]>;
13
+ }
@@ -0,0 +1,191 @@
1
+ import { get } from "@toruslabs/http-helpers";
2
+ import { BrowserProvider, Contract } from "ethers";
3
+ import log from "loglevel";
4
+
5
+ import { erc721Abi, erc1155Abi } from "../utils/abis";
6
+ import { CONTRACT_TYPE_ERC721, CONTRACT_TYPE_ERC1155, ERC721_INTERFACE_ID, ERC1155_INTERFACE_ID, OLD_ERC721_LIST } from "../utils/constants";
7
+ import { sanitizeNftMetdataUrl } from "../utils/helpers";
8
+ import { CustomNftInfo, CustomNftItemInfo, NftStandardType } from "../utils/interfaces";
9
+
10
+ interface INftOptions {
11
+ contractAddress: string;
12
+ contractName?: string;
13
+ contractSymbol?: string;
14
+ contractImage?: string;
15
+ contractSupply?: string;
16
+ contractFallbackLogo?: string;
17
+ nftStandard?: "erc721" | "erc1155";
18
+ contractDescription?: string;
19
+ chainId: string;
20
+ provider: BrowserProvider;
21
+ }
22
+
23
+ export class NftHandler {
24
+ public contractAddress: string;
25
+
26
+ public contractName: string;
27
+
28
+ public contractSymbol: string;
29
+
30
+ public contractImage: string;
31
+
32
+ public contractSupply?: string;
33
+
34
+ public contractFallbackLogo?: string;
35
+
36
+ public nftStandard: "erc721" | "erc1155";
37
+
38
+ public contractDescription?: string;
39
+
40
+ public chainId: string;
41
+
42
+ public provider: BrowserProvider;
43
+
44
+ public isSpecial?: boolean;
45
+
46
+ constructor({
47
+ chainId,
48
+ contractAddress,
49
+ contractImage,
50
+ contractName,
51
+ contractSymbol,
52
+ nftStandard,
53
+ provider,
54
+ contractDescription,
55
+ contractFallbackLogo,
56
+ contractSupply,
57
+ }: INftOptions) {
58
+ this.chainId = chainId;
59
+ this.contractAddress = contractAddress;
60
+ this.contractImage = contractImage;
61
+ this.contractName = contractName;
62
+ this.contractSymbol = contractSymbol;
63
+ this.nftStandard = nftStandard;
64
+ this.provider = provider;
65
+ this.contractDescription = contractDescription;
66
+ this.contractFallbackLogo = contractFallbackLogo;
67
+ this.contractSupply = contractSupply;
68
+ }
69
+
70
+ async getNftMetadata(userAddress: string, tokenInfo: Partial<CustomNftItemInfo>): Promise<CustomNftItemInfo> {
71
+ const returnNftItem: CustomNftItemInfo = { description: "", image: "", name: "", tokenBalance: "", tokenId: "", decimals: "1", ...tokenInfo };
72
+
73
+ const [tokenURI, balance] = await Promise.all([
74
+ this.getCollectibleTokenURI(returnNftItem.tokenId, this.nftStandard),
75
+ !returnNftItem.tokenBalance ? this.fetchNftBalance(userAddress, returnNftItem.tokenId) : Promise.resolve("0"),
76
+ ]);
77
+ returnNftItem.tokenBalance = returnNftItem.tokenBalance || balance;
78
+ // some people put full json object in uri
79
+ try {
80
+ const object = JSON.parse(tokenURI);
81
+ returnNftItem.image = returnNftItem.image || sanitizeNftMetdataUrl(object.image);
82
+ returnNftItem.name = returnNftItem.name || object.name;
83
+ returnNftItem.description = returnNftItem.description || object.description;
84
+ returnNftItem.decimals = returnNftItem.decimals || object.decimals;
85
+ } catch (error) {
86
+ log.warn("Token uri is not a valid json object", error);
87
+ }
88
+ const finalTokenMetaUri = sanitizeNftMetdataUrl(tokenURI);
89
+ try {
90
+ if (!returnNftItem.description || !returnNftItem.image || !returnNftItem.name) {
91
+ // this call might fail, if metadata url available in smart contract is not reachable
92
+ const object = await get<{ name: string; description: string; image: string; decimals?: string }>(finalTokenMetaUri);
93
+ returnNftItem.image = returnNftItem.image || sanitizeNftMetdataUrl(object.image);
94
+ returnNftItem.name = returnNftItem.name || object.name;
95
+ returnNftItem.description = returnNftItem.description || object.description;
96
+ returnNftItem.decimals = returnNftItem.decimals || object.decimals;
97
+ }
98
+ } catch (error) {
99
+ log.error("Failed to fetch nft metadata", error);
100
+ }
101
+ return returnNftItem;
102
+ }
103
+
104
+ async getContractMetadata(): Promise<Omit<CustomNftInfo, "assets">> {
105
+ const returnNft: Omit<CustomNftInfo, "assets"> = {
106
+ chainId: this.chainId,
107
+ contractAddress: this.contractAddress,
108
+ contractName: this.contractName,
109
+ contractSymbol: this.contractSymbol,
110
+ nftStandard: this.nftStandard,
111
+ contractImage: this.contractImage,
112
+ contractDescription: this.contractDescription,
113
+ contractFallbackLogo: this.contractFallbackLogo,
114
+ contractSupply: this.contractSupply,
115
+ };
116
+ if (!this.nftStandard) {
117
+ const { standard, isSpecial } = await this.checkNftStandard();
118
+ returnNft.nftStandard = standard;
119
+ this.nftStandard = standard;
120
+ this.isSpecial = isSpecial;
121
+ }
122
+ if (!this.contractName || !this.contractSymbol || !this.contractDescription) {
123
+ const abi = this.nftStandard === CONTRACT_TYPE_ERC721 ? erc721Abi : erc1155Abi;
124
+ const contract = new Contract(this.contractAddress, abi, this.provider);
125
+ const [name, symbol] = await Promise.all([contract.name(), contract.symbol()]);
126
+ returnNft.contractName = name;
127
+ returnNft.contractSymbol = symbol;
128
+ if (!this.contractName) this.contractName = name;
129
+ if (!this.contractSymbol) this.contractSymbol = symbol;
130
+ }
131
+
132
+ return returnNft;
133
+ }
134
+
135
+ async fetchNftBalance(userAddress: string, tokenId: string): Promise<string> {
136
+ const { standard } = await this.checkNftStandard();
137
+ const abi = standard === CONTRACT_TYPE_ERC721 ? erc721Abi : erc1155Abi;
138
+ const contract = new Contract(this.contractAddress, abi, this.provider);
139
+ if (standard === CONTRACT_TYPE_ERC1155) {
140
+ const balance = await contract.balanceOf(userAddress, tokenId);
141
+ return balance;
142
+ }
143
+ let owner = "";
144
+ try {
145
+ owner = await contract.ownerOf(tokenId);
146
+ } catch {
147
+ throw new Error("Token id doesn't exists");
148
+ }
149
+ if (owner.toLowerCase() === userAddress.toLowerCase()) {
150
+ return "1";
151
+ }
152
+ return "0";
153
+ }
154
+
155
+ private async checkNftStandard(): Promise<{ isSpecial: boolean; standard: NftStandardType }> {
156
+ // For Cryptokitties
157
+ if (this.nftStandard && this.isSpecial !== undefined) return;
158
+ if (Object.prototype.hasOwnProperty.call(OLD_ERC721_LIST, this.contractAddress.toLowerCase())) {
159
+ this.nftStandard = CONTRACT_TYPE_ERC721;
160
+ this.isSpecial = true;
161
+ return { standard: CONTRACT_TYPE_ERC721, isSpecial: true };
162
+ }
163
+ const isErc721 = await this.contractSupportsInterface(CONTRACT_TYPE_ERC721, ERC721_INTERFACE_ID);
164
+ if (isErc721) {
165
+ this.nftStandard = CONTRACT_TYPE_ERC721;
166
+ this.isSpecial = false;
167
+ return { standard: CONTRACT_TYPE_ERC721, isSpecial: false };
168
+ }
169
+ const isErc1155 = await this.contractSupportsInterface(CONTRACT_TYPE_ERC1155, ERC1155_INTERFACE_ID);
170
+ if (isErc1155) {
171
+ this.nftStandard = CONTRACT_TYPE_ERC1155;
172
+ this.isSpecial = false;
173
+ return { standard: CONTRACT_TYPE_ERC1155, isSpecial: false };
174
+ }
175
+
176
+ throw new Error("Unsupported nft standard");
177
+ }
178
+
179
+ private async contractSupportsInterface(standard: NftStandardType, interfaceId: string): Promise<boolean> {
180
+ const abi = standard === CONTRACT_TYPE_ERC721 ? erc721Abi : erc1155Abi;
181
+ const contract = new Contract(this.contractAddress, abi, this.provider);
182
+ return contract.supportsInterface(interfaceId);
183
+ }
184
+
185
+ private async getCollectibleTokenURI(tokenId: string, standard: NftStandardType = CONTRACT_TYPE_ERC721): Promise<string> {
186
+ const method = standard === CONTRACT_TYPE_ERC721 ? "tokenURI" : "uri";
187
+ const abi = standard === CONTRACT_TYPE_ERC721 ? erc721Abi : erc1155Abi;
188
+ const contract = new Contract(this.contractAddress, abi, this.provider);
189
+ return contract[method](tokenId);
190
+ }
191
+ }