Solidity gives you built‑in tools that tell you who called, when, and with how much value.
Key globals include:
msg.sender: the caller of the function.
msg.value: how much VET was sent.
block.timestamp: time of the current block.
For example, a paid registration function uses these tools directly:
function addStudent(string memory name, string memory familyName)
public
payable
{
require(msg.value == 1 ether, "You must pay 1 VET to register");
require(!students[msg.sender].registered, "You are already registered");
students[msg.sender] = Student({
wallet: msg.sender,
name: name,
familyName: familyName,
registered: true,
graduated: false,
certificate: 0
});
}function addStudent(string memory name, string memory familyName)
public
payable
{
require(msg.value == 1 ether, "You must pay 1 VET to register");
require(!students[msg.sender].registered, "You are already registered");
students[msg.sender] = Student({
wallet: msg.sender,
name: name,
familyName: familyName,
registered: true,
graduated: false,
certificate: 0
});
}function addStudent(string memory name, string memory familyName)
public
payable
{
require(msg.value == 1 ether, "You must pay 1 VET to register");
require(!students[msg.sender].registered, "You are already registered");
students[msg.sender] = Student({
wallet: msg.sender,
name: name,
familyName: familyName,
registered: true,
graduated: false,
certificate: 0
});
}require is your main safety gate: if the condition fails, execution stops and state changes are reverted.
For more efficient, structured errors, you can define custom errors:
error UnauthorizedAccess(address caller);
modifier onlyOwner() {
if (msg.sender != contractOwner) {
revert UnauthorizedAccess(msg.sender);
}
_;
}error UnauthorizedAccess(address caller);
modifier onlyOwner() {
if (msg.sender != contractOwner) {
revert UnauthorizedAccess(msg.sender);
}
_;
}error UnauthorizedAccess(address caller);
modifier onlyOwner() {
if (msg.sender != contractOwner) {
revert UnauthorizedAccess(msg.sender);
}
_;
}This lowers gas and makes it easier to decode failures in tools and explorers.
Solidity even supports try/catch for calls that might fail:
try this.getUser(msg.sender) returns (User memory user) {
return user;
} catch {
}try this.getUser(msg.sender) returns (User memory user) {
return user;
} catch {
}try this.getUser(msg.sender) returns (User memory user) {
return user;
} catch {
}Think of it as an internal fallback: instead of crashing the whole transaction, you can choose how to respond.
Using tx.origin for auth is dangerous; always prefer msg.sender.
Congratulations! You now know when to use msg.value, why tx.origin is rarely appropriate, and how structured errors and debugging.