Smart Contracts Guide
Learn how to create, deploy, and interact with smart contracts in Civra.
Introduction
Smart contracts are self-executing programs on the blockchain. Civra makes it easy to create, test, and deploy secure smart contracts.
Creating Your First Contract
Using AI
Simply describe what you want:
Create an ERC-20 token called MyToken with 1 million supplyCivra will generate:
- Complete Solidity contract
- Deployment script
- Basic tests
- Frontend integration
Starting from Templates
Browse pre-built templates:
- ERC-20 Token
- ERC-721 NFT
- ERC-1155 Multi-Token
- Staking Contract
- DAO Governance
- Marketplace
- MultiSig Wallet
Contract Standards
ERC-20 (Fungible Tokens)
Standard for cryptocurrencies and utility tokens.
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
constructor() ERC20("MyToken", "MTK") Ownable(msg.sender) {
_mint(msg.sender, 1000000 * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}ERC-721 (NFTs)
Standard for non-fungible tokens.
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721, ERC721URIStorage, Ownable {
uint256 private _tokenIdCounter;
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
function safeMint(address to, string memory uri) public onlyOwner {
uint256 tokenId = _tokenIdCounter++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}ERC-1155 (Multi-Token)
Standard for managing multiple token types.
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GameItems is ERC1155, Ownable {
uint256 public constant GOLD = 0;
uint256 public constant SILVER = 1;
uint256 public constant SWORD = 2;
constructor() ERC1155("https://game.example/api/item/{id}.json")
Ownable(msg.sender) {
_mint(msg.sender, GOLD, 10**18, "");
_mint(msg.sender, SILVER, 10**27, "");
_mint(msg.sender, SWORD, 1, "");
}
function mint(address account, uint256 id, uint256 amount)
public
onlyOwner
{
_mint(account, id, amount, "");
}
}Advanced Patterns
Staking Contract
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract StakingPool is ReentrancyGuard {
IERC20 public stakingToken;
IERC20 public rewardToken;
uint256 public rewardRate = 100; // tokens per second
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
mapping(address => uint256) public balances;
uint256 private _totalSupply;
constructor(address _stakingToken, address _rewardToken) {
stakingToken = IERC20(_stakingToken);
rewardToken = IERC20(_rewardToken);
}
function stake(uint256 amount) external nonReentrant {
require(amount > 0, "Cannot stake 0");
_totalSupply += amount;
balances[msg.sender] += amount;
stakingToken.transferFrom(msg.sender, address(this), amount);
}
function withdraw(uint256 amount) external nonReentrant {
require(amount > 0, "Cannot withdraw 0");
_totalSupply -= amount;
balances[msg.sender] -= amount;
stakingToken.transfer(msg.sender, amount);
}
function claimReward() external nonReentrant {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardToken.transfer(msg.sender, reward);
}
}
}Marketplace Contract
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract NFTMarketplace is ReentrancyGuard {
struct Listing {
address seller;
uint256 price;
bool active;
}
mapping(address => mapping(uint256 => Listing)) public listings;
uint256 public feePercent = 2; // 2% fee
event Listed(
address indexed nftContract,
uint256 indexed tokenId,
address seller,
uint256 price
);
event Sold(
address indexed nftContract,
uint256 indexed tokenId,
address seller,
address buyer,
uint256 price
);
function list(address nftContract, uint256 tokenId, uint256 price)
external
{
require(price > 0, "Price must be > 0");
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
listings[nftContract][tokenId] = Listing({
seller: msg.sender,
price: price,
active: true
});
emit Listed(nftContract, tokenId, msg.sender, price);
}
function buy(address nftContract, uint256 tokenId)
external
payable
nonReentrant
{
Listing memory listing = listings[nftContract][tokenId];
require(listing.active, "Not listed");
require(msg.value >= listing.price, "Insufficient payment");
uint256 fee = (listing.price * feePercent) / 100;
uint256 sellerProceeds = listing.price - fee;
listings[nftContract][tokenId].active = false;
IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
payable(listing.seller).transfer(sellerProceeds);
emit Sold(nftContract, tokenId, listing.seller, msg.sender, listing.price);
}
}Security Best Practices
Access Control
solidity
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract SecureContract is AccessControl {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ADMIN_ROLE, msg.sender);
}
function adminFunction() public onlyRole(ADMIN_ROLE) {
// Admin only
}
function mint() public onlyRole(MINTER_ROLE) {
// Minters only
}
}Reentrancy Protection
solidity
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureWithdraw is ReentrancyGuard {
function withdraw() external nonReentrant {
// Safe from reentrancy
}
}Input Validation
solidity
function transfer(address to, uint256 amount) public {
require(to != address(0), "Invalid address");
require(amount > 0, "Amount must be > 0");
require(balances[msg.sender] >= amount, "Insufficient balance");
// ... transfer logic
}Testing Contracts
Civra automatically generates tests:
javascript
describe("MyToken", function() {
it("Should mint initial supply", async function() {
const [owner] = await ethers.getSigners()
const MyToken = await ethers.getContractFactory("MyToken")
const token = await MyToken.deploy()
const balance = await token.balanceOf(owner.address)
expect(balance).to.equal(ethers.parseEther("1000000"))
})
})Deployment
Deploy to Testnet
- Select network (Sepolia, Mumbai, etc.)
- Review gas estimates
- Confirm deployment
- Get contract address
Verify on Etherscan
Civra automatically verifies your contracts:
bash
npx hardhat verify --network sepolia DEPLOYED_CONTRACT_ADDRESSGas Optimization
Tips
- Use
uint256instead of smaller uints - Pack variables in storage
- Use
calldatafor function parameters - Batch operations
- Use events instead of storage when possible
Example
solidity
// ❌ Inefficient
function badMint(address[] memory recipients) public {
for (uint i = 0; i < recipients.length; i++) {
_mint(recipients[i], 1);
}
}
// ✅ Optimized
function goodMint(address[] calldata recipients) public {
uint256 length = recipients.length;
for (uint256 i; i < length;) {
_mint(recipients[i], 1);
unchecked { ++i; }
}
}Common Issues
Transaction Failing?
- Check gas limits
- Verify approvals
- Confirm correct network
- Review contract state
High Gas Costs?
- Optimize loops
- Batch transactions
- Use events
- Pack storage
