From 7fd504a21600c15739967ff5bf938df0abcd9893 Mon Sep 17 00:00:00 2001 From: RUKAYAT-CODER Date: Mon, 7 Sep 2026 17:05:10 +0100 Subject: [PATCH] chore: remove unused ethers dependency and dead service code Nothing in the application imports ethersService or serviceAccount; the ethers package was only referenced by its own tests. On-chain actions are mediated by the backend and the Stellar/Soroban contract, so the EVM-oriented ethers library is unused. - Delete src/services/ethersService.ts and src/services/serviceAccount.ts and their test files. - Drop the ethers dependency from package.json and refresh pnpm-lock.yaml. - Remove leftover ethers references in next.config.ts (optimizePackageImports and the webpack splitChunks cache group). --- next.config.ts | 7 -- package.json | 1 - pnpm-lock.yaml | 55 -------------- src/services/__tests__/ethersService.test.ts | 17 ----- src/services/__tests__/serviceAccount.test.ts | 62 --------------- src/services/ethersService.ts | 76 ------------------- src/services/serviceAccount.ts | 75 ------------------ 7 files changed, 293 deletions(-) delete mode 100644 src/services/__tests__/ethersService.test.ts delete mode 100644 src/services/__tests__/serviceAccount.test.ts delete mode 100644 src/services/ethersService.ts delete mode 100644 src/services/serviceAccount.ts diff --git a/next.config.ts b/next.config.ts index 1f307283..d6d300ff 100644 --- a/next.config.ts +++ b/next.config.ts @@ -10,7 +10,6 @@ const nextConfig: NextConfig = { optimizePackageImports: [ "@monaco-editor/react", "video.js", - "ethers", "recharts", "framer-motion", "date-fns", @@ -152,12 +151,6 @@ const nextConfig: NextConfig = { chunks: "async", priority: 30, }, - ethers: { - test: /[\\/]node_modules[\\/]ethers[\\/]/, - name: "ethers", - chunks: "async", - priority: 30, - }, }, }, }; diff --git a/package.json b/package.json index 34ebce44..4f688132 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "clsx": "^2.1.1", "date-fns": "^3.6.0", "dompurify": "^3.2.4", - "ethers": "^6.12.0", "framer-motion": "^12.23.0", "graphql": "^16.8.0", "graphql-ws": "^5.14.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 603975d4..ca5ffc75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,9 +76,6 @@ importers: dompurify: specifier: ^3.2.4 version: 3.4.7 - ethers: - specifier: ^6.12.0 - version: 6.17.0 framer-motion: specifier: ^12.23.0 version: 12.40.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1687,9 +1684,6 @@ packages: resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} - '@noble/curves@1.2.0': - resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} - '@noble/curves@1.8.0': resolution: {integrity: sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==} engines: {node: ^14.21.3 || >=16} @@ -1709,10 +1703,6 @@ packages: '@noble/hashes@1.1.5': resolution: {integrity: sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==} - '@noble/hashes@1.3.2': - resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} - engines: {node: '>= 16'} - '@noble/hashes@1.7.0': resolution: {integrity: sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==} engines: {node: ^14.21.3 || >=16} @@ -3057,9 +3047,6 @@ packages: '@types/node@20.19.41': resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==} - '@types/node@22.7.5': - resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} - '@types/pg@8.20.0': resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} @@ -3590,9 +3577,6 @@ packages: aes-decrypter@4.0.2: resolution: {integrity: sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==} - aes-js@4.0.0-beta.5: - resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} - agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -4687,10 +4671,6 @@ packages: resolution: {integrity: sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==} engines: {node: '>=20'} - ethers@6.17.0: - resolution: {integrity: sha512-BpyrpIPJ3ydEVow8zGaz1DuPS7YU8DcWxuBnY9a0UA/lvAPwrMr+EPXsfrul628SRaekPNeIM4UFh/91GWZang==} - engines: {node: '>=14.0.0'} - eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -7271,9 +7251,6 @@ packages: tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -7353,9 +7330,6 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -9490,10 +9464,6 @@ snapshots: '@noble/ciphers@1.3.0': {} - '@noble/curves@1.2.0': - dependencies: - '@noble/hashes': 1.3.2 - '@noble/curves@1.8.0': dependencies: '@noble/hashes': 1.7.0 @@ -9512,8 +9482,6 @@ snapshots: '@noble/hashes@1.1.5': {} - '@noble/hashes@1.3.2': {} - '@noble/hashes@1.7.0': {} '@noble/hashes@1.8.0': {} @@ -11136,10 +11104,6 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@22.7.5': - dependencies: - undici-types: 6.19.8 - '@types/pg@8.20.0': dependencies: '@types/node': 20.19.41 @@ -11914,8 +11878,6 @@ snapshots: global: 4.4.0 pkcs7: 1.0.4 - aes-js@4.0.0-beta.5: {} - agent-base@7.1.4: {} ajv-formats@2.1.1(ajv@8.20.0): @@ -13304,19 +13266,6 @@ snapshots: eta@4.6.0: {} - ethers@6.17.0: - dependencies: - '@adraffy/ens-normalize': 1.11.1 - '@noble/curves': 1.2.0 - '@noble/hashes': 1.3.2 - '@types/node': 22.7.5 - aes-js: 4.0.0-beta.5 - tslib: 2.7.0 - ws: 8.21.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - eventemitter3@4.0.7: {} eventemitter3@5.0.1: {} @@ -16380,8 +16329,6 @@ snapshots: tslib@1.14.1: {} - tslib@2.7.0: {} - tslib@2.8.1: {} tsx@4.22.3: @@ -16472,8 +16419,6 @@ snapshots: undici-types@5.26.5: {} - undici-types@6.19.8: {} - undici-types@6.21.0: {} unicode-canonical-property-names-ecmascript@2.0.1: {} diff --git a/src/services/__tests__/ethersService.test.ts b/src/services/__tests__/ethersService.test.ts deleted file mode 100644 index 816591d9..00000000 --- a/src/services/__tests__/ethersService.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; - -describe('ethersService', () => { - beforeEach(() => { - vi.resetModules(); - }); - - it('lazy-loads ethers successfully through the circuit breaker', async () => { - const { getEthers } = await import('../ethersService'); - - const [first, second] = await Promise.all([getEthers(), getEthers()]); - - expect(first).toBe(second); - expect(first).toHaveProperty('Wallet'); - expect(first).toHaveProperty('Contract'); - }); -}); diff --git a/src/services/__tests__/serviceAccount.test.ts b/src/services/__tests__/serviceAccount.test.ts deleted file mode 100644 index 94d2fc95..00000000 --- a/src/services/__tests__/serviceAccount.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { getEthers } from '../ethersService'; - -describe('Service Account utilities', () => { - beforeEach(() => { - vi.resetModules(); - process.env.SERVICE_ACCOUNT_ADDRESS = '0x1234567890abcdef1234567890abcdef12345678'; - process.env.SERVICE_PRIVATE_KEY = - '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; - }); - - test('module can be imported without SERVICE_PRIVATE_KEY until it is used', async () => { - const previousKey = process.env.SERVICE_PRIVATE_KEY; - delete process.env.SERVICE_PRIVATE_KEY; - - try { - const serviceAccountModule = await import('@/services/serviceAccount'); - await expect(serviceAccountModule.getServiceAddress()).rejects.toThrow( - 'SERVICE_PRIVATE_KEY is not set in environment', - ); - } finally { - if (previousKey) { - process.env.SERVICE_PRIVATE_KEY = previousKey; - } else { - delete process.env.SERVICE_PRIVATE_KEY; - } - } - }); - - test('getServiceAddress returns address from wallet', async () => { - const { getServiceAddress } = await import('@/services/serviceAccount'); - const address = await getServiceAddress(); - expect(typeof address).toBe('string'); - expect(address).toMatch(/^0x[0-9a-fA-F]{40}$/); - }); - - test('signMessage returns a signature string', async () => { - const { signMessage } = await import('@/services/serviceAccount'); - const sig = await signMessage('test-message'); - expect(typeof sig).toBe('string'); - expect(sig).toMatch(/^0x[0-9a-fA-F]+$/); - }); - - test('sendTransaction without provider returns signed tx hex', async () => { - const { sendTransaction } = await import('@/services/serviceAccount'); - const ethers = await getEthers(); - const tx = { - to: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', - value: ethers.parseEther('0.01'), - }; - const signed = await sendTransaction(tx); - expect(typeof signed).toBe('string'); - expect(signed).toMatch(/^0x[0-9a-fA-F]+$/); - }); - - test('getBalance returns ETH balance string', async () => { - const { getBalance } = await import('@/services/serviceAccount'); - const ethers = await getEthers(); - const provider = new ethers.InfuraProvider('goerli'); - const balance = await getBalance(provider); - expect(typeof balance).toBe('string'); - }); -}); diff --git a/src/services/ethersService.ts b/src/services/ethersService.ts deleted file mode 100644 index b15b170b..00000000 --- a/src/services/ethersService.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Lazy-loaded Ethers.js service wrapper - * This module dynamically imports ethers only when needed, reducing initial bundle size - */ - -import { CircuitBreaker } from '@/utils/circuitBreaker'; - -type EthersModule = typeof import('ethers'); - -const ethersCircuitBreaker = new CircuitBreaker({ - failureThreshold: 1, - successThreshold: 1, - timeout: 60000, - monitoringPeriod: 10000, - maxConcurrentRequests: 10, - maxConcurrentHalfOpenProbes: 1, -}); - -let ethersPromise: Promise | null = null; - -const loadEthers = (): Promise => { - if (!ethersPromise) { - ethersPromise = import('ethers').catch((error: unknown) => { - ethersPromise = null; - throw error; - }); - } - return ethersPromise; -}; - -/** - * Get ethers library (lazy-loaded) - */ -export const getEthers = async (): Promise => { - return ethersCircuitBreaker.execute(async () => { - const ethersModule = await loadEthers(); - return ethersModule.ethers; - }); -}; - -/** - * Create a wallet from private key (lazy-loaded) - */ -export const createWallet = async (privateKey: string) => { - const ethers = await getEthers(); - return new ethers.Wallet(privateKey); -}; - -/** - * Format ether value (lazy-loaded) - */ -export const formatEther = async (value: string | bigint) => { - const ethers = await getEthers(); - return ethers.formatEther(value); -}; - -/** - * Format units (lazy-loaded) - */ -export const formatUnits = async (value: string | bigint, units: number) => { - const ethers = await getEthers(); - return ethers.formatUnits(value, units); -}; - -/** - * Create contract instance (lazy-loaded) - */ -export const createContract = async ( - address: string, - abi: string | readonly string[], - signerOrProvider: unknown, -) => { - const ethers = await getEthers(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return new ethers.Contract(address, abi as any, signerOrProvider as any); -}; diff --git a/src/services/serviceAccount.ts b/src/services/serviceAccount.ts deleted file mode 100644 index 024741b7..00000000 --- a/src/services/serviceAccount.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { createWallet, formatEther, formatUnits, createContract } from './ethersService'; - -/** - * Service Account utilities - * Uses a private key from the environment variable SERVICE_PRIVATE_KEY. - * The address is derived from the private key and cached. - * Ethers.js is lazy-loaded to reduce initial bundle size. - */ - -let walletInstance: Awaited> | null = null; - -const getPrivateKey = (): string => { - const privateKey = process.env.SERVICE_PRIVATE_KEY; - if (!privateKey) { - throw new Error('SERVICE_PRIVATE_KEY is not set in environment'); - } - return privateKey; -}; - -const getWallet = async () => { - if (!walletInstance) { - walletInstance = await createWallet(getPrivateKey()); - } - return walletInstance; -}; - -/** Get the address of the service account */ -export const getServiceAddress = async (): Promise => { - const wallet = await getWallet(); - return wallet.address; -}; - -/** Sign an arbitrary message */ -export const signMessage = async (message: string): Promise => { - const wallet = await getWallet(); - return await wallet.signMessage(message); -}; - -/** Send a transaction using a provider (optional) */ -export const sendTransaction = async ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - tx: any, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - provider?: any, -): Promise => { - const wallet = await getWallet(); - if (provider) { - const signer = wallet.connect(provider); - const response = await signer.sendTransaction(tx); - return response.hash; - } - // If no provider, just return the serialized transaction as hex (useful for offline signing) - const signedTx = await wallet.signTransaction(tx); - return signedTx; -}; - -/** Get balance of the service account for a given token (default ETH) */ -export const getBalance = async ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - provider: any, - tokenAddress?: string, -): Promise => { - const wallet = await getWallet(); - if (!tokenAddress) { - const balance = await provider.getBalance(wallet.address); - return await formatEther(balance); - } - const erc20 = await createContract( - tokenAddress, - ['function balanceOf(address) view returns (uint256)'], - provider, - ); - const balance = await erc20.balanceOf(wallet.address); - return await formatUnits(balance, 18); -};