diff --git a/plugin/.env b/plugin/.env index c963c53b..337f3d87 100644 --- a/plugin/.env +++ b/plugin/.env @@ -1,5 +1,5 @@ GENERATE_SOURCEMAP=false -VITE_API_URL=$API_SERVICE_URL +VITE_API_URL=http://127.0.0.1:8000 VITE_DEVNET_URL=http://127.0.0.1:5050 VITE_REMOTE_DEVNET_URL=$STARKNET_DEVNET_URL VITE_VERSION=$npm_package_version \ No newline at end of file diff --git a/plugin/package.json b/plugin/package.json index bc6800bf..fa66ee50 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -49,15 +49,16 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.32.2", "typescript": "^4.9.5", - "vite": "^4.4.9" + "vite": "^4.4.9", + "vite-plugin-checker": "^0.6.2" }, "scripts": { "start": "vite", "build": "vite build", "serve": "vite preview --port 3000", "deploy": "pnpm run build; pnpm run serve", - "test": "react-scripts test", - "eject": "react-scripts eject", + "test": "vite test", + "eject": "vite eject", "lint": "eslint \"src/**/*.{ts,tsx,js,jsx}\"", "lint:fix": "eslint \"src/**/*.{ts,tsx,js,jsx}\" --fix" }, diff --git a/plugin/pnpm-lock.yaml b/plugin/pnpm-lock.yaml index ed11e4dd..c01458d2 100644 --- a/plugin/pnpm-lock.yaml +++ b/plugin/pnpm-lock.yaml @@ -142,6 +142,9 @@ devDependencies: vite: specifier: ^4.4.9 version: 4.4.9(@types/node@16.18.38) + vite-plugin-checker: + specifier: ^0.6.2 + version: 0.6.2(eslint@8.45.0)(typescript@4.9.5)(vite@4.4.9) packages: @@ -2160,6 +2163,13 @@ packages: uri-js: 4.4.1 dev: true + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2180,6 +2190,14 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2311,6 +2329,11 @@ packages: resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} dev: false + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + /binaryen@112.0.0-nightly.20230411: resolution: {integrity: sha512-4V9r9x9fjAVFZdR2yvBFc3BEJJIBYvd2X8X8k0zAuJsao2gl9wNHDmpQ30QsLo6hgkRfRImkCbCjhXW3RDOYXQ==} hasBin: true @@ -2403,6 +2426,21 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -2427,6 +2465,11 @@ packages: delayed-stream: 1.0.0 dev: false + /commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + dev: true + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true @@ -3212,6 +3255,15 @@ packages: tslib: 2.6.0 dev: false + /fs-extra@11.1.1: + resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true @@ -3341,6 +3393,10 @@ packages: dependencies: get-intrinsic: 1.2.1 + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + /graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true @@ -3472,6 +3528,13 @@ packages: dependencies: has-bigints: 1.0.2 + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} @@ -3680,6 +3743,14 @@ packages: engines: {node: '>=6'} hasBin: true + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + /jsx-ast-utils@3.3.4: resolution: {integrity: sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==} engines: {node: '>=4.0'} @@ -3723,10 +3794,18 @@ packages: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} dev: false + /lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + dev: true + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.pick@4.4.0: + resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} + dev: true + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: false @@ -3871,6 +3950,18 @@ packages: /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: true + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -4154,6 +4245,13 @@ packages: loose-envify: 1.4.0 dev: false + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -4401,6 +4499,10 @@ packages: resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} dev: false + /tiny-invariant@1.3.1: + resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} + dev: true + /tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} dev: false @@ -4471,6 +4573,11 @@ packages: engines: {node: '>=10'} dev: true + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} @@ -4509,6 +4616,11 @@ packages: which-boxed-primitive: 1.0.2 dev: true + /universalify@2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: true + /update-browserslist-db@1.0.11(browserslist@4.21.9): resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true @@ -4560,6 +4672,59 @@ packages: tslib: 2.6.0 dev: false + /vite-plugin-checker@0.6.2(eslint@8.45.0)(typescript@4.9.5)(vite@4.4.9): + resolution: {integrity: sha512-YvvvQ+IjY09BX7Ab+1pjxkELQsBd4rPhWNw8WLBeFVxu/E7O+n6VYAqNsKdK/a2luFlX/sMpoWdGFfg4HvwdJQ==} + engines: {node: '>=14.16'} + peerDependencies: + eslint: '>=7' + meow: ^9.0.0 + optionator: ^0.9.1 + stylelint: '>=13' + typescript: '*' + vite: '>=2.0.0' + vls: '*' + vti: '*' + vue-tsc: '>=1.3.9' + peerDependenciesMeta: + eslint: + optional: true + meow: + optional: true + optionator: + optional: true + stylelint: + optional: true + typescript: + optional: true + vls: + optional: true + vti: + optional: true + vue-tsc: + optional: true + dependencies: + '@babel/code-frame': 7.22.13 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + chokidar: 3.5.3 + commander: 8.3.0 + eslint: 8.45.0 + fast-glob: 3.3.0 + fs-extra: 11.1.1 + lodash.debounce: 4.0.8 + lodash.pick: 4.4.0 + npm-run-path: 4.0.1 + semver: 7.5.4 + strip-ansi: 6.0.1 + tiny-invariant: 1.3.1 + typescript: 4.9.5 + vite: 4.4.9(@types/node@16.18.38) + vscode-languageclient: 7.0.0 + vscode-languageserver: 7.0.0 + vscode-languageserver-textdocument: 1.0.11 + vscode-uri: 3.0.8 + dev: true + /vite-plugin-svgr@4.0.0(typescript@4.9.5)(vite@4.4.9): resolution: {integrity: sha512-ingW8FEJ4vz9mQumnMDhNysE+YleiaThYmgflhUIVI4iIjVsVA1SswYIKprWVmyFsiIk1DqcwUeTFCnUJA3Vvg==} peerDependencies: @@ -4610,6 +4775,46 @@ packages: optionalDependencies: fsevents: 2.3.3 + /vscode-jsonrpc@6.0.0: + resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} + engines: {node: '>=8.0.0 || >=10.0.0'} + dev: true + + /vscode-languageclient@7.0.0: + resolution: {integrity: sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==} + engines: {vscode: ^1.52.0} + dependencies: + minimatch: 3.1.2 + semver: 7.5.4 + vscode-languageserver-protocol: 3.16.0 + dev: true + + /vscode-languageserver-protocol@3.16.0: + resolution: {integrity: sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==} + dependencies: + vscode-jsonrpc: 6.0.0 + vscode-languageserver-types: 3.16.0 + dev: true + + /vscode-languageserver-textdocument@1.0.11: + resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==} + dev: true + + /vscode-languageserver-types@3.16.0: + resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==} + dev: true + + /vscode-languageserver@7.0.0: + resolution: {integrity: sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==} + hasBin: true + dependencies: + vscode-languageserver-protocol: 3.16.0 + dev: true + + /vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + dev: true + /web-vitals@2.1.4: resolution: {integrity: sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==} dev: false diff --git a/plugin/src/App.tsx b/plugin/src/App.tsx index b01865ae..465af829 100644 --- a/plugin/src/App.tsx +++ b/plugin/src/App.tsx @@ -1,104 +1,26 @@ -import React, { useEffect, useState } from 'react' -import { PluginClient } from '@remixproject/plugin' -import { createClient } from '@remixproject/plugin-webview' +import React from 'react' import './App.css' import Plugin from './features/Plugin' -import { RemixClientContext } from './contexts/RemixClientContext' -import Loader from './ui_components/CircularLoader' -import FullScreenOverlay from './ui_components/FullScreenOverlay' -import { fetchGitHubFilesRecursively } from './utils/initial_scarb_codes' +import Loader from './components/ui_components/CircularLoader' +import FullScreenOverlay from './components/ui_components/FullScreenOverlay' +import useRemixClient from './hooks/useRemixClient' -const remixClient = createClient(new PluginClient()) const App: React.FC = () => { - const [pluginLoaded, setPluginLoaded] = useState(false) - - useEffect(() => { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - const id = setTimeout(async (): Promise => { - await remixClient.onload(() => { - setPluginLoaded(true) - // eslint-disable-next-line @typescript-eslint/no-misused-promises - setTimeout(async () => { - const workspaces = await remixClient.filePanel.getWorkspaces() - - const workspaceLets: Array<{ name: string, isGitRepo: boolean }> = - JSON.parse(JSON.stringify(workspaces)) - - if ( - !workspaceLets.some( - (workspaceLet) => workspaceLet.name === 'cairo_scarb_sample' - ) - ) { - await remixClient.filePanel.createWorkspace( - 'cairo_scarb_sample', - true - ) - try { - await remixClient.fileManager.mkdir('hello_world') - } catch (e) { - console.log(e) - } - const exampleRepo = await fetchGitHubFilesRecursively( - 'software-mansion/scarb', - 'examples/starknet_multiple_contracts' - ) - - try { - for (const file of exampleRepo) { - const filePath = file?.path - .replace('examples/starknet_multiple_contracts/', '') - .replace('examples/starknet_multiple_contracts', '') ?? '' - - let fileContent: string = file?.content ?? '' - - if (file != null && file.fileName === 'Scarb.toml') { - fileContent = fileContent.concat('\ncasm = true') - } - - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - await remixClient.fileManager.writeFile( - `hello_world/${ - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - filePath - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - }/${file?.fileName}`, - fileContent - ) - } - } catch (e) { - if (e instanceof Error) { - await remixClient.call('notification' as any, 'alert', { - id: 'starknetRemixPluginAlert', - title: 'Please check the write file permission', - message: e.message + '\n' + 'Did you provide the write file permission?' - }) - } - console.log(e) - } - } - }) - }) - }, 1) - return () => { - clearInterval(id) - } - }) + const { isPluginLoaded } = useRemixClient() return ( - -
- {pluginLoaded - ? ( +
+ {isPluginLoaded + ? ( - ) - : ( + ) + : ( - )} -
- + )} +
) } diff --git a/plugin/src/atoms/cairoVersion.ts b/plugin/src/atoms/cairoVersion.ts new file mode 100644 index 00000000..3cfb098f --- /dev/null +++ b/plugin/src/atoms/cairoVersion.ts @@ -0,0 +1,5 @@ +import { atom } from 'jotai' + +const cairoVersion = atom('0.0.0') + +export default cairoVersion diff --git a/plugin/src/atoms/compilation.ts b/plugin/src/atoms/compilation.ts new file mode 100644 index 00000000..e29591d3 --- /dev/null +++ b/plugin/src/atoms/compilation.ts @@ -0,0 +1,82 @@ +// status: 'Compiling...' as string, +// setStatus: ((_: string) => {}) as React.Dispatch>, + +import { atom } from 'jotai' + +const statusAtom = atom('Compiling....') + +// currentFilename: '' as string, +// setCurrentFilename: ((_: string) => {}) as React.Dispatch>, + +const currentFilenameAtom = atom('') +// isCompiling: false as boolean, +// setIsCompiling: ((_: boolean) => {}) as React.Dispatch>, + +const isCompilingAtom = atom(false) + +// isValidCairo: false as boolean, +// setIsValidCairo: ((_: boolean) => {}) as React.Dispatch>, + +const isValidCairoAtom = atom(false) +// noFileSelected: false as boolean, +// setNoFileSelected: ((_: boolean) => {}) as React.Dispatch>, + +const noFileSelectedAtom = atom(false) +// hashDir: '' as string, +// setHashDir: ((_: string) => {}) as React.Dispatch>, + +const hashDirAtom = atom('') + +// tomlPaths: [] as string[], +// setTomlPaths: ((_: string[]) => {}) as React.Dispatch>, +const tomlPathsAtom = atom([]) + +// activeTomlPath: '' as string, +// setActiveTomlPath: ((_: string) => {}) as React.Dispatch> +const activeTomlPathAtom = atom('') + +type Keys = 'status' | 'currentFilename' | 'isCompiling' | 'isValidCairo' | 'noFileSelected' | 'hashDir' | 'tomlPaths' | 'activeTomlPath' + +interface SetCompilationValue { + key: Keys + value: string | boolean | string[] +} + +const compilationAtom = atom( + (get) => { + return { + status: get(statusAtom), + currentFilename: get(currentFilenameAtom), + isCompiling: get(isCompilingAtom), + isValidCairo: get(isValidCairoAtom), + noFileSelected: get(noFileSelectedAtom), + hashDir: get(hashDirAtom), + tomlPaths: get(tomlPathsAtom), + activeTomlPath: get(activeTomlPathAtom) + } + }, + (_get, set, newValue: SetCompilationValue) => { + switch (newValue?.key) { + case 'status': typeof newValue?.value === 'string' && set(statusAtom, newValue?.value); break + case 'currentFilename': typeof newValue?.value === 'string' && set(currentFilenameAtom, newValue?.value); break + case 'isCompiling': typeof newValue?.value === 'boolean' && set(isCompilingAtom, newValue?.value); break + case 'isValidCairo': typeof newValue?.value === 'boolean' && set(isValidCairoAtom, newValue?.value); break + case 'noFileSelected': typeof newValue?.value === 'boolean' && set(noFileSelectedAtom, newValue?.value); break + case 'hashDir': typeof newValue?.value === 'string' && set(hashDirAtom, newValue?.value); break + case 'tomlPaths': Array.isArray(newValue?.value) && set(tomlPathsAtom, newValue?.value); break + case 'activeTomlPath': typeof newValue?.value === 'string' && set(activeTomlPathAtom, newValue?.value); break + } + } +) + +export { + statusAtom, + currentFilenameAtom, + isCompilingAtom, + isValidCairoAtom, + noFileSelectedAtom, + hashDirAtom, + tomlPathsAtom, + activeTomlPathAtom, + compilationAtom +} diff --git a/plugin/src/atoms/compiledContracts.ts b/plugin/src/atoms/compiledContracts.ts new file mode 100644 index 00000000..ac8a2234 --- /dev/null +++ b/plugin/src/atoms/compiledContracts.ts @@ -0,0 +1,11 @@ +import { atom } from 'jotai' + +import { type Contract } from '../utils/types/contracts' + +const compiledContractsAtom = atom([]) +const selectedCompiledContract = atom(null) + +export { + compiledContractsAtom, + selectedCompiledContract +} diff --git a/plugin/src/atoms/deployment.ts b/plugin/src/atoms/deployment.ts new file mode 100644 index 00000000..a96acc14 --- /dev/null +++ b/plugin/src/atoms/deployment.ts @@ -0,0 +1,46 @@ +import { atom } from 'jotai' +import { type CallDataObject, type Input } from '../utils/types/contracts' + +const isDeployingAtom = atom(false) + +const deployStatusAtom = atom('') + +const constructorCalldataAtom = atom({}) + +const constructorInputsAtom = atom([]) + +const notEnoughInputsAtom = atom(false) + +type Key = 'isDeploying' | 'deployStatus' | 'constructorCalldata' | 'constructorInputs' | 'notEnoughInputs' + +interface SetDeploymentAtom { + key: Key + value: string | boolean | CallDataObject | Input[] +} + +const deploymentAtom = atom((get) => { + return { + isDeploying: get(isDeployingAtom), + deployStatus: get(deployStatusAtom), + constructorCalldata: get(constructorCalldataAtom), + constructorInputs: get(constructorInputsAtom), + notEnoughInputs: get(notEnoughInputsAtom) + } +}, (_get, set, newValue: SetDeploymentAtom) => { + switch (newValue?.key) { + case 'isDeploying': typeof newValue?.value === 'boolean' && set(isDeployingAtom, newValue?.value); break + case 'deployStatus': typeof newValue?.value === 'string' && set(deployStatusAtom, newValue?.value); break + case 'constructorCalldata': typeof newValue?.value === 'object' && !Array.isArray(newValue?.value) && set(constructorCalldataAtom, newValue?.value); break + case 'constructorInputs': Array.isArray(newValue?.value) && set(constructorInputsAtom, newValue?.value); break + case 'notEnoughInputs': typeof newValue?.value === 'boolean' && set(notEnoughInputsAtom, newValue?.value); break + } +}) + +export { + isDeployingAtom, + deployStatusAtom, + constructorCalldataAtom, + constructorInputsAtom, + notEnoughInputsAtom, + deploymentAtom +} diff --git a/plugin/src/atoms/environment.ts b/plugin/src/atoms/environment.ts new file mode 100644 index 00000000..53192bb3 --- /dev/null +++ b/plugin/src/atoms/environment.ts @@ -0,0 +1,23 @@ +import { atom } from 'jotai' + +import { type Devnet, devnets, type DevnetAccount } from '../utils/network' + +const devnetAtom = atom(devnets[0]) + +export type Env = 'remoteDevnet' | 'wallet' | 'manual' | 'localDevnet' + +const envAtom = atom('remoteDevnet') + +const isDevnetAliveAtom = atom(true) + +const selectedDevnetAccountAtom = atom(null) + +const availableDevnetAccountsAtom = atom([]) + +export { + devnetAtom, + envAtom, + isDevnetAliveAtom, + selectedDevnetAccountAtom, + availableDevnetAccountsAtom +} diff --git a/plugin/src/atoms/index.ts b/plugin/src/atoms/index.ts index 933822c8..f628611d 100644 --- a/plugin/src/atoms/index.ts +++ b/plugin/src/atoms/index.ts @@ -1,5 +1,5 @@ import { atomWithStorage } from 'jotai/utils' -import { type AbiElement, type Input } from '../types/contracts' +import { type AbiElement, type Input } from '../utils/types/contracts' import { type CallContractResponse, type InvokeTransactionReceiptResponse diff --git a/plugin/src/atoms/manualAccount.ts b/plugin/src/atoms/manualAccount.ts new file mode 100644 index 00000000..dee1a3c5 --- /dev/null +++ b/plugin/src/atoms/manualAccount.ts @@ -0,0 +1,15 @@ +import { atom } from 'jotai' +import { type ManualAccount } from '../utils/types/accounts' +import { networks } from '../utils/constants' + +const accountAtom = atom([]) + +const selectedAccountAtom = atom(null) + +const networkAtom = atom(networks[0].name) + +export { + accountAtom, + selectedAccountAtom, + networkAtom +} diff --git a/plugin/src/atoms/transactions.ts b/plugin/src/atoms/transactions.ts new file mode 100644 index 00000000..e3b85433 --- /dev/null +++ b/plugin/src/atoms/transactions.ts @@ -0,0 +1,7 @@ +import { atom } from 'jotai' +import { type Transaction } from '../utils/types/transaction' + +// Transaction History Context state variables +const transactions = atom([]) + +export default transactions diff --git a/plugin/src/components/BackgroundNotices/index.tsx b/plugin/src/components/BackgroundNotices/index.tsx index 17abdbdc..5732f54f 100644 --- a/plugin/src/components/BackgroundNotices/index.tsx +++ b/plugin/src/components/BackgroundNotices/index.tsx @@ -1,3 +1,4 @@ +import { nanoid } from 'nanoid' import React from 'react' const Notices = [ @@ -6,7 +7,7 @@ const Notices = [ 'Declaration of contracts with some wallets will be supported when they update to the latest starknet.js version' ] -const BackgroundNotices = () => { +const BackgroundNotices: React.FC = () => { return (

Notices

@@ -14,7 +15,7 @@ const BackgroundNotices = () => {
    {Notices.map((notice, index) => { return ( -
  • +
  • {index + 1} diff --git a/plugin/src/components/Card/index.tsx b/plugin/src/components/Card/index.tsx index 8a1bbe07..0533f12b 100644 --- a/plugin/src/components/Card/index.tsx +++ b/plugin/src/components/Card/index.tsx @@ -11,7 +11,7 @@ export interface CardProps { export const Card: React.FC = ({ header, children, rightItem }) => { return (
    - {header != undefined && ( + {header !== undefined && (
    {header}
    {rightItem} diff --git a/plugin/src/components/CompiledContracts/index.tsx b/plugin/src/components/CompiledContracts/index.tsx index 077ba316..14f28fe8 100644 --- a/plugin/src/components/CompiledContracts/index.tsx +++ b/plugin/src/components/CompiledContracts/index.tsx @@ -1,26 +1,22 @@ -// A component that reads the compiled contracts from the context and displays them in a select - -import React, { useContext } from 'react' -import { CompiledContractsContext } from '../../contexts/CompiledContractsContext' +import React from 'react' import { getContractNameFromFullName, getSelectedContractIndex, getShortenedHash } from '../../utils/utils' - -// eslint-disable-next-line @typescript-eslint/no-empty-interface +import { useAtom } from 'jotai' +import { compiledContractsAtom, selectedCompiledContract } from '../../atoms/compiledContracts' interface CompiledContractsProps { show: 'class' | 'contract' } const CompiledContracts: React.FC = (props) => { - const { contracts, selectedContract, setSelectedContract } = useContext( - CompiledContractsContext - ) + const [contracts] = useAtom(compiledContractsAtom) + const [selectedContract, setSelectedContract] = useAtom(selectedCompiledContract) - function handleCompiledContractSelectionChange (event: any): void { + const handleCompiledContractSelectionChange = (event: React.ChangeEvent): void => { event.preventDefault() - setSelectedContract(contracts[event.target.value]) + if (!isNaN(parseInt(event?.target?.value))) { setSelectedContract(contracts[event.target.value as unknown as number]) } } return ( diff --git a/plugin/src/components/DevnetAccountSelector/index.tsx b/plugin/src/components/DevnetAccountSelector/index.tsx index 84f73d9d..2559eb62 100644 --- a/plugin/src/components/DevnetAccountSelector/index.tsx +++ b/plugin/src/components/DevnetAccountSelector/index.tsx @@ -5,53 +5,55 @@ import { weiToEth } from '../../utils/utils' import { getAccounts } from '../../utils/network' -import React, { useContext, useEffect, useState } from 'react' -import { ConnectionContext } from '../../contexts/ConnectionContext' +import React, { useEffect, useState } from 'react' import { Account, Provider } from 'starknet' -import { RemixClientContext } from '../../contexts/RemixClientContext' import { MdCopyAll, MdRefresh } from 'react-icons/md' import './devnetAccountSelector.css' -import EnvironmentContext from '../../contexts/EnvironmentContext' import copy from 'copy-to-clipboard' +import { useAtom, useAtomValue } from 'jotai' +import { availableDevnetAccountsAtom, devnetAtom, envAtom, isDevnetAliveAtom, selectedDevnetAccountAtom } from '../../atoms/environment' +import useAccount from '../../hooks/useAccount' +import useProvider from '../../hooks/useProvider' +import useRemixClient from '../../hooks/useRemixClient' const DevnetAccountSelector: React.FC = () => { - const { account, setAccount, provider, setProvider } = - useContext(ConnectionContext) - const remixClient = useContext(RemixClientContext) - const { - env, - devnet, - isDevnetAlive, - setIsDevnetAlive, - selectedDevnetAccount, - setSelectedDevnetAccount, - availableDevnetAccounts, - setAvailableDevnetAccounts - } = useContext(EnvironmentContext) + const { account, setAccount } = useAccount() + const { provider, setProvider } = useProvider() + const { remixClient } = useRemixClient() + const env = useAtomValue(envAtom) + const devnet = useAtomValue(devnetAtom) + const [isDevnetAlive, setIsDevnetAlive] = useAtom(isDevnetAliveAtom) + const [selectedDevnetAccount, setSelectedDevnetAccount] = useAtom(selectedDevnetAccountAtom) + const [availableDevnetAccounts, setAvailableDevnetAccounts] = useAtom(availableDevnetAccountsAtom) - // devnet live status - useEffect(() => { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - const interval = setInterval(async () => { - try { - const response = await fetch(`${devnet.url}/is_alive`, { - method: 'GET', - redirect: 'follow', - headers: { - 'Content-Type': 'application/json' - } - }) - const status = await response.text() - - if (status !== 'Alive!!!' || response.status !== 200) { - setIsDevnetAlive(() => false) - } else { - setIsDevnetAlive(() => true) + const checkDevnetUrl = async (): Promise => { + try { + const response = await fetch(`${devnet.url}/is_alive`, { + method: 'GET', + redirect: 'follow', + headers: { + 'Content-Type': 'application/json' } - } catch (error) { - setIsDevnetAlive(() => false) + }) + const status = await response.text() + + if (status !== 'Alive!!!' || response.status !== 200) { + setIsDevnetAlive(false) + } else { + setIsDevnetAlive(true) } - }, 1000) + } catch (error) { + setIsDevnetAlive(false) + } + } + + // devnet live status + useEffect(() => { + const interval = setInterval(() => { + checkDevnetUrl().catch(e => { + console.error(e) + }) + }, 3000) return () => { clearInterval(interval) } @@ -65,14 +67,14 @@ const DevnetAccountSelector: React.FC = () => { `❗️ Server ${devnet.name} - ${devnet.url} is not healthy or not reachable at the moment` ) } catch (e) { - console.log(e) + console.error(e) } } useEffect(() => { if (!isDevnetAlive) { notifyDevnetStatus().catch((e) => { - console.log(e) + console.error(e) }) } }, [isDevnetAlive]) @@ -96,12 +98,13 @@ const DevnetAccountSelector: React.FC = () => { } useEffect(() => { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - setTimeout(async () => { + setTimeout(() => { if (!isDevnetAlive) { return } - await refreshDevnetAccounts() + refreshDevnetAccounts().catch(e => { + console.error(e) + }) }, 1) }, [devnet, isDevnetAlive]) @@ -164,6 +167,7 @@ const DevnetAccountSelector: React.FC = () => { useEffect(() => { setAccountIdx(0) }, [env]) + return ( <> @@ -181,22 +185,22 @@ const DevnetAccountSelector: React.FC = () => { {isDevnetAlive && availableDevnetAccounts.length > 0 ? availableDevnetAccounts.map((account, index) => { return ( - + ) }) : ([ - + ] as JSX.Element[])}
    diff --git a/plugin/src/components/EnvCard/index.tsx b/plugin/src/components/EnvCard/index.tsx index 42962d8f..25aa9f1f 100644 --- a/plugin/src/components/EnvCard/index.tsx +++ b/plugin/src/components/EnvCard/index.tsx @@ -1,9 +1,10 @@ /* eslint-disable react/prop-types */ import { type DisconnectOptions } from 'get-starknet' -import { useContext, type ReactNode, useState } from 'react' +import { type ReactNode, useState } from 'react' import React from 'react' import './envCard.css' -import EnvironmentContext from '../../contexts/EnvironmentContext' +import { useAtomValue } from 'jotai' +import { envAtom } from '../../atoms/environment' interface EnvCardProps { header: string @@ -18,7 +19,7 @@ export const EnvCard: React.FC = ({ disconnectWalletHandler, children }) => { - const { env } = useContext(EnvironmentContext) + const env = useAtomValue(envAtom) const [prevEnv, setPrevEnv] = useState(env) return ( diff --git a/plugin/src/components/EnvironmentSelector/index.tsx b/plugin/src/components/EnvironmentSelector/index.tsx index 043a03bb..677e5a7f 100644 --- a/plugin/src/components/EnvironmentSelector/index.tsx +++ b/plugin/src/components/EnvironmentSelector/index.tsx @@ -1,19 +1,17 @@ -import React, { useContext } from 'react' +import React from 'react' import { devnets } from '../../utils/network' -import { type ConnectOptions, type DisconnectOptions } from 'get-starknet' -import { ConnectionContext } from '../../contexts/ConnectionContext' import './styles.css' -import EnvironmentContext from '../../contexts/EnvironmentContext' +import { devnetAtom, envAtom } from '../../atoms/environment' +import { useAtom, useSetAtom } from 'jotai' +import useStarknetWindow from '../../hooks/starknetWindow' +import useProvider from '../../hooks/useProvider' -interface EnvironmentSelectorProps { - connectWalletHandler: (options?: ConnectOptions) => Promise - disconnectWalletHandler: (options?: DisconnectOptions) => Promise -} - -const EnvironmentSelector: React.FC = (props) => { - const { setProvider } = useContext(ConnectionContext) - const { env, setEnv, setDevnet, starknetWindowObject } = useContext(EnvironmentContext) +const EnvironmentSelector: React.FC = () => { + const { setProvider } = useProvider() + const [env, setEnv] = useAtom(envAtom) + const setDevnet = useSetAtom(devnetAtom) + const { starknetWindowObject, connectWalletHandler } = useStarknetWindow() async function handleEnvironmentChange (event: any): Promise { const value = parseInt(event.target.value) @@ -25,7 +23,7 @@ const EnvironmentSelector: React.FC = (props) => { return } setEnv('wallet') - if (starknetWindowObject === null) await props.connectWalletHandler() + if (starknetWindowObject === null) await connectWalletHandler() } const getDefualtIndex = (): number => { diff --git a/plugin/src/components/ExplorerSelector/index.tsx b/plugin/src/components/ExplorerSelector/index.tsx index c26ebbe8..fcf85ea5 100644 --- a/plugin/src/components/ExplorerSelector/index.tsx +++ b/plugin/src/components/ExplorerSelector/index.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react' -import * as D from '../../ui_components/Dropdown' +import * as D from '../ui_components/Dropdown' import { networkExplorerUrls as EXPLORERS } from '../../utils/constants' import './index.css' @@ -8,8 +8,7 @@ import { type IExplorerSelector, type IUseCurrentExplorer } from '../../utils/mi const VOYAGER_LOGO = 'https://voyager.online/favicons/favicon-32x32.png' const STARKSCAN_LOGO = 'https://starkscan.co/img/company/favicon.ico' -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const explorerToLogo = (explorer: keyof typeof EXPLORERS) => { +const explorerToLogo = (explorer: keyof typeof EXPLORERS): string => { switch (explorer) { case 'starkscan': return STARKSCAN_LOGO @@ -36,7 +35,7 @@ const ExplorerSelector: React.FC = ({ const { explorer, setExplorer } = controlHook return (
    { e.stopPropagation() }} diff --git a/plugin/src/components/JSONView/index.tsx b/plugin/src/components/JSONView/index.tsx index 5e08b3cb..d740cc65 100644 --- a/plugin/src/components/JSONView/index.tsx +++ b/plugin/src/components/JSONView/index.tsx @@ -1,9 +1,9 @@ import React, { useState } from 'react' -const JSONView = (data: JSON) => { +const JSONView = (data: JSON): JSX.Element => { const [expandedRows, setExpandedRows] = useState([]) - const toggleRow = (rowId: string) => { + const toggleRow = (rowId: string): void => { const isExpanded = expandedRows.includes(rowId) if (isExpanded) { setExpandedRows(expandedRows.filter((id) => id !== rowId)) @@ -12,29 +12,19 @@ const JSONView = (data: JSON) => { } } - const renderRow = (row: any, level = 0) => { + const renderRow = (row: { key: string, value: any }, level = 0): JSX.Element => { + console.log(row) const rowId = `${level}-${row.key}` return ( { toggleRow(rowId) }}> - {row?.children && ( - {expandedRows.includes(rowId) ? '-' : '+'} - )} + {expandedRows.includes(rowId) ? '-' : '+'} {row.key} {row.value} - {/* {row.children && - expandedRows.includes(rowId) && - Object.entries(row.value).map(([childKey, childValue]) => ( - - - {childKey} - {childValue} - - ))} */} ) } diff --git a/plugin/src/components/ManualAccount/index.tsx b/plugin/src/components/ManualAccount/index.tsx index 3488b8e5..494721b2 100644 --- a/plugin/src/components/ManualAccount/index.tsx +++ b/plugin/src/components/ManualAccount/index.tsx @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/no-misused-promises */ -import React, { useContext, useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' import { Account, CallData, Provider, ec, hash, stark } from 'starknet' import { type Network, @@ -7,14 +6,9 @@ import { networkEquivalents, networkNameEquivalents } from '../../utils/constants' -import { ConnectionContext } from '../../contexts/ConnectionContext' import { ethers } from 'ethers' -import ManualAccountContext from '../../contexts/ManualAccountContext' import storage from '../../utils/storage' -import { RemixClientContext } from '../../contexts/RemixClientContext' -import TransactionContext from '../../contexts/TransactionContext' -import EnvironmentContext from '../../contexts/EnvironmentContext' import './index.css' import { BiCopy, BiPlus } from 'react-icons/bi' @@ -22,13 +16,21 @@ import { getExplorerUrl, trimStr } from '../../utils/utils' import { MdRefresh, MdCheckCircleOutline } from 'react-icons/md' import copy from 'copy-to-clipboard' import ExplorerSelector, { useCurrentExplorer } from '../ExplorerSelector' +import { useAtom } from 'jotai' + +import transactionsAtom from '../../atoms/transactions' +import { accountAtom, networkAtom, selectedAccountAtom } from '../../atoms/manualAccount' +import { type Env, envAtom } from '../../atoms/environment' +import useAccount from '../../hooks/useAccount' +import useProvider from '../../hooks/useProvider' +import useRemixClient from '../../hooks/useRemixClient' // TODOS: move state parts to contexts // Account address selection // network selection drop down const ManualAccount: React.FC<{ - prevEnv: string + prevEnv: Env }> = ({ prevEnv }) => { const OZaccountClassHash = '0x2794ce20e5f2ff0d40e632cb53845b9f4e526ebd8471983f7dbd355b721d5a' @@ -36,25 +38,20 @@ const ManualAccount: React.FC<{ const balanceContractAddress = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7' - const { account, provider, setAccount, setProvider } = - useContext(ConnectionContext) + const { account, setAccount } = useAccount() + const { provider, setProvider } = useProvider() const [accountDeploying, setAccountDeploying] = useState(false) - const remixClient = useContext(RemixClientContext) + const { remixClient } = useRemixClient() - const { transactions, setTransactions } = useContext(TransactionContext) + const [transactions, setTransactions] = useAtom(transactionsAtom) - const { env, setEnv } = useContext(EnvironmentContext) + const [env, setEnv] = useAtom(envAtom) - const { - accounts, - setAccounts, - selectedAccount, - setSelectedAccount, - networkName, - setNetworkName - } = useContext(ManualAccountContext) + const [accounts, setAccounts] = useAtom(accountAtom) + const [selectedAccount, setSelectedAccount] = useAtom(selectedAccountAtom) + const [networkName, setNetworkName] = useAtom(networkAtom) useEffect(() => { setNetworkName(networkConstants[0].value) @@ -228,24 +225,23 @@ const ManualAccount: React.FC<{ publicKey: await account.signer.getPubKey() }) - // eslint-disable-next-line @typescript-eslint/naming-convention - const { transaction_hash, contract_address } = + const { transaction_hash: transactionHash, contract_address: contractAddress } = await account.deployAccount({ classHash: OZaccountClassHash, constructorCalldata: OZaccountConstructorCallData, addressSalt: await account.signer.getPubKey() }) - console.log('transaction_hash=', transaction_hash) + console.log('transaction_hash=', transactionHash) - await provider.waitForTransaction(transaction_hash) + await provider.waitForTransaction(transactionHash) setTransactions([ { type: 'deployAccount', account, provider, - txId: transaction_hash, + txId: transactionHash, env }, ...transactions @@ -266,7 +262,7 @@ const ManualAccount: React.FC<{ storage.set('manualAccounts', JSON.stringify(newAccounts)) console.log( '✅ New OpenZeppelin account created.\n address =', - contract_address + contractAddress ) } catch (e) { console.error(e) @@ -318,16 +314,16 @@ const ManualAccount: React.FC<{ ? ( accounts.map((account, index) => { return ( - + ) }) ) : ( - + )}
    - {props.starknetWindowObject != null ? ( - <> -
    -
    - wallet icon -

    {props.starknetWindowObject?.id}

    -

    {currentChain}

    + {starknetWindowObject != null + ? ( + <> +
    +
    + wallet icon +

    {starknetWindowObject?.id}

    +

    {currentChain}

    +
    +
    + +
    -
    - -
    -
    -
    -

    - +

    - {trimStr( - props.starknetWindowObject?.account?.address ?? '', - 10 + + {trimStr( + starknetWindowObject?.account?.address ?? '', + 10 + )} + +

    + + + {showCopied && ( +

    + Copied +

    )} - -

    - - - {showCopied && ( -

    - Copied -

    - )} -
    -
    - - ) : ( -

    Wallet not connected

    - )} + +
    + + ) + : ( +

    Wallet not connected

    + )}
    ) } diff --git a/plugin/src/ui_components/Accordian/accordian.css b/plugin/src/components/ui_components/Accordian/accordian.css similarity index 100% rename from plugin/src/ui_components/Accordian/accordian.css rename to plugin/src/components/ui_components/Accordian/accordian.css diff --git a/plugin/src/ui_components/Accordian/index.tsx b/plugin/src/components/ui_components/Accordian/index.tsx similarity index 100% rename from plugin/src/ui_components/Accordian/index.tsx rename to plugin/src/components/ui_components/Accordian/index.tsx diff --git a/plugin/src/ui_components/CircularLoader/index.tsx b/plugin/src/components/ui_components/CircularLoader/index.tsx similarity index 100% rename from plugin/src/ui_components/CircularLoader/index.tsx rename to plugin/src/components/ui_components/CircularLoader/index.tsx diff --git a/plugin/src/ui_components/CircularLoader/loader.css b/plugin/src/components/ui_components/CircularLoader/loader.css similarity index 100% rename from plugin/src/ui_components/CircularLoader/loader.css rename to plugin/src/components/ui_components/CircularLoader/loader.css diff --git a/plugin/src/ui_components/Container/container.css b/plugin/src/components/ui_components/Container/container.css similarity index 100% rename from plugin/src/ui_components/Container/container.css rename to plugin/src/components/ui_components/Container/container.css diff --git a/plugin/src/ui_components/Container/index.tsx b/plugin/src/components/ui_components/Container/index.tsx similarity index 100% rename from plugin/src/ui_components/Container/index.tsx rename to plugin/src/components/ui_components/Container/index.tsx diff --git a/plugin/src/ui_components/Dialog/dialog.css b/plugin/src/components/ui_components/Dialog/dialog.css similarity index 100% rename from plugin/src/ui_components/Dialog/dialog.css rename to plugin/src/components/ui_components/Dialog/dialog.css diff --git a/plugin/src/ui_components/Dialog/index.tsx b/plugin/src/components/ui_components/Dialog/index.tsx similarity index 100% rename from plugin/src/ui_components/Dialog/index.tsx rename to plugin/src/components/ui_components/Dialog/index.tsx diff --git a/plugin/src/ui_components/Dropdown/dropdown.css b/plugin/src/components/ui_components/Dropdown/dropdown.css similarity index 100% rename from plugin/src/ui_components/Dropdown/dropdown.css rename to plugin/src/components/ui_components/Dropdown/dropdown.css diff --git a/plugin/src/ui_components/Dropdown/index.tsx b/plugin/src/components/ui_components/Dropdown/index.tsx similarity index 100% rename from plugin/src/ui_components/Dropdown/index.tsx rename to plugin/src/components/ui_components/Dropdown/index.tsx diff --git a/plugin/src/ui_components/FullScreenOverlay/index.tsx b/plugin/src/components/ui_components/FullScreenOverlay/index.tsx similarity index 100% rename from plugin/src/ui_components/FullScreenOverlay/index.tsx rename to plugin/src/components/ui_components/FullScreenOverlay/index.tsx diff --git a/plugin/src/ui_components/FullScreenOverlay/overlay.css b/plugin/src/components/ui_components/FullScreenOverlay/overlay.css similarity index 100% rename from plugin/src/ui_components/FullScreenOverlay/overlay.css rename to plugin/src/components/ui_components/FullScreenOverlay/overlay.css diff --git a/plugin/src/ui_components/Tabs/index.tsx b/plugin/src/components/ui_components/Tabs/index.tsx similarity index 100% rename from plugin/src/ui_components/Tabs/index.tsx rename to plugin/src/components/ui_components/Tabs/index.tsx diff --git a/plugin/src/ui_components/Tabs/tabs.css b/plugin/src/components/ui_components/Tabs/tabs.css similarity index 100% rename from plugin/src/ui_components/Tabs/tabs.css rename to plugin/src/components/ui_components/Tabs/tabs.css diff --git a/plugin/src/ui_components/Tooltip/index.tsx b/plugin/src/components/ui_components/Tooltip/index.tsx similarity index 100% rename from plugin/src/ui_components/Tooltip/index.tsx rename to plugin/src/components/ui_components/Tooltip/index.tsx diff --git a/plugin/src/ui_components/Tooltip/tooltip.css b/plugin/src/components/ui_components/Tooltip/tooltip.css similarity index 100% rename from plugin/src/ui_components/Tooltip/tooltip.css rename to plugin/src/components/ui_components/Tooltip/tooltip.css diff --git a/plugin/src/contexts/CairoVersion.ts b/plugin/src/contexts/CairoVersion.ts deleted file mode 100644 index 30871ca3..00000000 --- a/plugin/src/contexts/CairoVersion.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type React from 'react' -import { createContext } from 'react' - -const CairoVersion = createContext({ - version: '0.0.0' as string, - setVersion: ((_: string) => {}) as React.Dispatch>, -}) - -export default CairoVersion diff --git a/plugin/src/contexts/CompilationContext.ts b/plugin/src/contexts/CompilationContext.ts deleted file mode 100644 index 24343fd9..00000000 --- a/plugin/src/contexts/CompilationContext.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type React from 'react' -import { createContext } from 'react' - -const CompilationContext = createContext({ - status: 'Compiling...' as string, - setStatus: ((_: string) => {}) as React.Dispatch>, - currentFilename: '' as string, - setCurrentFilename: ((_: string) => {}) as React.Dispatch>, - isCompiling: false as boolean, - setIsCompiling: ((_: boolean) => {}) as React.Dispatch>, - isValidCairo: false as boolean, - setIsValidCairo: ((_: boolean) => {}) as React.Dispatch>, - noFileSelected: false as boolean, - setNoFileSelected: ((_: boolean) => {}) as React.Dispatch>, - hashDir: '' as string, - setHashDir: ((_: string) => {}) as React.Dispatch>, - tomlPaths: [] as string[], - setTomlPaths: ((_: string[]) => {}) as React.Dispatch>, - activeTomlPath: '' as string, - setActiveTomlPath: ((_: string) => {}) as React.Dispatch> -}) - -export default CompilationContext diff --git a/plugin/src/contexts/CompiledContractsContext.ts b/plugin/src/contexts/CompiledContractsContext.ts deleted file mode 100644 index d3862d34..00000000 --- a/plugin/src/contexts/CompiledContractsContext.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createContext } from 'react' -import { type Contract } from '../types/contracts' - -const CompiledContractsContext = createContext({ - contracts: [] as Contract[], - setContracts: (contracts: Contract[]) => {}, - selectedContract: null as Contract | null, - setSelectedContract: (contract: Contract | null) => {} -}) - -export { CompiledContractsContext } diff --git a/plugin/src/contexts/ConnectionContext.ts b/plugin/src/contexts/ConnectionContext.ts deleted file mode 100644 index 9528d274..00000000 --- a/plugin/src/contexts/ConnectionContext.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createContext } from 'react' -import { - type Account, - type AccountInterface, - type Provider, - type ProviderInterface -} from 'starknet' - -const ConnectionContext = createContext({ - provider: null as Provider | ProviderInterface | null, - setProvider: (_: Provider | ProviderInterface | null) => {}, - account: null as Account | AccountInterface | null, - setAccount: (_: Account | AccountInterface | null) => {} -}) - -export { ConnectionContext } diff --git a/plugin/src/contexts/DeploymentContext.ts b/plugin/src/contexts/DeploymentContext.ts deleted file mode 100644 index 8ce4bc15..00000000 --- a/plugin/src/contexts/DeploymentContext.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type React from 'react' -import { createContext } from 'react' -import { type Input, type CallDataObject } from '../types/contracts' - -const DeploymentContext = createContext({ - isDeploying: false as boolean, - setIsDeploying: ((_: boolean) => {}) as React.Dispatch>, - deployStatus: '' as string, - setDeployStatus: ((_: string) => {}) as React.Dispatch>, - constructorCalldata: {} satisfies CallDataObject, - setConstructorCalldata: ((_: CallDataObject) => {}) as React.Dispatch>, - constructorInputs: [] as Input[], - setConstructorInputs: ((_: Input[]) => {}) as React.Dispatch>, - notEnoughInputs: false as boolean, - setNotEnoughInputs: ((_: boolean) => {}) as React.Dispatch> -}) - -export default DeploymentContext diff --git a/plugin/src/contexts/EnvironmentContext.ts b/plugin/src/contexts/EnvironmentContext.ts deleted file mode 100644 index cd3396a0..00000000 --- a/plugin/src/contexts/EnvironmentContext.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type React from 'react' -import { createContext } from 'react' -import { type Devnet, devnets, type DevnetAccount } from '../utils/network' -import { type StarknetWindowObject } from 'get-starknet' - -const EnvironmentContext = createContext({ - devnet: devnets[0], - setDevnet: ((_: Devnet) => {}) as React.Dispatch>, - env: 'remoteDevnet' as string, - setEnv: ((_: string) => {}) as React.Dispatch>, - isDevnetAlive: true as boolean, - setIsDevnetAlive: ((_: boolean) => {}) as React.Dispatch>, - starknetWindowObject: null as (StarknetWindowObject | null), - setStarknetWindowObject: ((_: StarknetWindowObject | null) => {}) as React.Dispatch>, - selectedDevnetAccount: null as (DevnetAccount | null), - setSelectedDevnetAccount: ((_: DevnetAccount | null) => {}) as React.Dispatch>, - availableDevnetAccounts: [] as DevnetAccount[], - setAvailableDevnetAccounts: ((_: DevnetAccount[]) => {}) as React.Dispatch> -}) - -export default EnvironmentContext diff --git a/plugin/src/contexts/ManualAccountContext.ts b/plugin/src/contexts/ManualAccountContext.ts deleted file mode 100644 index 92b7b4aa..00000000 --- a/plugin/src/contexts/ManualAccountContext.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createContext } from 'react' -import { type ManualAccount } from '../types/accounts' -import { networks } from '../utils/constants' - -const TransactionContext = createContext({ - accounts: [] as ManualAccount[], - setAccounts: ((_: ManualAccount[]) => {}) as React.Dispatch>, - selectedAccount: null as ManualAccount | null, - setSelectedAccount: ((_: ManualAccount | null) => {}) as React.Dispatch>, - networkName: networks[0].value, - setNetworkName: ((_: string) => {}) as React.Dispatch> -}) - -export default TransactionContext diff --git a/plugin/src/contexts/RemixClientContext.ts b/plugin/src/contexts/RemixClientContext.ts deleted file mode 100644 index 66d0e86d..00000000 --- a/plugin/src/contexts/RemixClientContext.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { PluginClient } from '@remixproject/plugin' -import { createClient } from '@remixproject/plugin-webview' -import { createContext } from 'react' - -const remixClient = createClient(new PluginClient()) - -const RemixClientContext = createContext(remixClient) - -export { RemixClientContext } diff --git a/plugin/src/contexts/TransactionContext.ts b/plugin/src/contexts/TransactionContext.ts deleted file mode 100644 index b9624db4..00000000 --- a/plugin/src/contexts/TransactionContext.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createContext } from 'react' -import { type Transaction } from '../types/transaction' - -const TransactionContext = createContext({ - transactions: [] as Transaction[], - setTransactions: ((_: Transaction[]) => {}) as React.Dispatch> -}) - -export default TransactionContext diff --git a/plugin/src/env.d.ts b/plugin/src/env.d.ts index 5a8ce38e..7fff2da6 100644 --- a/plugin/src/env.d.ts +++ b/plugin/src/env.d.ts @@ -1,14 +1,14 @@ -/// +import 'vite/client' interface ImportMetaEnv { - readonly VITE_URL: string - readonly VITE_API_URL: string - readonly VITE_DEVNET_URL: string - readonly VITE_REMOTE_DEVNET_URL: string - readonly VITE_VERSION: string - // more env variables... - } - - interface ImportMeta { - readonly env: ImportMetaEnv - } \ No newline at end of file + readonly VITE_URL: string + readonly VITE_API_URL: string + readonly VITE_DEVNET_URL: string + readonly VITE_REMOTE_DEVNET_URL: string + readonly VITE_VERSION: string + // more env variables... +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/plugin/src/features/CairoVersion/index.tsx b/plugin/src/features/CairoVersion/index.tsx index 17118d92..7bb0aca0 100644 --- a/plugin/src/features/CairoVersion/index.tsx +++ b/plugin/src/features/CairoVersion/index.tsx @@ -1,96 +1,101 @@ -import * as D from '../../ui_components/Dropdown'; -import React, {useContext, useEffect, useState} from 'react'; -import { apiUrl } from '../../utils/network'; -import { RemixClientContext } from '../../contexts/RemixClientContext'; -import CairoVersionContext from '../../contexts/CairoVersion'; -import { BsChevronDown } from 'react-icons/bs'; -import Nethermind from '../../components/NM'; -import './style.css'; +import * as D from '../../components/ui_components/Dropdown' +import React, { useEffect, useState } from 'react' +import { apiUrl } from '../../utils/network' +import { BsChevronDown } from 'react-icons/bs' +import Nethermind from '../../components/NM' +import cairoVersionAtom from '../../atoms/cairoVersion' +import './style.css' +import { useAtom } from 'jotai' +import useRemixClient from '../../hooks/useRemixClient' const CairoVersion: React.FC = () => { - const { version: cairoVersion, setVersion: setCairoVersion } = useContext(CairoVersionContext); - const remixClient = useContext(RemixClientContext); + const [cairoVersion, setCairoVersion] = useAtom(cairoVersionAtom) + const { remixClient } = useRemixClient() - const pluginVersion = import.meta.env.VITE_VERSION !== undefined ? `v${import.meta.env.VITE_VERSION}` : 'v0.2.0'; + const envViteVersion: string | undefined = import.meta.env.VITE_VERSION + const pluginVersion = envViteVersion !== undefined ? `v${envViteVersion}` : 'v0.2.0' // Hard-coded versions for the example - const [getVersions, setVersions] = useState([]); + const [getVersions, setVersions] = useState([]) useEffect(() => { - const fetchCairoVersions = async () => { + const fetchCairoVersions = async (): Promise => { try { if (apiUrl !== undefined) { const response = await fetch( - `${apiUrl}/cairo_versions`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/octet-stream' - }, - redirect: 'follow' - } - ); - const versions = JSON.parse(await response.text()); - setVersions(versions); + `${apiUrl}/cairo_versions`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/octet-stream' + }, + redirect: 'follow' + } + ) + const versions = JSON.parse(await response.text()) + setVersions(versions) } } catch (e) { - await remixClient.call('notification' as any, 'toast', '🔴 Failed to fetch cairo versions from the compilation server'); - console.error(e); - await remixClient.terminal.log(`🔴 Failed to fetch cairo versions from the compilation server ${e}` as any); + await remixClient.call('notification' as any, 'toast', '🔴 Failed to fetch cairo versions from the compilation server') + console.error(e) + await remixClient.terminal.log(`🔴 Failed to fetch cairo versions from the compilation server ${e as string}` as any) } } - setTimeout(async () => { - await fetchCairoVersions(); + setTimeout(() => { + const fetchCairo = async (): Promise => { + await fetchCairoVersions() - if (getVersions.length > 0) { - setCairoVersion(getVersions[getVersions.length - 1]) + if (getVersions.length > 0) { + setCairoVersion(getVersions[getVersions.length - 1]) + } } - }, 10000); - }, [remixClient]); + fetchCairo().catch(e => { console.error(e) }) + }, 10000) + }, [remixClient]) useEffect(() => { if (getVersions.length > 0) { setCairoVersion(getVersions[getVersions.length - 1]) } - }, [remixClient, getVersions]); + }, [remixClient, getVersions]) return ( -
    -
    - - - - - - - {getVersions.map((v, i) => ( - { - setCairoVersion(v); - }} - > - {v} - - ))} - - - -
    -
    - - -
    +
    +
    + + + + + + + {getVersions.map((v, i) => ( + { + setCairoVersion(v) + }} + > + {v} + + ))} + + + +
    +
    + +
    - ); +
    + ) } -export default CairoVersion; +export default CairoVersion diff --git a/plugin/src/features/Compilation/index.tsx b/plugin/src/features/Compilation/index.tsx index c0228b91..a9cb2865 100644 --- a/plugin/src/features/Compilation/index.tsx +++ b/plugin/src/features/Compilation/index.tsx @@ -1,8 +1,4 @@ -/* eslint-disable @typescript-eslint/restrict-plus-operands */ -/* eslint-disable @typescript-eslint/strict-boolean-expressions */ -import React, { useContext, useEffect } from 'react' -import { CompiledContractsContext } from '../../contexts/CompiledContractsContext' -import { RemixClientContext } from '../../contexts/RemixClientContext' +import React, { useEffect } from 'react' import { apiUrl } from '../../utils/network' import { artifactFilename, @@ -12,48 +8,193 @@ import { } from '../../utils/utils' import './styles.css' import { hash } from 'starknet' -import Container from '../../ui_components/Container' +import Container from '../../components/ui_components/Container' import storage from '../../utils/storage' import { ethers } from 'ethers' -import CompilationContext from '../../contexts/CompilationContext' import { type AccordianTabs } from '../Plugin' -import * as D from '../../ui_components/Dropdown' +import * as D from '../../components/ui_components/Dropdown' import { BsChevronDown } from 'react-icons/bs' -import { type Contract } from '../../types/contracts' +import { type Contract } from '../../utils/types/contracts' import { asyncFetch } from '../../utils/async_fetch' -import CairoVersionContext from "../../contexts/CairoVersion"; +import { useAtom, useAtomValue, useSetAtom } from 'jotai' + +// Imported Atoms +import cairoVersionAtom from '../../atoms/cairoVersion' +import { compiledContractsAtom, selectedCompiledContract } from '../../atoms/compiledContracts' +import { activeTomlPathAtom, compilationAtom, currentFilenameAtom, hashDirAtom, isCompilingAtom, isValidCairoAtom, noFileSelectedAtom, statusAtom, tomlPathsAtom } from '../../atoms/compilation' +import useRemixClient from '../../hooks/useRemixClient' +import { isEmpty } from '../../utils/misc' + +interface FileContentMap { + file_name: string + file_content: string +} + +interface ScarbCompileResponse { + status: string + message: string + file_content_map_array: FileContentMap[] +} + +const CompilationCard: React.FC<{ + validation: boolean + isLoading: boolean + onClick: () => unknown + compileScarb: (workspacePath: string, scarbPath: string) => Promise + currentWorkspacePath: string +}> = ( + { + validation, + isLoading, + onClick, + compileScarb, + currentWorkspacePath + } +): React.ReactElement => { + const { remixClient } = useRemixClient() + + const { + activeTomlPath, + tomlPaths, + isCompiling, + currentFilename + } = useAtomValue(compilationAtom) + + const setActiveTomlPath = useSetAtom(activeTomlPathAtom) + + const isCurrentFileName = isEmpty(currentFilename) + + return ( + + {activeTomlPath !== undefined && tomlPaths?.length > 0 && ( +
    + + + + +
    + + +
    +
    + + + {tomlPaths.map((tomlPath, i) => { + return ( + { + setActiveTomlPath(tomlPath) + }} + > + {tomlPath} + + ) + })} + + +
    +
    Or compile a single file:
    +
    + )} + +
    + ) +} -// eslint-disable-next-line @typescript-eslint/no-empty-interface interface CompilationProps { setAccordian: React.Dispatch> } const Compilation: React.FC = ({ setAccordian }) => { - const remixClient = useContext(RemixClientContext) - const { version: cairoVersion, setVersion: setCairoVersion } = useContext(CairoVersionContext); + const { remixClient } = useRemixClient() + const cairoVersion = useAtomValue(cairoVersionAtom) - const { contracts, selectedContract, setContracts, setSelectedContract } = useContext( - CompiledContractsContext - ) + const [contracts, setContracts] = useAtom(compiledContractsAtom) + const [selectedContract, setSelectedContract] = useAtom(selectedCompiledContract) const { - status, - setStatus, currentFilename, - setCurrentFilename, isCompiling, - setIsCompiling, isValidCairo, - setIsValidCairo, noFileSelected, - setNoFileSelected, hashDir, - setHashDir, tomlPaths, - setTomlPaths, - activeTomlPath, - setActiveTomlPath - } = useContext(CompilationContext) + activeTomlPath + } = useAtomValue(compilationAtom) + + const setStatus = useSetAtom(statusAtom) + const setHashDir = useSetAtom(hashDirAtom) + const setNoFileSelected = useSetAtom(noFileSelectedAtom) + const setIsValidCairo = useSetAtom(isValidCairoAtom) + const setIsCompiling = useSetAtom(isCompilingAtom) + const setCurrentFilename = useSetAtom(currentFilenameAtom) + const setTomlPaths = useSetAtom(tomlPathsAtom) + const setActiveTomlPath = useSetAtom(activeTomlPathAtom) const [currWorkspacePath, setCurrWorkspacePath] = React.useState('') @@ -185,7 +326,7 @@ const Compilation: React.FC = ({ setAccordian }) => { workspacePath + '/' + currPath ) // get keys of allFiles object - const allFilesKeys = Object.keys(allFiles) + const allFilesKeys: string[] = Object.keys(allFiles) // const get all values of allFiles object const allFilesValues = Object.values(allFiles) @@ -193,7 +334,8 @@ const Compilation: React.FC = ({ setAccordian }) => { if (allFilesKeys[i].endsWith('Scarb.toml')) { resTomlPaths.push(currPath) } - if (Object.values(allFilesValues[i])[0]) { + + if (Object.values(allFilesValues[i])[0] as unknown as boolean) { const recTomlPaths = await getTomlPaths( workspacePath, allFilesKeys[i] @@ -363,8 +505,8 @@ const Compilation: React.FC = ({ setAccordian }) => { setStatus('Compiling to sierra...') const compileToSierraResponse = await asyncFetch( - `compile-to-sierra-async/${cairoVersion}/${hashDir}/${currentFilePath}`, - 'compile-to-sierra-result' + `compile-to-sierra-async/${cairoVersion}/${hashDir}/${currentFilePath}`, + 'compile-to-sierra-result' ) // get Json body from response @@ -374,7 +516,7 @@ const Compilation: React.FC = ({ setAccordian }) => { setStatus('Reporting Errors...') await remixClient.terminal.log(sierra.message) - const errorLets = sierra.message.trim().split('\n') + const errorLets: string[] = sierra.message.trim().split('\n') // remove last element if it's starts with `Error:` if (errorLets[errorLets.length - 1].startsWith('Error:')) { @@ -383,7 +525,7 @@ const Compilation: React.FC = ({ setAccordian }) => { // break the errorLets in array of arrays with first element contains the string `Plugin diagnostic` const errorLetsArray = errorLets.reduce( - (acc: any, curr: any) => { + (acc: string[][], curr: string) => { if (curr.startsWith('error:') || curr.startsWith('warning:')) { acc.push([curr]) } else { @@ -400,11 +542,11 @@ const Compilation: React.FC = ({ setAccordian }) => { // eslint-disable-next-line @typescript-eslint/no-misused-promises errorLetsArray.forEach(async (errorLet: any) => { const errorType = errorLet[0].split(':')[0].trim() - const errorTitle = errorLet[0].split(':').slice(1).join(':').trim() + const errorTitle: string = errorLet[0].split(':').slice(1).join(':').trim() const errorLine = errorLet[1].split(':')[1].trim() const errorColumn = errorLet[1].split(':')[2].trim() // join the rest of the array - const errorMsg = errorLet.slice(2).join('\n') + const errorMsg: string = errorLet.slice(2).join('\n') await remixClient.editor.addAnnotation({ row: Number(errorLine) - 1, @@ -415,12 +557,12 @@ const Compilation: React.FC = ({ setAccordian }) => { }) // trim sierra message to get last line - const lastLine = sierra.message.trim().split('\n').pop().trim() + const lastLine: string = sierra.message.trim().split('\n').pop().trim() remixClient.emit('statusChanged', { key: 'failed', type: 'error', - title: lastLine.startsWith('Error') ? lastLine : 'Compilation Failed' + title: lastLine?.startsWith('Error') ? lastLine : 'Compilation Failed' }) throw new Error( 'Cairo Compilation Failed, logs can be read in the terminal log' @@ -429,8 +571,8 @@ const Compilation: React.FC = ({ setAccordian }) => { setStatus('Compiling to casm...') const compileToCasmResponse = await asyncFetch( - `compile-to-casm-async/${cairoVersion}/${hashDir}/${currentFilePath.replaceAll(getFileExtension(currentFilePath), 'sierra')}`, - 'compile-to-casm-result' + `compile-to-casm-async/${cairoVersion}/${hashDir}/${currentFilePath.replaceAll(getFileExtension(currentFilePath), 'sierra')}`, + 'compile-to-casm-result' ) // get Json body from response @@ -502,10 +644,10 @@ const Compilation: React.FC = ({ setAccordian }) => { 'notification' as any, 'toast', e.message + - ' try deleting the files: ' + - sierraPath + - ' and ' + - casmPath + ' try deleting the files: ' + + sierraPath + + ' and ' + + casmPath ) } remixClient.emit('statusChanged', { @@ -567,8 +709,7 @@ const Compilation: React.FC = ({ setAccordian }) => { ) setStatus(`Saving ${allFilesKeys[i]}...`) const response = await fetch( - `${apiUrl}/save_code/${hashDir}/${ - workspacePath.replace('.', '') + '/' + allFilesKeys[i] + `${apiUrl}/save_code/${hashDir}/${workspacePath.replace('.', '') + '/' + allFilesKeys[i] }`, { method: 'POST', @@ -588,7 +729,8 @@ const Compilation: React.FC = ({ setAccordian }) => { throw new Error('Cairo Compilation Request Failed') } } - if (Object.values(allFilesValues[i])[0]) { + const checkVal = Object.values(allFilesValues[i])[0] + if (!isEmpty(checkVal)) { await saveScarbWorkspace(workspacePath, allFilesKeys[i]) } } @@ -610,8 +752,8 @@ const Compilation: React.FC = ({ setAccordian }) => { let result: string try { result = await asyncFetch(`compile-scarb-async/${hashDir}/${workspacePath.replace( - '.', - '' + '.', + '' )}/${scarbPath}`, 'compile-scarb-result') } catch (e) { await remixClient.call( @@ -621,8 +763,7 @@ const Compilation: React.FC = ({ setAccordian }) => { ) throw new Error('Cairo Compilation Request Failed') } - - const scarbCompile = JSON.parse(await result) + const scarbCompile: ScarbCompileResponse = JSON.parse(result) if (scarbCompile.status !== 'Success') { await remixClient.call( @@ -656,14 +797,14 @@ const Compilation: React.FC = ({ setAccordian }) => { const contractsToStore: Contract[] = [] for (const file of scarbCompile.file_content_map_array) { - if (file.file_name.endsWith('.sierra.json')) { - const contractName = file.file_name.replace('.sierra.json', '') + if (file.file_name?.endsWith('.sierra.json')) { + const contractName: string = file.file_name.replace('.sierra.json', '') const sierra = JSON.parse(file.file_content) if ( - !scarbCompile.file_content_map_array.find( + (scarbCompile.file_content_map_array?.find( (file: { file_name: string }) => file.file_name === contractName + '.casm.json' - ) + )) == null ) { notifyCasmInclusion = true continue @@ -751,8 +892,8 @@ const Compilation: React.FC = ({ setAccordian }) => { contracts.find( (contract) => contract.classHash === classHash && - contract.compiledClassHash === compiledClassHash - ) + contract.compiledClassHash === compiledClassHash + ) != null ) { return null } @@ -771,124 +912,17 @@ const Compilation: React.FC = ({ setAccordian }) => { return contract } - const compilationCard = ( - validation: boolean, - isLoading: boolean, - onClick: () => unknown - ): React.ReactElement => { - return ( - - {activeTomlPath && tomlPaths?.length > 0 && ( -
    - - - - -
    - - -
    -
    - - - {tomlPaths.map((tomlPath, i) => { - return ( - { - setActiveTomlPath(tomlPath) - }} - > - {tomlPath} - - ) - })} - - -
    -
    Or compile a single file:
    -
    - )} - -
    - ) - } - return (
    - {compilations.map((compilation) => { - return compilationCard( - compilation.validation, - compilation.isLoading, - compilation.onClick - ) + {compilations.map((compilation, idx) => { + return })}
    ) diff --git a/plugin/src/features/Deployment/index.tsx b/plugin/src/features/Deployment/index.tsx index 728b247c..ed7ba4f4 100644 --- a/plugin/src/features/Deployment/index.tsx +++ b/plugin/src/features/Deployment/index.tsx @@ -1,51 +1,56 @@ -import React, { useContext, useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' import { type BigNumberish } from 'ethers' import CompiledContracts from '../../components/CompiledContracts' -import { CompiledContractsContext } from '../../contexts/CompiledContractsContext' import { type CallDataObj, type CallDataObject, type Contract -} from '../../types/contracts' +} from '../../utils/types/contracts' import { getConstructor, getParameterType } from '../../utils/utils' -import './styles.css' -import Container from '../../ui_components/Container' +import Container from '../../components/ui_components/Container' -import { ConnectionContext } from '../../contexts/ConnectionContext' -import { RemixClientContext } from '../../contexts/RemixClientContext' import { type AccordianTabs } from '../Plugin' -import DeploymentContext from '../../contexts/DeploymentContext' -import TransactionContext from '../../contexts/TransactionContext' import { constants } from 'starknet' -import EnvironmentContext from '../../contexts/EnvironmentContext' +import { useAtom, useAtomValue, useSetAtom } from 'jotai' +import transactionsAtom from '../../atoms/transactions' +import './styles.css' +import { compiledContractsAtom, selectedCompiledContract } from '../../atoms/compiledContracts' +import { envAtom } from '../../atoms/environment' +import useAccount from '../../hooks/useAccount' +import useProvider from '../../hooks/useProvider' +import useRemixClient from '../../hooks/useRemixClient' +import { constructorInputsAtom, deployStatusAtom, deploymentAtom, isDeployingAtom, notEnoughInputsAtom } from '../../atoms/deployment' interface DeploymentProps { setActiveTab: (tab: AccordianTabs) => void } const Deployment: React.FC = ({ setActiveTab }) => { - const remixClient = useContext(RemixClientContext) - const { account, provider } = useContext(ConnectionContext) - const { contracts, selectedContract, setContracts, setSelectedContract } = - useContext(CompiledContractsContext) + const { remixClient } = useRemixClient() + const { account } = useAccount() + const { provider } = useProvider() + + const [contracts, setContracts] = useAtom(compiledContractsAtom) + const [selectedContract, setSelectedContract] = useAtom(selectedCompiledContract) const [constructorCalldata, setConstructorCalldata] = useState({}) const { isDeploying, - setIsDeploying, deployStatus, - setDeployStatus, constructorInputs, - setConstructorInputs, - notEnoughInputs, - setNotEnoughInputs - } = useContext(DeploymentContext) + notEnoughInputs + } = useAtomValue(deploymentAtom) + + const setIsDeploying = useSetAtom(isDeployingAtom) + const setDeployStatus = useSetAtom(deployStatusAtom) + const setConstructorInputs = useSetAtom(constructorInputsAtom) + const setNotEnoughInputs = useSetAtom(notEnoughInputsAtom) - const { transactions, setTransactions } = useContext(TransactionContext) - const { env } = useContext(EnvironmentContext) + const [transactions, setTransactions] = useAtom(transactionsAtom) + const env = useAtomValue(envAtom) const [chainId, setChainId] = useState( constants.StarknetChainId.SN_GOERLI @@ -215,15 +220,16 @@ const Deployment: React.FC = ({ setActiveTab }) => { value, dataset: { type, index } } = event.target - setConstructorCalldata((prevCalldata) => ({ - ...prevCalldata, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - [index!]: { - name, - value, - type - } - })) + if (index != null) { + setConstructorCalldata((prevCalldata) => ({ + ...prevCalldata, + [index]: { + name, + value, + type + } + })) + } } const getFormattedCalldata = (): BigNumberish[] => { @@ -273,35 +279,45 @@ const Deployment: React.FC = ({ setActiveTab }) => { {contracts.length > 0 && selectedContract != null ? ( -
    - -
    - {constructorInputs.map((input, index) => { - return ( -
    - - -
    - ) - })} - -
    - {account != null && - selectedContract.deployedInfo.some( - (info) => - info.address === account.address && info.chainId === chainId - ) && ( -
    - -
    - )} - {notEnoughInputs && ( - - )} -
    + + + {account != null && + selectedContract.deployedInfo.some( + (info) => + info.address === account.address && info.chainId === chainId + ) && ( +
    + +
    + )} + {notEnoughInputs && ( + + )} +
    ) : ( -

    No contracts ready for deployment yet, compile a cairo contract

    +

    No contracts ready for deployment yet, compile a cairo contract

    )} diff --git a/plugin/src/features/Environment/index.tsx b/plugin/src/features/Environment/index.tsx index ed9f9877..0c736495 100644 --- a/plugin/src/features/Environment/index.tsx +++ b/plugin/src/features/Environment/index.tsx @@ -1,106 +1,25 @@ /* eslint-disable multiline-ternary */ -import React, { useContext, useState } from 'react' +import React, { useState } from 'react' import DevnetAccountSelector from '../../components/DevnetAccountSelector' import './styles.css' -import { - type ConnectOptions, - type DisconnectOptions, - connect, - disconnect -} from 'get-starknet' -import { RemixClientContext } from '../../contexts/RemixClientContext' + import EnvironmentSelector from '../../components/EnvironmentSelector' -import { ConnectionContext } from '../../contexts/ConnectionContext' import Wallet from '../../components/Wallet' import { RxDotFilled } from 'react-icons/rx' -import EnvironmentContext from '../../contexts/EnvironmentContext' import Accordian, { AccordianItem, AccordionContent, AccordionTrigger -} from '../../ui_components/Accordian' +} from '../../components/ui_components/Accordian' import ManualAccount from '../../components/ManualAccount' +import { useAtom, useAtomValue } from 'jotai' +import { type Env, envAtom, isDevnetAliveAtom } from '../../atoms/environment' -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface EnvironmentProps {} - -const Environment: React.FC = () => { - const remixClient = useContext(RemixClientContext) - const { setAccount, setProvider } = - useContext(ConnectionContext) - - const { - env, - setEnv, - isDevnetAlive, - starknetWindowObject, - setStarknetWindowObject - } = useContext(EnvironmentContext) - const [prevEnv, setPrevEnv] = useState(env) - - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - const connectWalletHandler = async ( - options: ConnectOptions = { - modalMode: 'alwaysAsk', - modalTheme: 'dark' - } - ) => { - try { - const connectedStarknetWindowObject = await connect(options) - if (connectedStarknetWindowObject == null) { - throw new Error('Failed to connect to wallet') - } - await connectedStarknetWindowObject.enable({ starknetVersion: 'v5' }) - connectedStarknetWindowObject.on( - 'accountsChanged', - (accounts: string[]) => { - console.log('accountsChanged', accounts) - void connectWalletHandler({ - modalMode: 'neverAsk', - modalTheme: 'dark' - }) - } - ) +const Environment: React.FC = () => { + const [env, setEnv] = useAtom(envAtom) + const isDevnetAlive = useAtomValue(isDevnetAliveAtom) - connectedStarknetWindowObject.on('networkChanged', (network?: string) => { - console.log('networkChanged', network) - void connectWalletHandler({ - modalMode: 'neverAsk', - modalTheme: 'dark' - }) - }) - setStarknetWindowObject(connectedStarknetWindowObject) - if (connectedStarknetWindowObject.account != null) { - setAccount(connectedStarknetWindowObject.account) - } - if (connectedStarknetWindowObject.provider != null) { - setProvider(connectedStarknetWindowObject.provider) - } - } catch (e) { - if (e instanceof Error) { - await remixClient.call('notification' as any, 'alert', e) - } - setStarknetWindowObject(null) - console.log(e) - } - } - - const disconnectWalletHandler = async ( - options: DisconnectOptions = { - clearLastWallet: true - } - ): Promise => { - if (starknetWindowObject != null) { - starknetWindowObject.off('accountsChanged', (_accounts: string[]) => {}) - starknetWindowObject.off('networkChanged', (_network?: string) => {}) - } - await disconnect(options) - setStarknetWindowObject(null) - setAccount(null) - setProvider(null) - } - - // END: WALLET + const [prevEnv, setPrevEnv] = useState(env) const [currentPane, setCurrentPane] = useState('environment') @@ -135,32 +54,29 @@ const Environment: React.FC = () => {
    - + {env === 'wallet' ? ( - + ) : isDevnetAlive ? ( - + ) : ( - + )}
    @@ -169,11 +85,6 @@ const Environment: React.FC = () => { ) : ( )} diff --git a/plugin/src/features/Interaction/index.tsx b/plugin/src/features/Interaction/index.tsx index e56854ed..f501671c 100644 --- a/plugin/src/features/Interaction/index.tsx +++ b/plugin/src/features/Interaction/index.tsx @@ -1,9 +1,4 @@ -/* eslint-disable react/jsx-key */ -/* eslint-disable @typescript-eslint/strict-boolean-expressions */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-case-declarations */ -/* eslint-disable multiline-ternary */ -import React, { useContext, useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' import { type BigNumberish } from 'ethers' import { @@ -16,45 +11,50 @@ import { constants } from 'starknet' import CompiledContracts from '../../components/CompiledContracts' -import { CompiledContractsContext } from '../../contexts/CompiledContractsContext' -import { type CallDataObj, type Input } from '../../types/contracts' +import { type CallDataObj, type Input } from '../../utils/types/contracts' import { getParameterType, getReadFunctions, getWriteFunctions } from '../../utils/utils' -import Container from '../../ui_components/Container' -import { ConnectionContext } from '../../contexts/ConnectionContext' -import TransactionContext from '../../contexts/TransactionContext' -import { RemixClientContext } from '../../contexts/RemixClientContext' +import Container from '../../components/ui_components/Container' import storage from '../../utils/storage' import './index.css' -import { useAtom } from 'jotai' -import { type EnhancedAbiElement, interactAtom } from '../../atoms' -import { Formik } from 'formik' +import { useAtom, useAtomValue } from 'jotai' +import { type EnhancedAbiElement, interactAtom, type UiAbiState } from '../../atoms' +import { Formik, type FormikProps } from 'formik' import Yup, { transformInputs } from '../../utils/yup' import { BiReset } from 'react-icons/bi' -import EnvironmentContext from '../../contexts/EnvironmentContext' +import transactionsAtom from '../../atoms/transactions' +import { compiledContractsAtom, selectedCompiledContract } from '../../atoms/compiledContracts' +import { envAtom } from '../../atoms/environment' +import useAccount from '../../hooks/useAccount' +import useProvider from '../../hooks/useProvider' +import useRemixClient from '../../hooks/useRemixClient' +import { isEmpty } from '../../utils/misc' -// eslint-disable-next-line @typescript-eslint/no-empty-interface interface InteractionProps { setInteractionStatus: React.Dispatch> } const Interaction: React.FC = (props) => { - const { contracts, selectedContract } = useContext(CompiledContractsContext) - const { account, provider } = useContext(ConnectionContext) + const contracts = useAtomValue(compiledContractsAtom) + const selectedContract = useAtomValue(selectedCompiledContract) - const { transactions, setTransactions } = useContext(TransactionContext) - const remixClient = useContext(RemixClientContext) - const { env } = useContext(EnvironmentContext) + const { account } = useAccount() + const { provider } = useProvider() + + const [transactions, setTransactions] = useAtom(transactionsAtom) + + const { remixClient } = useRemixClient() + const env = useAtomValue(envAtom) const [contractsState, setContractsState] = useAtom(interactAtom) // console.log(contractsState[selectedContract?.address!]) - const setReadState = (readState: EnhancedAbiElement[]) => { + const setReadState = (readState: EnhancedAbiElement[]): void => { if (selectedContract != null) { setContractsState({ ...contractsState, @@ -65,7 +65,7 @@ const Interaction: React.FC = (props) => { }) } } - const setWriteState = (writeState: EnhancedAbiElement[]) => { + const setWriteState = (writeState: EnhancedAbiElement[]): void => { if (selectedContract != null) { setContractsState({ ...contractsState, @@ -81,11 +81,12 @@ const Interaction: React.FC = (props) => { response: CallContractResponse | GetTransactionReceiptResponse, funcName: string, stateType: 'external' | 'view' - ) => { + ): void => { if (selectedContract != null) { const currentContractObj = contractsState[selectedContract.address] switch (stateType) { case 'view': + { const readState = currentContractObj.readState const oldElemIdx = readState.findIndex( (rObj) => rObj.name === funcName @@ -112,7 +113,9 @@ const Interaction: React.FC = (props) => { /// If no old elem found, no need to udpate } break + } case 'external': + { const writeState = currentContractObj.writeState const oldElemWIdx = writeState.findIndex( (rObj) => rObj.name === funcName @@ -140,6 +143,7 @@ const Interaction: React.FC = (props) => { } /// If no old elem found, no need to udpate break + } } } } @@ -196,8 +200,8 @@ const Interaction: React.FC = (props) => { }) // Merge with old objs, since old objs can have responses. - const oldContractObj = contractsState[selectedContract.address] - if (oldContractObj) { + const oldContractObj: undefined | UiAbiState = contractsState[selectedContract.address] + if (oldContractObj !== undefined) { const oldReadObjs = oldContractObj.readState const oldWriteObj = oldContractObj.writeState const mergedReadFuncs = readFunctions.map((f) => { @@ -344,8 +348,8 @@ const Interaction: React.FC = (props) => { const inputs: CallDataObj[] = [] try { input.forEach((c) => { - if (c.name) { - if (finalInputForm[c.name]) { + if (!isEmpty(c.name)) { + if (!isEmpty(finalInputForm[c.name])) { const callDataB = transformInputs(finalInputForm[c.name]) if (!Array.isArray(callDataB)) { inputs.push([callDataB.toHexString()]) @@ -363,18 +367,19 @@ const Interaction: React.FC = (props) => { return inputs } - const clearRawInputs = async ( + const clearRawInputs = ( type: 'view' | 'external', funcName: string - ) => { + ): void => { if (selectedContract == null) { console.error('No Contract Selected!!') return } switch (type) { case 'view': + { const readFunctions = - contractsState[selectedContract?.address].readState + contractsState[selectedContract?.address].readState const newReadFns = readFunctions.map((rf) => { if (rf.name === funcName) { return { @@ -389,9 +394,11 @@ const Interaction: React.FC = (props) => { }) setReadState(newReadFns) break + } case 'external': + { const writeFunctions = - contractsState[selectedContract?.address].writeState + contractsState[selectedContract?.address].writeState const newWriteFns = writeFunctions.map((rf) => { if (rf.name === funcName) { return { @@ -406,23 +413,25 @@ const Interaction: React.FC = (props) => { }) setWriteState(newWriteFns) break + } } } - const propogateInputToState = async ( + const propogateInputToState = ( type: 'view' | 'external', funcName: string, inputName: string, newValue?: string - ) => { + ): void => { if (selectedContract == null) { console.error('No Contract Selected!!') return } switch (type) { case 'view': + { const readFunctions = - contractsState[selectedContract?.address].readState + contractsState[selectedContract?.address].readState const newReadFns = readFunctions.map((rf) => { if (rf.name === funcName) { const oldInputIdx = rf.inputs.findIndex((i) => i.name === inputName) @@ -446,9 +455,11 @@ const Interaction: React.FC = (props) => { }) setReadState(newReadFns) break + } case 'external': + { const writeFunctions = - contractsState[selectedContract?.address].writeState + contractsState[selectedContract?.address].writeState const newWriteFns = writeFunctions.map((rf) => { if (rf.name === funcName) { const oldInputIdx = rf.inputs.findIndex((i) => i.name === inputName) @@ -472,43 +483,48 @@ const Interaction: React.FC = (props) => { }) setWriteState(newWriteFns) break + } } } - const makeCallDataAndHandleCall = async ( + const makeCallDataAndHandleCall = ( finalIPs: any, type: 'view' | 'external', funcName: string - ) => { + ): void => { if (selectedContract == null) { console.error('No Contract Selected!!') return } switch (type) { case 'view': + { const readFunctions = - contractsState[selectedContract?.address].readState + contractsState[selectedContract?.address].readState const calledReadFn = readFunctions.find((rf) => rf.name === funcName) if (calledReadFn != null) { const transformedCallData = makeCallDatafromInput( calledReadFn.inputs, finalIPs ) - await handleCall(funcName, 'view', transformedCallData) + handleCall(funcName, 'view', transformedCallData).catch(e => { console.error(e) }) } break + } case 'external': + { const writeFunctions = - contractsState[selectedContract?.address].writeState + contractsState[selectedContract?.address].writeState const calledWriteFn = writeFunctions.find((rf) => rf.name === funcName) if (calledWriteFn != null) { const transformedCallData = makeCallDatafromInput( calledWriteFn.inputs, finalIPs ) - await handleCall(funcName, 'external', transformedCallData) + handleCall(funcName, 'external', transformedCallData).catch(e => { console.error(e) }) } break + } } } @@ -524,29 +540,29 @@ const Interaction: React.FC = (props) => { }) props.setInteractionStatus('loading') try { - if (selectedContract == null) { + if (selectedContract === null) { console.error('No Contract Selected!!') return } + if (account === null || account === undefined) { + console.error('No account selected.') + return + } + if (type === 'view') { const callFunction = getCall( selectedContract.address, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain name, (callData?.flat() as BigNumberish[]) ?? [] ) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain - const response = await callFunction(account!) + const response = await callFunction(account) writeResponse(response, name, type) setResponses((responses) => [ ...responses, { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain functionName: name, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain contractName: selectedContract?.name, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain contractAddress: selectedContract?.address, callResponse: response } @@ -558,14 +574,11 @@ const Interaction: React.FC = (props) => { }) } else { const invocation = getInvocation( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain selectedContract?.address, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain name, (callData?.flat() as BigNumberish[]) ?? [] ) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain - const response = await invocation(account!) + const response = await invocation(account) const resultOfTx = await provider?.waitForTransaction( response.transaction_hash ) @@ -588,11 +601,8 @@ const Interaction: React.FC = (props) => { setResponses((responses) => [ ...responses, { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain functionName: name, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain contractName: selectedContract?.name, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain contractAddress: selectedContract?.address, invocationResponse: resultOfTx } @@ -619,166 +629,32 @@ const Interaction: React.FC = (props) => { }) } } + const isContractSelected = contracts.length > 0 && selectedContract != null && selectedContract !== undefined + const isAccountAndContractValid = isContractSelected && account != null && selectedContract.deployedInfo.some( + (info) => info.address === account.address && info.chainId === chainId + ) return ( - {contracts.length > 0 && selectedContract != null ? ( - - ) : ( -
    -

    No compiled contracts to interact with... Yet.

    -
    - )} - - { (contracts.length > 0 && selectedContract != null) && ((account != null && - selectedContract != null) && - selectedContract.deployedInfo.some( - (info) => info.address === account.address && info.chainId === chainId - ) ? ( - // eslint-disable-next-line multiline-ternary - <> -
    - {selectedContract && - contractsState[selectedContract.address]?.readState?.map( - (func, _index) => { - const init: any = func.inputs.reduce((p, c) => { - return { - ...p, - // Check if already has a rawInput in storage - [c.name]: c.rawInput ?? '' - } - }, {}) - - const validationSchema = func.inputs.reduce((p, c) => { - return { - ...p, - [c.name]: Yup.string() - .required(`${c.name} is required.`) - // @ts-expect-error because validate_ip is not a function of Yup - .validate_ip(c.type) - } - }, {}) - - return ( -
    - { - makeCallDataAndHandleCall( - finalState, - 'view', - func?.name - ).catch((e) => { - console.error(e) - }) - setSubmitting(false) - }} - validationSchema={Yup.object().shape({ - ...validationSchema - })} - > - {(props) => { - const { - values, - touched, - errors, - isSubmitting, - handleChange, - handleBlur, - handleSubmit, - handleReset - } = props - - // console.log(values, errors) - return ( -
    -
    - - -
    -
    - {func.inputs.length > 0 && - func.inputs.map((input, _index) => { - return ( -
    -
    - {errors[input.name] && - touched[input.name] && ( -
    - {(errors as any)[input?.name]} -
    - )} -
    - { - handleChange(e) - // Propogate to rawInputs in storage. - propogateInputToState( - 'view', - func.name, - input.name, - e.target.value - ).catch((e) => { - console.error(e) - }) - }} - /> -
    - ) - })} -
    -
    - ) - }} -
    -
    - ) - } - )} + {contracts.length > 0 && selectedContract != null + ? ( + + ) + : ( +
    +

    No compiled contracts to interact with... Yet.

    - {selectedContract && - contractsState[selectedContract.address]?.writeState?.map( + )} + + {isContractSelected && isAccountAndContractValid + ? <> +
    + {contractsState[selectedContract.address]?.readState?.map( (func, index) => { - const init: any = func.inputs.reduce((p, c) => { + const init: Record = func.inputs.reduce((p, c) => { return { ...p, + // Check if already has a rawInput in storage [c.name]: c.rawInput ?? '' } }, {}) @@ -792,133 +668,244 @@ const Interaction: React.FC = (props) => { .validate_ip(c.type) } }, {}) + return ( - <> -
    - { - // console.log( - // finalState, - // 'this conforms to init state' - // ) - makeCallDataAndHandleCall( - finalState, - 'external', - func?.name - ).catch((e) => { - console.error(e) - }) - setSubmitting(false) - }} - validationSchema={Yup.object().shape({ - ...validationSchema - })} - > - {(props) => { - const { - values, - touched, - errors, - isSubmitting, - handleChange, - handleBlur, - handleSubmit, - handleReset - } = props - - // console.log(values, errors) - return ( -
    -
    - - -
    -
    - {func.inputs.length > 0 && - func.inputs.map((input, index) => { - return ( -
    -
    - {errors[input.name] && - touched[input.name] && ( -
    - {(errors as any)[input?.name]} -
    - )} -
    - { - handleChange(e) - // Propogate to rawInputs in storage. - propogateInputToState( - 'external', - func.name, - input.name, - e.target.value - ).catch((e) => { - console.error(e) - }) - }} - key={index} - /> +
    + { + makeCallDataAndHandleCall( + finalState, + 'view', + func?.name + ) + setSubmitting(false) + }} + validationSchema={Yup.object().shape({ + ...validationSchema + })} + > + {(props) => { + const { + values, + touched, + errors, + isSubmitting, + handleChange, + handleBlur, + handleSubmit, + handleReset + } = props + + // console.log(values, errors) + return ( + +
    + + +
    +
    + {func.inputs.length > 0 && + func.inputs.map((input, index) => { + const hasErrors = (!isEmpty(errors[input.name]) && touched[input.name]) ?? false + return ( +
    +
    + {hasErrors && ( +
    + {(errors as any)[input?.name]} +
    + )}
    - ) - })} -
    - - ) - }} - -
    - + { + handleChange(e) + // Propogate to rawInputs in storage. + propogateInputToState( + 'view', + func.name, + input.name, + e.target.value + ) + }} + /> +
    + ) + })} +
    + + ) + }} + +
    ) } )} +
    + {contractsState[selectedContract.address]?.writeState?.map( + (func, index) => { + const init: Record = func.inputs.reduce((p, c) => { + return { + ...p, + [c.name]: c.rawInput ?? '' + } + }, {}) + + const validationSchema = func.inputs.reduce((p, c) => { + return { + ...p, + [c.name]: Yup.string() + .required(`${c.name} is required.`) + // @ts-expect-error because validate_ip is not a function of Yup + .validate_ip(c.type) + } + }, {}) + + return ( + <> +
    + { + makeCallDataAndHandleCall( + finalState, + 'external', + func?.name + ) + setSubmitting(false) + }} + validationSchema={Yup.object().shape({ + ...validationSchema + })} + > + {(props: FormikProps>) => { + const { + values, + touched, + errors, + isSubmitting, + handleChange, + handleBlur, + handleSubmit, + handleReset + } = props + + // console.log(values, errors) + return ( +
    +
    + + +
    +
    + {func.inputs.length > 0 && + func.inputs.map((input, index) => { + const hasErrors = (errors[input.name] != null && touched[input.name] != null) ?? false + return ( +
    +
    + {hasErrors && ( +
    + {(errors as any)[input?.name]} +
    + )} +
    + { + handleChange(e) + // Propogate to rawInputs in storage. + propogateInputToState( + 'external', + func.name, + input.name, + e.target.value + ) + }} + key={index} + /> +
    + ) + })} +
    +
    + ) + }} +
    +
    + + ) + } + )} - ) : ( -

    Selected contract is not deployed yet...

    - ))} + :

    Selected contract is not deployed yet...

    + } ) } diff --git a/plugin/src/features/Plugin/index.tsx b/plugin/src/features/Plugin/index.tsx index 1ce4c057..199a519a 100644 --- a/plugin/src/features/Plugin/index.tsx +++ b/plugin/src/features/Plugin/index.tsx @@ -1,19 +1,8 @@ import React, { useState } from 'react' -import { CompiledContractsContext } from '../../contexts/CompiledContractsContext' -import { ConnectionContext } from '../../contexts/ConnectionContext' -import { - type CallDataObject, - type Input, - type Contract -} from '../../types/contracts' + import { Environment } from '../Environment' import './styles.css' -import { - type Account, - type AccountInterface, - type Provider, - type ProviderInterface -} from 'starknet' + import Compilation from '../Compilation' import Deployment from '../Deployment' import Interaction from '../Interaction' @@ -21,25 +10,17 @@ import Accordian, { AccordianItem, AccordionContent, AccordionTrigger -} from '../../ui_components/Accordian' +} from '../../components/ui_components/Accordian' import TransactionHistory from '../TransactionHistory' import CairoVersion from '../CairoVersion' -import CompilationContext from '../../contexts/CompilationContext' -import DeploymentContext from '../../contexts/DeploymentContext' -import { type Devnet, devnets, type DevnetAccount } from '../../utils/network' -import { type StarknetWindowObject } from 'get-starknet' -import EnvironmentContext from '../../contexts/EnvironmentContext' -import ManualAccountContext from '../../contexts/ManualAccountContext' -import { type Transaction } from '../../types/transaction' -import TransactionContext from '../../contexts/TransactionContext' import StateAction from '../../components/StateAction' -import type { ManualAccount } from '../../types/accounts' -import { networks } from '../../utils/constants' import BackgroundNotices from '../../components/BackgroundNotices' import ExplorerSelector, { useCurrentExplorer } from '../../components/ExplorerSelector' -import CairoVersionContext from '../../contexts/CairoVersion' +import { useAtomValue } from 'jotai' +import { isCompilingAtom } from '../../atoms/compilation' +import { deploymentAtom } from '../../atoms/deployment' export type AccordianTabs = | 'compile' | 'deploy' @@ -48,59 +29,12 @@ export type AccordianTabs = | '' const Plugin: React.FC = () => { - // Compilation Context state variables - const [status, setStatus] = useState('Compiling...') - const [currentFilename, setCurrentFilename] = useState('') - const [isCompiling, setIsCompiling] = useState(false) - const [isValidCairo, setIsValidCairo] = useState(false) - const [noFileSelected, setNoFileSelected] = useState(false) - const [hashDir, setHashDir] = useState('') - const [tomlPaths, setTomlPaths] = useState([]) - const [activeTomlPath, setActiveTomlPath] = useState('') - - // Deployment Context state variables - const [isDeploying, setIsDeploying] = useState(false) - const [deployStatus, setDeployStatus] = useState('') - const [constructorCalldata, setConstructorCalldata] = - useState({}) - const [constructorInputs, setConstructorInputs] = useState([]) - const [notEnoughInputs, setNotEnoughInputs] = useState(false) - - // Environment Context state variables - const [devnet, setDevnet] = useState(devnets[1]) - const [env, setEnv] = useState('remoteDevnet') - const [isDevnetAlive, setIsDevnetAlive] = useState(true) - const [starknetWindowObject, setStarknetWindowObject] = - useState(null) - const [selectedDevnetAccount, setSelectedDevnetAccount] = - useState(null) - const [availableDevnetAccounts, setAvailableDevnetAccounts] = useState< - DevnetAccount[] - >([]) - - // Manual Account Context state variables - const [accounts, setAccounts] = useState([]) - const [selectedAccount, setSelectedAccount] = useState( - null - ) - const [networkName, setNetworkName] = useState(networks[0].value) - - // Transaction History Context state variables - const [transactions, setTransactions] = useState([]) + const isCompiling = useAtomValue(isCompilingAtom) - // Compilation Context state variables - const [compiledContracts, setCompiledContracts] = useState([]) - const [selectedContract, setSelectedContract] = useState( - null - ) - - // Connection Context state variables - const [provider, setProvider] = useState( - null - ) - const [account, setAccount] = useState( - null - ) + const { + isDeploying, + deployStatus + } = useAtomValue(deploymentAtom) // Interaction state variables const [interactionStatus, setInteractionStatus] = useState<'loading' | 'success' | 'error' | ''>('') @@ -108,8 +42,6 @@ const Plugin: React.FC = () => { const [currentAccordian, setCurrentAccordian] = useState('compile') - const [cairoVersion, setCairoVersion] = useState('version is loading...') - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const handleTabView = (clicked: AccordianTabs) => { if (currentAccordian === clicked) { @@ -122,216 +54,121 @@ const Plugin: React.FC = () => { const explorerHook = useCurrentExplorer() return ( - // add a button for selecting the cairo version <>
    - - + + - - - -
    - - - - - { - handleTabView('compile') - }} - > - -

    Compile

    - -
    -
    - - - -
    -
    - - - { - handleTabView('deploy') - }} - > - -

    Deploy

    - -
    -
    - - - -
    - - { - handleTabView('interaction') - }} - > - -

    Interact

    - -
    -
    - - - -
    -
    - - { - handleTabView('transactions') - }} - > - -

    Transactions

    - -
    -
    - - - -
    -
    -
    - -
    -
    -
    - - - -
    -
    -
    -
    -
    -
    + + + { + handleTabView('compile') + }} + > + +

    Compile

    + +
    +
    + + + +
    + + + { + handleTabView('deploy') + }} + > + +

    Deploy

    + +
    +
    + + + +
    + + { + handleTabView('interaction') + }} + > + +

    Interact

    + +
    +
    + + + +
    + + { + handleTabView('transactions') + }} + > + +

    Transactions

    + +
    +
    + + + +
    + +
    + +
    +
    +
    + +
    ) diff --git a/plugin/src/features/TransactionHistory/TransactionCard.tsx b/plugin/src/features/TransactionHistory/TransactionCard.tsx index c8df79b0..4d49e971 100644 --- a/plugin/src/features/TransactionHistory/TransactionCard.tsx +++ b/plugin/src/features/TransactionHistory/TransactionCard.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef } from 'react' -import { type Transaction } from '../../types/transaction' +import { type Transaction } from '../../utils/types/transaction' import './transactioncard.css' import { type Network, networkEquivalentsRev, type networkExplorerUrls } from '../../utils/constants' import { getExplorerUrl } from '../../utils/utils' diff --git a/plugin/src/features/TransactionHistory/index.tsx b/plugin/src/features/TransactionHistory/index.tsx index d48187c4..2a6b6427 100644 --- a/plugin/src/features/TransactionHistory/index.tsx +++ b/plugin/src/features/TransactionHistory/index.tsx @@ -1,11 +1,12 @@ -import React, { useContext } from 'react' -import Container from '../../ui_components/Container' -import TransactionContext from '../../contexts/TransactionContext' +import React from 'react' +import Container from '../../components/ui_components/Container' import TransactionCard from './TransactionCard' import { type IExplorerSelector } from '../../utils/misc' +import { useAtomValue } from 'jotai' +import transactionsAtom from '../../atoms/transactions' const TransactionHistory: React.FC = (props) => { - const { transactions } = useContext(TransactionContext) + const transactions = useAtomValue(transactionsAtom) return (
    diff --git a/plugin/src/hooks/starknetWindow.ts b/plugin/src/hooks/starknetWindow.ts new file mode 100644 index 00000000..93991b2c --- /dev/null +++ b/plugin/src/hooks/starknetWindow.ts @@ -0,0 +1,118 @@ +import { + type StarknetWindowObject, + type ConnectOptions, + type DisconnectOptions, + connect, + disconnect +} from 'get-starknet' +import { useEffect, useState } from 'react' +import useAccount from './useAccount' +import useProvider from './useProvider' +import useRemixClient from './useRemixClient' + +const useStarknetWindow = (): { + starknetWindowObject: StarknetWindowObject | null + setStarknetWindowObject: React.Dispatch> + currentChainId: string | undefined + connectWalletHandler: (options?: ConnectOptions) => Promise + disconnectWalletHandler: (options?: DisconnectOptions) => Promise + refreshWalletConnection: () => Promise +} => { + const { remixClient } = useRemixClient() + const { setAccount } = useAccount() + const { setProvider } = useProvider() + + const [starknetWindowObject, setStarknetWindowObject] = useState(null) + const [currentChainId, setCurrentChainId] = useState(undefined) + const getChainId = async (): Promise => { + if (starknetWindowObject != null) { + const value: string | undefined = await starknetWindowObject?.provider?.getChainId() + setCurrentChainId(value) + } + } + + useEffect((): void => { + if (starknetWindowObject != null) { + getChainId().catch(e => { + console.error(e) + }) + } + }, [starknetWindowObject]) + + const connectWalletHandler = async ( + options: ConnectOptions = { + modalMode: 'alwaysAsk', + modalTheme: 'dark' + } + ): Promise => { + try { + const connectedStarknetWindowObject = await connect(options) + if (connectedStarknetWindowObject == null) { + throw new Error('Failed to connect to wallet') + } + await connectedStarknetWindowObject.enable({ starknetVersion: 'v5' }) + connectedStarknetWindowObject.on( + 'accountsChanged', + (accounts: string[]) => { + console.log('accountsChanged', accounts) + void connectWalletHandler({ + modalMode: 'neverAsk', + modalTheme: 'dark' + }) + } + ) + + connectedStarknetWindowObject.on('networkChanged', (network?: string) => { + console.log('networkChanged', network) + void connectWalletHandler({ + modalMode: 'neverAsk', + modalTheme: 'dark' + }) + }) + setStarknetWindowObject(connectedStarknetWindowObject) + if (connectedStarknetWindowObject.account != null) { + setAccount(connectedStarknetWindowObject.account) + } + if (connectedStarknetWindowObject.provider != null) { + setProvider(connectedStarknetWindowObject.provider) + } + } catch (e) { + if (e instanceof Error) { + await remixClient.call('notification' as any, 'alert', e) + } + setStarknetWindowObject(null) + console.log(e) + } + } + + const disconnectWalletHandler = async ( + options: DisconnectOptions = { + clearLastWallet: true + } + ): Promise => { + if (starknetWindowObject != null) { + starknetWindowObject.off('accountsChanged', (_accounts: string[]) => { }) + starknetWindowObject.off('networkChanged', (_network?: string) => { }) + } + await disconnect(options) + setStarknetWindowObject(null) + setAccount(null) + setProvider(null) + } + + const refreshWalletConnection = async (): Promise => { + if (starknetWindowObject !== null) await disconnectWalletHandler() + await connectWalletHandler() + } + + return { + starknetWindowObject, + currentChainId, + setStarknetWindowObject, + connectWalletHandler, + disconnectWalletHandler, + refreshWalletConnection + } +} + +export default useStarknetWindow diff --git a/plugin/src/hooks/useAccount.ts b/plugin/src/hooks/useAccount.ts new file mode 100644 index 00000000..b5082078 --- /dev/null +++ b/plugin/src/hooks/useAccount.ts @@ -0,0 +1,16 @@ +import type React from 'react' +import { useState } from 'react' +import { type Account, type AccountInterface } from 'starknet' + +const useAccount = (): { + account: Account | AccountInterface | null + setAccount: React.Dispatch> +} => { + const [account, setAccount] = useState( + null + ) + + return { account, setAccount } +} + +export default useAccount diff --git a/plugin/src/hooks/useProvider.ts b/plugin/src/hooks/useProvider.ts new file mode 100644 index 00000000..d2909a26 --- /dev/null +++ b/plugin/src/hooks/useProvider.ts @@ -0,0 +1,15 @@ +import type React from 'react' +import { useState } from 'react' +import { type Provider, type ProviderInterface } from 'starknet' + +const useProvider = (): { + provider: Provider | ProviderInterface | null + setProvider: React.Dispatch> +} => { + const [provider, setProvider] = useState( + null + ) + return { provider, setProvider } +} + +export default useProvider diff --git a/plugin/src/hooks/useRemixClient.ts b/plugin/src/hooks/useRemixClient.ts new file mode 100644 index 00000000..e70a013b --- /dev/null +++ b/plugin/src/hooks/useRemixClient.ts @@ -0,0 +1,89 @@ +import { PluginClient } from '@remixproject/plugin' +import { createClient } from '@remixproject/plugin-webview' +import { useEffect, useState } from 'react' +import { fetchGitHubFilesRecursively } from '../utils/initial_scarb_codes' + +const remixClient = createClient(new PluginClient()) + +const useRemixClient = (): { + remixClient: typeof remixClient + isPluginLoaded: boolean +} => { + const [pluginLoaded, setPluginLoaded] = useState(false) + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + const id = setTimeout(async (): Promise => { + await remixClient.onload(() => { + setPluginLoaded(true) + // eslint-disable-next-line @typescript-eslint/no-misused-promises + setTimeout(async () => { + const workspaces = await remixClient.filePanel.getWorkspaces() + + const workspaceLets: Array<{ name: string, isGitRepo: boolean }> = + JSON.parse(JSON.stringify(workspaces)) + + if ( + !workspaceLets.some( + (workspaceLet) => workspaceLet.name === 'cairo_scarb_sample' + ) + ) { + await remixClient.filePanel.createWorkspace( + 'cairo_scarb_sample', + true + ) + try { + await remixClient.fileManager.mkdir('hello_world') + } catch (e) { + console.log(e) + } + const exampleRepo = await fetchGitHubFilesRecursively( + 'software-mansion/scarb', + 'examples/starknet_multiple_contracts' + ) + + try { + for (const file of exampleRepo) { + const filePath = file?.path + .replace('examples/starknet_multiple_contracts/', '') + .replace('examples/starknet_multiple_contracts', '') ?? '' + + let fileContent: string = file?.content ?? '' + + if (file != null && file.fileName === 'Scarb.toml') { + fileContent = fileContent.concat('\ncasm = true') + } + + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + await remixClient.fileManager.writeFile( + `hello_world/${ + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + filePath + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + }/${file?.fileName}`, + fileContent + ) + } + } catch (e) { + if (e instanceof Error) { + await remixClient.call('notification' as any, 'alert', { + id: 'starknetRemixPluginAlert', + title: 'Please check the write file permission', + message: e.message + '\n' + 'Did you provide the write file permission?' + }) + } + console.log(e) + } + } + }) + }) + }, 1) + return () => { + clearInterval(id) + } + }, []) + + return { remixClient, isPluginLoaded: pluginLoaded } +} + +export default useRemixClient diff --git a/plugin/src/index.tsx b/plugin/src/index.tsx index 2fc909f7..af038846 100644 --- a/plugin/src/index.tsx +++ b/plugin/src/index.tsx @@ -4,11 +4,11 @@ import './index.css' import App from './App' import reportWebVitals from './reportWebVitals' -const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) +const root = ReactDOM.createRoot(document.getElementById('root') as Element) root.render( - // + - // + ) // If you want to start measuring performance in your app, pass a function diff --git a/plugin/src/utils/async_fetch.ts b/plugin/src/utils/async_fetch.ts index b4c48224..7581936d 100644 --- a/plugin/src/utils/async_fetch.ts +++ b/plugin/src/utils/async_fetch.ts @@ -1,6 +1,6 @@ import { apiUrl } from './network' -export async function asyncFetch (method: string, getterMethod: string) { +export async function asyncFetch (method: string, getterMethod: string): Promise { const response = await fetch(`${apiUrl}/${method}`, { method: 'GET', redirect: 'follow', @@ -24,7 +24,7 @@ export async function asyncFetch (method: string, getterMethod: string) { return await response.text() } catch (e) { - throw new Error(`Error while running process with id ${pid}, error: ${e}`) + throw new Error(`Error while running process with id ${pid}, error: ${e as string}`) } } diff --git a/plugin/src/utils/constants.ts b/plugin/src/utils/constants.ts index 4c2075ca..d85b17ce 100644 --- a/plugin/src/utils/constants.ts +++ b/plugin/src/utils/constants.ts @@ -4,49 +4,41 @@ const devnetUrl = 'http://127.0.0.1:5050' type Network = | 'goerli-alpha' - | 'goerli-alpha-2' | 'mainnet-alpha' const networks = [ { name: 'Testnet', value: 'goerli-alpha' }, - { name: 'Testnet 2', value: 'goerli-alpha-2' }, { name: 'Mainnet', value: 'mainnet-alpha' } ] const networkExplorerUrls = { voyager: { 'goerli-alpha': 'https://goerli.voyager.online', - 'goerli-alpha-2': 'https://goerli-2.voyager.online', 'mainnet-alpha': 'https://voyager.online' }, starkscan: { 'goerli-alpha': 'https://testnet.starkscan.co', - 'goerli-alpha-2': 'https://testnet-2.starkscan.co', 'mainnet-alpha': 'https://starkscan.co' } } const networkEquivalents = new Map([ ['goerli-alpha', constants.StarknetChainId.SN_GOERLI], - ['goerli-alpha-2', constants.StarknetChainId.SN_GOERLI2], ['mainnet-alpha', constants.StarknetChainId.SN_MAIN] ]) const networkEquivalentsRev = new Map([ [constants.StarknetChainId.SN_GOERLI, 'goerli-alpha'], - [constants.StarknetChainId.SN_GOERLI2, 'goerli-alpha-2'], [constants.StarknetChainId.SN_MAIN, 'mainnet-alpha'] ]) const networkNameEquivalents = new Map([ ['goerli-alpha', constants.NetworkName.SN_GOERLI], - ['goerli-alpha-2', constants.NetworkName.SN_GOERLI2], ['mainnet-alpha', constants.NetworkName.SN_MAIN] ]) const networkNameEquivalentsRev = new Map([ [constants.NetworkName.SN_GOERLI, 'goerli-alpha'], - [constants.NetworkName.SN_GOERLI2, 'goerli-alpha-2'], [constants.NetworkName.SN_MAIN, 'mainnet-alpha'] ]) diff --git a/plugin/src/utils/misc.ts b/plugin/src/utils/misc.ts index 6d6a7e4e..69bb38fa 100644 --- a/plugin/src/utils/misc.ts +++ b/plugin/src/utils/misc.ts @@ -4,7 +4,7 @@ import { Provider } from 'starknet' import { devnetUrl } from './constants' -const getProvider = (network: string) => { +const getProvider = (network: string): Provider => { switch (network) { case 'mainnet-alpha': return new Provider({ @@ -46,3 +46,9 @@ interface IUseCurrentExplorer { } export { getProvider, type IExplorerSelector, type IUseCurrentExplorer } + +function isEmpty (str: string | undefined): boolean { + return str === '' || str === null || str === undefined +} + +export { isEmpty } diff --git a/plugin/src/utils/network.ts b/plugin/src/utils/network.ts index f1e02dc1..54b5eaf4 100644 --- a/plugin/src/utils/network.ts +++ b/plugin/src/utils/network.ts @@ -1,8 +1,10 @@ -import { type DevnetAccount } from '../types/accounts' +import { type DevnetAccount } from './types/accounts' -const apiUrl = import.meta.env.VITE_API_URL ?? 'cairo-compile-remix-test.nethermind.io' -const devnetUrl = import.meta.env.VITE_DEVNET_URL ?? 'http://localhost:5050' -const remoteDevnetUrl = import.meta.env.VITE_REMOTE_DEVNET_URL ?? 'https://starknet-devnet-dev.nethermind.io' +const apiUrl: string = import.meta.env.VITE_API_URL ?? 'cairo-compile-remix-test.nethermind.io' +const devnetUrl: string = import.meta.env.VITE_DEVNET_URL ?? 'http://localhost:5050' +const remoteDevnetUrl: string = import.meta.env.VITE_REMOTE_DEVNET_URL ?? 'https://starknet-devnet-dev.nethermind.io' + +console.log(apiUrl) interface Devnet { name: string diff --git a/plugin/src/utils/starknet.ts b/plugin/src/utils/starknet.ts index 5ccaa362..91b1d16e 100644 --- a/plugin/src/utils/starknet.ts +++ b/plugin/src/utils/starknet.ts @@ -1,11 +1,10 @@ import { BigNumber } from 'ethers' -import { type ParameterMetadata, ParameterType } from '../types/contracts' +import { type ParameterMetadata, ParameterType } from './types/contracts' export enum StarknetChainId { SN_MAIN = '0x534e5f4d41494e', - SN_GOERLI = '0x534e5f474f45524c49', - SN_GOERLI2 = '0x534e5f474f45524c4932', + SN_GOERLI = '0x534e5f474f45524c49' } export function normalizeParam ( diff --git a/plugin/src/utils/storage.ts b/plugin/src/utils/storage.ts index 048de060..5a1444bd 100644 --- a/plugin/src/utils/storage.ts +++ b/plugin/src/utils/storage.ts @@ -4,11 +4,9 @@ const storage = { if (item != null) return JSON.parse(item) return undefined }, - set: (key: string, value: any): void => { localStorage.setItem(key, JSON.stringify(value)) }, - remove: (key: string): void => { localStorage.removeItem(key) } diff --git a/plugin/src/types/accounts.ts b/plugin/src/utils/types/accounts.ts similarity index 100% rename from plugin/src/types/accounts.ts rename to plugin/src/utils/types/accounts.ts diff --git a/plugin/src/types/contracts.ts b/plugin/src/utils/types/contracts.ts similarity index 100% rename from plugin/src/types/contracts.ts rename to plugin/src/utils/types/contracts.ts diff --git a/plugin/src/types/transaction.ts b/plugin/src/utils/types/transaction.ts similarity index 100% rename from plugin/src/types/transaction.ts rename to plugin/src/utils/types/transaction.ts diff --git a/plugin/src/utils/utils.ts b/plugin/src/utils/utils.ts index b8f53a23..b85f994c 100644 --- a/plugin/src/utils/utils.ts +++ b/plugin/src/utils/utils.ts @@ -1,9 +1,9 @@ -import { type DevnetAccount } from '../types/accounts' -import { type AbiElement, type Abi, type Contract } from '../types/contracts' +import { type DevnetAccount } from './types/accounts' +import { type AbiElement, type Abi, type Contract } from './types/contracts' import { type Network, networkExplorerUrls } from './constants' function isValidCairo (filename: string): boolean { - return filename.endsWith('.cairo') + return filename?.endsWith('.cairo') ?? false } const getFileExtension = (filename: string): string => @@ -73,7 +73,6 @@ const getReadFunctions = (abi: Abi): AbiElement[] => { } const getWriteFunctions = (abi: Abi): AbiElement[] => { - console.log('abi', abi) const writeFunctions = abi.filter( (item) => item.type === 'function' && @@ -96,10 +95,10 @@ const getWriteFunctions = (abi: Abi): AbiElement[] => { return writeFunctions } -const getParameterType = (parameter: string): string | undefined => { +const getParameterType = (parameter: string): string => { const type = parameter.split('::').pop() if (type === 'u256') return 'u256 (low, high)' - return type + return type ?? '' } const getSelectedContractIndex = ( @@ -137,11 +136,12 @@ const weiToEth = (wei: number): number => { const getExplorerUrl = (explorer: keyof typeof networkExplorerUrls, chain: Network): string => networkExplorerUrls[explorer][chain] const trimStr = (str?: string, strip?: number): string => { - if (!str) { + if (str === undefined || str === null) { return '' } + const lStrip = strip ?? 6 const length = str.length - return `${str?.slice(0, strip || 6)}...${str?.slice(length - (strip || 6))}` + return `${str?.slice(0, lStrip)}...${str?.slice(length - lStrip)}` } export { diff --git a/plugin/tsconfig.json b/plugin/tsconfig.json index 4907582a..a6d098a8 100644 --- a/plugin/tsconfig.json +++ b/plugin/tsconfig.json @@ -15,7 +15,7 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "typeRoots": ["src/types"] + "typeRoots": ["src/utils/types"] }, "include": ["src"] } diff --git a/plugin/vite.config.ts b/plugin/vite.config.ts index ee4c62ba..654d52a3 100644 --- a/plugin/vite.config.ts +++ b/plugin/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import svgr from 'vite-plugin-svgr' +import checker from 'vite-plugin-checker'; export default defineConfig(() => { return { @@ -14,6 +15,15 @@ export default defineConfig(() => { build: { outDir: 'build' }, - plugins: [react(), svgr({ svgrOptions: { icon: true } })] + plugins: [ + react(), + svgr({ svgrOptions: { icon: true } }), + checker({ + typescript: true, + eslint: { + lintCommand: 'eslint "./src/**/*.{ts,tsx}"', // for example, lint .ts & .tsx + }, + }) + ] } })