@trezo/evm 0.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/index.mjs ADDED
@@ -0,0 +1,439 @@
1
+ // src/types/config.ts
2
+ import * as Chains from "viem/chains";
3
+ var EvmChains = Chains;
4
+
5
+ // src/factory/create.tsx
6
+ import React, { useSyncExternalStore } from "react";
7
+
8
+ // src/store/evm.store.ts
9
+ import { ethers } from "ethers";
10
+
11
+ // src/store/state.ts
12
+ var getInitialState = () => ({
13
+ wallet: {
14
+ isConnected: false,
15
+ isConnecting: false,
16
+ address: void 0,
17
+ chainId: void 0,
18
+ error: void 0
19
+ },
20
+ provider: {
21
+ isAvailable: typeof window !== "undefined" && !!window.ethereum,
22
+ error: void 0
23
+ },
24
+ _signer: void 0,
25
+ _provider: void 0,
26
+ _contract: void 0,
27
+ _abi: void 0
28
+ });
29
+
30
+ // src/utils/events.ts
31
+ var createEventPoller = ({ provider, contract, eventName, intervalMs = 5e3 }, listener) => {
32
+ let lastBlock;
33
+ const poll = async () => {
34
+ try {
35
+ const currentBlock = await provider.getBlockNumber();
36
+ if (!lastBlock) {
37
+ lastBlock = currentBlock;
38
+ return;
39
+ }
40
+ if (currentBlock > lastBlock) {
41
+ const logs = await provider.getLogs({
42
+ address: await contract.getAddress(),
43
+ fromBlock: lastBlock + 1,
44
+ toBlock: currentBlock,
45
+ topics: [contract.interface.getEvent(eventName).topicHash]
46
+ });
47
+ logs.forEach((log) => {
48
+ const parsed = contract.interface.parseLog(log);
49
+ if (parsed) listener(...parsed.args);
50
+ });
51
+ lastBlock = currentBlock;
52
+ }
53
+ } catch (err) {
54
+ console.error("Event Polling Error:", err);
55
+ }
56
+ };
57
+ const intervalId = setInterval(poll, intervalMs);
58
+ return () => clearInterval(intervalId);
59
+ };
60
+
61
+ // src/utils/errors.ts
62
+ function throwError(message) {
63
+ throw new Error(message ?? "An unexpected error occurred");
64
+ }
65
+ var mapEthersError = (error) => {
66
+ if (!error) return "Unknown blockchain error.";
67
+ const message = error.message?.toLowerCase?.() || "";
68
+ if (error.code === "ACTION_REJECTED" || error.code === 4001) {
69
+ return "Transaction was rejected in the wallet.";
70
+ }
71
+ if (error.code === "CANCELLED") {
72
+ return "The blockchain request was cancelled.";
73
+ }
74
+ if (error.code === "CALL_EXCEPTION") {
75
+ return `Smart contract execution reverted${error.reason ? `: ${error.reason}` : "."}`;
76
+ }
77
+ if (error.code === "INSUFFICIENT_FUNDS" || message.includes("insufficient funds")) {
78
+ return "Your wallet does not have enough ETH to pay for gas or value.";
79
+ }
80
+ if (error.code === "NONCE_EXPIRED") {
81
+ return "Transaction nonce is too low. Try resetting your wallet nonce.";
82
+ }
83
+ if (error.code === "REPLACEMENT_UNDERPRICED") {
84
+ return "Replacement transaction gas price is too low.";
85
+ }
86
+ if (error.code === "TRANSACTION_REPLACED") {
87
+ return "The transaction was replaced by another transaction.";
88
+ }
89
+ if (error.code === "NETWORK_ERROR") {
90
+ return "Network connection failed. Check your internet or RPC endpoint.";
91
+ }
92
+ if (error.code === "SERVER_ERROR") {
93
+ return "Blockchain RPC server returned an error.";
94
+ }
95
+ if (error.code === "TIMEOUT") {
96
+ return "Blockchain request timed out. Please try again.";
97
+ }
98
+ if (message.includes("network mismatch")) {
99
+ return "Please switch to the correct blockchain network in your wallet.";
100
+ }
101
+ if (error.code === "INVALID_ARGUMENT") {
102
+ return "Invalid argument passed to a blockchain function.";
103
+ }
104
+ if (error.code === "MISSING_ARGUMENT") {
105
+ return "Missing required argument for blockchain call.";
106
+ }
107
+ if (error.code === "UNEXPECTED_ARGUMENT") {
108
+ return "Unexpected argument passed to blockchain function.";
109
+ }
110
+ if (error.code === "VALUE_MISMATCH") {
111
+ return "Provided values do not match the expected format.";
112
+ }
113
+ if (error.code === "BAD_DATA") {
114
+ return "Received malformed data from the blockchain.";
115
+ }
116
+ if (error.code === "BUFFER_OVERRUN") {
117
+ return "Data buffer overflow occurred while decoding blockchain response.";
118
+ }
119
+ if (error.code === "NUMERIC_FAULT") {
120
+ return "Numeric overflow or underflow occurred.";
121
+ }
122
+ if (error.code === "NOT_IMPLEMENTED") {
123
+ return "This blockchain operation is not implemented.";
124
+ }
125
+ if (error.code === "UNSUPPORTED_OPERATION") {
126
+ return "This operation is not supported by the current provider.";
127
+ }
128
+ if (error.code === "UNCONFIGURED_NAME") {
129
+ return "ENS name is not configured correctly.";
130
+ }
131
+ if (error.code === "OFFCHAIN_FAULT") {
132
+ return "Off-chain data lookup failed.";
133
+ }
134
+ if (error.code === "UNKNOWN_ERROR") {
135
+ return "An unknown blockchain error occurred.";
136
+ }
137
+ return error.reason || error.message || "An unexpected blockchain error occurred.";
138
+ };
139
+
140
+ // src/store/evm.store.ts
141
+ function createEvmStore(config) {
142
+ let state = getInitialState();
143
+ const listeners = /* @__PURE__ */ new Set();
144
+ const getState = () => state;
145
+ const subscribe = (onStoreChange) => {
146
+ listeners.add(onStoreChange);
147
+ return () => {
148
+ listeners.delete(onStoreChange);
149
+ };
150
+ };
151
+ const setState = (partial) => {
152
+ const next = typeof partial === "function" ? partial(state) : { ...state, ...partial };
153
+ if (next !== state) {
154
+ state = next;
155
+ listeners.forEach((listener) => listener());
156
+ }
157
+ };
158
+ const getProvider = () => {
159
+ if (state._signer) return state._signer.provider;
160
+ if (state._provider) return state._provider;
161
+ if (config.rpcUrl) {
162
+ const provider = config.rpcUrl.startsWith("wss") ? new ethers.WebSocketProvider(config.rpcUrl) : new ethers.JsonRpcProvider(config.rpcUrl);
163
+ setState({ _provider: provider });
164
+ return provider;
165
+ }
166
+ if (typeof window !== "undefined" && window.ethereum) {
167
+ const provider = new ethers.BrowserProvider(window.ethereum);
168
+ setState({ _provider: provider });
169
+ return provider;
170
+ }
171
+ throwError(
172
+ "getProvider: No provider available (no wallet connected, no RPC URL, and no injected provider)"
173
+ );
174
+ };
175
+ const getSigner = () => {
176
+ if (state._signer) return state._signer;
177
+ if (config.rpcUrl) {
178
+ const provider = new ethers.JsonRpcProvider(config.rpcUrl);
179
+ const wallet = ethers.Wallet.createRandom().connect(provider);
180
+ setState({ _signer: wallet });
181
+ return wallet;
182
+ }
183
+ throwError("getSigner: No signer available");
184
+ };
185
+ const getContract = (forWrite = false) => {
186
+ const signerOrProvider = forWrite ? getSigner() : getProvider();
187
+ if (state._contract && state._contract.runner === signerOrProvider) {
188
+ return state._contract;
189
+ }
190
+ const contract = new ethers.Contract(
191
+ config.address,
192
+ config.abi,
193
+ signerOrProvider
194
+ );
195
+ setState({ _contract: contract });
196
+ return contract;
197
+ };
198
+ const queryRegistry = /* @__PURE__ */ new Map();
199
+ const queryFn = async (functionName, args) => {
200
+ const safeArgs = JSON.stringify(
201
+ args,
202
+ (_, value) => typeof value === "bigint" ? value.toString() : value
203
+ );
204
+ const queryKey = `${functionName}:${safeArgs}`;
205
+ const execute = async () => {
206
+ try {
207
+ const contract = getContract(false);
208
+ const result = await contract[functionName](...args);
209
+ return { data: result };
210
+ } catch (error) {
211
+ return { error: new Error(mapEthersError(error)) };
212
+ }
213
+ };
214
+ queryRegistry.set(queryKey, async () => {
215
+ await execute();
216
+ });
217
+ return execute();
218
+ };
219
+ const invalidate = (functionName) => {
220
+ queryRegistry.forEach((refresh, key) => {
221
+ if (key.startsWith(`${functionName}:`)) {
222
+ refresh();
223
+ }
224
+ });
225
+ };
226
+ const mutateFn = async (functionName, args) => {
227
+ if (!state.wallet.isConnected)
228
+ return { error: new Error("mutateFn: Wallet not connected") };
229
+ const targetChain = config.chains.find(
230
+ (c) => c.id === state.wallet.chainId
231
+ );
232
+ if (!targetChain) {
233
+ return {
234
+ error: new Error(
235
+ `targetChain: Chain ${state.wallet.chainId} not supported`
236
+ )
237
+ };
238
+ }
239
+ try {
240
+ const contract = getContract(true);
241
+ const tx = await contract[functionName](...args);
242
+ const receipt = await tx.wait();
243
+ return { data: { hash: tx.hash, receipt } };
244
+ } catch (error) {
245
+ return { error: new Error(mapEthersError(error)) };
246
+ }
247
+ };
248
+ const listenFn = (eventName, listener) => {
249
+ let contract = getContract(false);
250
+ let cleanupFallback;
251
+ const startListening = async () => {
252
+ try {
253
+ await contract.on(eventName, listener);
254
+ } catch (error) {
255
+ const isRpcRestriction = error.message.includes("eth_newFilter") || error.message.includes("whitelisted");
256
+ if (isRpcRestriction) {
257
+ console.warn(`Polling fallback for event: ${eventName}`);
258
+ cleanupFallback = createEventPoller(
259
+ {
260
+ provider: getProvider(),
261
+ contract,
262
+ eventName
263
+ },
264
+ listener
265
+ );
266
+ } else {
267
+ console.error("Failed to set up event listener:", error);
268
+ }
269
+ }
270
+ };
271
+ startListening();
272
+ return () => {
273
+ contract.off(eventName, listener);
274
+ if (cleanupFallback) cleanupFallback();
275
+ };
276
+ };
277
+ return {
278
+ getState,
279
+ setState,
280
+ subscribe,
281
+ queryFn,
282
+ invalidate,
283
+ mutateFn,
284
+ listenFn
285
+ };
286
+ }
287
+
288
+ // src/factory/create.tsx
289
+ import { Fragment, jsx } from "react/jsx-runtime";
290
+ function create(config) {
291
+ const store = createEvmStore(config);
292
+ let kitPromise = null;
293
+ const getKit = () => {
294
+ if (kitPromise) return kitPromise;
295
+ kitPromise = (async () => {
296
+ const kit = config.kit;
297
+ const bridgeProps = {
298
+ onConnect: (address, chainId, signer) => {
299
+ store.setState({
300
+ wallet: {
301
+ ...store.getState().wallet,
302
+ isConnected: true,
303
+ isConnecting: false,
304
+ address,
305
+ chainId
306
+ },
307
+ _signer: signer,
308
+ _provider: signer.provider,
309
+ _contract: void 0
310
+ });
311
+ },
312
+ onDisconnect: () => {
313
+ store.setState({
314
+ wallet: {
315
+ ...store.getState().wallet,
316
+ isConnected: false,
317
+ address: void 0,
318
+ chainId: void 0
319
+ },
320
+ _signer: void 0,
321
+ _contract: void 0
322
+ });
323
+ }
324
+ };
325
+ if (kit.name === "connectkit") {
326
+ const { createConnectkitProvider } = await import("./connectkit.provider-6R2D3NSB.mjs");
327
+ return createConnectkitProvider(
328
+ kit,
329
+ config.chains,
330
+ config.rpcUrl || "",
331
+ bridgeProps
332
+ );
333
+ } else if (kit.name === "reown") {
334
+ const { createReownProvider } = await import("./reown.provider-QWD2VLQQ.mjs");
335
+ return createReownProvider(
336
+ kit,
337
+ config.chains,
338
+ config.rpcUrl || "",
339
+ bridgeProps
340
+ );
341
+ }
342
+ })();
343
+ return kitPromise;
344
+ };
345
+ const call = {
346
+ // Query (view/pure) – args as tuple
347
+ queryFn: (functionName, args) => {
348
+ return store.queryFn(functionName, [...args]);
349
+ },
350
+ // Mutation (nonpayable/payable) – args as tuple
351
+ mutateFn: (functionName, args) => {
352
+ return store.mutateFn(functionName, [...args]);
353
+ },
354
+ // Event listener – args are spread naturally
355
+ listenFn: (eventName, listener) => {
356
+ return store.listenFn(eventName, listener);
357
+ }
358
+ };
359
+ const Provider = ({ children }) => {
360
+ const [KitProvider, setKitProvider] = React.useState(null);
361
+ React.useEffect(() => {
362
+ getKit().then((kitInstance) => {
363
+ if (kitInstance) {
364
+ setKitProvider(() => kitInstance.Provider);
365
+ }
366
+ });
367
+ }, []);
368
+ if (config.kit && !KitProvider) {
369
+ return null;
370
+ }
371
+ if (KitProvider) {
372
+ return /* @__PURE__ */ jsx(KitProvider, { children });
373
+ }
374
+ return /* @__PURE__ */ jsx(Fragment, { children });
375
+ };
376
+ const ConnectButton = ({ children, ...props }) => {
377
+ const [KitButton, setKitButton] = React.useState(
378
+ null
379
+ );
380
+ React.useEffect(() => {
381
+ getKit().then((kitInstance) => {
382
+ if (kitInstance) {
383
+ setKitButton(() => kitInstance.ConnectButton);
384
+ }
385
+ });
386
+ }, []);
387
+ if (config.kit && !KitButton) return null;
388
+ if (KitButton) return /* @__PURE__ */ jsx(KitButton, { ...props, children });
389
+ return null;
390
+ };
391
+ function useStore() {
392
+ const state = useSyncExternalStore(
393
+ store.subscribe,
394
+ store.getState,
395
+ () => store.getState()
396
+ // SSR Snapshot for Next.js
397
+ );
398
+ return {
399
+ call,
400
+ web3Provider: state.provider,
401
+ wallet: {
402
+ account: state.wallet
403
+ }
404
+ };
405
+ }
406
+ useStore.call = call;
407
+ useStore.getState = store.getState;
408
+ useStore.subscribe = store.subscribe;
409
+ useStore.Provider = Provider;
410
+ useStore.ConnectButton = ConnectButton;
411
+ return useStore;
412
+ }
413
+
414
+ // src/utils/formatter.ts
415
+ import { ethers as ethers2 } from "ethers";
416
+ var fromWei = (value, decimals = 18) => ethers2.formatUnits(value.toString(), decimals);
417
+ var toWei = (value, decimals = 18) => ethers2.parseUnits(value, decimals);
418
+ function formatAddress(address, options) {
419
+ if (!address) throwError("formatAddress: address is required");
420
+ const endChars = options?.endChars ?? 6;
421
+ const separator = options?.separator ?? "...";
422
+ const position = options?.position ?? "middle";
423
+ const start = address.slice(0, 4);
424
+ const end = address.slice(-endChars);
425
+ if (position === "end") {
426
+ return `${start}${separator}`;
427
+ }
428
+ return `${start}${separator}${end}`;
429
+ }
430
+ export {
431
+ EvmChains,
432
+ create,
433
+ createEventPoller,
434
+ formatAddress,
435
+ fromWei,
436
+ mapEthersError,
437
+ throwError,
438
+ toWei
439
+ };
@@ -0,0 +1,80 @@
1
+ "use client";
2
+ import {
3
+ KitBridge
4
+ } from "./chunk-I6Z24RI3.mjs";
5
+
6
+ // src/components/providers/reown.provider.tsx
7
+ import React from "react";
8
+ import { WagmiProvider } from "wagmi";
9
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
10
+ import { createAppKit, useAppKit } from "@reown/appkit/react";
11
+ import { useAccount } from "wagmi";
12
+ import { WagmiAdapter } from "@reown/appkit-adapter-wagmi";
13
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
+ function createReownProvider(kitConfig, chains, rpcUrl, bridgeProps) {
15
+ const wagmiAdapter = new WagmiAdapter({
16
+ networks: chains,
17
+ projectId: kitConfig.config.projectId,
18
+ ssr: kitConfig.config.ssr
19
+ });
20
+ const metadata = {
21
+ ...kitConfig.config.metadata,
22
+ url: typeof window !== "undefined" && window.location.hostname === "localhost" ? window.location.origin : kitConfig.config.metadata.url
23
+ };
24
+ if (typeof window !== "undefined") {
25
+ createAppKit({
26
+ adapters: [wagmiAdapter],
27
+ networks: chains,
28
+ projectId: kitConfig.config.projectId,
29
+ metadata,
30
+ features: {
31
+ analytics: true
32
+ }
33
+ });
34
+ }
35
+ const queryClient = new QueryClient();
36
+ const Provider = ({ children }) => /* @__PURE__ */ jsx(WagmiProvider, { config: wagmiAdapter.wagmiConfig, children: /* @__PURE__ */ jsxs(QueryClientProvider, { client: queryClient, children: [
37
+ /* @__PURE__ */ jsx(KitBridge, { ...bridgeProps }),
38
+ children
39
+ ] }) });
40
+ const ConnectButton = ({
41
+ children,
42
+ ...props
43
+ }) => {
44
+ const { open: reownOpen, close } = useAppKit();
45
+ const { address, isConnected, isConnecting, chainId } = useAccount();
46
+ const open = React.useCallback(async () => {
47
+ try {
48
+ await reownOpen();
49
+ } catch (e) {
50
+ console.error("AppKit open error, retrying...", e);
51
+ }
52
+ }, [reownOpen]);
53
+ if (children) {
54
+ return /* @__PURE__ */ jsx(Fragment, { children: children({
55
+ isConnected,
56
+ isConnecting,
57
+ address,
58
+ ensName: null,
59
+ // AppKit doesn't expose ENS directly in useAccount easily without extra hooks
60
+ chain: chainId ? { id: chainId } : void 0,
61
+ open,
62
+ close
63
+ }) });
64
+ }
65
+ return (
66
+ // @ts-ignore
67
+ /* @__PURE__ */ jsx(
68
+ "appkit-button",
69
+ {
70
+ label: props.label,
71
+ balance: props.showBalance ? "show" : "hide"
72
+ }
73
+ )
74
+ );
75
+ };
76
+ return { Provider, ConnectButton };
77
+ }
78
+ export {
79
+ createReownProvider
80
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@trezo/evm",
3
+ "description": "A high-performance, type-safe EVM state orchestration layer for modern Web3 applications.",
4
+ "version": "0.1.0",
5
+ "author": "thelastofinusa",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "scripts": {
11
+ "build": "tsup src/index.ts --format esm,cjs --dts",
12
+ "prepublishOnly": "npm run build",
13
+ "genokit": "tsx scripts/genokit.ts",
14
+ "typecheck": "tsc --noEmit",
15
+ "publish-package": "npm publish --access public"
16
+ },
17
+ "files": [
18
+ "dist/**/*.js",
19
+ "dist/**/*.mjs",
20
+ "dist/**/*.d.ts",
21
+ "dist/**/*.d.mts",
22
+ "LICENSE",
23
+ "README.md"
24
+ ],
25
+ "peerDependencies": {
26
+ "react": ">=17.0.0",
27
+ "react-dom": ">=17.0.0"
28
+ },
29
+ "dependencies": {
30
+ "@reown/appkit": "^1.6.8",
31
+ "@reown/appkit-adapter-wagmi": "^1.6.8",
32
+ "@tanstack/react-query": "^5.90.21",
33
+ "abitype": "^1.2.3",
34
+ "connectkit": "^1.9.1",
35
+ "ethers": "^6.16.0",
36
+ "viem": "^2.21.0",
37
+ "wagmi": "^2.14.9"
38
+ },
39
+ "devDependencies": {
40
+ "@metamask/providers": "^22.1.1",
41
+ "@types/node": "^25.5.0",
42
+ "@types/react": "^19",
43
+ "tsup": "^8.5.1",
44
+ "tsx": "^4.21.0",
45
+ "typescript": "^5.9.3"
46
+ }
47
+ }