Comment on page
How to Create and Deploy an XRC20 Token Using Foundry
This tutorial’s purpose is to create an XRC-20 token with foundry that will return a royalty amount (of that XRC-20 token) to the original creator of the contract.

image
Foundry manages your dependencies, compiles your project, runs tests, deploys, and lets you interact with the chain from the command-line and via Solidity scripts.
In this tutorial, you will learn how to set up Foundry and use it to build, test and deploy a XRC20 Royalty Token on both the XDC Network mainnet and XDC Apothem testnet.
- Install and setup Foundry
- Create an XRC20 Royalty token
- Compile the Contract
- Deploy the Contract
- Interact with the XRC20 Royalty token
- Foundry : a fast Solidity development toolkit that enables developers to write their tests in Solidity.
- Visual Studio Code : a source-code editor made by Microsoft with the Electron Framework, for Windows, Linux and macOS.
- XDCPay : is an extension for accessing XDC's XDPoS enabled distributed applications, or "Dapps" in your browser.
Before we can start writing some code, we need to set up our environment. We're going to be writing our contracts and testing them in Foundry.
Open-up terminal and type in the command:
curl -L https://foundry.paradigm.xyz | bash
Afterward type:
foundryup
Foundry should now be installed and ready to go!
If using Windows, you need to install Foundry from the source. First, install Rust with the official documentation.
Then, open the command prompt and type in the command:
cargo install --git https://github.com/foundry-rs/foundry foundry-cli anvil --bins --locked
To update from source, run the same command again.
Now that we've installed Foundry, it's time to set up our folder where we will write our smart contract. From the same terminal window that you installed Foundry, type the following commands:
- 1.Make our folder where will we initialize our project. Then navigate into that folder with the following commands:
mkdir XRC20_Royalty && cd XRC20_Royalty
- 1.Initialize our Foundry project within our XRC20_Royalty folder:
forge init
- 1.Install Solmate into our Foundry project:
forge install rari-capital/solmate
- 1.Create a remappings.txt file for the Solmate library we just added:
touch remappings.txt
- 1.Open up your project in your IDE. For this tutorial, we’ll be using VSCode with this Solidity plugin:
code .
Here’s what our IDE looks like.

image
- 1.Add these lines to remappings.txt so we can easily call the Solmate library in our contract:
solmate/=lib/solmate/src/
forge-std=lib/forge-std/src/
Now that our environment and libraries are set up, we'll move into developing our smart contract!
We will make a contract that passes tokens to the original contract creator whenever a token is transferred between wallets!
- 1.In your IDE, navigate to
src/Counter.sol
and rename the file toRoyaltyToken.sol
. - 2.Import the Solmate ERC20 library in
RoyaltyToken.sol
and change the name of the contract. Underpragma solidity ^0.8.14;
, add the following lines of code:
import { ERC20 } from "solmate/tokens/ERC20.sol";
contract RoyaltyToken is ERC20 {}
- 1.Add in our state variables for the royalties. In the contract, add an address
royaltyAddress
variable and uint256royaltyFeePercentage
variable:
contract RoyaltyToken is ERC20 {
address public royaltyAddress;
uint256 public royaltyFeePercentage;
}
- 1.Make a constructor for the token. A constructor is what creates our token from the imported Solmate template.
Add the following variables to the constructor:
- 1.
string memory _name
- 2.
string memory _token
- 3.
uint8 _decimals
- 4.
uint256 _royaltyFeePercentage
- 5.
uint256 _initialSupply
Directly after we’ve added these variables and closed the()
, addERC20(_name, _symbol, _decimals)
.
After that add brackets
{}
and inside the brackets set the following variables:Set
royaltyAddress
variable as the wallet address of the creator of the contract: royaltyAddress = msg.sender;
The RoyaltyFeePercentage
as the constructor variable: royaltyFeePercentage = _royaltyFeePercentage;
Mint the tokens to the creator of the contract and pass in the _initialSupply
variable: _mint(msg.sender, _initialSupply);
Our constructor should now look like the following:contract RoyaltyToken is ERC20 {
address public royaltyAddress;
uint256 public royaltyFeePercentage;
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
uint256 _royaltyFeePercentage,
uint256 _initialSupply
) ERC20(_name, _symbol, _decimals) {
royaltyAddress = msg.sender;
royaltyFeePercentage = _royaltyFeePercentage;
_mint(msg.sender, _initialSupply);
}
}
- 1.Next, override the transfer function.
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
Our contract now looks like this:
pragma solidity ^0.8.14;
import { ERC20 } from "solmate/tokens/ERC20.sol";
contract RoyaltyToken is ERC20 {
address public royaltyAddress;
uint256 public royaltyFeePercentage;
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
uint256 _royaltyFeePercentage,
uint256 _initialSupply
) ERC20(_name, _symbol, _decimals) {
royaltyAddress = msg.sender;
royaltyFeePercentage = _royaltyFeePercentage;
_mint(msg.sender, _initialSupply);
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
}
Add override after virtual in the function declaration:
function transfer(address to, uint256 amount) public virtual override returns (bool) { ... }
Inside of the transfer
function, create a uint256 called royaltyAmount
and set it equal to the amount in the function parameters multiplied by the royaltyFeePercentage
divided by 100
. This calculates the royalty amount that we will be sending to our royaltyAddress
.uint256 royaltyAmount = amount * royaltyFeePercentage / 100;
function transfer(address to, uint256 amount) public virtual returns (bool) {
uint256 royaltyAmount = amount * royaltyFeePercentage / 100;
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
In the
unchecked {}
for the balanceOf[to]
, subtract the amount by the royaltyAmount
and add an additional balanceOf[royaltyAddress]
where we add the royaltyAmount
:function transfer(address to, uint256 amount) public virtual returns (bool) {
uint256 royaltyAmount = amount * royaltyFeePercentage / 100;
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
//subtract the amount by the royalty amount
balanceOf[to] += amount - royaltyAmount;
//add to the royaltyAddress wallet the royaltyAmount
balanceOf[royaltyAddress] += royaltyAmount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
Add an additional emit Transfer where we send the royaltyAddress the royaltyAmount. Additionally, subtract the original emit Transfer amount by the royaltyAmount:
function transfer(address to, uint256 amount) public virtual override returns (bool) {
uint256 royaltyAmount = amount * royaltyFeePercentage / 100;
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
//subtract the amount by the royalty amount
balanceOf[to] += amount - royaltyAmount;
//add to the royaltyAddress wallet the royaltyAmount
balanceOf[royaltyAddress] += royaltyAmount;
}
//transfer to the royalty address
emit Transfer(msg.sender, royaltyAddress, royaltyAmount);
//transfer to the original address
emit Transfer(msg.sender, to, amount - royaltyAmount);
return true;
}
- 1.Our contract is now finished! In total it should look like this:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;
import { ERC20 } from "solmate/tokens/ERC20.sol";
contract RoyaltyToken is ERC20 {
address public royaltyAddress;
uint256 public royaltyFeePercentage;
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
uint256 _royaltyFeePercentage,
uint256 _initialSupply
) ERC20(_name, _symbol, _decimals) {
royaltyAddress = msg.sender;
royaltyFeePercentage = _royaltyFeePercentage;
_mint(msg.sender, _initialSupply);
}
function transferWithRoyalty (address to, uint256 amount) public returns (bool) {
uint256 royaltyAmount = amount * royaltyFeePercentage / 100;
transfer(royaltyAddress, royaltyAmount);
transfer(to, amount - royaltyAmount);
return true;
}
function transfer(address to, uint256 amount) public virtual override returns (bool) {
uint256 royaltyAmount = amount * royaltyFeePercentage / 100;
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount - royaltyAmount;
balanceOf[royaltyAddress] += royaltyAmount;
}
//transfer to the royalty address
emit Transfer(msg.sender, royaltyAddress, royaltyAmount);
//transfer to the original address
emit Transfer(msg.sender, to, amount - royaltyAmount);
return true;
}
}
Thanks to Foundry, we can test our new
RoyaltyToken.sol
contract in Solidity!Set up your test contract
- 1.In your IDE, head to
test/Counter.t.sol
and rename the file toRoyaltyToken.t.sol
. - 2.Delete everything in the original body of
RoyaltyToken.t.sol
. - 3.Add our solidity version to the top:
pragma solidity ^0.8.14;
. - 4.Import the RoyaltyToken from
royaltyToken.sol
. Addimport {RoyaltyToken} from "src//RoyaltyToken.sol";
to the top of your contract. - 5.Import forge testing tools:
import "forge-std/Test.sol";
. - 6.Make a new contract called
RoyaltyTokenTest
and set it to aTest
. Our contract should look like this.
pragma solidity ^0.8.14;
import {RoyaltyToken} from "src//RoyaltyToken.sol";
import "forge-std/Test.sol";
contract RoyaltyTokenTest is Test {}
- 1.Create your
RoyaltyToken
,RoyaltyFeePercentage
, andInitialSupply
arguments. For this test, we will be using2%
for the fee and10,000
initial tokens:
pragma solidity ^0.8.14;
import {RoyaltyToken} from "src//RoyaltyToken.sol";
import "forge-std/Test.sol";
contract RoyaltyTokenTest is Test {
RoyaltyToken public token;
uint256 public royaltyFeePercentage = 2;
uint256 public initialSupply = 10 ** 4;
}
- 1.Create a
setUp()
function that constructs ourRoyaltyToken
.
pragma solidity ^0.8.14;
import {RoyaltyToken} from "src//RoyaltyToken.sol";
import "forge-std/Test.sol";
contract RoyaltyTokenTest is Test {
RoyaltyToken public token;
uint256 public royaltyFeePercentage = 2;
uint256 public initialSupply = 10 ** 4;
function setUp() public {
token = new RoyaltyToken("RoyaltyToken", "ROYT", 18, royaltyFeePercentage, initialSupply);
}
}
- 1.Create a
testTransfer()
function that makes two dummy addresses and transfers funds between them. We will transfer 1,000 of the 10,000 tokens we created to an address. We're then going to check whether the address received 980 of those 1,000 tokens and whether our original contract address received the other 20. Afterward, we will initiate a transfer of 100 tokens between the newly created address and another wallet. We'll then check whether all 3 of the wallets have the correct amounts.
function testTransfer() public {
address alice = address(1);
address bob = address(2);
token.transfer(alice, 1000);
assertEq(token.balanceOf(alice), 980);
assertEq(token.balanceOf(address(this)), 9020);
hoax(alice);
token.transfer(bob, 100);
assertEq(token.balanceOf(alice), 880);
assertEq(token.balanceOf(bob), 98);
assertEq(token.balanceOf(address(this)), 9022);
}
- 1.Our entire contract should look like this:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;
import {RoyaltyToken} from "src//RoyaltyToken.sol";
import "forge-std/Test.sol";
contract RoyaltyTokenTest is Test {
RoyaltyToken public token;
uint256 public royaltyFeePercentage = 2;
uint256 public initialSupply = 10 ** 4;
function setUp() public {
token = new RoyaltyToken("RoyaltyToken", "ROYT", 18, royaltyFeePercentage, initialSupply);
}
function testTransfer() public {
address alice = address(1);
address bob = address(2);
token.transfer(alice, 1000);
assertEq(token.balanceOf(alice), 980);
assertEq(token.balanceOf(address(this)), 9020);
hoax(alice);
token.transfer(bob, 100);
assertEq(token.balanceOf(alice), 880);
assertEq(token.balanceOf(bob), 98);
assertEq(token.balanceOf(address(this)), 9022);
}
}
- 1.Now, let’s compile our contract. Open up that terminal window we used earlier and type the command
forge build
.
forge build
Compiling... [⠰] Compiling 19 files with 0.8.14 [⠒] Solc 0.8.14 finished in 2.00s Compiler run successful
Our smart contract is finished and is correctly compiling! Now let's test our smart contract to make sure it's actually doing what we want it to do.
- 1.Open up terminal and run
forge test
. This runs our tests and helps us understand whether or not they passed.
forge test
`Compiling... No files changed, compilation skipped
Running 1 test for test/RoyaltyToken.t.sol:RoyaltyTokenTest [PASS] testTransfer() (gas: 78242) Test result: ok. 1 passed; 0 failed; finished in 1.45ms`
If all goes well, you've just successfully made a smart contract in foundry, overrode the original transfer function, and ran some successful tests! Now, it's time to deploy the contract.
It's time for us to deploy our smart contract to the blockchain.
- 1.Open up terminal and run
cast wallet new
- Create a new random keypair.
cast wallet new
Successfully created new keypair. Address: 0x80B75825D86a005453A08cD1a6Bd44C24d73A41d Private Key: 0x4159ae5d34bb48367f9773c48de0e0effb2082681a69cd95f4e613246720af24
- 1.Now Copy the address and go to XDC faucet for test XDC, And replace
0x
by xdc.Click on request 1000 XDC.

image
We've got everything we need to deploy our contract to the blockchain now.
Head back to your terminal window to complete deployment.
- 1.Open up the terminal and type the following command, replacing [PASTE YOUR PRIVATE KEY HERE]
forge create --rpc-url https://erpc.apothem.network --private-key [PASTE YOUR PRIVATE KEY HERE] src/RoyaltyToken.sol:RoyaltyToken --constructor-args "RoyaltyToken" "ROYT" 18 2 10000000000000000000000 --legacy
Compiling... No files changed, compilation skipped Deployer: 0x80B75825D86a005453A08cD1a6Bd44C24d73A41d Deployed to: 0x27f4D21150640df0856fF6CB5d57eB4447CC59AD Transaction hash: 0x89f0edbf4755e93b8d25857e5a0fa0f7cae414cefac42e6ec9cb02930a932d41
We can now see that our contract is deployed to the blockchain. If I copy the address in Deployed to, we can view the contract on explorer.

image
Once you have successfully deployed your smart contract to the blockchain, it might be interesting to verify you contract on Apothem Block Explorer.
- 1.Flatten our smart contract, Open up the terminal and type the following command
forge flatten --output src/Contract.flattened.sol src/RoyaltyToken.sol
Flattened file written at src/Contract.flattened.sol
Now open the Contract.flattened.sol
file and copy all source code, go to the block explorer and click on Verify and Publish. 

image

image

image
If everything is correctly filled out, your contract page on the block explorer should display a new tab called Contract.
For more information about XDC Network, Please Visit XDC Network Documention.
For more information about Foundry, Please Visit Foundry Book.
XDC Network Disocrd.