├── .gitignore ├── README.md ├── packages ├── abi-to-sol │ ├── .gitignore │ ├── jest.config.js │ ├── src │ │ ├── index.ts │ │ ├── defaults.ts │ │ ├── options.ts │ │ ├── solidity.test.ts │ │ ├── version-features.ts │ │ ├── visitor.ts │ │ ├── abi-features.ts │ │ ├── declarations.ts │ │ ├── declarations.test.ts │ │ └── solidity.ts │ ├── test │ │ ├── compile-abi.ts │ │ ├── preflight.ts │ │ └── custom-example.ts │ ├── package.json │ ├── bin │ │ └── abi-to-sol.ts │ ├── README.md │ └── tsconfig.json └── web-ui │ ├── src │ ├── react-app-env.d.ts │ ├── highlightjs-solidity.d.ts │ ├── prettier-plugin-solidity.d.ts │ ├── abi │ │ ├── index.ts │ │ ├── Header.tsx │ │ ├── Editor.tsx │ │ ├── Status.tsx │ │ ├── examples │ │ │ ├── DepositContract.abi.json │ │ │ ├── BunchaStructs.abi.json │ │ │ ├── AirSwap.abi.json │ │ │ ├── ENS.abi.json │ │ │ └── UniswapV2Router02.abi.json │ │ ├── Input.ts │ │ └── Examples.ts │ ├── solidity │ │ ├── index.ts │ │ ├── Header.tsx │ │ ├── Options.ts │ │ ├── Status.tsx │ │ ├── Code.tsx │ │ ├── Controls.tsx │ │ ├── OptionsControls.tsx │ │ └── Output.ts │ ├── setupTests.ts │ ├── reportWebVitals.ts │ ├── index.tsx │ ├── Layout.tsx │ ├── CopyButton.tsx │ ├── makeSet.ts │ ├── Footer.tsx │ ├── logo.svg │ ├── App.tsx │ └── defaultAbi.json │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html │ ├── config │ ├── jest │ │ ├── cssTransform.js │ │ ├── babelTransform.js │ │ └── fileTransform.js │ ├── pnpTs.js │ ├── getHttpsConfig.js │ ├── paths.js │ ├── modules.js │ ├── env.js │ └── webpackDevServer.config.js │ ├── .gitignore │ ├── tsconfig.json │ ├── scripts │ ├── test.js │ ├── start.js │ └── build.js │ ├── README.md │ └── package.json ├── lerna.json ├── package.json ├── .github └── workflows │ ├── changelog.yml │ ├── node.js.yml │ ├── web-ui.yml │ └── codeql-analysis.yml ├── LICENSE ├── RELEASE.md └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/abi-to-sol/README.md -------------------------------------------------------------------------------- /packages/abi-to-sol/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /packages/web-ui/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/web-ui/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/abi-to-sol/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | }; 5 | -------------------------------------------------------------------------------- /packages/web-ui/src/highlightjs-solidity.d.ts: -------------------------------------------------------------------------------- 1 | declare module "highlightjs-solidity" { 2 | export = {} as any; 3 | } 4 | -------------------------------------------------------------------------------- /packages/web-ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phonbopit/abi-to-sol/develop/packages/web-ui/public/favicon.ico -------------------------------------------------------------------------------- /packages/web-ui/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phonbopit/abi-to-sol/develop/packages/web-ui/public/logo192.png -------------------------------------------------------------------------------- /packages/web-ui/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phonbopit/abi-to-sol/develop/packages/web-ui/public/logo512.png -------------------------------------------------------------------------------- /packages/web-ui/src/prettier-plugin-solidity.d.ts: -------------------------------------------------------------------------------- 1 | declare module "prettier-plugin-solidity" { 2 | export = {} as any; 3 | } 4 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "independent", 6 | "npmClient": "yarn", 7 | "useWorkspaces": true 8 | } 9 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/index.ts: -------------------------------------------------------------------------------- 1 | export { Header } from "./Header"; 2 | export * as Input from "./Input"; 3 | export { Editor } from "./Editor"; 4 | export * as Examples from "./Examples"; 5 | -------------------------------------------------------------------------------- /packages/abi-to-sol/src/index.ts: -------------------------------------------------------------------------------- 1 | import "source-map-support/register"; 2 | 3 | export * as defaults from "./defaults"; 4 | export {generateSolidity} from "./solidity"; 5 | export {GenerateSolidityOptions, GenerateSolidityMode} from "./options"; 6 | 7 | -------------------------------------------------------------------------------- /packages/web-ui/src/solidity/index.ts: -------------------------------------------------------------------------------- 1 | export { Header } from "./Header"; 2 | export { Controls } from "./Controls"; 3 | export { OptionsControls } from "./OptionsControls"; 4 | export { Code } from "./Code"; 5 | export * as Options from "./Options"; 6 | export * as Output from "./Output"; 7 | -------------------------------------------------------------------------------- /packages/web-ui/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom"; 6 | -------------------------------------------------------------------------------- /packages/abi-to-sol/src/defaults.ts: -------------------------------------------------------------------------------- 1 | import { GenerateSolidityMode } from "./options"; 2 | 3 | export const name = "MyInterface"; 4 | export const license = "UNLICENSED"; 5 | export const solidityVersion = ">=0.7.0 <0.9.0"; 6 | export const prettifyOutput = true; 7 | export const outputAttribution = true; 8 | export const outputSource = true; 9 | export const mode = GenerateSolidityMode.Normal; 10 | -------------------------------------------------------------------------------- /packages/web-ui/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "scripts": { 5 | "lernaupdate": "lernaupdate", 6 | "prepare": "lerna bootstrap", 7 | "test": "lerna run test --stream --concurrency=1 -- --colors" 8 | }, 9 | "devDependencies": { 10 | "lerna": "^4.0.0", 11 | "lerna-update-wizard": "^1.1.0" 12 | }, 13 | "workspaces": { 14 | "packages": [ 15 | "packages/*" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/web-ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { 4 | Box, 5 | HStack, 6 | Spacer, 7 | Heading, 8 | } from "@chakra-ui/react"; 9 | 10 | import { Status } from "./Status"; 11 | 12 | export const Header = () => { 13 | return ( 14 | 15 | ABI 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/web-ui/src/solidity/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { 4 | Box, 5 | HStack, 6 | Spacer, 7 | Heading, 8 | } from "@chakra-ui/react"; 9 | 10 | import { Status } from "./Status"; 11 | 12 | export const Header = () => { 13 | return ( 14 | 15 | Solidity 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: Changelog Enforcer 2 | 3 | on: 4 | pull_request: 5 | branches: [ develop ] 6 | 7 | jobs: 8 | # Enforces the update of a changelog file on every pull request 9 | changelog: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: dangoslen/changelog-enforcer@v2 14 | with: 15 | changeLogPath: 'CHANGELOG.md' 16 | skipLabels: 'skip-changelog' 17 | 18 | -------------------------------------------------------------------------------- /packages/web-ui/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from "web-vitals"; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /packages/abi-to-sol/src/options.ts: -------------------------------------------------------------------------------- 1 | import type * as Abi from "@truffle/abi-utils"; 2 | import type {Abi as SchemaAbi} from "@truffle/contract-schema/spec"; 3 | 4 | export enum GenerateSolidityMode { 5 | Normal = "normal", 6 | Embedded = "embedded" 7 | } 8 | 9 | export interface GenerateSolidityOptions { 10 | abi: Abi.Abi | SchemaAbi; 11 | name?: string; 12 | solidityVersion?: string; 13 | license?: string; 14 | mode?: GenerateSolidityMode; 15 | outputAttribution?: boolean; 16 | outputSource?: boolean; 17 | prettifyOutput?: boolean; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /packages/web-ui/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import reportWebVitals from './reportWebVitals'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | 13 | // If you want to start measuring performance in your app, pass a function 14 | // to log results (for example: reportWebVitals(console.log)) 15 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 16 | reportWebVitals(); 17 | -------------------------------------------------------------------------------- /packages/web-ui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/web-ui/src/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import{ 3 | Box, 4 | Flex, 5 | PropsOf, 6 | } from "@chakra-ui/react"; 7 | 8 | export const Page: React.FC> = ({ 9 | children, 10 | ...props 11 | }) => { 12 | return ( 13 | 14 | {children} 15 | 16 | ) 17 | } 18 | 19 | 20 | export const Container: React.FC> = ({ 21 | children, 22 | ...props 23 | }) => { 24 | return ( 25 | 31 | {children} 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/web-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/web-ui/config/jest/babelTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const babelJest = require('babel-jest'); 4 | 5 | const hasJsxRuntime = (() => { 6 | if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { 7 | return false; 8 | } 9 | 10 | try { 11 | require.resolve('react/jsx-runtime'); 12 | return true; 13 | } catch (e) { 14 | return false; 15 | } 16 | })(); 17 | 18 | module.exports = babelJest.createTransformer({ 19 | presets: [ 20 | [ 21 | require.resolve('babel-preset-react-app'), 22 | { 23 | runtime: hasJsxRuntime ? 'automatic' : 'classic', 24 | }, 25 | ], 26 | ], 27 | babelrc: false, 28 | configFile: false, 29 | }); 30 | -------------------------------------------------------------------------------- /packages/web-ui/config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /packages/web-ui/src/CopyButton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Text, Button, useClipboard } from "@chakra-ui/react"; 3 | import { CopyIcon } from "@chakra-ui/icons"; 4 | 5 | export interface CopyButtonOptions { 6 | text?: string; 7 | } 8 | 9 | export const CopyButton = ({ text }: CopyButtonOptions) => { 10 | let buttonText = "Copy to clipboard"; 11 | 12 | const { hasCopied, onCopy } = useClipboard(text || ""); 13 | 14 | if (!text) { 15 | buttonText = "No result"; 16 | } else if (hasCopied) { 17 | buttonText = 'Copied' 18 | } 19 | 20 | return ( 21 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master, develop ] 9 | pull_request: 10 | branches: [ master, develop ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [14.x, 16.x, 18.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm install -g yarn 28 | - run: yarn 29 | - run: yarn test 30 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/Editor.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box } from "@chakra-ui/react"; 3 | 4 | import SimpleEditor from "react-simple-code-editor"; 5 | 6 | // Highlight.js setup 7 | import "highlight.js/styles/default.css"; 8 | import hljs from "highlight.js"; 9 | 10 | import * as Input from "./Input"; 11 | 12 | export const Editor = () => { 13 | const { contents, setContents } = Input.Container.useContainer(); 14 | 15 | return ( 16 | 20 | hljs.highlight(contents, { language: "json" }).value} 24 | style={{ 25 | fontFamily: "SFMono-Regular,Menlo,Monaco,Consolas,monospace" 26 | 27 | }} 28 | /> 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/web-ui/src/makeSet.ts: -------------------------------------------------------------------------------- 1 | export interface ForStateOptions { 2 | state: State; 3 | setState(newState: State): void; 4 | } 5 | 6 | /** 7 | * Predicate that says whether a value should be interpreted as undefined 8 | */ 9 | export type IsEmpty = ( 10 | value: State[N] 11 | ) => boolean; 12 | 13 | export type MakeSet = ( 14 | propertyName: N, 15 | isEmpty?: IsEmpty 16 | ) => (newValue: State[N]) => void; 17 | 18 | export const forState = 19 | ({ state, setState }: ForStateOptions): MakeSet => 20 | (propertyName, isEmpty) => 21 | (value) => { 22 | const newState = { 23 | ...state, 24 | }; 25 | 26 | if (isEmpty && isEmpty(value)) { 27 | delete newState[propertyName]; 28 | } else { 29 | newState[propertyName] = value; 30 | } 31 | 32 | setState(newState); 33 | }; 34 | -------------------------------------------------------------------------------- /packages/abi-to-sol/test/compile-abi.ts: -------------------------------------------------------------------------------- 1 | import {Abi as SchemaAbi} from "@truffle/contract-schema/spec"; 2 | 3 | const solc = require("solc"); 4 | 5 | export const compileAbi = (content: string): SchemaAbi => { 6 | const source = "interface.sol"; 7 | 8 | const input = { 9 | language: "Solidity", 10 | sources: { 11 | [source]: { 12 | content, 13 | }, 14 | }, 15 | settings: { 16 | outputSelection: { 17 | "*": { 18 | "*": ["abi"], 19 | }, 20 | }, 21 | }, 22 | }; 23 | 24 | const output: any = JSON.parse(solc.compile(JSON.stringify(input))); 25 | const errors = (output.errors || []).filter( 26 | ({type}: any) => type !== "Warning" 27 | ); 28 | if (errors.length > 0) { 29 | console.error(errors); 30 | } 31 | const {contracts} = output; 32 | const sourceOutput: any = contracts[source]; 33 | 34 | return (Object.values(sourceOutput)[0] as any).abi; 35 | }; 36 | -------------------------------------------------------------------------------- /.github/workflows/web-ui.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Web UI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | build-and-deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 🛎️ 12 | uses: actions/checkout@v2.3.1 13 | 14 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 15 | run: | 16 | yarn 17 | cd packages/abi-to-sol 18 | yarn prepare 19 | cd ../web-ui 20 | PUBLIC_URL=https://gnidan.github.io/abi-to-sol/ yarn build 21 | 22 | - name: Deploy 🚀 23 | uses: JamesIves/github-pages-deploy-action@4.1.4 24 | with: 25 | branch: gh-pages # The branch the action should deploy to. 26 | folder: packages/web-ui/build # The folder the action should deploy. 27 | 28 | -------------------------------------------------------------------------------- /packages/web-ui/src/solidity/Options.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createContainer } from "unstated-next"; 3 | 4 | import type { GenerateSolidityOptions } from "abi-to-sol"; 5 | import { forState } from "../makeSet"; 6 | 7 | export type State = Pick< 8 | GenerateSolidityOptions, 9 | "name" | "solidityVersion" | "license" 10 | > & { 11 | prettifyOutput: boolean; 12 | }; 13 | 14 | export function useOptions() { 15 | const [state, setState] = React.useState({ 16 | prettifyOutput: true, 17 | }); 18 | 19 | const makeSet = forState({ state, setState }); 20 | 21 | return { 22 | ...state, 23 | setName: makeSet("name", (value) => value === ""), 24 | setSolidityVersion: makeSet("solidityVersion", (value) => value === ""), 25 | setLicense: makeSet("license", (value) => value === ""), 26 | setPrettifyOutput: makeSet("prettifyOutput"), 27 | setState, 28 | }; 29 | } 30 | 31 | export const Container = createContainer(useOptions); 32 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/Status.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Box, 4 | Center, 5 | Spinner, 6 | Text 7 | } from "@chakra-ui/react"; 8 | import { 9 | CheckCircleIcon, 10 | WarningIcon, 11 | } from "@chakra-ui/icons"; 12 | 13 | import * as Input from "./Input"; 14 | 15 | export const Status = () => { 16 | const { 17 | isReading, 18 | contents: _contents, 19 | ...result 20 | } = Input.Container.useContainer(); 21 | 22 | let component; 23 | if (isReading) { 24 | component = ( 25 | 26 | Reading ABI{" "} 27 | 28 | 29 | ) 30 | } else if ("abi" in result) { 31 | component = ( 32 | 33 | Valid{" "} 34 | 35 | 36 | ); 37 | } else { 38 | component = ( 39 | 40 | Invalid (maybe copy/paste again?){" "} 41 | 42 | 43 | ); 44 | } 45 | 46 | return ( 47 |
48 | {component} 49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 g. nicholas d'andrea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/web-ui/src/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Box, 4 | Link, 5 | Heading, 6 | Text, 7 | } from "@chakra-ui/react"; 8 | import { FaGithub } from "react-icons/fa"; 9 | import { version } from "abi-to-sol/package.json"; 10 | 11 | export const Footer = () => { 12 | return ( 13 | 14 | 15 | abi-to-sol v{version} 19 | 20 | 21 | 22 | 23 | Tool & web UI © 2020-2022{" "} 24 | 28 | @gnidan 29 | and distributed under the MIT license. 30 | 31 | 32 | 36 | Having issues with this tool? 37 | 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /packages/web-ui/src/solidity/Status.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Box, 4 | Center, 5 | Spinner, 6 | Text 7 | } from "@chakra-ui/react"; 8 | import { 9 | CheckCircleIcon, 10 | QuestionIcon, 11 | WarningIcon, 12 | } from "@chakra-ui/icons"; 13 | 14 | import * as Output from "./Output"; 15 | 16 | export const Status = () => { 17 | const { 18 | isGenerating, 19 | ...result 20 | } = Output.Container.useContainer(); 21 | 22 | let component; 23 | if (isGenerating) { 24 | component = ( 25 | 26 | Generating{" "} 27 | 28 | 29 | ) 30 | } else if ("contents" in result) { 31 | component = ( 32 | 33 | Success{" "} 34 | 35 | 36 | ); 37 | } else if ("error" in result) { 38 | component = ( 39 | 40 | Error{" "} 41 | 42 | 43 | ); 44 | } else { 45 | component = ( 46 | 47 | Waiting for input{" "} 48 | 49 | 50 | ); 51 | } 52 | 53 | return ( 54 |
55 | {component} 56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/examples/DepositContract.abi.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"pubkey","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"withdrawal_credentials","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"amount","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"signature","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"index","type":"bytes"}],"name":"DepositEvent","type":"event"},{"inputs":[{"internalType":"bytes","name":"pubkey","type":"bytes"},{"internalType":"bytes","name":"withdrawal_credentials","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes32","name":"deposit_data_root","type":"bytes32"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"get_deposit_count","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"get_deposit_root","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"}] 2 | -------------------------------------------------------------------------------- /packages/web-ui/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /packages/web-ui/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 && 45 | argv.indexOf('--watchAll=false') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # abi-to-sol Release Procedure 2 | 3 | 1. Cut release branch from `develop` 4 | 5 | ```console 6 | git checkout -b release 7 | git push -u origin release 8 | ``` 9 | 10 | 2. Sanity-check: see what packages changed 11 | 12 | ```console 13 | npx lerna changed 14 | ``` 15 | 16 | 3. Update package versions 17 | 18 | ```console 19 | npx lerna version 20 | ``` 21 | 22 | 4. Rebuild project 23 | 24 | ```console 25 | yarn 26 | ``` 27 | 28 | 5. Perform release 29 | 30 | ```console 31 | npx lerna publish from-package 32 | ``` 33 | 34 | 6. Update CHANGELOG.md, replacing `vNext` with corresponding version, and 35 | adding link to release notes page (although URL won't exist yet) 36 | 37 | ```console 38 | vim CHANGELOG.md 39 | git add CHANGELOG.md 40 | git commit -m "Update CHANGELOG" 41 | ``` 42 | 43 | 7. PR `release` -> `develop` and then delete branch `release` on GitHub 44 | once merged. 45 | 46 | 8. Delete local `release` branch 47 | 48 | ```console 49 | git checkout develop 50 | git pull 51 | git branch -D release 52 | ``` 53 | 54 | 9. Sync `master` with `develop` and such 55 | 56 | ```console 57 | git checkout master 58 | git pull 59 | git merge develop 60 | git push 61 | git checkout develop 62 | git merge master 63 | git push 64 | ``` 65 | 66 | 10. Write+publish release notes on GitHub (**don't forget to start 67 | discussion topic for the release**) 68 | 69 | 11. Wait for Web UI to build and visit page to make sure everything's 70 | honkidori 71 | 72 | 12. Install abi-to-sol package for good measure 73 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/examples/BunchaStructs.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "components": [ 7 | { 8 | "internalType": "uint256", 9 | "name": "a", 10 | "type": "uint256" 11 | } 12 | ], 13 | "indexed": false, 14 | "internalType": "struct BunchaStructs.A[]", 15 | "name": "a", 16 | "type": "tuple[]" 17 | }, 18 | { 19 | "components": [ 20 | { 21 | "components": [ 22 | { 23 | "components": [ 24 | { 25 | "components": [ 26 | { 27 | "internalType": "address", 28 | "name": "e", 29 | "type": "address" 30 | } 31 | ], 32 | "internalType": "struct Other.E", 33 | "name": "e", 34 | "type": "tuple" 35 | }, 36 | { 37 | "internalType": "string", 38 | "name": "d", 39 | "type": "string" 40 | } 41 | ], 42 | "internalType": "struct C[]", 43 | "name": "c", 44 | "type": "tuple[]" 45 | } 46 | ], 47 | "internalType": "struct B", 48 | "name": "b", 49 | "type": "tuple" 50 | }, 51 | { 52 | "components": [ 53 | { 54 | "internalType": "address", 55 | "name": "e", 56 | "type": "address" 57 | } 58 | ], 59 | "internalType": "struct Other.E", 60 | "name": "e", 61 | "type": "tuple" 62 | } 63 | ], 64 | "indexed": false, 65 | "internalType": "struct Other.D", 66 | "name": "d", 67 | "type": "tuple" 68 | } 69 | ], 70 | "name": "Event", 71 | "type": "event" 72 | } 73 | ] 74 | -------------------------------------------------------------------------------- /packages/web-ui/src/solidity/Code.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Alert, AlertIcon, AlertTitle, AlertDescription, Box } from "@chakra-ui/react"; 3 | 4 | import * as Output from "./Output"; 5 | 6 | // Highlight.js setup 7 | import "highlight.js/styles/default.css"; 8 | import hljs from "highlight.js"; 9 | import hljsDefineSolidity from "highlightjs-solidity"; 10 | hljsDefineSolidity(hljs); 11 | hljs.initHighlightingOnLoad(); 12 | 13 | 14 | export const Code = () => { 15 | const { isGenerating, ...result } = Output.Container.useContainer(); 16 | 17 | const [html, setHtml] = React.useState(""); 18 | const [showHtml, setShowHtml] = React.useState(false); 19 | 20 | const error = "error" in result && result.error; 21 | 22 | React.useEffect(() => { 23 | if (isGenerating) { 24 | setShowHtml(false); 25 | return; 26 | } 27 | 28 | if ("contents" in result) { 29 | const { contents } = result; 30 | 31 | try { 32 | setHtml(hljs.highlight(contents, { language: "solidity" }).value); 33 | setShowHtml(true); 34 | } catch { 35 | setHtml(contents); 36 | setShowHtml(true); 37 | } 38 | 39 | return; 40 | } 41 | 42 | setShowHtml(false); 43 | 44 | }, [isGenerating, result]); 45 | 46 | return ( 47 | 48 | {error && ( 49 | 50 | 51 | Could not generate Solidity! 52 | {error.message} 53 | 54 | )} 55 | {showHtml && ( 56 |
59 |       )}
60 |     
61 |   )
62 | }
63 | 
64 | 


--------------------------------------------------------------------------------
/packages/abi-to-sol/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "abi-to-sol",
 3 |   "version": "0.6.6",
 4 |   "description": "Compile ABI JSON to Solidity interface",
 5 |   "main": "dist/src/index.js",
 6 |   "types": "dist/src/index.d.ts",
 7 |   "bin": {
 8 |     "abi-to-sol": "dist/bin/abi-to-sol.js"
 9 |   },
10 |   "files": [
11 |     "dist"
12 |   ],
13 |   "author": "g. nicholas d'andrea ",
14 |   "license": "MIT",
15 |   "scripts": {
16 |     "abi-to-sol": "ts-node ./bin/abi-to-sol.ts",
17 |     "prepare": "tsc",
18 |     "test": "jest src/**",
19 |     "test:test": "jest test/**",
20 |     "test:dist": "yarn prepare && jest dist/src",
21 |     "test:dist:test": "yarn prepare && jest dist/test"
22 |   },
23 |   "homepage": "https://github.com/gnidan/abi-to-sol#readme",
24 |   "repository": {
25 |     "type": "git",
26 |     "url": "https://github.com/gnidan/abi-to-sol.git",
27 |     "directory": "packages/abi-to-sol"
28 |   },
29 |   "devDependencies": {
30 |     "@types/faker": "^5.1.2",
31 |     "@types/jest": "^26.0.14",
32 |     "@types/jest-json-schema": "^2.1.2",
33 |     "@types/prettier": "^2.1.1",
34 |     "@types/semver": "^7.3.7",
35 |     "change-case": "^4.1.1",
36 |     "faker": "^5.1.0",
37 |     "fast-check": "^3.1.1",
38 |     "husky": ">=4",
39 |     "jest": "^26.4.2",
40 |     "jest-fast-check": "^0.0.1",
41 |     "jest-json-schema": "^2.1.0",
42 |     "lint-staged": ">=10",
43 |     "solc": "^0.8.6",
44 |     "ts-jest": "^26.4.0",
45 |     "ts-node": "^9.0.0",
46 |     "typescript": "4.5.2"
47 |   },
48 |   "dependencies": {
49 |     "@truffle/abi-utils": "^0.3.0",
50 |     "@truffle/contract-schema": "^3.3.1",
51 |     "ajv": "^6.12.5",
52 |     "better-ajv-errors": "^0.8.2",
53 |     "neodoc": "^2.0.2",
54 |     "semver": "^7.3.5",
55 |     "source-map-support": "^0.5.19"
56 |   },
57 |   "optionalDependencies": {
58 |     "prettier": "^2.7.1",
59 |     "prettier-plugin-solidity": "^1.0.0-dev.23"
60 |   },
61 |   "husky": {
62 |     "hooks": {
63 |       "pre-commit": "lint-staged"
64 |     }
65 |   },
66 |   "lint-staged": {
67 |     "*.{ts,js,css,md}": "prettier --write"
68 |   }
69 | }
70 | 


--------------------------------------------------------------------------------
/packages/web-ui/config/getHttpsConfig.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const fs = require('fs');
 4 | const path = require('path');
 5 | const crypto = require('crypto');
 6 | const chalk = require('react-dev-utils/chalk');
 7 | const paths = require('./paths');
 8 | 
 9 | // Ensure the certificate and key provided are valid and if not
10 | // throw an easy to debug error
11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
12 |   let encrypted;
13 |   try {
14 |     // publicEncrypt will throw an error with an invalid cert
15 |     encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
16 |   } catch (err) {
17 |     throw new Error(
18 |       `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`
19 |     );
20 |   }
21 | 
22 |   try {
23 |     // privateDecrypt will throw an error with an invalid key
24 |     crypto.privateDecrypt(key, encrypted);
25 |   } catch (err) {
26 |     throw new Error(
27 |       `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${
28 |         err.message
29 |       }`
30 |     );
31 |   }
32 | }
33 | 
34 | // Read file and throw an error if it doesn't exist
35 | function readEnvFile(file, type) {
36 |   if (!fs.existsSync(file)) {
37 |     throw new Error(
38 |       `You specified ${chalk.cyan(
39 |         type
40 |       )} in your env, but the file "${chalk.yellow(file)}" can't be found.`
41 |     );
42 |   }
43 |   return fs.readFileSync(file);
44 | }
45 | 
46 | // Get the https config
47 | // Return cert files if provided in env, otherwise just true or false
48 | function getHttpsConfig() {
49 |   const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
50 |   const isHttps = HTTPS === 'true';
51 | 
52 |   if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
53 |     const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE);
54 |     const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE);
55 |     const config = {
56 |       cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
57 |       key: readEnvFile(keyFile, 'SSL_KEY_FILE'),
58 |     };
59 | 
60 |     validateKeyAndCerts({ ...config, keyFile, crtFile });
61 |     return config;
62 |   }
63 |   return isHttps;
64 | }
65 | 
66 | module.exports = getHttpsConfig;
67 | 


--------------------------------------------------------------------------------
/packages/web-ui/src/solidity/Controls.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import {
 3 |   Box,
 4 |   HStack,
 5 |   Stack,
 6 |   FormLabel,
 7 |   Center,
 8 |   Spacer,
 9 |   Input,
10 |   Switch,
11 |   InputGroup,
12 |   InputLeftElement,
13 | } from "@chakra-ui/react";
14 | import { LockIcon } from "@chakra-ui/icons";
15 | 
16 | import * as Output from "./Output";
17 | import * as Options from "./Options";
18 | import { CopyButton } from "../CopyButton";
19 | 
20 | export const Controls = () => {
21 |   const { isGenerating, ...result } = Output.Container.useContainer();
22 |   const {
23 |     license,
24 |     prettifyOutput,
25 |     setLicense,
26 |     setPrettifyOutput
27 |   } = Options.Container.useContainer();
28 | 
29 |   return (
30 |     
35 |       
36 |         License
37 |         
38 |           }
41 |           />
42 |           ) => {
48 |               setLicense(event.target.value)
49 |             }}
50 |           />
51 |         
52 |       
53 |       
54 |         Prettify output?
55 |         
56 | { 60 | setPrettifyOutput(!prettifyOutput); 61 | }} 62 | /> 63 |
64 |
65 | 66 | 67 | 68 | 75 | 76 | 77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /packages/web-ui/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/Input.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createContainer } from "unstated-next"; 3 | import TruffleContractSchema from "@truffle/contract-schema"; 4 | 5 | import type { GenerateSolidityOptions } from "abi-to-sol"; 6 | 7 | import { forState } from "../makeSet"; 8 | 9 | export interface CommonState { 10 | contents: string; 11 | } 12 | 13 | export interface ReadingState { 14 | isReading: true; 15 | } 16 | 17 | export interface SuccessState { 18 | isReading: false; 19 | abi: GenerateSolidityOptions["abi"]; 20 | } 21 | 22 | export interface ErrorState { 23 | isReading: false; 24 | error: Error; 25 | } 26 | 27 | export type State = CommonState & (ReadingState | SuccessState | ErrorState); 28 | 29 | type ReadResult = Pick | Pick; 30 | 31 | export const useInput = (defaultContents: string = "") => { 32 | const [state, setState] = React.useState({ 33 | contents: defaultContents, 34 | isReading: true, 35 | }); 36 | 37 | const makeSet = forState({ state, setState }); 38 | 39 | const setContents = makeSet("contents"); 40 | 41 | React.useEffect(() => { 42 | // mark isReading 43 | setState({ 44 | contents: state.contents, 45 | isReading: true, 46 | }); 47 | 48 | // read 49 | const result = readContents(state.contents); 50 | 51 | // mark result 52 | setState( 53 | "abi" in result 54 | ? ({ 55 | contents: state.contents, 56 | isReading: false, 57 | abi: result.abi, 58 | } as const) 59 | : ({ 60 | contents: state.contents, 61 | isReading: false, 62 | error: result.error, 63 | } as const) 64 | ); 65 | }, [state.contents]); 66 | 67 | return { 68 | ...state, 69 | setContents, 70 | }; 71 | }; 72 | 73 | function readContents(contents: string): ReadResult { 74 | try { 75 | const abi = JSON.parse(contents); 76 | 77 | try { 78 | TruffleContractSchema.validate({ abi }); 79 | } catch (error) { 80 | console.error(error); 81 | throw error; 82 | } 83 | 84 | return { abi }; 85 | } catch (error) { 86 | return { error: error as Error }; 87 | } 88 | } 89 | 90 | export const Container = createContainer(useInput); 91 | -------------------------------------------------------------------------------- /packages/abi-to-sol/src/solidity.test.ts: -------------------------------------------------------------------------------- 1 | import * as fc from "fast-check"; 2 | import {testProp} from "jest-fast-check"; 3 | import * as Abi from "@truffle/abi-utils"; 4 | import * as Example from "../test/custom-example"; 5 | import {compileAbi} from "../test/compile-abi"; 6 | import {excludesFunctionParameters} from "../test/preflight"; 7 | 8 | import {generateSolidity} from "./solidity"; 9 | 10 | const removeProps = (obj: any, keys: Set) => { 11 | if (obj instanceof Array) { 12 | for (const item of obj) { 13 | removeProps(item, keys); 14 | } 15 | } else if (typeof obj === "object") { 16 | for (const [key, value] of Object.entries(obj)) { 17 | if (keys.has(key)) { 18 | delete obj[key]; 19 | } else { 20 | removeProps(obj[key], keys); 21 | } 22 | } 23 | } 24 | 25 | return obj; 26 | }; 27 | 28 | describe("generateSolidity", () => { 29 | testProp("compiles to input ABI", [Abi.Arbitrary.Abi()], (abi) => { 30 | fc.pre( 31 | abi.every((entry) => "type" in entry && entry.type !== "constructor") 32 | ); 33 | fc.pre(excludesFunctionParameters(abi)); 34 | 35 | fc.pre(abi.length > 0); 36 | 37 | const output = generateSolidity({ 38 | name: "MyInterface", 39 | abi, 40 | solidityVersion: "^0.8.4", 41 | }); 42 | 43 | let resultAbi; 44 | try { 45 | resultAbi = compileAbi(output); 46 | } catch (error) { 47 | console.log("Failed to compile. Solidity:\n%s", output); 48 | throw error; 49 | } 50 | 51 | const compiledAbi = new Set( 52 | removeProps(resultAbi, new Set(["internalType"])) 53 | ); 54 | 55 | const expectedAbi = new Set(Abi.normalize(abi)); 56 | 57 | expect(compiledAbi).toEqual(expectedAbi); 58 | }); 59 | 60 | describe("custom example", () => { 61 | const abiWithoutConstructor = Abi.normalize( 62 | Example.abi.filter(({type}) => type !== "constructor") 63 | ); 64 | 65 | const output = generateSolidity({ 66 | name: "Example", 67 | abi: abiWithoutConstructor, 68 | solidityVersion: "^0.8.4", 69 | }); 70 | 71 | it("generates output", () => { 72 | const compiledAbi = compileAbi(output); 73 | 74 | const expectedAbi = abiWithoutConstructor.map((entry) => ({ 75 | ...entry, 76 | type: entry.type || "function", 77 | })); 78 | 79 | expect(compiledAbi).toEqual(expectedAbi); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /packages/abi-to-sol/src/version-features.ts: -------------------------------------------------------------------------------- 1 | import * as semver from "semver"; 2 | 3 | export const mixed: unique symbol = Symbol(); 4 | 5 | export const allFeatures = { 6 | "receive-keyword": { 7 | ">=0.6.0": true, 8 | "<0.6.0": false 9 | }, 10 | "fallback-keyword": { 11 | ">=0.6.0": true, 12 | "<0.6.0": false 13 | }, 14 | "array-parameter-location": { 15 | ">=0.7.0": "memory", 16 | "^0.5.0 || ^0.6.0": "calldata", 17 | "<0.5.0": undefined 18 | }, 19 | "abiencoder-v2": { 20 | ">=0.8.0": "default", 21 | "<0.8.0": "experimental", 22 | }, 23 | "global-structs": { 24 | ">=0.6.0": true, 25 | "<0.6.0": false 26 | }, 27 | "structs-in-interfaces": { 28 | ">=0.5.0": true, 29 | "<0.5.0": false 30 | }, 31 | "custom-errors": { 32 | ">=0.8.4": true, 33 | "<0.8.4": false 34 | }, 35 | "user-defined-value-types": { 36 | ">=0.8.8": true, 37 | "<0.8.8": false 38 | } 39 | } as const; 40 | 41 | export type AllFeatures = typeof allFeatures; 42 | 43 | export type Category = keyof AllFeatures; 44 | 45 | export type CategoryOptions = AllFeatures[C]; 46 | 47 | export type CategoryOptionRange = string & { 48 | [K in C]: keyof CategoryOptions 49 | }[C]; 50 | 51 | export type CategoryOption = { 52 | [K in C]: CategoryOptions[CategoryOptionRange] 53 | }[C]; 54 | 55 | export type VersionFeatures = { 56 | [C in Category]: VersionFeature; 57 | }; 58 | 59 | export type VersionFeature = 60 | CategoryOption | typeof mixed; 61 | 62 | export const forRange = (range: string | semver.Range): VersionFeatures => { 63 | const forCategory = ( 64 | category: C 65 | ): VersionFeature => { 66 | const options = allFeatures[category]; 67 | const matchingRanges: CategoryOptionRange[] = 68 | (Object.keys(options) as CategoryOptionRange[]) 69 | .filter(optionRange => semver.intersects(range, optionRange)); 70 | 71 | if (matchingRanges.length > 1) { 72 | return mixed; 73 | } 74 | 75 | const [matchingRange] = matchingRanges; 76 | return options[matchingRange]; 77 | } 78 | 79 | return (Object.keys(allFeatures) as Category[]) 80 | .map((category: C) => ({ [category]: forCategory(category) })) 81 | .reduce((a, b) => ({ ...a, ...b }), {}) as VersionFeatures; 82 | } 83 | -------------------------------------------------------------------------------- /packages/web-ui/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/abi-to-sol/src/visitor.ts: -------------------------------------------------------------------------------- 1 | import type {Abi as SchemaAbi} from "@truffle/contract-schema/spec"; 2 | import * as Abi from "@truffle/abi-utils"; 3 | 4 | export interface VisitOptions { 5 | node: N; 6 | context?: C; 7 | } 8 | 9 | export interface Visitor { 10 | visitAbi(options: VisitOptions): T; 11 | visitFunctionEntry(options: VisitOptions): T; 12 | visitConstructorEntry(options: VisitOptions): T; 13 | visitFallbackEntry(options: VisitOptions): T; 14 | visitReceiveEntry(options: VisitOptions): T; 15 | visitEventEntry(options: VisitOptions): T; 16 | visitErrorEntry(options: VisitOptions): T; 17 | visitParameter(options: VisitOptions): T; 18 | } 19 | 20 | export interface DispatchOptions { 21 | node: Node | SchemaAbi; 22 | visitor: Visitor; 23 | context?: C; 24 | } 25 | 26 | export type Node = 27 | | Abi.Abi 28 | | Abi.Entry 29 | | Abi.FunctionEntry 30 | | Abi.ConstructorEntry 31 | | Abi.FallbackEntry 32 | | Abi.ReceiveEntry 33 | | Abi.EventEntry 34 | | Abi.Parameter 35 | | Abi.EventParameter; 36 | 37 | export const dispatch = (options: DispatchOptions): T => { 38 | const {node, visitor, context} = options; 39 | 40 | if (isAbi(node)) { 41 | return visitor.visitAbi({ 42 | node: Abi.normalize(node), 43 | context, 44 | }); 45 | } 46 | 47 | if (isEntry(node)) { 48 | switch (node.type) { 49 | case "function": 50 | return visitor.visitFunctionEntry({node, context}); 51 | case "constructor": 52 | return visitor.visitConstructorEntry({node, context}); 53 | case "fallback": 54 | return visitor.visitFallbackEntry({node, context}); 55 | case "receive": 56 | return visitor.visitReceiveEntry({node, context}); 57 | case "event": 58 | return visitor.visitEventEntry({node, context}); 59 | case "error": 60 | return visitor.visitErrorEntry({node, context}); 61 | } 62 | } 63 | 64 | return visitor.visitParameter({node, context}); 65 | }; 66 | 67 | const isAbi = (node: Node | SchemaAbi): node is Abi.Abi | SchemaAbi => 68 | node instanceof Array; 69 | 70 | const isEntry = (node: Node): node is Abi.Entry => 71 | typeof node === "object" && 72 | "type" in node && 73 | ["function", "constructor", "fallback", "receive", "event", "error"].includes( 74 | node.type 75 | ) && 76 | (node.type !== "function" || "stateMutability" in node || "constant" in node); 77 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ develop ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ develop ] 20 | schedule: 21 | - cron: '22 11 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /packages/web-ui/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 13 | // "public path" at which the app is served. 14 | // webpack needs to know it to put the right 28 | 31 | 32 | 33 | 46 | 47 | 48 | 49 | 50 |
51 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /packages/abi-to-sol/src/abi-features.ts: -------------------------------------------------------------------------------- 1 | import type { Abi as SchemaAbi } from "@truffle/contract-schema/spec"; 2 | import type * as Abi from "@truffle/abi-utils"; 3 | 4 | import { Visitor, VisitOptions, dispatch, Node } from "./visitor"; 5 | 6 | export const allFeatures = [ 7 | "defines-receive", 8 | "defines-fallback", 9 | "needs-abiencoder-v2", 10 | "defines-error", 11 | ] as const; 12 | 13 | export type AbiFeature = typeof allFeatures[number]; 14 | export type AbiFeatures = Partial<{ 15 | [F in AbiFeature]: true 16 | }>; 17 | 18 | export const collectAbiFeatures = (node: SchemaAbi | Node) => 19 | dispatch({ 20 | node, 21 | visitor: new AbiFeaturesCollector(), 22 | }); 23 | 24 | export class AbiFeaturesCollector implements Visitor { 25 | visitAbi({ node: nodes }: VisitOptions): AbiFeatures { 26 | return nodes 27 | .map((node) => dispatch({ node, visitor: this })) 28 | .reduce((a, b) => ({ ...a, ...b }), {}); 29 | } 30 | 31 | visitEventEntry({ node: entry }: VisitOptions): AbiFeatures { 32 | return entry.inputs 33 | .map((node) => dispatch({ node, visitor: this })) 34 | .reduce((a, b) => ({ ...a, ...b }), {}); 35 | } 36 | 37 | visitErrorEntry({ node: entry }: VisitOptions): AbiFeatures { 38 | return entry.inputs 39 | .map((node) => dispatch({ node, visitor: this })) 40 | .reduce((a, b) => ({ ...a, ...b }), {}); 41 | } 42 | 43 | visitFunctionEntry({ 44 | node: entry, 45 | }: VisitOptions): AbiFeatures { 46 | return [...entry.inputs, ...(entry.outputs || [])] 47 | .map((node) => dispatch({ node, visitor: this })) 48 | .reduce((a, b) => ({ ...a, ...b }), {}); 49 | } 50 | 51 | visitConstructorEntry({ 52 | node: entry, 53 | }: VisitOptions): AbiFeatures { 54 | return entry.inputs 55 | .map((node) => dispatch({ node, visitor: this })) 56 | .reduce((a, b) => ({ ...a, ...b }), {}); 57 | } 58 | 59 | visitFallbackEntry({ 60 | node: entry, 61 | }: VisitOptions): AbiFeatures { 62 | return { "defines-fallback": true }; 63 | } 64 | 65 | visitReceiveEntry({ 66 | node: entry, 67 | }: VisitOptions): AbiFeatures { 68 | return { "defines-receive": true }; 69 | } 70 | 71 | visitParameter({ 72 | node: parameter, 73 | }: VisitOptions): AbiFeatures { 74 | if ( 75 | parameter.type.startsWith("tuple") || // anything with tuples 76 | parameter.type.includes("string[") || // arrays of strings 77 | parameter.type.includes("bytes[") || // arrays of bytes 78 | parameter.type.includes("][") // anything with nested arrays 79 | ) { 80 | return { "needs-abiencoder-v2": true }; 81 | } 82 | 83 | return {}; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /packages/web-ui/src/solidity/OptionsControls.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Stack, 4 | HStack, 5 | FormLabel, 6 | Input, 7 | Select, 8 | InputGroup, 9 | InputLeftElement, 10 | } from "@chakra-ui/react"; 11 | import { InfoIcon } from "@chakra-ui/icons"; 12 | 13 | import * as Options from "./Options"; 14 | import * as Examples from "../abi/Examples"; 15 | import { examples } from "../abi/Examples"; 16 | import { defaults } from "abi-to-sol"; 17 | 18 | 19 | export const OptionsControls = () => { 20 | const { 21 | name, 22 | setName, 23 | setSolidityVersion, 24 | ...options 25 | } = Options.Container.useContainer(); 26 | 27 | const { 28 | selectedExample, 29 | selectExample 30 | } = Examples.Container.useContainer(); 31 | 32 | console.debug("name %s", name); 33 | 34 | return ( 35 | 36 | 37 | Interface name 38 | 39 | } 42 | /> 43 | ) => { 49 | console.debug("changing name via form"); 50 | setName(event.target.value); 51 | }} 52 | /> 53 | 54 | 55 | 56 | Solidity version 57 | 71 | 72 | 73 | See example 74 | 87 | 88 | 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /packages/web-ui/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ChakraProvider, 4 | Box, 5 | Flex, 6 | Divider, 7 | } from "@chakra-ui/react"; 8 | import { Page, Container } from "./Layout"; 9 | import { Footer } from "./Footer"; 10 | import * as Abi from "./abi"; 11 | import * as Solidity from "./solidity"; 12 | 13 | function App() { 14 | return ( 15 |
19 | 20 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | {/**/} 90 | 91 |
92 | ); 93 | } 94 | 95 | export default App; 96 | -------------------------------------------------------------------------------- /packages/abi-to-sol/test/custom-example.ts: -------------------------------------------------------------------------------- 1 | import {Abi as SchemaAbi} from "@truffle/contract-schema/spec"; 2 | 3 | /** 4 | * Solidity used to generate this ABI: 5 | * 6 | * ```solidity 7 | * // SPDX-License-Identifier: UNLICENSED 8 | * pragma solidity ^0.7.0; 9 | * pragma experimental ABIEncoderV2; 10 | * 11 | * struct Bar { 12 | * uint256 a; 13 | * uint256 b; 14 | * } 15 | * 16 | * struct Foo { 17 | * Bar[] bars; 18 | * uint256 c; 19 | * } 20 | * 21 | * contract TestCase { 22 | * event Event (Bar[] indexed); 23 | * 24 | * constructor (Foo memory foo1, Foo memory foo2, Bar memory bar) { 25 | * } 26 | * } 27 | * ``` 28 | */ 29 | export const abi: SchemaAbi = [ 30 | { 31 | inputs: [ 32 | { 33 | components: [ 34 | { 35 | components: [ 36 | {internalType: "uint256", name: "a", type: "uint256"}, 37 | {internalType: "uint256", name: "b", type: "uint256"}, 38 | ], 39 | internalType: "struct Bar[]", 40 | name: "bars", 41 | type: "tuple[]", 42 | }, 43 | {internalType: "uint256", name: "c", type: "uint256"}, 44 | ], 45 | internalType: "struct Foo", 46 | name: "foo1", 47 | type: "tuple", 48 | }, 49 | { 50 | components: [ 51 | { 52 | components: [ 53 | {internalType: "uint256", name: "a", type: "uint256"}, 54 | {internalType: "uint256", name: "b", type: "uint256"}, 55 | ], 56 | internalType: "struct Bar[]", 57 | name: "bars", 58 | type: "tuple[]", 59 | }, 60 | {internalType: "uint256", name: "c", type: "uint256"}, 61 | ], 62 | internalType: "struct Foo", 63 | name: "foo2", 64 | type: "tuple", 65 | }, 66 | { 67 | components: [ 68 | {internalType: "uint256", name: "a", type: "uint256"}, 69 | {internalType: "uint256", name: "b", type: "uint256"}, 70 | ], 71 | internalType: "struct Bar", 72 | name: "bar", 73 | type: "tuple", 74 | }, 75 | ], 76 | stateMutability: "nonpayable", 77 | type: "constructor", 78 | }, 79 | { 80 | anonymous: false, 81 | inputs: [ 82 | { 83 | components: [ 84 | {internalType: "uint256", name: "a", type: "uint256"}, 85 | {internalType: "uint256", name: "b", type: "uint256"}, 86 | ], 87 | indexed: true, 88 | internalType: "struct Bar[]", 89 | name: "", 90 | type: "tuple[]", 91 | }, 92 | ], 93 | name: "Event", 94 | type: "event", 95 | }, 96 | ]; 97 | 98 | export const expectedSignatures: {[name: string]: string} = { 99 | Foo: "((uint256,uint256)[],uint256)", 100 | Bar: "(uint256,uint256)", 101 | }; 102 | 103 | export const expectedDeclarations = { 104 | Foo: { 105 | bars: { 106 | type: "tuple[]", 107 | signature: expectedSignatures.Bar, 108 | }, 109 | c: { 110 | type: "uint256", 111 | }, 112 | }, 113 | Bar: { 114 | a: { 115 | type: "uint256", 116 | }, 117 | b: { 118 | type: "uint256", 119 | }, 120 | }, 121 | }; 122 | -------------------------------------------------------------------------------- /packages/abi-to-sol/bin/abi-to-sol.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const neodoc = require("neodoc"); 4 | import {Abi as SchemaAbi} from "@truffle/contract-schema/spec"; 5 | import * as abiSchema from "@truffle/contract-schema/spec/abi.spec.json"; 6 | import betterAjvErrors from "better-ajv-errors"; 7 | import Ajv from "ajv"; 8 | 9 | import {generateSolidity, GenerateSolidityMode} from "../src"; 10 | import * as defaults from "../src/defaults"; 11 | 12 | const usage = ` 13 | abi-to-sol 14 | 15 | Usage: 16 | abi-to-sol 17 | [--solidity-version=] 18 | [--license=] 19 | [--validate] 20 | [--embedded] 21 | [--no-attribution] 22 | [--no-source] 23 | [] 24 | abi-to-sol -h | --help 25 | abi-to-sol --version 26 | 27 | Options: 28 | 29 | Name of generated interface. Default: ${defaults.name} 30 | 31 | --validate 32 | Validate JSON before starting 33 | 34 | -V --solidity-version 35 | Version of Solidity (for pragma). Default: ${defaults.solidityVersion} 36 | 37 | -L --license 38 | SPDX license identifier. Default: ${defaults.license} 39 | 40 | -E --embedded 41 | Omit pragma and SPDX license identifier (only \`interface\`s and \`struct\`s) 42 | 43 | -A --no-attribution 44 | Skip printing "AUTOGENERATED by abi-to-sol" as a header comment in output 45 | 46 | -S --no-source 47 | Skip printing source ABI JSON as footer comment in output 48 | 49 | -h --help Show this screen. 50 | --version Show version. 51 | `; 52 | 53 | const readStdin = async () => 54 | await new Promise((accept, reject) => { 55 | const chunks: Buffer[] = []; 56 | 57 | process.stdin.setEncoding("utf8"); 58 | 59 | process.stdin.on("data", (chunk) => chunks.push(chunk)); 60 | 61 | process.stdin.on("end", () => { 62 | try { 63 | const json = chunks.join(); 64 | const abi = JSON.parse(json); 65 | accept(abi); 66 | } catch (error) { 67 | reject(error); 68 | } 69 | }); 70 | }); 71 | 72 | const main = async () => { 73 | const args = neodoc.run(usage, { 74 | smartOptions: true, 75 | laxPlacement: true, 76 | }); 77 | 78 | const ajv = new Ajv({jsonPointers: true}); 79 | const validate = ajv.compile(abiSchema); 80 | 81 | const options = { 82 | solidityVersion: args["-V"] || args["--solidity-version"], 83 | name: args[""], 84 | license: args["-L"] || args["--license"], 85 | validate: args["--validate"] || false, 86 | outputAttribution: !(args["-A"] || args["--no-attribution"]), 87 | outputSource: !(args["-S"] || args["--no-source"]), 88 | mode: (args["-E"] || args["--embedded"]) ? GenerateSolidityMode.Embedded : GenerateSolidityMode.Normal 89 | }; 90 | 91 | const abi: SchemaAbi = (await readStdin()) as SchemaAbi; 92 | 93 | if (options.validate) { 94 | const valid = validate(abi); 95 | if (!valid) { 96 | const output = betterAjvErrors(abiSchema, abi, validate.errors, { 97 | format: "cli", 98 | }); 99 | console.log(output); 100 | process.exit(1); 101 | } 102 | } 103 | 104 | process.stdout.write( 105 | generateSolidity({ 106 | ...options, 107 | abi, 108 | }) 109 | ); 110 | }; 111 | 112 | main(); 113 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/Examples.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createContainer } from "unstated-next"; 3 | 4 | import * as Input from "./Input"; 5 | import { Options } from "../solidity"; 6 | import ENS from "./examples/ENS.abi.json"; 7 | import DepositContract from "./examples/DepositContract.abi.json"; 8 | import UniswapV2RouterO2 from "./examples/UniswapV2Router02.abi.json"; 9 | import AirSwap from "./examples/AirSwap.abi.json"; 10 | import BunchaStructs from "./examples/BunchaStructs.abi.json"; 11 | 12 | export interface Example { 13 | name: string; 14 | license: string; 15 | contents: string; 16 | } 17 | 18 | export const examples: { [exampleName: string]: Example } = { 19 | ens: { 20 | name: "ENS", 21 | license: "BSD-2-Clause", 22 | contents: JSON.stringify(ENS, undefined, 2), 23 | }, 24 | eth2: { 25 | name: "Eth2Deposit", 26 | license: "CC0-1.0", 27 | contents: JSON.stringify(DepositContract, undefined, 2) 28 | }, 29 | uniswap: { 30 | name: "UniswapV2Router02", 31 | license: "GPL-3.0", 32 | contents: JSON.stringify(UniswapV2RouterO2, undefined, 2), 33 | }, 34 | airswap: { 35 | name: "AirSwap", 36 | license: "Apache-2.0", 37 | contents: JSON.stringify(AirSwap, undefined, 2) 38 | }, 39 | bunchastructs: { 40 | name: "BunchaStructs", 41 | license: "", 42 | contents: JSON.stringify(BunchaStructs, undefined, 2) 43 | } 44 | }; 45 | 46 | export type State = string | undefined; 47 | 48 | export const useExample = () => { 49 | const [state, setState] = React.useState(); 50 | const [locked, setLocked] = React.useState(false); 51 | const input = Input.Container.useContainer(); 52 | const options = Options.Container.useContainer(); 53 | 54 | const selectExample = (exampleName: string) => { 55 | if (exampleName === "") { 56 | setState(undefined); 57 | return; 58 | } 59 | 60 | const example = examples[exampleName]; 61 | 62 | if (!example) { 63 | throw new Error(`Unknown example: "${exampleName}"`); 64 | } 65 | 66 | setLocked(true); 67 | 68 | const { name, license, contents } = example; 69 | const { prettifyOutput, solidityVersion, setState: setOptions } = options; 70 | const { setContents } = input; 71 | 72 | console.debug("setting example %o", example); 73 | setState(exampleName); 74 | setOptions({ 75 | name: name, 76 | license: license, 77 | solidityVersion, 78 | prettifyOutput, 79 | }); 80 | setContents(contents); 81 | 82 | setLocked(false); 83 | }; 84 | 85 | React.useEffect(() => { 86 | if (locked) { 87 | console.debug("locked"); 88 | return; 89 | } 90 | 91 | if (!state) { 92 | return; 93 | } 94 | 95 | console.debug("state %o", state); 96 | const { name, license, contents } = examples[state]; 97 | const nameDiffers = name !== options.name; 98 | const licenseDiffers = license !== options.license; 99 | const contentsDiffer = contents !== input.contents; 100 | 101 | if (nameDiffers) { 102 | console.debug("name differs"); 103 | } 104 | if (licenseDiffers) { 105 | console.debug("license differs"); 106 | } 107 | if (contentsDiffer) { 108 | console.debug("contentsDiffer"); 109 | } 110 | 111 | if (nameDiffers || licenseDiffers || contentsDiffer) { 112 | setState(undefined); 113 | } 114 | }, [locked, state, input.contents, options.name, options.license]); 115 | 116 | return { selectedExample: state, selectExample }; 117 | }; 118 | 119 | export const Container = createContainer(useExample); 120 | -------------------------------------------------------------------------------- /packages/web-ui/config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const resolve = require('resolve'); 8 | 9 | /** 10 | * Get additional module paths based on the baseUrl of a compilerOptions object. 11 | * 12 | * @param {Object} options 13 | */ 14 | function getAdditionalModulePaths(options = {}) { 15 | const baseUrl = options.baseUrl; 16 | 17 | if (!baseUrl) { 18 | return ''; 19 | } 20 | 21 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 22 | 23 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 24 | // the default behavior. 25 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 26 | return null; 27 | } 28 | 29 | // Allow the user set the `baseUrl` to `appSrc`. 30 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 31 | return [paths.appSrc]; 32 | } 33 | 34 | // If the path is equal to the root directory we ignore it here. 35 | // We don't want to allow importing from the root directly as source files are 36 | // not transpiled outside of `src`. We do allow importing them with the 37 | // absolute path (e.g. `src/Components/Button.js`) but we set that up with 38 | // an alias. 39 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 40 | return null; 41 | } 42 | 43 | // Otherwise, throw an error. 44 | throw new Error( 45 | chalk.red.bold( 46 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 47 | ' Create React App does not support other values at this time.' 48 | ) 49 | ); 50 | } 51 | 52 | /** 53 | * Get webpack aliases based on the baseUrl of a compilerOptions object. 54 | * 55 | * @param {*} options 56 | */ 57 | function getWebpackAliases(options = {}) { 58 | const baseUrl = options.baseUrl; 59 | 60 | if (!baseUrl) { 61 | return {}; 62 | } 63 | 64 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 65 | 66 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 67 | return { 68 | src: paths.appSrc, 69 | }; 70 | } 71 | } 72 | 73 | /** 74 | * Get jest aliases based on the baseUrl of a compilerOptions object. 75 | * 76 | * @param {*} options 77 | */ 78 | function getJestAliases(options = {}) { 79 | const baseUrl = options.baseUrl; 80 | 81 | if (!baseUrl) { 82 | return {}; 83 | } 84 | 85 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 86 | 87 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 88 | return { 89 | '^src/(.*)$': '/src/$1', 90 | }; 91 | } 92 | } 93 | 94 | function getModules() { 95 | // Check if TypeScript is setup 96 | const hasTsConfig = fs.existsSync(paths.appTsConfig); 97 | const hasJsConfig = fs.existsSync(paths.appJsConfig); 98 | 99 | if (hasTsConfig && hasJsConfig) { 100 | throw new Error( 101 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 102 | ); 103 | } 104 | 105 | let config; 106 | 107 | // If there's a tsconfig.json we assume it's a 108 | // TypeScript project and set up the config 109 | // based on tsconfig.json 110 | if (hasTsConfig) { 111 | const ts = require(resolve.sync('typescript', { 112 | basedir: paths.appNodeModules, 113 | })); 114 | config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; 115 | // Otherwise we'll check if there is jsconfig.json 116 | // for non TS projects. 117 | } else if (hasJsConfig) { 118 | config = require(paths.appJsConfig); 119 | } 120 | 121 | config = config || {}; 122 | const options = config.compilerOptions || {}; 123 | 124 | const additionalModulePaths = getAdditionalModulePaths(options); 125 | 126 | return { 127 | additionalModulePaths: additionalModulePaths, 128 | webpackAliases: getWebpackAliases(options), 129 | jestAliases: getJestAliases(options), 130 | hasTsConfig, 131 | }; 132 | } 133 | 134 | module.exports = getModules(); 135 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/examples/AirSwap.abi.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"wrapperSwapContract","type":"address"},{"internalType":"address","name":"wrapperWethContract","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"constant":false,"inputs":[{"components":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"components":[{"internalType":"bytes4","name":"kind","type":"bytes4"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"id","type":"uint256"}],"internalType":"struct Types.Party","name":"signer","type":"tuple"},{"components":[{"internalType":"bytes4","name":"kind","type":"bytes4"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"id","type":"uint256"}],"internalType":"struct Types.Party","name":"sender","type":"tuple"},{"components":[{"internalType":"bytes4","name":"kind","type":"bytes4"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"id","type":"uint256"}],"internalType":"struct Types.Party","name":"affiliate","type":"tuple"},{"components":[{"internalType":"address","name":"signatory","type":"address"},{"internalType":"address","name":"validator","type":"address"},{"internalType":"bytes1","name":"version","type":"bytes1"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct Types.Signature","name":"signature","type":"tuple"}],"internalType":"struct Types.Order","name":"order","type":"tuple"},{"internalType":"contract IDelegate","name":"delegate","type":"address"}],"name":"provideDelegateOrder","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"components":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"components":[{"internalType":"bytes4","name":"kind","type":"bytes4"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"id","type":"uint256"}],"internalType":"struct Types.Party","name":"signer","type":"tuple"},{"components":[{"internalType":"bytes4","name":"kind","type":"bytes4"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"id","type":"uint256"}],"internalType":"struct Types.Party","name":"sender","type":"tuple"},{"components":[{"internalType":"bytes4","name":"kind","type":"bytes4"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"id","type":"uint256"}],"internalType":"struct Types.Party","name":"affiliate","type":"tuple"},{"components":[{"internalType":"address","name":"signatory","type":"address"},{"internalType":"address","name":"validator","type":"address"},{"internalType":"bytes1","name":"version","type":"bytes1"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct Types.Signature","name":"signature","type":"tuple"}],"internalType":"struct Types.Order","name":"order","type":"tuple"}],"name":"swap","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"swapContract","outputs":[{"internalType":"contract ISwap","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"wethContract","outputs":[{"internalType":"contract IWETH","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /packages/web-ui/src/solidity/Output.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createContainer } from "unstated-next"; 3 | 4 | import { useToast } from "@chakra-ui/react"; 5 | 6 | import prettierSolidity from "prettier-plugin-solidity"; 7 | import { generateSolidity } from "abi-to-sol"; 8 | import * as Options from "./Options"; 9 | import * as Abi from "../abi"; 10 | 11 | // @ts-ignore 12 | const prettier = window.prettier; 13 | 14 | export interface GeneratingState { 15 | isGenerating: true; 16 | } 17 | 18 | export interface NoInputState { 19 | isGenerating: false; 20 | } 21 | 22 | export interface SuccessState { 23 | isGenerating: false; 24 | contents: string; 25 | } 26 | 27 | export interface ErrorState { 28 | isGenerating: false; 29 | error: Error; 30 | } 31 | 32 | export type State = GeneratingState | NoInputState | SuccessState | ErrorState; 33 | 34 | export const useOutput = (defaultContents: string = "") => { 35 | const toast = useToast(); 36 | 37 | const [state, setState] = React.useState({ 38 | isGenerating: true, 39 | }); 40 | 41 | const { 42 | name, 43 | license, 44 | solidityVersion, 45 | prettifyOutput, 46 | setPrettifyOutput, 47 | } = Options.Container.useContainer(); 48 | 49 | const abiInput = Abi.Input.Container.useContainer() as Abi.Input.State; 50 | 51 | React.useEffect(() => { 52 | setState({ isGenerating: true }); 53 | 54 | if (abiInput.isReading || !("abi" in abiInput)) { 55 | console.debug("waiting for input"); 56 | setState({ 57 | isGenerating: false, 58 | }); 59 | return; 60 | } 61 | 62 | const { abi } = abiInput; 63 | 64 | try { 65 | console.info("generating solidity"); 66 | const unformatted = generateSolidity({ 67 | abi, 68 | name, 69 | license, 70 | solidityVersion, 71 | prettifyOutput: false, 72 | }); 73 | 74 | if (prettifyOutput) { 75 | try { 76 | const formatted = prettier.format(unformatted, { 77 | plugins: [prettierSolidity], 78 | parser: "solidity-parse", 79 | }); 80 | 81 | setState({ 82 | isGenerating: false, 83 | contents: formatted, 84 | }); 85 | } catch { 86 | setPrettifyOutput(false); 87 | 88 | setState({ 89 | isGenerating: false, 90 | contents: unformatted, 91 | }); 92 | } 93 | } else { 94 | setState({ 95 | isGenerating: false, 96 | contents: unformatted, 97 | }); 98 | } 99 | } catch (error) { 100 | setState({ 101 | isGenerating: false, 102 | error: error as Error, 103 | }); 104 | } 105 | }, [ 106 | abiInput, 107 | license, 108 | name, 109 | prettifyOutput, 110 | solidityVersion, 111 | setPrettifyOutput, 112 | ]); 113 | 114 | // const abi = "abi" in abiInput && abiInput.abi; 115 | const contents = useDebounce( 116 | "contents" in state ? state.contents : undefined, 117 | 500 118 | ); 119 | const [previousContents, setPreviousContents] = React.useState< 120 | string | undefined 121 | >(undefined); 122 | React.useEffect(() => { 123 | if (contents && contents !== previousContents) { 124 | const toastOptions = { 125 | title: "Generated Solidity", 126 | status: "success", 127 | position: "bottom-right", 128 | duration: 1000, 129 | } as const; 130 | 131 | toast(toastOptions); 132 | } 133 | setPreviousContents(contents); 134 | }, [contents, previousContents, toast]); 135 | 136 | return state; 137 | }; 138 | 139 | export const Container = createContainer(useOutput); 140 | 141 | function useDebounce(value: T, delay: number): T { 142 | // State and setters for debounced value 143 | const [debouncedValue, setDebouncedValue] = React.useState(value); 144 | React.useEffect( 145 | () => { 146 | // Update debounced value after delay 147 | const handler = setTimeout(() => { 148 | setDebouncedValue(value); 149 | }, delay); 150 | // Cancel the timeout if value changes (also on delay change or unmount) 151 | // This is how we prevent debounced value from updating if value is changed ... 152 | // .. within the delay period. Timeout gets cleared and restarted. 153 | return () => { 154 | clearTimeout(handler); 155 | }; 156 | }, 157 | [value, delay] // Only re-call effect if value or delay changes 158 | ); 159 | return debouncedValue; 160 | } 161 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/examples/ENS.abi.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"contract ENS","name":"_old","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"label","type":"bytes32"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"resolver","type":"address"}],"name":"NewResolver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"NewTTL","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"old","outputs":[{"internalType":"contract ENS","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"recordExists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"resolver","type":"address"},{"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"setRecord","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"resolver","type":"address"}],"name":"setResolver","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"label","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"}],"name":"setSubnodeOwner","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"label","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"resolver","type":"address"},{"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"setSubnodeRecord","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"setTTL","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"ttl","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | 3 | -------------------------------------------------------------------------------- /packages/web-ui/config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | const dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | // Don't include `.env.local` for `test` environment 21 | // since normally you expect tests to produce the same 22 | // results for everyone 23 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 24 | `${paths.dotenv}.${NODE_ENV}`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebook/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of webpack shims. 50 | // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | // We support configuring the sockjs pathname during development. 81 | // These settings let a developer run multiple simultaneous projects. 82 | // They are used as the connection `hostname`, `pathname` and `port` 83 | // in webpackHotDevClient. They are used as the `sockHost`, `sockPath` 84 | // and `sockPort` options in webpack-dev-server. 85 | WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST, 86 | WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, 87 | WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, 88 | // Whether or not react-refresh is enabled. 89 | // react-refresh is not 100% stable at this time, 90 | // which is why it's disabled by default. 91 | // It is defined here so it is available in the webpackHotDevClient. 92 | FAST_REFRESH: process.env.FAST_REFRESH !== 'false', 93 | } 94 | ); 95 | // Stringify all values so we can feed into webpack DefinePlugin 96 | const stringified = { 97 | 'process.env': Object.keys(raw).reduce((env, key) => { 98 | env[key] = JSON.stringify(raw[key]); 99 | return env; 100 | }, {}), 101 | }; 102 | 103 | return { raw, stringified }; 104 | } 105 | 106 | module.exports = getClientEnvironment; 107 | -------------------------------------------------------------------------------- /packages/abi-to-sol/README.md: -------------------------------------------------------------------------------- 1 | # abi-to-sol 2 | 3 | [![npm version](https://badge.fury.io/js/abi-to-sol.svg)](https://www.npmjs.com/package/abi-to-sol) 4 | [![Join the chat at https://gitter.im/gnidan/abi-to-sol](https://badges.gitter.im/gnidan/abi-to-sol.svg)](https://gitter.im/gnidan/abi-to-sol?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | [![gitpoap badge](https://public-api.gitpoap.io/v1/repo/gnidan/abi-to-sol/badge)](https://www.gitpoap.io/gh/gnidan/abi-to-sol) 6 | 7 | _Generate Solidity `interface` source from a given ABI JSON!_ 8 | 9 | ## Try online! 10 | 11 | Skip the terminal and just use the hosted 12 | [Web UI](https://gnidan.github.io/abi-to-sol). 13 | 14 | ## CLI instructions 15 | 16 | Install globally via: 17 | 18 | ```console 19 | $ npm install -g abi-to-sol 20 | ``` 21 | 22 | Installing locally should work fine as well, but you may have to jump through 23 | hoops to get the `abi-to-sol` script available on your PATH. 24 | 25 | ### Usage 26 | 27 | Pipe ABI JSON to stdin, get Solidity on stdout. 28 | 29 | ```console 30 | abi-to-sol [--solidity-version=] [--license=] [--validate] [] 31 | abi-to-sol -h | --help 32 | abi-to-sol --version 33 | ``` 34 | 35 | Options: 36 | 37 | ```console 38 | 39 | Name of generated interface. Default: MyInterface 40 | 41 | --validate 42 | Validate JSON before starting 43 | 44 | -V --solidity-version 45 | Version of Solidity (for pragma). Default: >=0.7.0 <0.9.0 46 | 47 | -L --license 48 | SPDX license identifier. default: UNLICENSED 49 | 50 | -h --help Show this screen. 51 | --version Show version. 52 | ``` 53 | 54 | ### Example 55 | 56 | Run the following command: 57 | 58 | ```console 59 | $ echo '[{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"label","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setSubnodeOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"ttl","type":"uint64"}],"name":"setTTL","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"ttl","outputs":[{"name":"","type":"uint64"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"resolver","type":"address"}],"name":"setResolver","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"label","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"resolver","type":"address"}],"name":"NewResolver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"ttl","type":"uint64"}],"name":"NewTTL","type":"event"}]' \ 60 | | npx abi-to-sol ENS 61 | ``` 62 | 63 | Get this output: 64 | 65 | ```solidity 66 | // SPDX-License-Identifier: UNLICENSED 67 | // !! THIS FILE WAS AUTOGENERATED BY abi-to-sol. SEE BELOW FOR SOURCE. !! 68 | pragma solidity ^0.7.0; 69 | pragma experimental ABIEncoderV2; 70 | 71 | interface ENS { 72 | function resolver(bytes32 node) external view returns (address); 73 | 74 | function owner(bytes32 node) external view returns (address); 75 | 76 | function setSubnodeOwner( 77 | bytes32 node, 78 | bytes32 label, 79 | address owner 80 | ) external; 81 | 82 | function setTTL(bytes32 node, uint64 ttl) external; 83 | 84 | function ttl(bytes32 node) external view returns (uint64); 85 | 86 | function setResolver(bytes32 node, address resolver) external; 87 | 88 | function setOwner(bytes32 node, address owner) external; 89 | 90 | event Transfer(bytes32 indexed node, address owner); 91 | event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); 92 | event NewResolver(bytes32 indexed node, address resolver); 93 | event NewTTL(bytes32 indexed node, uint64 ttl); 94 | } 95 | 96 | // THIS FILE WAS AUTOGENERATED FROM THE FOLLOWING ABI JSON: 97 | /* ... */ 98 | 99 | ``` 100 | 101 | ## Currently unsupported (PRs welcome! :wink:) 102 | 103 | - [User defined value types](https://blog.soliditylang.org/2021/09/27/user-defined-value-types/) 104 | 105 | ## Is this project useful to you? 106 | 107 | Feel free to donate to 108 | [gnidan.eth](https://etherscan.io/address/0xefef50ebacd8da3c13932ac204361b704eb8292c) 109 | ❤️ 110 | -------------------------------------------------------------------------------- /packages/abi-to-sol/src/declarations.ts: -------------------------------------------------------------------------------- 1 | import type {Abi as SchemaAbi} from "@truffle/contract-schema/spec"; 2 | import type * as Abi from "@truffle/abi-utils"; 3 | import { abiTupleSignature } from "@truffle/abi-utils"; 4 | 5 | import {Visitor, VisitOptions, dispatch, Node} from "./visitor"; 6 | 7 | export interface Component { 8 | name: string; 9 | type: string; 10 | signature?: string; 11 | } 12 | 13 | export interface Declaration { 14 | identifier?: string; 15 | components: Component[]; 16 | } 17 | 18 | export interface Declarations { 19 | signatureDeclarations: { 20 | [signature: string]: Declaration; 21 | }; 22 | containerSignatures: { 23 | [container: string]: string[]; 24 | } 25 | } 26 | 27 | export class DeclarationsCollector implements Visitor { 28 | visitAbi({node: nodes}: VisitOptions): Declarations { 29 | return nodes 30 | .map((node) => dispatch({node, visitor: this})) 31 | .reduce(mergeDeclarations, emptyDeclarations()); 32 | } 33 | 34 | visitEventEntry({node: entry}: VisitOptions): Declarations { 35 | return entry.inputs 36 | .map((node) => dispatch({node, visitor: this})) 37 | .reduce(mergeDeclarations, emptyDeclarations()); 38 | } 39 | 40 | visitErrorEntry({node: entry}: VisitOptions): Declarations { 41 | return entry.inputs 42 | .map((node) => dispatch({node, visitor: this})) 43 | .reduce(mergeDeclarations, emptyDeclarations()); 44 | } 45 | 46 | visitFunctionEntry({ 47 | node: entry, 48 | }: VisitOptions): Declarations { 49 | return [...entry.inputs, ...(entry.outputs || [])] 50 | .map((node) => dispatch({node, visitor: this})) 51 | .reduce(mergeDeclarations, emptyDeclarations()); 52 | } 53 | 54 | visitConstructorEntry({ 55 | node: entry, 56 | }: VisitOptions): Declarations { 57 | return entry.inputs 58 | .map((node) => dispatch({node, visitor: this})) 59 | .reduce(mergeDeclarations, emptyDeclarations()); 60 | } 61 | 62 | visitFallbackEntry({ 63 | node: entry, 64 | }: VisitOptions): Declarations { 65 | return emptyDeclarations(); 66 | } 67 | 68 | visitReceiveEntry({ 69 | node: entry, 70 | }: VisitOptions): Declarations { 71 | return emptyDeclarations(); 72 | } 73 | 74 | visitParameter({node: parameter}: VisitOptions): Declarations { 75 | if (!parameter.type.startsWith("tuple")) { 76 | return emptyDeclarations(); 77 | } 78 | 79 | let container = ""; 80 | const components = parameter.components || []; 81 | const signature = abiTupleSignature(components); 82 | const declaration: Declaration = { 83 | components: components.map(({name, type, components}) => 84 | !components 85 | ? {name, type} 86 | : { 87 | name, 88 | type, 89 | signature: abiTupleSignature(components), 90 | } 91 | ), 92 | }; 93 | 94 | if ("internalType" in parameter && parameter.internalType) { 95 | const match = parameter.internalType.match(/struct ([^\[]+).*/); 96 | if (match) { 97 | const possiblyQualifiedIdentifier = match[1]; 98 | const parts = possiblyQualifiedIdentifier.split("."); 99 | if (parts.length === 1) { 100 | declaration.identifier = parts[0]; 101 | } else if (parts.length === 2) { 102 | container = parts[0]; 103 | declaration.identifier = parts[1]; 104 | } 105 | } 106 | } 107 | 108 | const declarations = { 109 | signatureDeclarations: { 110 | [signature]: declaration 111 | }, 112 | containerSignatures: { 113 | [container]: [signature] 114 | } 115 | }; 116 | 117 | const componentDeclarations: Declarations = components 118 | .map((component: Abi.Parameter) => 119 | this.visitParameter({node: component}) 120 | ) 121 | .reduce(mergeDeclarations, emptyDeclarations()) 122 | 123 | 124 | return mergeDeclarations(declarations, componentDeclarations); 125 | } 126 | } 127 | 128 | export const collectDeclarations = (node: SchemaAbi | Node) => 129 | dispatch({ 130 | node, 131 | visitor: new DeclarationsCollector(), 132 | }); 133 | 134 | function mergeDeclarations( 135 | a: Declarations, 136 | b: Declarations 137 | ): Declarations { 138 | const declarations: Declarations = { 139 | signatureDeclarations: { 140 | ...a.signatureDeclarations, 141 | ...b.signatureDeclarations 142 | }, 143 | containerSignatures: { 144 | ...a.containerSignatures, 145 | // add b iteratively separately to merge arrays 146 | } 147 | }; 148 | 149 | for (const [container, signatures] of Object.entries(b.containerSignatures)) { 150 | const mergedSignatures = new Set([ 151 | ...(declarations.containerSignatures[container] || []), 152 | ...signatures 153 | ]) 154 | 155 | declarations.containerSignatures[container] = [...mergedSignatures]; 156 | } 157 | 158 | return declarations; 159 | } 160 | 161 | function emptyDeclarations(): Declarations { 162 | return { 163 | signatureDeclarations: {}, 164 | containerSignatures: {} 165 | }; 166 | } 167 | -------------------------------------------------------------------------------- /packages/abi-to-sol/src/declarations.test.ts: -------------------------------------------------------------------------------- 1 | import * as fc from "fast-check"; 2 | import {testProp} from "jest-fast-check"; 3 | import {Arbitrary} from "@truffle/abi-utils"; 4 | import * as Example from "../test/custom-example"; 5 | 6 | import {collectDeclarations} from "./declarations"; 7 | 8 | describe("collectDeclarations", () => { 9 | describe("arbitrary examples", () => { 10 | describe("for non-tuple parameters / event parameters", () => { 11 | testProp( 12 | "are empty", 13 | [fc.oneof(Arbitrary.Parameter(), Arbitrary.EventParameter())], 14 | (parameter) => { 15 | fc.pre(!parameter.type.startsWith("tuple")); 16 | 17 | expect(collectDeclarations(parameter)).toEqual({ 18 | signatureDeclarations: {}, 19 | containerSignatures: {} 20 | }); 21 | } 22 | ); 23 | }); 24 | 25 | describe("for tuple parameters with non-tuple components", () => { 26 | testProp( 27 | "have length 1", 28 | [fc.oneof(Arbitrary.Parameter(), Arbitrary.EventParameter())], 29 | (parameter) => { 30 | fc.pre(parameter.type.startsWith("tuple")); 31 | fc.pre( 32 | parameter.components.every( 33 | (component: any) => !component.type.startsWith("tuple") 34 | ) 35 | ); 36 | 37 | const declarations = collectDeclarations(parameter); 38 | expect(Object.keys(declarations.signatureDeclarations)).toHaveLength(1); 39 | 40 | const [declaration] = Object.values(declarations.signatureDeclarations); 41 | expect(declaration).toHaveProperty("components"); 42 | 43 | const {components} = declaration; 44 | expect(components).toHaveLength(parameter.components.length); 45 | 46 | for (const [index, component] of components.entries()) { 47 | expect(component.name).toEqual(parameter.components[index].name); 48 | } 49 | } 50 | ); 51 | }); 52 | 53 | describe("for tuple parameters with exactly one tuple component", () => { 54 | testProp( 55 | "have length 2", 56 | [fc.oneof(Arbitrary.Parameter(), Arbitrary.EventParameter())], 57 | (parameter) => { 58 | fc.pre(parameter.type.startsWith("tuple")); 59 | 60 | // find exactly one tuple-based component 61 | const tupleComponents = parameter.components.filter( 62 | (component: any) => component.type.startsWith("tuple") 63 | ); 64 | 65 | fc.pre(tupleComponents.length === 1); 66 | 67 | const [tupleComponent] = tupleComponents; 68 | 69 | fc.pre( 70 | tupleComponent.components.every( 71 | (component: any) => !component.type.startsWith("tuple") 72 | ) 73 | ); 74 | 75 | const declarations = collectDeclarations(parameter); 76 | expect(Object.keys(declarations.signatureDeclarations)).toHaveLength(2); 77 | } 78 | ); 79 | }); 80 | 81 | testProp( 82 | "produce only valid references to each other", 83 | [fc.oneof(Arbitrary.Parameter(), Arbitrary.EventParameter())], 84 | (parameter) => { 85 | fc.pre(parameter.type.startsWith("tuple")); 86 | 87 | const components = parameter.components || []; 88 | 89 | const declarations = collectDeclarations(parameter); 90 | 91 | for (const {components} of Object.values(declarations.signatureDeclarations)) { 92 | for (const {signature} of components) { 93 | if (signature) { 94 | expect(declarations.signatureDeclarations).toHaveProperty(signature); 95 | } 96 | } 97 | } 98 | } 99 | ); 100 | }); 101 | 102 | describe("custom example", () => { 103 | const declarations = collectDeclarations(Example.abi); 104 | 105 | for (const [structName, signature] of Object.entries( 106 | Example.expectedSignatures 107 | )) { 108 | describe(`struct ${structName}`, () => { 109 | it("exists in declarations", () => { 110 | expect(declarations.signatureDeclarations).toHaveProperty(signature); 111 | }); 112 | 113 | const expectedComponents = (Example.expectedDeclarations as any)[ 114 | structName 115 | ]; 116 | const declaration = declarations.signatureDeclarations[signature]; 117 | 118 | for (const [componentName, component] of Object.entries( 119 | expectedComponents 120 | )) { 121 | describe(`component ${componentName}`, () => { 122 | it("exists in declarations", () => { 123 | const names = declaration.components.map(({name}) => name); 124 | expect(names).toContain(componentName); 125 | }); 126 | 127 | const expectedComponent = (expectedComponents as any)[ 128 | componentName 129 | ]; 130 | 131 | const component: any = declaration.components.find( 132 | ({name}) => name === componentName 133 | ); 134 | 135 | it("has correct type", () => { 136 | expect(component.type).toEqual(expectedComponent.type); 137 | }); 138 | 139 | if (component.signature) { 140 | it("has correct signature", () => { 141 | expect(component.signature).toEqual( 142 | expectedComponent.signature 143 | ); 144 | }); 145 | } 146 | }); 147 | } 148 | }); 149 | } 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /packages/web-ui/scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const fs = require('fs'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const webpack = require('webpack'); 21 | const WebpackDevServer = require('webpack-dev-server'); 22 | const clearConsole = require('react-dev-utils/clearConsole'); 23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 24 | const { 25 | choosePort, 26 | createCompiler, 27 | prepareProxy, 28 | prepareUrls, 29 | } = require('react-dev-utils/WebpackDevServerUtils'); 30 | const openBrowser = require('react-dev-utils/openBrowser'); 31 | const semver = require('semver'); 32 | const paths = require('../config/paths'); 33 | const configFactory = require('../config/webpack.config'); 34 | const createDevServerConfig = require('../config/webpackDevServer.config'); 35 | const getClientEnvironment = require('../config/env'); 36 | const react = require(require.resolve('react', { paths: [paths.appPath] })); 37 | 38 | const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); 39 | const useYarn = fs.existsSync(paths.yarnLockFile); 40 | const isInteractive = process.stdout.isTTY; 41 | 42 | // Warn and crash if required files are missing 43 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 44 | process.exit(1); 45 | } 46 | 47 | // Tools like Cloud9 rely on this. 48 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 49 | const HOST = process.env.HOST || '0.0.0.0'; 50 | 51 | if (process.env.HOST) { 52 | console.log( 53 | chalk.cyan( 54 | `Attempting to bind to HOST environment variable: ${chalk.yellow( 55 | chalk.bold(process.env.HOST) 56 | )}` 57 | ) 58 | ); 59 | console.log( 60 | `If this was unintentional, check that you haven't mistakenly set it in your shell.` 61 | ); 62 | console.log( 63 | `Learn more here: ${chalk.yellow('https://cra.link/advanced-config')}` 64 | ); 65 | console.log(); 66 | } 67 | 68 | // We require that you explicitly set browsers and do not fall back to 69 | // browserslist defaults. 70 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 71 | checkBrowsers(paths.appPath, isInteractive) 72 | .then(() => { 73 | // We attempt to use the default port but if it is busy, we offer the user to 74 | // run on a different port. `choosePort()` Promise resolves to the next free port. 75 | return choosePort(HOST, DEFAULT_PORT); 76 | }) 77 | .then(port => { 78 | if (port == null) { 79 | // We have not found a port. 80 | return; 81 | } 82 | 83 | const config = configFactory('development'); 84 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 85 | const appName = require(paths.appPackageJson).name; 86 | 87 | const useTypeScript = fs.existsSync(paths.appTsConfig); 88 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; 89 | const urls = prepareUrls( 90 | protocol, 91 | HOST, 92 | port, 93 | paths.publicUrlOrPath.slice(0, -1) 94 | ); 95 | const devSocket = { 96 | warnings: warnings => 97 | devServer.sockWrite(devServer.sockets, 'warnings', warnings), 98 | errors: errors => 99 | devServer.sockWrite(devServer.sockets, 'errors', errors), 100 | }; 101 | // Create a webpack compiler that is configured with custom messages. 102 | const compiler = createCompiler({ 103 | appName, 104 | config, 105 | devSocket, 106 | urls, 107 | useYarn, 108 | useTypeScript, 109 | tscCompileOnError, 110 | webpack, 111 | }); 112 | // Load proxy config 113 | const proxySetting = require(paths.appPackageJson).proxy; 114 | const proxyConfig = prepareProxy( 115 | proxySetting, 116 | paths.appPublic, 117 | paths.publicUrlOrPath 118 | ); 119 | // Serve webpack assets generated by the compiler over a web server. 120 | const serverConfig = createDevServerConfig( 121 | proxyConfig, 122 | urls.lanUrlForConfig 123 | ); 124 | const devServer = new WebpackDevServer(compiler, serverConfig); 125 | // Launch WebpackDevServer. 126 | devServer.listen(port, HOST, err => { 127 | if (err) { 128 | return console.log(err); 129 | } 130 | if (isInteractive) { 131 | clearConsole(); 132 | } 133 | 134 | if (env.raw.FAST_REFRESH && semver.lt(react.version, '16.10.0')) { 135 | console.log( 136 | chalk.yellow( 137 | `Fast Refresh requires React 16.10 or higher. You are using React ${react.version}.` 138 | ) 139 | ); 140 | } 141 | 142 | console.log(chalk.cyan('Starting the development server...\n')); 143 | openBrowser(urls.localUrlForBrowser); 144 | }); 145 | 146 | ['SIGINT', 'SIGTERM'].forEach(function (sig) { 147 | process.on(sig, function () { 148 | devServer.close(); 149 | process.exit(); 150 | }); 151 | }); 152 | 153 | if (process.env.CI !== 'true') { 154 | // Gracefully exit when stdin ends 155 | process.stdin.on('end', function () { 156 | devServer.close(); 157 | process.exit(); 158 | }); 159 | } 160 | }) 161 | .catch(err => { 162 | if (err && err.message) { 163 | console.log(err.message); 164 | } 165 | process.exit(1); 166 | }); 167 | -------------------------------------------------------------------------------- /packages/abi-to-sol/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | "lib": ["es2017"], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "dist", /* Redirect output structure to the directory. */ 18 | "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | "resolveJsonModule": true, 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | 44 | /* Module Resolution Options */ 45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | 66 | /* Advanced Options */ 67 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 69 | }, 70 | "include": [ 71 | "./src/**/*.ts", 72 | "./test/**/*.ts", 73 | "./bin/**/*.ts" 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /packages/web-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abi-to-sol/web-ui", 3 | "version": "0.0.13", 4 | "private": true, 5 | "description": "Web UI for abi-to-sol", 6 | "author": "g. nicholas d'andrea ", 7 | "homepage": "https://gnidan.github.io/abi-to-sol", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/gnidan/abi-to-sol.git", 12 | "directory": "packages/web-ui" 13 | }, 14 | "scripts": { 15 | "start": "node scripts/start.js", 16 | "build": "CI=false node --max-old-space-size=4096 scripts/build.js", 17 | "test": "echo 'No tests defined'" 18 | }, 19 | "dependencies": { 20 | "@babel/core": "7.12.3", 21 | "@chakra-ui/icons": "^1.0.14", 22 | "@chakra-ui/react": "^1.6.5", 23 | "@emotion/react": "^11", 24 | "@emotion/styled": "^11", 25 | "@pmmmwh/react-refresh-webpack-plugin": "0.4.3", 26 | "@svgr/webpack": "5.5.0", 27 | "@testing-library/jest-dom": "^5.11.4", 28 | "@testing-library/react": "^11.1.0", 29 | "@testing-library/user-event": "^12.1.10", 30 | "@truffle/contract-schema": "^3.4.1", 31 | "@types/jest": "^26.0.15", 32 | "@types/node": "^12.0.0", 33 | "@types/react": "^17.0.0", 34 | "@types/react-dom": "^17.0.0", 35 | "@typescript-eslint/eslint-plugin": "^4.5.0", 36 | "@typescript-eslint/parser": "^4.5.0", 37 | "abi-to-sol": "^0.6.6", 38 | "add": "^2.0.6", 39 | "babel-eslint": "^10.1.0", 40 | "babel-jest": "^26.6.0", 41 | "babel-loader": "8.1.0", 42 | "babel-plugin-named-asset-import": "^0.3.7", 43 | "babel-preset-react-app": "^10.0.0", 44 | "bfj": "^7.0.2", 45 | "camelcase": "^6.1.0", 46 | "case-sensitive-paths-webpack-plugin": "2.3.0", 47 | "css-loader": "4.3.0", 48 | "dotenv": "8.2.0", 49 | "dotenv-expand": "5.1.0", 50 | "eslint": "^7.11.0", 51 | "eslint-config-react-app": "^6.0.0", 52 | "eslint-plugin-flowtype": "^5.2.0", 53 | "eslint-plugin-import": "^2.22.1", 54 | "eslint-plugin-jest": "^24.1.0", 55 | "eslint-plugin-jsx-a11y": "^6.3.1", 56 | "eslint-plugin-react": "^7.21.5", 57 | "eslint-plugin-react-hooks": "^4.2.0", 58 | "eslint-plugin-testing-library": "^3.9.2", 59 | "eslint-webpack-plugin": "^2.5.2", 60 | "file-loader": "6.1.1", 61 | "framer-motion": "^4", 62 | "fs-extra": "^9.0.1", 63 | "highlight.js": "^11.0.1", 64 | "highlightjs-solidity": "^1.1.1", 65 | "html-webpack-plugin": "4.5.0", 66 | "identity-obj-proxy": "3.0.0", 67 | "jest": "26.6.0", 68 | "jest-circus": "26.6.0", 69 | "jest-resolve": "26.6.0", 70 | "jest-watch-typeahead": "0.6.1", 71 | "mini-css-extract-plugin": "0.11.3", 72 | "optimize-css-assets-webpack-plugin": "5.0.4", 73 | "pnp-webpack-plugin": "1.6.4", 74 | "postcss-flexbugs-fixes": "4.2.1", 75 | "postcss-loader": "3.0.0", 76 | "postcss-normalize": "8.0.1", 77 | "postcss-preset-env": "6.7.0", 78 | "postcss-safe-parser": "5.0.2", 79 | "prettier": "^2.7.1", 80 | "prettier-plugin-solidity": "^1.0.0-dev.23", 81 | "prompts": "2.4.0", 82 | "react": "^17.0.2", 83 | "react-app-polyfill": "^2.0.0", 84 | "react-dev-utils": "^11.0.3", 85 | "react-dom": "^17.0.2", 86 | "react-icons": "^4.2.0", 87 | "react-refresh": "^0.8.3", 88 | "react-simple-code-editor": "^0.11.0", 89 | "react-use": "^17.2.4", 90 | "resolve": "1.18.1", 91 | "resolve-url-loader": "^3.1.2", 92 | "sass-loader": "^10.0.5", 93 | "semver": "7.3.2", 94 | "style-loader": "1.3.0", 95 | "terser-webpack-plugin": "4.2.3", 96 | "ts-pnp": "1.2.0", 97 | "typescript": "4.5.2", 98 | "unstated-next": "^1.1.0", 99 | "url-loader": "4.1.1", 100 | "web-vitals": "^1.0.1", 101 | "webpack": "4.44.2", 102 | "webpack-bundle-analyzer": "^4.4.2", 103 | "webpack-dev-server": "3.11.1", 104 | "webpack-manifest-plugin": "2.2.0", 105 | "workbox-webpack-plugin": "5.1.4", 106 | "yarn": "^1.22.10" 107 | }, 108 | "eslintConfig": { 109 | "extends": [ 110 | "react-app", 111 | "react-app/jest" 112 | ] 113 | }, 114 | "browserslist": { 115 | "production": [ 116 | ">0.2%", 117 | "not dead", 118 | "not op_mini all" 119 | ], 120 | "development": [ 121 | "last 1 chrome version", 122 | "last 1 firefox version", 123 | "last 1 safari version" 124 | ] 125 | }, 126 | "devDependencies": { 127 | "babel-jest": "^26" 128 | }, 129 | "jest": { 130 | "roots": [ 131 | "/src" 132 | ], 133 | "collectCoverageFrom": [ 134 | "src/**/*.{js,jsx,ts,tsx}", 135 | "!src/**/*.d.ts" 136 | ], 137 | "setupFiles": [ 138 | "react-app-polyfill/jsdom" 139 | ], 140 | "setupFilesAfterEnv": [ 141 | "/src/setupTests.ts" 142 | ], 143 | "testMatch": [ 144 | "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", 145 | "/src/**/*.{spec,test}.{js,jsx,ts,tsx}" 146 | ], 147 | "testEnvironment": "jsdom", 148 | "testRunner": "/Users/gnidan/src/abi-to-sol/web/node_modules/jest-circus/runner.js", 149 | "transform": { 150 | "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "/config/jest/babelTransform.js", 151 | "^.+\\.css$": "/config/jest/cssTransform.js", 152 | "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)": "/config/jest/fileTransform.js" 153 | }, 154 | "transformIgnorePatterns": [ 155 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$", 156 | "^.+\\.module\\.(css|sass|scss)$" 157 | ], 158 | "modulePaths": [], 159 | "moduleNameMapper": { 160 | "^react-native$": "react-native-web", 161 | "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy" 162 | }, 163 | "moduleFileExtensions": [ 164 | "web.js", 165 | "js", 166 | "web.ts", 167 | "ts", 168 | "web.tsx", 169 | "tsx", 170 | "json", 171 | "web.jsx", 172 | "jsx", 173 | "node" 174 | ], 175 | "watchPlugins": [ 176 | "jest-watch-typeahead/filename", 177 | "jest-watch-typeahead/testname" 178 | ], 179 | "resetMocks": true 180 | }, 181 | "babel": { 182 | "presets": [ 183 | "react-app" 184 | ] 185 | }, 186 | "bugs": { 187 | "url": "https://github.com/gnidan/abi-to-sol/issues" 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /packages/web-ui/config/webpackDevServer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); 5 | const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware'); 6 | const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); 7 | const ignoredFiles = require('react-dev-utils/ignoredFiles'); 8 | const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware'); 9 | const paths = require('./paths'); 10 | const getHttpsConfig = require('./getHttpsConfig'); 11 | 12 | const host = process.env.HOST || '0.0.0.0'; 13 | const sockHost = process.env.WDS_SOCKET_HOST; 14 | const sockPath = process.env.WDS_SOCKET_PATH; // default: '/sockjs-node' 15 | const sockPort = process.env.WDS_SOCKET_PORT; 16 | 17 | module.exports = function (proxy, allowedHost) { 18 | return { 19 | // WebpackDevServer 2.4.3 introduced a security fix that prevents remote 20 | // websites from potentially accessing local content through DNS rebinding: 21 | // https://github.com/webpack/webpack-dev-server/issues/887 22 | // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a 23 | // However, it made several existing use cases such as development in cloud 24 | // environment or subdomains in development significantly more complicated: 25 | // https://github.com/facebook/create-react-app/issues/2271 26 | // https://github.com/facebook/create-react-app/issues/2233 27 | // While we're investigating better solutions, for now we will take a 28 | // compromise. Since our WDS configuration only serves files in the `public` 29 | // folder we won't consider accessing them a vulnerability. However, if you 30 | // use the `proxy` feature, it gets more dangerous because it can expose 31 | // remote code execution vulnerabilities in backends like Django and Rails. 32 | // So we will disable the host check normally, but enable it if you have 33 | // specified the `proxy` setting. Finally, we let you override it if you 34 | // really know what you're doing with a special environment variable. 35 | disableHostCheck: 36 | !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true', 37 | // Enable gzip compression of generated files. 38 | compress: true, 39 | // Silence WebpackDevServer's own logs since they're generally not useful. 40 | // It will still show compile warnings and errors with this setting. 41 | clientLogLevel: 'none', 42 | // By default WebpackDevServer serves physical files from current directory 43 | // in addition to all the virtual build products that it serves from memory. 44 | // This is confusing because those files won’t automatically be available in 45 | // production build folder unless we copy them. However, copying the whole 46 | // project directory is dangerous because we may expose sensitive files. 47 | // Instead, we establish a convention that only files in `public` directory 48 | // get served. Our build script will copy `public` into the `build` folder. 49 | // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: 50 | // 51 | // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. 52 | // Note that we only recommend to use `public` folder as an escape hatch 53 | // for files like `favicon.ico`, `manifest.json`, and libraries that are 54 | // for some reason broken when imported through webpack. If you just want to 55 | // use an image, put it in `src` and `import` it from JavaScript instead. 56 | contentBase: paths.appPublic, 57 | contentBasePublicPath: paths.publicUrlOrPath, 58 | // By default files from `contentBase` will not trigger a page reload. 59 | watchContentBase: true, 60 | // Enable hot reloading server. It will provide WDS_SOCKET_PATH endpoint 61 | // for the WebpackDevServer client so it can learn when the files were 62 | // updated. The WebpackDevServer client is included as an entry point 63 | // in the webpack development configuration. Note that only changes 64 | // to CSS are currently hot reloaded. JS changes will refresh the browser. 65 | hot: true, 66 | // Use 'ws' instead of 'sockjs-node' on server since we're using native 67 | // websockets in `webpackHotDevClient`. 68 | transportMode: 'ws', 69 | // Prevent a WS client from getting injected as we're already including 70 | // `webpackHotDevClient`. 71 | injectClient: false, 72 | // Enable custom sockjs pathname for websocket connection to hot reloading server. 73 | // Enable custom sockjs hostname, pathname and port for websocket connection 74 | // to hot reloading server. 75 | sockHost, 76 | sockPath, 77 | sockPort, 78 | // It is important to tell WebpackDevServer to use the same "publicPath" path as 79 | // we specified in the webpack config. When homepage is '.', default to serving 80 | // from the root. 81 | // remove last slash so user can land on `/test` instead of `/test/` 82 | publicPath: paths.publicUrlOrPath.slice(0, -1), 83 | // WebpackDevServer is noisy by default so we emit custom message instead 84 | // by listening to the compiler events with `compiler.hooks[...].tap` calls above. 85 | quiet: true, 86 | // Reportedly, this avoids CPU overload on some systems. 87 | // https://github.com/facebook/create-react-app/issues/293 88 | // src/node_modules is not ignored to support absolute imports 89 | // https://github.com/facebook/create-react-app/issues/1065 90 | watchOptions: { 91 | ignored: ignoredFiles(paths.appSrc), 92 | }, 93 | https: getHttpsConfig(), 94 | host, 95 | overlay: false, 96 | historyApiFallback: { 97 | // Paths with dots should still use the history fallback. 98 | // See https://github.com/facebook/create-react-app/issues/387. 99 | disableDotRule: true, 100 | index: paths.publicUrlOrPath, 101 | }, 102 | public: allowedHost, 103 | // `proxy` is run between `before` and `after` `webpack-dev-server` hooks 104 | proxy, 105 | before(app, server) { 106 | // Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware` 107 | // middlewares before `redirectServedPath` otherwise will not have any effect 108 | // This lets us fetch source contents from webpack for the error overlay 109 | app.use(evalSourceMapMiddleware(server)); 110 | // This lets us open files from the runtime error overlay. 111 | app.use(errorOverlayMiddleware()); 112 | 113 | if (fs.existsSync(paths.proxySetup)) { 114 | // This registers user provided middleware for proxy reasons 115 | require(paths.proxySetup)(app); 116 | } 117 | }, 118 | after(app) { 119 | // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match 120 | app.use(redirectServedPath(paths.publicUrlOrPath)); 121 | 122 | // This service worker file is effectively a 'no-op' that will reset any 123 | // previous service worker registered for the same host:port combination. 124 | // We do this in development to avoid hitting the production cache if 125 | // it used the same host and port. 126 | // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432 127 | app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath)); 128 | }, 129 | }; 130 | }; 131 | -------------------------------------------------------------------------------- /packages/web-ui/scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const path = require('path'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const fs = require('fs-extra'); 21 | const bfj = require('bfj'); 22 | const webpack = require('webpack'); 23 | const configFactory = require('../config/webpack.config'); 24 | const paths = require('../config/paths'); 25 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 26 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 27 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 28 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 29 | const printBuildError = require('react-dev-utils/printBuildError'); 30 | 31 | const measureFileSizesBeforeBuild = 32 | FileSizeReporter.measureFileSizesBeforeBuild; 33 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | 36 | // These sizes are pretty large. We'll warn for bundles exceeding them. 37 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 38 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 39 | 40 | const isInteractive = process.stdout.isTTY; 41 | 42 | // Warn and crash if required files are missing 43 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 44 | process.exit(1); 45 | } 46 | 47 | const argv = process.argv.slice(2); 48 | const writeStatsJson = argv.indexOf('--stats') !== -1; 49 | 50 | // Generate configuration 51 | const config = configFactory('production'); 52 | 53 | // We require that you explicitly set browsers and do not fall back to 54 | // browserslist defaults. 55 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 56 | checkBrowsers(paths.appPath, isInteractive) 57 | .then(() => { 58 | // First, read the current file sizes in build directory. 59 | // This lets us display how much they changed later. 60 | return measureFileSizesBeforeBuild(paths.appBuild); 61 | }) 62 | .then(previousFileSizes => { 63 | // Remove all content but keep the directory so that 64 | // if you're in it, you don't end up in Trash 65 | fs.emptyDirSync(paths.appBuild); 66 | // Merge with the public folder 67 | copyPublicFolder(); 68 | // Start the webpack build 69 | return build(previousFileSizes); 70 | }) 71 | .then( 72 | ({ stats, previousFileSizes, warnings }) => { 73 | if (warnings.length) { 74 | console.log(chalk.yellow('Compiled with warnings.\n')); 75 | console.log(warnings.join('\n\n')); 76 | console.log( 77 | '\nSearch for the ' + 78 | chalk.underline(chalk.yellow('keywords')) + 79 | ' to learn more about each warning.' 80 | ); 81 | console.log( 82 | 'To ignore, add ' + 83 | chalk.cyan('// eslint-disable-next-line') + 84 | ' to the line before.\n' 85 | ); 86 | } else { 87 | console.log(chalk.green('Compiled successfully.\n')); 88 | } 89 | 90 | console.log('File sizes after gzip:\n'); 91 | printFileSizesAfterBuild( 92 | stats, 93 | previousFileSizes, 94 | paths.appBuild, 95 | WARN_AFTER_BUNDLE_GZIP_SIZE, 96 | WARN_AFTER_CHUNK_GZIP_SIZE 97 | ); 98 | console.log(); 99 | 100 | const appPackage = require(paths.appPackageJson); 101 | const publicUrl = paths.publicUrlOrPath; 102 | const publicPath = config.output.publicPath; 103 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 104 | printHostingInstructions( 105 | appPackage, 106 | publicUrl, 107 | publicPath, 108 | buildFolder, 109 | useYarn 110 | ); 111 | }, 112 | err => { 113 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; 114 | if (tscCompileOnError) { 115 | console.log( 116 | chalk.yellow( 117 | 'Compiled with the following type errors (you may want to check these before deploying your app):\n' 118 | ) 119 | ); 120 | printBuildError(err); 121 | } else { 122 | console.log(chalk.red('Failed to compile.\n')); 123 | printBuildError(err); 124 | process.exit(1); 125 | } 126 | } 127 | ) 128 | .catch(err => { 129 | if (err && err.message) { 130 | console.log(err.message); 131 | } 132 | process.exit(1); 133 | }); 134 | 135 | // Create the production build and print the deployment instructions. 136 | function build(previousFileSizes) { 137 | console.log('Creating an optimized production build...'); 138 | 139 | const compiler = webpack(config); 140 | return new Promise((resolve, reject) => { 141 | compiler.run((err, stats) => { 142 | let messages; 143 | if (err) { 144 | if (!err.message) { 145 | return reject(err); 146 | } 147 | 148 | let errMessage = err.message; 149 | 150 | // Add additional information for postcss errors 151 | if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) { 152 | errMessage += 153 | '\nCompileError: Begins at CSS selector ' + 154 | err['postcssNode'].selector; 155 | } 156 | 157 | messages = formatWebpackMessages({ 158 | errors: [errMessage], 159 | warnings: [], 160 | }); 161 | } else { 162 | messages = formatWebpackMessages( 163 | stats.toJson({ all: false, warnings: true, errors: true }) 164 | ); 165 | } 166 | if (messages.errors.length) { 167 | // Only keep the first error. Others are often indicative 168 | // of the same problem, but confuse the reader with noise. 169 | if (messages.errors.length > 1) { 170 | messages.errors.length = 1; 171 | } 172 | return reject(new Error(messages.errors.join('\n\n'))); 173 | } 174 | if ( 175 | process.env.CI && 176 | (typeof process.env.CI !== 'string' || 177 | process.env.CI.toLowerCase() !== 'false') && 178 | messages.warnings.length 179 | ) { 180 | console.log( 181 | chalk.yellow( 182 | '\nTreating warnings as errors because process.env.CI = true.\n' + 183 | 'Most CI servers set it automatically.\n' 184 | ) 185 | ); 186 | return reject(new Error(messages.warnings.join('\n\n'))); 187 | } 188 | 189 | const resolveArgs = { 190 | stats, 191 | previousFileSizes, 192 | warnings: messages.warnings, 193 | }; 194 | 195 | if (writeStatsJson) { 196 | return bfj 197 | .write(paths.appBuild + '/bundle-stats.json', stats.toJson()) 198 | .then(() => resolve(resolveArgs)) 199 | .catch(error => reject(new Error(error))); 200 | } 201 | 202 | return resolve(resolveArgs); 203 | }); 204 | }); 205 | } 206 | 207 | function copyPublicFolder() { 208 | fs.copySync(paths.appPublic, paths.appBuild, { 209 | dereference: true, 210 | filter: file => file !== paths.appHtml, 211 | }); 212 | } 213 | -------------------------------------------------------------------------------- /packages/web-ui/src/defaultAbi.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address","name":"_WETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsIn","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"reserveA","type":"uint256"},{"internalType":"uint256","name":"reserveB","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETHSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermit","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermitSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityWithPermit","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapETHForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETHSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] 2 | 3 | -------------------------------------------------------------------------------- /packages/web-ui/src/abi/examples/UniswapV2Router02.abi.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address","name":"_WETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsIn","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"reserveA","type":"uint256"},{"internalType":"uint256","name":"reserveB","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETHSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermit","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermitSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityWithPermit","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapETHForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETHSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] 2 | 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # abi-to-sol changelog 2 | 3 | ## vNext 4 | 5 | ### New features 6 | 7 | - Allow producing embeddable output, e.g. no pragma 8 | ([#91](https://github.com/gnidan/abi-to-sol/pull/91) by 9 | [@gnidan](https://github.com/gnidan)) 10 | 11 | ### Project updates 12 | 13 | - Add GitPOAP badge to README 14 | ([#96](https://github.com/gnidan/abi-to-sol/pull/96) by 15 | [@gnidan](https://github.com/gnidan)) 16 | 17 | ## v0.6.6 18 | 19 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.6.6). 20 | 21 | ### Internal improvements 22 | 23 | - Cleanup imports ([#88](https://github.com/gnidan/abi-to-sol/pull/88) by 24 | [@gnidan](https://github.com/gnidan)) 25 | 26 | ### Dependency updates 27 | 28 | - Bump @truffle/abi-utils to ^0.3.0, drop dependency @truffle/codec 29 | ([#92](https://github.com/gnidan/abi-to-sol/pull/92) by 30 | [@benjamincburns](https://github.com/benjamincburns)) 31 | 32 | ## v0.6.5 33 | 34 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.6.5). 35 | 36 | ### Project updates 37 | 38 | - Update README to remove npx support 39 | ([#83](https://github.com/gnidan/abi-to-sol/pull/83) by 40 | [@gnidan](https://github.com/gnidan)) 41 | 42 | ### Dependency updates 43 | 44 | - Bump terser from 4.8.0 to 4.8.1 45 | ([#84](https://github.com/gnidan/abi-to-sol/pull/84) by 46 | [@dependabot](https://github.com/dependabot)) 47 | 48 | - Update dependency: @truffle/codec@^0.14.0 49 | ([#85](https://github.com/gnidan/abi-to-sol/pull/85) by 50 | [@gnidan](https://github.com/gnidan)) 51 | 52 | ## v0.6.4 53 | 54 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.6.4). 55 | 56 | ### Dependency updates 57 | 58 | - Upgrade prettier-plugin-solidity and prettier to latest 59 | ([#80](https://github.com/gnidan/abi-to-sol/pull/80) by 60 | [@gnidan](https://github.com/gnidan)) 61 | 62 | ## v0.6.3 63 | 64 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.6.3). 65 | 66 | ### Bug fixes 67 | 68 | - Produce valid syntax for function type params 69 | ([#77](https://github.com/gnidan/abi-to-sol/pull/77) by 70 | [@gnidan](https://github.com/gnidan)) 71 | 72 | ### Dependency updates 73 | 74 | - Bump parse-url from 6.0.0 to 6.0.2 75 | ([#75](https://github.com/gnidan/abi-to-sol/pull/75) by 76 | [@dependabot](https://github.com/dependabot)) 77 | 78 | ## v0.6.2 79 | 80 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.6.2). 81 | 82 | ### Dependency updates 83 | 84 | - Bump @truffle/codec from ^0.11.24 to ^0.13.2 85 | ([#72](https://github.com/gnidan/abi-to-sol/pull/72) by 86 | [@cds-amal](https://github.com/cds-amal)) 87 | 88 | ## v0.6.1 89 | 90 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.6.1). 91 | 92 | ### New features 93 | 94 | - Add `--no-attribution` and `--no-source` flags 95 | ([#69](https://github.com/gnidan/abi-to-sol/pull/69) by 96 | [@gnidan](https://github.com/gnidan)) 97 | 98 | ### Internal improvements 99 | 100 | - Update to Node.js LTS ([#63](https://github.com/gnidan/abi-to-sol/pull/63) by 101 | [@gnidan](https://github.com/gnidan)) 102 | 103 | - Update copyright notice ([#64](https://github.com/gnidan/abi-to-sol/pull/64) 104 | by [@gnidan](https://github.com/gnidan)) 105 | 106 | - Add version feature knowledge for user-defined value types 107 | ([#65](https://github.com/gnidan/abi-to-sol/pull/65) by 108 | [@gnidan](https://github.com/gnidan)) 109 | 110 | ### Dependency updates 111 | 112 | - Bump eventsource from 1.1.0 to 1.1.1 113 | ([#66](https://github.com/gnidan/abi-to-sol/pull/66) by 114 | [@dependabot](https://github.com/dependabot)) 115 | 116 | ## v0.5.3 117 | 118 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.5.3). 119 | 120 | ### Dependency updates 121 | 122 | - Bump follow-redirects from 1.14.1 to 1.14.7 123 | ([#50](https://github.com/gnidan/abi-to-sol/pull/50) by 124 | [@dependabot](https://github.com/dependabot)) 125 | 126 | - Bump nanoid from 3.1.23 to 3.2.0 127 | ([#51](https://github.com/gnidan/abi-to-sol/pull/51) by 128 | [@dependabot](https://github.com/dependabot)) 129 | 130 | - Bump node-fetch from 2.6.1 to 2.6.7 131 | ([#52](https://github.com/gnidan/abi-to-sol/pull/52) by 132 | [@dependabot](https://github.com/dependabot)) 133 | 134 | - Bump trim-off-newlines from 1.0.1 to 1.0.3 135 | ([#53](https://github.com/gnidan/abi-to-sol/pull/53) by 136 | [@dependabot](https://github.com/dependabot)) 137 | 138 | - Upgrade @truffle/codec to ^0.11.24 139 | ([#54](https://github.com/gnidan/abi-to-sol/pull/54) by 140 | [@gnidan](https://github.com/gnidan)) 141 | 142 | - Bump follow-redirects from 1.14.7 to 1.14.8 143 | ([#55](https://github.com/gnidan/abi-to-sol/pull/55) by 144 | [@dependabot](https://github.com/dependabot)) 145 | 146 | - Bump url-parse from 1.5.3 to 1.5.10 147 | ([#57](https://github.com/gnidan/abi-to-sol/pull/57) by 148 | [@dependabot](https://github.com/dependabot)) 149 | 150 | - Bump minimist from 1.2.5 to 1.2.6 151 | ([#58](https://github.com/gnidan/abi-to-sol/pull/58) by 152 | [@dependabot](https://github.com/dependabot)) 153 | 154 | - Bump async from 2.6.3 to 2.6.4 155 | ([#59](https://github.com/gnidan/abi-to-sol/pull/59) by 156 | [@dependabot](https://github.com/dependabot)) 157 | 158 | - Bump simple-get from 2.8.1 to 2.8.2 159 | ([#60](https://github.com/gnidan/abi-to-sol/pull/60) by 160 | [@dependabot](https://github.com/dependabot)) 161 | 162 | ## v0.5.2 163 | 164 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.5.2). 165 | 166 | ### Dependency updates 167 | 168 | - Update dependency: typescript@4.5.2 169 | ([#43](https://github.com/gnidan/abi-to-sol/pull/43) by 170 | [@gnidan](https://github.com/gnidan)) 171 | 172 | - Update dependency: better-ajv-errors@^0.8.2 173 | ([#43](https://github.com/gnidan/abi-to-sol/pull/44) by 174 | [@gnidan](https://github.com/gnidan)) 175 | 176 | - Bump tar from 4.4.13 to 4.4.19 177 | ([#45](https://github.com/gnidan/abi-to-sol/pull/45) by 178 | [@dependabot](https://github.com/dependabot)) 179 | 180 | - Bump tmpl from 1.0.4 to 1.0.5 181 | ([#46](https://github.com/gnidan/abi-to-sol/pull/46) by 182 | [@dependabot](https://github.com/dependabot)) 183 | 184 | - Bump url-parse from 1.5.1 to 1.5.3 185 | ([#47](https://github.com/gnidan/abi-to-sol/pull/47) by 186 | [@dependabot](https://github.com/dependabot)) 187 | 188 | ## v0.5.1 189 | 190 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.5.1). 191 | 192 | ### Internal improvements 193 | 194 | - Add script for upgrading dependencies 195 | ([#37](https://github.com/gnidan/abi-to-sol/pull/37) by 196 | [@gnidan](https://github.com/gnidan)) 197 | 198 | - Upgrade typescript to ^4.4.4 199 | ([#38](https://github.com/gnidan/abi-to-sol/pull/38) by 200 | [@gnidan](https://github.com/gnidan)) 201 | 202 | ## v0.5.0 203 | 204 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.5.0). 205 | 206 | ### New features 207 | 208 | - Support custom ABI errors ([#33](https://github.com/gnidan/abi-to-sol/pull/33) 209 | by [@gnidan](https://github.com/gnidan)) 210 | 211 | ### Fixes 212 | 213 | - Properly error when generating structs on unsupported Solidity versions 214 | ([#32](https://github.com/gnidan/abi-to-sol/pull/32) by 215 | [@gnidan](https://github.com/gnidan)) 216 | - Fix version-feature checks to succeed only when entire range has feature 217 | ([#35](https://github.com/gnidan/abi-to-sol/pull/35) by 218 | [@gnidan](https://github.com/gnidan)) 219 | 220 | ### Internal improvements 221 | 222 | - Enforce CHANGELOG entries in pull requests 223 | ([#30](https://github.com/gnidan/abi-to-sol/pull/30) by 224 | [@gnidan](https://github.com/gnidan)) 225 | 226 | ### Dependency updates 227 | 228 | - Update dependency: @truffle/abi-utils@^0.2.2 229 | ([#29](https://github.com/gnidan/abi-to-sol/pull/29) by 230 | [@gnidan](https://github.com/gnidan)) 231 | 232 | ### web-ui changes 233 | 234 | - Switch to react-simple-code-editor 235 | ([#31](https://github.com/gnidan/abi-to-sol/pull/31) by 236 | [@gnidan](https://github.com/gnidan)) 237 | 238 | ## v0.4.0 239 | 240 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.4.0). 241 | 242 | ### New features 243 | 244 | - Error upon Solidity version syntax ambiguity 245 | ([#21](https://github.com/gnidan/abi-to-sol/pull/21) by 246 | [@gnidan](https://github.com/gnidan)) 247 | 248 | ### Enhancements 249 | 250 | - Include package version in autogen notice 251 | ([#25](https://github.com/gnidan/abi-to-sol/pull/25) by 252 | [@gnidan](https://github.com/gnidan)) 253 | - Only emit `pragma experimental ABIEncoderV2` when necessary 254 | ([#22](https://github.com/gnidan/abi-to-sol/pull/22) by 255 | [@gnidan](https://github.com/gnidan)) 256 | 257 | ### Bug fixes 258 | 259 | - Put structs in the right places 260 | ([#24](https://github.com/gnidan/abi-to-sol/pull/24) by 261 | [@gnidan](https://github.com/gnidan)) 262 | 263 | ### Housekeeping 264 | 265 | - Note lack of support for ABI errors 266 | ([#26](https://github.com/gnidan/abi-to-sol/pull/26) by 267 | [@gnidan](https://github.com/gnidan)) 268 | - Change default version to >=0.7.0 <0.9.0 269 | ([#20](https://github.com/gnidan/abi-to-sol/pull/20) by 270 | [@gnidan](https://github.com/gnidan)) 271 | 272 | ### Internal improvements 273 | 274 | - Refactor things a bit ([#23](https://github.com/gnidan/abi-to-sol/pull/23) by 275 | [@gnidan](https://github.com/gnidan)) 276 | 277 | ### web-ui changes 278 | 279 | - Show errors from generateSolidity 280 | ([#19](https://github.com/gnidan/abi-to-sol/pull/19) by 281 | [@gnidan](https://github.com/gnidan)) 282 | 283 | ## v0.3.0 284 | 285 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.3.0). 286 | 287 | ### New features 288 | 289 | - Export GenerateSolidityOptions interface type 290 | ([#13](https://github.com/gnidan/abi-to-sol/pull/13) by @gnidan) 291 | - Allow disabling prettier ([#14](https://github.com/gnidan/abi-to-sol/pull/14) 292 | by [@gnidan]) 293 | - Add web UI ([#16](https://github.com/gnidan/abi-to-sol/pull/16) by [@gnidan]) 294 | - Begin to differ output based on Solidity version / features used by ABI 295 | ([#17](https://github.com/gnidan/abi-to-sol/pull/17) by [@gnidan]) 296 | 297 | ### Bug fixes 298 | 299 | - Replace invalid characters in internalTypes 300 | ([#15](https://github.com/gnidan/abi-to-sol/pull/15) by [@gnidan]) 301 | 302 | ## v0.2.1 303 | 304 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.2.1). 305 | 306 | ### Dependency updates 307 | 308 | - Remove unused @drizzle/store dependency 309 | ([#12](https://github.com/gnidan/abi-to-sol/pull/12) by 310 | [@gnidan](https://github.com/gnidan)) 311 | 312 | ## v0.2.0 313 | 314 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.2.0). 315 | 316 | ### Dependency updates 317 | 318 | - Switch to using @truffle/abi-utils 319 | ([#11](https://github.com/gnidan/abi-to-sol/pull/11) by 320 | [@gnidan](https://github.com/gnidan)) 321 | 322 | ## v0.1.6 323 | 324 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.1.6). 325 | 326 | ### Enhancements 327 | 328 | - Support anonymous events ([#7](https://github.com/gnidan/abi-to-sol/pull/7) by 329 | [@gnidan](https://github.com/gnidan)) 330 | 331 | ### Dependency updates 332 | 333 | - Upgrade prettier-plugin-solidity to v1.0.0-alpha.59 334 | ([#6](https://github.com/gnidan/abi-to-sol/pull/6) by 335 | [@gnidan](https://github.com/gnidan)) 336 | 337 | ### Internal improvements 338 | 339 | - Fix incorrect type string ([#5](https://github.com/gnidan/abi-to-sol/pull/5) 340 | by [@gnidan](https://github.com/gnidan)) 341 | 342 | ## v0.1.5 343 | 344 | See [release notes](https://github.com/gnidan/abi-to-sol/releases/tag/v0.1.5). 345 | 346 | ### Enhancements 347 | 348 | - Broaden default Solidity version to `>=0.5.0 <0.8.0` (and centralize 349 | defaulting logic) ([#1](https://github.com/gnidan/abi-to-sol/pull/1) by 350 | [@gnidan](https://github.com/gnidan)) 351 | - Add runtime source map support 352 | ([#4](https://github.com/gnidan/abi-to-sol/pull/4) by 353 | [@gnidan](https://github.com/gnidan)) 354 | 355 | ### Internal improvements 356 | 357 | - Add a Gitter chat badge to README.md 358 | ([#3](https://github.com/gnidan/abi-to-sol/pull/3) by 359 | [@gitter-badger](https://github.com/gitter-badger)) 360 | - Fix invalid package.json script 361 | ([#2](https://github.com/gnidan/abi-to-sol/pull/2) by 362 | [@gnidan](https://github.com/gnidan)) 363 | -------------------------------------------------------------------------------- /packages/abi-to-sol/src/solidity.ts: -------------------------------------------------------------------------------- 1 | import type Prettier from "prettier"; 2 | import type * as Abi from "@truffle/abi-utils"; 3 | import { abiTupleSignature } from "@truffle/abi-utils"; 4 | import type {Abi as SchemaAbi} from "@truffle/contract-schema/spec"; 5 | import { version } from "../package.json"; 6 | import {Visitor, VisitOptions, dispatch, Node} from "./visitor"; 7 | import { forRange, VersionFeatures, mixed } from "./version-features"; 8 | import { GenerateSolidityOptions, GenerateSolidityMode } from "./options"; 9 | import * as defaults from "./defaults"; 10 | import { 11 | Component, 12 | Declaration, 13 | Declarations, 14 | collectDeclarations, 15 | } from "./declarations"; 16 | import { collectAbiFeatures, AbiFeatures } from "./abi-features"; 17 | 18 | let prettier: typeof Prettier 19 | try { 20 | prettier = require("prettier"); 21 | } catch { 22 | // no-op 23 | } 24 | 25 | export const generateSolidity = ({ 26 | abi, 27 | name = defaults.name, 28 | solidityVersion = defaults.solidityVersion, 29 | license = defaults.license, 30 | mode = defaults.mode, 31 | outputAttribution = defaults.outputAttribution, 32 | outputSource = defaults.outputSource, 33 | prettifyOutput = prettier && defaults.prettifyOutput, 34 | }: GenerateSolidityOptions) => { 35 | if (!prettier && prettifyOutput) { 36 | throw new Error("Could not require() prettier"); 37 | } 38 | 39 | const versionFeatures = forRange(solidityVersion); 40 | const abiFeatures = collectAbiFeatures(abi); 41 | const declarations = collectDeclarations(abi); 42 | 43 | const generated = dispatch({ 44 | node: abi, 45 | visitor: new SolidityGenerator({ 46 | name, 47 | solidityVersion, 48 | license, 49 | mode, 50 | outputAttribution, 51 | outputSource, 52 | versionFeatures, 53 | abiFeatures, 54 | declarations, 55 | }), 56 | }); 57 | 58 | if (!prettifyOutput) { 59 | return generated; 60 | } 61 | 62 | try { 63 | return prettier.format(generated, { 64 | plugins: ["prettier-plugin-solidity"], 65 | // @ts-ignore 66 | parser: "solidity-parse", 67 | }); 68 | } catch (error) { 69 | return generated; 70 | } 71 | }; 72 | 73 | interface Context { 74 | interfaceName?: string; 75 | parameterModifiers?: (parameter: Abi.Parameter) => string[]; 76 | } 77 | 78 | type Visit = VisitOptions; 79 | 80 | type ConstructorOptions = { 81 | versionFeatures: VersionFeatures; 82 | abiFeatures: AbiFeatures; 83 | declarations: Declarations; 84 | } & Required< 85 | Omit 86 | >; 87 | 88 | const shimGlobalInterfaceName = "__Structs"; 89 | 90 | class SolidityGenerator implements Visitor { 91 | private name: string; 92 | private license: string; 93 | private mode: GenerateSolidityMode; 94 | private solidityVersion: string; 95 | private outputAttribution: boolean; 96 | private outputSource: boolean; 97 | private versionFeatures: VersionFeatures; 98 | private abiFeatures: AbiFeatures; 99 | private declarations: Declarations; 100 | private identifiers: { 101 | [signature: string]: { 102 | identifier: string; 103 | container?: string; 104 | } 105 | }; 106 | 107 | constructor({ 108 | name, 109 | license, 110 | mode, 111 | outputAttribution, 112 | outputSource, 113 | solidityVersion, 114 | versionFeatures, 115 | abiFeatures, 116 | declarations, 117 | }: ConstructorOptions) { 118 | this.name = name; 119 | this.license = license; 120 | this.mode = mode; 121 | this.solidityVersion = solidityVersion; 122 | this.versionFeatures = versionFeatures; 123 | this.abiFeatures = abiFeatures; 124 | this.declarations = declarations; 125 | this.outputAttribution = outputAttribution; 126 | this.outputSource = outputSource; 127 | 128 | this.identifiers = {}; 129 | let index = 0; 130 | for (const [container, signatures] of Object.entries(declarations.containerSignatures)) { 131 | for (const signature of signatures) { 132 | const { 133 | identifier = `S_${index++}` 134 | } = declarations.signatureDeclarations[signature]; 135 | 136 | if (container === "" && this.versionFeatures["global-structs"] !== true) { 137 | this.identifiers[signature] = { 138 | container: shimGlobalInterfaceName, 139 | identifier 140 | }; 141 | } else if (container === "") { 142 | this.identifiers[signature] = { identifier }; 143 | } else { 144 | this.identifiers[signature] = { 145 | container, 146 | identifier 147 | } 148 | } 149 | } 150 | } 151 | } 152 | 153 | visitAbi({node: abi}: Visit) { 154 | switch (this.mode) { 155 | case GenerateSolidityMode.Normal: { 156 | return [ 157 | this.generateHeader(), 158 | this.generateInterface(abi), 159 | this.generateDeclarations(), 160 | this.generateAutogeneratedNotice(abi), 161 | ].join("\n\n"); 162 | } 163 | case GenerateSolidityMode.Embedded: { 164 | return [ 165 | this.generateInterface(abi), 166 | this.generateDeclarations(), 167 | ].join("\n\n"); 168 | } 169 | } 170 | } 171 | 172 | visitFunctionEntry({node: entry, context}: Visit): string { 173 | const {name, inputs, stateMutability} = entry; 174 | 175 | return [ 176 | `function ${name}(`, 177 | entry.inputs.map((node) => 178 | dispatch({ 179 | node, 180 | visitor: this, 181 | context: { 182 | ...context, 183 | parameterModifiers: (parameter: Abi.Parameter) => 184 | parameter.type.startsWith("tuple") || 185 | parameter.type.includes("[") || 186 | parameter.type === "bytes" || 187 | parameter.type === "string" 188 | ? [this.generateArrayParameterLocation(parameter)] 189 | : [], 190 | }, 191 | }) 192 | ), 193 | `) external`, 194 | this.generateStateMutability(entry), 195 | entry.outputs && entry.outputs.length > 0 196 | ? [ 197 | `returns (`, 198 | entry.outputs 199 | .map((node) => 200 | dispatch({ 201 | node, 202 | visitor: this, 203 | context: { 204 | parameterModifiers: (parameter: Abi.Parameter) => 205 | parameter.type.startsWith("tuple") || 206 | parameter.type.includes("[") || 207 | parameter.type === "bytes" || 208 | parameter.type === "string" 209 | ? ["memory"] 210 | : [], 211 | }, 212 | }) 213 | ) 214 | .join(", "), 215 | `)`, 216 | ].join("") 217 | : ``, 218 | `;`, 219 | ].join(" "); 220 | } 221 | 222 | visitConstructorEntry({node: entry}: Visit): string { 223 | // interfaces don't have constructors 224 | return ""; 225 | } 226 | 227 | visitFallbackEntry({ node: entry }: Visit): string { 228 | const servesAsReceive = this.abiFeatures["defines-receive"] && 229 | this.versionFeatures["receive-keyword"] !== true; 230 | 231 | const { stateMutability } = entry; 232 | return `${this.generateFallbackName()} () external ${ 233 | stateMutability === "payable" || servesAsReceive ? "payable" : "" 234 | };`; 235 | } 236 | 237 | visitReceiveEntry() { 238 | // if version has receive, emit as normal 239 | if (this.versionFeatures["receive-keyword"] === true) { 240 | return `receive () external payable;`; 241 | } 242 | 243 | // if this ABI defines a fallback separately, emit nothing, since 244 | // visitFallbackEntry will cover it 245 | if (this.abiFeatures["defines-fallback"]) { 246 | return ""; 247 | } 248 | 249 | // otherwise, explicitly invoke visitFallbackEntry 250 | return this.visitFallbackEntry({ 251 | node: { type: "fallback", stateMutability: "payable" }, 252 | }); 253 | } 254 | 255 | visitEventEntry({node: entry, context}: Visit): string { 256 | const {name, inputs, anonymous} = entry; 257 | 258 | return [ 259 | `event ${name}(`, 260 | inputs.map((node) => 261 | dispatch({ 262 | node, 263 | visitor: this, 264 | context: { 265 | ...context, 266 | parameterModifiers: (parameter: Abi.Parameter) => 267 | // TODO fix this 268 | (parameter as Abi.EventParameter).indexed ? ["indexed"] : [], 269 | }, 270 | }) 271 | ), 272 | `)`, 273 | `${anonymous ? "anonymous" : ""};`, 274 | ].join(" "); 275 | } 276 | 277 | visitErrorEntry({node: entry, context}: Visit): string { 278 | if (this.versionFeatures["custom-errors"] !== true) { 279 | throw new Error("ABI defines custom errors; use Solidity v0.8.4 or higher"); 280 | } 281 | 282 | const {name, inputs} = entry; 283 | 284 | return [ 285 | `error ${name}(`, 286 | inputs.map((node) => 287 | dispatch({ 288 | node, 289 | visitor: this, 290 | context: { 291 | ...context, 292 | parameterModifiers: (parameter: Abi.Parameter) => [] 293 | }, 294 | }) 295 | ), 296 | `);`, 297 | ].join(" "); 298 | } 299 | 300 | visitParameter({node: parameter, context}: Visit) { 301 | const type = this.generateType(parameter, context); 302 | 303 | // @ts-ignore 304 | const {parameterModifiers} = context; 305 | 306 | return [type, ...parameterModifiers(parameter), parameter.name].join(" "); 307 | } 308 | 309 | private generateHeader(): string { 310 | const includeExperimentalPragma = 311 | this.abiFeatures["needs-abiencoder-v2"] && 312 | this.versionFeatures["abiencoder-v2"] !== "default"; 313 | 314 | const attribution = 315 | !this.outputAttribution 316 | ? [] 317 | : [this.generateAttribution()] 318 | 319 | return [ 320 | `// SPDX-License-Identifier: ${this.license}`, 321 | ...attribution, 322 | `pragma solidity ${this.solidityVersion};`, 323 | ...( 324 | includeExperimentalPragma 325 | ? [`pragma experimental ABIEncoderV2;`] 326 | : [] 327 | ) 328 | ].join("\n"); 329 | } 330 | 331 | private generateAttribution(): string { 332 | const unit = this.mode === GenerateSolidityMode.Normal 333 | ? "FILE" 334 | : "INTERFACE" 335 | return this.outputSource 336 | ? `// !! THIS ${unit} WAS AUTOGENERATED BY abi-to-sol v${version}. SEE SOURCE BELOW. !!` 337 | : `// !! THIS ${unit} WAS AUTOGENERATED BY abi-to-sol v${version}. !!`; 338 | } 339 | 340 | private generateAutogeneratedNotice(abi: Abi.Abi): string { 341 | if (!this.outputSource) { 342 | return ""; 343 | } 344 | 345 | return [ 346 | ``, 347 | `// THIS FILE WAS AUTOGENERATED FROM THE FOLLOWING ABI JSON:`, 348 | `/*`, 349 | JSON.stringify(abi), 350 | `*/`, 351 | ].join("\n"); 352 | } 353 | 354 | private generateDeclarations(): string { 355 | if ( 356 | this.versionFeatures["structs-in-interfaces"] !== true && 357 | Object.keys(this.declarations.signatureDeclarations).length > 0 358 | ) { 359 | throw new Error( 360 | "abi-to-sol does not support custom struct types for this Solidity version" 361 | ); 362 | } 363 | 364 | const externalContainers = Object.keys(this.declarations.containerSignatures) 365 | .filter(container => container !== "" && container !== this.name); 366 | 367 | const externalDeclarations = externalContainers 368 | .map(container => [ 369 | `interface ${container} {`, 370 | this.generateDeclarationsForContainer(container), 371 | `}` 372 | ].join("\n")) 373 | .join("\n\n"); 374 | 375 | const globalSignatures = this.declarations.containerSignatures[""] || []; 376 | if (globalSignatures.length > 0) { 377 | const declarations = this.versionFeatures["global-structs"] === true 378 | ? this.generateDeclarationsForContainer("") 379 | : [ 380 | `interface ${shimGlobalInterfaceName} {`, 381 | this.generateDeclarationsForContainer(""), 382 | `}` 383 | ].join("\n"); 384 | 385 | return [declarations, externalDeclarations].join("\n\n"); 386 | } 387 | 388 | return externalDeclarations; 389 | } 390 | 391 | private generateDeclarationsForContainer(container: string): string { 392 | const signatures = new Set( 393 | this.declarations.containerSignatures[container] 394 | ); 395 | 396 | if (container === "" && this.versionFeatures["global-structs"] !== true) { 397 | container = shimGlobalInterfaceName; 398 | } 399 | 400 | return Object.entries(this.declarations.signatureDeclarations) 401 | .filter(([signature]) => signatures.has(signature)) 402 | .map(([signature, declaration]) => { 403 | const { identifier } = this.identifiers[signature]; 404 | const components = this.generateComponents(declaration, { interfaceName: container }); 405 | 406 | return `struct ${identifier} { ${components} }`; 407 | }) 408 | .join("\n\n"); 409 | } 410 | 411 | private generateComponents( 412 | declaration: Declaration, 413 | context?: Pick 414 | ): string { 415 | return declaration.components 416 | .map((component) => { 417 | const {name} = component; 418 | 419 | return `${this.generateType(component, context)} ${name};`; 420 | }) 421 | .join("\n"); 422 | } 423 | 424 | private generateType( 425 | variable: Abi.Parameter | Component, 426 | context: Pick = {} 427 | ): string { 428 | const signature = this.generateSignature(variable); 429 | 430 | if (!signature) { 431 | return this.generateElementaryType(variable, context); 432 | } 433 | 434 | const { type } = variable; 435 | 436 | const { container, identifier } = this.identifiers[signature]; 437 | 438 | if (container && container !== context.interfaceName) { 439 | return type.replace("tuple", `${container}.${identifier}`); 440 | } 441 | 442 | if (!container && this.versionFeatures["global-structs"] !== true) { 443 | return type.replace("tuple", `${shimGlobalInterfaceName}.${identifier}`); 444 | } 445 | 446 | return type.replace("tuple", identifier); 447 | } 448 | 449 | private generateElementaryType( 450 | variable: Abi.Parameter | Component, 451 | context: Pick = {} 452 | ): string { 453 | // normally we can return the type itself, but functions are a special case 454 | if (variable.type !== "function") { 455 | return variable.type; 456 | } 457 | 458 | // use just the `internalType` field if it exists 459 | if ("internalType" in variable && variable.internalType) { 460 | return variable.internalType; 461 | } 462 | 463 | // otherwise output minimally syntactically-valid syntax with a warning 464 | return [ 465 | "/* warning: the following type may be incomplete. ", 466 | "the receiving contract may expect additional input or output parameters. */ ", 467 | "function() external" 468 | ].join(""); 469 | } 470 | 471 | 472 | private generateSignature( 473 | variable: Abi.Parameter | Component 474 | ): string | undefined { 475 | if ("signature" in variable && variable.signature) { 476 | return variable.signature; 477 | } 478 | 479 | if ("components" in variable && variable.components) { 480 | return abiTupleSignature(variable.components); 481 | } 482 | } 483 | 484 | private generateStateMutability( 485 | entry: 486 | | Abi.FunctionEntry 487 | | Abi.FallbackEntry 488 | | Abi.ConstructorEntry 489 | | Abi.ReceiveEntry 490 | ): string { 491 | if (entry.stateMutability && entry.stateMutability !== "nonpayable") { 492 | return entry.stateMutability; 493 | } 494 | 495 | return ""; 496 | } 497 | 498 | private generateFallbackName(): string { 499 | switch (this.versionFeatures["fallback-keyword"]) { 500 | case true: { 501 | return "fallback"; 502 | } 503 | case false: { 504 | return "function"; 505 | } 506 | case mixed: { 507 | throw new Error( 508 | `Desired Solidity range lacks unambigious fallback syntax.` 509 | ); 510 | } 511 | } 512 | } 513 | 514 | private generateArrayParameterLocation(parameter: Abi.Parameter): string { 515 | switch (this.versionFeatures["array-parameter-location"]) { 516 | case undefined: { 517 | return ""; 518 | } 519 | case mixed: { 520 | throw new Error( 521 | `Desired Solidity range lacks unambiguous location specifier for ` + 522 | `parameter of type "${parameter.type}".` 523 | ); 524 | } 525 | default: { 526 | return this.versionFeatures["array-parameter-location"]; 527 | } 528 | } 529 | } 530 | 531 | private generateInterface(abi: Abi.Abi): string { 532 | return [ 533 | `interface ${this.name} {`, 534 | ...( 535 | this.mode === GenerateSolidityMode.Embedded && this.outputAttribution 536 | ? [this.generateAttribution()] 537 | : [] 538 | ), 539 | this.generateDeclarationsForContainer(this.name), 540 | ``, 541 | ...abi.map((node) => dispatch({ 542 | node, 543 | context: { interfaceName: this.name }, 544 | visitor: this 545 | })), 546 | ...( 547 | this.mode === GenerateSolidityMode.Embedded 548 | ? [this.generateAutogeneratedNotice(abi)] 549 | : [] 550 | ), 551 | `}`, 552 | ].join("\n"); 553 | } 554 | } 555 | --------------------------------------------------------------------------------