aere-contracts/contracts/season1/AereQuestCheckIn.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
2026-07-20 01:02:37 +03:00

103 lines
4.6 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "./interfaces/ISeason1.sol";
/**
* @title AereQuestCheckIn — "First Flight" soulbound attestation
* @notice A SOULBOUND (non-transferable) ERC-721-shaped attestation minted
* exactly once per account. It is the on-chain "First Flight" marker of
* Season 1 onboarding, intended to be minted via an
* AereOnboardingPaymaster-sponsored UserOp so the user pays no gas.
*
* Soulbound: tokenId is deterministically uint256(uint160(account)), so an
* account can hold at most one, and it can never be transferred — every
* transfer/approval entrypoint HARD-REVERTS (Soulbound).
*
* ERC-721 read surface (ownerOf/balanceOf/tokenURI/supportsInterface) and the
* mint Transfer event are provided so wallets and the AERE indexer can render
* the badge. There is no burn (the attestation is permanent).
*
* No owner / no admin: the only state transition is a one-shot self check-in.
* The referral registry reads `hasCheckedIn` as one of its activation gates.
*/
contract AereQuestCheckIn is IERC165, IAereQuestCheckIn {
string public constant name = "AERE First Flight";
string public constant symbol = "FLIGHT";
/// @notice account => minted.
mapping(address => bool) private _minted;
uint256 public totalCheckedIn;
/// @dev ERC-721 events (mint only; no transfers ever happen).
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event CheckedIn(address indexed account, uint256 tokenId, uint256 timestamp);
error Soulbound();
error AlreadyCheckedIn();
error NotMinted();
error ZeroAccount();
/* ------------------------------- check-in ------------------------------- */
/// @notice Mint the caller's First-Flight attestation. One per account.
/// In production this is called by the user's (passkey) smart
/// account inside a paymaster-sponsored UserOp, so msg.sender is
/// that account and the badge is bound to it.
function checkIn() external returns (uint256 tokenId) {
return _checkIn(msg.sender);
}
function _checkIn(address account) internal returns (uint256 tokenId) {
if (account == address(0)) revert ZeroAccount();
if (_minted[account]) revert AlreadyCheckedIn();
_minted[account] = true;
totalCheckedIn += 1;
tokenId = uint256(uint160(account));
emit Transfer(address(0), account, tokenId);
emit CheckedIn(account, tokenId, block.timestamp);
}
/* --------------------------------- views -------------------------------- */
function hasCheckedIn(address account) external view returns (bool) {
return _minted[account];
}
function balanceOf(address account) external view returns (uint256) {
if (account == address(0)) revert ZeroAccount();
return _minted[account] ? 1 : 0;
}
function ownerOf(uint256 tokenId) public view returns (address) {
address account = address(uint160(tokenId));
if (!_minted[account]) revert NotMinted();
return account;
}
function tokenURI(uint256 tokenId) external view returns (string memory) {
ownerOf(tokenId); // reverts NotMinted if absent
// Static on-chain metadata; no external hosting dependency.
return "data:application/json;base64,eyJuYW1lIjoiQUVSRSBGaXJzdCBGbGlnaHQiLCJkZXNjcmlwdGlvbiI6IlNvdWxib3VuZCBTZWFzb24gMSBBbHRpdHVkZSBjaGVjay1pbi4gUG9pbnRzIGFyZSBub3QgYSB0b2tlbiBhbmQgaGF2ZSBubyBndWFyYW50ZWVkIHZhbHVlLiJ9";
}
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
return
interfaceId == type(IERC165).interfaceId ||
interfaceId == 0x80ac58cd || // ERC-721
interfaceId == 0x5b5e139f; // ERC-721 Metadata
}
/* --------------------------- bricked ERC-721 write ---------------------- */
function transferFrom(address, address, uint256) external pure { revert Soulbound(); }
function safeTransferFrom(address, address, uint256) external pure { revert Soulbound(); }
function safeTransferFrom(address, address, uint256, bytes calldata) external pure { revert Soulbound(); }
function approve(address, uint256) external pure { revert Soulbound(); }
function setApprovalForAll(address, bool) external pure { revert Soulbound(); }
function getApproved(uint256) external pure returns (address) { return address(0); }
function isApprovedForAll(address, address) external pure returns (bool) { return false; }
}