Building with Solidity

Smart Contract Actions and Safety

This lesson covers common smart contract actions - minting tokens, spending/locking assets, transfers, and safety functions like access controls - along with read-only queries that let frontends display contract data without gas costs.

Common Contract Actions

Smart contracts let you define logic that runs automatically when triggered by a transaction.

Here are some of the most common:

Action Type

What It Does

Minting

Creates new tokens or points for a user

Spending

Moves tokens or reduces a balance

Locking

Prevents access to something until a condition is met

Sending

Transfers tokens or NFTs from one address to another

In the Learn2Earn University contract, we’ve already talked about:

  • addStudent() spends as the user has use 1VET to activate this function.

  • issueCertificate() mints and creates a certificate

Minting

Minting is the process of creating something new onchain, typically tokens, NFTs, or points. In a Focus2Earn contract, when a user completes a focus session, the contract “mints” focus points by increasing their onchain balance.

users[msg.sender].points += rewardAmount;

This line mints new points for the user. No one has to “send” them; they’re generated by the logic of the contract itself.

Spending

Spending refers to reducing a user’s onchain balance, often as part of a payment or burn mechanism. This might be used in a game contract to deduct energy points or in a marketplace contract to deduct tokens when buying an item.

Example:

require(users[msg.sender].points >= cost, "Not enough points");
users[msg.sender].points -= cost;

This simple pattern enforces that users can only take certain actions if they have enough points, and then reduces their balance accordingly.

Locking

Smart contracts can lock access to functions or data until certain conditions are met. In the Focus2Earn contract, a cooldown period prevents users from earning rewards too frequently. This ensures fair use and prevents abuse.

modifier cooldownPassed() {
    require(block.timestamp >= users[msg.sender].lastSession + 5 minutes, "Cooldown active");
    _;
}

This is a form of time-based locking. The function will only execute if enough time has passed since the user’s last action.

Sending

Contracts can send tokens or NFTs from one wallet to another. This is one of the most powerful features of smart contracts, enabling decentralized transfers with custom rules, like only sending a reward if a condition is met.

token.transfer(msg.sender, rewardAmount);

In this example, the contract sends tokens to a user, but it could be set to trigger only if the user completes a task, proves a behavior, or passes a verification step.

Optional Logic: Cooldowns and Timers

In your Focus2Earn contract, you introduced time-based logic to prevent abuse.

Example:

require(block.timestamp >= lastSessionTimestamp[msg.sender] + cooldown, "Please wait before next session");

This ensures users can’t spam reward claims, all enforced onchain.

Queries and Read Functions

Not all contract functions change state. Some simply return data. These are known as read-only functions, and they’re marked with the view or pure keyword.

  • view: reads onchain data but doesn’t change it.

  • pure: doesn’t even read from storage — it only uses input parameters or internal calculations.

Example:

function getMyPoints() external view returns (uint256) {
    return users[msg.sender].points;
}

This allows a frontend to display a user's points without requiring a wallet signature.

They’re free to call from outside the blockchain. Frontends can use them to display contract data without requiring users to sign a transaction or pay gas.

Queries are essential for user-facing dApps. They let you:

  • Display account stats, balances, cooldown timers, etc.

  • Power leaderboards, dashboards, or activity logs

  • Keep your UI reactive and real-time without requiring blockchain writes

Safety Functions in Solidity

Safety functions are used to make your contract more robust and resistant to bugs, errors, or malicious misuse. They often enforce logical checks, error handling, and safe fallbacks.

Using require for Access Control and Validation

One of the most important tools in Solidity is the require statement. It ensures that specific conditions are met before continuing execution.

Example:

modifier onlyOwner() {
    require(msg.sender == contractOwner, "Only the contract owner can call this function");
    _;
}

This ensures that only the contract owner can proceed with certain actions. If the condition is not met, the transaction reverts with a helpful error message. You’ve already applied this pattern to restrict addUser.

Example: Event + Modifier + Struct

Here’s a complete function using everything above:

function addUser(string memory username, uint256 age, address userAddress) public onlyOwner {
  users[userAddress] = User(username, age, userAddress);
  emit NewUserAdded(username, age, userAddress);
}

It:

  • Checks that the caller is the owner

  • Stores a new User struct on-chain

  • Emits a public event

This pattern is the foundation of most secure smart contracts.