To create a token factory on Ethereum that deploys ownable ERC20 tokens and charges 0.02 ETH per deployment, you can follow these steps. The token factory will be a smart contract written in Solidity that deploys new instances of an ERC20 token contract. The deployer (caller of the deploy function) will be the owner of the new ERC20 token.
First, create an ERC20 token contract that implements the Ownable
pattern. This contract will be used by the token factory to deploy new tokens.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract CustomERC20 is ERC20, Ownable {
constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
transferOwnership(msg.sender);
}
}
Next, create the token factory contract that will deploy new instances of the CustomERC20
contract and charge a fee of 0.02 ETH per deployment.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./CustomERC20.sol";
contract TokenFactory {
uint256 public constant DEPLOYMENT_FEE = 0.02 ether;
address public owner;
event TokenDeployed(address indexed tokenAddress, address indexed owner, string name, string symbol, uint256 initialSupply);
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not the factory owner");
_;
}
function deployToken(string memory name, string memory symbol, uint256 initialSupply) external payable returns (address) {
require(msg.value >= DEPLOYMENT_FEE, "Insufficient ETH for deployment fee");
// Transfer the deployment fee to the factory owner
payable(owner).transfer(DEPLOYMENT_FEE);
// Deploy the new token
CustomERC20 newToken = new CustomERC20(name, symbol, initialSupply);
newToken.transferOwnership(msg.sender);
emit TokenDeployed(address(newToken), msg.sender, name, symbol, initialSupply);
return address(newToken);
}
// Optional: Function to withdraw any excess ETH sent
function withdrawExcessETH() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No excess ETH to withdraw");
payable(owner).transfer(balance);
}
}
-
Compile and Deploy the
CustomERC20
Contract: Compile theCustomERC20
contract and deploy it on the Ethereum network. -
Deploy the
TokenFactory
Contract: After deploying theCustomERC20
contract, compile and deploy theTokenFactory
contract. This contract will handle the deployment of new ERC20 tokens.
Once the TokenFactory
contract is deployed, users can interact with it to create new ERC20 tokens:
-
Deploy a New Token:
- Users call the
deployToken
function of theTokenFactory
contract. - They need to provide the name, symbol, and initial supply of the token, as well as send 0.02 ETH as the deployment fee.
- Users call the
-
Token Ownership:
- The
TokenFactory
automatically sets the caller of thedeployToken
function as the owner of the new ERC20 token. - The owner of the factory contract receives the 0.02 ETH fee.
- The
Here’s how a user might deploy a new token via the factory:
pragma solidity ^0.8.0;
import "./TokenFactory.sol";
contract DeployNewToken {
function createMyToken(TokenFactory factory) external payable {
address newToken = factory.deployToken{value: 0.02 ether}("MyToken", "MTK", 1000000 * 10 ** 18);
// Now newToken is the address of the newly created token.
}
}
-
Fee Handling: The factory contract ensures that exactly 0.02 ETH is paid for each deployment. Any excess ETH can be withdrawn by the factory owner using the
withdrawExcessETH
function. -
Security Considerations: Ensure that your contracts are thoroughly tested and audited before deploying on the mainnet.
-
Customization: You can further customize the
CustomERC20
contract to include features like minting, burning, or pausing tokens based on your
To automatically create a trading pair for the newly deployed ERC20 token on Uniswap, you can extend the TokenFactory
contract to include this functionality. Here's how you can do it:
You'll need to interact with Uniswap's UniswapV2Factory
contract to create a new pair. Import the necessary Uniswap interfaces:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./CustomERC20.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
contract TokenFactory {
uint256 public constant DEPLOYMENT_FEE = 0.02 ether;
address public owner;
address public uniswapFactory;
address public uniswapRouter;
event TokenDeployed(
address indexed tokenAddress,
address indexed owner,
string name,
string symbol,
uint256 initialSupply,
address pairAddress
);
constructor(address _uniswapFactory, address _uniswapRouter) {
owner = msg.sender;
uniswapFactory = _uniswapFactory;
uniswapRouter = _uniswapRouter;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not the factory owner");
_;
}
function deployToken(
string memory name,
string memory symbol,
uint256 initialSupply,
address baseToken // e.g., WETH
) external payable returns (address) {
require(msg.value >= DEPLOYMENT_FEE, "Insufficient ETH for deployment fee");
// Transfer the deployment fee to the factory owner
payable(owner).transfer(DEPLOYMENT_FEE);
// Deploy the new token
CustomERC20 newToken = new CustomERC20(name, symbol, initialSupply);
newToken.transferOwnership(msg.sender);
// Create a Uniswap pair
address pair = IUniswapV2Factory(uniswapFactory).createPair(address(newToken), baseToken);
// Optionally: Provide initial liquidity, set allowance, etc.
emit TokenDeployed(address(newToken), msg.sender, name, symbol, initialSupply, pair);
return address(newToken);
}
// Optional: Function to withdraw any excess ETH sent
function withdrawExcessETH() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No excess ETH to withdraw");
payable(owner).transfer(balance);
}
}
-
Uniswap Factory and Router Addresses:
- The contract constructor now accepts the addresses of the Uniswap Factory (
_uniswapFactory
) and Uniswap Router (_uniswapRouter
). - These addresses are stored in the contract's state and used when deploying tokens and creating pairs.
- The contract constructor now accepts the addresses of the Uniswap Factory (
-
Uniswap Pair Creation:
- The
deployToken
function now includes a step to automatically create a trading pair on Uniswap using thecreatePair
function fromIUniswapV2Factory
. - The
baseToken
is an address of an existing token like WETH, USDT, or USDC that you want to pair with the new token.
- The
-
Event Emission:
- The
TokenDeployed
event now also includes the address of the Uniswap pair.
- The
-
Deploy the
TokenFactory
Contract:- Deploy the
TokenFactory
contract, passing the Uniswap Factory and Router addresses as arguments.
- Deploy the
-
Deploy a New Token:
- When you call the
deployToken
function, it will automatically deploy the token, create a Uniswap pair with the specified base token, and return the addresses of both the token and the pair.
- When you call the
You can extend the contract to provide initial liquidity automatically. This would involve interacting with the IUniswapV2Router02
interface to call the addLiquidity
function after the pair is created.
Here's a conceptual example of adding liquidity:
function addLiquidity(
address token,
uint256 tokenAmount,
uint256 ethAmount
) internal {
IERC20(token).approve(uniswapRouter, tokenAmount);
IUniswapV2Router02(uniswapRouter).addLiquidityETH{value: ethAmount}(
token,
tokenAmount,
0, // slippage is unavoidable
0, // slippage is unavoidable
msg.sender,
block.timestamp
);
}
You would integrate this function within deployToken
to automatically add liquidity to the Uniswap pair.
- Gas Costs: Deploying a new token and creating a Uniswap pair can be gas-intensive. Ensure that the
DEPLOYMENT_FEE
covers these costs adequately. - Security: Make sure the contracts are thoroughly tested and possibly audited before deploying them on the mainnet.
With these changes, the TokenFactory
contract will not only deploy new ownable ERC20 tokens but also automatically create trading pairs on Uniswap, making the new token immediately tradable.
To provide more flexibility in your token factory for deploying tokens that can be either mintable, burnable, both, or neither, you can enhance the CustomERC20
contract and modify the TokenFactory
to allow these options during deployment.
Here's a version of the CustomERC20
contract that supports optional minting and burning:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
contract CustomERC20 is ERC20, Ownable {
bool public isMintable;
bool public isBurnable;
constructor(
string memory name,
string memory symbol,
uint256 initialSupply,
bool _isMintable,
bool _isBurnable
) ERC20(name, symbol) {
isMintable = _isMintable;
isBurnable = _isBurnable;
// Mint the initial supply to the deployer
_mint(msg.sender, initialSupply);
// Transfer ownership to the deployer
transferOwnership(msg.sender);
}
function mint(address to, uint256 amount) external onlyOwner {
require(isMintable, "Minting is disabled for this token");
_mint(to, amount);
}
function burn(uint256 amount) public override {
require(isBurnable, "Burning is disabled for this token");
super.burn(amount);
}
}
Now, we need to update the TokenFactory
to allow the deployment of tokens with customizable mintable and burnable features.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./CustomERC20.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
contract TokenFactory {
uint256 public constant DEPLOYMENT_FEE = 0.02 ether;
address public owner;
address public uniswapFactory;
event TokenDeployed(
address indexed tokenAddress,
address indexed owner,
string name,
string symbol,
uint256 initialSupply,
bool isMintable,
bool isBurnable,
address pairAddress
);
constructor(address _uniswapFactory) {
owner = msg.sender;
uniswapFactory = _uniswapFactory;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not the factory owner");
_;
}
function deployToken(
string memory name,
string memory symbol,
uint256 initialSupply,
bool isMintable,
bool isBurnable,
address baseToken // e.g., WETH
) external payable returns (address) {
require(msg.value >= DEPLOYMENT_FEE, "Insufficient ETH for deployment fee");
// Transfer the deployment fee to the factory owner
payable(owner).transfer(DEPLOYMENT_FEE);
// Deploy the new token
CustomERC20 newToken = new CustomERC20(name, symbol, initialSupply, isMintable, isBurnable);
newToken.transferOwnership(msg.sender);
// Create a Uniswap pair
address pair = IUniswapV2Factory(uniswapFactory).createPair(address(newToken), baseToken);
emit TokenDeployed(
address(newToken),
msg.sender,
name,
symbol,
initialSupply,
isMintable,
isBurnable,
pair
);
return address(newToken);
}
// Optional: Function to withdraw any excess ETH sent
function withdrawExcessETH() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No excess ETH to withdraw");
payable(owner).transfer(balance);
}
}
-
Deploy the
CustomERC20
Contract:- This contract can now be used by the factory to deploy tokens with options for minting and burning.
-
Deploy the
TokenFactory
Contract:- Deploy the
TokenFactory
contract, passing the Uniswap Factory address as an argument.
- Deploy the
-
Deploy a New Token:
- Users can call the
deployToken
function with additional parameters forisMintable
andisBurnable
to specify whether the new token should support these features. - A Uniswap pair will be created automatically for the new token.
- Users can call the
Here’s how you might call the deployToken
function to create a mintable and burnable token:
tokenFactory.deployToken{value: 0.02 ether}("MyToken", "MTK", 1000000 * 10 ** 18, true, true, WETH);
- Customizability: The new version of the token contract allows users to decide whether their token can be minted or burned.
- Security: Ensure that your contracts are thoroughly tested, especially when adding features like minting, which can affect the total supply of the token.
With these changes, the TokenFactory
can deploy a wider variety of tokens, each tailored to the specific needs of the user, including options for minting and burning.
To create a React Native UI/UX with HTML and CSS that allows users to input a token's supply, name, symbol, and check options for isMintable
and isBurnable
, I'll guide you through setting up the basic structure, form, and interaction with the blockchain to deploy the token using the provided ABI.
-
Install React Native CLI: If you haven't already, you can install React Native CLI by running:
npm install -g react-native-cli
-
Create a New React Native Project:
npx react-native init TokenFactoryApp
-
Navigate to the Project Directory:
cd TokenFactoryApp
-
Install Required Dependencies: Install the necessary packages:
npm install web3 ethers
Inside the App.js
file, we will create a form where users can input the token name, symbol, initial supply, and select the mintable and burnable options.
import React, { useState } from 'react';
import { StyleSheet, Text, TextInput, View, Button, CheckBox, Alert } from 'react-native';
import { ethers } from 'ethers';
import abi from './TokenFactoryABI.json'; // Make sure to include the ABI file
export default function App() {
const [name, setName] = useState('');
const [symbol, setSymbol] = useState('');
const [supply, setSupply] = useState('');
const [isMintable, setIsMintable] = useState(false);
const [isBurnable, setIsBurnable] = useState(false);
const deployToken = async () => {
try {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contractAddress = "0xYourTokenFactoryAddress"; // Replace with your deployed factory contract address
const contract = new ethers.Contract(contractAddress, abi, signer);
const tx = await contract.deployToken(
name,
symbol,
ethers.utils.parseUnits(supply, 18),
isMintable,
isBurnable,
"0xYourBaseTokenAddress", // Replace with your base token address like WETH
{ value: ethers.utils.parseEther("0.02") }
);
await tx.wait();
Alert.alert('Token Deployed', `Transaction Hash: ${tx.hash}`);
} catch (error) {
Alert.alert('Error', error.message);
}
};
return (
<View style={styles.container}>
<Text style={styles.header}>Deploy Your ERC20 Token</Text>
<TextInput
style={styles.input}
placeholder="Token Name"
value={name}
onChangeText={setName}
/>
<TextInput
style={styles.input}
placeholder="Token Symbol"
value={symbol}
onChangeText={setSymbol}
/>
<TextInput
style={styles.input}
placeholder="Initial Supply"
value={supply}
keyboardType="numeric"
onChangeText={setSupply}
/>
<View style={styles.checkboxContainer}>
<CheckBox
value={isMintable}
onValueChange={setIsMintable}
/>
<Text style={styles.label}>Mintable</Text>
</View>
<View style={styles.checkboxContainer}>
<CheckBox
value={isBurnable}
onValueChange={setIsBurnable}
/>
<Text style={styles.label}>Burnable</Text>
</View>
<Button title="Deploy Token" onPress={deployToken} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 16,
backgroundColor: '#f8f9fa',
},
header: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 24,
textAlign: 'center',
},
input: {
height: 40,
borderColor: '#ccc',
borderWidth: 1,
marginBottom: 12,
paddingLeft: 8,
borderRadius: 4,
backgroundColor: '#fff',
},
checkboxContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
label: {
marginLeft: 8,
},
});
-
Create the ABI File:
- Save your contract ABI in a JSON file, e.g.,
TokenFactoryABI.json
, and place it in your project directory.
- Save your contract ABI in a JSON file, e.g.,
-
Replace the Placeholder Addresses:
- Make sure to replace
"0xYourTokenFactoryAddress"
with the actual address of your deployedTokenFactory
contract. - Replace
"0xYourBaseTokenAddress"
with the base token address (e.g., WETH) that you want to pair with.
- Make sure to replace
-
Running the Application:
- If you're running on an Android emulator, use:
npx react-native run-android
- For an iOS simulator, use:
npx react-native run-ios
- If you're running on an Android emulator, use:
This example assumes the user is accessing the DApp via a mobile browser with MetaMask installed. The Web3Provider
from ethers.js handles interactions with the blockchain.
- Testing: Before deploying on the mainnet, test thoroughly on a testnet like Ropsten or Rinkeby.
- UX Enhancements: You can add better error handling, loading states, and further customize the UI to improve user experience.
- Security: Always consider security best practices when interacting with smart contracts and handling user inputs.
This setup provides a basic UI/UX for deploying customizable ERC20 tokens using React Native, allowing users to input their desired token parameters and deploy them directly to the blockchain.
The ABI (Application Binary Interface) is a JSON object that describes how to interact with a smart contract on the Ethereum blockchain (or other EVM-compatible chains). It includes information about the contract's functions, their inputs, outputs, and events.
For the TokenFactory
contract we discussed earlier, the ABI would include details about the deployToken
function, which allows the user to deploy new tokens. Below is an example of what the ABI might look like for that contract:
This ABI corresponds to the TokenFactory
contract that deploys ownable ERC20 tokens with options for mintable and burnable features.
[
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "uint256",
"name": "initialSupply",
"type": "uint256"
},
{
"internalType": "bool",
"name": "isMintable",
"type": "bool"
},
{
"internalType": "bool",
"name": "isBurnable",
"type": "bool"
},
{
"internalType": "address",
"name": "baseToken",
"type": "address"
}
],
"name": "deployToken",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "DEPLOYMENT_FEE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "uniswapFactory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_uniswapFactory",
"type": "address"
}
],
"name": "constructor",
"outputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "withdrawExcessETH",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"indexed": false,
"internalType": "uint256",
"name": "initialSupply",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "isMintable",
"type": "bool"
},
{
"indexed": false,
"internalType": "bool",
"name": "isBurnable",
"type": "bool"
},
{
"indexed": false,
"internalType": "address",
"name": "pairAddress",
"type": "address"
}
],
"name": "TokenDeployed",
"type": "event"
}
]
-
Functions:
-
deployToken
: The main function to deploy a new token. It accepts parameters likename
,symbol
,initialSupply
,isMintable
,isBurnable
, andbaseToken
. The function is marked aspayable
, meaning it can accept Ether, which is required for the deployment fee. -
DEPLOYMENT_FEE
,owner
,uniswapFactory
: These are view functions that return information about the contract's deployment fee, owner, and the Uniswap factory address. -
withdrawExcessETH
: A function to withdraw any excess ETH that might be in the contract. -
transferOwnership
: A function to transfer ownership of the contract.
-
-
Constructor:
constructor
: Initializes the contract with the Uniswap factory address.
-
Events:
TokenDeployed
: This event is emitted when a new token is deployed. It includes details such as the token's address, owner, name, symbol, initial supply, and whether it is mintable or burnable.
In your React Native app, you’ll use this ABI along with the contract address to interact with the TokenFactory
smart contract. The ABI is crucial for encoding the data required to call the contract's functions and for decoding the responses and events emitted by the contract.
Here’s how you would use the ABI in your React Native app:
import abi from './TokenFactoryABI.json';
// Example: Instantiate the contract using ethers.js
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract('0xYourTokenFactoryAddress', abi, signer);
Replace '0xYourTokenFactoryAddress'
with your deployed contract's address and ensure you have the correct ABI file to interact with the contract’s functions.