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.
103 lines
4.6 KiB
Solidity
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; }
|
|
}
|