Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "cronos-contract-boilerplate/foundry/lib/forge-std"]
path = cronos-contract-boilerplate/foundry/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "cronos-contract-boilerplate/foundry/lib/openzeppelin-contracts"]
path = cronos-contract-boilerplate/foundry/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
10 changes: 10 additions & 0 deletions cronos-contract-boilerplate/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Environment
.env

# IDE / OS
.idea
.vscode
.DS_Store

# Dependencies
node_modules
46 changes: 46 additions & 0 deletions cronos-contract-boilerplate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Cronos contract boilerplate

Example ERC20, ERC721 and ERC1155 contracts for the Cronos EVM chain, provided for three toolchains. Every subproject ships the same three contracts and can deploy and verify them on Cronos mainnet (chain id 25) and testnet (chain id 338).

All contracts use OpenZeppelin `^5.6.0`, Solidity `0.8.28` and `evmVersion: cancun`. The contract sources are identical across all three.

## Subprojects

| Folder | Toolchain | Deploy | Verify |
| --- | --- | --- | --- |
| [`foundry`](foundry/README.md) | Foundry (forge) | `forge script` | `forge verify-contract` |
| [`hardhat/v3`](hardhat/v3/README.md) | Hardhat 3 (viem + Ignition) | Ignition modules | `hardhat verify` |
| [`hardhat/v2`](hardhat/v2/README.md) | Hardhat 2 (ethers + TypeScript) | `hardhat run` scripts | `hardhat verify` |

Each folder is independent and has its own README, dependencies and setup steps.

## Tests

Every subproject ships the same test suite for all three contracts, covering
metadata, the initial supply, minting under `MINTER_ROLE`, rejection of minting
without that role, and the pause guard.

| Folder | Command | Local chain |
| --- | --- | --- |
| `foundry` | `forge test` | in-process (forge) |
| `hardhat/v3` | `npx hardhat test` | in-process (Hardhat) |
| `hardhat/v2` | `npx hardhat test` | in-process (Hardhat) |

None of them need RPC access, a funded wallet or a `.env` file.

## Contracts

- `MyERC20.sol` - ERC20 with Burnable, Pausable, Permit, AccessControl and Ownable.
- `MyERC721.sol` - ERC721 with URIStorage, Burnable, Pausable, AccessControl and Ownable.
- `MyERC1155.sol` - ERC1155 with Burnable, Pausable, Supply, AccessControl and Ownable.

## Cronos networks

| Network | Chain ID | RPC | Explorer |
| --- | --- | --- | --- |
| Mainnet | 25 | https://evm.cronos.com/ | https://explorer.cronos.com |
| Testnet | 338 | https://evm-t3.cronos.com/ | https://explorer.cronos.com/testnet |

## Verification

Both Hardhat and Foundry verify programmatically against the Cronos Explorer API. The API key is passed either inside the explorer apiUrl (Hardhat) or with `--etherscan-api-key` (Foundry). See the [Cronos Explorer API documentation](https://explorer-api-doc.cronos.org/testnet/) for how to get a key.
5 changes: 5 additions & 0 deletions cronos-contract-boilerplate/foundry/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Private key of the deployment wallet (hex, with or without 0x prefix)
PRIVATE_KEY=
# Block explorer API keys - used to verify contracts on the Cronos Explorer
CRONOS_EXPLORER_MAINNET_API_KEY=
CRONOS_EXPLORER_TESTNET_API_KEY=
14 changes: 14 additions & 0 deletions cronos-contract-boilerplate/foundry/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Foundry output
out
cache
broadcast

# Dependencies
lib

.env

# IDE
.idea
.DS_Store
.vscode
76 changes: 76 additions & 0 deletions cronos-contract-boilerplate/foundry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Cronos contract boilerplate: Foundry

Example ERC20, ERC721 and ERC1155 contracts for Cronos EVM, built with Foundry (forge).

Contracts use OpenZeppelin `^5.6.0`, Solidity `0.8.28` and `evmVersion: cancun`.

## Contracts

- `src/MyERC20.sol` - ERC20 with Burnable, Pausable, Permit, AccessControl and Ownable.
- `src/MyERC721.sol` - ERC721 with URIStorage, Burnable, Pausable, AccessControl and Ownable.
- `src/MyERC1155.sol` - ERC1155 with Burnable, Pausable, Supply, AccessControl and Ownable.

## Setup

Install Foundry if you have not already (see the [Foundry book](https://book.getfoundry.sh/getting-started/installation)):

```bash
curl -L https://foundry.paradigm.xyz | bash
foundryup
```

Install the dependencies:

```bash
forge install foundry-rs/forge-std@v1.16.2
forge install OpenZeppelin/openzeppelin-contracts@v5.6.0
```

The `@openzeppelin/` remapping is already set in `foundry.toml`.

Copy the environment file and fill it in:

```bash
cp .env.example .env
source .env
```

- `PRIVATE_KEY` - private key of the deployment wallet (needs CRO for gas).
- `CRONOS_EXPLORER_MAINNET_API_KEY` / `CRONOS_EXPLORER_TESTNET_API_KEY` - explorer API keys for verification.

See the [Cronos Explorer API documentation](https://docs.cronos.com/block-explorers/block-explorer-and-api-keys) for how to get an API key.

## Build and test

```bash
forge build
forge test
```

## Deploy

```bash
forge script script/MyERC20.s.sol:MyERC20Script --rpc-url cronos_testnet --broadcast
forge script script/MyERC721.s.sol:MyERC721Script --rpc-url cronos_testnet --broadcast
forge script script/MyERC1155.s.sol:MyERC1155Script --rpc-url cronos_testnet --broadcast
```

Swap `cronos_testnet` for `cronos_mainnet` to deploy to mainnet.

## Verify

```bash
forge verify-contract <ADDRESS> src/MyERC20.sol:MyERC20 \
--chain-id 338 \
--verifier-url https://explorer-api.cronos.org/testnet/api/v2 \
--etherscan-api-key $CRONOS_EXPLORER_TESTNET_API_KEY \
--constructor-args $(cast abi-encode "constructor(string,string)" "My token name" "My token symbol")
```

`MyERC721` takes no constructor arguments (drop `--constructor-args`), and `MyERC1155` takes the metadata URI:

```bash
--constructor-args $(cast abi-encode "constructor(string)" "https://example.com/api/token/{id}.json")
```

For mainnet use `--chain-id 25`, `--verifier-url https://explorer-api.cronos.org/mainnet/api/v2` and `$CRONOS_EXPLORER_MAINNET_API_KEY`.
14 changes: 14 additions & 0 deletions cronos-contract-boilerplate/foundry/foundry.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"lib/forge-std": {
"tag": {
"name": "v1.16.2",
"rev": "bf647bd6046f2f7da30d0c2bf435e5c76a780c1b"
}
},
"lib/openzeppelin-contracts": {
"tag": {
"name": "v5.6.0",
"rev": "56a3de2cea907c9a500d32e70c275f68393b7ba6"
}
}
}
20 changes: 20 additions & 0 deletions cronos-contract-boilerplate/foundry/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.28"
evm_version = "cancun"
optimizer = true
optimizer_runs = 200
remappings = ["@openzeppelin/=lib/openzeppelin-contracts/"]

# Cronos EVM RPC endpoints
[rpc_endpoints]
cronos_mainnet = "https://evm.cronos.com/"
cronos_testnet = "https://evm-t3.cronos.com/"

# Cronos Explorer verification endpoints.
# The API key is passed on the command line with --etherscan-api-key.
[etherscan]
cronos_mainnet = { key = "${CRONOS_EXPLORER_MAINNET_API_KEY}", chain = 25, url = "https://explorer-api.cronos.org/mainnet/api/v2" }
cronos_testnet = { key = "${CRONOS_EXPLORER_TESTNET_API_KEY}", chain = 338, url = "https://explorer-api.cronos.org/testnet/api/v2" }
21 changes: 21 additions & 0 deletions cronos-contract-boilerplate/foundry/script/MyERC1155.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Script, console} from "forge-std/Script.sol";
import {MyERC1155} from "../src/MyERC1155.sol";

// Deploy with:
// forge script script/MyERC1155.s.sol:MyERC1155Script --rpc-url cronos_testnet --broadcast
contract MyERC1155Script is Script {
string constant TOKEN_URI = "https://example.com/api/token/{id}.json";

function run() external returns (MyERC1155 token) {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");

vm.startBroadcast(deployerKey);
token = new MyERC1155(TOKEN_URI);
vm.stopBroadcast();

console.log("MyERC1155 deployed to:", address(token));
}
}
19 changes: 19 additions & 0 deletions cronos-contract-boilerplate/foundry/script/MyERC20.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Script, console} from "forge-std/Script.sol";
import {MyERC20} from "../src/MyERC20.sol";

// Deploy with:
// forge script script/MyERC20.s.sol:MyERC20Script --rpc-url cronos_testnet --broadcast
contract MyERC20Script is Script {
function run() external returns (MyERC20 token) {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");

vm.startBroadcast(deployerKey);
token = new MyERC20("My token name", "My token symbol");
vm.stopBroadcast();

console.log("MyERC20 deployed to:", address(token));
}
}
20 changes: 20 additions & 0 deletions cronos-contract-boilerplate/foundry/script/MyERC721.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Script, console} from "forge-std/Script.sol";
import {MyERC721} from "../src/MyERC721.sol";

// Deploy with:
// forge script script/MyERC721.s.sol:MyERC721Script --rpc-url cronos_testnet --broadcast
contract MyERC721Script is Script {
function run() external returns (MyERC721 token) {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");

vm.startBroadcast(deployerKey);
// MyERC721 constructor takes no arguments
token = new MyERC721();
vm.stopBroadcast();

console.log("MyERC721 deployed to:", address(token));
}
}
82 changes: 82 additions & 0 deletions cronos-contract-boilerplate/foundry/src/MyERC1155.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyERC1155 is
ERC1155,
ERC1155Burnable,
ERC1155Pausable,
ERC1155Supply,
AccessControl,
Ownable
{
bytes32 public constant CONTROLLER_ROLE = keccak256("CONTROLLER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

constructor(string memory uri) ERC1155(uri) Ownable(_msgSender()) {
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
_grantRole(CONTROLLER_ROLE, _msgSender());
_grantRole(MINTER_ROLE, _msgSender());
}

function getControllerRole() public view virtual returns (bytes32) {
return CONTROLLER_ROLE;
}

function getMinterRole() public view virtual returns (bytes32) {
return MINTER_ROLE;
}

function setURI(string memory newuri) public onlyRole(CONTROLLER_ROLE) {
_setURI(newuri);
}

function mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) public onlyRole(MINTER_ROLE) {
_mint(to, id, amount, data);
}

function mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public onlyRole(MINTER_ROLE) {
_mintBatch(to, ids, amounts, data);
}

function pause() public onlyRole(CONTROLLER_ROLE) {
_pause();
}

function unpause() public onlyRole(CONTROLLER_ROLE) {
_unpause();
}

// The following functions are overrides required by Solidity.

function _update(
address from,
address to,
uint256[] memory ids,
uint256[] memory values
) internal override(ERC1155, ERC1155Pausable, ERC1155Supply) {
super._update(from, to, ids, values);
}

function supportsInterface(
bytes4 interfaceId
) public view override(ERC1155, AccessControl) returns (bool) {
return super.supportsInterface(interfaceId);
}
}
Loading