web3.js Plugin: web3-plugin-craftsman
Smoothly interact with a Smart Contract directly from its Solidity Code, or save its compilation for later use.
Description
The web3-plugin-craftsman
allows instantiating contracts directly from Solidity source code or a Solidity file. This is because it compiles the Solidity source code and generates the bytecode and the ABI. This is to enable creating and interacting with smart contracts directly from source without pre-compiling.
So, you can pass the solidity source code or a file path to the the ExtendedContract
class constructor; And then use it like a normal web3.js
Contract
object.
And you can also save the generated contract's ABI and Bytecode to a TypeScript, or a JavaScript, file that is compatible and easily readable by a normal web3.js
Contract
.
Compatibility
This plugin is compatible with:
- web3.js v4.x
- Node.js 16+
- Browsers (untested but should work via Webpack/Rollup/Browserify)
Features
-
Compile Solidity source code that is provided to the ExtendedContract constructor, as mentioned in Creating an instance of the ExtendedContract object.
-
The bytecode and ABI are available to read after the compilation finishes, as mentioned in Waiting for the compilation result.
-
The possibility to save the ABI and the Bytecode, as mentioned at Save the compilation result. And this ABI can be used later to enable IntelliSense for the smart contract methods.
-
When deploying the Contract, the generated bytecode is used internally automatically (no need to provide it), as mentioned in Deploying and interacting with your Contract.
-
The rest is exactly as you would interact with the
Contract
instance, according to the official web3.js documentation (https://docs.web3js.org/). -
Do you need more or something else? Try to open an issue and we will discuss it... Are you interested in developing a web3.js plugin for your network, platform, or project? reach out using email to discuss.
Installation
yarn add web3-plugin-craftsman
or
npm install web3-plugin-craftsman
Usage
Basically, you just need to create an object of ExtendedContract
by passing the solidity source code.
After that, you have to call await contract.compilationResult
to be able to wait for the compilation to finish.
ExtendedContract
object
Creating an instance of the However, there is 3 ways to create an object of ExtendedContract
:
-
First alternative is to instantiate from the
ExtendedContract
classimport { ExtendedContract } from 'web3-plugin-craftsman'; // Using ExtendedContract directly const contract = new ExtendedContract(sourceCodeOrPath); // You need to set the provider only if you need to deploy or execute methods // You do not need it incase you only need to compile the source code, // and save the ABI and bytecode for later usage. contract.provider = 'http://localhost:8545';
-
Alternatively, you can create the contract using an instance of
ExtendedWeb3
that is basically the same as if you created an object ofWeb3
. But it has.eth.ExtendedContract
that is equivalent to.eth.Contract
but with the ability to accept a smart contract source code.import { ExtendedWeb3 } from 'web3-plugin-craftsman'; // Using ExtendedWeb3 const web3 = new ExtendedWeb3('http://localhost:8545'); const contract = new web3.eth.ExtendedContract(sourceCodeOrPath);
-
As a third alternative, you can use the functionality as a plugin to Web3. This is helpful if you like to using this functionality on an existing
Web3
object.import { ExtendedWeb3 } from 'web3-plugin-craftsman'; const web3 = new Web3('http://localhost:8545'); // your Web3 object // Using ExtendedWeb3 as a plugin web3.registerPlugin(new ExtendedWeb3()); const contract = new web3.craftsman.ExtendedContract(sourceCodeOrPath);
After you have your contract
object created, the rest is the same regardless of the alternative you chose.
Waiting for the compilation result
You need to wait for the compilation to finish before you can go further. This is a one call that you need to await
as below:
// Note: After initializing the contract using one of the 3 alternatives
// mentioned above, the rest is the same regardless of the initialization.
// Wait for the contract compilation and handle compilation errors if any
try {
const compilationResult = await contract.compilationResult;
// the compilationResult will consists of:
// {
// abi: ContractAbi,
// bytecodeString: string,
// contractName: string,
// }
// Note: if you did not provide a source code or a file to
// the constructor, the compilationResult will be `undefined`.
// This might be useful to know that your contract does not need
// to be waited for compilation. But that would also mean you need
// to provide the ABI and the bytecode manually.
} catch (e) {
console.log(e);
}
Note: The contract object will not recognize the ABI. However, To make it do so, save and then use the ABI, as mentioned in a later section named Save the compilation result.
You can detect if the compilation had finished by checking the boolean contract.hadFinishedCompilation
. It will be false
if it is still compiling, true
if it had finished, and undefined
if there was no code or file path provided.
Deploying and interacting with your Contract
// get the accounts provided by your Ethereum node (like Ganache).
const accounts = await web3.eth.getAccounts();
fromAccount = accounts[0];
// Deploy contract
const deployed = await contract
.deploy({ arguments: [1000] })
.send({ from: fromAccount });
// Call a method
const myNumber = await deployed.methods.myNumber().call();
// Send a transaction
await deployed.methods.setMyNumber(100).send({ from: fromAccount });
// If you are using TypeScript you need to ask similar to the following:
// await(deployed.methods.setMyNumber(100) as any)
// .send({ from: fromAccount });
// Call a method
const myNumberUpdated = await deployed.methods.myNumber().call();
The constructor options
Note that, you can pass one of the following, as the first argument, to the extended contract contractor. (This is what you can enter to replace the variable sourceCodeOrPath
mentioned above):
- A string containing a Smart Contract source code
- A string containing a Smart Contract file path
- An object containing the source code and the Smart Contract name:
{ sourceCode: string; // The name must be specified if there are multiple contracts contractName: string; }
- An object containing the path of the file (path/file.sol), or the paths, and the Smart Contract name:
{ // Pass an array, if the contract inherits from other contracts // that exist at other files path: string | string[]; // The name must be specified if there are multiple contracts contractName: string; }
Examples for the constructor options
And here is an example for passing the file path (the recommended way):
const contract = new ExtendedContract(
'./test/smart_contracts/simple-contract.sol'
);
Or as the following when you need to pass multiple files:
const contract = new ExtendedContract({
path: [
'./test/smart_contracts/simple-contract.sol',
'./test/smart_contracts/child-contract.sol',
],
contractName: 'ChildContract',
});
And here is an example for passing the solidity code in-line (not recommended):
const contract = new ExtendedContract('
// SPDX-License-Identifier: MIT
pragma solidity 0.8.0;
contract SimpleContract {
uint256 public myNumber;
constructor(uint256 _myNumber) {
myNumber = _myNumber;
}
function setMyNumber(uint256 _myNumber) public {
myNumber = _myNumber;
}
}
'
);
Save the compilation result
Saving the compilation result is very handy. Because you do not need to compile the contract every time you interact with your smart contract. The plugin can take care of saving both the ABI and the Bytecode and assign them to exported variables.
// If you provide a directory name. The file name will be automatically generated as `[Smart Contract Name at Solidity code]-artifacts.ts`.
// However, you can specify a file that must end with either `.ts` or `.js`.
await contract.saveCompilationResult('../compilation_output_dir');
The above will create a file named according to your smart contract. And here is the file content for the smart contract named SimpleContract
that is mentioned in the previous section:
export const SimpleContractAbi = [
{
inputs: [
{
internalType: 'uint256',
name: '_myNumber',
type: 'uint256'
}
],
stateMutability: 'nonpayable',
type: 'constructor',
signature: ''
},
{
inputs: [],
name: 'myNumber',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256'
}
],
stateMutability: 'view',
type: 'function',
signature: '0x23fd0e40',
constant: true,
payable: false
},
{
inputs: [
{
internalType: 'uint256',
name: '_myNumber',
type: 'uint256'
}
],
name: 'setMyNumber',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
signature: '0x6ffd773c',
constant: false,
payable: false
}
] as const;
export const SimpleContractBytecode = '608060405234801561000f575f80fd5b506040516101d23803806101d283398181016040528101906100319190610074565b805f819055505061009f565b5f80fd5b5f819050919050565b61005381610041565b811461005d575f80fd5b50565b5f8151905061006e8161004a565b92915050565b5f602082840312156100895761008861003d565b5b5f61009684828501610060565b91505092915050565b610126806100ac5f395ff3fe6080604052348015600e575f80fd5b50600436106030575f3560e01c806323fd0e401460345780636ffd773c14604e575b5f80fd5b603a6066565b60405160459190608a565b60405180910390f35b606460048036038101906060919060ca565b606b565b005b5f5481565b805f8190555050565b5f819050919050565b6084816074565b82525050565b5f602082019050609b5f830184607d565b92915050565b5f80fd5b60ac816074565b811460b5575f80fd5b50565b5f8135905060c48160a5565b92915050565b5f6020828403121560dc5760db60a1565b5b5f60e78482850160b8565b9150509291505056fea26469706673582212206e0456eccefdc957c99835d074d2cbebb77403b8d6ac9c68b75079b173f5ac6664736f6c63430008140033';
Note that the auto-generated file name for the above contract compilation result will be: SimpleContract-artifacts.ts
. And the variables names are also auto generated according to your Smart Contract name. Additionally, to make the intellisense for the smart contracts object works according to the ABI, the syntax as const
is added to the ABI variable.
And you can use the above ABI and Bytecode simply by:
import {
SimpleContractAbi,
SimpleContractBytecode,
} from '../compilation_output_dir/SimpleContract-artifacts.ts';
// you use those constants when interacting with your smart contract object,
// as usual, according to web3.js documentation (https://docs.web3js.org/)
Contributing
Please include relevant test cases to cover any new functionality.
Compatibility
This plugin is compatible with:
- web3.js version 4.x
- Node.js 16+
- Browsers (via Webpack/Rollup/Browserify)
Troubleshooting
Just try to run your code again, if you faced the following error:
EACCES: permission denied, open '/home/maltabba/repos/experiments/web3-plugin-craftsman/node_modules/solc-typed-ast/.compiler_cache/wasm/soljson-v0.8.20+commit.a1b79de6.js'
And if the error persist, you may open an issue at: https://github.com/ConsenSys/solc-typed-ast.
If you like to contribute to the development of this package, you may check the CONTRIBUTION.md file.
Tutorials
If you are new in the space and would like to follow a tutorial, here is one: Interacting with Ethereum Smart Contracts, Directly from Solidity Source Code`