Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Solidity is a high-level, object-oriented programming language specifically designed for implementing smart contracts on blockchain platforms like Ethereum. Smart contracts are self-executing programs with the contract's terms written directly into lines of code. These contracts automatically enforce and execute agreements once predefined conditions are met, without the need for intermediaries.
Statically Typed: Variables are declared with specific data types, such as uint, bool, address, etc. This allows for more optimized and predictable code execution.
Inheritance: Solidity supports multiple inheritance, allowing developers to create modular, reusable code for smart contracts.
Contract-Oriented: Solidity is specifically built for writing smart contracts. Each contract has its own state and can interact with other contracts.
EVM Compatibility: Solidity compiles down to bytecode that runs on the Ethereum Virtual Machine (EVM). This makes it compatible with all Ethereum-based blockchains.
Libraries: Solidity allows developers to define reusable libraries, which help in reducing code duplication.
Interfaces and Abstract Contracts: These are used to define templates and expected behaviors for other contracts, providing structure and reusability across the system.
Run the deployment script:
npx hardhat run scripts/deploy.js --network localhostConfigure Networks in Hardhat: Edit hardhat.config.js to include testnet configurations:
codemodule.exports = {
networks: {
phron: {
url: "https://testnet.phron.ai",
}
},
solidity: "0.8.4",
};npx hardhat compileasync function main() {
const Contract = await ethers.getContractFactory("MyContract");
const contract = await Contract.deploy();
console.log("Contract deployed to:", contract.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});OpenZeppelin is well known in the Ethereum developer community as their set of audited smart contracts and libraries are a standard in the industry. For example, most of the tutorials that show developers how to deploy an ERC-20 token use OpenZeppelin contracts.
You can find more information about OpenZeppelin on their .
As part of its Ethereum compatibility features, OpenZeppelin products can be seamlessly used on Phron. This page will provide information on different OpenZeppelin solutions that you can test.
Currently, the following OpenZeppelin products/solutions work on the different networks available on Phron:
You will find a corresponding tutorial for each product in the following links:
Contracts Wizard — where you'll find a guide on how to use OpenZeppelin web-based wizard to create different token contracts with different functionalities
Contracts & libraries — where you'll find tutorials to deploy the most common token contracts using OpenZeppelin's templates: ERC-20, ERC-721 and ERC-1155
Defender — where you'll find a guide on how to use OpenZeppelin Defender to manage your smart contracts in the Phron TestNet. This guide can also be adapted for Phron
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Geth's debug and txpool APIs and OpenEthereum's trace module provide non-standard RPC methods for deeper insight into transaction processing. Some of these non-standard RPC methods are supported as part of Phron's goal of providing a seamless Ethereum experience for developers. Supporting these RPC methods is an essential milestone because many projects like rely on them to index blockchain data.
This guide will cover the supported RPC methods available on Phron and how to invoke them using curl commands against a tracing node with the debug, txpool, and tracing flags enabled. You can access a tracing node in one of two ways: through a supported tracing RPC provider or by spinning up a tracing node of your own.
To view a list of tracing RPC providers, please check out the Network Endpoints page.
When writing and deploying smart contracts, ensuring security is crucial. Below are some key security considerations that must be followed to mitigate potential vulnerabilities and protect user funds and data.
1. Reentrancy Protection
Reentrancy attacks occur when an external contract makes recursive calls to the original function before the first invocation is completed. This can lead to funds being drained. Use OpenZeppelin’s nonReentrant modifier from the ReentrancyGuard library to protect against reentrancy attacks.
Verifying smart contracts on a PhronScan is a great way to improve the transparency and security of deployed smart contracts on Phron. Users can directly view the source code for verified smart contracts, and for some block explorers, they can also directly interact with the contract's public methods through the PhronScan's interface.
Product
Phron
Contracts & libraries
✓
Contracts Wizard
✓
Defender
✓
This will generate a package.json file for managing dependencies and scripts.
Hardhat is a powerful development environment for compiling, deploying, and testing Ethereum smart contracts. Install Hardhat along with ethers.js for interacting with Ethereum, and the necessary plugins:
This command installs:
Hardhat: For development, testing, and deployment.
ethers.js: A library for interacting with Ethereum.
@nomiclabs/hardhat-ethers: A plugin that integrates Hardhat with ethers.js.
Run Hardhat’s initialization command to generate the basic configuration and project files:
You will be prompted to select a task. Choose Create a basic sample project or Create an advanced project, depending on your needs. This will generate:
hardhat.config.js: Your Hardhat configuration file.
Sample contract, test, and script files.
Inside the contracts/ folder, create a simple Solidity contract, e.g., MyContract.sol:
In the scripts/ folder, create a deployment script (deploy.js):
To compile and deploy your contract locally:
Start a local Ethereum node using Hardhat:
Deploy your contract:
This will deploy your contract to a local test network.
In the test/ folder, create a test script (test.js) using Hardhat's testing framework and ethers.js:
To run your tests:
To deploy on a real test network , configure your hardhat.config.js:
nonReentrant modifier prevents a contract from calling itself, directly or indirectly, ensuring that each function can only be called once per transaction.Improvement: OpenZeppelin’s ReentrancyGuard is battle-tested and widely adopted, providing out-of-the-box protection without the need to manually implement custom reentrancy checks.
2. Input Validation
Always validate the inputs provided by users to prevent unintended behavior, especially when dealing with sensitive operations such as token transfers, withdrawals, or access controls. Ensuring the integrity of the data passed to the contract can prevent various attacks.
Best practices:
Validate that addresses are not zero (address(0)).
Ensure that numerical values like token amounts are within acceptable ranges.
Use require statements to validate input conditions.
Improvement: Adding thorough input validation prevents invalid or malicious inputs from breaking the contract or leading to unexpected behavior.
3. Safe Math (Overflow and Underflow Prevention)
Solidity versions 0.8.0 and above include built-in overflow and underflow protection, making SafeMath libraries unnecessary for new versions. For older versions, you should use libraries like OpenZeppelin's SafeMath to prevent overflows and underflows.
Why it’s important: Overflow and underflow bugs can allow attackers to manipulate contract balances or bypass critical checks.
For Solidity 0.8.0+:
For older Solidity versions:
Improvement: For new contracts, leverage Solidity’s built-in protections, simplifying your code and reducing dependencies.
4. Ownership Control
Restrict critical functions (such as withdrawing funds or changing contract state) to the owner or a trusted party. Using the onlyOwner modifier from OpenZeppelin’s Ownable contract ensures that only the contract owner can execute sensitive operations.
How it works: The onlyOwner modifier checks that the function caller is the contract owner, protecting critical functions from unauthorized access.
Improvement: OpenZeppelin’s Ownable provides well-tested access control patterns, making it easy to implement robust ownership control mechanisms.
Incorporating the above best practices, here’s a secure withdraw function that follows reentrancy protection, ownership control, and proper input validation:
Improvements:
Reentrancy Protection: The nonReentrant modifier ensures that reentrancy attacks are blocked.
Ownership Control: Only the contract owner can execute the withdraw function.
Input Validation: The contract checks that there is a positive balance before attempting to withdraw, preventing unnecessary gas expenditure on invalid transactions.
Fallback Functions: Ensure that fallback functions are properly secured, and consider using them only for receiving Ether.
Gas Limit Awareness: Be mindful of gas limits, especially in loops or when interacting with external contracts.
Timelocks: Consider using timelocks for critical operations to mitigate risks of sudden or malicious contract updates.
By following these security best practices, you significantly reduce the risk of vulnerabilities and ensure your smart contracts are robust and reliable.
npx hardhat nodenpx hardhat run scripts/deploy.js --network localhostmkdir MySolidityProject
cd MySolidityProject
npm init -ynpm install --save-dev hardhat @nomiclabs/hardhat-ethers ethersnpx hardhatMySolidityProject/
├── contracts/ # Contains all the Solidity contracts
│ └── MyContract.sol # Example contract
├── scripts/ # Scripts for deployment and contract interaction
│ └── deploy.js # Script for contract deployment
├── test/ # Contains unit tests for smart contracts
│ └── test.js # Example test file
├── artifacts/ # Generated files (compiled contracts, ABIs, etc.)
├── cache/ # Cached files for faster compilation
├── node_modules/ # Installed npm packages
├── hardhat.config.js # Hardhat configuration file
└── package.json # Project dependencies and scripts// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyContract {
uint256 public value;
function setValue(uint256 _value) public {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
}javascriptCopy codeasync function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
const MyContract = await ethers.getContractFactory("MyContract");
const myContract = await MyContract.deploy();
console.log("MyContract deployed to:", myContract.address);
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});const { expect } = require("chai");
describe("MyContract", function () {
it("Should set and get the correct value", async function () {
const MyContract = await ethers.getContractFactory("MyContract");
const myContract = await MyContract.deploy();
await myContract.deployed();
// Set a value
await myContract.setValue(42);
// Test the value
expect(await myContract.getValue()).to.equal(42);
});
});npx hardhat testrequire("@nomiclabs/hardhat-ethers");
module.exports = {
solidity: "0.8.0",
networks: {
phron: {
url: `https://testnet.phron.ai`,
},
},
};import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract MyContract is ReentrancyGuard {
function withdraw() external nonReentrant onlyOwner {
(bool success, ) = owner.call{value: address(this).balance}("");
require(success, "Transfer failed.");
}
}function transfer(address recipient, uint256 amount) external {
require(recipient != address(0), "Invalid recipient address");
require(amount > 0, "Transfer amount must be greater than zero");
// Transfer logic here
}function add(uint256 a, uint256 b) external pure returns (uint256) {
return a + b;
// Safe from overflow/underflow
}import "@openzeppelin/contracts/utils/math/SafeMath.sol";
contract MyContract {
using SafeMath for uint256;
function add(uint256 a, uint256 b) external pure returns (uint256) {
return a.add(b);
// Overflow protection via SafeMath
}
}import "@openzeppelin/contracts/access/Ownable.sol";
contract MyContract is Ownable {
function withdraw() external onlyOwner {
(bool success, ) = owner.call{value: address(this).balance}("");
require(success, "Transfer failed.");
}
}import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureContract is Ownable, ReentrancyGuard {
// Secure withdrawal function with reentrancy protection and ownership control
function withdraw() external nonReentrant onlyOwner {
uint256 contractBalance = address(this).balance;
require(contractBalance > 0, "No funds to withdraw");
(bool success, ) = owner.call{value: contractBalance}("");
require(success, "Transfer failed.");
}
}This page will outline the steps for verifying smart contracts on Phron networks through block explorers.
In order to verify a smart contract on a PhronScan, the contract must first be deployed on the target network. This tutorial will be about deploying the smart contract to Phron.
You can check out this page for a tutorial on deploying smart contracts using Ethereum libraries on Phron. You may also use a developer tool such as Remix, Hardhat, or another tool if preferred, to deploy the smart contract to Phron.
This tutorial will use the same contract as the above deployment tutorial for the contract verification example.
The contract used is a simple incrementer, arbitrarily named Incrementer.sol. The Solidity code is the following:
You will need to collect some information related to the contract's compiler and deployment in order to verify it successfully.
Take note of the Solidity compiler version used to compile and deploy the contract. The Solidity compiler version can usually be selected or specified in the deployment tool used
Take note of any SPDX license identifier used at the beginning of the Solidity source file (this example uses an MIT license):
(Optional) If optimization is enabled during compilation, take note of the value of the optimization runs parameter
(Optional) If the contract constructor method accepts arguments, take note of the ABI-encoded form of the constructor arguments
After deployment, take note of the deployed contract address of the smart contract. The deployment address of the contract can be found either in the console output if using a command-line-based tool such as Hardhat, or an Ethereum library, or it can be copied from the GUI in tools such as Remix IDE
The next step will be verifying the smart contract in an EVM-compatible explorer for the Phron network that you deployed to.
Take the following steps to verify the contract on Phronscan:
Go to the PhronScan.
Fill in the contract's deployed address in the first field, including the 0x prefix
Click on contract tab
Then click on Verify & publish
Select the compiler type. For the current Incrementer.sol example, select Solidity (Single file)
After selecting the compiler type, select the compiler version used to compile the contract. If the compiler version used was a nightly commit, uncheck the box under the field to select the nightly version
Select the open-source license used. For the current Incrementer.sol example, select the option MIT License (MIT). If there was none used, select No License (None)
Copy and paste the entirety of the contract's content into the text field labeled as such
(Optional) Select Yes for Optimization if it was enabled during compilation, and fill in the number of runs under Misc Settings/Runs(Optimizer)
(Optional) Add contract libraries and their addresses, if any were used in the contract
(Optional) Check any other optional fields that may apply to your contract, and fill them out accordingly
After a short wait, the result of verification will be displayed in the browser, and a success result page will display the contract's ABI-encoded constructor arguments, the contract name, bytecode, and ABI.
For verifying smart contracts that are made up of multiple files, the process is slightly different and requires some pre-processing to combine all the dependencies of the target smart contract into a single Solidity file.
This pre-processing is usually referred to as smart contract flattening. There are a number of tools that can be used to flatten a multi-part smart contract into a single Solidity file, such as Hardhat's Flatten task. Please refer to the respective smart contract flattening tool's documentation for more detailed instructions on its usage.
After flattening the multi-part smart contract, it can be verified using the new flattened Solidity file on a PhronScan in the same way that a single-file smart contract is verified, as described in this tutorial.
Unit tests are essential to ensure your smart contract behaves correctly. Using Mocha and Chai in conjunction with Hardhat provides a robust framework for testing your Solidity contracts.
Install Mocha/Chai
To set up testing, install the required libraries:
Here's an improved example of a test suite for a token contract:
Deployment Tests:
Ownership: Verifies that the contract's owner is correctly assigned upon deployment.
Initial Token Distribution: Confirms that the contract assigns the total token supply to the owner's balance.
Transaction Tests
To run the test suite, use the following command:
This will execute all test cases and display the results, ensuring that your contract behaves as expected under various conditions.
Modular Tests: Divided tests into logical groups (e.g., Deployment and Transactions) for better organization and readability.
Edge Case Testing: Added test cases for failure conditions (like insufficient balances) to ensure that your contract handles errors correctly.
Reusability: Used beforeEach to deploy a fresh contract instance for each test, ensuring isolation between test cases and preventing state leakage.
This structure makes the tests more maintainable and easier to extend as your contract grows.
Before starting development, ensure that you have the following tools installed:
Node.js (v14 or above)
NPM or Yarn (for package management)
Solidity Compiler (solc)
Truffle or Hardhat (for smart contract development)
Phron testnet for local testing
Metamask or any other Ethereum wallet
Here’s a quick guide on installing and setting up the tools you need for Solidity smart contract development, along with code examples for using each tool:
Node.js is required to run JavaScript-based tools like Truffle and Hardhat.
Installation:
and install it for your operating system.
Verify installation:
NPM comes with Node.js by default, but you can use Yarn as an alternative.
Installation (NPM is included with Node.js):
Verify installation:
Installation (Yarn):
Solidity compiler (solc) is needed to compile smart contracts.
Installation:
Using NPM:
Verify installation:
Example:
Compiling a contract with solc:
Both tools are widely used for developing, testing, and deploying smart contracts.
Truffle Installation:
Hardhat Installation:
Example (Hardhat project setup):
Example (Truffle project setup):
Generally speaking, a JSON-RPC is a remote procedure call (RPC) protocol that utilizes JSON to encode data. For Web3, they refer to the specific JSON-RPCs that DApp developers use to send requests and receive responses from blockchain nodes, making it a crucial element in interactions with smart contracts. They allow frontend user interfaces to seamlessly interact with the smart contracts and provide users with real-time feedback on their actions. They also allow developers to deploy their smart contracts in the first place!
To get a JSON-RPC to communicate with a Phron blockchain, you need to run a node. But that can be expensive, complicated, and a hassle. Fortunately, as long as you have access to a node, you can interact with the blockchain. Phron has a handful of free and paid node options. For this tutorial, we will be using the Phron's public node for Phron, but you are encouraged to get your own private endpoint.
So now you have a URL. How do you use it? Over HTTPS, JSON-RPC requests are POST requests that include specific methods for reading and writing data, such as eth_call for executing a smart contract function in a read-only manner or eth_sendRawTransaction for submitting signed transactions to the network (calls that change the blockchain state). The entire JSON request structure will always have a structure similar to the following:
This example is getting the balance (in DEV on Phron) of the 0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac account. Let's break down the elements:
jsonrpc — the JSON-RPC API version, usually "2.0"
id — an integer value that helps identify a response to a request. Can usually just keep it as `
method — the specific method to read/write data from/to the blockchain. You can see many of the RPC methods on our docs site
There are also additional elements that can be added to JSON-RPC requests, but those four will be seen the most often.
Now, these JSON-RPC requests are pretty useful, but when writing code, it can be a hassle to create a JSON object over and over again. That's why there exist libraries that help abstract and facilitate the usage of these requests. Phron provides documentation on many libraries, and the one that we will be using in this tutorial is Ethers.js. Just understand that whenever we interact with the blockchain through the Ethers.js package, we're really using JSON-RPC!
Metamask is a browser-based Ethereum wallet for interacting with decentralized applications (dApps).
Installation:
and install the browser extension.
Interfaces in Solidity are used to define the structure of external functions without providing their internal logic. They are essential for creating interoperable contracts that follow a standard design, ensuring that different contracts can interact with one another seamlessly.
transfer(address recipient, uint256 amount): Defines a function to transfer tokens to a specified recipient. It returns a boolean (true
// SPDX-License-Identifier: MIT// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Incrementer {
uint256 public number;
constructor(uint256 _initialNumber) {
number = _initialNumber;
}
function increment(uint256 _value) public {
number = number + _value;
}
function reset() public {
number = 0;
}
}npm install --save-dev mocha chaiSuccessful Transfers: Verifies that tokens can be transferred from one account to another and the balances are updated correctly.
Failed Transfers: Ensures that a transfer will fail if the sender doesn't have enough tokens, reverting the transaction.
Balance Updates: Checks that the balances are correctly updated after multiple transactions.
params — an array of the input parameters expected by the specific method
falsebalanceOf(address account): Defines a function that returns the current balance of an account. It's a read-only function (view) that doesn't modify the state.
Interfaces allow for modularity and upgradeability by ensuring that any contract implementing this interface will have these two key functions, without enforcing their specific implementation details.
Modifiers are used to alter the behavior of functions. They allow you to add prerequisites or checks before the function’s execution proceeds. This helps streamline code by avoiding repetition of common checks and improving contract security.
onlyOwner: This modifier restricts function access to only the contract's owner. The require statement checks that the caller (msg.sender) is the owner. If the condition fails, it reverts the transaction with an error message. The _ in the modifier represents where the function body will be inserted when the modifier is applied. This is a common pattern for restricting administrative functions, ensuring only the owner can perform sensitive operations.
Events are a logging mechanism in Solidity that allow contracts to emit information during execution. Off-chain services, like blockchain explorers and front-end applications, can listen to these events and react to them. They are a fundamental part of communication between the blockchain and external systems.
Transfer: This event logs token transfers between accounts. It includes three key pieces of information:
from: The address of the sender.
to: The address of the recipient.
value: The number of tokens transferred.
The indexed keyword allows these fields to be easily filtered in external event logs. Emitting events is gas-efficient and provides a way to track contract activity without modifying the blockchain state.
Storage variables are used to hold the persistent state of the contract. These variables are stored directly on the blockchain and are essential for managing the contract’s data.
owner: This variable stores the address of the contract owner, typically set during the contract’s deployment. Marked as public, it automatically generates a getter function, allowing anyone to view the owner’s address.
balances: This is a mapping (a key-value data structure) that associates each account (address) with its token balance (uint256). Marked as private, this ensures that the balance data can only be accessed through functions explicitly defined in the contract, maintaining data security.
Here's an example of how interfaces, modifiers, events, and storage variables come together in a complete smart contract:
IMyContract interface ensures that the contract implements the required functions (transfer and balanceOf).
onlyOwner modifier restricts certain actions (like minting new tokens) to the contract’s owner.
Transfer event provides a way to track token transfers, ensuring transparency.
balances storage variable keeps track of how many tokens each account holds.
const { expect } = require("chai");
describe("Token Contract", function () {
let Token, token, owner, addr1, addr2;
// Before each test, deploy a new token contract instance
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners(); // Retrieve test accounts
Token = await ethers.getContractFactory("MyToken");
token = await Token.deploy(); // Deploy the contract
});
describe("Deployment", function () {
it("Should set the correct owner", async function () {
expect(await token.owner()).to.equal(owner.address);
});
it("Should assign the total supply of tokens to the owner", async function () {
const ownerBalance = await token.balanceOf(owner.address);
expect(await token.totalSupply()).to.equal(ownerBalance);
});
});
describe("Transactions", function () {
it("Should transfer tokens between accounts", async function () {
// Transfer 50 tokens from owner to addr1
await token.transfer(addr1.address, 50);
const addr1Balance = await token.balanceOf(addr1.address);
expect(addr1Balance).to.equal(50);
});
it("Should fail if sender doesn’t have enough tokens", async function () {
const initialOwnerBalance = await token.balanceOf(owner.address);
// Attempt to transfer 1 token from addr1 (has 0 tokens) to addr2
await expect(
token.connect(addr1).transfer(addr2.address, 1)
).to.be.revertedWith("Insufficient balance");
// Ensure owner's balance remains unchanged
expect(await token.balanceOf(owner.address)).to.equal(initialOwnerBalance);
});
it("Should update balances after transfers", async function () {
const initialOwnerBalance = await token.balanceOf(owner.address);
// Transfer 100 tokens from owner to addr1
await token.transfer(addr1.address, 100);
// Transfer 50 tokens from addr1 to addr2
await token.connect(addr1).transfer(addr2.address, 50);
const finalOwnerBalance = await token.balanceOf(owner.address);
expect(finalOwnerBalance).to.equal(initialOwnerBalance - 100);
const addr1Balance = await token.balanceOf(addr1.address);
expect(addr1Balance).to.equal(50);
const addr2Balance = await token.balanceOf(addr2.address);
expect(addr2Balance).to.equal(50);
});
});
});npx hardhat testnode -v
npm -vnpm -vnpm install -g yarn
yarn -vnpm install -g solcsolc --versionsolc --bin --abi SimpleBank.sol -o build/npm install -g truffle
truffle versionnpm install --save-dev hardhat
npx hardhatnpx hardhat compile
npx hardhat run scripts/deploy.jstruffle init
truffle compile
truffle migratehttps://testnet.phron.ai{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getBalance",
"params": ["0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac", "latest"]
}interface IMyContract {
function transfer(address recipient, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}modifier onlyOwner() {
require(msg.sender == owner, "Caller is not the owner");
_;
}event Transfer(address indexed from, address indexed to, uint256 value);address public owner;
mapping(address => uint256) private balances;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IMyContract {
function transfer(address recipient, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
contract MyToken is IMyContract {
address public owner;
mapping(address => uint256) private balances;
event Transfer(address indexed from, address indexed to, uint256 value);
modifier onlyOwner() {
require(msg.sender == owner, "Caller is not the owner");
_;
}
constructor() {
owner = msg.sender;
balances[owner] = 1000000; // Initial token supply to owner
}
function transfer(address recipient, uint256 amount) external override returns (bool) {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function balanceOf(address account) external view override returns (uint256) {
return balances[account];
}
function mint(uint256 amount) external onlyOwner {
balances[owner] += amount;
emit Transfer(address(0), owner, amount);
// Emitting a mint event as a "transfer" from the zero address
}
}Nevertheless, not all Ethereum JSON-RPC methods are supported; some of those supported return default values (those related to Ethereum's PoW consensus mechanism in particular). This guide provides a comprehensive list of supported Ethereum JSON-RPC methods on Phron. Developers can quickly reference this list to understand the available functionality for interfacing with Phron's Ethereum-compatible blockchain.
The basic JSON-RPC methods from the Ethereum API supported by Phron are:
eth_protocolVersion — returns 1 by default
eth_syncing — returns an object with data about the sync status or false
eth_hashrate — returns "0x0" by default
— returns the latest block author. Not necessarily a finalized block
— returns false by default
— returns the chain ID used for signing at the current block
— returns the base fee per unit of gas used. This is currently the minimum gas price for each network
— returns a list of addresses owned by the client
— returns the highest available block number
— returns the balance of the given address
— returns the content of the storage at a given address
— returns information about the block of the given hash, including baseFeePerGas on post-London blocks
— returns information about the block specified by block number, including baseFeePerGas on post-London blocks
— returns all transaction receipts for a given block
— returns the number of transactions sent from the given address (nonce)
— returns the number of transactions in a block with a given block hash
— returns the number of transactions in a block with a given block number
— returns "0x0" by default
— returns "0x0" by default
— returns the code at the given address at the given block number
— creates a new message call transaction or a contract creation, if the data field contains code. Returns the transaction hash or the zero hash if the transaction is not yet available
— creates a new message call transaction or a contract creation for signed transactions. Returns the transaction hash or the zero hash if the transaction is not yet available
— executes a new message call immediately without creating a transaction on the blockchain, returning the value of the executed call
Phron supports the use of the optional state override set object. This address-to-state mapping object allows the user to specify some state to be ephemerally overridden before executing a call to eth_call. The state override set is commonly used for tasks like debugging smart contracts. Visit the documentation to learn more
— returns an estimated amount of gas necessary for a given transaction to succeed. You can optionally specify a gasPrice or maxFeePerGas and maxPriorityFeePerGas
- returns an estimate of how much priority fee, in Wei, is needed for inclusion in a block
— returns baseFeePerGas, gasUsedRatio, oldestBlock, and reward for a specified range of up to 1024 blocks
— returns the information about a transaction with a given hash. EIP-1559 transactions have maxPriorityFeePerGas and maxFeePerGas fields
— returns information about a transaction at a given block hash and a given index position. EIP-1559 transactions have maxPriorityFeePerGas and maxFeePerGas fields
— returns information about a transaction at a given block number and a given index position. EIP-1559 transactions have maxPriorityFeePerGas and maxFeePerGas fields
— returns the transaction receipt of a given transaction hash
— returns null by default
— returns null by default
— returns an array of all logs matching a given filter object
— creates a filter object based on the input provided. Returns a filter ID
— creates a filter in the node to notify when a new block arrives. Returns a filter ID
- creates a filter in the node to notify when new pending transactions arrive. Returns a filter ID
— polling method for filters (see methods above). Returns an array of logs that occurred since the last poll
— returns an array of all the logs matching the filter with a given ID
— uninstall a filter with a given ID. It should be used when polling is no longer needed. Filters timeout when they are not requested using eth_getFilterChanges after some time
Phron does not support the following Ethereum API JSON-RPC methods:
eth_getProof - returns the account and storage values of the specified account including the Merkle-proof
eth_blobBaseFee - returns the expected base fee for blobs in the next block
eth_createAccessList - creates an EIP-2930 type accessList based on a given transaction object
eth_sign - allows the user to sign an arbitrary hash to be sent at a later time. Presents a as the arbitrary hash can be fraudulently applied to other transactions
- allows the user to sign a transaction to be sent at a later time. It is rarely used due to associated security risks
1. transfer: Transfers tokens from the caller's account to the recipient.
Purpose: To allow a user to send tokens from their balance to another address.
Inputs:
recipient: The address receiving the tokens.
amount: The number of tokens to transfer.
Outputs: Returns true if the transfer is successful.
Logic: The function first checks if the sender has enough tokens, then deducts the specified amount from the sender’s balance and adds it to the recipient's balance. It also emits a Transfer event for transparency.
2. mint: Allows the contract owner to mint new tokens and increase the total supply.
Purpose: To create (mint) new tokens and add them to the owner's balance, increasing the total token supply.
Inputs:
amount: The number of tokens to be created and assigned to the owner's balance.
3. burn: Allows a user to burn tokens from their balance, reducing the total supply.
Purpose: To allow users to burn (destroy) tokens from their balance, reducing the total supply.
Inputs:
amount: The number of tokens to burn.
4. balanceOf: Returns the balance of a specified address.
Purpose: To provide a way for anyone to check the token balance of a specific address.
Inputs:
account: The address whose balance is being queried.
5. transferFrom: Allows an approved spender to transfer tokens on behalf of the token owner.
Purpose: To allow a third-party (spender) to transfer tokens on behalf of the token owner, given that they’ve been granted approval.
Inputs:
sender: The address of the token owner who is authorizing the transfer.
6. approve: Allows a token owner to approve a spender to transfer tokens on their behalf.
Purpose: To grant approval to a third-party (spender) to transfer tokens on behalf of the token owner.
Inputs:
spender: The address of the entity authorized to transfer tokens.
Each function serves a specific role in token management, ensuring that users can securely transfer, mint, burn, and authorize token transactions, all while maintaining transparency via events.
is a Web3 development platform that contains a suite of tools designed to help developers throughout the DApp development life cycle. With Tenderly, you can build, debug, test, optimize, monitor, set up alerts, and view analytics for your smart contracts on Phron.
function transfer(address recipient, uint256 amount) external returns (bool) {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}Transfer is emitted to reflect the minting process (from address 0x0 to the owner).Logic: This function can only be called by the owner (enforced by the onlyOwner modifier). It adds the specified amount of tokens to the owner's balance and emits a Transfer event, treating it as if tokens were sent from the zero address to the owner.
TransferLogic: The function checks that the caller has sufficient tokens to burn. If the condition is met, the specified amount is deducted from the user's balance, and the tokens are effectively destroyed. A Transfer event is emitted to indicate the burn process, with the destination address being 0x0.
account.Logic: This is a simple read-only function (view) that does not modify the contract’s state. It looks up the balance of the given address in the balances mapping and returns the value.
recipient: The address receiving the tokens.amount: The number of tokens to transfer.
Outputs: Returns true if the transfer is successful.
Logic: The function first checks that the sender has approved enough tokens (allowances) for the caller (spender) to transfer on their behalf. It also ensures the sender has sufficient balance. Upon success, it transfers the tokens and updates the allowance. A Transfer event is emitted for logging purposes.
amount: The number of tokens they are allowed to transfer.Outputs: Returns true if the approval is successful.
Logic: The function updates the allowances mapping, allowing the specified spender to transfer up to amount tokens from the caller's account. The Approval event is emitted to notify off-chain services of the approval.
balanceOf
Returns the token balance of a specified account.
account (address)
Returns the balance as uint256.
transferFrom
Allows a spender to transfer tokens on behalf of the token owner if they have approval.
sender (address), recipient (address), amount (uint256)
true if the transfer is successful.
approve
Approves a third-party spender to transfer tokens on behalf of the token owner.
spender (address), amount (uint256)
true if the approval is successful.
Function
Purpose
Inputs
Outputs
transfer
Transfers tokens from the caller’s account to a recipient.
recipient (address), amount (uint256)
true if the transfer is successful.
mint
Allows the owner to mint new tokens and add them to their balance.
amount (uint256)
Emits a Transfer event from 0x0 to owner.
burn
Burns tokens from the caller's balance, reducing the total supply.
amount (uint256)
Emits a Transfer event from caller to 0x0.
function mint(uint256 amount) external onlyOwner {
balances[owner] += amount;
emit Transfer(address(0), owner, amount);
// Mint event treated as a "transfer" from zero address
}function burn(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance to burn");
balances[msg.sender] -= amount;
emit Transfer(msg.sender, address(0), amount);
// Burn event treated as a "transfer" to zero address
}function balanceOf(address account) external view returns (uint256) {
return balances[account];
}function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
require(allowances[sender][msg.sender] >= amount, "Allowance exceeded");
require(balances[sender] >= amount, "Insufficient balance");
balances[sender] -= amount;
balances[recipient] += amount;
allowances[sender][msg.sender] -= amount;
emit Transfer(sender, recipient, amount);
return true;
}function approve(address spender, uint256 amount) external returns (bool) {
allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}Contract Verification - as it is essential to verify your smart contracts to take full advantage of all of Tenderly's features, Tenderly provides several methods of verification. You can verify smart contracts through the Tenderly dashboard, the Tenderly CLI and Foundry, or the Tenderly Hardhat plugin
Debugger - use the visual debugger to inspect transactions and get better insight into the behavior of your code. With the debugger, you can review a transaction's stack trace, view the calls made in a transaction, step through a contract, and review decoded inputs, outputs, and state variables. You can use the debugger on the Tenderly dashboard or the Tenderly Debugger Chrome Extension
Gas Profiler - view how much gas you're spending on a granular level, so you can optimize your smart contracts and reduce transaction gas costs
- simulate transactions in a forked development environment to learn how your transactions will behave without having to send them on-chain. This way, you can know the outcome of the transaction and make sure it works as expected before sending it to the network. You can experiment with different parameters, simulate historical and current transactions, and edit the contract source code. You can access the simulator from the Tenderly dashboard or you can use the to take advantage of the simulator programmatically
- this feature simulates the live Phron network in an isolated environment, which enables you to interact with deployed contracts and live on-chain data. Forking also takes transaction simulations a step further by enabling you to chain multiple simulations together chronologically. This allows for the testing of complex transaction scenarios where one transaction depends upon another, with the benefit of using live on-chain data. There are some limitations to be aware of when using Tenderly's forking feature. You cannot interact with any of the Phron precompiled contracts and their functions. Precompiles are a part of the Substrate implementation and therefore cannot be replicated in the simulated EVM environment. This prohibits you from interacting with cross-chain assets on Phron and Substrate-based functionality such as staking and governance
- configure real-time alerts to notify you whenever a specific event occurs, allowing you to stay informed about what's going on with your smart contracts
- create programmable functions in JavaScript or TypeScript that are executed automatically by Tenderly when a specific smart contract or chain event occurs
- visualize transaction and on-chain data to get useful insights into what's going on with your project. You can use Tenderly's analytics builder or create custom queries and scripts to meet your analytic needs
- write, compile, execute, and debug your smart contracts directly in your browser with baked-in JavaScript and Solidity editors. Every time you run your code, Tenderly creates a temporary fork that comes with 10 pre-funded accounts, each with 100 tokens for testing purposes
NotePhron is fully supported by Tenderly with the exception of the Web3 Gateway. Phron is not currently supported by Tenderly. For more information, check out Tenderly's documentation on Supported Networks.
The Tenderly dashboard provides access to the all-in-one Web3 development platform. To get started with the dashboard, you'll need to sign up for an account. Once you've signed up, you'll be able to start exploring your Tenderly dashboard.
If you prefer not to set up an account, you can also access limited features using Tenderly's explorer. Without an account, you can still gain insights for contracts and transactions. However, you won't be able to simulate transactions or create forked environments.
To interact with Tenderly's features programmatically, you can check out the Tenderly CLI GitHub repository for more information.
The following sections will show you how to get started with Tenderly on Phron. For more detailed documentation, please refer to Tenderly's documentation site.
To deploy contracts to Phron with a Tenderly Sandbox, you can navigate to sandbox.tenderly.co and take the following steps:
Enter your smart contract into the Solidity editor on the left-hand side
Select Phron from the Network menu, adjust any of the compilation settings, and specify the block to run your code on if needed
Update the JavaScript editor on the right-hand side for your contract. Ethers.js and Web3.js are included in the Sandbox by default and can be instantiated with ethers and web3, respectively. It's also important to note that the Sandbox includes global variables to ease development, so you don't need to worry about updating the RPC URL for Phron
Click on RUN when you're ready to compile your contract and execute your code
If your code contained logic to deploy your contract or send a transaction, you'll see the transaction(s) appear under the Simulated Transactions section on the bottom left-hand side.
A good place to start with the Tenderly dashboard is to add a deployed smart contract. Once you've added a contract, you'll be able to create transaction simulations and forks, use the debugger, set up monitoring and alerts, and more.
To add a new contract, you can click on Contracts on the left-side panel and click Add Contract. A pop-up will appear and you can take the following steps:
Enter the contract address
Choose Phron as the network, depending on which network you've deployed your smart contract to
(Optional) You can give your contract a name
(Optional) You can toggle the Add more slider to on if you'd like to add additional contracts. This will allow you to add more contracts after the initial contract has been added
Finally to add the contract to the dashboard, click Add contract
After a contract has been added, it will appear in the list of contracts on the Contracts dashboard. If the contract hasn't been verified yet, the dashboard will display an Unverified status along with a Verify button.
To take full advantage of the Tenderly tool set, it is recommended that you verify your smart contracts, which you can do by clicking on Verify. You can choose to verify your contract by uploading the contract's JSON, ABI, or source code. For more information, please refer to Tenderly's documentation on Smart Contract Verification.
Tenderly's forking feature simulates the live Phron network in an isolated environment, which enables you to interact with deployed contracts and live on-chain data.
There are some limitations to be aware of when using Tenderly's forking feature. You cannot interact with any of the Phron precompiled contracts and their functions. Precompiles are a part of the Substrate implementation and therefore cannot be replicated in the simulated EVM environment. This prohibits you from interacting with cross-chain assets on Phron and Substrate-based functionality such as staking and governance.
Tenderly makes creating a fork through the dashboard quite simple. To get started, click on Forks on the left-side menu and then click Create Fork. From there, you can take the following steps:
Select Phron from the Network dropdown
(Optional) Give your fork a name
If you only need data up until a specific block, you can toggle the Use Latest Block slider to off and specify the block number. Otherwise, you can leave the slider as is to include all blocks up until the latest block
Click Create
Once you've created your fork, you can start using it by deploying a contract to it or creating a transaction simulation using it.
To deploy a contract to your fork, you can click on the Deploy Contract button, upload your contract's source code, and set the compiler configurations. Once you submit the deployment, you'll see the transaction of your deployment appear under the Simulated Transactions tab and can click on the simulation for more information.
To create additional simulations, you can click the New Simulation button and enter in the configurations for the simulation. For more information on simulations, please refer to Tenderly's Simulator UI Overview documentation.
Now that you've learned how to get started with a few of Tenderly's features on Phron, please feel free to dive in and check out the other tools available in their development platform. You can visit Tenderly's documentation site for more information. You can also check out Phron's tutorial on Using Tenderly to Simulate and Debug Transactions.
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
OpenZeppelin Defender is a web-based application that allows developers to perform and automate smart contract operations in a secure way. Defender V2 offers the following components:
— Automatic code analysis powered by AI models and tools developed by OpenZeppelin engineers
— Manage the smart contract audit process and track issues and resolutions
— Manage deployments and upgrades to ensure secure releases
— to monitor your smart contract's events, functions, and transactions, and receive notifications via email
— Configure predefined incident response scenarios triggered automatically by monitors or on-demand
— Create automated actions to perform on-chain and off-chain operations
— Manage smart contract accounts, roles, and permissions easily
OpenZeppelin Defender can be used on Phron, and the Phron TestNet. This guide will show you how to get started with Defender and demonstrate using OpenZeppelin Actions and Access Control to pause a smart contract on Phron.
This section goes through the steps for getting started with OpenZeppelin Defender on Phron.
The steps described in this section assume you have installed and connected to the Phron TestNet. If you haven't connected MetaMask to the TestNet, check out our MetaMask integration guide.
In addition, you need to sign up for a free OpenZeppelin Defender account, which you can do on the main .
The contract used in this guide is an extension of the Box.sol contract used in the from the OpenZeppelin documentation. Also, the contract was made upgradable and to take full advantage of the Admin component. You can deploy your contract using the following code and following the :
NoteAfter deploying the above contract using Remix or another tool such as Hardhat, you'll need to call the
initializefunction to properly set the owner of the upgradeable contract. If you don't call this function, the owner will be set to the zero address, and you will be unable to proceed with the remainder of this tutorial.
This section goes through the steps for getting started with the to manage smart contracts on Phron.
The first step to using Defender Access Control is to add the contract you want to manage. To do so, take the following steps:
Click on the Access Control menu item
Click Add Contract
Add a name for your contract
Select the Network on which the contract is deployed. For the demo,Phron is selected
If everything was successfully imported, you should see your contract on the Access Control Contracts main screen. You should see the address that you used to deploy the Pausable Box contract in the Owner field. If you see 0x0000000000000000000000000000000000000000, this means that you didn't call the initialize function after deploying the Pausable Box contract. To simplify a later step, take a moment to add your address to your Defender Address Book by hovering over the address in the Owner field and clicking Import into Defender 2.0.
Then, you can add your address to the Defender Address Book as follows:
Enter a name for your address
Select the relevant network that the address pertains to
Paste the address
Review all the information and press Create
Proposals are actions to be carried out in the contract. You can propose any function of the contract to be enacted, including but not limited to:
Pause — available if the pause feature is detected. Pauses token transfers, minting, and burning
Upgrade — available if the upgrade feature is detected. Allows for a contract to be
Admin action — call to any function in the managed contract
In this case, a new proposal is created to pause the contract. To do so, take the following steps:
Click on the Actions menu item
Click Transaction Proposals
Enter a name for the proposal
Optionally, you may enter a description of the proposal
To create a simple new approval process consisting of only the contract owner, take the following steps:
Enter a name for the approval process
Select EOA
Select the owner of the Pausable Box contract
Review all information and press Save Changes
The last step remaining is to submit the transaction proposal. To do so, take the following steps:
Press Connect Wallet and connect your EVM account to Defender
Press Submit Transaction Proposal
Press Continue, and you'll be taken to the proposal status page. Here, you'll be able to execute the proposal. Press Approve and Execute, and confirm the transaction in your EVM wallet. Once the transaction is processed, the status should show Executed.
If all went smoothly, your Pausable Box Contract is now paused. If you'd like to try out additional scenarios, you can try creating a proposal to unpause your contract. And that's it! You're now well on your way to mastering OpenZeppelin Defender to manage your smart contracts on Phron. For more information, be sure to check out the .
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Scaffold-ETH 2 is a collection of commonly used Ethereum development tools to quickly deploy a Solidity smart contract and launch a DApp with a React frontend.
Scaffold-ETH 2 consists of several sub-components, including for creating, deploying, and testing smart contracts and for building a React frontend. These components can be used on Phron networks with some slight modifications.
This guide will walk through the steps to deploy and run the default example contract and DApp that Scaffold-ETH 2 comes with on a Phron network.
To get started, you will need the following:
An account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the
A Phronscan API key
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
First, download .
From the command line, enter:
After the download completes, run:
The process for developing a project with Scaffold-ETH 2 can be outlined as follows:
Update the network configurations in Hardhat for Phron
Add your smart contracts to the packages/hardhat/contracts
Edit your deployment scripts in the packages/hardhat/deploy
Deploy your smart contracts to Phron
In this guide, you can use the default contract and frontend that you get out of the box when you clone the Scaffold-ETH 2 repository. All you'll have to do is modify these components for Phron.
In the following sections, you'll update the network configurations in the Hardhat configuration file to target the Phron-based network you want to interact with, and deploy and verify the example contract to that network.
You can begin by making modifications to the Hardhat component under the packages/hardhat folder. You'll primarily be editing the hardhat.config.js file to configure it for Phron. However, you'll also need to create a .env file to store a couple of variables that will be consumed by the hardhat.config.js file.
You can refer to the .env.example file for the variables that are already used in the hardhat.config.js file. For Phron, you'll only need to create two variables: DEPLOYED_PRIVATE_KEY and ETHERSCAN_API_KEY.
Check out the Etherscan Plugins documentation to learn how to generate a Phronscan API key.
To get started, create a .env file:
Edit your .env file to include the following variables:
The private key you add to your .env file corresponds to the account that will deploy and interact with the smart contracts in your Hardhat project. Additionally, the Etherscan API key will correspond to your Phronscan API key and will be used to verify your deployed smart contracts. To learn how to generate a Phronscan API key, check out the Etherscan Plugins documentation.
With the environment variables taken care of, next you can modify the hardhat.config.js file for Phronscan:
Set the constant defaultNetwork to the network you are deploying the smart contract to
Add the network configurations for the Phron network you want to interact with under the networks configuration object
For more information on using Hardhat with Phron, please check the dedicated Hardhat page for more details.
After all the modifications to the configuration files are done, you can deploy your contract to the configured Phron-based network.
First, you can compile your contract by running:
Then, you can run the following command from the root directory of your project:
NoteIf you did not set the
defaultNetworkconfig in thehardhat.config.jsfile, you can append--network INSERT_NETWORKto the command. For example, the following command would deploy a contract to Phron.
If you would also like to use Scaffold-ETH 2 to verify the deployed smart contract and have entered your Phronscan API key into the .env file, you can go ahead and verify your deployed contract.
If the smart contract you are verifying has constructor method parameters, you will also need to append the parameters used to the end of the command.
You can use the following command to verify the smart contract:
NoteIf you did not set the
defaultNetworkconfiguration in thehardhat.config.jsfile, you can append--network INSERT_NETWORKto the command. For example, the following command would verify a contract on Phron.
After a short wait, the console output will display the verification result and, if successful, the URL to the verified contract on Phronscan.
For more information about verifying smart contracts on Phron using the Hardhat Etherscan plugin, please refer to the Etherscan Plugins page.
In the following sections, you'll modify the Next.js configuration so that it targets the Phron-based network that your contract has been deployed to, and then you'll launch the dApp.
To target the Phron network that you deployed your smart contract to, you'll need to edit the configurations in the packages/nextjs/scaffold.config.ts file. More specifically, you'll need to modify the targetNetworks array in the scaffoldConfig object. You can use the to specify the chain(s) you've deployed your contract to.
That's all you have to do to configure Next.js! Next, you can launch the dApp.
After all the modifications to the configuration files are done, you can launch the example dApp. To do so, you can run:
This will launch the React-based DApp frontend at by default. You can then point your browser to and interact with the React frontend by connecting your wallet or checking out the contract debugger page.
And that's it! Now that you have the basics down, feel free to create and deploy your own smart contracts and modify the frontend to fit your dApp's needs! For more information, you can check out the .
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
contracts and libraries have become a standard in the industry. They help developers minimize risk, as their open-source code templates are battle-tested for Ethereum and other blockchains. Their code includes the most used implementations of ERC standards and add-ons and often appears in guides and tutorials around the community.
is an integrated development environment (IDE) for developing smart contracts on Ethereum and Ethereum-compatible chains. It provides an easy-to-use interface for writing, compiling, and deploying smart contracts. Given Phron’s Ethereum compatibility features, you can use Remix directly with any Phron network.
Paste the contract address
If you have verified your contract, the ABI will be automatically imported. Otherwise, paste the contract ABI. This can be obtained either in Remix or in the .json file generally created after the compilation process (for example, in Hardhat)
Once you've checked all the information, click on the Create button
Select the target contract from the dropdown of imported contracts
Select the function to be carried out as part of the proposal
Select the desired approval process. For demo purposes, a simple approval process consisting of only the owner will be created in the following step
Verify your smart contracts with the Etherscan plugin and your Phronscan API key
Configure your frontend to target Phron in the packages/nextjs/scaffold.config.ts file
Edit your frontend as needed in the packages/nextjs/pages directory
Because Phron is fully Ethereum compatible, all of OpenZeppelin's contracts and libraries can be implemented without any changes.
This guide is divided into two sections. The first part describes the OpenZeppelin Contracts Wizard, a great online tool to help you create smart contracts using OpenZeppelin code. The second section provides a step-by-step guide on how you can deploy these contracts using Remix on Phron.
OpenZeppelin has developed an online web-based interactive contract generator tool that is probably the easiest and fastest way to write your smart contract using OpenZeppelin code, called Contracts Wizard.
Currently, the Contracts Wizard support the following ERC standards:
The wizard is comprised of the following sections:
Token standard selection — shows all the different standards supported by the wizard
Settings — provides the baseline settings for each token standard, such as token name, symbol, pre-mint (token supply when the contract is deployed), and URI (for non-fungible tokens)
Features — list of all features available for each token standard. You can find more information about the different features in the following links:
Access Control — list of all the available for each token standard
Interactive code display — shows the smart contract code with the configuration as set by the user
Once you have set up your contract with all the settings and features, it is just as easy as copying and pasting the code into your contract file.
This section goes through the steps for deploying OpenZeppelin contracts on Phron. It covers the following contracts:
ERC-20 (fungible tokens)
ERC-721 (non-fungible tokens)
ERC-1155 (multi-token standard)
All the code of the contracts was obtained using OpenZeppelin Contract Wizard.
The steps described in this section assume you have MetaMask installed and connected to the Phron TestNet. If you're adapting this guide for Phron, make sure you're connected to the correct network. Contract deployment is done using the Remix IDE via the Injected Provider environment. You can find corresponding tutorials in the following links:
Interacting with Phron using MetaMask
Interacting with Phron using Remix
For this example, an ERC-20 token will be deployed to Phron. The final code used combines different contracts from OpenZeppelin:
ERC20.sol — ERC-20 token implementation with the optional features from the base interface. Includes the supply mechanism with a mint function but needs to be explicitly called from within the main contract
Ownable.sol — extension to restrict access to certain functions
The mintable ERC-20 OpenZeppelin token contract provides a mint function that the owner of the contract can only call. By default, the owner is the contract's deployer address. There is also a premint of 1000 tokens sent to the contract's deployer configured in the constructor function.
The first step is to go to Remix and take the following steps:
Click on the Create New File icon and set a file name. For this example, it was set to ERC20.sol
Make sure the file was created successfully. Click on the file to open it up in the text editor
Write your smart contract using the file editor. For this example, the following code was used:
This ERC-20 token smart contract was extracted from the Contract Wizard, setting a premint of 1000 tokens and activating the Mintable and Permit features.
Once your smart contract is written, you can compile it by taking the following steps:
Head to the Solidity Compiler
Click on the compile button
Alternatively, you can check the Auto compile feature
With the contract compiled, you are ready to deploy it taking the following steps:
Head to the Deploy & Run Transactions tab
Change the environment to Injected Provider. This will use MetaMask's injected provider. Consequently, the contract will be deployed to whatever network MetaMask is connected to. MetaMask might show a pop-up outlining that Remix is trying to connect to your wallet
Select the proper contract to deploy. In this example, it is the MyToken contract inside the ERC20.sol file
Enter the address of the initial owner and click on the Deploy button. Review the transaction information in MetaMask and confirm it
After a few seconds, the transaction should get confirmed, and you should see your contract under Deployed Contracts
And that is it! You've deployed an ERC-20 token contract using OpenZeppelin's contracts and libraries. Next, you can interact with your token contract via Remix, or add it to MetaMask.
For this example, an ERC-721 token will be deployed to Phron. The final code used combines different contracts from OpenZeppelin:
ERC721.sol — ERC-721 token implementation with the optional features from the base interface. Includes the supply mechanism with a _mint function but needs to be explicitly called from within the main contract
ERC721Burnable.sol — extension to allow tokens to be destroyed by their owners (or approved addresses)
ERC721Enumerable.sol — extension to allow on-chain enumeration of tokens
Ownable.sol — extension to restrict access to certain functions
The mintable ERC-721 OpenZeppelin token contract provides a mint function that can only be called by the owner of the contract. By default, the owner is the contract's deployer address.
As with the ERC-20 contract, the first step is to go to Remix and create a new file. For this example, the file name will be ERC721.sol.
Next, you'll need to write the smart contract and compile it. For this example, the following code is used:
This ERC-721 token smart contract was extracted from the Contract Wizard, setting the Base URI as Test and activating the Mintable, Burnable, and Enumerable features.
With the contract compiled, next you will need to:
Head to the Deploy & Run Transactions tab
Change the environment to Injected Provider. This will use MetaMask's injected provider. Consequently, the contract will be deployed to whatever network MetaMask is connected to. MetaMask might show a pop-up outlining that Remix is trying to connect to your wallet
Select the proper contract to deploy. In this example, it is the MyToken contract inside the ERC721.sol file
Enter the address of the initial owner and click on the Deploy button. Review the transaction information in MetaMask and confirm it
After a few seconds, the transaction should get confirmed, and you should see your contract under Deployed Contracts
And that is it! You've deployed an ERC-721 token contract using OpenZeppelin's contracts and libraries. Next, you can interact with your token contract via Remix, or add it to MetaMask.
For this example, an ERC-1155 token will be deployed to Phron. The final code used combines different contracts from OpenZeppelin:
ERC1155.sol — ERC-1155 token implementation with the optional features from the base interface. Includes the supply mechanism with a _mint function but needs to be explicitly called from within the main contract
Pausable.sol — extension to allows pausing tokens transfer, mintings and burnings
Ownable.sol — extension to restrict access to certain functions
OpenZeppelin's ERC-1155 token contract provides a _mint function that can only be called in the constructor function. Therefore, this example creates 1000 tokens with an ID of 0, and 1 unique token with an ID of 1.
The first step is to go to Remix and create a new file. For this example, the file name will be ERC1155.sol.
As shown for the ERC-20 token, you'll need to write the smart contract and compile it. For this example, the following code is used:
This ERC-1155 token smart contract was extracted from the Contract Wizard, setting no Base URI and activating Pausable feature. The constructor function was modified to include the minting of both a fungible and a non-fungible token.
With the contract compiled, next you will need to:
Head to the Deploy & Run Transactions tab
Change the environment to Injected Provider. This will use MetaMask's injected provider. Consequently, the contract will be deployed to whatever network MetaMask is connected to. MetaMask might show a pop-up outlining that Remix is trying to connect to your wallet
Select the proper contract to deploy. In this example, it is the MyToken contract inside the ERC1155.sol file
Enter the address of the initial owner and click on the Deploy button. Review the transaction information in MetaMask and confirm it
After a few seconds, the transaction should get confirmed, and you should see your contract under Deployed Contracts
And that is it! You've deployed an ERC-1155 token contract using OpenZeppelin's contracts and libraries. Next, you can interact with your token contract via Remix.
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
This guide walks through the process of creating and deploying a Solidity smart contract to a Phron development node using the Remix IDE.
If you're familiar with Remix, you can skip ahead to the Connect Remix to Phron section to learn how to use Remix with Phron.
For the purposes of this guide, you'll need to have the following:
A locally running Phron development node
MetaMask installed and connected to your development node
If you followed the guides above, you should have a local Phron node, which will begin to author blocks as transactions arrive.
Your development node comes with 10 pre-funded accounts. You should have MetaMask connected to your Phron development node and have imported at least one of the pre-funded accounts. You can refer to the Import Accounts section of the MetaMask docs for step-by-step instructions on how to import a development account.
If you're adapting this guide for Phron, make sure you are connected to the correct network and have an account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet.
If you navigate to https://remix.ethereum.org/, you'll see that the layout of Remix is split into four sections:
The plugin panel
The side panel
The main panel
The terminal
The plugin panel displays icons for each of the preloaded plugins, the plugin manager, and the settings menu. You'll see a few icons there for each of the preloaded plugins, which are the File explorer, Search in files, Solidity compiler, and Deploy and run transactions plugins. As additional plugins are activated, their icons will appear in this panel.
The side panel displays the content of the plugin that is currently being viewed. By default, you'll see the File explorer plugin, which displays the default workspace and some preloaded files and folders. However, if you select one of the other icons from the plugin panel, you'll see the content for the selected plugin.
The main panel is automatically loaded with the Home tab, which contains links to a variety of resources. You can close this tab at any time and reopen it by clicking on the blue Remix icon in the top left corner of the plugin panel. The main panel is where you'll be able to see each of the files you're working with. For example, you can double-click on any file in the File explorer side panel and it will appear as a tab in the main panel.
The terminal panel is similar to a standard terminal that you have on your OS; you can execute scripts from it, and logs are printed to it. All transactions and contract interactions are automatically logged to the terminal. You can also interact with the Ethers and Web3 JavaScript libraries directly from the terminal.
For this example, you will create a new file that contains an ERC-20 token contract. This will be a simple ERC-20 contract based on the current OpenZeppelin ERC-20 template. The contract will create a MyToken token with the MYTOK symbol that mints the entirety of the initial supply to the creator of the contract.
From the File explorer tab on the plugin panel, you can create a new file by taking the following steps:
Click on the file icon
Enter the name of the contract: MyToken.sol
The main panel will switch to an empty file where you can add the Solidity code for the contract. Paste the MyToken.sol smart contract into the new file:
Before you compile a contract, make sure you've selected the file of the contract from the File explorer tab. Then, select the Solidity Compiler option from the plugin panel.
Make sure that the compiler version in the top-left corner meets the version defined in your contract and the version defined in OpenZeppelin's ERC20.sol contract. For example, the MyToken.sol contract requires Solidity ^0.8.0, but at the time of writing, OpenZeppelin's ERC20.sol contract requires ^0.8.20, so the compiler needs to be set to version 0.8.20 or newer.
The Solidity compiler plugin also lets you change some settings and apply advanced configurations for the compiler. If you're planning on iterating over the smart contract, you can check the Auto compile box, and whenever you make a change, the contract will automatically be recompiled.
Additionally, from the Advanced Configurations menu, you can change the EVM version, enable optimizations, and set the number of times the bytecode is expected to be run throughout the contract's lifetime; the default is set to 200 times. For more information on contract optimization, please refer to the Solidity docs on The Optimizer.
For this example, no additional configurations are needed. To compile the MyToken.sol contract, simply click on the Compile MyToken.sol contract. If the compilation was successful, you'll see a green check mark appear on the plugin panel next to the Solidity compiler plugin.
If you tried to compile your smart contract but there was an error or warning, you can easily debug the issue with the help of ChatGPT directly from the Solidity compiler plugin in Remix.
For example, if you only provided the token name to the ERC-20 constructor but forgot the token symbol and tried to compile the contract, an error would appear in the side panel. You can scroll down to read the error, and you'll see that there is also an ASK GPT button. To get help debugging the issue, you can click on ASK GPT, and a response will be returned in the Remix terminal that will guide you in the right direction to try and fix the issue. If you need additional help, you can go straight to the source and ask ChatGPT directly.
Once you successfully fix the issue and recompile the contract, you'll see a green check mark appear on the plugin panel next to the Solidity compiler plugin.
The Deploy and run transactions plugin enables you to configure contract deployment options, deploy contracts, and interact with deployed contracts.
The side panel consists of the following deployment options:
Environment - allows you to choose the execution environment for deployment
Account - the account from which the deployment transaction will be sent
Gas Limit - the maximum amount of gas that the deployment transaction can consume
Value - the amount of the native asset to send along with the deployment transaction
Contract - the contract to deploy
Deploy - sends the deployment transaction to the specified environment using the selected account, gas limit, value, and the values for any constructor arguments
At Address - allows you to interact with an existing contract by specifying its address
The following section will cover how to configure the environment for deployment to be Phron.
To deploy the smart contract to Phron, you'll need to make sure that you've connected your wallet to your Phron development node or the Phron network of your choice. Then, from the Deploy and run transactions tab, you can connect Remix to your wallet by selecting your wallet from the ENVIRONMENT dropdown. For example, if you have Trust Wallet installed, you'll see Injected Provider - TrustWallet from the dropdown. Aside from injected providers, you can also connect to Phron via WalletConnect.
For this example, MetaMask will be used. You should already have MetaMask installed and connected to your local Phron development node. If not, please refer to the Interacting with Phron Using MetaMask guide for step-by-step instructions.
From the ENVIRONMENT dropdown, select Injected Provider - MetaMask.
MetaMask will pop up automatically and prompt you to connect to Remix. You'll need to:
Select the account you want to connect to Remix
Click Next
Click Connect to connect your account to Remix
Once you've connected MetaMask to Remix, the side panel will update to reveal the network and account you're connected to. For a Phron development node, you should see Custom (1281) network.
Now that you've connected your wallet, you're ready to deploy the contract. Since you're deploying a simple ERC-20 token smart contract, the default gas limit set by Remix of 3 million is more than enough, and you don't need to specify a value to send along with the deployment. As such, you can take the following steps to deploy the contract:
Make sure the ENVIRONMENT is set to Injected Provider - MetaMask
Make sure the connected account is the one you want to deploy the transaction from
Use the default GAS LIMIT of 3000000
Leave the VALUE as 0
Make sure MyToken.sol is the selected contract
Expand the DEPLOY dropdown
Specify the initial supply. For this example, you can set it to 8 million tokens. Since this contract uses the default of 18 decimals, the value to put in the box is 8000000000000000000000000
Click transact to send the deployment transaction
MetaMask will pop up, and you can click Confirm to deploy the contract
Once the transaction has been deployed, you'll see details about the deployment transaction in the Remix terminal. Additionally, the contract will appear under the Deployed Contracts section of the side panel.
Once you've deployed a smart contract or accessed an existing contract via the At Address button, the contract will appear under the Deployed Contracts section of the side panel. You can expand the contract to view all of the contract's functions you can interact with.
To interact with a given function, you can click on the function name, which will be contained in an orange, red, or blue button. Orange buttons are for functions that write to the blockchain and are non-payable; red buttons are for functions that write to the blockchain and are payable; and blue buttons are for functions that read data from the blockchain.
Depending on the function you're interacting with, you may need to input parameter values. If the function requires inputs, you'll be able to enter them by expanding the function and entering a value for each of the parameters.
If the function you're interacting with is payable, you'll be able to enter an amount in the VALUE field towards the top of the side panel, in the same value field used for contracts that have payable constructors.
If you expand the MYTOKEN contract dropdown, you'll be able to see all of the available functions you can interact with. To interact with a given function, you can provide any inputs, if needed, and then click on the button containing the function name you want to interact with.
For example, if you wanted to call the tokenSupply function, you wouldn't need to sign a transaction, as you'd get a response right away.
On the other hand, if you call the approve function, which will approve an account as a spender of a given amount of MYTOK tokens, you'll need to submit the approval in MetaMask. To test this out, you can take the following steps:
Set the spender to an account that you want to be able to spend tokens on your behalf. For this example, you can use Bob's account (one of the pre-funded development accounts): 0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0
Enter the amount the spender can spend. For this example, you can approve Bob to spend 10 MYTOK by entering in 10000000000000000000
Press transact
MetaMask will pop up and you'll need to review the details of the approval and submit the approval
To view your balance or approvals, or transfer MYTOKs, you can add the MYTOK to your wallet. For information on how to add a token to MetaMask, you can refer to the Add an ERC-20 Token section of our MetaMask documentation.
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Brownie is an Ethereum development environment that helps Python developers manage and automate the recurring tasks inherent to building smart contracts and DApps. Brownie can directly interact with Phron's Ethereum API so it can also be used to deploy smart contracts on Phron.
This guide will cover how to use Brownie to compile, deploy, and interact with Ethereum smart contracts on the Phron TestNet.
Please note that Brownie is no longer actively maintained. You can check out as an alternative Python Ethereum development environment.
To get started, you will need the following:
Have MetaMask installed and connected to Phron
Have an account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the .
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
For this guide, Python version 3.9.10, pip version 22.0.3, and pipx version 1.0.0 were used.
You will need to install Brownie and create a Brownie project if you don't already have one. You can choose to either create an empty project or use a , which is essentially a template to build your project on. For this example, you can create an empty project. You can get started by completing the following steps:
Create a directory for your project
If you don't already have pipx installed, go ahead and install it
, which is used to run executables installed locally in your project. Brownie will be installed into a virtual environment and be available directly from the command line
NoteA common error while installing Brownie on Ubuntu is:
This can be resolved by using the following command:
Create a project
Your Brownie project should contain the following empty directories:
build - for project data such as contract artifacts from compilation
contracts - to store the smart contract files
interfaces - for smart contract interfaces that are required for your project
reports - for JSON report files for use in the
Another important file to note that is not included in an empty project is the brownie-config.yaml configuration file. The configuration file is optional and comes in handy when customizing specific settings such as a default network, compiler version and settings, and more.
To deploy to a Phron network, you'll need to add and configure the network. Network configurations in Brownie are added from the command line. Brownie can be used with both development and live environments.
Phron are supported out of the box with Brownie as of version 1.19.3. To view the complete list of supported networks, you can run the following command:
If you're looking to deploy a contract to a Phron development node you'll need to add the network configurations. Under the hood, Brownie uses Ganache for development environments. However, since a Phron development node acts as your own personal development environment, Ganache isn't needed. Therefore, you can configure a development node as a "live" network.
To add Phron development node configurations, you can run the following command:
If you successfully added the network, you'll see a success message along with the network details in the terminal.
To deploy to a Phron network, or run tests on a specific network, you can specify the network by appending the following to the given command:
If you would like to set a default network, you can do so by adding the following snippet to the brownie-config.yaml configuration file:
NoteKeep in mind that the
brownie-config.yamlfile isn't automatically created, you can optionally create it yourself.
It is recommended that you override the default Brownie RPC URLs to your own RPC endpoint or the public Phron network endpoints. You can override the default Brownie RPC URL for each network as follows:
Before you can deploy a contract, you'll need to configure your account, which is also done from the command line. To add a new account you can run:
Make sure to replace INSERT_ACCOUNT_NAME with your name of choice. For this example, alice will be used as the account name.
You'll be prompted to enter in your private key and a password to encrypt the account with. If the account was successfully configured, you'll see your account address printed to the terminal.
Next you can create a contract inside of the contracts directory. The smart contract that you'll deploy as an example will be called Box, it will let you store a value that can be retrieved later. You can create a Box.sol contract by running the following command:
Open the file and add the following contract to it:
To compile the contract you can simply run:
NoteThe first time you compile your contracts it may take longer than usual while the
solcbinary is installed.
After compilation, you'll find the build artifacts in the build/contracts directory. The artifacts contain the bytecode and metadata of the contract, which are .json files. The build directory should already be in the .gitignore file but if it's not, it’s a good idea to add it there.
If you want to specify the compiler version or compilation settings, you can do so in the brownie-config.yaml file. Please note that if you haven't already created this file, you will need to do so. Then you can specify the compiler like so:
NoteYou can view the list of in their documentation.
Your contracts will only be compiled again if Brownie notices that a change has been made. To force a new compilation, you can run:
In order to deploy the Box.sol smart contract, you will need to write a simple deployment script. You can create a new file under the scripts directory and name it deploy.py:
Next, you need to write your deployment script. To get started start, take the following steps:
Import the Box contract and the accounts module from brownie
Load your account using accounts.load() which decrypts a keystore file and returns the account information for the given account name
Use the deploy method that exists within this instance to instantiate the smart contract specifying the
You can now deploy the Box.sol contract using the run command and specifying the network:
After a few seconds, the contract is deployed, and you should see the address in the terminal.
Congratulations, your contract is live! Save the address, as you will use it to interact with this contract instance in the next step.
You can interact with contracts using the Brownie console for quick debugging and testing or you can also write a script to interact.
To interact with your newly deployed contract, you can launch the Brownie console by running:
The contract instance will automatically be accessible from the console. It will be wrapped in a ContractContainer which also enables you to deploy new contract instances. To access the deployed contract you can use Box[0]. To call the store method and set the value to 5, you can take the following steps:
Create a variable for the contract
Call the store method using your account and set the value to 5
Enter the password for your account
The transaction will be signed by your account and broadcasted to the network. Now, you can retrieve the value by taking these steps:
Call the retrieve method
Enter your password
You should see 5 or the value you have stored initially.
You can also write a script to interact with your newly deployed contract. To get started, you can create a new file in the scripts directory:
Next, you need to write your script that will store and then retrieve a value. To get started, take the following steps:
Import the Box contract and the accounts module from brownie
Load your account using accounts.load() which decrypts a keystore file and returns the account information for the given account name
Create a variable for the Box contract
To run the script, you can use the following command:
You'll need to enter the password for Alice to send the transaction to update the stored value. Once the transaction goes through, you should see a transaction hash and a value of 5 printed to the console.
Congratulations, you have successfully deployed and interacted with a contract using Brownie!
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Ape is an Ethereum development environment that helps Python developers manage and automate the recurring tasks inherent to building smart contracts and DApps. Ape can directly interact with Phron's Ethereum API, so you can also use Ape to deploy smart contracts on Phron.
This guide will walk you through using Ape to compile, deploy, and interact with Ethereum smart contracts on the Phron TestNet.
To get started, ensure you have the following:
MetaMask installed and connected to Phron
An account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
If you don't already have an Ape project, you must install Ape and create a new one. You can follow the steps below to get started and create an empty project:
Create a directory for your project
If you don't have pipx installed, install it
Create a project
Your Ape project contains a bare-bones ape-config.yaml file for customizing specific settings and the following empty directories:
contracts - an empty directory for storing smart contracts
scripts - an empty directory for storing Python scripts, such as deployment scripts and scripts to interact with your deployed contracts
tests - an empty directory for pytest testing scripts
You'll need to import an account before you can deploy smart contracts or interact with previously deployed contracts from your Ape project. You can run the following command, which will import your account and give it a name:
You'll then be prompted to enter your private key and add a password to encrypt your account.
NoteIf you wish to use a mnemonic instead, you can append the
--use-mnemonicoption to the import command.
Now that you have set up your account, you can start writing smart contracts. As a basic example, you can use the following Box contract to store a value you can retrieve later.
Start by creating a file named Box.sol inside the contracts directory:
Open the file and add the following contract to it:
You can store any additional contracts in the contracts directory.
Before compiling the Solidity, you must install the Solidity compiler plugin. Running the following command will install the latest version of the plugin:
To use a specific version of Solidity or a specific EVM version, you can modify your ape-config.yaml file as follows:
For more information on the Solidity plugin, please refer to the .
With the Solidity plugin installed, the next step is to compile the smart contract:
After compilation, you can find the bytecode and ABI for your contracts in the .build directory.
Before you deploy your contract, you can test it out directly inside your Ape project using the to make sure it works as you expect.
You should already have a tests directory where you'll create your tests, but if not, please create one, as all tests must be located in a directory named tests. Additionally, each test file name must start with test_ and end with .py. So, first, you can create a test file for the Box.sol contract:
In addition to the test file, you can create a conftest.py file that will define a couple of essential . Fixtures allow you to define functions that set up the necessary environment or resources to run your tests. Note that while the Box.sol contract is simple, incorporating fixtures into your testing process is good practice.
To create the file, you can run the following command:
Since your tests will rely on the injection of the fixtures, you must define the fixtures first. When defining fixtures, you need to apply the pytest.fixture decorator above each function. For this example, you'll create two fixtures: one that defines the owner of the contract and one that deploys the contract from the owner's account.
The owner fixture will use the built-in accounts fixture to take the first account in the list of test accounts provided by Ape and return it. The box fixture will deploy the Box contract type using the built-in project fixture, you simply have to provide the name of the contract and use the owner fixture to deploy it.
tests/conftest.py
Now that you've created the fixtures, you can start creating your tests. Each test function name must start with test_ and describe what the test does. For this example, you can use test_store_value, as you'll create a test for the store function. The test will store a value and then retrieve it, asserting that the retrieved value is equal to the value passed into the store function.
To use the owner and box fixtures, you must pass them into your test function, which will inject the fixtures automatically for you to use. The owner account will be used to call the store function of the box contract instance.
tests/test_box.py
And that's it! That's all you'll need inside your test file. You can use the following command to run the test:
The results of running the test will be printed to the terminal.
Now that you have confidence in your contract, the next step is to deploy it.
To deploy your contracts, create a deployment script named deploy.py inside of the scripts directory:
Next, you'll need to write the deployment script. You'll need to load the account you will use to deploy the contract and access it by its name using the project manager.
scripts/deploy.py
Now you're ready to deploy the Box contract! To configure your project for Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
Take the following steps to initiate and send the deployment transaction:
Run the deployment script using the ape run deploy command
Review the transaction details and enter y to sign the transaction
Enter your passphrase for your account
Enter y to leave your account unlocked or n to lock it
After you follow the prompts and submit the transaction, the transaction hash, total fees paid, and contract address will be displayed in the terminal.
Congratulations! Your contract is live! Save the address to interact with your contract in the following section.
You can interact with contracts using the Ape console for quick debugging and testing, or write a script.
To interact with your newly deployed contract, you can launch the Ape console by running:
Next, you'll need to create a contract instance using the contract's address:
Now, you can interact with your contract instance! For example, you can set the variable to be stored in the Box contract using the following commands:
Call the store method by passing in a value to store and the account you want to use to send the transaction:
Review the transaction details and enter y to sign the transaction
If you previously locked your account, you must enter your passphrase to unlock it. Otherwise, Ape will use the cached key for your account
If you unlocked your account in the previous step, you'll be asked if you want to leave your account unlocked. You can enter
After you follow the prompts and submit the transaction, the transaction hash and total fees paid will be displayed in the terminal.
Then, you can retrieve the stored value by calling the retrieve method:
The number you just stored in the previous steps will be printed to the console.
contract.retrieve() 4
You can also write a script to interact with your newly deployed contract. To get started, you can create a new file in the scripts directory:
Next, you can write a script that stores and retrieves a value. Note that when creating a contract instance to interact with, you must pass in the address of the deployed contract.
scripts/store-and-retrieve.py
Now, you can run the script to set the stored value and retrieve it:
Run the script
Review the transaction details and enter y to sign the transaction
If you previously locked your account, you must enter your passphrase to unlock it. Otherwise, Ape will use the cached key for your account
If you unlocked your account in the previous step, you'll be asked if you want to leave your account unlocked. You can enter y to leave it unlocked or n to lock it
Once completed, you should see a transaction hash and a value of 4 printed to the console.
Congratulations! You have successfully deployed and interacted with a contract using Ape!
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract PausableBox is Initializable, PausableUpgradeable, OwnableUpgradeable {
uint256 private value;
// Emitted when the stored value changes
event ValueChanged(uint256 newValue);
// Initialize
function initialize() initializer public {
__Ownable_init(_msgSender());
__Pausable_init_unchained();
}
// Stores a new value in the contract
function store(uint256 newValue) whenNotPaused public {
value = newValue;
emit ValueChanged(newValue);
}
// Reads the last stored value
function retrieve() public view returns (uint256) {
return value;
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
}git clone https://github.com/scaffold-eth/scaffold-eth-2.gityarn installtouch packages/hardhat/.envDEPLOYER_PRIVATE_KEY=INSERT_PRIVATE_KEY
ETHERSCAN_API_KEY=INSERT_PHRONSCAN_API_KEYdefaultNetwork = 'phron';phron: {
url: "INSERT_RPC_API_ENDPOINT",
accounts: [deployerPrivateKey],
},yarn compileyarn deployyarn deploy --network phronyarn verify --api-url https://testnet.phronscan.ioyarn verify --network phron --api-url https://testnet.phronscan.iotargetNetworks: [chains.phron],yarn start// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract MyToken is ERC20, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
ERC20Permit("MyToken")
{
_mint(msg.sender, 1000 * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC721, ERC721Enumerable, ERC721Burnable, Ownable {
constructor(address initialOwner)
ERC721("MyToken", "MTK")
Ownable(initialOwner)
{}
function _baseURI() internal pure override returns (string memory) {
return "Test";
}
function safeMint(address to, uint256 tokenId) public onlyOwner {
_safeMint(to, tokenId);
}
// The following functions are overrides required by Solidity
function _update(address to, uint256 tokenId, address auth)
internal
override(ERC721, ERC721Enumerable)
returns (address)
{
return super._update(to, tokenId, auth);
}
function _increaseBalance(address account, uint128 value)
internal
override(ERC721, ERC721Enumerable)
{
super._increaseBalance(account, value);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol";
contract MyToken is ERC1155, Ownable, ERC1155Pausable {
constructor() ERC1155("") Ownable() {
_mint(msg.sender, 0, 1000 * 10 ** 18, "");
_mint(msg.sender, 1, 1, "");
}
function setURI(string memory newuri) public onlyOwner {
_setURI(newuri);
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
// The following function is an override required by Solidity
function _update(address from, address to, uint256[] memory ids, uint256[] memory values)
internal
override(ERC1155, ERC1155Pausable)
{
super._update(from, to, ids, values);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MYTOK") {
_mint(msg.sender, initialSupply);
}
}scripts - where Python scripts used for deploying contracts or other automated tasks will live
tests - to store Python scripts for testing your project. Brownie uses the pytest framework for unit testing
fromgas_limitUse the store and retrieve functions to store a value and then retrieve it and print it to the console
Enter a name for your project
mkdir brownie && cd browniepython3 -m pip install --user pipx
python3 -m pipx ensurepathpipx install eth-browniepip seemed to fail to build package:
pyyaml==5.4.1
Some possibly relevant errors from pip install:
error: subprocess-exited-with-error
AttributeError: cython_sources
Error installing eth-brownie.pip3 install wheel && \
pip3 install --no-build-isolation "Cython<3" "pyyaml==5.4.1" && \
pip3 install --upgrade --no-cache-dir eth-browniebrownie initphron@ubuntu:~$ brownie init
Brownie v1.19.1 - Python development framework for Ethereum
SUCCESS: A new Brownie project has been initialized at /home/moonbeam/brownie
phron@ubuntu:~$ ls
build contracts interfaces reports scripts testsbrownie networks listbrownie networks list
Phron ├─Mainnet: Phronbrownie networks add Phron phron-dev host=http://127.0.0.1:9944 name=Development chainid=7744--network phron-mainnetworks:
default: phron-mainbrownie networks modify phron-main host=INSERT_RPC_API_ENDPOINTbrownie networks modify phron-main host=https://testnet.phron.ai
SUCCESS: Network 'Mainnet' has been modified └─Mainnet ├─id: phron-main ├─chainid: 7744 ├─explorer: https://testnet.phronscan.io/ ├─host: https://testnet.phronscan └─multicall2: 0x1337BedC9D22ecbe766dF105c9623922A27963ECbrownie accounts new INSERT_ACCOUNT_NAMEbrownie accounts new aliceBrownie v1.19.1 - Python development framework for Ethereum
Enter the private key you wish to add: 0x5b92d6e98884f76de468fa3f6278f880748bebc13595d45af5bdc4da702133Enter the password to encrypt this account with: SUCCESS: A new account 'Oxf24FF3a9CF04c71Dbc94D06566f7A27B94566cac' has been generated with the id 'alice'cd contracts && touch Box.sol// contracts/Box.sol
pragma solidity ^0.8.1;
contract Box {
uint256 private value;
// Emitted when the stored value changes
event ValueChanged(uint256 newValue);
// Stores a new value in the contract
function store(uint256 newValue) public {
value = newValue;
emit ValueChanged(newValue);
}
// Reads the last stored value
function retrieve() public view returns (uint256) {
return value;
}
}brownie compilebrownie compileBrownie v1.19.1 - Python development framework for Ethereum
New compatible solc version available: 0.8.4Compiling contracts... Solc version: 0.8.4 Optimizer: Enabled Runs: 200 EVM Version: IstanbulGenerating build data... - BoxProject has been compiled. Build artifacts saved at /home/phron/brownie/build/contractscompiler:
evm_version: null
solc:
version: 0.8.13
optimizer:
enabled: true
runs: 200brownie compile --allcd scripts && touch deploy.py# scripts/deploy.py
from brownie import Box, accounts
def main():
account = accounts.load("alice")
return Box.deploy({"from": account, "gas_limit": "200000"})brownie run scripts/deploy.py --network phron-mainbrownie run scripts/deploy.py --network phron-testnetBrownie v1.19.1 - Python development framework for Ethereum
BrownieProject is the active project.
Running 'scripts/deploy.py: :mainEnter password for "alice":Transaction sent: Oxf091d0415d1a4614ccd76a5f5f985fdf6abbd68d7481647fb3144dfecffb333Gas price: 1.0 gwei Gas limit: 200000 Nonce: 15298Box.constructor confirmed Block: 1901367 Gas used: 103095 (51.55%)Box deployed at: OxccA4aDdD51Af1E76beZaBE5FD04A82EB6063C65brownie console --network phron-mainbox = Box[0]box.store(5, {'from': accounts.load('alice'), 'gas_limit': '50000'})box.retrieve({'from': accounts.load('alice')})brownie console --network phron-testnetBrownie v1.19.1 - Python development framework for Ethereum
BrownieProject is the active project.Brownie environment is ready.box = Box[0]
box. store(5, {'from': accounts. load('alice"), 'gas-limit: "50000").Enter password for "alice":Transaction sent: 0x2418038735934917861dfa77068fe6dadd0b3c7e4362d6f73c1712aaf6a89aGas price: 1.0 gwei Box.store confirmed Gas limit: 50000 Nonce: 15299Box.store confirmed Block: 1901372 Gas used: 44593 (89.19%)
Transaction '0×24180387359349178b1dfa770e68fe6dadd0b3c7e4362d6f73c1712aaf6a89a'box.retrieve ({'from': accounts. load('alice")})Enter password for "alice":5cd scripts && touch store-and-retrieve.py# scripts/store-and-retrieve.py
from brownie import Box, accounts
def main():
account = accounts.load("alice")
box = Box[0]
store = box.store(5, {"from": accounts.load("alice"), "gas_limit": "50000"})
retrieve = box.retrieve({"from": accounts.load("alice")})
print("Transaction hash for updating the stored value: " + store)
print("Stored value: " + retrieve)brownie run scripts/store-and-retrieve.py --network phron-mainmkdir ape && cd apepython3 -m pip install --user pipx
python3 -m pipx ensurepathpipx install eth-apeape initape initPlease enter project name: ape-demo
SUCCESS: ape-demo is written in ape-config.yaml
lsape-config.yaml contracts scripts testsape accounts import INSERT_ACCOUNT_NAMEape accounts import aliceEnter Private Key:Create Passphrase to encrypt account:Repeat for confirmation:SUCCESS: A new account '0x097D9Eea23DE2D3081169e0225173d0C55768338' has been added with the id 'alice'touch contracts/Box.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
contract Box {
uint256 private value;
event ValueChanged(uint256 newValue);
function store(uint256 newValue) public {
value = newValue;
emit ValueChanged(newValue);
}
function retrieve() public view returns (uint256) {
return value;
}
}ape plugins install soliditysolidity:
version: INSERT_VERSION
evm_version: INSERT_VERSIONape compileape compileINFO: Compiling 'Box.sol'.INFO: Compiling using Solidity compiler '0.8.23+commit.f704f362'.touch tests/test_box.pytouch tests/conftest.pyimport pytest
@pytest.fixture
def owner(accounts):
return accounts[0]
@pytest.fixture
def box(owner, project):
return owner.deploy(project.Box)def test_store_value(box, owner):
new_value = 5
box.store(new_value, sender=owner)
assert box.retrieve() == new_valueape testape test===================== test session starts ======================platform darwin -- Python 3.10.4, pytest-7.2.1, pluggy-1.4.0rootdir: /Users/phron/apeplugins: eth-ape-0.7.7, web3-6.15.1collected 1 item
tests/test_box.py . [100%]
====================== 1 passed in 0.70s =======================touch scripts/deploy.pyfrom ape import project, accounts
def main():
# Load your account by its name
account = accounts.load("alice")
# Deploy the contract using your account
return account.deploy(project.Box)ape run deploy --network INSERT_RPC_API_ENDPOINTNote
For the ape run deploy command to work as intended, the deployment script must be named deploy.py and stored in the scripts directory, and the script must define a main() method.ape run deploy --network https://testnet.phron.ai : Connecting to a 'phron' node.
DynamicFeeTransaction: chainId: 7744 from: 0x097D9Eea23DE2D3081169e0225173d0C55768338 gas: 123964 nonce: 372 value: 0 data: 0x307836...303333 type: 2 maxFeePerGas: 125000000 maxPriorityFeePerGas: 0 accessList: []
Sign: [y/N]: yEnter passphrase to unlock 'alice' []:Leave 'alice' unlocked? [y/N]: nINFO: Confirmed 0x365cd903e7fac5ad1f815d7a6f211b1aa32bd7d78630c2e81d67514cfb9e55bb (total fees paid = 15326250000000)SUCCESS: Contract 'Box' deployed to: 0x68039277300E8B104dDf848029dCA04C2EFe8610ape console --network INSERT_RPC_API_ENDPOINTbox = Contract("INSERT_CONTRACT_ADDRESS")ape console --network https://testnet.phron.ai : Connecting to a 'phron' node.
alice = accounts.load("alice") box = Contract("0x68039277300E8B104dDf848029dCA04C2EFe8610")box.store(4, sender=alice)box.store(4, sender=alice)DynamicFeeTransaction: chainId: 7744 to: 0x68039277300E8B104dDf848029dCA04C2EFe8610 from: 0x097D9Eea23DE2D3081169e0225173d0C55768338 gas: 45668 nonce: 373 value: 0 data: 0x307836...303034 type: 2 maxFeePerGas: 125000000 maxPriorityFeePerGas: 0 accessList: []
Sign: [y/N]: yEnter passphrase to unlock 'alice' []:Leave 'alice' unlocked? [y/N]: nINFO: Confirmed 0xd2e8305f22f33c1ab8ccaaef94252a93ff0f69c9bf98503fc2744bf257f1ef67 (total fees paid = 5573750000000) Receipt 0xd2e8305f22f33c1ab8ccaaef94252a93ff0f69c9bf98503fc2744bf257f1ef67box.retrieve()touch scripts/store-and-retrieve.pyfrom ape import Contract, accounts
def main():
account = accounts.load("alice")
box = Contract("INSERT_CONTRACT_ADDRESS")
store = box.store(4, sender=account)
print("Transaction hash for updating the stored value:", store.txn_hash)
retrieve = box.retrieve()
print("Stored value:", retrieve)ape run store-and-retrieve --network INSERT_RPC_API_ENDPOINTape run store-and-retrieve --network https://testnet.phron.ai : chainId: 7744 to: 0x68039277300E8B104dDf848029dCA04C2EFe8610 from: 0x097D9Eea23DE2D3081169e0225173d0C55768338 gas: 25974 nonce: 374 value: 0 data: 0x307836...303035 type: 2 maxFeePerGas: 125000000 maxPriorityFeePerGas: 0 accessList: []
Sign: [y/N]: yEnter passphrase to unlock 'alice' []:Leave 'alice' unlocked? [y/N]: nINFO: Confirmed 0x6d74e48c23fd48438bf48baad34e235693c737bd880ef0077c0fb996f3896f5f (total fees paid = 3086250000000)Transaction hash for updating the stored value: 0x6d74e48c23fd48438bf48baad34e235693c737bd880ef0077c0fb996f3896f5fStored value: 5





Waffle is a library for compiling and testing smart contracts, and Mars is a deployment manager. Together, Waffle and Mars can be used to write, compile, test, and deploy Ethereum smart contracts. Since Phron is Ethereum compatible, Waffle and Mars can be used to deploy smart contracts to a Phron development node or the Phron TestNet.
Waffle uses minimal dependencies, has syntax that is easy to learn and extend, and provides fast execution times when compiling and testing smart contracts. Furthermore, it is compatible and uses to make tests easy to read and write.
Mars provides a simple, TypeScript compatible framework for creating advanced deployment scripts and staying in sync with state changes. Mars focuses on infrastructure-as-code, allowing developers to specify how their smart contracts should be deployed and then using those specifications to automatically handle state changes and deployments.
In this guide, you'll be creating a TypeScript project to write, compile, and test a smart contract using Waffle, then deploy it on to the Phron TestNet using Mars.
You will need to have the following:
MetaMask installed and connected to Phron
An account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
Once you've created an account you'll need to export the private key to be used in this guide.
To get started, you'll create a TypeScript project and install and configure a few dependencies.
Create the project directory and change to it:
Initialize the project. Which will create a package.json in the directory:
Install the following dependencies:
- for writing, compiling, and testing smart contracts
Now, you should have a basic TypeScript project with the necessary dependencies to get started building with Waffle and Mars.
For this guide, you will create an ERC-20 contract that mints a specified amount of tokens to the contract creator. It's based on the OpenZeppelin ERC-20 template.
Create a directory to store your contracts and a file for the smart contract:
Add the following contract to MyToken.sol:
In this contract, you are creating an ERC-20 token called MyToken with the symbol MYTOK, that allows you, as the contract creator, to mint as many MYTOKs as desired.
Now that you have written a smart contract, the next step is to use Waffle to compile it. Before diving into compiling your contract, you will need to configure Waffle:
Go back to the root project directory and create a waffle.json file to configure Waffle:
Edit the waffle.json to specify compiler configurations, the directory containing your contracts, and more. For this example, we'll use solcjs and the Solidity version you used for the contract, which is 0.8.0:
Add a script to run Waffle in the package.json
That is all you need to do to configure Waffle, now you're all set to compile the MyToken contract using the build script:
After compiling your contracts, Waffle stores the JSON output in the build directory. Since the contract in this guide is based on OpenZeppelin's ERC-20 template, relevant ERC-20 JSON files will appear in the build directory too.
Before deploying your contract and sending it off into the wild, you should test it first. Waffle provides an advanced testing framework and has plenty of tools to help you with testing.
You'll be running tests against the Phron TestNet and will need the corresponding RPC URL to connect to it: https://testnet.phron.ai.
To configure your project for Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
Since you will be running tests against the TestNet, it might take a couple minutes to run all of the tests. If you want a more efficient testing experience, you can spin up a Phron development node using . Running a local Phron development node with the instant seal feature is similar to the quick and iterative experience you would get with .
Create a directory to contain your tests and a file to test your MyToken contract:
Open the MyToken.test.ts file and setup your test file to use Waffle's Solidity plugin and use Ethers custom JSON-RPC provider to connect to Phron:
Before each test is run, you'll want to create wallets and connect them to the provider, use the wallets to deploy an instance of the MyToken contract, and then call the initialize function once with an initial supply of 10 tokens:
Congratulations, you should now have two passing tests! Altogether, your test file should look like this:
If you want to write more tests on your own, you could consider testing transfers from accounts without any funds or transfers from accounts without enough funds.
After you compile your contracts and before deployment, you will have to generate contract artifacts for Mars. Mars uses the contract artifacts for typechecks in deployments. Then you'll need to create a deployment script and deploy the MyToken smart contract.
Remember, you will be deploying to Phron and will need to use the TestNet RPC URL:
To configure your project for Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
The deployment will be broken up into three sections: generate artifacts, create a deployment script, and deploy with Mars.
Artifacts need to be generated for Mars so that typechecks are enabled within deployment scripts.
Update existing script to run Waffle in the package.json to include Mars:
Generate the artifacts and create the artifacts.ts file needed for deployments:
If you open the build directory, you should now see an artifacts.ts file containing the artifact data needed for deployments. To continue on with the deployment process, you'll need to write a deployment script. The deployment script will be used to tell Mars which contract to deploy, to what network, and which account is to be used to trigger the deployment.
Now you need to configure the deployment for the MyToken contract to the Phron TestNet.
In this step, you'll create the deployment script which will define how the contract should be deployed. Mars offers a deploy function that you can pass options to such as the private key of the account to deploy the contract, the network to deploy to, and more. Inside of the deploy function is where the contracts to be deployed are defined. Mars has a contract function that accepts the name, artifact, and constructorArgs. This function will be used to deploy the MyToken contract with an initial supply of 10 MYTOKs.
Create a src directory to contain your deployment scripts and create the script to deploy the MyToken contract:
In deploy.ts, use Mars' deploy function to create a script to deploy to Phron using your account's private key:
Set up the deploy
So far, you should have created a deployment script in deploy.ts that will deploy the MyToken contract to Phron, and added the ability to easily call the script and deploy the contract.
You've configured the deployment, now it's time to actually deploy to Phron.
Deploy the contract using the script you just created:
In your Terminal, Mars will prompt you to press ENTER to send your transaction
If successful, you should see details about your transaction including it's hash, the block it was included in, and it's address.
Congratulations! You've deployed a contract to Phron using Waffle and Mars!
If you want to see a completed example of a Waffle and Mars project on Phron, check out the phron-waffle-mars-example created by the team behind Waffle and Mars, EthWorks.
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
is an Ethereum development environment written in Rust that helps developers manage dependencies, compile projects, run tests, deploy contracts, and interact with blockchains from the command line. Foundry can directly interact with Phron's Ethereum API so it can be used to deploy smart contracts into Phron.
Mars - for deploying smart contracts to Phron
Ethers - for interacting with Phron's Ethereum API
OpenZeppelin Contracts - the contract you'll be creating will use OpenZeppelin's ERC-20 base implementation
TypeScript - the project will be a TypeScript project
TS Node - for executing the deployment script you'll create later in this guide
Chai - an assertion library used alongside Waffle for writing tests
@types/chai - contains the type definitions for chai
Mocha - a testing framework for writing tests alongside Waffle
@types/mocha - contains the type definitions for mocha
Create a TypeScript configuration file:
Add a basic TypeScript configuration:
Now you can create your first test. The first test will check your initial balance to ensure you received the initial supply of 10 tokens. However, to follow good testing practices, write a failing test first:
Before you can run your first test, you'll need to go back to the root direction and add a .mocharc.json Mocha configuration file:
Now edit the .mocharc.json file to configure Mocha:
You'll also need to add a script in the package.json to run your tests:
You're all set to run the tests, simply use the test script you just created and run:
Please note that it could take a few minutes to process because the tests are running against Phron, but if all worked as expected, you should have one failing test.
Next, you can go back and edit the test to check for 10 tokens:
If you run the tests again, you should now see one passing test:
You've tested the ability to mint tokens, next you'll test the ability to transfer the minted tokens. If you want to write a failing test first again that is up to, however the final test should look like this:
MyTokenAdd a deploy script to the scripts object in the package.json:
Four tools make up Foundry:
This guide will cover how to use Foundry to compile, deploy, and debug Ethereum smart contracts on the Phron TestNet.
To get started, you will need the following:
Have an account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
Have Foundry installed
You will need to create a Foundry project if you don't already have one. You can create one by completing the following steps:
Install Foundry if you haven't already. If on Linux or MacOS, you can run these commands:
If on Windows, you'll have to install Rust and then build Foundry from source:
Create the project, which will create a folder with three folders within it, and open it:
With the default project created, you should see three folders.
lib - all of the project's dependencies in the form of git submodules
src - where to put your smart contracts (with functionality)
test - where to put the forge tests for your project, which are written in Solidity
In addition to these three folders, a git project will also be created along with a prewritten .gitignore file with relevant file types and folders ignored.
The src folder may already contain Counter.sol, a minimal Solidity contract. Feel free to delete it. To avoid errors, you should also delete the Counter.s.sol file in the scripts folder and the Counter.t.sol file in the test folder. In the following steps, you will be deploying an ERC-20 contract. In the contracts directory, you can create the MyToken.sol file:
Open the file and add the following contract to it:
Before you attempt to compile, install OpenZeppelin contracts as a dependency. You may have to commit previous changes to git beforehand. By default, Foundry uses git submodules instead of npm packages, so the traditional npm import path and command are not used. Instead, use the name of OpenZeppelin's GitHub repository:
Once all dependencies have been installed, you can compile the contract:
After compilation, two folders will be created: out and cache. The ABI and bytecode for your contracts will be contained within the out folder. These two folders are already ignored by the .gitignore included in the default Foundry project initialization.
There are two primary ways to deploy contracts using Foundry. The first is the straightforward command forge create. There's also the more flexible and powerful option of foundry scripting, which runs simulations before any deployments. In the following sections, forge create and foundry scripting will both be covered.
Deploying the contract with forge create takes a single command, but you must include an RPC endpoint, a funded private key, and constructor arguments. MyToken.sol asks for an initial supply of tokens in its constructor, so each of the following commands includes 100 as a constructor argument. You can deploy the MyToken.sol contract using the following command for the correct network:
After you've deployed the contract and a few seconds have passed, you should see the address in the terminal.
Congratulations! Your contract is live! Save the address, as you will use it to interact with this contract instance in the next step.
Solidity scripting is a more powerful and flexible way to deploy contracts than forge create. Writing a Solidity script is identical to writing a typical Solidity smart contract, though you won't ever deploy this contract.
You can tailor the behavior of forge script with various parameters. All components are optional except for local simulation, which is a required part of every run. The forge script command will attempt to execute all applicable steps in the following order:
Local simulation - simulate the transaction(s) in a local EVM
Onchain simulation - simulate the transaction(s) via the provided RPC URL
Broadcasting - when the --broadcast flag is provided, and simulations succeed, the transaction(s) are dispatched
Verification - API-based smart contract verification when the --verify flag and a valid API key are provided
Now, go ahead and write the script. In the script folder, create a file named MyToken.s.sol. Copy and paste the contents of the below file.
Notice that even though the above script is not being deployed, it still requires all the typical formatting for a Solidity contract, such as the pragma statement.
You can deploy the MyToken.sol contract with the below command. Remember that it will execute all relevant steps in order. For this example, Foundry will first attempt a local simulation and a simulation against the provided RPC before deploying the contract. Foundry won't proceed with the deployment if any of the simulations fail.
If your script's execution succeeds, your terminal should resemble the output below.
And that's it! For more information about Solidity scripting with Foundry, be sure to check out Foundry's documentation site.
Foundry includes cast, a CLI for performing Ethereum RPC calls.
Try to retrieve your token's name using Cast, where INSERT_YOUR_CONTRACT_ADDRESS is the address of the contract that you deployed in the previous section:
You should get this data in hexadecimal format:
This is far from readable, but you can use Cast to convert it into your desired format. In this case, the data is text, so you can convert it into ASCII characters to see "My Token":
You can also mutate data with cast as well. Try burning tokens by sending them to the zero address.
The transaction will be signed by your Phron account and be broadcast to the network. The output should look similar to:
Congratulations, you have successfully deployed and interacted with a contract using Foundry!
As previously mentioned, Anvil is a local TestNet node for development purposes that can fork preexisting networks. Forking Phron allows you to interact with live contracts deployed on the network.
There are some limitations to be aware of when forking with Anvil. Since Anvil is based on an EVM implementation, you cannot interact with any of the Phron precompiled contracts and their functions. Precompiles are a part of the Substrate implementation and therefore cannot be replicated in the simulated EVM environment. This prohibits you from interacting with cross-chain assets on Phron and Substrate-based functionality such as staking and governance.
To fork Phron, you will need to have your own endpoint and API key which you can get from one of the supported Endpoint Providers.
To fork Phron from the command line, you can run the following command from within your Foundry project directory:
Your forked instance will have 10 development accounts that are pre-funded with 10,000 test tokens. The forked instance is available at http://127.0.0.1:8545/. The output in your terminal should resemble the following:
To verify you have forked the network, you can query the latest block number:
If you convert the result from hex to decimal, you should get the latest block number from the time you forked the network. You can cross reference the block number using a block explorer.
From here you can deploy new contracts to your forked instance of Phron or interact with contracts already deployed. Building off of the previous example in this guide, you can make a call using Cast to check the balance of the minted MYTOK tokens in the account you deployed the contract with:
Chisel is a Solidity REPL or shell. It allows a developer to write Solidity directly in the console for testing small snippets of code, letting developers skip the project setup and contract deployment steps for what should be a quick process.
Since Chisel is mainly useful for quick testing, it can be used outside of a Foundry project. But, if executed within a Foundry project, it will keep the configurations within foundry.toml when running.
For this example, you will be testing out some of the features of abi within Solidity because it is complex enough to demonstrate how Chisel could be useful. To get started using Chisel, run the following in the command line to start the shell:
In the shell, you can write Solidity code as if it were running within a function:
Let's say you were interested in how abi encoded data because you're looking into how to most efficiently store data on the blockchain and thus save gas. To view how the myData is stored in memory, you can use the following command while in the Chisel shell:
memdump will dump all of the data in your current session. You'll likely see something like this below. If you aren't good at reading hexadecimal or if you don't know how ABI encoding works, then you might not be able to find where the myData variable has been stored.
Fortunately, Chisel lets you easily figure out where this information is stored. Using the !rawstack command, you can find the location in the stack where the value of a variable:
In this situation, since bytes is over 32 bytes in length, the memory pointer is displayed instead. But that's exactly what's needed since you already know the entirety of the stack from the !memdump command.
The !rawstack command shows that the myData variable is stored at 0x80, so when comparing this with the memory dump retrieved from the !memdump command, it looks like myData is stored like this:
At first glance, this makes sense, since 0xa0 has a value of 0x64 which is equal to 100, and 0xc0 has a value of 0x01 which is equal to true. If you want to learn more about how ABI-encoding works, the Solidity documentation for ABI is helpful. In this case, there are a lot of zeros in this method of data packing, so as a smart contract developer you might instead try to use structs or pack the data together more efficiently with bitwise code.
Since you're done with this code, you can clear the state of Chisel so that it doesn't mess with any future logic that you want to try out (while running the same instance of Chisel):
There's an even easier way to test with Chisel. When writing code that ends with a semicolon (;), Chisel will run it as a statement, storing its value in Chisel's runtime state. But if you only needed to see how the ABI-encoded data was represented, then you could get away with running the code as an expression. To try this out with the same abi example, write the following in the Chisel shell:
You should see something like the following:
While it doesn't display the data in the same way, you still get the contents of the data, and it also further breaks down how the information is coded, such as letting you know that the 0xa0 value defines the length of the data.
By default, when you leave the Chisel shell, none of the data is persisted. But you can instruct chisel to do so. For example, you can take the following steps to store a variable:
Store a uint256 in Chisel
Store the session with !save. For this example, you can use the number 1 as a save ID
Quit the session
Then to view and interact with your stored Chisel states, you can take the following steps:
View a list of saved Chisel states
Load your stored states
View the uint256 saved in Chisel from the previous set of steps
You can even fork networks while using Chisel:
Then, for example, you can query the balance of one of Phron's collators:
If you want to learn more about Chisel, download Foundry and refer to its official reference page.
Often, there will be the case where a project that you wish to integrate with has all of its setup within Hardhat, making it an arduous task to convert the entirety of the project into Foundry. This additional work is avoidable by creating a hybrid project that uses both Hardhat and Foundry features together. This is possible with Hardhat's hardhat-foundry plugin.
To convert your preexisting Foundry project to a hybrid project, you will essentially have to install a Hardhat project into the same folder:
For more information, please refer to our documentation on Creating a Hardhat Project.
After initializing the new Hardhat project, a few new folders and files should appear: contracts, hardhat.config.js, scripts, and test/Lock.js. You'll need to make a few modifications to create a hybrid project:
Edit the hardhat.config.js file within your repository. Open it up, and at the top, add the following:
After adding the hardhat-foundry plugin, the typical contracts folders for Hardhat will not work because now Hardhat expects all smart contracts to be stored within Foundry's src folder
Move all smart contracts within the contracts folder into the src folder, and then delete the contracts folder
Edit the foundry.toml file to ensure that dependencies installed via Git submodules and npm can be compiled by the Forge tool. Edit the profile.default to ensure that the libs entry has both lib and node_modules:
Now both forge build and npx hardhat compile should work regardless of the dependencies.
Both forge test and npx hardhat test should now be able to access all smart contracts and dependencies. forge test will only test the Solidity tests, whereas npx hardhat test will only test the JavaScript tests. If you would like to use them in conjunction, then you can create a new script within your package.json file:
You can run this command with:
Finally, while not necessary, it could be worthwhile to move all JavaScript scripts from the scripts folder into Foundry's script folder and delete the scripts folder so that you don't have two folders that serve the same purpose.
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Web3.py is a set of libraries that allow developers to interact with Ethereum nodes using HTTP, IPC, or WebSocket protocols with Python. Phron has an Ethereum-like API available that is fully compatible with Ethereum-style JSON-RPC invocations. Therefore, developers can leverage this compatibility and use the Web3.py library to interact with a Phron python3 as if they were doing so on Ethereum.
In this guide, you'll learn how to use the Web3.py library to send a transaction and deploy a contract on Phron.
For the examples in this guide, you will need to have the following:
An account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
NoteThe examples in this guide assume you have a MacOS or Ubuntu 22.04-based environment and will need to be adapted accordingly for Windows.
To get started, you can create a directory to store all of the files you'll be creating throughout this guide:
For this guide, you'll need to install the Web3.py library and the Solidity compiler. To install both packages, you can run the following command:
Throughout this guide, you'll be creating a bunch of scripts that provide different functionalities, such as sending a transaction, deploying a contract, and interacting with a deployed contract. In most of these scripts, you'll need to create a to interact with the network.
To configure your project for Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
To create a provider, you can take the following steps:
Import the web3 library
Create the web3 provider using the Web3(Web3.HTTPProvider()) method and providing the endpoint URL
Save this code snippet, as you'll need it for the scripts that are used in the following sections.
During this section, you'll be creating a couple of scripts. The first one will be to check the balances of your accounts before trying to send a transaction. The second script will actually send the transaction.
You can also use the balance script to check the account balances after the transaction has been sent.
You'll only need one file to check the balances of both addresses before and after the transaction is sent. To get started, you can create a balances.py file by running:
Next, you will create the script for this file and complete the following steps:
Set up the Web3 provider
Define the address_from and address_to variables
Get the balance for the accounts using the web3.eth.get_balance function and format the results using the web3.from_wei
To run the script and fetch the account balances, you can run the following command:
If successful, the balances for the origin and receiving address will be displayed in your terminal in ETH.
You'll only need one file for executing a transaction between accounts. For this example, you'll be transferring 1 DEV token from an origin address (from which you hold the private key) to another address. To get started, you can create a transaction.py file by running:
Next, you will create the script for this file and complete the following steps:
Add imports, including Web3.py and the rpc_gas_price_strategy, which will be used in the following steps to get the gas price used for the transaction
Set up the Web3 provider
Define the account_from, including the private_key, and the address_to variables. The private key is required to sign the transaction. Note: This is for example purposes only. Never store your private keys in a Python file
To run the script, you can run the following command in your terminal:
If the transaction was succesful, in your terminal you'll see the transaction hash has been printed out.
You can also use the balances.py script to check that the balances for the origin and receiving accounts have changed. The entire workflow would look like this:
The contract you'll be compiling and deploying in the next couple of sections is a simple incrementer contract, arbitrarily named Incrementer.sol. You can get started by creating a file for the contract:
Next, you can add the Solidity code to the file:
The constructor function, which runs when the contract is deployed, sets the initial value of the number variable stored on-chain (the default is 0). The increment function adds the _value provided to the current number, but a transaction needs to be sent, which modifies the stored data. Lastly, the reset function resets the stored value to zero.
NoteThis contract is a simple example for illustration purposes only and does not handle values wrapping around.
In this section, you'll create a script that uses the Solidity compiler to output the bytecode and interface (ABI) for the Incrementer.sol contract. To get started, you can create a compile.py file by running:
Next, you will create the script for this file and complete the following steps:
Import the solcx package
Optional - If you haven't already installed the Solidity compiler, you can do so with by using the solcx.install_solc function
Compile the Incrementer.sol function using the solcx.compile_files function
NoteIf you see an error stating that
Solc is not installed, uncomment step 2 described in the code snippet.
With the script for compiling the Incrementer.sol contract in place, you can then use the results to send a signed transaction that deploys it. To do so, you can create a file for the deployment script called deploy.py:
Next, you will create the script for this file and complete the following steps:
Add imports, including Web3.py and the ABI and bytecode of the Incrementer.sol contract
Set up the Web3 provider
Define the account_from, including the private_key. The private key is required to sign the transaction. Note: This is for example purposes only. Never store your private keys in a Python file
To run the script, you can enter the following command into your terminal:
If successful, the contract's address will be displayed in the terminal.
Call methods are the type of interaction that don't modify the contract's storage (change variables), meaning no transaction needs to be sent. They simply read various storage variables of the deployed contract.
To get started, you can create a file and name it get.py:
Then you can take the following steps to create the script:
Add imports, including Web3.py and the ABI of the Incrementer.sol contract
Set up the Web3 provider
Define the contract_address of the deployed contract
Create a contract instance using the
To run the script, you can enter the following command in your terminal:
If successful, the value will be displayed in the terminal.
Send methods are the type of interaction that modify the contract's storage (change variables), meaning a transaction needs to be signed and sent. In this section, you'll create two scripts: one to increment and one to reset the incrementer. To get started, you can create a file for each script and name them increment.py and reset.py:
Open the increment.py file and take the following steps to create the script:
Add imports, including Web3.py and the ABI of the Incrementer.sol contract
Set up the Web3 provider
Define the account_from, including the private_key, the contract_address of the deployed contract, and the value to increment by. The private key is required to sign the transaction.
To run the script, you can enter the following command in your terminal:
If successful, the transaction hash will be displayed in the terminal. You can use the get.py script alongside the increment.py script to make sure that value is changing as expected:
Next you can open the reset.py file and take the following steps to create the script:
Add imports, including Web3.py and the ABI of the Incrementer.sol contract
Set up the Web3 provider
Define the account_from, including the private_key, and the contract_address of the deployed contract. The private key is required to sign the transaction. Note: This is for example purposes only. Never store your private keys in a Python file
To run the script, you can enter the following command in your terminal:
If successful, the transaction hash will be displayed in the terminal. You can use the get.py script alongside the reset.py script to make sure that value is changing as expected:
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
thirdweb is a complete Web3 development framework that provides everything you need to develop smart contracts, build dApps, and more.
With thirdweb, you can access tools to help you through every phase of the dApp development cycle. You can create your own custom smart contracts or use any of thirdweb's prebuilt contracts to get started quickly. From there, you can use thirdweb's CLI to deploy your smart contracts. Then you can interact with your smart contracts by creating a Web3 application using the language of your choice, including but not limited to React and TypeScript.
This guide will show you some of the thirdweb features you can use to develop smart contracts and dApps on Phron. To check out all of the features thirdweb has to offer, please refer to the . For a comprehensive step-by-step tutorial for building a dApp on Phron with thirdweb, be sure to check out Phron's thirdweb tutorial in the tutorials section.
To create a new smart contract using the , follow these steps:
In your CLI, run the following command:
Input your preferences for the command line prompts:
Give your project a name
Choose your preferred framework: Hardhat or Foundry
Alternatively, you can deploy a prebuilt contract for NFTs, tokens, or marketplace directly from the thirdweb Explore page:
Go to the
Choose the type of contract you want to deploy from the available options: NFTs, tokens, marketplace, and more
Follow the on-screen prompts to configure and deploy your contract
For more information on different contracts available on Explore, check out .
is thirdweb's tool that allows you to easily deploy a smart contract to any EVM compatible network without configuring RPC URLs, exposing your private keys, writing scripts, and other additional setup such as verifying your contract.
To deploy your smart contract using deploy, navigate to the contracts directory of your project and execute the following command:
Executing this command will trigger the following actions:
Compiling all the contracts in the current directory
Providing the option to select which contract(s) you wish to deploy
For additional information on Deploy, please reference .
thirdweb offers SDKs for a range of programming languages, such as React, React Native, TypeScript, and Unity. You'll start off by creating an application and then you can choose which SDK to use:
In your CLI run the following command:
Input your preferences for the command line prompts:
Give your project a name
Choose your preferred framework: Next.js, Vite, or React Native. For this example, select Vite
Before you launch your dApp (locally or publicly deployed), you must have a thirdweb Client ID associated with your project. A thirdweb Client ID is synonymous with an API key. You can create a free API key by .
Press Create API Key then take the following steps:
Give your API key a name
Enter the allowed domains that the API key should accept requests from. It's recommended that you allow only necessary domains, but for development purposes, you can select Allow all domains
Press Next and confirm the prompt on the next page
NoteThe respective name for your Client ID variable will vary with the framework you've chosen, e.g., Vite will be
VITE_TEMPLATE_CLIENT_ID, Next.js will beNEXT_PUBLIC_TEMPLATE_CLIENT_ID, and React Native will beEXPO_PUBLIC_THIRDWEB_CLIENT_ID.
Finally, specify your Client ID (API Key) in your .env file. Your .env file must be located at the root directory of the project (e.g., not the src folder).
If you generated your thirdweb app with Vite, you'll have a client.ts file that looks like the below. As long you've created a .env file with your thirdweb API Key (Client ID) defined in VITE_TEMPLATE_CLIENT_ID, you can leave the client.ts as is and proceed to the next section.
client.ts
NoteIf you don't create a Client ID and specify is correctly in your
.envfile, you'll get a blank screen when trying to build the web app. There is no error message shown without digging into the console, so ensure you've set your Client ID correctly first and foremost.
To run your dApp locally for testing and debugging purposes, use the command:
The app will compile and specify the localhost and port number for you to visit in your browser.
thirdweb offers a small number of chains from @thirdweb/chains and does not include Phron networks in that list, so you'll need to specify the network details including chain ID and RPC URL. You can create a custom chain with as follows:
chains.ts
The following sections will provide an overview of fundamental methods of the thirdweb SDK and how to interact with them. Each code snippet will showcase the relevant import statements and demonstrate using the method in a typical scenario. This guide is intended to be a quick reference guide to the most common thirdweb methods that dApp developers will use. However, it does not include information on each and every thirdweb offering. For details on the entirety of thirdweb's offerings, be sure to visit the .
For a comprehensive, step-by-step guide to building a dApp with thirdweb be sure to check out Phron's thirdweb tutorial in the tutorials section. The following sections will cover everything from connecting wallets, to preparing transactions, and more.
thirdweb distinguishes between accounts and wallets in the SDK. In the eyes of the thirdweb SDK, an account always has a single blockchain address and can sign messages, transactions, and typed data, but it cannot be "connected" or "disconnected." In contrast, a wallet contains one or more accounts, can be connected or disconnected, and delegates the signing tasks to its accounts.
The below code snippet demonstrates how to initialize and connect a MetaMask wallet using the thirdweb SDK, then sign and send a transaction, retrieving the transaction hash. This process is applicable to any of the 300+ wallet connectors supported by the SDK.
To connect to your contract, use the SDK’s method. As an example, you could fetch data from an incrementer contract on Phron.
To call a contract in the latest version of the SDK, you can use .
Returning to our incrementer contract, preparing a call to increment the contract looks like the following:
You can also prepare a transaction directly with encoded data. To do so, you'll use thirdweb's and specify the to, value, chain, and client values directly.
Use the to call any read functions on your contract by passing in the Solidity method signature and any parameters.
For a function that takes no parameters, such as the number function that returns the current number stored in the incrementer contract, you simply need to provide the function name as follows:
Did you know? With the , you can easily and generate functions for all of the possible calls to a contract. To do so, run the following command in the command line:
Both the chain ID and the contract address are required. As an example, if you wanted to generate the functions for the incrementer contract on Phron , you would use the following command:
The file generated with all of the corresponding methods will be placed in a directory labelled thirdweb/CHAIN_ID/CONTRACT_ADDRESS. In the example shown above, the output file is located at thirdweb/1287/0xa72f549a1a12b9b49f30a7f3aeb1f4e96389c5d8.ts. For more information, see the .
Every transaction sent using the SDK must first be prepared. This preparation process is synchronous and lightweight, requiring no network requests. Additionally, it provides type-safe definitions for your contract calls.
You can prepare a transaction as follows:
Prepare a transaction
After the transaction is prepared, you can send it as follows:
Send a transaction
You can optionally use sendAndConfirmTransaction to wait for the transaction to be mined. This is relevant if you want to block the user from continuing until the transaction is confirmed.
Send and Confirm a Transaction
thirdweb provides a number of helpful utility methods surrounding preparing and sending transactions.
You can estimate the gas used by a transaction as follows:
Estimating gas
You can estimate the gas cost in Ether and Wei as follows:
Estimating gas cost
thirdweb also provides a handy way to simulate transactions and verify their integrity before actually submitting it to the blockchain. You can simulate a transaction as follows:
Simulate a transaction
You can encode transaction data to act on later by taking the following steps:
Encode transaction data
Perhaps the first and most important interaction users will have with your dApp is connecting their wallet. thirdweb provides an easy and highly customizable way for you to enable this. thirdweb provides a highly customizable to tailor it to your desired wallets. The ConnectButton accepts an optional wallets parameter with an array of wallets. You can add or remove wallets from the wallets array to change the options available to users. thirdweb also offers a to customize and view changes for the ConnectButton in real-time, given the button's high degree of flexibility.
ConnectButton
As a reminder, you can build your example project locally by running:
To host your static web application on decentralized storage, run:
By running this command, your application is built for production and stored using , thirdweb's decentralized file management solution. The built application is uploaded to IPFS, a decentralized storage network, and a unique URL is generated for your application. This URL serves as a permanent hosting location for your application on the web.
If you have any further questions or encounter any issues during the process, please reach out to thirdweb support at .
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
touch tsconfig.json{
"compilerOptions": {
"strict": true,
"target": "ES2019",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"module": "CommonJS",
"composite": true,
"sourceMap": true,
"declaration": true,
"noEmit": true
}
}it('Mints the correct initial balance', async () => {
expect(await token.balanceOf(wallet.address)).to.equal(1); // This should fail
});cd .. && touch .mocharc.json{
"require": "ts-node/register/transpile-only",
"timeout": 600000,
"extension": "test.ts"
}"scripts": {
"build": "waffle",
"test": "mocha"
},npm run testit('Mints the correct initial balance', async () => {
expect(await token.balanceOf(wallet.address)).to.equal(10); // This should pass
});npm run testit('Should transfer the correct amount of tokens to the destination account', async () => {
// Send the destination wallet 7 tokens
await (await token.transfer(walletTo.address, 7)).wait();
// Expect the destination wallet to have received the 7 tokens
expect(await token.balanceOf(walletTo.address)).to.equal(7);
});"scripts": {
"build": "waffle && mars",
"test": "mocha",
"deploy": "ts-node src/deploy.ts"
}mkdir waffle-mars && cd waffle-marsnpm init -ynpm install ethereum-waffle ethereum-mars ethers \
@openzeppelin/contracts typescript ts-node chai \
@types/chai mocha @types/mochamkdir contracts && cd contracts && touch MyToken.solpragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MYTOK") {}
function initialize(uint initialSupply) public {
_mint(msg.sender, initialSupply);
}
}cd .. && touch waffle.json{
"compilerType": "solcjs",
"compilerVersion": "0.8.0",
"compilerOptions": {
"optimizer": {
"enabled": true,
"runs": 20000
}
},
"sourceDirectory": "./contracts",
"outputDirectory": "./build",
"typechainEnabled": true
}npm run buildmkdir test && cd test && touch MyToken.test.tsimport { use, expect } from 'chai';
import { Provider } from '@ethersproject/providers';
import { solidity } from 'ethereum-waffle';
import { ethers, Wallet } from 'ethers';
import { MyToken, MyTokenFactory } from '../build/types';
// Tell Chai to use Waffle's Solidity plugin
use(solidity);
describe ('MyToken', () => {
// Use custom provider to connect to Phron
let provider: Provider = new ethers.providers.JsonRpcProvider(
'https://testnet.phron.ai'
);
let wallet: Wallet;
let walletTo: Wallet;
let token: MyToken;
beforeEach(async () => {
// Logic for setting up the wallet and deploying MyToken will go here
});
// Tests will go here
})import { use, expect } from 'chai';
import { Provider } from '@ethersproject/providers';
import { solidity } from 'ethereum-waffle';
import { ethers, Wallet } from 'ethers';
import { MyToken, MyTokenFactory } from '../build/types';
use(solidity);
describe('MyToken', () => {
let provider: Provider = new ethers.providers.JsonRpcProvider(
'https://testnet.phron.ai'
);
let wallet: Wallet;
let walletTo: Wallet;
let token: MyToken;
beforeEach(async () => {
// For demo purposes only. Never store your private key in a JavaScript/TypeScript file
const privateKey = 'INSERT_PRIVATE_KEY';
wallet = new Wallet(privateKey).connect(provider);
walletTo = Wallet.createRandom().connect(provider);
token = await new MyTokenFactory(wallet).deploy();
let contractTransaction = await token.initialize(10);
await contractTransaction.wait();
});
it('Mints the correct initial balance', async () => {
expect(await token.balanceOf(wallet.address)).to.equal(10);
});
it('Should transfer the correct amount of tokens to the destination account', async () => {
await (await token.transfer(walletTo.address, 7)).wait();
expect(await token.balanceOf(walletTo.address)).to.equal(7);
});
});https://testnet.phron.ai"scripts": {
"build": "waffle && mars",
"test": "mocha"
},npm run buildmkdir src && cd src && touch deploy.tsimport { deploy } from 'ethereum-mars';
// For demo purposes only. Never store your private key in a JavaScript/TypeScript file
const privateKey = 'INSERT_PRIVATE_KEY';
deploy(
{ network: 'https://testnet.phron.ai', privateKey },
(deployer) => {
// Deployment logic will go here
}
);npm run deploy"scripts": {
"build": "waffle"
}, beforeEach(async () => {
// This is for demo purposes only. Never store your private key in a JavaScript/TypeScript file
const privateKey = 'INSERT_PRIVATE_KEY'
// Create a wallet instance using your private key & connect it to the provider
wallet = new Wallet(privateKey).connect(provider);
// Create a random account to transfer tokens to & connect it to the provider
walletTo = Wallet.createRandom().connect(provider);
// Use your wallet to deploy the MyToken contract
token = await new MyTokenFactory(wallet).deploy();
// Mint 10 tokens to the contract owner, which is you
let contractTransaction = await token.initialize(10);
// Wait until the transaction is confirmed before running tests
await contractTransaction.wait();
});import { deploy, contract } from 'ethereum-mars';
import { MyToken } from '../build/artifacts';
// For demo purposes only. Never store your private key in a JavaScript/TypeScript file
const privateKey = 'INSERT_PRIVATE_KEY';
deploy({ network: 'https://testnet.phron.ai', privateKey }, () => {
contract('myToken', MyToken);
});curl -L https://foundry.paradigm.xyz | bash
foundryupcurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs/ | sh
cargo install --git https://github.com/foundry-rs/foundry foundry-cli anvil --bins --lockedforge init foundry && cd foundryuint256 myNumber = 101;!save 1!quitchisel listchisel load 1!rawstack myNumberrequire("@nomicfoundation/hardhat-foundry");cd src
touch MyToken.solpragma solidity ^0.8.0;
// Import OpenZeppelin Contract
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// This ERC-20 contract mints the specified amount of tokens to the contract creator
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MYTOK") {
_mint(msg.sender, initialSupply);
}
}forge install OpenZeppelin/openzeppelin-contractsforge buildforge build[⠒] Compiling...[⠰] Compiling 30 files with 0.8.23[⠔] Solc 0.8.23 finished in 2.29sCompiler run successful!forge create --rpc-url INSERT_RPC_API_ENDPOINT \
--constructor-args 100 \
--private-key INSERT_YOUR_PRIVATE_KEY \
src/MyToken.sol:MyTokenforge create --rpc-url https://testnet.phron.ai \ --constructor-args 100 \ --private-key INSERT_PRIVATE_KEY \ src/MyToken.sol:MyToken
[⠒] Compiling...No files changed, compilation skippedDeployer: 0x3B939FeaD1557C741Ff06492FD0127bd287A421eDeployed to: 0xc111402Aa1136ff6224106709ae51864512eC68fTransaction hash: 0xd77fc26aa296e81f35718b5878cda98e8371f6bf33b0f57e7d92997a36cf6465// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Script.sol";
import "../src/MyToken.sol";
contract MyScript is Script {
function run() external {
uint256 deployerPrivateKey = INSERT_PRIVATE_KEY;
vm.startBroadcast(deployerPrivateKey);
MyToken mytoken = new MyToken(1000000000);
vm.stopBroadcast();
}
}forge script script/MyToken.s.sol --rpc-url https://testnet.phron.ai --broadcastforge script script/MyToken.s.sol --rpc-url https://testnet.phron.ai --broadcast[⠒] Compiling...Script ran successfully.EIP-3855 is not supported in one or more of the RPCs used. Unsupported Chain IDs: 7744.Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.For more information, please see https://eips.ethereum.org/EIPS/eip-3855## Setting up 1 EVM.==========================
Chain 1287Estimated gas price: 3.25 gweiEstimated total gas used for script: 1346155Estimated amount required: 0.00437500375 ETH==========================
Finding wallets for all the necessary addresses...Sending transactions [0 - 0].⠁ [00:00:00] [#################################################] 1/1 txes (0.0s)Waiting for receipts. ⠉ [00:00:25] [#############################################] 1/1 receipts (0.0s)##### phron
✅ [Success]Hash: 0x95766ca2c8bc94171f9de783652d62468f004d686eb5ab82b3546774eee301bc Contract Address: 0x2A19aD12E9e8479207B78c39f5bCc848D386b9DABlock: 5881522Paid: 0.00309613125 ETH (990762 gas * 3.125 gwei)ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.Total Paid: 0.00309613125 ETH (990762 gas * avg 3.125 gwei)Transactions saved to: /Users/ubuntu-jammy/foundry/foundry/broadcast/MyToken.s.sol/1287/run-latest.jsonSensitive values saved to: /Users/ubuntu-jammy/foundry/foundry/cache/MyToken.s.sol/1287/run-latest.jsoncast call INSERT_YOUR_CONTRACT_ADDRESS "name()" --rpc-url INSERT_RPC_API_ENDPOINT0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000074d79546f6b656e00000000000000000000000000000000000000000000000000cast --to-ascii 0x000000000000000000000000000000000000000000000000000000000000002000 000000000000000000000000000000000000000000000000000000000000074d7954 6f6b656e00000000000000000000000000000000000000000000000000
MyTokencast --to-ascii 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000074d79546f6b656e00000000000000000000000000000000000000000000000000cast send --private-key INSERT_YOUR_PRIVATE_KEY \
--rpc-url INSERT_RPC_API_ENDPOINT \
--chain 7744 \
INSERT_YOUR_CONTRACT_ADDRESS \
"transfer(address,uint256)" 0x0000000000000000000000000000000000000001 1cast send --private-key INSERT_PRIVATE_KEY \ --rpc-url https://testnet.phron.ai \ --chain 7744 \ INSERT_CONTRACT_ADDRESS \ "transfer(address,uint256)" 0x0000000000000000000000000000000000000001 1
blockHash 0x6f99fac1bb49feccb7b0476e0ffcd3cef4c456aa9111e193ce11c7a1ab62314eblockNumber 5892860contractAddresscumulativeGasUsed 51332effectiveGasPrice 3125000000gasUsed 51332logs [{"address":"0xc111402aa1136ff6224106709ae51864512ec68f","topics":["0xddf252ad1be2c89b69 c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000003b939fead155 7c741ff06492fd0127bd287a421e", "0x0000000000000000000000000000000000000000000000000000000000000001"], "data":"0x0000000000000000000000000000000000000 000000000000000000000000001", "blockHash":"0x6f99fac1bb49feccb7b0476e0ffcd3cef4c4 56aa9111e193ce11c7a1ab62314e", "blockNumber":"0x59eafc", "transactionHash":"0xdd5f11be68d5 2967356ccf34b9a4b2632d0d5ac8932ff27e72c544320dec33e3", "transactionIndex":"0x0","logIndex":"0x0","transactionLogIndex":"0x0","removed":false}]logsBloom 0x000000000000000000000000000000000000000000000000000000000000000000000000000000004 00000000000000000000000000000000000000000040000000000000000000000000008000000000000 00000004000000000000000000000000000000000000000100000000000000000000000000000000001 00000010000000000000000000000000000000000000000000000000000000002000000040000000000 00000000000000000000000000000000000000000000000000000000002000000000000000000000000 00000000000000000000000000004000000000000000000000000000000000000000000000000000000 0001000000rootstatus 1transactionHash 0xdd5f11be68d52967356ccf34b9a4b2632d0d5ac8932ff27e72c544320dec33e3transactionIndex 0type 2anvil --fork-url INSERT_RPC_API_ENDPOINTanvil --fork-url https://testnet.phron.ai
Available Accounts==================(0) "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" (10000.000000000000000000 ETH)(1) "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" (10000.000000000000000000 ETH)(2) "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" (10000.000000000000000000 ETH)(3) "0x90F79bf6EB2c4f870365E785982E1f101E93b906" (10000.000000000000000000 ETH)(4) "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" (10000.000000000000000000 ETH)(5) "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" (10000.000000000000000000 ETH)(6) "0x976EA74026E726554dB657fA54763abd0C3a0aa9" (10000.000000000000000000 ETH)(7) "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955" (10000.000000000000000000 ETH)(8) "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" (10000.000000000000000000 ETH)(9) "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" (10000.000000000000000000 ETH)
Private Keys==================(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d(2) 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a(3) 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6(4) 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a(5) 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba(6) 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e(7) 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356(8) 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97(9) 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6
Wallet==================Mnemonic: test test test test test test test test test test test junkDerivation path: m/44'/60'/0'/0/
Fork==================Endpoint: https://testnet.phron.networkBlock number: 5892944Block hash: 0xc9579299f55d507c305d5357d4c1b9d9c550788ddb471b0231d8d0146e7144b7Chain ID: 7744
Base Fee==================125000000
Gas Limit==================30000000
Genesis Timestamp==================1705278817
Listening on 127.0.0.1:8545curl --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545 cast call INSERT_CONTRACT_ADDRESS "balanceOf(address)(uint256)" INSERT_YOUR_ADDRESS --rpc-url http://localhost:8545chiselbytes memory myData = abi.encode(100, true, "Develop on Phron");!memdumpchisel
Welcome to Chisel! Type `!help` to show available commands. bytes memory myData = abi.encode(100, true, "Develop on Phron");
!memdump[0x00:0x20]: 0x0000000000000000000000000000000000000000000000000000000000000000[0x20:0x40]: 0x0000000000000000000000000000000000000000000000000000000000000000[0x40:0x60]: 0x0000000000000000000000000000000000000000000000000000000000000140[0x60:0x80]: 0x0000000000000000000000000000000000000000000000000000000000000000[0x80:0xa0]: 0x00000000000000000000000000000000000000000000000000000000000000a0[0xa0:0xc0]: 0x0000000000000000000000000000000000000000000000000000000000000064[0xc0:0xe0]: 0x0000000000000000000000000000000000000000000000000000000000000001[0xe0:0x100]: 0x0000000000000000000000000000000000000000000000000000000000000060[0x100:0x120]: 0x0000000000000000000000000000000000000000000000000000000000000013[0x120:0x140]: 0x446576656c6f70206f6e204d6f6f6e6265616d00000000000000000000000000!rawstack myDatachisel
Welcome to Chisel! Type `!help` to show available commands. bytes memory myData = abi.encode(100, true, "Develop on Phron");
!memdump[0x00:0x20]: 0x0000000000000000000000000000000000000000000000000000000000000000[0x20:0x40]: 0x0000000000000000000000000000000000000000000000000000000000000000[0x40:0x60]: 0x0000000000000000000000000000000000000000000000000000000000000140[0x60:0x80]: 0x0000000000000000000000000000000000000000000000000000000000000000[0x80:0xa0]: 0x00000000000000000000000000000000000000000000000000000000000000a0[0xa0:0xc0]: 0x0000000000000000000000000000000000000000000000000000000000000064[0xc0:0xe0]: 0x0000000000000000000000000000000000000000000000000000000000000001[0xe0:0x100]: 0x0000000000000000000000000000000000000000000000000000000000000060[0x100:0x120]: 0x0000000000000000000000000000000000000000000000000000000000000013[0x120:0x140]: 0x446576656c6f70206f6e204d6f6f6e6265616d00000000000000000000000000 !rawstack myData
Type: bytes32 └ Data: 0x0000000000000000000000000000000000000000000000000000000000000080[0x80:0xa0]: 0x00000000000000000000000000000000000000000000000000000000000000a0
[0xa0:0xc0]: 0x0000000000000000000000000000000000000000000000000000000000000064
[0xc0:0xe0]: 0x0000000000000000000000000000000000000000000000000000000000000001
[0xe0:0x100]: 0x0000000000000000000000000000000000000000000000000000000000000060
[0x100:0x120]: 0x0000000000000000000000000000000000000000000000000000000000000013
[0x120:0x140]: 0x446576656c6f70206f6e204d6f6f6e6265616d00000000000000000000000000!clearabi.encode(100, true, "Develop on Phron")!clearCleared session! abi.encode(100, true, "Develop on Phron")Type: dynamic bytes├ Hex (Memory):├─ Length ([0x00:0x20]): 0x00000000000000000000000000000000000000000000000000000000000000a0├─ Contents ([0x20:..]): 0x000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000134446576656c6f70206f6e204d6f6f6e6265616d00000000000000000000000000├ Hex (Tuple Encoded):├─ Pointer ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000020├─ Length ([0x20:0x40]): 0x00000000000000000000000000000000000000000000000000000000000000a0└─ Contents ([0x40:..]): 0x000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000134446576656c6f70206f6e204d6f6f6e6265616d00000000000000000000000000uint256 myNumber = 101; !save 1 Saved session to cache with ID = 1 !quitchisel list⚒️ Chisel Sessions├─ "2024-01-15 01:17:34" - chisel-1.jsonchisel load 1Welcome to Chisel! Type `!help` to show available commands. !rawstack myNumberType: bytes32└ Data: 0x0000000000000000000000000000000000000000000000000000000000000065!fork https://testnet.phronscan.io0x12E7BCCA9b1B15f33585b5fc898B967149BDb9a5.balance!fork https://testnet.phron.networkSet fork URL to https://testnet.phronscan.io 0x12E7BCCA9b1B15f33585b5fc898B967149BDb9a5.balanceType: uint├ Hex: 0x000000000000000000000000000000000000000000000358affd3d76ebb78555└ Decimal: 15803094286802091476309npm init
npm install --save-dev hardhat @nomicfoundation/hardhat-foundry
npx hardhat init"scripts": {
"test": "npx hardhat test && forge test"
}npm run testUse the Web3.py Gas Price API to set a gas price strategy. For this example, you'll use the imported rpc_gas_price_strategy
Create and sign the transaction using the web3.eth.account.sign_transaction function. Pass in the nonce gas, gasPrice, to, and value for the transaction along with the sender's private_key. To get the nonce you can use the web3.eth.get_transaction_count function and pass in the sender's address. To predetermine the gasPrice you'll use the web3.eth.generate_gas_price function. For the value, you can format the amount to send from an easily readable format to Wei using the web3.to_wei function
Using the signed transaction, you can then send it using the web3.eth.send_raw_transaction function and wait for the transaction receipt by using the web3.eth.wait_for_transaction_receipt function
Export the contract's ABI and bytecode
Create a contract instance using the web3.eth.contract function and passing in the ABI and bytecode of the contract
Build a constructor transaction using the contract instance and passing in the value to increment by. For this example, you can use 5. You'll then use the build_transaction function to pass in the transaction information including the from address and the nonce for the sender. To get the nonce you can use the web3.eth.get_transaction_count function
Sign the transaction using the web3.eth.account.sign_transaction function and pass in the constructor transaction and the private_key of the sender
Using the signed transaction, you can then send it using the web3.eth.send_raw_transaction function and wait for the transaction receipt by using the web3.eth.wait_for_transaction_receipt function
web3.eth.contractUsing the contract instance, you can then call the number function
Create a contract instance using the web3.eth.contract function and passing in the ABI and address of the deployed contract
Build the increment transaction using the contract instance and passing in the value to increment by. You'll then use the build_transaction function to pass in the transaction information including the from address and the nonce for the sender. To get the nonce you can use the web3.eth.get_transaction_count function
Sign the transaction using the web3.eth.account.sign_transaction function and pass in the increment transaction and the private_key of the sender
Using the signed transaction, you can then send it using the web3.eth.send_raw_transaction function and wait for the transaction receipt by using the web3.eth.wait_for_transaction_receipt function
Create a contract instance using the web3.eth.contract function and passing in the ABI and address of the deployed contract
Build the reset transaction using the contract instance. You'll then use the build_transaction function to pass in the transaction information including the from address and the nonce for the sender. To get the nonce you can use the web3.eth.get_transaction_count function
Sign the transaction using the web3.eth.account.sign_transaction function and pass in the reset transaction and the private_key of the sender
Using the signed transaction, you can then send it using the web3.eth.send_raw_transaction function and wait for the transaction receipt by using the web3.eth.wait_for_transaction_receipt function
[profile.default]
src = 'src'
out = 'out'
libs = ['lib', 'node_modules']
solc = '0.8.20'
evm_version = 'london'mkdir web3-examples && cd web3-examplespip3 install web3 py-solc-x solc-select# 1. Import web3.py
from web3 import Web3
# 2. Create web3.py provider
web3 = Web3(Web3.HTTPProvider("INSERT_RPC_API_ENDPOINT")) # Insert your RPC URL heretouch balances.pyfrom web3 import Web3
# 1. Add the Web3 provider logic here:
provider_rpc = {
"development": "http://localhost:9944",
"phron": "https://testnet.phron.ai",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["phron"])) # Change to correct network
# 2. Create address variables
address_from = 'INSERT_FROM_ADDRESS'
address_to = 'INSERT_TO_ADDRESS'
# 3. Fetch balance data
balance_from = web3.from_wei(
web3.eth.get_balance(Web3.to_checksum_address(address_from)), "ether"
)
balance_to = web3.from_wei(
web3.eth.get_balance(Web3.to_checksum_address(address_to)), "ether"
)
print(f"The balance of { address_from } is: { balance_from } DEV")
print(f"The balance of { address_to } is: { balance_to } DEV")python3 balances.pytouch transaction.py# 1. Add imports
from web3.gas_strategies.rpc import rpc_gas_price_strategy
from web3 import Web3
# 2. Add the Web3 provider logic here:
provider_rpc = {
"development": "http://localhost:9944",
"phron": "https://testnet.phron.ai",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["phron"])) # Change to correct network
# 3. Create address variables
account_from = {
'private_key': 'INSERT_YOUR_PRIVATE_KEY',
'address': 'INSERT_PUBLIC_ADDRESS_OF_PK',
}
address_to = 'INSERT_TO_ADDRESS'
print(
f'Attempting to send transaction from { account_from["address"] } to { address_to }'
)
# 4. Set the gas price strategy
web3.eth.set_gas_price_strategy(rpc_gas_price_strategy)
# 5. Sign tx with PK
tx_create = web3.eth.account.sign_transaction(
{
"nonce": web3.eth.get_transaction_count(
Web3.to_checksum_address(account_from["address"])
),
"gasPrice": web3.eth.generate_gas_price(),
"gas": 21000,
"to": Web3.to_checksum_address(address_to),
"value": web3.to_wei("1", "ether"),
},
account_from["private_key"],
)
# 6. Send tx and wait for receipt
tx_hash = web3.eth.send_raw_transaction(tx_create.rawTransaction)
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Transaction successful with hash: { tx_receipt.transactionHash.hex() }")python3 transaction.pypython3 balances.pyThe balance of 0x3B939FeaD1557C741Ff06492FD0127bd287A421e is: 3563.79 DEVThe balance of 0x9Bf5Ae10540a1ab9B363bEA02A9406E6b2efA9af is: 0 DEVpython3 transaction.pyAttempting to send transaction from 0x3B939FeaD1557C741Ff06492FD0127bd287A421e to 0x9Bf5Ae10540a1ab9B363bEA02A9406E6b2efA9afTransaction successful with hash: 0xac70452510657ed43c27510578d3ce4b3b880d4cca1a24ade1497c6e0ee7f5d6python3 balances.pyThe balance of 0x3B939FeaD1557C741Ff06492FD0127bd287A421e is: 3562.79 DEVThe balance of 0x9Bf5Ae10540a1ab9B363bEA02A9406E6b2efA9af is: 1 DEVtouch Incrementer.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Incrementer {
uint256 public number;
constructor(uint256 _initialNumber) {
number = _initialNumber;
}
function increment(uint256 _value) public {
number = number + _value;
}
function reset() public {
number = 0;
}
}touch compile.py# 1. Import solcx
import solcx
# 2. If you haven't already installed the Solidity compiler, uncomment the following line
# solcx.install_solc()
# 3. Compile contract
temp_file = solcx.compile_files(
'Incrementer.sol',
output_values=['abi', 'bin'],
# solc_version='0.8.19'
)
# 4. Export contract data
abi = temp_file['Incrementer.sol:Incrementer']['abi']
bytecode = temp_file['Incrementer.sol:Incrementer']['bin']touch deploy.py# 1. Add imports
from compile import abi, bytecode
from web3 import Web3
# 2. Add the Web3 provider logic here:
provider_rpc = {
"development": "http://localhost:9944",
"phron": "https://testnet.phron.ai",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["phron"])) # Change to correct network
# 3. Create address variable
account_from = {
'private_key': 'INSERT_YOUR_PRIVATE_KEY',
'address': 'INSERT_PUBLIC_ADDRESS_OF_PK',
}
print(f'Attempting to deploy from account: { account_from["address"] }')
# 4. Create contract instance
Incrementer = web3.eth.contract(abi=abi, bytecode=bytecode)
# 5. Build constructor tx
construct_txn = Incrementer.constructor(5).build_transaction(
{
"from": Web3.to_checksum_address(account_from["address"]),
"nonce": web3.eth.get_transaction_count(
Web3.to_checksum_address(account_from["address"])
),
}
)
# 6. Sign tx with PK
tx_create = web3.eth.account.sign_transaction(
construct_txn, account_from["private_key"]
)
# 7. Send tx and wait for receipt
tx_hash = web3.eth.send_raw_transaction(tx_create.rawTransaction)
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Contract deployed at address: { tx_receipt.contractAddress }")python3 deploy.pypython3 deploy.pyAttempting to deploy from account: 0x3B939FeaD1557C741Ff06492FD0127bd287A421eContract deployed at address: 0xFef3cFb8eE1FE727b3848E551ae5DC8903237B08touch get.py# 1. Import the ABI
from compile import abi
from web3 import Web3
# 2. Add the Web3 provider logic here:
provider_rpc = {
"development": "http://localhost:9944",
"phron": "https://testnet.phron.ai",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["phron"])) # Change to correct network
# 3. Create address variable
contract_address = 'INSERT_CONTRACT_ADDRESS'
print(f"Making a call to contract at address: { contract_address }")
# 4. Create contract instance
Incrementer = web3.eth.contract(address=contract_address, abi=abi)
# 5. Call Contract
number = Incrementer.functions.number().call()
print(f"The current number stored is: { number } ")python3 get.pytouch increment.py reset.py# 1. Add imports
from compile import abi
from web3 import Web3
# 2. Add the Web3 provider logic here:
provider_rpc = {
"development": "http://localhost:9944",
"phron": "https://testnet.phron.ai",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["phron"])) # Change to correct network
# 3. Create variables
account_from = {
'private_key': 'INSERT_YOUR_PRIVATE_KEY',
'address': 'INSERT_PUBLIC_ADDRESS_OF_PK',
}
contract_address = 'INSERT_CONTRACT_ADDRESS'
value = 3
print(
f"Calling the increment by { value } function in contract at address: { contract_address }"
)
# 4. Create contract instance
Incrementer = web3.eth.contract(address=contract_address, abi=abi)
# 5. Build increment tx
increment_tx = Incrementer.functions.increment(value).build_transaction(
{
"from": Web3.to_checksum_address(account_from["address"]),
"nonce": web3.eth.get_transaction_count(
Web3.to_checksum_address(account_from["address"])
),
}
)
# 6. Sign tx with PK
tx_create = web3.eth.account.sign_transaction(increment_tx, account_from["private_key"])
# 7. Send tx and wait for receipt
tx_hash = web3.eth.send_raw_transaction(tx_create.rawTransaction)
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Tx successful with hash: { tx_receipt.transactionHash.hex() }")python3 increment.pypython get.pyMaking a call to contract at address: 0xFef3cFb8eE1FE727b3848E551ae5DC8903237B08The current number stored is: 5python increment.pyCalling the increment by 3 function in contract at address: 0xFef3cFb8eE1FE727b3848E551ae5DC8903237B08Tx successful with hash: 0x47757fd97e3ef8db973e335d1f2d19c46b37d0dbd53fea1636ec559ccf119a13python get.pyMaking a call to contract at address: 0xFef3cFb8eE1FE727b3848E551ae5DC8903237B08The current number stored is: 8# 1. Add imports
from compile import abi
from web3 import Web3
# 2. Add the Web3 provider logic here:
provider_rpc = {
"development": "http://localhost:9944",
"phron": "https://testnet.phron.ai",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["phron"])) # Change to correct network
# 3. Create variables
account_from = {
'private_key': 'INSERT_YOUR_PRIVATE_KEY',
'address': 'INSERT_PUBLIC_ADDRESS_OF_PK',
}
contract_address = 'INSERT_CONTRACT_ADDRESS'
print(f"Calling the reset function in contract at address: { contract_address }")
# 4. Create contract instance
Incrementer = web3.eth.contract(address=contract_address, abi=abi)
# 5. Build reset tx
reset_tx = Incrementer.functions.reset().build_transaction(
{
"from": Web3.to_checksum_address(account_from["address"]),
"nonce": web3.eth.get_transaction_count(
Web3.to_checksum_address(account_from["address"])
),
}
)
# 6. Sign tx with PK
tx_create = web3.eth.account.sign_transaction(reset_tx, account_from["private_key"])
# 7. Send tx and wait for receipt
tx_hash = web3.eth.send_raw_transaction(tx_create.rawTransaction)
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Tx successful with hash: { tx_receipt.transactionHash.hex() }")python3 reset.pypython get.pyMaking a call to contract at address: 0xFef3cFb8eE1FE727b3848E551ae5DC8903237B08The current number stored is: 8python reset.pyCalling the reset function in contract at address: 0xFef3cFb8eE1FE727b3848E551ae5DC8903237B08Tx successful with hash: 0x152f07430b524838da848b44d58577db252681fba6fbeaf117b2f9d432e301b2python get.pyMaking a call to contract at address: 0xFef3cFb8eE1FE727b3848E551ae5DC8903237B08The current number stored is: 0Name your smart contract
Add any desired extensions
Once created, navigate to your project’s directory and open in your preferred code editor
If you open the contracts folder, you will find your smart contract; this is your smart contract written in Solidity
The following is code for an ERC721Base contract without specified extensions. It implements all of the logic inside the ERC721Base.sol contract; which implements the ERC721A standard.
This contract inherits the functionality of ERC721Base through the following steps:
Importing the ERC721Base contract
Inheriting the contract by declaring that your contract is an ERC721Base contract
Implementing any required methods, such as the constructor
After modifying your contract with your desired custom logic, you can deploy it to Phron using Deploy. That will be covered in the next section!
Uploading your contract source code (ABI) to IPFS
When it is completed, it will open a dashboard interface to finish filling out the parameters
_name - contract name
_symbol - symbol or "ticker"
_royaltyRecipient - wallet address to receive royalties from secondary sales
_royaltyBps - basis points (bps) that will be given to the royalty recipient for each secondary sale, e.g. 500 = 5%
Select the desired Phron network.
Manage additional settings on your contract’s dashboard as needed such as uploading NFTs, configuring permissions, and more
Use the React or TypeScript SDK to interact with your application’s functions. This will be covered in the following section on interacting with a contract
Foundry has become an increasingly popular development environment for smart contracts because it requires only one language: Solidity. Phron offers introductory documentation on using Foundry with Phron networks, which is recommended to read to get an introduction to using Foundry. In this tutorial, we will dip our toes deeper into the library to get a more cohesive look at properly developing, testing, and deploying with Foundry.
In this demonstration, we will deploy two smart contracts. One is a token, and the other will depend on that token. We will also write unit tests to ensure the contracts work as expected. To deploy them, we will write a script that Foundry will use to determine the deployment logic. Finally, we will verify the smart contracts on Phron's blockchain explorer.
To get started, you will need the following:
Have an account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
Have
Have a Phron API Key
The first step to start a Foundry project is, of course, to create it. If you have Foundry installed, you can run:
This will have the forge utility initialize a new folder named foundry with a Foundry project initialized within it. The script, src, and test folders may have files in them already. Be sure to delete them, because we will be writing our own soon.
From here, there are a few things to do first before writing any code. First, we want to add a dependency to , because they include helpful contracts to use when writing token smart contracts. To do so, add them using their GitHub repository name:
This will add the OpenZeppelin git submodule to your lib folder. To be sure that this dependency is mapped, you can override the mappings in a special file, remappings.txt:
Every line in this file is one of the dependencies that can be referenced in the project's smart contracts. Dependencies can be edited and renamed so that it's easier to reference different folders and files when working on smart contracts. It should look similar to this with OpenZeppelin installed properly:
Finally, let's open up the foundry.toml file. In preparation for Etherscan verification and deployment, add this to the file:
The first addition is a specification of the solc_version, underneath profile.default. The rpc_endpoints tag allows you to define which RPC endpoints to use when deploying to a named network, in this case, Phron. The etherscan tag allows you to add Etherscan API keys for smart contract verification, which we will review later.
Smart contracts in Foundry destined for deployment by default belong in the src folder. In this tutorial, we'll write two smart contracts. Starting with the token:
Open the file and add the following to it:
As you can see, the OpenZeppelin ERC20 smart contract is imported by the mapping defined in remappings.txt.
The second smart contract, which we'll name Container.sol, will depend on this token contract. It is a simple contract that holds the ERC-20 token we'll deploy. You can create the file by executing:
Open the file and add the following to it:
The Container smart contract can have its status updated based on how many tokens it holds and what its initial capacity value was set to. If the number of tokens it holds is above its capacity, its status can be updated to Overflowing. If it holds tokens equal to capacity, its status can be updated to Full. Otherwise, the contract will start and stay in the Unsatisfied state.
Container requires a MyToken smart contract instance to function, so when we deploy it, we will need logic to ensure that it is deployed with a MyToken smart contract.
Before we deploy anything to a TestNet or MainNet, however, it's good to test your smart contracts. There are many types of tests:
Unit tests — allow you to test specific parts of a smart contract's functionality. When writing your own smart contracts, it can be a good idea to break functionality into different sections so that it is easier to unit test
Fuzz tests — allow you to test a smart contract with a wide variety of inputs to check for edge cases
Integration tests — allow you to test a smart contract when it works in conjunction with other smart contracts, so that you know it works as expected in a deployed environment
To get started with writing tests for this tutorial, make a new file in the test folder:
By convention, all of your tests should end with .t.sol and start with the name of the smart contract that it is testing. In practice, the test can be stored anywhere and is considered a test if it has a function that starts with the word "test".
Let's start by writing a test for the token smart contract. Open up MyToken.t.sol and add:
Let's break down what's happening here. The first line is typical for a Solidity file: setting the Solidity version. The next two lines are imports. forge-std/Test.sol is the standard library that Forge (and thus Foundry) includes to help with testing. This includes the Test smart contract, certain assertions, and .
If you take a look at the MyTokenTest smart contract, you'll see two functions. The first is setUp, which is run before each test. So in this test contract, a new instance of MyToken is deployed every time a test function is run. You know if a function is a test function if it starts with the word "test", so the second function, testConstructorMint is a test function.
Great! Let's write some more tests, but for Container.
And add the following:
This test smart contract has two tests, so when running the tests, there will be two deployments of both MyToken and Container, for four smart contracts. You can run the following command to see the result of the test:
When testing, you should see the following output:
Sometimes you'll want to unit test an internal function in a smart contract. To do so, you'll have to write a test harness smart contract, which inherits from the smart contract and exposes the internal function as a public one.
For example, in Container, there is an internal function named _isOverflowing, which checks to see if the smart contract has more tokens than its capacity. To test this, add the following test harness smart contract to the Container.t.sol file:
Now, inside of the ContainerTest smart contract, you can add a new test that tests the previously unreachable _isOverflowing contract:
Now, when you run the test with forge test, you should see that testIsOverflowingFalse passes!
When you write a unit test, you can only use so many inputs to test. You can test edge cases, a few select values, and perhaps one or two random ones. But when working with inputs, there are nearly an infinite amount of different ones to test! How can you be sure that they work for every value? Wouldn't you feel safer if you could test 10000 different inputs instead of less than 10?
One of the best ways that developers can test many inputs is through fuzzing, or fuzz tests. Foundry automatically fuzz tests when an input in a test function is included. To illustrate this, add the following test to the MyTokenTest contract in MyToken.t.sol.
This test includes uint256 amountToMint as input, which tells Foundry to fuzz with uint256 inputs! By default, Foundry will input 256 different inputs, but this can be configured with the .
Additionally, the first line in the function uses vm.assume to only use inputs that are less than or equal to one ether since the mint function reverts if someone tries to mint more than one ether at a time. This cheatcode helps you direct the fuzzing into the right range.
Let's look at another fuzzing test to put in the MyTokenTest contract, but this time where we expect to fail:
In Foundry, when you want to test for a failure, instead of just starting your test function with the world "test", you start it with "testFail". In this test, we assume that the amountToMint is above one ether, which should fail!
Now run the tests:
You should see something similar to the following in the console:
In Foundry, you can locally fork a network so that you can test out how the contracts would work in an environment with already deployed smart contracts. For example, if someone deployed smart contract A on Phron that required a token smart contract, you could fork the Phron network and deploy your own token to test how smart contract A would react to it.
NotePhron's custom precompile smart contracts currently do not work in Foundry forks because precompiles are Substrate-based whereas typical smart contracts are completely based on the EVM. Learn more about forking on Phron and the differences between Phron and Ethereum.
In this tutorial, you will test how your Container smart contract interacts with an already deployed MyToken contract on Phron
Let's add a new test function to the ContainerTest smart contract in Container.t.sol called testAlternateTokenOnPhronFork:
The first step (and thus first line) in this function is to have the test function fork a network with vm.createFork. Recall that vm is a cheat code provided by the Forge standard library. All that's necessary to create a fork is an RPC URL, or an alias for an RPC URL that's stored in the foundry.toml file. In this case, we added an RPC URL for "phron" in the setup step, so in the test function we will just pass the word "phronbase". This cheat code function returns an ID for the fork created, which is stored in an uint256 and is necessary for activating the fork.
On the second line, after the fork has been created, the environment will select and use the fork in the test environment with vm.selectFork. The third line just demonstrates that the current fork, retrieved with vm.activeFork, is the same as the Phron fork.
The fourth line of code retrieves an already deployed instance of MyToken, which is what's so useful about forking: you can use contracts that are already deployed.
The rest of the code tests capacity like you would expect a local test to. If you run the tests (with the -vvvv tag for extra logging), you'll see that it passes:
That's it for testing! You can find the complete Container.t.sol and MyToken.t.sol files below:
Not only are tests in Foundry written in Solidity, the scripts are too! Like other developer environments, scripts can be written to help interact with deployed smart contracts or can help along a complex deployment process that would be difficult to do manually. Even though scripts are written in Solidity, they are never deployed to a chain. Instead, much of the logic is actually run off-chain, so don't worry about any additional gas costs for using Foundry instead of a JavaScript environment like Hardhat.
In this tutorial, we will use Foundry's scripts to deploy the MyToken and Container smart contracts. To create the deployment scripts, create a new file in the script folder:
By convention, scripts should end with s.sol and have a name similar to the script they relate to. In this case, we are deploying the Container smart contract, so we have named the script Container.s.sol, though it's not the end of the world if you use a more suitable or descriptive name.
In this script, add:
Let's break this script down. The first line is standard: declaring the solidity version. The imports include the two smart contracts you previously added, which will be deployed. This includes additional functionality to use in a script, including the Script contract.
Now let's look at the logic in the contract. There is a single function, run, which is where the script logic is hosted. In this run function, the vm object is used often. This is where all of the Forge cheatcodes are stored, which determines the state of the virtual machine that the solidity is run in.
In the first line within the run function, vm.envUint is used to get a private key from the system's environment variables (we will do this soon). In the second line, vm.startBroadcast starts a broadcast, which indicates that the following logic should take place on-chain. So when the MyToken and the Container contracts are instantiated with the new keyword, they are instantiated on-chain. The final line, vm.stopBroadcast ends the broadcast.
Before we run this script, let's set up some of our environment variables. Create a new .env file:
And within this file, add the following:
NoteFoundry provides . It is up to you to decide whether or not you would rather use it in the console, have it stored in your device's environment, using a hardware wallet, or using a keystore.
To add these environment variables, run the following command:
Now your script and project should be ready for deployment! Use the following command to do so:
This command runs the ContainerDeployScript contract as a script. The --broadcast option tells Forge to allow broadcasting of transactions, the --verify option tells Forge to verify to Phronscan when deploying, -vvvv makes the command output verbose, and --rpc-url phronbase sets the network to what phronbase was set to in foundry.toml. The --legacy flag instructs Foundry to bypass EIP-1559. While all Phron networks support EIP-1559, Foundry will refuse to submit the transaction to phronbase and revert to a local simulation if you omit the --legacy flag.
You should see something like this as output:
You should be able to see that your contracts were deployed, and are verified on Phronscan! For example, this is where my Container.sol contract was deployed.
The entire deployment script is available below:
Let's say that you're comfortable with your smart contracts and you want to deploy on the Phron MainNet! The process isn't too different from what was just done, you just have to change the command's rpc-url from phronbase to phron, since you've already added Phron MainNet's information in the foundry.toml file:
It's important to note that there are additional, albeit more complex, . Some of these methods can be considered safer than storing a production private key in environment variables.
That's it! You've gone from nothing to a fully tested, deployed, and verified Foundry project. You can now adapt this so that you can use Foundry in your own projects!
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Hardhat is a flexible and extensible Ethereum development environment that streamlines the smart contract development process. Since Phron is Ethereum-compatible, you can use Hardhat to develop and deploy smart contracts on Phron.
Hardhat takes a task-based approach to development, where you can define and execute that perform specific actions. These actions include compiling and deploying contracts, running tests, and more. Tasks are highly configurable, so you can create, customize, and execute tasks that are tailored to meet your needs.
You can also extend Hardhat's functionality through the use of . Plugins are external extensions that integrate with Hardhat to provide additional features and tools for your workflow. For example, there are plugins for common Ethereum libraries, like Ethers.js and viem, a plugin that extends the Chai assertion library to include Ethereum-specific functionality, and more. All of these plugins can be used to extend your Hardhat project on Phron.
This guide will provide a brief introduction to Hardhat and show you how to use Hardhat to compile, deploy, and debug Ethereum smart contracts on the Phron TestNet.
Please note that although Hardhat comes with a component, which provides a local development environment, you should use a local Phron development node instead. You can connect a Phron development node to Hardhat just like you would with any other network.
To get started, you will need the following:
Have MetaMask installed and connected to Phron
Have an account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
You will need to create a Hardhat project if you don't already have one. You can create one by completing the following steps:
Create a directory for your project
Initialize the project, which will create a package.json file
Install Hardhat
Create a Hardhat project
Note
npxis used to run executables installed locally in your project. Although Hardhat can be installed globally, it is recommended to install it locally in each project so that you can control the version on a project-by-project basis.
A menu will appear, which will allow you to create a new project or use a sample project. For this example, you can choose Create an empty hardhat.config.js, which will create a Hardhat configuration file for your project
The Hardhat configuration file is the entry point into your Hardhat project. It defines various settings and options for your Hardhat project, such as the Solidity compiler version to use and the networks you can deploy your contracts to.
To start, your hardhat.config.js should resemble the following:
For this example, you can leave the Solidity compiler version to 0.8.20; however, if you are using a different contract that requires a newer version, don't forget to update the version here.
Next, you'll need to modify your configuration file to add the network configurations for the network you want to deploy your contract to. For Phron networks, you'll need to specify the following:
url - the RPC endpoint of the node
chainId - the chain ID, which is used to validate the network
accounts - the accounts that can be used to deploy and interact with contracts. You can either enter an array of the private keys for your accounts or use an
For this example, the network will be Phron, but you can modify the configuration to use any of the Phron networks:
If you are planning on using any plugins with your project, you'll need to install the plugin and import it into the hardhat.config.js file. Once a plugin has been imported, it becomes part of the , and you can leverage the plugin's functionality within tasks, scripts, and more.
For this example, you can install the hardhat-ethers plugin and import it into the configuration file. This plugin provides a convenient way to use the Ethers.js library to interact with the network.
Additionally, you'll need to install the hardhat-ignition-ethers plugin to enable deployment of smart contracts with Hardhat Ignition. You can install it with the following command:
To import both plugins, add the following require statements to the top of the Hardhat configuration file:
For more information on the available configuration options, please refer to Hardhat's documentation on .
Now that you've configured your project, you can begin the development process by creating your smart contract. The contract will be a simple one that will let you store a value that can be retrieved later, called Box.
To add the contract, you'll take the following steps:
Create a contracts directory
Create a Box.sol file
Open the file and add the following contract to it:
The next step is to compile the Box.sol smart contract. For this, you can use the built-in compile task, which will look for Solidity files in the contracts directory and compile them using the version and compiler settings defined in the hardhat.config.js file.
To use the compile task, all you have to do is run:
After compilation, an artifacts directory is created that holds the bytecode and metadata of the contract, which are .json files. It’s a good idea to add this directory to a .gitignore file.
If you make changes to the contract after you've compiled it, you can compile it again using the same command. Hardhat will look for any changes and recompile the contract. If no changes are found, nothing will be compiled. If needed, you can force a compilation using the clean task, which will clear the cache and delete the old artifacts.
To deploy the contract, you'll use Hardhat Ignition, a declarative framework for deploying smart contracts. Hardhat Ignition is designed to make it easy to manage recurring tasks surrounding smart contract deployment and testing. For more information, be sure to check out the .
To set up the proper file structure for your Ignition module, create a folder named ignition and a subdirectory called modules. Then add a new file to it called Box.js. You can take all three of these steps with the following command:
Next, you can write your Hardhat Ignition module. To get started, take the following steps:
Import the buildModule function from the Hardhat Ignition module
Export a module using buildModule
Use the getAccount method to select the deployer account
To run the script and deploy the Box.sol contract, use the following command, which requires you to specify the network name as defined in your hardhat.config.js. If you don't specify a network, hardhat will deploy the contract to a local hardhat network by default.
NoteIf you're using another Phron network, make sure that you specify the correct network. The network name needs to match how it's defined in the
hardhat.config.jsfile.
You'll be prompted to confirm the network you wish to deploy to. After a few seconds after you confirm, the contract is deployed, and you'll see the contract address in the terminal.
Congratulations, your contract is live! Save the address, as you will use it to interact with this contract instance in the next step.
There are a couple of ways that you can interact with your newly deployed contract using Hardhat: you can use the console task, which spins up an interactive JavaScript console, or you can create another script and use the run task to execute it.
The uses the same execution environment as the tasks and scripts, so it automatically uses the configurations and plugins defined in the hardhat.config.js.
To launch the Hardhat console, you can run:
Next, you can take the following steps, entering one line at a time:
Create a local instance of the Box.sol contract
Connect the local instance to the deployed contract, using the address of the contract
Interact with the attached contract. For this example, you can call the store method and store a simple value
The transaction will be signed by your account configured in the hardhat.config.js file and broadcasted to the network. The output should look similar to:
Notice your address labeled from, the address of the contract, and the data that is being passed. Now, you can retrieve the value by running:
You should see 5, or the value you initially stored.
Similarly to the deployment script, you can create a script to interact with your deployed contract, store it in the scripts directory, and run it using the built-in run task.
To get started, create a set-value.js file in the scripts directory:
Now paste the following contract into the set-value.js file:
To run the script, you can use the following command:
The script should return 2 as the value.
You can any EVM-compatible chain using Hardhat, including Phron. Forking simulates the live Phron network locally, enabling you to interact with deployed contracts on Phron in a local test environment. Since Hardhat forking is based on an EVM implementation, you can interact with the fork using standard Ethereum JSON-RPC methods supported by Phron and .
There are some limitations to be aware of when using Hardhat forking. You cannot interact with any of the Phron precompiled contracts or their functions. Precompiles are a part of the Substrate implementation and therefore cannot be replicated in the simulated EVM environment. This prohibits you from interacting with cross-chain assets on Phron and Substrate-based functionality such as staking and governance.
There is currently an issue related to forking Phron, so in order to fix the issue, you'll need to manually patch Hardhat first. You can find out more information by following the as well as the related .
Before getting started, you'll need to apply a temporary patch to workaround an RPC error until Hardhat fixes the root issue. The error is as follows:
To patch Hardhat, you'll need to open the node_modules/hardhat/internal/hardhat-network/jsonrpc/client.js file of your project. Next, you'll add an addAccessList function and update the _perform and _performBatch functions.
To get started, you can remove the preexisting _perform and _performBatch functions and, in their place, add the following code snippet:
Then you can use to automatically patch the package by running the following command:
A patches directory will be created, and now you should be all set to fork Phron without running into any errors.
You can fork Phron from the command line or configure your Hardhat project to always run the fork from your hardhat.config.js file. To fork Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
To fork Phron from the command line, you can run the following command from within your Hardhat project directory:
If you prefer to configure your Hardhat project, you can update your hardhat.config.js file with the following configurations:
When you spin up the Hardhat fork, you'll have 20 development accounts that are pre-funded with 10,000 test tokens. The forked instance is available at http://127.0.0.1:8545/. The output in your terminal should resemble the following:
To verify you have forked the network, you can query the latest block number:
If you convert the result from , you should get the latest block number from the time you forked the network. You can cross-reference the block number using a block explorer.
From here, you can deploy new contracts to your forked instance of Phron or interact with contracts already deployed by creating a local instance of the deployed contract.
To interact with an already deployed contract, you can create a new script in the scripts directory using ethers. Because you'll be running it with Hardhat, you don't need to import any libraries. Inside the script, you can access a live contract on the network using the following snippet:
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import '@thirdweb-dev/contracts/base/ERC721Base.sol';
contract Contract is ERC721Base {
constructor(
string memory _name,
string memory _symbol,
address _royaltyRecipient,
uint128 _royaltyBps
) ERC721Base(_name, _symbol, _royaltyRecipient, _royaltyBps) {}
}npx thirdweb create contractnpx thirdweb deploynpx thirdweb create --appimport { createThirdwebClient } from 'thirdweb';
// Replace this with your client ID string.
// Refer to https://portal.thirdweb.com/typescript/v5/client on how to get a client ID
const clientId = import.meta.env.VITE_TEMPLATE_CLIENT_ID;
export const client = createThirdwebClient({
clientId: clientId,
});yarn devimport { defineChain } from 'thirdweb';
const phron = defineChain({
id: 7744,
rpc: 'https://testnet.phron.ai',
});import { sendTransaction } from 'thirdweb';
// MetaMask wallet used for example, the pattern is the same for all wallets
import { createWallet } from 'thirdweb/wallets';
// Initialize the wallet. thirdweb supports 300+ wallet connectors
const wallet = createWallet('io.metamask');
// Connect the wallet. This returns a promise that resolves to the connected account
const account = await wallet.connect({
// Pass the client you created with `createThirdwebClient()`
client,
});
// Sign and send a transaction with the account. Returns the transaction hash
const { transactionHash } = await sendTransaction({
// Assuming you have called `prepareTransaction()` or `prepareContractCall()` before, which returns the prepared transaction to send
transaction,
// Pass the account to sign the transaction with
account,
});import { getContract } from 'thirdweb';
import { client } from './client';
const myContract = getContract({
client,
chain: phron,
address: 0xa72f549a1a12b9b49f30a7f3aeb1f4e96389c5d8, // Incrementer contract address on Phron
abi: '[{"inputs":[],"name":"increment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"number","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]';
});import { prepareContractCall, toWei } from 'thirdweb';
const tx = prepareContractCall({
contract,
// Pass the method signature that you want to call
method: 'function mintTo(address to, uint256 amount)',
// Pass the params for that method.
// Their types are automatically inferred based on the method signature
params: ['0x123...', toWei('100')],
});import { prepareContractCall } from 'thirdweb';
const tx = prepareContractCall({
contract,
// Pass the method signature that you want to call
method: 'function increment()',
// Increment takes no params so we are leaving an empty array
params: [],
});import { prepareTransaction, toWei } from 'thirdweb';
const transaction = prepareTransaction({
// The account that will be the receiver
to: '0x456...',
// The value is the amount of ether you want to send with the transaction
value: toWei('1'),
// The chain to execute the transaction on. This assumes you already set up
// phron as a custom chain as shown in the configure chain section
chain: phron,
// Your thirdweb client
client,
});import { readContract } from 'thirdweb';
const balance = await readContract({
contract: contract,
method: 'function balanceOf(address) view returns (uint256)',
params: ['0x123...'],
});import { readContract } from 'thirdweb';
const number = await readContract({
contract: contract,
method: 'number',
params: [],
});npx thirdweb generate INSERT_CHAIN_ID/INSERT_CONTRACT_ADDRESSnpx thirdweb generate 1287/0xa72f549a1a12b9b49f30a7f3aeb1f4e96389c5d8import { prepareTransaction, toWei } from 'thirdweb';
const transaction = prepareTransaction({
to: '0x1234567890123456789012345678901234567890',
chain: phron,
client: thirdwebClient,
value: toWei('1.0'),
gasPrice: 150n,
});import { sendTransaction } from 'thirdweb';
const { transactionHash } = await sendTransaction({
account,
transaction,
});import { sendAndConfirmTransaction } from 'thirdweb';
import { createWallet } from 'thirdweb/wallets';
const wallet = createWallet('io.metamask');
const account = await wallet.connect({ client });
const receipt = await sendAndConfirmTransaction({
transaction,
account,
});import { estimateGas } from 'thirdweb';
const gasEstimate = await estimateGas({ transaction });
console.log('estmated gas used', gasEstimate);import { estimateGas } from 'thirdweb';
const gasCost = await estimateGasCost({ transaction });
console.log('cost in ether', gasCost.ether);import { simulateTransaction } from 'thirdweb';
const result = await simulateTransaction({ transaction });
console.log('simulation result', result);import { encode } from 'thirdweb';
const data = await encode(transaction);
console.log('encoded data', data);import { ConnectButton } from 'thirdweb/react';
import { createWallet, inAppWallet } from 'thirdweb/wallets';
const wallets = [
inAppWallet(),
createWallet('io.metamask'),
createWallet('com.coinbase.wallet'),
createWallet('me.rainbow'),
];
function Example() {
return (
<div>
<ConnectButton client={client} wallets={wallets} />
</div>
);
}yarn devnpx thirdweb deploy --appForking tests - integration tests that allows you to make a fork (a carbon copy of a network), so that you can simulate a series of transactions on a preexisting network
Box contractReturn an object from the module. This makes the Box contract accessible for interaction in Hardhat tests and scripts
forge init foundry && cd foundryforge install OpenZeppelin/openzeppelin-contractsforge remappings > remappings.txtds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/[profile.default]
src = 'src'
out = 'out'
libs = ['lib']
solc_version = '0.8.20'
[rpc_endpoints]
phronbase = "https://testnet.phron.ai"
phronapi = "INSERT_RPC_API_ENDPOINT"
[etherscan]
phronbase = { key = "${PHRONSCAN_API_KEY}" }
phronapi = { key = "${PHRONSCAN_API_KEY}" }touch MyToken.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Import OpenZeppelin Contract
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// This ERC-20 contract mints the specified amount of tokens to the contract creator
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MYTOK") {
_mint(msg.sender, initialSupply);
}
// An external minting function allows anyone to mint as many tokens as they want
function mint(uint256 toMint, address to) external {
require(toMint <= 1 ether);
_mint(to, toMint);
}
}touch Container.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Import OpenZeppelin Contract
import {MyToken} from "./MyToken.sol";
enum ContainerStatus {
Unsatisfied,
Full,
Overflowing
}
contract Container {
MyToken token;
uint256 capacity;
ContainerStatus public status;
constructor(MyToken _token, uint256 _capacity) {
token = _token;
capacity = _capacity;
status = ContainerStatus.Unsatisfied;
}
// Updates the status value based on the number of tokens that this contract has
function updateStatus() public {
address container = address(this);
uint256 balance = token.balanceOf(container);
if (balance < capacity) {
status = ContainerStatus.Unsatisfied;
} else if (balance == capacity) {
status = ContainerStatus.Full;
} else if (_isOverflowing(balance)) {
status = ContainerStatus.Overflowing;
}
}
// Returns true if the contract should be in an overflowing state, false if otherwise
function _isOverflowing(uint256 balance) internal view returns (bool) {
return balance > capacity;
}
}cd test
touch MyToken.t.solpragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/MyToken.sol";
contract MyTokenTest is Test {
MyToken public token;
// Runs before each test
function setUp() public {
token = new MyToken(100);
}
// Tests if minting during the constructor happens properly
function testConstructorMint() public {
assertEq(token.balanceOf(address(this)), 100);
}
}touch Container.t.sol// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";
import {Container, ContainerStatus} from "../src/Container.sol";
contract ContainerTest is Test {
MyToken public token;
Container public container;
uint256 constant CAPACITY = 100;
// Runs before each test
function setUp() public {
token = new MyToken(1000);
container = new Container(token, CAPACITY);
}
// Tests if the container is unsatisfied right after constructing
function testInitialUnsatisfied() public {
assertEq(token.balanceOf(address(container)), 0);
assertTrue(container.status() == ContainerStatus.Unsatisfied);
}
// Tests if the container will be "full" once it reaches its capacity
function testContainerFull() public {
token.transfer(address(container), CAPACITY);
container.updateStatus();
assertEq(token.balanceOf(address(container)), CAPACITY);
assertTrue(container.status() == ContainerStatus.Full);
}
}forge testforge test[⠊] Compiling...No files changed, compilation skipped
Ran 1 test for test/MyToken.t.sol:MyTokenTest[PASS] testConstructorMint() (gas: 10651)Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 361.83µs (39.46µs CPU time)
Ran 2 tests for test/Container.t.sol:ContainerTest[PASS] testContainerFull() (gas: 73204)[PASS] testInitialUnsatisfied() (gas: 18476)Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 422.00µs (128.67µs CPU time)Ran 2 test suites in 138.17ms (783.83µs CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests)contract ContainerHarness is Container {
constructor(MyToken _token, uint256 _capacity) Container(_token, _capacity) {}
function exposed_isOverflowing(uint256 balance) external view returns(bool) {
return _isOverflowing(balance);
}
}// Tests for negative cases of the internal _isOverflowing function
function testIsOverflowingFalse() public {
ContainerHarness harness = new ContainerHarness(token , CAPACITY);
assertFalse(harness.exposed_isOverflowing(CAPACITY - 1));
assertFalse(harness.exposed_isOverflowing(CAPACITY));
assertFalse(harness.exposed_isOverflowing(0));
}forge test[⠊] Compiling...[⠒] Compiling 1 files with 0.8.20[⠢] Solc 0.8.20 finished in 1.06sCompiler run successful
Ran 1 test for test/MyToken.t.sol:MyTokenTest[PASS] testConstructorMint() (gas: 10651)Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 498.00µs (48.96µs CPU time)
Ran 3 tests for test/Container.t.sol:ContainerTest[PASS] testContainerFull() (gas: 73238)[PASS] testInitialUnsatisfied() (gas: 18510)[PASS] testIsOverflowingFalse() (gas: 192130)Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 606.17µs (183.54µs CPU time)Ran 2 test suites in 138.29ms (1.10ms CPU time): 4 tests passed, 0 failed, 0 skipped (4 total tests)// Fuzz tests for success upon minting tokens one ether or below
function testMintOneEtherOrBelow(uint256 amountToMint) public {
vm.assume(amountToMint <= 1 ether);
token.mint(amountToMint, msg.sender);
assertEq(token.balanceOf(msg.sender), amountToMint);
}// Fuzz tests for failure upon minting tokens above one ether
function testFailMintAboveOneEther(uint256 amountToMint) public {
vm.assume(amountToMint > 1 ether);
token.mint(amountToMint, msg.sender);
}forge testforge test[⠊] Compiling...[⠊] Compiling 1 files with 0.8.20[⠒] Solc 0.8.20 finished in 982.65msCompiler run successful
Ran 3 tests for test/Container.t.sol:ContainerTest[PASS] testContainerFull() (gas: 73238)[PASS] testInitialUnsatisfied() (gas: 18510)[PASS] testIsOverflowingFalse() (gas: 192130)Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 446.25µs (223.67µs CPU time)
Ran 3 tests for test/MyToken.t.sol:MyTokenTest[PASS] testConstructorMint() (gas: 10651)[PASS] testFailMintAboveOneEther(uint256) (runs: 256, μ: 8462, ~: 8462)[PASS] testMintOneEtherOrBelow(uint256) (runs: 256, μ: 37939, ~: 39270)Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 10.78ms (18.32ms CPU time)Ran 2 test suites in 138.88ms (11.23ms CPU time): 6 tests passed, 0 failed, 0 skipped (6 total tests)// Fork tests in the Phron environment
function testAlternateTokenOnPhronFork() public {
// Creates and selects a fork, returns a fork ID
uint256 phronFork = vm.createFork("phron");
vm.selectFork(phronFork);
assertEq(vm.activeFork(), phronFork);
// Get token that's already deployed & deploys a container instance
token = MyToken(0x359436610E917e477D73d8946C2A2505765ACe90);
container = new Container(token, CAPACITY);
// Mint tokens to the container & update container status
token.mint(CAPACITY, address(container));
container.updateStatus();
// Assert that the capacity is full, just like the rest of the time
assertEq(token.balanceOf(address(container)), CAPACITY);
assertTrue(container.status() == ContainerStatus.Full);
}forge test -vvvvforge test[PASS] testIsOverflowingFalse() (gas: 192130)Traces: [192130] ContainerTest::testIsOverflowingFalse() ├─ [151256] → new ContainerHarness@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a │ └─ ← [Return] 522 bytes of code ├─ [421] ContainerHarness::exposed_isOverflowing(99) [staticcall] │ └─ ← [Return] false ├─ [0] VM::assertFalse(false) [staticcall] │ └─ ← [Return] ├─ [421] ContainerHarness::exposed_isOverflowing(100) [staticcall] │ └─ ← [Return] false ├─ [0] VM::assertFalse(false) [staticcall] │ └─ ← [Return] ├─ [421] ContainerHarness::exposed_isOverflowing(0) [staticcall] │ └─ ← [Return] false ├─ [0] VM::assertFalse(false) [staticcall] │ └─ ← [Return] └─ ← [Stop]Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.07s (2.07s CPU time)Ran 2 test suites in 2.44s (2.08s CPU time): 7 tests passed, 0 failed, 0 skipped (7 total tests)// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";
import {Container, ContainerStatus} from "../src/Container.sol";
contract ContainerTest is Test {
MyToken public token;
Container public container;
uint256 constant CAPACITY = 100;
function setUp() public {
token = new MyToken(1000);
container = new Container(token, CAPACITY);
}
function testInitialUnsatisfied() public {
assertEq(token.balanceOf(address(container)), 0);
assertTrue(container.status() == ContainerStatus.Unsatisfied);
}
function testContainerFull() public {
token.transfer(address(container), CAPACITY);
container.updateStatus();
assertEq(token.balanceOf(address(container)), CAPACITY);
assertTrue(container.status() == ContainerStatus.Full);
}
function testIsOverflowingFalse() public {
ContainerHarness harness = new ContainerHarness(token , CAPACITY);
assertFalse(harness.exposed_isOverflowing(CAPACITY - 1));
assertFalse(harness.exposed_isOverflowing(CAPACITY));
assertFalse(harness.exposed_isOverflowing(0));
}
function testAlternateTokenOnPhronFork() public {
// Creates and selects a fork
uint256 phronFork = vm.createFork("phron");
vm.selectFork(phronFork);
assertEq(vm.activeFork(), phronFork);
// Get token that's already deployed & deploys a container instance
token = MyToken(0x93e1e9EC6c1A8736266A595EFe97B5673ea0fEAc);
container = new Container(token, CAPACITY);
// Mint tokens to the container & update container status
token.mint(CAPACITY, address(container));
container.updateStatus();
// Assert that the capacity is full
assertEq(token.balanceOf(address(container)), CAPACITY);
assertTrue(container.status() == ContainerStatus.Full);
}
}
contract ContainerHarness is Container {
constructor(MyToken _token, uint256 _capacity) Container(_token, _capacity) {}
function exposed_isOverflowing(uint256 balance) external view returns(bool) {
return _isOverflowing(balance);
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/MyToken.sol";
contract MyTokenTest is Test {
MyToken public token;
function setUp() public {
token = new MyToken(100);
}
function testConstructorMint() public {
assertEq(token.balanceOf(address(this)), 100);
}
function testMintOneEtherOrBelow(uint256 amountToMint) public {
vm.assume(amountToMint <= 1 ether);
token.mint(amountToMint, msg.sender);
assertEq(token.balanceOf(msg.sender), amountToMint);
}
function testFailMintAboveOneEther(uint256 amountToMint) public {
vm.assume(amountToMint > 1 ether);
token.mint(amountToMint, msg.sender);
}
}cd script
touch Container.s.solpragma solidity ^0.8.0;
import "forge-std/Script.sol";
import {MyToken} from "../src/MyToken.sol";
import {Container} from "../src/Container.sol";
contract ContainerDeployScript is Script {
// Runs the script; deploys MyToken and Container
function run() public {
// Get the private key from the .env
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
// Make a new token
MyToken token = new MyToken(1000);
// Make a new container
new Container(token, 500);
vm.stopBroadcast();
}
}touch .envPRIVATE_KEY=YOUR_PRIVATE_KEY
PHRONSCAN_API_KEY=YOUR_PHRONSCAN_API_KEYsource .envforge script Container.s.sol:ContainerDeployScript --broadcast --verify -vvvv --legacy --rpc-url phronScript ran successfully.Setting up 1 EVM.Simulated On-chain Traces: [488164] → new MyToken@0xAEe1a769b10d03a6CeB4D9DFd3aBB2EF807ee6aa ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x3B939FeaD1557C741Ff06492FD0127bd287A421e, value: 1000) └─ ← [Return] 1980 bytes of code [133238] → new Container@0xeb1Ff38A645Eae4E64dFb93772D8129F88E11Ab1 └─ ← [Return] 432 bytes of codeChain 1287Estimated gas price: 0.125 gweiEstimated total gas used for script: 1670737Estimated amount required: 0.000208842125 ETHSending transactions [0 - 0].⠁ [00:00:00] [#############################################################>-------------------------------------------------------------] 1/2 txes (0.7s)Waiting for receipts.⠉ [00:00:22] [#######################################################################################################################] 1/1 receipts (0.0s)phron✅ [Success]Hash: 0x2ad8994c12af74bdcb04873e13d97dc543a2fa7390c1e194732ab43ec828cb3bContract Address: 0xAEe1a769b10d03a6CeB4D9DFd3aBB2EF807ee6aaBlock: 6717135Paid: 0.000116937 ETH (935496 gas * 0.125 gwei)Sending transactions [1 - 1].⠉ [00:00:23] [###########################################################################################################################] 2/2 txes (0.0s)Waiting for receipts.⠉ [00:00:21] [#######################################################################################################################] 1/1 receipts (0.0s)phron✅ [Success]Hash: 0x3bfb4cee2be4269badc57e0053d8b4d94d9d57d7936ecaa1e13ac1e2199f3b12Contract Address: 0xeb1Ff38A645Eae4E64dFb93772D8129F88E11Ab1Block: 6717137Paid: 0.000035502 ETH (284016 gas * 0.125 gwei)ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.Total Paid: 0.000152439 ETH (1219512 gas * avg 0.125 gwei)// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "forge-std/Script.sol";
import {MyToken} from "../src/MyToken.sol";
import {Container} from "../src/Container.sol";
contract ContainerDeployScript is Script {
function run() public {
// Get the private key from the .env
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
// Make a new token
MyToken token = new MyToken(1000);
// Make a new container
new Container(token, 500);
vm.stopBroadcast();
}
}forge script Container.s.sol:ContainerDeployScript --broadcast --verify -vvvv --legacy --rpc-url phronmkdir hardhat && cd hardhatnpm init -ynpm install hardhatnpx hardhat initnpx hardhat init888 888 888 888 888888 888 888 888 888888 888 888 888 8888888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888888 888 "88b 888P" d88" 888 888 "88b "88b 888888 888 .d888888 888 888 888 888 888 .d888888 888888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
👷 Welcome to Hardhat v2.22.2 👷
What do you want to do? … Create a JavaScript project Create a TypeScript project Create a TypeScript project (with Viem) Quit/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: '0.8.20',
};module.exports = {
solidity: '0.8.20',
networks: {
phron: {
url: 'INSERT_RPC_API_ENDPOINT', // Insert your RPC URL here
chainId: 7744, // (hex: 0x504),
accounts: ['INSERT_PRIVATE_KEY'],
},
},
};npm install @nomicfoundation/hardhat-ethers ethersnpm install --save-dev @nomicfoundation/hardhat-ignition-ethers/** @type import('hardhat/config').HardhatUserConfig */
require('@nomicfoundation/hardhat-ethers');
require('@nomicfoundation/hardhat-ignition-ethers');
const privateKey = 'INSERT_PRIVATE_KEY';
module.exports = {
solidity: '0.8.20',
networks: {
phron: {
url: 'https://',
chainId: 1287, // 0x507 in hex,
accounts: [privateKey]
}
}
};mkdir contractstouch contracts/Box.sol// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
contract Box {
uint256 private value;
// Emitted when the stored value changes
event ValueChanged(uint256 newValue);
// Stores a new value in the contract
function store(uint256 newValue) public {
value = newValue;
emit ValueChanged(newValue);
}
// Reads the last stored value
function retrieve() public view returns (uint256) {
return value;
}
}npx hardhat compilenpx hardhat compileCompiled 1 Solidity files successfully (evm target: paris).ls -lartifactscachecontractshardhat.config.jsnode_modulespackage.jsonpackage-lock.jsonmkdir ignition ignition/modules && touch ignition/modules/Box.js// 1. Import the `buildModule` function from the Hardhat Ignition module
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
// 2. Export a module using `buildModule`
module.exports = buildModule("BoxModule", (m) => {
// 3. Use the `getAccount` method to select the deployer account
const deployer = m.getAccount(0);
// 4. Deploy the `Box` contract
const box = m.contract("Box", [], {
from: deployer,
});
// 5. Return an object from the module
return { box };
});npx hardhat ignition deploy ./ignition/modules/Box.js --network phronnpx hardhat ignition deploy ./ignition/modules/Box.js --network phron
✅ Confirm deploy to network phron (7744)? … yesHardhat Ignition 🚀
Deploying [ BoxModule ]
Batch #1Executed BoxModule#Box
[ BoxModule ] successfully deployed 🚀
Deployed Addresses
BoxModule#Box - 0xfBD78CE8C9E1169851119754C4Ea2f70AB159289npx hardhat console --network phronconst Box = await ethers.getContractFactory('Box');const box = await Box.attach('0xfBD78CE8C9E1169851119754C4Ea2f70AB159289');await box.store(5);npx hardhat console --network phron
Welcome to Node.js v20.9.0.Type ".help" for more information. const Box = await ethers.getContractFactory('Box');undefined
const box = await Box.attach('0xfBD78CE8C9E1169851119754C4Ea2f70AB159289');undefined
await box.store(5);ContractTransactionResponse {
provider: HardhatEthersProvider { ... },
blockNumber: null,
blockHash: null,
index: undefined,
hash: '0x1c49a64a601fc5dd184f0a368a91130cb49203ec0f533c6fcf20445c68e20264',
type: 2,
to: '0xa84caB60db6541573a091e5C622fB79e175E17be',
from: '0x3B939FeaD1557C741Ff06492FD0127bd287A421e',
nonce: 87,
gasLimit: 45881n,
gasPrice: 1107421875n,
maxPriorityFeePerGas: 1n,
maxFeePerGas: 1107421875n,
data: '0x6057361d0000000000000000000000000000000000000000000000000000000000000005',
value: 0n,
chainId: 5678n,
signature: Signature { r: "0x9233b9cc4ae6879b7e08b9f1a4bfb175c8216eee0099966eca4a305c7f369ecc", s: "0x7663688633006b5a449d02cb08311569fadf2f9696bd7fe65417860a3b5fc57d", yParity: 0, networkV: null },
accessList: [],
blobVersionedHashes: null
} await box.retrieve();5nawait box.retrieve();mkdir scripts && touch scripts/set-value.js// scripts/set-value.js
async function main() {
// Create instance of the Box contract
const Box = await ethers.getContractFactory('Box');
// Connect the instance to the deployed contract
const box = await Box.attach('0xfBD78CE8C9E1169851119754C4Ea2f70AB159289');
// Store a new value
await box.store(2);
// Retrieve the value
const value = await box.retrieve();
console.log(`The new value is: ${value}`);
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});npx hardhat run --network phron scripts/set-value.jsnpx hardhat run --network phron scripts/set-value.js
The new value is: 2Error HH604: Error running JSON-RPC server: Invalid JSON-RPC response's result.
Errors: Invalid value null supplied to : RpcBlockWithTransactions | null/transactions: RpcTransaction Array/0: RpcTransaction/accessList: Array<{ address: DATA, storageKeys: Array<DATA> | null }> | undefined, Invalid value null supplied to : RpcBlockWithTransactions | null/transactions: RpcTransaction Array/1: RpcTransaction/accessList: Array<{ address: DATA, storageKeys: Array<DATA> | null }> | undefined, Invalid value null supplied to : RpcBlockWithTransactions | null/transactions: RpcTransaction Array/2: RpcTransaction/accessList: Array<{ address: DATA, storageKeys: Array<DATA> | null }> | undefined addAccessList(method, rawResult) {
if (
method.startsWith('eth_getBlock') &&
rawResult &&
rawResult.transactions?.length
) {
rawResult.transactions.forEach((t) => {
if (t.accessList == null) t.accessList = [];
});
}
}
async _perform(method, params, tType, getMaxAffectedBlockNumber) {
const cacheKey = this._getCacheKey(method, params);
const cachedResult = this._getFromCache(cacheKey);
if (cachedResult !== undefined) {
return cachedResult;
}
if (this._forkCachePath !== undefined) {
const diskCachedResult = await this._getFromDiskCache(
this._forkCachePath,
cacheKey,
tType
);
if (diskCachedResult !== undefined) {
this._storeInCache(cacheKey, diskCachedResult);
return diskCachedResult;
}
}
const rawResult = await this._send(method, params);
this.addAccessList(method, rawResult);
const decodedResult = (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(
rawResult,
tType
);
const blockNumber = getMaxAffectedBlockNumber(decodedResult);
if (this._canBeCached(blockNumber)) {
this._storeInCache(cacheKey, decodedResult);
if (this._forkCachePath !== undefined) {
await this._storeInDiskCache(this._forkCachePath, cacheKey, rawResult);
}
}
return decodedResult;
}
async _performBatch(batch, getMaxAffectedBlockNumber) {
// Perform Batch caches the entire batch at once.
// It could implement something more clever, like caching per request
// but it's only used in one place, and those other requests aren't
// used anywhere else.
const cacheKey = this._getBatchCacheKey(batch);
const cachedResult = this._getFromCache(cacheKey);
if (cachedResult !== undefined) {
return cachedResult;
}
if (this._forkCachePath !== undefined) {
const diskCachedResult = await this._getBatchFromDiskCache(
this._forkCachePath,
cacheKey,
batch.map((b) => b.tType)
);
if (diskCachedResult !== undefined) {
this._storeInCache(cacheKey, diskCachedResult);
return diskCachedResult;
}
}
const rawResults = await this._sendBatch(batch);
const decodedResults = rawResults.map((result, i) => {
this.addAccessList(batch[i].method, result);
return (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(
result,
batch[i].tType
);
});
const blockNumber = getMaxAffectedBlockNumber(decodedResults);
if (this._canBeCached(blockNumber)) {
this._storeInCache(cacheKey, decodedResults);
if (this._forkCachePath !== undefined) {
await this._storeInDiskCache(this._forkCachePath, cacheKey, rawResults);
}
}
return decodedResults;
}npx patch-package hardhatnpx hardhat node --fork INSERT_RPC_API_ENDPOINT...
networks: {
hardhat: {
forking: {
url: 'INSERT_RPC_API_ENDPOINT',
},
},
},
...Private Key: Oxdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97Account #9: Oxa0Ee7A142d267C1f36714E4a8F75612F20a79720 (10000 ETH)Private Key: 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6Account #10: OxBcd4042DE499D14e55001CcbB24a551F3b954096 (10000 ETH)Private Key: Oxf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897Account #11: 0x71bE63f3384f5fb98995898A86B02Fb2426c5788 (10000 ETH)Private Key: 0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82Account #12: OxFABBOac9d68B0B445fB7357272F202C5651694a (10000 ETH)Private Key: Oxa267530f49f8280200edf313ee7af6b827f2a8bce2897751d06a843f644967b1Account #13: 0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec (10000 ETH)Private Key: 0x47c99abed3324a2707c28affff1267e45918ec8c3f20b8aa892e8b065d2942ddAccount #14: OxdF3e18d64BC6A983f673Ab319CCaE4f1a5707097 (10000 ETH)Private Key: Oxc526ee95bf44d8fc405a158bb884d9d1238d990612e9f33d006bb0789009aaaAccount #15: Oxcd3B766CCDd6AE721141F452C550Ca635964ce71 (10000 ETH)Private Key: 0x8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61Account #16: 0×2546BcD3c84621e976D8185a91A922aE77ECEc30 (10000 ETH)Private Key: Oxea6c44ac03bff858b476bba40716402b03e41b8e97e276d1baec7c37d42484a0Account #17: OxbDA5747bFD65F08deb54cb465eB87D40e51B197E (10000 ETH)Private Key: 0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b06166765a93e037fdAccount #18: OxdD2FD4581271e230360230F9337D5c0430Bf44C0 (10000 ETH)Private Key: Oxde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0Account #19: 0×8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199 (10000 ETH)Private Key: Oxdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656eWARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.curl --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545 const hre = require('hardhat');
async function main() {
const provider = new ethers.JsonRpcProvider(
'http://127.0.0.1:8545/'
);
const contract = new ethers.Contract(
'INSERT_CONTRACT_ADDRESS',
'INSERT_CONTRACT_ABI',
provider
);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});





















viem is a modular TypeScript library that allows developers to interact with abstractions over the JSON-RPC API, making it easy to interact with Ethereum nodes. Since Phron has an Ethereum-like API available that is fully compatible with Ethereum-style JSON RPC invocations, developers can leverage this compatibility to interact with Phron nodes. For more information on viem, check out their .
In this guide, you'll learn how to use viem to send a transaction and deploy a contract on the Phron TestNet.
For the examples in this guide, you will need to have the following:
An account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
NoteThe examples in this guide assume you have a MacOS or Ubuntu 22.04-based environment and will need to be adapted accordingly for Windows.
To get started, you'll need to create a basic TypeScript project. First, create a directory to store all of the files you'll be creating throughout this guide, and initialize the project with the following command:
For this guide, you'll need to install the viem library and the Solidity compiler. To install both packages, you can run the following command:
You can create a TypeScript configuration file by running:
NoteThis tutorial was created using Node.js v18.18.0.
Throughout this guide, you'll be creating a bunch of scripts that provide different functionality, such as sending a transaction, deploying a contract, and interacting with a deployed contract. In most of these scripts, you'll need to create a to interact with the network.
You can create a viem client for reading chain data, like balances or contract data, using the createPublicClient function, or you can create a viem client for writing chain data, like sending transactions, using the createWalletClient function.
To create a client for reading chain data, you can take the following steps:
Import the createPublicClient and http functions from viem and the network you want to interact with from viem/chains. The chain can be any of the following: phron
Create the client using the createPublicClient function and pass in the network and the HTTP RPC endpoint
To create a client for writing chain data, you can take the following steps:
Import the createWalletClient and http functions from viem, the privateKeyToAccount function for loading your accounts via their private keys, and the network you want to interact with from viem/chains. The chain can be any of the following: phron
Create your account using the privateKeyToAccount function
NoteTo interact with browser-based wallets, you can use the following code to create an account:
During this section, you'll be creating a couple of scripts. The first one will be to check the balances of your accounts before trying to send a transaction. The second script will actually send the transaction.
You can also use the balance script to check the account balances after the transaction has been sent.
You'll only need one file to check the balances of both addresses before and after the transaction is sent. To get started, you can create a balances.ts file by running:
Next, you will create the script for this file and complete the following steps:
Update your imports to include the createPublicClient, http, and formatEther functions from viem and the network you want to interact with from viem/chains
Set up a public viem client, which can be used for reading chain data, such as account balances
To run the script and fetch the account balances, you can run the following command:
If successful, the balances for the origin and receiving address will be displayed in your terminal in DEV.
You'll only need one file to execute a transaction between accounts. For this example, you'll be transferring 1 DEV token from an origin address (from which you hold the private key) to another address. To get started, you can create a transaction.ts file by running:
Next, you will create the script for this file and complete the following steps:
Update your imports to include the createWalletClient, http, and parseEther functions from viem, the privateKeyToAccount function from viem/accounts, and the network you want to interact with from viem/chains
Set up a viem wallet client for writing chain data, which can be used along with your private key to send transactions. Note: This is for example purposes only. Never store your private keys in a TypeScript file
To run the script, you can run the following command in your terminal:
If the transaction was successful, in your terminal you'll see the transaction hash has been printed out.
NoteViem requires that you prepend your private key with
0x. Many wallets omit this0x, so verify you've included it as you replaceINSERT_PRIVATE_KEY.
You can also use the balances.ts script to check that the balances for the origin and receiving accounts have changed. The entire workflow would look like this:
The contract you'll be compiling and deploying in the next couple of sections is a simple incrementer contract, arbitrarily named Incrementer.sol. You can get started by creating a file for the contract:
Next, you can add the Solidity code to the file:
The constructor function, which runs when the contract is deployed, sets the initial value of the number variable stored on-chain (the default is 0). The increment function adds the _value provided to the current number, but a transaction needs to be sent, which modifies the stored data. Lastly, the reset function resets the stored value to zero.
NoteThis contract is a simple example for illustration purposes only and does not handle values wrapping around.
In this section, you'll create a script that uses the Solidity compiler to output the bytecode and interface (ABI) for the Incrementer.sol contract. To get started, you can create a compile.ts file by running:
Next, you will create the script for this file and complete the following steps:
Import the fs and solc packages
Using the fs.readFileSync function, you'll read and save the file contents of Incrementer.sol to source
Build the input
With the script for compiling the Incrementer.sol contract in place, you can then use the results to send a signed transaction that deploys it. To do so, you can create a file for the deployment script called deploy.ts:
Next, you will create the script for this file and complete the following steps:
Update your imports to include the createPublicClient, createWalletClient, and http functions from viem, the privateKeyToAccount function from viem/accounts, the network you want to interact with from viem/chains, and the contractFile from the compile.ts file you created in the Compile Contract Script section
To run the script, you can enter the following command into your terminal:
If successful, the contract's address will be displayed in the terminal.
Call methods are the type of interaction that doesn't modify the contract's storage (change variables), meaning no transaction needs to be sent. They simply read various storage variables of the deployed contract.
To get started, you can create a file and name it get.ts:
Then you can take the following steps to create the script:
Update your imports to include the createPublicClient and http functions from viem, the network you want to interact with from viem/chains, and the contractFile from the compile.ts file you created in the Compile Contract Script section
Set up a public viem client for reading chain data, which will be used to read the current number of the Incrementer contract
To run the script, you can enter the following command in your terminal:
If successful, the value will be displayed in the terminal.
Send methods are the type of interactions that modify the contract's storage (change variables), meaning a transaction needs to be signed and sent. In this section, you'll create two scripts: one to increment and one to reset the incrementer. To get started, you can create a file for each script and name them increment.ts and reset.ts:
Open the increment.ts file and take the following steps to create the script:
Update your imports to include the createWalletClient and http functions from viem, the network you want to interact with from viem/chains, and the contractFile from the compile.ts file you created in the Compile Contract Script section
Set up a viem wallet client for writing chain data, which will be used along with your private key to send a transaction. Note: This is for example purposes only. Never store your private keys in a TypeScript file
To run the script, you can enter the following command in your terminal:
If successful, the transaction hash will be displayed in the terminal. You can use the get.ts script alongside the increment.ts script to make sure that value is changing as expected.
Next, you can open the reset.ts file and take the following steps to create the script:
Update your imports to include the createWalletClient and http functions from viem, the network you want to interact with from viem/chains, and the contractFile from the compile.ts file you created in the Compile Contract Script section
Set up a viem wallet client for writing chain data, which will be used along with your private key to send a transaction. Note: This is for example purposes only. Never store your private keys in a TypeScript file
To run the script, you can enter the following command in your terminal:
If successful, the transaction hash will be displayed in the terminal. You can use the get.ts script alongside the reset.ts script to make sure that value is changing as expected.
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.





Create the client using the createWalletClient function and pass in the account, network, and the HTTP RPC endpoint
addressFrom and addressTo variablesCreate the asynchronous balances function that wraps the publicClient.getBalance method
Use the publicClient.getBalance function to fetch the balances for the addressFrom and addressTo addresses. You can also leverage the formatEther function to transform the balance into a more readable number (in GLMR, MOVR, or DEV)
Lastly, run the balances function
Set up a public viem client for reading chain data, which will be used to wait for the transaction receipt
Define the addressTo variable
Create the asynchronous send function, which wraps the transaction object and the walletClient.sendTransaction method
Use the walletClient.sendTransaction function to sign and send the transaction. You'll need to pass in the transaction object, which only requires the recipient's address and the amount to send. Note that parseEther can be used, which handles the necessary unit conversions from Ether to Wei, similar to using parseUnits(value, decimals). Use await to wait until the transaction is processed and the transaction hash is returned
Use the publicClient.waitForTransactionReceipt function to wait for the transaction receipt, signaling that the transaction has been completed. This is particularly helpful if you need the transaction receipt or if you're running the balances.ts script directly after this one to check if the balances have been updated as expected
Lastly, run the send function
languagesourcessettingsUsing the input object, you can compile the contract using solc.compile
Extract the compiled contract file and export it to be used in the deployment script
Set up a viem wallet client for writing chain data, which will be used along with your private key to deploy the Incrementer contract. Note: This is for example purposes only. Never store your private keys in a TypeScript file
Set up a public viem client for reading chain data, which will be used to read the transaction receipt for the deployment
Load the contract bytecode and abi for the compiled contract
Create the asynchronous deploy function that will be used to deploy the contract via the walletClient.deployContract method
Use the walletClient.deployContract function to sign and send the transaction. You'll need to pass in the contract's ABI and bytecode, the account to deploy the transaction from, and the initial value for the incrementer. Use await to wait until the transaction is processed and the transaction hash is returned
Use the publicClient.readContract function to get the transaction receipt for the deployment. Use await to wait until the transaction is processed and the contract address is returned
Lastly, run the deploy function
Create the contractAddress variable using the address of the deployed contract and the abi variable using the contractFile from the compile.ts file
Create the asynchronous get function
Call the contract using the publicClient.readContract function, passing in the abi, the name of the function, the contractAddress, and any arguments (if needed). You can use await, which will return the value requested once the request promise resolves
Lastly, call the get function
Set up a public viem client for reading chain data, which will be used to wait for the transaction receipt
Create the contractAddress variable using the address of the deployed contract, the abi variable using the contractFile from the compile.ts file, and the _value to increment the contract by
Create the asynchronous increment function
Call the contract using the walletClient.writeContract function, passing in the abi, the name of the function, the contractAddress, and the _value. You can use await, which will return the transaction hash once the request promise resolves
Use the publicClient.waitForTransactionReceipt function to wait for the transaction receipt, signaling that the transaction has been completed. This is particularly helpful if you need the transaction receipt or if you're running the get.ts script directly after this one to check that the current number has been updated as expected
Lastly, call the increment function
Set up a public viem client for reading chain data, which will be used to wait for the transaction receipt
Create the contractAddress variable using the address of the deployed contract and the abi variable using the contractFile from the compile.ts file to increment the contract by
Create the asynchronous reset function
Call the contract using the walletClient.writeContract function, passing in the abi, the name of the function, the contractAddress, and an empty array for the arguments. You can use await, which will return the transaction hash once the request promise resolves
Use the publicClient.waitForTransactionReceipt function to wait for the transaction receipt, signaling that the transaction has been completed. This is particularly helpful if you need the transaction receipt or if you're running the get.ts script directly after this one to check that the current number has been reset to 0
Lastly, call the reset function
mkdir viem-examples && cd viem-examples && npm init --ynpm install typescript ts-node viem [email protected]npx tsc --initimport { createPublicClient, http } from 'viem';
import { phron } from 'viem/chains';
const rpcUrl = 'INSERT_RPC_API_ENDPOINT'
const publicClient = createPublicClient({
chain: phron,
transport: http(rpcUrl),
});import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { phron } from 'viem/chains';
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'INSERT_RPC_API_ENDPOINT'
const walletClient = createWalletClient({
account,
chain: phron,
transport: http(rpcUrl),
});const [account] = await window.ethereum.request({
method: 'eth_requestAccounts',
});
const walletClient = createWalletClient({
account,
chain: phron,
transport: custom(window.ethereum),
});const [account] = await window.ethereum.request({
method: 'eth_requestAccounts',
});
const walletClient = createWalletClient({
account,
chain: phron,
transport: custom(window.ethereum),
});touch balances.ts// 1. Imports
import { createPublicClient, http, formatEther } from 'viem';
import { phron } from 'viem/chains';
// 2. Create a public client for reading chain data
const rpcUrl = 'https://testnet.phron.ai';
const publicClient = createPublicClient({
chain: phron,
transport: http(rpcUrl),
});
// 3. Create address variables
const addressFrom = 'INSERT_FROM_ADDRESS';
const addressTo = 'INSERT_TO_ADDRESS';
// 4. Create balances function
const balances = async () => {
// 5. Fetch balances
const balanceFrom = formatEther(
await publicClient.getBalance({ address: addressFrom })
);
const balanceTo = formatEther(
await publicClient.getBalance({ address: addressTo })
);
console.log(`The balance of ${addressFrom} is: ${balanceFrom} DEV`);
console.log(`The balance of ${addressTo} is: ${balanceTo} DEV`);
};
// 6. Call the balances function
balances();npx ts-node balances.tsnpx ts-node balances.tsThe balance of 0x3B939FeaD1557C741Ff06492FD0127bd287A421e is: 3601.72 DEVThe balance of 0x78F34038c82638E0563b974246D421154C26b004 is: 0 DEVtouch transaction.ts// 1. Imports
import { createPublicClient, createWalletClient, http, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { phron } from 'viem/chains';
// 2. Create a wallet client for writing chain data
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'https://testnet.phron.ai';
const walletClient = createWalletClient({
account,
chain: phron,
transport: http(rpcUrl),
});
// 3. Create a public client for reading chain data
const publicClient = createPublicClient({
chain: phron,
transport: http(rpcUrl),
});
// 4. Create to address variable
const addressTo = 'INSERT_ADDRESS';
// 5. Create send function
const send = async () => {
console.log(
`Attempting to send transaction from ${account.address} to ${addressTo}`
);
// 6. Sign and send tx
const hash = await walletClient.sendTransaction({
to: addressTo,
value: parseEther('1'),
});
// 7. Wait for the transaction receipt
await publicClient.waitForTransactionReceipt({
hash,
});
console.log(`Transaction successful with hash: ${hash}`);
};
// 8. Call the send function
send();npx ts-node transaction.tsnpx ts-node balances.tsThe balance of 0x3B939FeaD1557C741Ff06492FD0127bd287A421e is: 3601.72 DEVThe balance of 0x78F34038c82638E0563b974246D421154C26b004 is: 0 DEV
npx ts-node transaction.ts Attempting to send transaction from 0x3B939FeaD1557C741Ff06492FD0127bd287A421e to 0x78F34038c82638E0563b974246D421154C26b004 Transaction successful with hash: 0xc482d907b2ae4ca1202c6cc5b486694b8439a9853caad9c2cdafec39defa1968 npx ts-node balances.ts The balance of 0x3B939FeaD1557C741Ff06492FD0127bd287A421e is: 3600.72 DEV The balance of 0x78F34038c82638E0563b974246D421154C26b004 is: 1 DEVtouch Incrementer.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Incrementer {
uint256 public number;
constructor(uint256 _initialNumber) {
number = _initialNumber;
}
function increment(uint256 _value) public {
number = number + _value;
}
function reset() public {
number = 0;
}
}touch compile.ts// 1. Import packages
const fs = require('fs');
const solc = require('solc');
// 2. Get path and load contract
const source = fs.readFileSync('Incrementer.sol', 'utf8');
// 3. Create input object
const input = {
language: 'Solidity',
sources: {
'Incrementer.sol': {
content: source,
},
},
settings: {
outputSelection: {
'*': {
'*': ['*'],
},
},
},
};
// 4. Compile the contract
const tempFile = JSON.parse(solc.compile(JSON.stringify(input)));
const contractFile = tempFile.contracts['Incrementer.sol']['Incrementer'];
// 5. Export contract data
export default contractFile;touch deploy.ts// 1. Update imports
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { phron } from 'viem/chains';
import contractFile from './compile';
// 2. Create a wallet client for writing chain data
// The private key must be prepended with `0x` to avoid errors
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'https://testnet.phron.ai';
const walletClient = createWalletClient({
account,
chain: phron,
transport: http(rpcUrl),
});
// 3. Create a public client for reading chain data
const publicClient = createPublicClient({
chain: phron,
transport: http(rpcUrl),
});
// 4. Load contract information
const bytecode = contractFile.evm.bytecode.object;
const abi = contractFile.abi;
const _initialNumber = 5;
// 5. Create deploy function
const deploy = async () => {
console.log(`Attempting to deploy from account: ${account.address}`);
// 6. Send tx (initial value set to 5)
const contract = await walletClient.deployContract({
abi,
account,
bytecode,
args: [_initialNumber],
});
// 7. Get the transaction receipt for the deployment
const transaction = await publicClient.waitForTransactionReceipt({
hash: contract,
});
console.log(`Contract deployed at address: ${transaction.contractAddress}`);
};
// 8. Call the deploy function
deploy();npx ts-node deploy.tsnpx ts-node deploy.tsAttempting to deploy from account: 0x3B939FeaD1557C741Ff06492FD0127bd287A421eContract deployed at address: 0x4503b1086c6780e888194fd9caebca5f65b210c9touch get.ts// 1. Update imports
import { createPublicClient, http } from 'viem';
import { phron } from 'viem/chains';
import contractFile from './compile';
// 2. Create a public client for reading chain data
const rpcUrl = 'https://testnet.phron.ai';
const client = createPublicClient({
chain: phron,
transport: http(rpcUrl),
});
// 3. Create contract variables
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const abi = contractFile.abi;
// 4. Create get function
const get = async () => {
console.log(`Making a call to contract at address: ${contractAddress}`);
// 5. Call contract
const data = await client.readContract({
abi,
functionName: 'number',
address: contractAddress,
args: [],
});
console.log(`The current number stored is: ${data}`);
};
// 6. Call get function
get();npx ts-node get.tsnpx ts-node get.tsMaking a call to contract at address: 0x4503b1086c6780e888194fd9caebca5f65b210c9The current number stored is: 5touch increment.ts reset.ts// 1. Update imports
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { phron } from 'viem/chains';
import contractFile from './compile';
// 2. Create a wallet client for writing chain data
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'https://testnet.phron.ai';
const walletClient = createWalletClient({
account,
chain: phron,
transport: http(rpcUrl),
});
// 3. Create a public client for reading chain data
const publicClient = createPublicClient({
chain: phron,
transport: http(rpcUrl),
});
// 4. Create contract variables
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const abi = contractFile.abi;
const _value = 3;
// 5. Create increment function
const increment = async () => {
console.log(
`Calling the increment by ${_value} function in contract at address: ${contractAddress}`
);
// 6. Call contract
const hash = await walletClient.writeContract({
abi,
functionName: 'increment',
address: contractAddress,
args: [_value],
});
// 7. Wait for the transaction receipt
await publicClient.waitForTransactionReceipt({
hash,
});
console.log(`Tx successful with hash: ${hash}`);
};
// 8. Call increment function
increment();npx ts-node increment.tsnpx ts-node get.tsMaking a call to contract at address: 0x4503b1086c6780e888194fd9caebca5f65b210c9The current number stored is: 5npx ts-node increment.tsCalling the increment by 3 function in contract at address: 0x4503b1086c6780e888194fd9caebca5f65b210c9Tx successful with hash: 0x041c9767e7a96f60f372341647430560569fd6ff64a27b4b9c6241e55dde57e1npx ts-node get.tsMaking a call to contract at address: 0x4503b1086c6780e888194fd9caebca5f65b210c9The current number stored is: 8// 1. Update imports
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { phron } from 'viem/chains';
import contractFile from './compile';
// 2. Create a wallet client for writing chain data
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'https://testnet.phron.ai';
const walletClient = createWalletClient({
account,
chain: phron,
transport: http(rpcUrl),
});
// 3. Create a public client for reading chain data
const publicClient = createPublicClient({
chain: phron,
transport: http(rpcUrl),
});
// 4. Create contract variables
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const abi = contractFile.abi;
// 5. Create reset function
const reset = async () => {
console.log(
`Calling the reset function in contract at address: ${contractAddress}`
);
// 6. Call contract
const hash = await walletClient.writeContract({
abi,
functionName: 'reset',
address: contractAddress,
args: [],
});
// 7. Wait for the transaction receipt
await publicClient.waitForTransactionReceipt({
hash,
});
console.log(`Tx successful with hash: ${hash}`);
};
// 8. Call reset function
reset();npx ts-node reset.tsnpx ts-node get.tsMaking a call to contract at address: 0x4503b1086c6780e888194fd9caebca5f65b210c9The current number stored is: 8npx ts-node reset.tsCalling the reset function in contract at address: 0x4503b1086c6780e888194fd9caebca5f65b210c9Tx successful with hash: 0xc1a772131ccf6a03675ff3e88798a6e70a99e145eeb0e98170ff2e3345ee14a7npx ts-node get.tsMaking a call to contract at address: 0x4503b1086c6780e888194fd9caebca5f65b210c9The current number stored is: 0






The Ethers.js library provides a set of tools to interact with Ethereum Nodes with JavaScript, similar to Web3.js. Phron has an Ethereum-like API available that is fully compatible with Ethereum-style JSON-RPC invocations. Therefore, developers can leverage this compatibility and use the Ethers.js library to interact with a Phron node as if they were doing so on Ethereum. For more information on Ethers.js, check their .
In this guide, you'll learn how to use the Ethers.js library to send a transaction and deploy a contract on Phron.
For the examples in this guide, you will need to have the following:
An account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
NoteThe examples in this guide assume you have a MacOS or Ubuntu 22.04-based environment and will need to be adapted accordingly for Windows.
To get started, you'll need to start a basic JavaScript project. First, create a directory to store all of the files you'll be creating throughout this guide and initialize the project with the following command:
For this guide, you'll need to install the Ethers.js library and the Solidity compiler. To install both NPM packages, you can run the following command:
Throughout this guide, you'll be creating a bunch of scripts that provide different functionality such as sending a transaction, deploying a contract, and interacting with a deployed contract. In most of these scripts you'll need to create an to interact with the network.
To configure your project for Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
To create a provider, you can take the following steps:
Import the ethers library
Define the providerRPC object, which can include the network configurations for any of the networks you want to send a transaction on. You'll include the name, rpc, and chainId for each network
Save this code snippet as you'll need it for the scripts that are used in the following sections.
During this section, you'll be creating a couple of scripts. The first one will be to check the balances of your accounts before trying to send a transaction. The second script will actually send the transaction.
You can also use the balance script to check the account balances after the transaction has been sent.
You'll only need one file to check the balances of both addresses before and after the transaction is sent. To get started, you can create a balances.js file by running:
Next, you will create the script for this file and complete the following steps:
Set up the Ethers provider
Define the addressFrom and addressTo variables
Create the asynchronous balances function which wraps the provider.getBalance method
To run the script and fetch the account balances, you can run the following command:
If successful, the balances for the origin and receiving address will be displayed in your terminal in DEV.
You'll only need one file for executing a transaction between accounts. For this example, you'll be transferring 1 DEV token from an origin address (from which you hold the private key) to another address. To get started, you can create a transaction.js file by running:
Next, you will create the script for this file and complete the following steps:
Set up the Ethers provider
Define the privateKey and the addressTo variables. The private key is required to create a wallet instance. Note: This is for example purposes only. Never store your private keys in a JavaScript file
Create a wallet using the privateKey and provider from the previous steps. The wallet instance is used to sign transactions
To run the script, you can run the following command in your terminal:
If the transaction was succesful, in your terminal you'll see the transaction hash has been printed out.
You can also use the balances.js script to check that the balances for the origin and receiving accounts have changed. The entire workflow would look like this:
The contract you'll be compiling and deploying in the next couple of sections is a simple incrementer contract, arbitrarily named Incrementer.sol. You can get started by creating a file for the contract:
Next, you can add the Solidity code to the file:
The constructor function, which runs when the contract is deployed, sets the initial value of the number variable stored on-chain (the default is 0). The increment function adds the _value provided to the current number, but a transaction needs to be sent, which modifies the stored data. Lastly, the reset function resets the stored value to zero.
NoteThis contract is a simple example for illustration purposes only and does not handle values wrapping around.
In this section, you'll create a script that uses the Solidity compiler to output the bytecode and interface (ABI) for the Incrementer.sol contract. To get started, you can create a compile.js file by running:
Next, you will create the script for this file and complete the following steps:
Import the fs and solc packages
Using the fs.readFileSync function, you'll read and save the file contents of Incrementer.sol to source
Build the input
With the script for compiling the Incrementer.sol contract in place, you can then use the results to send a signed transaction that deploys it. To do so, you can create a file for the deployment script called deploy.js:
Next, you will create the script for this file and complete the following steps:
Import the contract file from compile.js
Set up the Ethers provider
Define the privateKey for the origin account. The private key is required to create a wallet instance. Note: This is for example purposes only. Never store your private keys in a JavaScript file
To run the script, you can enter the following command into your terminal:
If successful, the contract's address will be displayed in the terminal.
Call methods are the type of interaction that don't modify the contract's storage (change variables), meaning no transaction needs to be sent. They simply read various storage variables of the deployed contract.
To get started, you can create a file and name it get.js:
Then you can take the following steps to create the script:
Import the abi from the compile.js file
Set up the Ethers provider
Create the contractAddress variable using the address of the deployed contract
To run the script, you can enter the following command in your terminal:
If successful, the value will be displayed in the terminal.
Send methods are the type of interaction that modify the contract's storage (change variables), meaning a transaction needs to be signed and sent. In this section, you'll create two scripts: one to increment and one to reset the incrementer. To get started, you can create a file for each script and name them increment.js and reset.js:
Open the increment.js file and take the following steps to create the script:
Import the abi from the compile.js file
Set up the Ethers provider
Define the privateKey for the origin account, the contractAddress of the deployed contract, and the _value to increment by. The private key is required to create a wallet instance.
To run the script, you can enter the following command in your terminal:
If successful, the transaction hash will be displayed in the terminal. You can use the get.js script alongside the increment.js script to make sure that value is changing as expected:
Next you can open the reset.js file and take the following steps to create the script:
Import the abi from the compile.js file
Set up the Ethers provider
Define the privateKey for the origin account and the contractAddress of the deployed contract. The private key is required to create a wallet instance. Note: This is for example purposes only. Never store your private keys in a JavaScript file
To run the script, you can enter the following command in your terminal:
If successful, the transaction hash will be displayed in the terminal. You can use the get.js script alongside the reset.js script to make sure that value is changing as expected:
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Web3.js is a set of libraries that allow developers to interact with Ethereum nodes using HTTP, IPC, or WebSocket protocols with JavaScript. Phron has an Ethereum-like API available that is fully compatible with Ethereum-style JSON-RPC invocations. Therefore, developers can leverage this compatibility and use the Web3.js library to interact with a Phron node as if they were doing so on Ethereum.
In this guide, you'll learn how to use the Web3.js library to send a transaction and deploy a contract on Phron.
For the examples in this guide, you will need to have the following:
An account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
NoteThe examples in this guide assume you have a MacOS or Ubuntu 22.04-based environment and will need to be adapted accordingly for Windows.
To get started, you'll need to start a basic JavaScript project. First, create a directory to store all of the files you'll be creating throughout this guide, and initialize the project with the following command:
For this guide, you'll need to install the Web3.js library and the Solidity compiler. To install both NPM packages, you can run the following command:
You can configure Web3.js to work with any of the Phron networks. To configure your project for Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
The simplest way to get started with each of the networks is as follows:
Save this code snippet, as you'll need it for the scripts that are used in the following sections.
During this section, you'll be creating a couple of scripts. The first one will be to check the balances of your accounts before trying to send a transaction. The second script will actually send the transaction.
You can also use the balance script to check the account balances after the transaction has been sent.
You'll only need one file to check the balances of both addresses before and after the transaction is sent. To get started, you can create a balances.js file by running:
Next, you will create the script for this file and complete the following steps:
Set up the Web3 provider
Define the addressFrom and addressTo variables
Create the asynchronous balances function, which wraps the web3.eth.getBalance method
To run the script and fetch the account balances, you can run the following command:
If successful, the balances for the origin and receiving address will be displayed in your terminal in DEV.
You'll only need one file to execute a transaction between accounts. For this example, you'll be transferring 1 DEV token from an origin address (from which you hold the private key) to another address. To get started, you can create a transaction.js file by running:
Next, you will create the script for this file and complete the following steps:
Set up the Web3 provider
Define the accountFrom, including the privateKey, and the addressTo variables. The private key is required to create a wallet instance. Note: This is for example purposes only. Never store your private keys in a JavaScript file
Create the asynchronous send function, which wraps the transaction object, and the sign and send transaction functions
To run the script, you can run the following command in your terminal:
If the transaction was successful, in your terminal, you'll see the transaction hash has been printed out.
You can also use the balances.js script to check that the balances for the origin and receiving accounts have changed. The entire workflow would look like this:
When sending a transaction with Web3.js, it is important that you have all of the required data for the transaction. You'll need to provide the from address or the nonce of the sender, the gas or gasLimit, and the gasPrice.
If you do not specify the from address or the nonce of the sender, you may receive the following error:
To fix this, simply add either the from or nonce field to the transaction object.
If you do not specify the gas correctly, you may receive the following error:
To resolve this error, you'll need to make sure that you've provided a gasPrice for the transaction. You can use await web3.eth.getGasPrice() to programmatically get this value.
The contract you'll be compiling and deploying in the next couple of sections is a simple incrementer contract, arbitrarily named Incrementer.sol. You can get started by creating a file for the contract:
Next, you can add the Solidity code to the file:
The constructor function, which runs when the contract is deployed, sets the initial value of the number variable stored on-chain (the default is 0). The increment function adds the _value provided to the current number, but a transaction needs to be sent, which modifies the stored data. Lastly, the reset function resets the stored value to zero.
NoteThis contract is a simple example for illustration purposes only and does not handle values wrapping around.
In this section, you'll create a script that uses the Solidity compiler to output the bytecode and interface (ABI) for the Incrementer.sol contract. To get started, you can create a compile.js file by running:
Next, you will create the script for this file and complete the following steps:
Import the fs and solc packages
Using the fs.readFileSync function, you'll read and save the file contents of Incrementer.sol to source
Build the input
With the script for compiling the Incrementer.sol contract in place, you can then use the results to send a signed transaction that deploys it. To do so, you can create a file for the deployment script called deploy.js:
Next, you will create the script for this file and complete the following steps:
Import the contract file from compile.js
Set up the Web3 provider
Define the accountFrom, including the privateKey, and the addressTo variables. The private key is required to create a wallet instance. Note: This is for example purposes only. Never store your private keys in a JavaScript file
To run the script, you can enter the following command into your terminal:
If successful, the contract's address will be displayed in the terminal.
Call methods are the type of interaction that doesn't modify the contract's storage (change variables), meaning no transaction needs to be sent. They simply read various storage variables of the deployed contract.
To get started, you can create a file and name it get.js:
Then you can take the following steps to create the script:
Import the abi from the compile.js file
Set up the Web3 provider
Create the contractAddress variable using the address of the deployed contract
To run the script, you can enter the following command in your terminal:
If successful, the value will be displayed in the terminal.
Send methods are the type of interaction that modifies the contract's storage (change variables), meaning a transaction needs to be signed and sent. In this section, you'll create two scripts: one to increment and one to reset the incrementer. To get started, you can create a file for each script and name them increment.js and reset.js:
Open the increment.js file and take the following steps to create the script:
Import the abi from the compile.js file
Set up the Web3 provider
Define the privateKey for the origin account, the contractAddress of the deployed contract, and the _value to increment by. The private key is required to create a wallet instance.
To run the script, you can enter the following command in your terminal:
If successful, the transaction hash will be displayed in the terminal. You can use the get.js script alongside the increment.js script to make sure that value is changing as expected:
Next, you can open the reset.js file and take the following steps to create the script:
Import the abi from the compile.js file
Set up the Web3 provider
Define the privateKey for the origin account and the contractAddress of the deployed contract. The private key is required to create a wallet instance. Note: This is for example purposes only. Never store your private keys in a JavaScript file
To run the script, you can enter the following command in your terminal:
If successful, the transaction hash will be displayed in the terminal. You can use the get.js script alongside the reset.js script to make sure that value is changing as expected:
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
providerethers.JsonRpcProviderUse the provider.getBalance function to fetch the balances for the addressFrom and addressTo addresses. You can also leverage the ethers.formatEther function to transform the balance into a more readable number in ETH
Lastly, run the balances function
Create the asynchronous send function which wraps the transaction object and the wallet.sendTransaction method
Create the transaction object which only requires the recipient's address and the amount to send. Note that ethers.parseEther can be used, which handles the necessary unit conversions from Ether to Wei - similar to using ethers.parseUnits(value, 'ether')
Send the transaction using the wallet.sendTransaction method and then use await to wait until the transaction is processed and the transaction receipt is returned
Lastly, run the send function
languagesourcessettingsUsing the input object, you can compile the contract using solc.compile
Extract the compiled contract file and export it to be used in the deployment script
privateKey and provider from the previous steps. The wallet instance is used to sign transactionsLoad the contract bytecode and abi for the compiled contract
Create a contract instance with signer using the ethers.ContractFactory function, providing the abi, bytecode, and wallet as parameters
Create the asynchronous deploy function that will be used to deploy the contract
Within the deploy function, use the incrementer contract instance to call deploy and pass in the initial value. For this example, you can set the initial value to 5. This will send the transaction for contract deployment. To wait for a transaction receipt you can use the deployed method of the contract deployment transaction
Lastly, run the deploy function
ethers.Contract function and passing in the contractAddress, abi, and providerCreate the asynchronous get function
Use the contract instance to call one of the contract's methods and pass in any inputs if necessary. For this example, you will call the number method which doesn't require any inputs. You can use await which will return the value requested once the request promise resolves
Lastly, call the get function
Create a wallet using the privateKey and provider from the previous steps. The wallet instance is used to sign transactions
Create an instance of the contract using the ethers.Contract function and passing in the contractAddress, abi, and provider
Create the asynchronous increment function
Use the contract instance to call one of the contract's methods and pass in any inputs if necessary. For this example, you will call the increment method which requires the value to increment by as an input. You can use await which will return the value requested once the request promise resolves
Lastly, call the increment function
Create a wallet using the privateKey and provider from the previous steps. The wallet instance is used to sign transactions
Create an instance of the contract using the ethers.Contract function and passing in the contractAddress, abi, and provider
Create the asynchronous reset function
Use the contract instance to call one of the contract's methods and pass in any inputs if necessary. For this example, you will call the reset method which doesn't require any inputs. You can use await which will return the value requested once the request promise resolves
Lastly, call the reset function
Use the web3.eth.getBalance function to fetch the balances for the addressFrom and addressTo addresses. You can also leverage the web3.utils.fromWei function to transform the balance into a more readable number in DEV
Lastly, run the balances function
Create and sign the transaction using the web3.eth.accounts.signTransaction function. Pass in the gas, addressTo, value, gasPrice, and nonce for the transaction along with the sender's privateKey
Send the signed transaction using the web3.eth.sendSignedTransaction method and pass in the raw transaction. Then use await to wait until the transaction is processed and the transaction receipt is returned
Lastly, run the send function
languagesourcessettingsUsing the input object, you can compile the contract using solc.compile
Extract the compiled contract file and export it to be used in the deployment script
Save the bytecode and abi for the compiled contract
Create the asynchronous deploy function that will be used to deploy the contract
Create the contract instance using the web3.eth.Contract function
Create the constructor and pass in the bytecode and the initial value for the incrementer. For this example, you can set the initial value to 5
Create and sign the transaction using the web3.eth.accounts.signTransaction function. Pass in the data, gas, gasPrice, and nonce for the transaction along with the sender's privateKey
Send the signed transaction using the web3.eth.sendSignedTransaction method and pass in the raw transaction. Then use await to wait until the transaction is processed and the transaction receipt is returned
Lastly, run the deploy function
web3.eth.Contract function and passing in the abi and contractAddressCreate the asynchronous get function
Use the contract instance to call one of the contract's methods and pass in any inputs if necessary. For this example, you will call the number method which doesn't require any inputs. You can use await, which will return the value requested once the request promise resolves
Lastly, call the get function
Create an instance of the contract using the web3.eth.Contract function and passing in the abi and contractAddress
Use the contract instance to build the increment transaction using the methods.increment function and passing in the _value as an input
Create the asynchronous increment function
Use the contract instance and the increment transaction you previously created to sign the transaction with the sender's private key. You'll use the web3.eth.accounts.signTransaction function and specify the to address, data, gas, gasPrice, and nonce for the transaction
Send the signed transaction using the web3.eth.sendSignedTransaction method and pass in the raw transaction. Then use await to wait until the transaction is processed and the transaction receipt is returned
Lastly, call the increment function
Create an instance of the contract using the web3.eth.Contract function and passing in the abi and contractAddress
Use the contract instance to build the reset transaction using the methods.reset function
Create the asynchronous reset function
Use the contract instance and the reset transaction you previously created to sign the transaction with the sender's private key. You'll use the web3.eth.accounts.signTransaction function and specify the to address, data, gas, gasPrice, and nonce for the transaction
Send the signed transaction using the web3.eth.sendSignedTransaction method and pass in the raw transaction. Then use await to wait until the transaction is processed and the transaction receipt is returned
Lastly, call the reset function
mkdir ethers-examples && cd ethers-examples && npm init --ynpm install ethers [email protected]// 1. Import ethers
const ethers = require('ethers');
// 2. Define network configurations
const providerRPC = {
phron: {
name: 'phron',
rpc: 'INSERT_RPC_API_ENDPOINT', // Insert your RPC URL here
chainId: 7744, // 0x504 in hex,
},
};
// 3. Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.phron.rpc, {
chainId: providerRPC.phron.chainId,
name: providerRPC.phron.name,
});touch balances.js// 1. Add the Ethers provider logic here:
// {...}
// 2. Create address variables
const addressFrom = 'INSERT_FROM_ADDRESS';
const addressTo = 'INSERT_TO_ADDRESS';
// 3. Create balances function
const balances = async () => {
// 4. Fetch balances
const balanceFrom = ethers.formatEther(await provider.getBalance(addressFrom));
const balanceTo = ethers.formatEther(await provider.getBalance(addressTo));
console.log(`The balance of ${addressFrom} is: ${balanceFrom} DEV`);
console.log(`The balance of ${addressTo} is: ${balanceTo} DEV`);
};
// 5. Call the balances function
balances();// Import ethers
const ethers = require('ethers');
// Define network configurations
const providerRPC = {
development: {
name: 'phron-development',
rpc: 'http://localhost:9944',
chainId: 7744,
},
phron: {
name: 'phron',
rpc: 'https://testnet.phron.ai',
chainId: 7744,
},
};
// Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.phron.rpc, {
chainId: providerRPC.phron.chainId,
name: providerRPC.phron.name,
}); // Change to correct network
// Define addresses
const addressFrom = 'INSERT_FROM_ADDRESS';
const addressTo = 'INSERT_TO_ADDRESS';
// Create balances function
const balances = async () => {
// Fetch balances
const balanceFrom = ethers.formatEther(
await provider.getBalance(addressFrom)
);
const balanceTo = ethers.formatEther(await provider.getBalance(addressTo));
console.log(`The balance of ${addressFrom} is: ${balanceFrom} DEV`);
console.log(`The balance of ${addressTo} is: ${balanceTo} DEV`);
};
// Call the balances function
balances();node balances.jstouch transaction.js// 1. Add the Ethers provider logic here:
// {...}
// 2. Create account variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const addressTo = 'INSERT_TO_ADDRESS';
// 3. Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// 4. Create send function
const send = async () => {
console.log(`Attempting to send transaction from ${wallet.address} to ${addressTo}`);
// 5. Create tx object
const tx = {
to: addressTo,
value: ethers.parseEther('1'),
};
// 6. Sign and send tx - wait for receipt
const createReceipt = await wallet.sendTransaction(tx);
await createReceipt.wait();
console.log(`Transaction successful with hash: ${createReceipt.hash}`);
};
// 7. Call the send function
send();// Import ethers
const ethers = require('ethers');
// Define network configurations
const providerRPC = {
development: {
name: 'phron-development',
rpc: 'http://localhost:9944',
chainId: 7744,
},
phronbase: {
name: 'phron',
rpc: 'https://testnet.phron.ai',
chainId: 7744,
},
};
// Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.phron.rpc, {
chainId: providerRPC.phron.chainId,
name: providerRPC.phron.name,
}); // Change to correct network
// Define accounts and wallet
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const addressTo = 'INSERT_TO_ADDRESS';
const wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// Create send function
const send = async () => {
console.log(
`Attempting to send transaction from ${wallet.address} to ${addressTo}`
);
// Create transaction
const tx = {
to: addressTo,
value: ethers.parseEther('1'),
};
// Send transaction and get hash
const createReceipt = await wallet.sendTransaction(tx);
await createReceipt.wait();
console.log(`Transaction successful with hash: ${createReceipt.hash}`);
};
// Call the send function
send();node transaction.jsnode balances.jsThe balance of 0x3B939FeaD1557C741Ff06492FD0127bd287A421e is: 3604.673685275447543445 DEVThe balance of 0xFFA0352d300cdd8aCdA5c947D87CbCc3f0B3485A is: 0 DEVnode transaction.jsAttempting to send transaction from 0x3B939FeaD1557C741Ff06492FD0127bd287A421e to 0xFFA0352d300cdd8aCdA5c947D87CbCc3f0B3485ATransaction successful with hash: 0x01e42c627fe79b1d5649a64d39fceec34aba3904e37d768e74ec71fcd62b897fnode balances.jsThe balance of 0x3B939FeaD1557C741Ff06492FD0127bd287A421e is: 3603.673682650447543445 DEVThe balance of 0xFFA0352d300cdd8aCdA5c947D87CbCc3f0B3485A is: 1.0 DEVtouch Incrementer.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Incrementer {
uint256 public number;
constructor(uint256 _initialNumber) {
number = _initialNumber;
}
function increment(uint256 _value) public {
number = number + _value;
}
function reset() public {
number = 0;
}
}touch compile.js// 1. Import packages
const fs = require('fs');
const solc = require('solc');
// 2. Get path and load contract
const source = fs.readFileSync('Incrementer.sol', 'utf8');
// 3. Create input object
const input = {
language: 'Solidity',
sources: {
'Incrementer.sol': {
content: source,
},
},
settings: {
outputSelection: {
'*': {
'*': ['*'],
},
},
},
};
// 4. Compile the contract
const tempFile = JSON.parse(solc.compile(JSON.stringify(input)));
const contractFile = tempFile.contracts['Incrementer.sol']['Incrementer'];
// 5. Export contract data
module.exports = contractFile;touch deploy.js// 1. Import the contract file
const contractFile = require('./compile');
// 2. Add the Ethers provider logic here:
// {...}
// 3. Create account variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
// 4. Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// 5. Load contract information
const bytecode = contractFile.evm.bytecode.object;
const abi = contractFile.abi;
// 6. Create contract instance with signer
const incrementer = new ethers.ContractFactory(abi, bytecode, wallet);
// 7. Create deploy function
const deploy = async () => {
console.log(`Attempting to deploy from account: ${wallet.address}`);
// 8. Send tx (initial value set to 5) and wait for receipt
const contract = await incrementer.deploy(5);
const txReceipt = await contract.deploymentTransaction().wait();
console.log(`Contract deployed at address: ${txReceipt.contractAddress}`);
};
// 9. Call the deploy function
deploy();// Import ethers and compile
const ethers = require('ethers');
const contractFile = require('./compile');
// Define network configurations
const providerRPC = {
development: {
name: 'phron-development',
rpc: 'http://localhost:9944',
chainId: 7744,
},
phronbase: {
name: 'phron',
rpc: 'https://testnet.phron.ai',
chainId: 7744,
},
};
// Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.phron.rpc, {
chainId: providerRPC.phron.chainId,
name: providerRPC.phron.name,
}); // Change to correct network
// Define accounts and wallet
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// Load contract info
const bytecode = contractFile.evm.bytecode.object;
const abi = contractFile.abi;
// Create contract instance with signer
const incrementer = new ethers.ContractFactory(abi, bytecode, wallet);
// Create deploy function
const deploy = async () => {
console.log(`Attempting to deploy from account: ${wallet.address}`);
// Send tx (initial value set to 5) and wait for receipt
const contract = await incrementer.deploy(5);
const txReceipt = await contract.deploymentTransaction().wait();
console.log(`Contract deployed at address: ${txReceipt.contractAddress}`);
};
// Call the deploy function
deploy();node deploy.jsnode deploy.jsAttempting to deploy from account: 0x3B939FeaD1557C741Ff06492FD0127bd287A421eContract deployed at address: 0x2B9c71fc2730B7353Dd3865ae26881Fa38FE598Atouch get.js// 1. Import the ABI
const { abi } = require('./compile');
// 2. Add the Ethers provider logic here:
// {...}
// 3. Contract address variable
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// 4. Create contract instance
const incrementer = new ethers.Contract(contractAddress, abi, provider);
// 5. Create get function
const get = async () => {
console.log(`Making a call to contract at address: ${contractAddress}`);
// 6. Call contract
const data = await incrementer.number();
console.log(`The current number stored is: ${data}`);
};
// 7. Call get function
get();// Import ethers and compile
const ethers = require('ethers');
const { abi } = require('./compile');
// Define network configurations
const providerRPC = {
development: {
name: 'phron-development',
rpc: 'http://localhost:9944',
chainId: 7744,
},
phronbase: {
name: 'phron',
rpc: 'https://testnet.phron.ai',
chainId: 7744,
},
};
const provider = new ethers.JsonRpcProvider(providerRPC.phron.rpc, {
chainId: providerRPC.phron.chainId,
name: providerRPC.phron.name,
}); // Change to correct network
// Contract address variable
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// Create contract instance
const incrementer = new ethers.Contract(contractAddress, abi, provider);
// Create get function
const get = async () => {
console.log(`Making a call to contract at address: ${contractAddress}`);
// Call contract
const data = await incrementer.number();
console.log(`The current number stored is: ${data}`);
};
// Call get function
get();node get.jstouch increment.js reset.js// 1. Import the contract ABI
const { abi } = require('./compile');
// 2. Add the Ethers provider logic here:
// {...}
// 3. Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const _value = 3;
// 4. Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// 5. Create contract instance with signer
const incrementer = new ethers.Contract(contractAddress, abi, wallet);
// 6. Create increment function
const increment = async () => {
console.log(
`Calling the increment by ${_value} function in contract at address: ${contractAddress}`
);
// 7. Sign and send tx and wait for receipt
const createReceipt = await incrementer.increment(_value);
await createReceipt.wait();
console.log(`Tx successful with hash: ${createReceipt.hash}`);
};
// 8. Call the increment function
increment();// Import ethers and compile
const ethers = require('ethers');
const { abi } = require('./compile');
// Define network configurations
const providerRPC = {
development: {
name: 'phron-development',
rpc: 'http://localhost:9944',
chainId: 7744,
},
phronbase: {
name: 'phron',
rpc: 'https://testnet.phron.ai',
chainId: 7744,
},
};
// Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.phron.rpc, {
chainId: providerRPC.phron.chainId,
name: providerRPC.phron.name,
}); // Change to correct network
// Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const _value = 3;
// Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// Create contract instance with signer
const incrementer = new ethers.Contract(contractAddress, abi, wallet);
// Create reset function
const increment = async () => {
console.log(
`Calling the increment by ${_value} function in contract at address: ${contractAddress}`
);
// Sign and send tx and wait for receipt
const createReceipt = await incrementer.increment(_value);
await createReceipt.wait();
console.log(`Tx successful with hash: ${createReceipt.hash}`);
};
// Call the reset function
increment();node increment.jsnode get.jsMaking a call to contract at address: 0x2B9c71fc2730B7353Dd3865ae26881Fa38FE598AThe current number stored is: 5node increment.jsCalling the increment by 3 function in contract at address: 0x2B9c71fc2730B7353Dd3865ae26881Fa38FE598ATx successful with hash: 0xc7fe935db03cfacf56c5649cd79a566d1a7b68417f904f0095a1b1c203875bf2node get.jsMaking a call to contract at address: 0x2B9c71fc2730B7353Dd3865ae26881Fa38FE598AThe current number stored is: 8// 1. Import the contract ABI
const { abi } = require('./compile');
// 2. Add the Ethers provider logic here:
// {...}
// 3. Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// 4. Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// 5. Create contract instance with signer
const incrementer = new ethers.Contract(contractAddress, abi, wallet);
// 6. Create reset function
const reset = async () => {
console.log(
`Calling the reset function in contract at address: ${contractAddress}`
);
// 7. sign and send tx and wait for receipt
const createReceipt = await incrementer.reset();
await createReceipt.wait();
console.log(`Tx successful with hash: ${createReceipt.hash}`);
};
// 8. Call the reset function
reset();// Import ethers and compile
const ethers = require('ethers');
const { abi } = require('./compile');
// Define network configurations
const providerRPC = {
development: {
name: 'phron-development',
rpc: 'http://localhost:9944',
chainId: 7744,
},
phron: {
name: 'phron',
rpc: 'https://testnet.phron.ai',
chainId: 7744,
},
};
// Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.phron.rpc, {
chainId: providerRPC.phron.chainId,
name: providerRPC.phron.name,
}); // Change to correct network
// Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// Create contract instance with signer
const incrementer = new ethers.Contract(contractAddress, abi, wallet);
// Create reset function
const reset = async () => {
console.log(
`Calling the reset function in contract at address: ${contractAddress}`
);
// Sign and send tx and wait for receipt
const createReceipt = await incrementer.reset();
await createReceipt.wait();
console.log(`Tx successful with hash: ${createReceipt.hash}`);
};
// Call the reset function
reset();node reset.jsnode get.jsMaking a call to contract at address: 0x2B9c71fc2730B7353Dd3865ae26881Fa38FE598AThe current number stored is: 8node reset.jsCalling the reset function in contract at address: 0x2B9c71fc2730B7353Dd3865ae26881Fa38FE598ATx successful with hash: 0xc452d21d8c2be6b81aadab7414103d68149c94a6399149ab8b79a58f0a3b5db7node get.jsMaking a call to contract at address: 0x2B9c71fc2730B7353Dd3865ae26881Fa38FE598AThe current number stored is: 0mkdir web3-examples && cd web3-examples && npm init --ynpm install web3 [email protected]const { Web3 } = require('web3');
// Create Web3 instance
const web3 = new Web3('INSERT_RPC_API_ENDPOINT'); // Insert your RPC URL heretouch balances.js// 1. Add the Web3 provider logic
// {...}
// 2. Create address variables
const addressFrom = 'INSERT_FROM_ADDRESS';
const addressTo = 'INSERT_TO_ADDRESS';
// 3. Create balances function
const balances = async () => {
// 4. Fetch balance info
const balanceFrom = web3.utils.fromWei(
await web3.eth.getBalance(addressFrom),
'ether'
);
const balanceTo = web3.utils.fromWei(
await web3.eth.getBalance(addressTo),
'ether'
);
console.log(`The balance of ${addressFrom} is: ${balanceFrom} DEV`);
console.log(`The balance of ${addressTo} is: ${balanceTo} DEV`);
};
// 5. Call balances function
balances();const { Web3 } = require('web3');
// 1. Add the Web3 provider logic here:
const providerRPC = {
development: 'http://localhost:9944',
phron: 'https://testnet.phron.ai',
};
const web3 = new Web3(providerRPC.phron); // Change to correct network
// 2. Create address variables
const addressFrom = 'INSERT_FROM_ADDRESS';
const addressTo = 'INSERT_TO_ADDRESS';
// 3. Create balances function
const balances = async () => {
// 4. Fetch balance info
const balanceFrom = web3.utils.fromWei(await web3.eth.getBalance(addressFrom), 'ether');
const balanceTo = web3.utils.fromWei(await web3.eth.getBalance(addressTo), 'ether');
console.log(`The balance of ${addressFrom} is: ${balanceFrom} DEV`);
console.log(`The balance of ${addressTo} is: ${balanceTo} DEV`);
};
// 5. Call balances function
balances();node balances.jstouch transaction.js// 1. Add the Web3 provider logic
// {...}
// 2. Create account variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
address: 'INSERT_PUBLIC_ADDRESS_OF_PK',
};
const addressTo = 'INSERT_TO_ADDRESS'; // Change addressTo
// 3. Create send function
const send = async () => {
console.log(
`Attempting to send transaction from ${accountFrom.address} to ${addressTo}`
);
// 4. Sign transaction with PK
const createTransaction = await web3.eth.accounts.signTransaction(
{
gas: 21000,
to: addressTo,
value: web3.utils.toWei('1', 'ether'),
gasPrice: await web3.eth.getGasPrice(),
nonce: await web3.eth.getTransactionCount(accountFrom.address),
},
accountFrom.privateKey
);
// 5. Send transaction and wait for receipt
const createReceipt = await web3.eth.sendSignedTransaction(
createTransaction.rawTransaction
);
console.log(
`Transaction successful with hash: ${createReceipt.transactionHash}`
);
};
// 6. Call send function
send();const { Web3 } = require('web3');
// 1. Add the Web3 provider logic
const providerRPC = {
development: 'http://localhost:9944',
phron: 'https://testnet.phron.ai',
};
const web3 = new Web3(providerRPC.phron); // Change to correct network
// 2. Create account variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
address: 'INSERT_PUBLIC_ADDRESS_OF_PK',
};
const addressTo = 'INSERT_TO_ADDRESS';
// 3. Create send function
const send = async () => {
console.log(
`Attempting to send transaction from ${accountFrom.address} to ${addressTo}`
);
// 4. Sign transaction with PK
const createTransaction = await web3.eth.accounts.signTransaction(
{
gas: 21000,
to: addressTo,
value: web3.utils.toWei('1', 'ether'),
gasPrice: await web3.eth.getGasPrice(),
nonce: await web3.eth.getTransactionCount(accountFrom.address),
},
accountFrom.privateKey
);
// 5. Send transaction and wait for receipt
const createReceipt = await web3.eth.sendSignedTransaction(
createTransaction.rawTransaction
);
console.log(
`Transaction successful with hash: ${createReceipt.transactionHash}`
);
};
// 6. Call send function
send();node transaction.jsnode balances.jsThe balance of 0x3B939FeaD1557C741Ff06492FD0127bd287A421e is: 3603.67979115380310679 DEVThe balance of 0xe29A0699e079FeBEe94A02f35C31B026f90F6040 is: 0. DEVnode transaction.jsAttempting to send transaction from 0x3B939FeaD1557C741Ff06492FD0127bd287A421e to 0xe29A0699e079FeBEe94A02f35C31B026f90F6040Transaction successful with hash: 0xf1d628ed12c5f40e03e29aa2c23c8c09680ee595c60607c7363a81c0be8ef3cbnode balances.jsThe balance of 0x3B939FeaD1557C741Ff06492FD0127bd287A421e is: 3602.67978852880310679 DEVThe balance of 0xe29A0699e079FeBEe94A02f35C31B026f90F6040 is: 1 DEVUnableToPopulateNonceError: Invalid value given "UnableToPopulateNonceError". Error: unable to populate nonce, no from address available.MissingGasError: Invalid value given "gas: 0x5208, gasPrice: undefined, maxPriorityFeePerGas: undefined, maxFeePerGas: undefined". Error: "gas" is missing.touch Incrementer.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Incrementer {
uint256 public number;
constructor(uint256 _initialNumber) {
number = _initialNumber;
}
function increment(uint256 _value) public {
number = number + _value;
}
function reset() public {
number = 0;
}
}touch compile.js// 1. Import packages
const fs = require('fs');
const solc = require('solc');
// 2. Get path and load contract
const source = fs.readFileSync('Incrementer.sol', 'utf8');
// 3. Create input object
const input = {
language: 'Solidity',
sources: {
'Incrementer.sol': {
content: source,
},
},
settings: {
outputSelection: {
'*': {
'*': ['*'],
},
},
},
};
// 4. Compile the contract
const tempFile = JSON.parse(solc.compile(JSON.stringify(input)));
const contractFile = tempFile.contracts['Incrementer.sol']['Incrementer'];
// 5. Export contract data
module.exports = contractFile;touch deploy.js// 1. Import the contract file
const contractFile = require('./compile');
// 2. Add the Web3 provider logic
// {...}
// 3. Create address variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
address: 'INSERT_PUBLIC_ADDRESS_OF_PK',
};
// 4. Get the bytecode and API
const bytecode = contractFile.evm.bytecode.object;
const abi = contractFile.abi;
// 5. Create deploy function
const deploy = async () => {
console.log(`Attempting to deploy from account ${accountFrom.address}`);
// 6. Create contract instance
const incrementer = new web3.eth.Contract(abi);
// 7. Create constructor transaction
const incrementerTx = incrementer.deploy({
data: bytecode,
arguments: [5],
});
// 8. Sign transaction with PK
const createTransaction = await web3.eth.accounts.signTransaction(
{
data: incrementerTx.encodeABI(),
gas: await incrementerTx.estimateGas(),
gasPrice: await web3.eth.getGasPrice(),
nonce: await web3.eth.getTransactionCount(accountFrom.address),
},
accountFrom.privateKey
);
// 9. Send transaction and wait for receipt
const createReceipt = await web3.eth.sendSignedTransaction(
createTransaction.rawTransaction
);
console.log(`Contract deployed at address: ${createReceipt.contractAddress}`);
};
// 10. Call deploy function
deploy();// 1. Import web3 and the contract file
const { Web3 } = require('web3');
const contractFile = require('./compile');
// 2. Add the Web3 provider logic
const providerRPC = {
development: 'http://localhost:9944',
phron: 'https://testnet.phron.ai',
};
const web3 = new Web3(providerRPC.phron); // Change to correct network
// 3. Create address variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
address: 'INSERT_PUBLIC_ADDRESS_OF_PK',
};
// 4. Get the bytecode and API
const bytecode = contractFile.evm.bytecode.object;
const abi = contractFile.abi;
// 5. Create deploy function
const deploy = async () => {
console.log(`Attempting to deploy from account ${accountFrom.address}`);
// 6. Create contract instance
const incrementer = new web3.eth.Contract(abi);
// 7. Create constructor transaction
const incrementerTx = incrementer.deploy({
data: bytecode,
arguments: [5],
});
// 8. Sign transaction with PK
const createTransaction = await web3.eth.accounts.signTransaction(
{
data: incrementerTx.encodeABI(),
gas: await incrementerTx.estimateGas(),
gasPrice: await web3.eth.getGasPrice(),
nonce: await web3.eth.getTransactionCount(accountFrom.address),
},
accountFrom.privateKey
);
// 9. Send transaction and wait for receipt
const createReceipt = await web3.eth.sendSignedTransaction(
createTransaction.rawTransaction
);
console.log(`Contract deployed at address: ${createReceipt.contractAddress}`);
};
// 10. Call deploy function
deploy();node deploy.jsnode deploy.jsAttempting to deploy from account 0x3B939FeaD1557C741Ff06492FD0127bd287A421eContract deployed at address: 0x6dcb33a7f6235e74fd553b50c96f900707142892touch get.js// 1. Import the contract ABI
const { abi } = require('./compile');
// 2. Add the Web3 provider logic
// {...}
// 3. Create address variables
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// 4. Create contract instance
const incrementer = new web3.eth.Contract(abi, contractAddress);
// 5. Create get function
const get = async () => {
console.log(`Making a call to contract at address: ${contractAddress}`);
// 6. Call contract
const data = await incrementer.methods.number().call();
console.log(`The current number stored is: ${data}`);
};
// 7. Call get function
get();// 1. Import Web3js and the contract ABI
const { Web3 } = require('web3');
const { abi } = require('./compile');
// 2. Add the Web3 provider logic
const providerRPC = {
development: 'http://localhost:9944',
phron: 'https://testnet.phron.ai',
};
const web3 = new Web3(providerRPC.phron); // Change to correct network
// 3. Create address variables
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// 4. Create contract instance
const incrementer = new web3.eth.Contract(abi, contractAddress);
// 5. Create get function
const get = async () => {
console.log(`Making a call to contract at address: ${contractAddress}`);
// 6. Call contract
const data = await incrementer.methods.number().call();
console.log(`The current number stored is: ${data}`);
};
// 7. Call get function
get();node get.jstouch increment.js reset.js// 1. Import the contract ABI
const { abi } = require('./compile');
// 2. Add the Web3 provider logic
// {...}
// 3. Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
address: 'INSERT_PUBLIC_ADDRESS_OF_PK',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const _value = 3;
// 4. Create contract instance
const incrementer = new web3.eth.Contract(abi, contractAddress);
// 5. Build the increment transaction
const incrementTx = incrementer.methods.increment(_value);
// 6. Create increment function
const increment = async () => {
console.log(
`Calling the increment by ${_value} function in contract at address: ${contractAddress}`
);
// 7. Sign transaction with PK
const createTransaction = await web3.eth.accounts.signTransaction(
{
to: contractAddress,
data: incrementTx.encodeABI(),
gas: await incrementTx.estimateGas(),
gasPrice: await web3.eth.getGasPrice(),
nonce: await web3.eth.getTransactionCount(accountFrom.address),
},
accountFrom.privateKey
);
// 8. Send transaction and wait for receipt
const createReceipt = await web3.eth.sendSignedTransaction(
createTransaction.rawTransaction
);
console.log(`Tx successful with hash: ${createReceipt.transactionHash}`);
};
// 9. Call increment function
increment();// 1. Import Web3js and the contract ABI
const { Web3 } = require('web3');
const { abi } = require('./compile');
// 2. Add the Web3 provider logic
const providerRPC = {
development: 'http://localhost:9944',
phron: 'https://testnet.phron.ai',
};
const web3 = new Web3(providerRPC.phron); // Change to correct network
// 3. Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
address: 'INSERT_PUBLIC_ADDRESS_OF_PK',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const _value = 3;
// 4. Create contract instance
const incrementer = new web3.eth.Contract(abi, contractAddress);
// 5. Build increment transaction
const incrementTx = incrementer.methods.increment(_value);
// 6. Create increment function
const increment = async () => {
console.log(
`Calling the increment by ${_value} function in contract at address: ${contractAddress}`
);
// 7. Sign transaction with PK
const createTransaction = await web3.eth.accounts.signTransaction(
{
to: contractAddress,
data: incrementTx.encodeABI(),
gas: await incrementTx.estimateGas(),
gasPrice: await web3.eth.getGasPrice(),
nonce: await web3.eth.getTransactionCount(accountFrom.address),
},
accountFrom.privateKey
);
// 8. Send transaction and wait for receipt
const createReceipt = await web3.eth.sendSignedTransaction(
createTransaction.rawTransaction
);
console.log(`Tx successful with hash: ${createReceipt.transactionHash}`);
};
// 9. Call increment function
increment();node increment.jsnode get.jsMaking a call to contract at address: 0x6dcb33a7f6235e74fd553b50c96f900707142892The current number stored is: 5node increment.jsCalling the increment by 3 function in contract at address: 0x6dcb33a7f6235e74fd553b50c96f900707142892Tx successful with hash: 0xb03d1426376e7efc49d8b6c69aaf91e548578db7fd4a9ba575dbd8030821f6a3node get.jsMaking a call to contract at address: 0x6dcb33a7f6235e74fd553b50c96f900707142892The current number stored is: 8// 1. Import the contract ABI
const { abi } = require('./compile');
// 2. Add the Web3 provider logic
// {...}
// 3. Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
address: 'INSERT_PUBLIC_ADDRESS_OF_PK',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// 4. Create a contract instance
const incrementer = new web3.eth.Contract(abi, contractAddress);
// 5. Build reset transaction
const resetTx = incrementer.methods.reset();
// 6. Create reset function
const reset = async () => {
console.log(
`Calling the reset function in contract at address: ${contractAddress}`
);
// 7. Sign transaction with PK
const createTransaction = await web3.eth.accounts.signTransaction(
{
to: contractAddress,
data: resetTx.encodeABI(),
gas: await resetTx.estimateGas(),
gasPrice: await web3.eth.getGasPrice(),
nonce: await web3.eth.getTransactionCount(accountFrom.address),
},
accountFrom.privateKey
);
// 8. Send transaction and wait for receipt
const createReceipt = await web3.eth.sendSignedTransaction(
createTransaction.rawTransaction
);
console.log(`Tx successful with hash: ${createReceipt.transactionHash}`);
};
// 9. Call reset function
reset();// 1. Import Web3js and the contract ABI
const { Web3 } = require('web3');
const { abi } = require('./compile');
// 2. Add the Web3 provider logic
const providerRPC = {
development: 'http://localhost:9944',
phron: 'https://testnet.phron.ai',
};
const web3 = new Web3(providerRPC.phron); // Change to correct network
// 3. Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
address: 'INSERT_PUBLIC_ADDRESS_OF_PK',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// 4. Create contract instance
const incrementer = new web3.eth.Contract(abi, contractAddress);
// 5. Build reset transaction
const resetTx = incrementer.methods.reset();
// 6. Create reset function
const reset = async () => {
console.log(`Calling the reset function in contract at address: ${contractAddress}`);
// 7. Sign transaction with PK
const createTransaction = await web3.eth.accounts.signTransaction(
{
to: contractAddress,
data: resetTx.encodeABI(),
gas: await resetTx.estimateGas(),
gasPrice: await web3.eth.getGasPrice(),
nonce: await web3.eth.getTransactionCount(accountFrom.address),
},
accountFrom.privateKey
);
// 8. Send transaction and wait for receipt
const createReceipt = await web3.eth.sendSignedTransaction(createTransaction.rawTransaction);
console.log(`Tx successful with hash: ${createReceipt.transactionHash}`);
};
// 9. Call reset function
reset();node reset.jsnode get.jsMaking a call to contract at address: 0x6dcb33a7f6235e74fd553b50c96f900707142892The current number stored is: 8node reset.jsCalling the reset function in contract at address: 0x6dcb33a7f6235e74fd553b50c96f900707142892Tx successful with hash: 0x557e908ca4da05d5af50983dbc116fbf8049bb2e86b9ec1e9f7d3f516b8a4c55node get.jsMaking a call to contract at address: 0x6dcb33a7f6235e74fd553b50c96f900707142892The current number stored is: 0The Ethers.rs library provides a set of tools to interact with Ethereum Nodes via the Rust programming language that works similar to Ethers.js. Phron has an Ethereum-like API available that is fully compatible with Ethereum-style JSON-RPC invocations. Therefore, developers can leverage this compatibility and use the Ethers.rs library to interact with a Phron node as if they were doing so on Ethereum. You can read more about how to use Ethers.rs on their .
In this guide, you'll learn how to use the Ethers.rs library to send a transaction and deploy a contract on Phron.
For the examples in this guide, you will need to have the following:
An account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
Have on your device
Have on your device. Using is recommended by the Ethers.rs package
NoteThe examples in this guide assumes you have a MacOS or Ubuntu 20.04-based environment and will need to be adapted accordingly for Windows.
To get started, you can create a new Rust project with the Cargo tool:
For this guide, you'll need to install the Ethers.rs library among others. To tell the Rust project to install it, you must edit the Cargo.toml file that's included with the document to include it under dependencies:
This example is using the ethers and ethers-solc crate versions 1.0.2 for RPC interactions and Solidity compiling. It also includes the tokio crate to run asynchronous Rust environments, since interacting with RPCs requires asynchronous code. Finally, it includes the serde_json and serde crates to help serialize/deserialize this example's code.
If this is your first time using solc-select, you'll need to install and configure the Solidity version using the following commands:
Throughout this guide, you'll be writing multiple functions that provide different functionality such as sending a transaction, deploying a contract, and interacting with a deployed contract. In most of these scripts you'll need to use an or an to interact with the network.
To configure your project for Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
There are multiple ways to create a provider and signer, but the easiest way is through try_from. In the src/main.rs file, you can take the following steps:
Import Provider and Http from the ethers crate
Add a Client type for convenience, which will be used once you start to create the functions for sending a transaction and deploying a contract
Add a tokio attribute above
During this section, you'll be creating a couple of functions, which will be contained in the same main.rs file to avoid additional complexity from implementing modules. The first function will be to check the balances of your accounts before trying to send a transaction. The second function will actually send the transaction. To run each of these functions, you will edit the main function and run the main.rs script.
You should already have your provider and client set up in main.rs in the way described in the previous section. In order to send a transaction, you'll need to add a few more lines of code:
Add use ethers::{utils, prelude::*}; to your imports, which will provide you access to utility functions and the prelude imports all of the necessary data types and traits
As you'll be sending a transaction from one address to another, you can specify the sending and receiving addresses in the main function. Note: the address_from value should correspond to the private key that is used in the main function
Next, you will create the function for getting the sending and receiving accounts' balances by completing the following steps:
Create a new asynchronous function named print_balances that takes a provider object's reference and the sending and receiving addresses as input
Use the provider object's get_balance function to get the balances of the sending and receiving addresses of the transaction
Print the resultant balances for the sending and receiving addresses
For this example, you'll be transferring 1 DEV from an origin address (of which you hold the private key) to another address.
Create a new asynchronous function named send_transaction that takes a client object's reference and the sending and receiving addresses as input
Create the transaction object, and include the to, value, and from. When writing the value input, use the ethers::utils::parse_ether function
To run the script, which will send the transaction and then check the balances once the transaction has been sent, you can run the following command:
If the transaction was succesful, in your terminal you'll see the transaction details printed out along with the balance of your address.
The contract you'll be compiling and deploying in the next couple of sections is a simple incrementer contract, arbitrarily named Incrementer.sol. You can get started by creating a file for the contract:
Next, you can add the Solidity code to the file:
The constructor function, which runs when the contract is deployed, sets the initial value of the number variable stored on-chain (the default is 0). The increment function adds the _value provided to the current number, but a transaction needs to be sent, which modifies the stored data. Lastly, the reset function resets the stored value to zero.
NoteThis contract is a simple example for illustration purposes only and does not handle values wrapping around.
During the rest of this section, you'll be creating a couple of functions, which will be contained in the main.rs file to avoid additional complexity from implementing modules. The first function will be to compile and deploy the contract. The remaining functions will interact with the deployed contract.
You should already have your provider and client set up in main.rs in the way described in the Setting up the Ethers Provider and Client section.
Before getting started with the contract deployment, you'll need to add a few more imports to your main.rs file:
The ethers_solc import will be used to compile the smart contract. The prelude from Ethers imports some necessary data types and traits. Lastly, the std imports will enables you to store your smart contracts and wrap the client into an Arc type for thread safety.
This example function will compile and deploy the Incrementer.sol smart contract you created in the previous section. The Incrementer.sol smart contract should be in the root directory. In the main.rs file, you can take the following steps:
Create a new asynchronous function named compile_deploy_contract that takes a client object's reference as input, and returns an address in the form of H160
Define a variable named source as the path for the directory that hosts all of the smart contracts that should be compiled, which is the root directory
Use the Solc crate to compile all of the smart contracts in the root directory
Call methods are the type of interaction that don't modify the contract's storage (change variables), meaning no transaction needs to be sent. They simply read various storage variables of the deployed contract.
Rust is typesafe, which is why the ABI for the Incrementer.sol contract is required to generate a typesafe Rust struct. For this example, you should create a new file in the root of the Cargo project called Incrementer_ABI.json:
The ABI for Incrementer.sol is below, which should be copied and pasted into the Incrementer_ABI.json file:
Then you can take the following steps to create a function that reads and returns the number method of the Incrementer.sol contract:
Generate a type-safe interface for the Incrementer smart contract with the abigen macro
Create a new asynchronous function named read_number that takes a client object's reference and a contract address reference as input, and returns a U256
Create a new instance of the Incrementer object generated by the abigen macro with the client and contract address values
To run the script, which will deploy the contract and return the current value stored in the Incrementer contract, you can enter the following command into your terminal:
If successful, you'll see the deployed contract's address and initial value set, which should be 5, displayed in the terminal.
Send methods are the type of interaction that modify the contract's storage (change variables), meaning a transaction needs to be signed and sent. In this section, you'll create two functions: one to increment and one to reset the incrementer. This section will also require the Incrementer_ABI.json file initialized when reading from the smart contract.
Take the following steps to create the function to increment:
Ensure that the abigen macro is called for the Incrementer_ABI.json somewhere in the main.rs file (if it is already in the main.rs file, you do not have to have a second one)
Create a new asynchronous function named increment_number that takes a client object's reference and an address as input
Create a new instance of the Incrementer
To run the script, you can enter the following command into your terminal:
If successful, the transaction receipt will be displayed in the terminal. You can use the read_number function in the main function to make sure that value is changing as expected. If you're using the read_number function after incrementing, you'll also see the incremented number, which should be 10.
Next you can interact with the reset function:
Ensure that the abigen macro is called for the Incrementer_ABI.json somewhere in the main.rs file (if it is already in the main.rs file, you do not have to have a second one)
Create a new asynchronous function named reset that takes a client object's reference and an address as input
Create a new instance of the Incrementer
If successful, the transaction receipt will be displayed in the terminal. You can use the read_number function in the main function to make sure that value is changing as expected. If you're using the read_number function after resetting the number, you should see 0 printed to the terminal.
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
async fn main()Use try_from to attempt to instantiate a JSON-RPC provider object from an RPC endpoint
Use a private key to create a wallet object (the private key will be used to sign transactions). Note: This is for example purposes only. Never store your private keys in a plain Rust file
Wrap the provider and wallet together into a client by providing them to a SignerMiddleware object
Call the print_balances function in the main function
Use the client object to send the transaction
Print the transaction after it is confirmed
Call the send_transaction function in the main function
Get the ABI and bytecode from the compiled result, searching for the Incrementer.sol contract
Create a contract factory for the smart contract using the ABI, bytecode, and client. The client must be wrapped into an Arc type for thread safety
Use the factory to deploy. For this example, the value 5 is used as the initial value in the constructor
Print out the address after the deployment
Return the address
Call the compile_deploy_contract function in main
Call the number function in the new Incrementer object
Print out the resultant value
Return the resultant value
Call the read_number function in main
Call the increment function in the new Incrementer object by including a U256 object as input. In this instance, the value provided is 5
Call the read_number function in main
Call the reset function in the new Incrementer object
Call the reset function in main
cargo init ethers-examples && cd ethers-examples[package]
name = "ethers-examples"
version = "0.1.0"
edition = "2021"
[dependencies]
ethers = "1.0.2"
ethers-solc = "1.0.2"
tokio = { version = "1", features = ["full"] }
serde_json = "1.0.89"
serde = "1.0.149"solc-select install 0.8.17 && solc-select use 0.8.17// 1. Import ethers crate
use ethers::providers::{Provider, Http};
// 2. Add client type
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
// 3. Add annotation
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 4. Use try_from with RPC endpoint
let provider = Provider::<Http>::try_from(
"INSERT_RPC_API_ENDPOINT"
)?;
// 5. Use a private key to create a wallet
// Do not include the private key in plain text in any production code
// This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_YOUR_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Phron);
// 6. Wrap the provider and wallet together to create a signer client
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
Ok(())
}// ...
// 1. Add to imports
use ethers::{utils, prelude::*};
// ...
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 2. Add from and to address
let address_from = "YOUR_FROM_ADDRESS".parse::<Address>()?
let address_to = "YOUR_TO_ADDRESS".parse::<Address>()?
}// ...
// 1. Create an asynchronous function that takes a provider reference and from and to address as input
async fn print_balances(provider: &Provider<Http>, address_from: Address, address_to: Address) -> Result<(), Box<dyn std::error::Error>> {
// 2. Use the get_balance function
let balance_from = provider.get_balance(address_from, None).await?;
let balance_to = provider.get_balance(address_to, None).await?;
// 3. Print the resultant balance
println!("{} has {}", address_from, balance_from);
println!("{} has {}", address_to, balance_to);
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 4. Call print_balances function in main
print_balances(&provider).await?;
Ok(())
}// ...
// 1. Define an asynchronous function that takes a client provider and the from and to addresses as input
async fn send_transaction(client: &Client, address_from: Address, address_to: Address) -> Result<(), Box<dyn std::error::Error>> {
println!(
"Beginning transfer of 1 native currency from {} to {}.",
address_from, address_to
);
// 2. Create a TransactionRequest object
let tx = TransactionRequest::new()
.to(address_to)
.value(U256::from(utils::parse_ether(1)?))
.from(address_from);
// 3. Send the transaction with the client
let tx = client.send_transaction(tx, None).await?.await?;
// 4. Print out the result
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 5. Call send_transaction function in main
send_transaction(&client, address_from, address_to).await?;
Ok(())
}use ethers::providers::{Provider, Http};
use ethers::{utils, prelude::*};
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider: Provider<Http> = Provider::<Http>::try_from("https://testnet.phron.ai")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes
let wallet: LocalWallet = "INSERT_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Phron); // Change to correct network
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
let address_from = "INSERT_FROM_ADDRESS".parse::<Address>()?;
let address_to = "INSERT_TO_ADDRESS".parse::<Address>()?;
send_transaction(&client, &address_from, &address_to).await?;
print_balances(&provider, &address_from, &address_to).await?;
Ok(())
}
// Print the balance of a wallet
async fn print_balances(provider: &Provider<Http>, address_from: &Address, address_to: &Address) -> Result<(), Box<dyn std::error::Error>> {
let balance_from = provider.get_balance(address_from.clone(), None).await?;
let balance_to = provider.get_balance(address_to.clone(), None).await?;
println!("{} has {}", address_from, balance_from);
println!("{} has {}", address_to, balance_to);
Ok(())
}
// Sends some native currency
async fn send_transaction(client: &Client, address_from: &Address, address_to: &Address) -> Result<(), Box<dyn std::error::Error>> {
println!(
"Beginning transfer of 1 native currency {} to {}.",
address_from, address_to
);
let tx = TransactionRequest::new()
.to(address_to.clone())
.value(U256::from(utils::parse_ether(1)?))
.from(address_from.clone());
let tx = client.send_transaction(tx, None).await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}cargo runcargo runCompiling ethers-examples v0.1.0 (/Users/phron/workspace/ethers-examples)Finished dev [unoptimized + debuginfo] target(s) in 32.76sRunning `target/debug/ethers-examples`Beginning transfer of 1 native currency 0x3b93…421e to 0xe773…8dde.Transaction Receipt: {"transactionHash":"0x6f2338c63286f8b27951ddb6748191149d82647b44a00465f1f776624f490ce9","transactionIndex":"0x0","blockHash":"0x8234eb2083e649ab45c7c5fcdf2026d8f47676f7e29305023d1d00cc349ba215","blockNumber":"0x7ac12d","from":"0x3b939fead1557c741ff06492fd0127bd287a421e","to":"0xe773f740828a968c8a9e1e8e05db486937768dde","cumulativeGasUsed":"0x5208","gasUsed":"0x5208","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","type":"0x0","effectiveGasPrice":"0x7735940"}0x3b93…421e has 36017039844708655891250xe773…8dde has 1000000000000000000touch Incrementer.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Incrementer {
uint256 public number;
constructor(uint256 _initialNumber) {
number = _initialNumber;
}
function increment(uint256 _value) public {
number = number + _value;
}
function reset() public {
number = 0;
}
}use ethers_solc::Solc;
use ethers::{prelude::*};
use std::{path::Path, sync::Arc};// ...
// 1. Define an asynchronous function that takes a client provider as input and returns H160
async fn compile_deploy_contract(client: &Client) -> Result<H160, Box<dyn std::error::Error>> {
// 2. Define a path as the directory that hosts the smart contracts in the project
let source = Path::new(&env!("CARGO_MANIFEST_DIR"));
// 3. Compile all of the smart contracts
let compiled = Solc::default()
.compile_source(source)
.expect("Could not compile contracts");
// 4. Get ABI & Bytecode for Incrementer.sol
let (abi, bytecode, _runtime_bytecode) = compiled
.find("Incrementer")
.expect("could not find contract")
.into_parts_or_default();
// 5. Create a contract factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(abi, bytecode, Arc::new(client.clone()));
// 6. Deploy
let contract = factory.deploy(U256::from(5))?.send().await?;
// 7. Print out the address
let addr = contract.address();
println!("Incrementer.sol has been deployed to {:?}", addr);
// 8. Return the address
Ok(addr)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 9. Call compile_deploy_contract function in main
let addr = compile_deploy_contract(&client).await?;
Ok(())
}touch Incrementer_ABI.json[
{
"inputs": [
{
"internalType": "uint256",
"name": "_value",
"type": "uint256"
}
],
"name": "increment",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "number",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "reset",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]// ...
// 1. Generate a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
// 2. Define an asynchronous function that takes a client provider and address as input and returns a U256
async fn read_number(client: &Client, contract_addr: &H160) -> Result<U256, Box<dyn std::error::Error>> {
// 3. Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// 4. Call contract's number function
let value = contract.number().call().await?;
// 5. Print out number
println!("Incrementer's number is {}", value);
// 6. Return the number
Ok(value)
}
// ...
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 7. Call read_number function in main
read_number(&client, &addr).await?;
Ok(())
}use ethers::providers::{Provider, Http};
use ethers::{prelude::*};
use ethers_solc::Solc;
use std::{path::Path, sync::Arc};
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider: Provider<Http> = Provider::<Http>::try_from("https://testnet.phron.ai")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Phron);
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
// Deploy contract and read initial incrementer value
let addr = compile_deploy_contract(&client).await?;
read_number(&client, &addr).await?;
// Increment and read the incremented number
increment_number(&client, &addr).await?;
read_number(&client, &addr).await?;
// Reset the incremented number and read it
reset(&client, &addr).await?;
read_number(&client, &addr).await?;
Ok(())
}
// Need to install solc for this tutorial: https://github.com/crytic/solc-select
async fn compile_deploy_contract(client: &Client) -> Result<H160, Box<dyn std::error::Error>> {
// Incrementer.sol is located in the root directory
let source = Path::new(&env!("INSERT_CARGO_MANIFEST_DIR"));
// Compile it
let compiled = Solc::default()
.compile_source(source)
.expect("Could not compile contracts");
// Get ABI & Bytecode for Incrementer.sol
let (abi, bytecode, _runtime_bytecode) = compiled
.find("Incrementer")
.expect("could not find contract")
.into_parts_or_default();
// Create a contract factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(abi, bytecode, Arc::new(client.clone()));
// Deploy
let contract = factory.deploy(U256::from(5))?.send().await?;
let addr = contract.address();
println!("Incrementer.sol has been deployed to {:?}", addr);
Ok(addr)
}
// Generates a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
async fn read_number(client: &Client, contract_addr: &H160) -> Result<U256, Box<dyn std::error::Error>> {
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Call contract's number function
let value = contract.number().call().await?;
// Print out value
println!("Incrementer's number is {}", value);
Ok(value)
}
async fn increment_number(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Incrementing number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.increment(U256::from(5)).send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
async fn reset(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Resetting number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.reset().send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}cargo runcargo runCompiling ethers-examples v0.1.0 (/Users/phron/workspace/ethers-examples)Finished dev [unoptimized + debuginfo] target(s) in 1.09sRunning `/Users/phron/workspace/ethers-examples/target/debug/ethers-examples`Incrementer.sol has been deployed to 0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5dIncrementer's number is 5// ...
// 1. Generate a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
// 2. Define an asynchronous function that takes a client provider and address as input
async fn increment_number(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Incrementing number...");
// 3. Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// 4. Send contract transaction
let tx = contract.increment(U256::from(5)).send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
// ...
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 5. Call increment_number function in main
increment_number(&client, &addr).await?;
Ok(())
}use ethers::providers::{Provider, Http};
use ethers::{prelude::*};
use ethers_solc::Solc;
use std::{path::Path, sync::Arc};
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider: Provider<Http> = Provider::<Http>::try_from("https://testnet.phron.ai")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Phron);
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
// Deploy contract and read initial incrementer value
let addr = compile_deploy_contract(&client).await?;
read_number(&client, &addr).await?;
// Increment and read the incremented number
increment_number(&client, &addr).await?;
read_number(&client, &addr).await?;
// Reset the incremented number and read it
reset(&client, &addr).await?;
read_number(&client, &addr).await?;
Ok(())
}
// Need to install solc for this tutorial: https://github.com/crytic/solc-select
async fn compile_deploy_contract(client: &Client) -> Result<H160, Box<dyn std::error::Error>> {
// Incrementer.sol is located in the root directory
let source = Path::new(&env!("INSERT_CARGO_MANIFEST_DIR"));
// Compile it
let compiled = Solc::default()
.compile_source(source)
.expect("Could not compile contracts");
// Get ABI & Bytecode for Incrementer.sol
let (abi, bytecode, _runtime_bytecode) = compiled
.find("Incrementer")
.expect("could not find contract")
.into_parts_or_default();
// Create a contract factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(abi, bytecode, Arc::new(client.clone()));
// Deploy
let contract = factory.deploy(U256::from(5))?.send().await?;
let addr = contract.address();
println!("Incrementer.sol has been deployed to {:?}", addr);
Ok(addr)
}
// Generates a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
async fn read_number(client: &Client, contract_addr: &H160) -> Result<U256, Box<dyn std::error::Error>> {
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Call contract's number function
let value = contract.number().call().await?;
// Print out value
println!("Incrementer's number is {}", value);
Ok(value)
}
async fn increment_number(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Incrementing number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.increment(U256::from(5)).send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
async fn reset(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Resetting number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.reset().send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}cargo runcargo runCompiling ethers-examples v0.1.0 (/Users/phron/workspace/ethers-examples)Finished dev [unoptimized + debuginfo] target(s) in 1.09sRunning `/Users/phron/workspace/ethers-examples/target/debug/ethers-examples`Incrementer.sol has been deployed to 0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5dIncrementer's number is 5Incrementing number...Transaction Receipt: {"transactionHash":"0x6f5c204e74b96b6cf6057512ba142ad727718646d4ebb7abe8bbabada198dafb","transactionIndex":"0x0","blockHash":"0x635a8a234b30c6ee907198ddda3a1478ae52c6adbcc4a67353dd9597ee626950","blockNumber":"0x7ac238","from":"0x3b939fead1557c741ff06492fd0127bd287a421e","to":"0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5d","cumulativeGasUsed":"0x68a6","gasUsed":"0x68a6","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","type":"0x2","effectiveGasPrice":"0xba43b740"}Incrementer's number is 10// ...
// 1. Generate a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
// 2. Define an asynchronous function that takes a client provider and address as input
async fn reset(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Resetting number...");
// 3. Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// 4. Send contract transaction
let tx = contract.reset().send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
// ...
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 5. Call reset function in main
reset(&client, &addr).await?;
Ok(())
}cargo runCompiling ethers-examples v0.1.0 (/Users/phron/workspace/ethers-examples)Finished dev [unoptimized + debuginfo] target(s) in 1.09sRunning `/Users/phron/workspace/ethers-examples/target/debug/ethers-examples`Incrementer.sol has been deployed to 0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5dIncrementer's number is 5Incrementing number...Transaction Receipt: {"transactionHash":"0x6f5c204e74b96b6cf6057512ba142ad727718646d4ebb7abe8bbabada198dafb","transactionIndex":"0x0","blockHash":"0x635a8a234b30c6ee907198ddda3a1478ae52c6adbcc4a67353dd9597ee626950","blockNumber":"0x7ac238","from":"0x3b939fead1557c741ff06492fd0127bd287a421e","to":"0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5d","cumulativeGasUsed":"0x68a6","gasUsed":"0x68a6","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","type":"0x2","effectiveGasPrice":"0xba43b740"}Incrementer's number is 10Resetting number...Transaction Receipt: {"transactionHash":"0xf1010597c6ab3d3cfcd6e8e68bf2eddf4ed38eb93a3052591c88b675ed1e83a4","transactionIndex":"0x0","blockHash":"0x5d4c09abf104cbd88e80487c170d8709aae7475ca84c1f3396f3e35222fbe87f","blockNumber":"0x7ac23b","from":"0x3b939fead1557c741ff06492fd0127bd287a421e","to":"0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5d","cumulativeGasUsed":"0x53c4","gasUsed":"0x53c4","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","type":"0x2","effectiveGasPrice":"0xba43b740"}Incrementer's number is 0use ethers::providers::{Provider, Http};
use ethers::{prelude::*};
use ethers_solc::Solc;
use std::{path::Path, sync::Arc};
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider: Provider<Http> = Provider::<Http>::try_from("https://testnet.phron.ai")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Phron);
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
// Deploy contract and read initial incrementer value
let addr = compile_deploy_contract(&client).await?;
read_number(&client, &addr).await?;
// Increment and read the incremented number
increment_number(&client, &addr).await?;
read_number(&client, &addr).await?;
// Reset the incremented number and read it
reset(&client, &addr).await?;
read_number(&client, &addr).await?;
Ok(())
}
// Need to install solc for this tutorial: https://github.com/crytic/solc-select
async fn compile_deploy_contract(client: &Client) -> Result<H160, Box<dyn std::error::Error>> {
// Incrementer.sol is located in the root directory
let source = Path::new(&env!("INSERT_CARGO_MANIFEST_DIR"));
// Compile it
let compiled = Solc::default()
.compile_source(source)
.expect("Could not compile contracts");
// Get ABI & Bytecode for Incrementer.sol
let (abi, bytecode, _runtime_bytecode) = compiled
.find("Incrementer")
.expect("could not find contract")
.into_parts_or_default();
// Create a contract factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(abi, bytecode, Arc::new(client.clone()));
// Deploy
let contract = factory.deploy(U256::from(5))?.send().await?;
let addr = contract.address();
println!("Incrementer.sol has been deployed to {:?}", addr);
Ok(addr)
}
// Generates a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
async fn read_number(client: &Client, contract_addr: &H160) -> Result<U256, Box<dyn std::error::Error>> {
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Call contract's number function
let value = contract.number().call().await?;
// Print out value
println!("Incrementer's number is {}", value);
Ok(value)
}
async fn increment_number(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Incrementing number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.increment(U256::from(5)).send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
async fn reset(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Resetting number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.reset().send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}Decentralized applications, or DApps, have redefined how applications are built, managed, and interacted with in Web3. By leveraging blockchain technology, DApps provide a secure, transparent, and trustless system that enables peer-to-peer interactions without any central authority. At the core of a DApp's architecture are several main components that work in tandem to create a robust, decentralized ecosystem. These components include smart contracts, nodes, frontend user interfaces, and more.
In this tutorial, you'll come face-to-face with each major component by writing a full DApp that mints tokens. We'll also explore additional optional components of DApps that can enhance user experience for your future projects. You can view the complete project in its monorepo on GitHub.
To get started, you should have the following:
A Phron account funded with DEV. You can get DEV tokens for testing on Phron once every 24 hours from the
version 16 or newer installed
with Juan Blanco's is a recommended IDE
Understanding of JavaScript and React
Generally speaking, a JSON-RPC is a remote procedure call (RPC) protocol that utilizes JSON to encode data. For Web3, they refer to the specific JSON-RPCs that DApp developers use to send requests and receive responses from blockchain nodes, making it a crucial element in interactions with smart contracts. They allow frontend user interfaces to seamlessly interact with the smart contracts and provide users with real-time feedback on their actions. They also allow developers to deploy their smart contracts in the first place!
To get a JSON-RPC to communicate with a Phron blockchain, you need to run a node. But that can be expensive, complicated, and a hassle. Fortunately, as long as you have access to a node, you can interact with the blockchain. Phron has a handful of free and paid node options. For this tutorial, we will be using the Phron's public node for Phron, but you are encouraged to get your own private endpoint.
So now you have a URL. How do you use it? Over HTTPS, JSON-RPC requests are POST requests that include specific methods for reading and writing data, such as eth_call for executing a smart contract function in a read-only manner or eth_sendRawTransaction for submitting signed transactions to the network (calls that change the blockchain state). The entire JSON request structure will always have a structure similar to the following:
This example is getting the balance (in DEV on Phron) of the 0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac account. Let's break down the elements:
jsonrpc — the JSON-RPC API version, usually "2.0"
id — an integer value that helps identify a response to a request. Can usually just keep it as `
method — the specific method to read/write data from/to the blockchain. You can see many of the RPC methods on our docs site
There are also additional elements that can be added to JSON-RPC requests, but those four will be seen the most often.
Now, these JSON-RPC requests are pretty useful, but when writing code, it can be a hassle to create a JSON object over and over again. That's why there exist libraries that help abstract and facilitate the usage of these requests. Phron provides documentation on many libraries, and the one that we will be using in this tutorial is Ethers.js. Just understand that whenever we interact with the blockchain through the Ethers.js package, we're really using JSON-RPC!
Smart contracts are self-executing contracts with the terms of the agreement directly written into code. They serve as the decentralized backend of any DApp, automating and enforcing the business logic within the system.
If coming from traditional web development, smart contracts are meant to replace the backend with important caveats: the user must have the native currency (GLMR, MOVR, DEV, etc.) to make state-changing requests, storing information can be expensive, and no information stored is private.
When you deploy a smart contract onto Phron, you upload a series of instructions that can be understood by the EVM, or the Ethereum Virtual Machine. Whenever someone interacts with a smart contract, these transparent, tamper-proof, and immutable instructions are executed by the EVM to change the blockchain's state. Writing the instructions in a smart contract properly is very important since the blockchain's state defines the most crucial information about your DApp, such as who has what amount of money.
Since the instructions are difficult to write and make sense of at a low (assembly) level, we have smart contract languages such as Solidity to make it easier to write them. To help write, debug, test, and compile these smart contract languages, developers in the Ethereum community have created developer environments such as Hardhat and Foundry. Phron's developer site provides information on a plethora of developer environments.
This tutorial will use Hardhat for managing smart contracts.
You can initialize a project with Hardhat using the following command:
When creating a JavaScript or TypeScript Hardhat project, you will be asked if you want to install the sample project's dependencies, which will install Hardhat and the . You don't need all of the plugins that come wrapped up in the Toolbox, so instead you can install Hardhat, Ethers, and the Hardhat Ethers plugin, which is all you'll need for this tutorial:
Before we start writing the smart contract, let's add a JSON-RPC URL to the config. Set the hardhat.config.js file with the following code, and replace INSERT_YOUR_PRIVATE_KEY with your funded account's private key.
Recall that we're making a DApp that allows you to mint a token for a price. Let's write a smart contract that reflects this functionality!
Once you've initialized a Hardhat project, you'll be able to write smart contracts in its contracts folder. This folder will have an initial smart contract, likely called Lock.sol, but you should delete it and add a new smart file called MintableERC20.sol.
The standard for tokens is called ERC-20, where ERC stands for "Ethereum Request for Comment". A long time ago, this standard was defined, and now most smart contracts that work with tokens expect tokens to have all of the functionality defined by ERC-20. Fortunately, you don't have to know it from memory since the OpenZeppelin smart contract team provides us with smart contract bases to use.
Install :
Now, in your MintableERC20.sol, add the following code:
When writing smart contracts, you're going to have to compile them eventually. Every developer environment for smart contracts will have this functionality. In Hardhat, you can compile with:
Everything should compile well, which should cause two new folders to pop up: artifacts and cache. These two folders hold information about the compiled smart contracts.
Let's continue by adding functionality. Add the following constants, errors, event, and function to your Solidity file:
This function will allow a user to send the native Phron currency (like GLMR, MOVR, or DEV) as value because it is a payable function. Let's break down the function section by section.
It will figure out how much of the token to mint based on the value sent
Then it will check to see if the amount minted is 0 or if the total amount minted is over the MAX_TO_MINT, giving a descriptive error in both cases
The contract will then forward the value included with the function call to the owner of the contract (by default, the address that deployed the contract, which will be you)
Finally, tokens will be minted to the user, and an event will be emitted to pick up on later
To make sure that this works, let's use Hardhat again:
You've now written the smart contract for your DApp! If this were a production app, we would write tests for it, but that is out of the scope of this tutorial. Let's deploy it next.
Under the hood, Hardhat is a Node project that uses the Ethers.js library to interact with the blockchain. You can also use Ethers.js in conjunction with Hardhat's tool to create scripts to do things like deploy contracts.
Your Hardhat project should already come with a script in the scripts folder, called deploy.js. Let's replace it with a similar, albeit simpler, script.
This script uses Hardhat's instance of the Ethers library to get a contract factory of the MintableERC20.sol smart contract that we wrote earlier. It then deploys it and prints the resultant smart contract's address. Very simple to do with Hardhat and the Ethers.js library, but significantly more difficult using just JSON-RPC!
Let's run the contract on Phron (whose JSON-RPC endpoint we defined in the hardhat.config.js script earlier):
You should see an output that displays the token address. Make sure to save it for use later!
Hardhat has a poor built-in solution for deploying smart contracts. It doesn't automatically save the transactions and addresses related to the deployment! This is why the package was created. Can you implement it yourself? Or can you switch to a different developer environment, like ?
Frontends provide an interface for users to interact with blockchain-based applications. React, a popular JavaScript library for building user interfaces, is often used for developing DApp frontends due to its component-based architecture, which promotes reusable code and efficient rendering. The , an Ethers.js based React framework for DApps, further simplifies the process of building DApp frontends by providing a comprehensive set of hooks and components that streamline the integration of Ethereum blockchain functionality.
NoteTypically, a larger project will create separate GitHub repositories for their frontend and smart contracts, but this is a small enough project to create a monorepo.
Let's set up a new React project and install dependencies, which we can create within our Hardhat project's folder without much issue. The create-react-app package will create a new frontend directory for us:
If you remember, Ethers.js is a library that assists with JSON-RPC communication. The useDApp package is a similar library that uses Ethers.js and formats them into React hooks so that they work better in frontend projects. We've also added two packages for styling and components.
Let's set up the App.js file located in the frontend/src directory to add some visual structure:
You can start the React project by running the following command from within the frontend directory:
NoteAt this point, you may see a couple compilation warnings, but as we continue to build the DApp, we'll make changes that will resolve the warnings.
Your frontend will be available at .
At this point, our frontend project is set up well enough to start working on the functional code!
The frontend communicates with the blockchain using JSON-RPC, but we will be using Ethers.js. When using JSON-RPC, Ethers.js likes to abstract degrees of interaction with the blockchain into objects, such as providers, signers, and wallets.
Providers are the bridge between the frontend user interface and the blockchain network, facilitating communication and data exchange. They abstract the complexities of interacting with the blockchain, offering a simple API for the frontend to use. They are responsible for connecting the DApp to a specific blockchain node, allowing it to read data from the blockchain, and essentially contain the JSON-RPC URL.
Signers are a type of provider that contain a secret that can be used to sign transactions with. This allows the frontend to create transactions, sign them, and then send them with eth_sendRawTransaction. There are multiple types of signers, but we're most interested in wallet objects, which securely store and manage users' private keys and digital assets. Wallets such as MetaMask facilitate transaction signing with a universal and user-friendly process. They act as a user's representation within the DApp, ensuring that only authorized transactions are executed. The Ethers.js wallet object represents this interface within our frontend code.
Typically, a frontend using Ethers.js will require you to create a provider, connect to the user's wallet if applicable, and create a wallet object. This process can become unwieldy in larger projects, especially with the number of wallets that exist other than MetaMask.
Fortunately, we have installed the useDApp package, which simplifies many of the processes for us. This simultaneously abstracts what Ethers is doing as well, which is why we took a bit of time to explain them here.
Create a Provider
Let's do a bit of setup with the useDApp package. First, in your React frontend's index.js file, which is located in the frontend/src directory, add a DAppProvider object and its config. This essentially acts as the Ethers.js provider object, but can be used throughout your entire project by useDApp hooks:
Connect to a Wallet
Now in your App.js file, let's add a button that allows us to connect to MetaMask. We don't have to write any code that's wallet-specific, fortunately, since useDApp does it for us with the useEthers hook.
Now there should be a button in the top right of your screen that connects your wallet to your frontend! Next, let's find out how we can read data from our smart contract.
Reading from contracts is quite easy, as long as we know what we want to read. For our application, we will be reading the maximum amount of tokens that can be minted and the number of tokens that have already been minted. This way, we can display to our users how many tokens can still be minted and hopefully invoke some FOMO...
If you were just using JSON-RPC, you would use eth_call to get this data, but it's quite difficult to do this since you have to in a non-straightforward method called ABI encoding. Fortunately, Ethers.js allows us to easily create objects that represent contracts in a human-readable way, so long as we have the ABI of the contract. And we have the ABI of the MintableERC20.sol contract, MintableERC20.json, within the artifacts directory of our Hardhat project!
So let's start by moving the MintableERC20.json file into our frontend directory. Every time you change and recompile the smart contract, you'll have to update the ABI in the frontend as well. Some projects will have developer setups that automatically pull ABIs from the same source, but in this case we will just copy it over:
Now that we have the ABI, we can use it to create a contract instance of MintableERC20.sol, which we'll use to retrieve token data.
Create a Smart Contract Instance
Let's import the JSON file and the Ethers Contract object within App.js. We can create a contract object instance with an address and ABI, so replace INSERT_CONTRACT_ADDRESS with the address of the contract that you copied back when you deployed it:
Interact with the Contract Interface to Read Supply Data
And let's create a new SupplyComponent within a new SupplyComponent.js file, which will use the contract interface to retrieve the token supply data and display it:
Notice that this component uses the useCall hook provided by the useDApp package. This call takes in the contract object we created earlier, a string method, and any relevant arguments for the read-only call and returns the output. While it required some setup, this one-liner is a lot simpler than the entire use_call RPC call that we would have had to do if we weren't using Ethers.js and useDApp.
Also note that we're using a utility format called formatEther to format the output values instead of displaying them directly. This is because our token, like gas currencies, is stored as an unsigned integer with a fixed decimal point of 18 figures. The utility function helps format this value into a way that we, as humans, expect.
Now we can spice up our frontend and call the read-only functions in the contract. We'll update the frontend so that we have a place to display our supply data:
Our frontend should now display the correct data!
There's additonal information that could be helpful to display, such as the amount of tokens that the connected account currently has: balanceOf(address). Can you add that to the frontend yourself?
Now for the most important part of all DApps: the state-changing transactions. This is where money moves, where tokens are minted, and value passes.
If you recall from our smart contract, we want to mint some tokens by calling the purchaseMint function with some native currency. So we're going to need:
A text input that lets the user specify how much value to enter
A button that lets the user initiate the transaction signature
Let's create a new component called MintingComponent in a new file called MintingComponent.js. First, we'll tackle the text input, which will require us to add the logic to store the number of tokens to mint and a text field element.
Next, we'll need to create the button to send the transaction, which will call the purchaseMint of our contract. Interacting with the contract will be a bit more difficult since you're likely not as familiar with it. We've already done a lot of setup in the previous sections, so it doesn't actually take too much code:
Let's break down the non-JSX code a bit:
The user's account information is being retrieved via useEthers, which can be done because useDApp provides this information throughout the entire project
The useContractFunction hook from useDApp is used to create a function, send, that will sign and send a transaction that calls the purchaseMint function on the contract defined by the contract object
Now let's look at the visual component. The button will call the handlePurchaseMint on press, which makes sense. The button will also be disabled while the transaction happens and if the user hasn't connected to the DApp with their wallet (when the account value isn't defined).
This code essentially boils down to using the useContractFunction hook in conjunction with the contract object, which is a lot simpler than what it does under the hood! Let's add this component to the main App.js file.
If you try entering a value like 0.1 and press the button, a MetaMask prompt should occur. Try it out!
A common way of listening to what happened in a transaction is through events, also known as logs. These logs are emitted by the smart contract through the emit and event keywords and can be very important in a responsive frontend. Often, DApps will use toast elements to represent events in real-time, but for this DApp, we will use a simple table.
We created an event in our smart contract: event PurchaseOccurred(address minter, uint256 amount), so let's figure out how to display its information in the frontend.
Let's create a new component PurchaseOccurredEvents within a new file PurchaseOccurredEvents.js that reads the last five logs and displays them in a table:
This component so far creates an empty table, so let's use two new hooks to read those logs:
Here's what happens in this code:
The block number is received from the useBlockNumber hook, similar to using the JSON-RPC method eth_blockNumber
A filter is created to filter for all events with any arguments on the contract injected into the component with the event name PurchaseOccurred
Logs are queried for via the useLogs hook, similar to using the eth_getLogs
If we want to display them, we can do it like so:
This too should be added to App.js.
And, if you've done any transactions, you'll see that they'll pop up!
Now you've implemented three main components of DApp frontends: reading from storage, sending transactions, and reading logs. With these building blocks as well as the knowledge you gained with smart contracts and nodes, you should be able to cover 80% of DApps.
You can view the complete .
In this tutorial, we covered a wide range of topics and tools essential for successful DApp development. We started with Hardhat, a powerful development environment that simplifies the process of writing, testing, and deploying smart contracts. Ethers.js, a popular library for interacting with Ethereum nodes, was introduced to manage wallets and transactions.
We delved into the process of writing smart contracts, highlighting best practices and key considerations when developing on-chain logic. The guide then explored useDApp, a React-based framework, for creating a user-friendly frontend. We discussed techniques for reading data from contracts, executing transactions, and working with logs to ensure a seamless user experience.
Of course, there are more advanced (but optional) components of DApps that have popped up over time:
Decentralized storage protocols — systems that store websites and files in a decentralized way
Oracles — third-party services that provide external data to smart contracts within blockchains
Indexing protocols — middleware that processes and organizes blockchain data, allowing it to be efficiently queried
An excellent Web2 to Web3 blogpost is available if you are interested in hearing about them in depth.
Hopefully, by reading this guide, you'll be well on your way to creating novel DApps on Phron!
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Novice familiarity with Solidity. If you are not familiar with writing Solidity, there are many resources out there, including Solidity by Example. A 15-minute skim should suffice for this tutorial
A wallet like MetaMask installed
params — an array of the input parameters expected by the specific method
handlePurchaseMint, is defined to help inject the native gas value defined by the TextField component into the send function. It first checks if the user has their wallet connected to Phron, and if not, it prompts the user to switch networksA helper constant will determine whether or not the transaction is still in the Mining phase, that is, it hasn't finished
The resultant logs are parsed, and the most recent five are selected
https://testnet.phron.ai{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getBalance",
"params": ["0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac", "latest"]
}npx hardhat initnpm install --save-dev hardhat @nomicfoundation/hardhat-ethers ethers@6require('@nomicfoundation/hardhat-ethers');
module.exports = {
solidity: '0.8.20',
networks: {
phron: {
url: 'https://testnet.phron.ai',
chainId: 7744,
accounts: ['INSERT_YOUR_PRIVATE_KEY']
}
}
};npm install @openzeppelin/contracts// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MintableERC20 is ERC20, Ownable {
constructor(address initialOwner) ERC20("Mintable ERC 20", "MERC") Ownable(initialOwner) {}
}npx hardhat compile uint256 public constant MAX_TO_MINT = 1000 ether;
event PurchaseOccurred(address minter, uint256 amount);
error MustMintOverZero();
error MintRequestOverMax();
error FailedToSendEtherToOwner();
/**Purchases some of the token with native currency. */
function purchaseMint() payable external {
// Calculate amount to mint
uint256 amountToMint = msg.value;
// Check for no errors
if(amountToMint == 0) revert MustMintOverZero();
if(amountToMint + totalSupply() > MAX_TO_MINT) revert MintRequestOverMax();
// Send to owner
(bool success, ) = owner().call{value: msg.value}("");
if(!success) revert FailedToSendEtherToOwner();
// Mint to user
_mint(msg.sender, amountToMint);
emit PurchaseOccurred(msg.sender, amountToMint);
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MintableERC20 is ERC20, Ownable {
constructor(address initialOwner) ERC20("Mintable ERC 20", "MERC") Ownable(initialOwner) {}
uint256 public constant MAX_TO_MINT = 1000 ether;
event PurchaseOccurred(address minter, uint256 amount);
error MustMintOverZero();
error MintRequestOverMax();
error FailedToSendEtherToOwner();
/**Purchases some of the token with native gas currency. */
function purchaseMint() external payable {
// Calculate amount to mint
uint256 amountToMint = msg.value;
// Check for no errors
if (amountToMint == 0) revert MustMintOverZero();
if (amountToMint + totalSupply() > MAX_TO_MINT)
revert MintRequestOverMax();
// Send to owner
(bool success, ) = owner().call{value: msg.value}("");
if (!success) revert FailedToSendEtherToOwner();
// Mint to user
_mint(msg.sender, amountToMint);
emit PurchaseOccurred(msg.sender, amountToMint);
}
}npx hardhat compileconst hre = require('hardhat');
async function main() {
const [deployer] = await hre.ethers.getSigners();
const MintableERC20 = await hre.ethers.getContractFactory('MintableERC20');
const token = await MintableERC20.deploy(deployer.address);
await token.waitForDeployment();
// Get and print the contract address
const myContractDeployedAddress = await token.getAddress();
console.log(`Deployed to ${myContractDeployedAddress}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});npx hardhat run scripts/deploy.js --network phronnpx create-react-app frontend
cd frontend
npm install [email protected] @usedapp/core @mui/material @mui/system @emotion/react @emotion/styledimport { useEthers } from '@usedapp/core';
import { Button, Grid, Card } from '@mui/material';
import { Box } from '@mui/system';
const styles = {
box: { minHeight: '100vh', backgroundColor: '#1b3864' },
vh100: { minHeight: '100vh' },
card: { borderRadius: 4, padding: 4, maxWidth: '550px', width: '100%' },
alignCenter: { textAlign: 'center' },
};
function App() {
return (
<Box sx={styles.box}>
<Grid
container
direction='column'
alignItems='center'
justifyContent='center'
style={styles.vh100}
>
{/* This is where we'll be putting our functional components! */}
</Grid>
</Box>
);
}
export default App;npm run start// Detect if the browser has MetaMask installed
let provider, signer;
if (typeof window.ethereum !== 'undefined') {
// Create a provider using MetaMask
provider = new ethers.BrowserProvider(window.ethereum);
// Connect to MetaMask
async function connectToMetaMask() {
try {
// Request access to the user's MetaMask account
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts',
});
// Create a signer (wallet) using the provider
signer = provider.getSigner(accounts[0]);
} catch (error) {
console.error('Error connecting to MetaMask:', error);
}
}
// Call the function to connect to MetaMask
connectToMetaMask();
} else {
console.log('MetaMask is not installed');
}
// ... also the code for disconnecting from the site
// ... also the code that handles other walletsimport React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { DAppProvider, Phron } from '@usedapp/core';
import { getDefaultProvider } from 'ethers';
const config = {
readOnlyChainId: Phron.chainId,
readOnlyUrls: {
[Phron.chainId]: getDefaultProvider(
'https://testnet.phron.ai'
),
},
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<DAppProvider config={config}>
<App />
</DAppProvider>
</React.StrictMode>
);function App() {
const { activateBrowserWallet, deactivate, account } = useEthers();
// Handle the wallet toggle
const handleWalletConnection = () => {
if (account) deactivate();
else activateBrowserWallet();
};
return (
<Box sx={styles.box}>
<Grid
container
direction='column'
alignItems='center'
justifyContent='center'
style={styles.vh100}
>
<Box position='absolute' top={8} right={16}>
<Button variant='contained' onClick={handleWalletConnection}>
{account
? `Disconnect ${account.substring(0, 5)}...`
: 'Connect Wallet'}
</Button>
</Box>
</Grid>
</Box>
);
};|--artifacts
|--@openzeppelin
|--build-info
|--contracts
|--MintableERC20.sol
|--MintableERC20.json // This is the file you're looking for!
...
|--cache
|--contracts
|--frontend
|--public
|--src
|--MintableERC20.json // Copy the file to here!
...
...
...// ... other imports
import MintableERC20 from './MintableERC20.json';
import { Contract } from 'ethers';
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
function App() {
const contract = new Contract(contractAddress, MintableERC20.abi);
// ...
}import { useEthers } from '@usedapp/core';
import { Button, Grid, Card } from '@mui/material';
import { Box } from '@mui/system';
import { Contract } from 'ethers';
import MintableERC20 from './MintableERC20.json';
const styles = {
box: { minHeight: '100vh', backgroundColor: '#1b3864' },
vh100: { minHeight: '100vh' },
card: { borderRadius: 4, padding: 4, maxWidth: '550px', width: '100%' },
alignCenter: { textAlign: 'center' },
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
function App() {
const contract = new Contract(contractAddress, MintableERC20.abi);
const { activateBrowserWallet, deactivate, account } = useEthers();
// Handle the wallet toggle
const handleWalletConnection = () => {
if (account) deactivate();
else activateBrowserWallet();
};
return (
<Box sx={styles.box}>
<Grid
container
direction='column'
alignItems='center'
justifyContent='center'
style={styles.vh100}
>
<Box position='absolute' top={8} right={16}>
<Button variant='contained' onClick={handleWalletConnection}>
{account
? `Disconnect ${account.substring(0, 5)}...`
: 'Connect Wallet'}
</Button>
</Box>
</Grid>
</Box>
);
}
export default App;import { useCall } from '@usedapp/core';
import { utils } from 'ethers';
import { Grid } from '@mui/material';
export default function SupplyComponent({ contract }) {
const totalSupply = useCall({ contract, method: 'totalSupply', args: [] });
const maxSupply = useCall({ contract, method: 'MAX_TO_MINT', args: [] });
const totalSupplyFormatted = totalSupply
? utils.formatEther(totalSupply.value.toString())
: '...';
const maxSupplyFormatted = maxSupply
? utils.formatEther(maxSupply.value.toString())
: '...';
const centeredText = { textAlign: 'center' };
return (
<Grid item xs={12}>
<h3 style={centeredText}>
Total Supply: {totalSupplyFormatted} / {maxSupplyFormatted}
</h3>
</Grid>
);
}// ... other imports
import SupplyComponent from './SupplyComponent';
function App() {
// ...
return (
{/* Wrapper Components */}
{/* Button Component */}
<Card sx={styles.card}>
<h1 style={styles.alignCenter}>Mint Your Token!</h1>
<SupplyComponent contract={contract} />
</Card>
{/* Wrapper Components */}
)
}import { useEthers } from '@usedapp/core';
import { Button, Grid, Card } from '@mui/material';
import { Box } from '@mui/system';
import { Contract } from 'ethers';
import MintableERC20 from './MintableERC20.json';
import SupplyComponent from './SupplyComponent';
const styles = {
box: { minHeight: '100vh', backgroundColor: '#1b3864' },
vh100: { minHeight: '100vh' },
card: { borderRadius: 4, padding: 4, maxWidth: '550px', width: '100%' },
alignCenter: { textAlign: 'center' },
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
function App() {
const { activateBrowserWallet, deactivate, account } = useEthers();
const contract = new Contract(contractAddress, MintableERC20.abi);
// Handle the wallet toggle
const handleWalletConnection = () => {
if (account) deactivate();
else activateBrowserWallet();
};
return (
<Box sx={styles.box}>
<Grid
container
direction='column'
alignItems='center'
justifyContent='center'
style={styles.vh100}
>
<Box position='absolute' top={8} right={16}>
<Button variant='contained' onClick={handleWalletConnection}>
{account
? `Disconnect ${account.substring(0, 5)}...`
: 'Connect Wallet'}
</Button>
</Box>
<Card sx={styles.card}>
<h1 style={styles.alignCenter}>Mint Your Token!</h1>
<SupplyComponent contract={contract} />
</Card>
</Grid>
</Box>
);
}
export default App;import { useState } from 'react';
import { useContractFunction, useEthers, Phron } from '@usedapp/core';
import { Button, CircularProgress, TextField, Grid } from '@mui/material';
import { utils } from 'ethers';
export default function MintingComponent({ contract }) {
const [value, setValue] = useState(0);
const textFieldStyle = { marginBottom: '16px' };
return (
<>
<Grid item xs={12}>
<TextField
type='number'
onChange={(e) => setValue(e.target.value)}
label='Enter value in DEV'
variant='outlined'
fullWidth
style={textFieldStyle}
/>
</Grid>
{/* This is where we'll add the button */}
</>
);
}export default function MintingComponent({ contract }) {
// ...
// Mint transaction
const { account, chainId, switchNetwork } = useEthers();
const { state, send } = useContractFunction(contract, 'purchaseMint');
const handlePurchaseMint = async () => {
if (chainId !== Phron.chainId) {
await switchNetwork(Phron.chainId);
}
send({ value: utils.parseEther(value.toString()) });
};
const isMining = state?.status === 'Mining';
return (
<>
{/* ... */}
<Grid item xs={12}>
<Button
variant='contained' color='primary' fullWidth
onClick={handlePurchaseMint}
disabled={state.status === 'Mining' || account == null}
>
{isMining? <CircularProgress size={24} /> : 'Purchase Mint'}
</Button>
</Grid>
</>
);
}import { useState } from 'react';
import { useContractFunction, useEthers, Phron } from '@usedapp/core';
import { Button, CircularProgress, TextField, Grid } from '@mui/material';
import { utils } from 'ethers';
export default function MintingComponent({ contract }) {
const [value, setValue] = useState(0);
const textFieldStyle = { marginBottom: '16px' };
const { account, chainId, switchNetwork } = useEthers();
const { state, send } = useContractFunction(contract, 'purchaseMint');
const handlePurchaseMint = async () => {
if (chainId !== Phron.chainId) {
await switchNetwork(Phron.chainId);
}
send({ value: utils.parseEther(value.toString()) });
};
const isMining = state?.status === 'Mining';
return (
<>
<Grid item xs={12}>
<TextField
type='number'
onChange={(e) => setValue(e.target.value)}
label='Enter value in DEV'
variant='outlined'
fullWidth
style={textFieldStyle}
/>
</Grid>
<Grid item xs={12}>
<Button
variant='contained' color='primary' fullWidth
onClick={handlePurchaseMint}
disabled={state.status === 'Mining' || account == null}
>
{isMining? <CircularProgress size={24} /> : 'Purchase Mint'}
</Button>
</Grid>
</>
);
}// ... other imports
import MintingComponent from './MintingComponent';
function App() {
// ...
return (
{/* Wrapper Components */}
{/* Button Component */}
<Card sx={styles.card}>
<h1 style={styles.alignCenter}>Mint Your Token!</h1>
<SupplyComponent contract={contract} />
<MintingComponent contract={contract} />
</Card>
{/* Wrapper Components */}
)
}import { useEthers } from '@usedapp/core';
import { Button, Grid, Card } from '@mui/material';
import { Box } from '@mui/system';
import { Contract } from 'ethers';
import MintableERC20 from './MintableERC20.json';
import SupplyComponent from './SupplyComponent';
import MintingComponent from './MintingComponent';
const styles = {
box: { minHeight: '100vh', backgroundColor: '#1b3864' },
vh100: { minHeight: '100vh' },
card: { borderRadius: 4, padding: 4, maxWidth: '550px', width: '100%' },
alignCenter: { textAlign: 'center' },
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
function App() {
const { activateBrowserWallet, deactivate, account } = useEthers();
const contract = new Contract(contractAddress, MintableERC20.abi);
// Handle the wallet toggle
const handleWalletConnection = () => {
if (account) deactivate();
else activateBrowserWallet();
};
return (
<Box sx={styles.box}>
<Grid
container
direction='column'
alignItems='center'
justifyContent='center'
style={styles.vh100}
>
<Box position='absolute' top={8} right={16}>
<Button variant='contained' onClick={handleWalletConnection}>
{account
? `Disconnect ${account.substring(0, 5)}...`
: 'Connect Wallet'}
</Button>
</Box>
<Card sx={styles.card}>
<h1 style={styles.alignCenter}>Mint Your Token!</h1>
<SupplyComponent contract={contract} />
<MintingComponent contract={contract} />
</Card>
</Grid>
</Box>
);
}
export default App;import { useLogs, useBlockNumber } from '@usedapp/core';
import { utils } from 'ethers';
import {
Grid,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from '@mui/material';
export default function PurchaseOccurredEvents({ contract }) {
return (
<Grid item xs={12} marginTop={5}>
<TableContainer >
<Table>
<TableHead>
<TableRow>
<TableCell>Minter</TableCell>
<TableCell align='right'>Amount</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* This is where we have to inject data from our logs! */}
</TableBody>
</Table>
</TableContainer>
</Grid>
);
}export default function PurchaseOccurredEvents({ contract }) {
// Get block number to ensure that the useLogs doesn't search from 0, otherwise it will time out
const blockNumber = useBlockNumber();
// Create a filter & get the logs
const filter = { args: [null, null], contract, event: 'PurchaseOccurred' };
const logs = useLogs(filter, { fromBlock: blockNumber - 10000 });
const parsedLogs = logs?.value.slice(-5).map(log => log.data);
// ...
}export default function PurchaseOccurredEvents({ contract }) {
// ...
return (
<Grid item xs={12} marginTop={5}>
<TableContainer >
<Table>
{/* TableHead Component */}
<TableBody>
{parsedLogs?.reverse().map((log, index) => (
<TableRow key={index}>
<TableCell>{log.minter}</TableCell>
<TableCell align='right'>
{utils.formatEther(log.amount)} tokens
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Grid>
);
}import { useLogs, useBlockNumber } from '@usedapp/core';
import { utils } from 'ethers';
import {
Grid,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from '@mui/material';
export default function PurchaseOccurredEvents({ contract }) {
// Get block number to ensure that the useLogs doesn't search from 0, otherwise it will time out
const blockNumber = useBlockNumber();
// Create a filter & get the logs
const filter = { args: [null, null], contract, event: 'PurchaseOccurred' };
const logs = useLogs(filter, { fromBlock: blockNumber - 10000 });
const parsedLogs = logs?.value.slice(-5).map((log) => log.data);
return (
<Grid item xs={12} marginTop={5}>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Minter</TableCell>
<TableCell align='right'>Amount</TableCell>
</TableRow>
</TableHead>
<TableBody>
{parsedLogs?.reverse().map((log, index) => (
<TableRow key={index}>
<TableCell>{log.minter}</TableCell>
<TableCell align='right'>
{utils.formatEther(log.amount)} tokens
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Grid>
);
}// ... other imports
import PurchaseOccurredEvents from './PurchaseOccurredEvents';
function App() {
// ...
return (
{/* Wrapper Components */}
{/* Button Component */}
<Card sx={styles.card}>
<h1 style={styles.alignCenter}>Mint Your Token!</h1>
<SupplyComponent contract={contract} />
<MintingComponent contract={contract} />
<PurchaseOccurredEvents contract={contract} />
</Card>
{/* Wrapper Components */}
)
}import { useEthers } from '@usedapp/core';
import { Button, Grid, Card } from '@mui/material';
import { Box } from '@mui/system';
import { Contract } from 'ethers';
import MintableERC20 from './MintableERC20.json';
import SupplyComponent from './SupplyComponent';
import MintingComponent from './MintingComponent';
import PurchaseOccurredEvents from './PurchaseOccurredEvents';
const styles = {
box: { minHeight: '100vh', backgroundColor: '#1b3864' },
vh100: { minHeight: '100vh' },
card: { borderRadius: 4, padding: 4, maxWidth: '550px', width: '100%' },
alignCenter: { textAlign: 'center' },
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
function App() {
const { activateBrowserWallet, deactivate, account } = useEthers();
const contract = new Contract(contractAddress, MintableERC20.abi);
// Handle the wallet toggle
const handleWalletConnection = () => {
if (account) deactivate();
else activateBrowserWallet();
};
return (
<Box sx={styles.box}>
<Grid
container
direction='column'
alignItems='center'
justifyContent='center'
style={styles.vh100}
>
<Box position='absolute' top={8} right={16}>
<Button variant='contained' onClick={handleWalletConnection}>
{account
? `Disconnect ${account.substring(0, 5)}...`
: 'Connect Wallet'}
</Button>
</Box>
<Card sx={styles.card}>
<h1 style={styles.alignCenter}>Mint Your Token!</h1>
<SupplyComponent contract={contract} />
<MintingComponent contract={contract} />
<PurchaseOccurredEvents contract={contract} />
</Card>
</Grid>
</Box>
);
}
export default App;



