Ready. This template is published in uzolabs/templates and has been deployed and verified on testnet. It’s a learning template and hasn’t been audited. Use test funds only.
Project layout
Project layout
The contract
contracts/UzoToken.sol combines three OpenZeppelin contracts:
The constructor takes
(name, symbol, initialSupply, owner) and mints the initial supply to the owner. The deploy scripts pass the deployer as the owner.
Tests
test/UzoToken.t.sol holds 18 Solidity tests. forge test and hardhat test solidity both run the same file, so you write each test once.
Terminal
Two toolchains, one project
foundry.toml and hardhat.config.ts both point at contracts/ and test/, and both compile with Solidity 0.8.28 for the cancun EVM. Foundry finds imports through remappings.txt. Hardhat finds them in node_modules. OpenZeppelin and forge-std come from npm, so there are no git submodules to update.
Deploying
scripts/deploy.ts does the same work for either toolchain:
- Reads the network from
@uzolabs/sdk/chainsand checks that the RPC reports the chain ID you asked for. - Checks that your wallet has gas. On mainnet it also asks you to type
MAINNET. - Runs
forge script script/Deploy.s.sol, orhardhat ignition deploywith--tool hardhat. - Confirms there is code at the new address.
- Writes
deployments/968.json,frontend/.envandfrontend/src/abi.ts.
BOT Chain blocks have a base fee of 0. Hardhat Ignition reads that as free gas and would send a fee of 0, which the node rejects with “transaction underpriced”.
scripts/lib/ignition-fees.ts is a Hardhat network hook, registered as the uzo-ignition-fees plugin, that fills in the fee the RPC asks for (20 gwei on testnet). Foundry gets this right on its own.Verifying
scripts/verify.ts reads the address and constructor arguments from deployments/968.json and prints the command it runs. With Foundry:
Terminal
Terminal
Sending from the command line
Terminal
scripts/transfer.ts sends 25 tokens from your .env wallet and prints both balances before and after.
The web app
Each transaction shows “confirm in wallet”, then “pending” with a BOTScan link, then “confirmed” or a readable error. Status lines sit in an
aria-live region so screen readers announce them.
The app doesn’t subscribe to events, because BOT Chain’s public RPCs don’t serve eth_getLogs. See Events without getLogs.
Using a local node
SetVITE_RPC_URL in frontend/.env to point the web app at another RPC, such as anvil --chain-id 968. The batched reads go through Multicall3, which a fresh anvil doesn’t have. Copy its code from testnet with cast code and set it with anvil_setCode.
Next steps
Customize
Change the supply rules, add burning or a cap.
USDT and decimals
Why decimals matter when you work with amounts.