https://www.npmjs.com/package/adelante
This is a code generation tool to aid in the rapid prototyping of Smart Contracts built with Solidity.
See it in action at: https://adelante-test-app.vercel.app/
As of version 1.1.7 you can:
- Generate inline functions or extract them to their own files
- Generate inline components or extract them to their own files
- Use a combination of the two, e.g inline functions and extracted components
- Generate javascript files
- Define the output path in the adelante.json
It automatically:
- Generates metamask connect fucntionality and component
- Generates a css file
- Generates a html file
- Generates an App.*sx file populated with the generated components
Other functionality:
It logs function calls and the return data from them (if any)
Features in development before 1.2.0
- Generate test files for the generated files - in development
- Make html, index and app file generation optional (components and functions only, formatted for general use)
Planned features:
- User defined themes
If you like it, send me a coffee :)
Eth address: 0x4A079D4417b522762C72dB9643234FCC4683a40E
Make sure that you have a package.json file before you install adelante. You can make one with the following command:
npm init
or
yarn init
You will be asked some questions to configure adelante.json when you use the Setup command.
If you want to use a default configuration and edit the json manually pick 'No' for the first question
For yarn users:
- Install using:
yarn add adelante
- Setup using:
yarn adelante --init
- Run using:
yarn adelante
For npm users:
- Install using:
npm install adelante
- Setup using:
npx adelante --init
- Run using:
npx adelante
- Copy your contracts .json file into the root project directory
- Define the path to your contract.json file in the adelante.json file (if you didnt do it when you generated the adelante file)
- It will genrate without a contract address but one should be addded if you want to it to work properly
{
"useTypescript": true,
"inlineFunctions": false,
"inlineComponents": false,
"contractPath": "/Contract.json", // Path to the compiled contract (found in your artifacts folder if using hardhat, it must be in your project directory)
"generateTests": true,
"contractAddress": "ENTER_CONTRACT_ADDRESS_HERE",
"projectPath": "/my-app/src", // Path to app directory, if you want to use create-react-app the path should be to the src file
"testDirectory": "./my-app/src" // Path to where you want the test file to be generated
}
For each callable function on the Smart Contract a function will be generated. Most of these functions will work straight away, payable functions will need to be customised to work correctly.
- With payable functions an ethers.utils.parseEther() is generated in the function file but an amount is not provided
Payable functions dont require the ether amount to de defined as an argument in the function inputs (these are recorded in the contract abi which is the source for the code generation)
The functions will return data if it is expected, otherwise a return statement will not generate.
import { getContract } from '../utils/utils'
/*
if the function is payable it will use:
import { ethers } from 'ethers';
and modify the function call to include it:
await contract.getRemainingUnits({ value: ethers.utils.parseEther("0.00")});
*/
export default async function getRemainingUnits() {
try {
const { ethereum } = window;
const contract = getContract(ethereum);
const data = await contract.getRemainingUnits();
return data;
}
catch (error) {
console.log(error);
return "getRemainingUnits failed";
}
}
These components have inputs and a button to call the smart contract function along with state and a handleChange function
import React, { useState } from 'react';
import purchaseUnit from '../functions/purchaseUnit';
type Props = {
handleMasterLogsChange: (data: any) => void;
};
type State = {
[key: string]: string
};
export default function PurchaseUnit(props : Props) {
const { handleMasterLogsChange } = props
const [state, setState] = useState<State>({});
const handleStateChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target
setState((prevState) => {
return {
...prevState,
[name]: value
}
})
}
const handleLogsClick = async (event: any) => {
const data = await purchaseUnit(state?._amount)
const outcome = data === "purchaseUnit failed" ? "failed" : `success: ${JSON.stringify(data)}`
handleMasterLogsChange(["purchaseUnit", outcome])
}
return (
<div className="function-box">
<div className="box-heading">
<h1>Purchase Unit</h1>
<span className="text-extra"><p>purchaseUnit</p></span><p>Function inputs:</p>
<p>(<span className="text-extra">uint16 _amount:</span> number)</p>
</div>
<div className="box-inputs">
<p>_amount</p>
<input name="_amount" onChange={handleStateChange} type="number" placeholder="_amount"/>
</div>
<button className="box-button" onClick={handleLogsClick} value="" >purchaseUnit</button>
</div>
)
}
It will create the imports and pass props down to the child components for the masterLogsState The example below is from the hosted priview at: https://adelante-test-app.vercel.app/
import React, { useState } from 'react';
import './App.css';
import Footer from './Footer';
import Details from './Details';
import changeTheme from './changeTheme';
import Nav from './Nav';
import ReturnSum from './components/returnSum';
import StoreNumber from './components/storeNumber';
import WithdrawBalance from './components/withdrawBalance';
import AddBalance from './components/addBalance';
import GetBalance from './components/getBalance';
import GetNumber from './components/getNumber';
import Hello from './components/hello';
type MasterLogs = any[];
export default function App() {
const [theme, setTheme] = useState("dark")
const [masterLogs, setMasterLogs] = useState<MasterLogs>([]);
const handleMasterLogsChange = (data: any) => {
setMasterLogs(prevState => [data, ...prevState])
}
const handleTheme = () => {
setTheme(changeTheme(theme))
}
return (
<div className="App">
<Nav handleTheme={handleTheme} />
<Details masterLogs={masterLogs} />
<div className="components">
<ReturnSum handleMasterLogsChange={handleMasterLogsChange} />
<StoreNumber handleMasterLogsChange={handleMasterLogsChange} />
<WithdrawBalance handleMasterLogsChange={handleMasterLogsChange} />
<AddBalance handleMasterLogsChange={handleMasterLogsChange} />
<GetBalance handleMasterLogsChange={handleMasterLogsChange} />
<GetNumber handleMasterLogsChange={handleMasterLogsChange} />
<Hello handleMasterLogsChange={handleMasterLogsChange} />
</div>
<Footer />
</div>
)
}
You have the option to generate tests for the react components created by adelante. If you choose this option adelante will read the ABI to determine some basic tests to be created.
An example of the generated test is below:
import '@testing-library/jest-dom'
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import ReturnSum from "../../test-app/components/ReturnSum";
type Props = {
handleMasterLogsChange: (data: any) => void;
};
const setup = (propOverrides?: Partial<Props>) => {
const props: Props = {
handleMasterLogsChange: jest.fn(),
...propOverrides,
}
return render(<ReturnSum { ...props} />);
}
describe('Test for returnSum component', () => {
it('should render without exploding, () => {}', () => {
expect(() => setup()).not.toThrow();
})
it('should render ReturnSum inputs', () => {
setup();
expect(screen.getAllByRole("spinbutton").length).toBe(2);
expect(screen.getByRole("spinbutton", {name: "_a"})).toBeInTheDocument()
expect(screen.getByRole("spinbutton", {name: "_b"})).toBeInTheDocument()
})
it('should render the button to call the contract function', () => {
setup();
expect(screen.getByRole("button", { name: "returnSum" })).toBeInTheDocument()
})
it('should render the component heading correctly', () => {
setup();
expect(screen.getByRole("heading", { name: "Return Sum" })).toBeInTheDocument()
})
it('should call handleMasterLogsChange on button click', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation()
const handleMock = jest.fn();
setup({ handleMasterLogsChange: handleMock });
const button = screen.getByRole("button", { name: "returnSum" });
await userEvent.click(button);
expect(handleMock).toHaveBeenCalled();
expect(consoleSpy.mock.calls.length).toBe(1);
})
it('should handle input change correctly', async () => {
setup();
const _a = screen.getByRole("spinbutton", {name: "_a"})
const _b = screen.getByRole("spinbutton", {name: "_b"})
await userEvent.type(_a, "150")
await userEvent.type(_b, "150")
expect(screen.getByRole("spinbutton", {name: "_a"})).toHaveValue(150)
expect(screen.getByRole("spinbutton", {name: "_b"})).toHaveValue(150)
})
})
Adelante will generate tests for the functions, they are very basic, more improvements coming soon.
import getBalance from "../../test-app/functions/getBalance"
describe('Test for getBalance function', () => {
it('should call the function', () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation()
getBalance()
expect(consoleSpy.mock.calls.length).toBe(1);
})
})