Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/NexusHub.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IAzimuth} from "../src/Interfaces/IAzimuth.sol";
import {IPlanetDispenser} from "../src/Interfaces/IPlanetDispenser.sol";
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {ECDSA} from "../lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
import {MessageHashUtils} from "../lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol";
import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {SlugLib} from "../src/libraries/SlugLib.sol";
/**
* @title NexusHub
* @notice Centralized registry for Nexus instances
*/
contract NexusHub is ReentrancyGuard {
using SafeERC20 for IERC20;
///////////////
// Errors ////
//////////////
error UnauthorizedOwnerOrProxy();
error DeployerMustControlOwnershipPoint();
error CallerNotOwnerOfUrbitId();
error UrbitIdNotActive();
error CallerNotAuthorized();
error PointNotOwnedByCaller();
error PointNotControlledByCaller();
error UnauthorizedAccess();
error NoInvitesRemaining();
error BadRecipient();
error InsufficientUrbit();
error UrbitIdAlreadyBanned(uint32 urbitId);
error RecipientNotOwner();
error InvalidSigner();
error NotAMember(uint32 urbitId);
error AlreadyMember(uint32 urbitId);
error InvalidCount();
error InsufficientInvites();
error InsufficientUrbitBalance();
error BadWithdrawalAddress();
error InvitesRemain();
error NoUrbitToRetrieve();
error BadInviter();
error NexusAlreadyExists();
error NexusDoesNotExist();
error NexusDestroyed();
error SlugAlreadyUsed();
error EthTransferFailed();
///////////////////////
// Storage Variables //
///////////////////////
IAzimuth private immutable i_azimuth;
address private immutable i_urbitToken;
uint32 private _ownerId;
uint32 private _proposedOwnerId;
address private _planetMarket;
//////////////////////////////
// Per-nexus state & ledgers//
//////////////////////////////
struct NexusNode {
uint32 ownerId;
address nexusInviter;
uint32 inviteCount;
uint8 freeMonths;
bool isDestroyed;
uint256 price;
string nexusId;
bool allowManagementProxy;
uint32 totalInvitesIssued;
uint256 escrowUrbit; // $URBIT allocated to a nexus
bool exists;
}
// key: keccak256(ownerId, slug)
mapping(bytes32 => NexusNode) private _nodes;
// per-nexus membership flags
mapping(bytes32 => mapping(uint32 => bool)) private _isMember;
mapping(bytes32 => mapping(uint32 => bool)) private _isBanned;
mapping(bytes32 => mapping(uint32 => bool)) private _hasQuit;
// anti-replay nonces, scoped per (nexusKey, urbitId)
mapping(bytes32 => mapping(uint32 => uint256)) private _onboardNonce;
mapping(bytes32 => mapping(uint32 => uint256)) private _quitNonce;
// prevent duplicate slugs per (ownerId, slug) pair via key;
mapping(bytes32 => bool) private _slugKeyUsed;
//////////////////////
// EIP-191 typehash //
//////////////////////
/// @dev keccak256("Onboard(bytes32 nexusKey,uint32 nexusOwnerId,uint32 urbitId,address recipient,uint256 nonce,address verifyingContract,uint256 chainId)")
bytes32 private constant ONBOARD_TYPEHASH =
keccak256("Onboard(bytes32 nexusKey,uint32 nexusOwnerId,uint32 urbitId,address recipient,uint256 nonce,address verifyingContract,uint256 chainId)");
/// @dev keccak256("Quit(bytes32 nexusKey,uint32 nexusOwnerId,uint32 quittingUrbitId,uint256 nonce,address verifyingContract,uint256 chainId)")
bytes32 private constant QUIT_TYPEHASH =
keccak256(
"Quit(bytes32 nexusKey,uint32 nexusOwnerId,uint32 quittingUrbitId,uint256 nonce,address verifyingContract,uint256 chainId)"
);
//////////////
// Events ///
//////////////
// Lifecycle of per-nexus records
event NexusOpened(uint32 indexed leaderUrbitId, bytes32 indexed nexusKey, string nexusId, address indexed inviteKey);
event NexusClosed(bytes32 indexed nexusKey, uint32 indexed leaderUrbitId);
// Ownership transfer flow
event OwnerProposed(uint32 indexed currentOwnerId, uint32 indexed proposedOwnerId);
event OwnershipProposalRevoked(uint32 indexed currentOwnerId);
event OwnershipAccepted(uint32 indexed newOwnerId);
event OwnershipRejected(uint32 indexed proposedOwnerId);
// Market / external integration
event PlanetMarketUpdated(address indexed newMarket);
// Invite escrow/accounting
event InviteTokenDeposited(bytes32 indexed nexusKey, address indexed depositor, uint256 count, uint256 amount);
event InviteTokenWithdrawn(bytes32 indexed nexusKey, address indexed withdrawer, uint256 count, uint256 amount);
event UrbitTokensRetrieved(bytes32 indexed nexusKey, address indexed to, uint256 amount);
// Inviter configuration
event NexusInviterAdded(bytes32 indexed nexusKey, address indexed nexusInviter);
event NexusInviterChanged(bytes32 indexed nexusKey, address indexed oldNexusInviter, address indexed newNexusInviter);
event NexusInviterRemoved(bytes32 indexed nexusKey, address indexed nexusInviter);
// Membership lifecycle
event InviteClaimed(bytes32 indexed nexusKey, uint32 indexed nexusOwnerUrbitId, uint32 indexed invitedUrbitId, address recipient, uint256 stipend);
event UrbitIdBanned(bytes32 indexed nexusKey, uint32 indexed bannedUrbitId);
event UrbitIdUnbanned(bytes32 indexed nexusKey, uint32 indexed unbannedUrbitId);
event UrbitIdQuit(bytes32 indexed nexusKey, uint32 indexed quittingUrbitId);
// Admin toggles / lifecycle
event ManagementProxyAccessSet(bytes32 indexed nexusKey, bool allowed, address indexed proxy);
///////////////
// Modifiers //
///////////////
modifier onlyHubOwner() {
if (!i_azimuth.isOwner(_ownerId, msg.sender) &&
!i_azimuth.isManagementProxy(_ownerId, msg.sender)) {
revert UnauthorizedOwnerOrProxy();
}
_;
}
modifier onlyExisting(bytes32 nexusKey) {
if (!_nodes[nexusKey].exists) revert NexusDoesNotExist();
if (_nodes[nexusKey].isDestroyed) revert NexusDestroyed();
_;
}
modifier onlyOwnerID(bytes32 nexusKey) {
uint32 nid = _nodes[nexusKey].ownerId;
if (!i_azimuth.isOwner(nid, msg.sender)) revert PointNotOwnedByCaller();
_;
}
modifier onlyController(bytes32 nexusKey) {
NexusNode storage n = _nodes[nexusKey];
bool asOwner = i_azimuth.isOwner(n.ownerId, msg.sender);
bool asMgmt = n.allowManagementProxy && i_azimuth.isManagementProxy(n.ownerId, msg.sender);
if (!(asOwner || asMgmt)) revert PointNotControlledByCaller();
_;
}
modifier permissionedAccess(bytes32 nexusKey) {
NexusNode storage n = _nodes[nexusKey];
bool mgmtOk = n.allowManagementProxy && i_azimuth.isManagementProxy(n.ownerId, msg.sender);
if (msg.sender != n.nexusInviter &&
!i_azimuth.isOwner(n.ownerId, msg.sender) &&
!mgmtOk) {
revert UnauthorizedAccess();
}
_;
}
/////////////////
// Constructor //
/////////////////
constructor(address azimuthAddress, uint32 ownerId, address urbitToken, address planetMarket) {
i_azimuth = IAzimuth(azimuthAddress);
i_urbitToken = urbitToken;
_ownerId = ownerId;
_planetMarket = planetMarket;
if (!i_azimuth.isOwner(ownerId, msg.sender)) {
revert DeployerMustControlOwnershipPoint();
}
}
////////////////////////////
//// Nexus CRUD (public) ////
////////////////////////////
function createNexus(
uint32 leaderUrbitId,
address nexusInviter,
uint8 freeMonths,
uint256 price,
string memory nexusId
) external nonReentrant returns (bytes32 nexusKey) {
if (!i_azimuth.isOwner(leaderUrbitId, msg.sender)) revert CallerNotOwnerOfUrbitId();
if (!i_azimuth.isActive(leaderUrbitId)) revert UrbitIdNotActive();
SlugLib.assertValidSlug(nexusId);
nexusKey = keccak256(abi.encodePacked(leaderUrbitId, nexusId));
if (_nodes[nexusKey].exists) revert NexusAlreadyExists();
if (_slugKeyUsed[nexusKey]) revert SlugAlreadyUsed();
_slugKeyUsed[nexusKey] = true;
NexusNode storage n = _nodes[nexusKey];
n.ownerId = leaderUrbitId;
n.nexusInviter = nexusInviter;
n.inviteCount = 0;
n.freeMonths = freeMonths;
n.price = price;
n.nexusId = nexusId;
n.isDestroyed = false;
n.allowManagementProxy = false;
n.totalInvitesIssued = 0;
n.escrowUrbit = 0;
n.exists = true;
emit NexusOpened(leaderUrbitId, nexusKey, nexusId, nexusInviter);
}
function destroyNexus(bytes32 nexusKey)
external
nonReentrant
onlyExisting(nexusKey)
onlyOwnerID(nexusKey)
returns (bool)
{
NexusNode storage n = _nodes[nexusKey];
// Close onboarding
address oldInviter = n.nexusInviter;
n.nexusInviter = address(0);
if (oldInviter != address(0)) {
emit NexusInviterRemoved(nexusKey, oldInviter);
emit NexusInviterChanged(nexusKey, oldInviter, address(0));
}
// Refund any remaining escrow for this nexus to the caller (owner)
uint256 escrow = n.escrowUrbit;
if (escrow > 0) {
n.escrowUrbit = 0;
IERC20(i_urbitToken).safeTransfer(msg.sender, escrow);
emit UrbitTokensRetrieved(nexusKey, msg.sender, escrow);
}
n.inviteCount = 0;
n.isDestroyed = true;
emit NexusClosed(nexusKey, n.ownerId);
return true;
}
///////////////////////////////
//// Invite Escrow & Flows ////
///////////////////////////////
function addInvites(bytes32 nexusKey, uint32 count)
external
nonReentrant
onlyExisting(nexusKey)
permissionedAccess(nexusKey)
returns (bool)
{
if (count == 0) revert InvalidCount();
IPlanetDispenser d = IPlanetDispenser(_planetMarket);
(uint256 unitPrice,,) = d.priceAndAllowance(address(this));
uint256 amount = unitPrice * uint256(count);
// Pull $URBIT into hub & attribute to this nexus
IERC20(i_urbitToken).safeTransferFrom(msg.sender, address(this), amount);
NexusNode storage n = _nodes[nexusKey];
unchecked {
n.inviteCount += count;
n.totalInvitesIssued += count;
}
n.escrowUrbit += amount;
emit InviteTokenDeposited(nexusKey, msg.sender, count, amount);
return true;
}
function removeInvites(bytes32 nexusKey, uint32 count)
external
nonReentrant
onlyExisting(nexusKey)
permissionedAccess(nexusKey)
returns (bool)
{
if (count == 0) revert InvalidCount();
NexusNode storage n = _nodes[nexusKey];
if (n.inviteCount < count) revert InsufficientInvites();
IPlanetDispenser d = IPlanetDispenser(_planetMarket);
(uint256 unitPrice,,) = d.priceAndAllowance(address(this));
uint256 amount = unitPrice * uint256(count);
if (n.escrowUrbit < amount) revert InsufficientUrbitBalance();
n.inviteCount -= count;
n.escrowUrbit -= amount;
// Refund $URBIT to caller
IERC20(i_urbitToken).safeTransfer(msg.sender, amount);
emit InviteTokenWithdrawn(nexusKey, msg.sender, count, amount);
return true;
}
function retrieveUrbitTokens(bytes32 nexusKey, address withdrawalAddress)
external
nonReentrant
onlyExisting(nexusKey)
onlyController(nexusKey)
returns (bool)
{
if (withdrawalAddress == address(0)) revert BadWithdrawalAddress();
NexusNode storage n = _nodes[nexusKey];
if (n.inviteCount != 0) revert InvitesRemain();
uint256 bal = n.escrowUrbit;
if (bal == 0) revert NoUrbitToRetrieve();
n.escrowUrbit = 0;
IERC20(i_urbitToken).safeTransfer(withdrawalAddress, bal);
emit UrbitTokensRetrieved(nexusKey, withdrawalAddress, bal);
return true;
}
/////////////////////////////////////
//// Membership: onboarding/quit ////
/////////////////////////////////////
function onboardNewUrbitID(bytes32 nexusKey, address recipient, uint32 newUrbitId)
external
payable
nonReentrant
onlyExisting(nexusKey)
permissionedAccess(nexusKey)
returns (bool)
{
NexusNode storage n = _nodes[nexusKey];
if (n.inviteCount == 0) revert NoInvitesRemaining();
if (_isMember[nexusKey][newUrbitId]) revert AlreadyMember(newUrbitId);
if (recipient == address(0)) revert BadRecipient();
IPlanetDispenser d = IPlanetDispenser(_planetMarket);
// Check hub-level $URBIT escrow for this nexus covers the spend
(uint256 price,,) = d.priceAndAllowance(address(this));
if (n.escrowUrbit < price) revert InsufficientUrbit();
// One-time or top-up approval for dispenser (global allowance)
uint256 allowance = IERC20(i_urbitToken).allowance(address(this), _planetMarket);
if (allowance < price) {
IERC20(i_urbitToken).forceApprove(_planetMarket, type(uint256).max);
}
// Spend & effect
d.redeemPlanet(newUrbitId, recipient);
unchecked { n.inviteCount -= 1; }
_isMember[nexusKey][newUrbitId] = true;
n.escrowUrbit -= price;
// Send ETH stipend to recipient if provided
if (msg.value > 0) {
(bool success, ) = payable(recipient).call{value: msg.value}("");
if (!success) revert EthTransferFailed();
}
emit InviteClaimed(nexusKey, n.ownerId, newUrbitId, recipient, msg.value);
return true;
}
function onboardExistingUrbitID(
bytes32 nexusKey,
address recipient,
uint32 existingUrbitId,
bytes memory signature
)
external
payable
nonReentrant
onlyExisting(nexusKey)
permissionedAccess(nexusKey)
returns (bool)
{
NexusNode storage n = _nodes[nexusKey];
if (n.inviteCount == 0) revert NoInvitesRemaining();
if (_isMember[nexusKey][existingUrbitId]) revert AlreadyMember(existingUrbitId);
if (_isBanned[nexusKey][existingUrbitId]) revert UrbitIdAlreadyBanned(existingUrbitId);
if (recipient != i_azimuth.getOwner(existingUrbitId)) revert RecipientNotOwner();
// Reconstruct the personal_sign payload
bytes32 structHash = keccak256(
abi.encode(
ONBOARD_TYPEHASH,
nexusKey,
n.ownerId,
existingUrbitId,
recipient,
_onboardNonce[nexusKey][existingUrbitId],
address(this),
block.chainid
)
);
bytes32 digest = MessageHashUtils.toEthSignedMessageHash(structHash);
address signer = ECDSA.recover(digest, signature);
bool ok = (signer == i_azimuth.getOwner(existingUrbitId)) ||
(i_azimuth.isManagementProxy(existingUrbitId, signer));
if (!ok) revert InvalidSigner();
unchecked { _onboardNonce[nexusKey][existingUrbitId]++; }
unchecked { n.inviteCount -= 1; }
_isMember[nexusKey][existingUrbitId] = true;
// Send ETH stipend to recipient if provided
if (msg.value > 0) {
(bool success, ) = payable(recipient).call{value: msg.value}("");
if (!success) revert EthTransferFailed();
}
emit InviteClaimed(nexusKey, n.ownerId, existingUrbitId, recipient, msg.value);
return true;
}
function permissionedQuitNexus(
bytes32 nexusKey,
uint32 quittingUrbitId,
bytes memory signature
)
external
nonReentrant
onlyExisting(nexusKey)
permissionedAccess(nexusKey)
returns (bool)
{
if (!_isMember[nexusKey][quittingUrbitId]) revert NotAMember(quittingUrbitId);
NexusNode storage n = _nodes[nexusKey];
bytes32 structHash = keccak256(
abi.encode(
QUIT_TYPEHASH,
nexusKey,
n.ownerId,
quittingUrbitId,
_quitNonce[nexusKey][quittingUrbitId],
address(this),
block.chainid
)
);
bytes32 digest = MessageHashUtils.toEthSignedMessageHash(structHash);
address signer = ECDSA.recover(digest, signature);
bool ok = (signer == i_azimuth.getOwner(quittingUrbitId)) ||
(i_azimuth.isManagementProxy(quittingUrbitId, signer));
if (!ok) revert InvalidSigner();
unchecked { _quitNonce[nexusKey][quittingUrbitId]++; }
_hasQuit[nexusKey][quittingUrbitId] = true;
_isMember[nexusKey][quittingUrbitId] = false;
emit UrbitIdQuit(nexusKey, quittingUrbitId);
return true;
}
function rageQuitNexus(bytes32 nexusKey, uint32 rageQuitUrbitId)
external
nonReentrant
onlyExisting(nexusKey)
returns (bool)
{
if (!_isMember[nexusKey][rageQuitUrbitId]) revert NotAMember(rageQuitUrbitId);
bool controls =
i_azimuth.isOwner(rageQuitUrbitId, msg.sender) ||
i_azimuth.isManagementProxy(rageQuitUrbitId, msg.sender);
if (!controls) revert CallerNotAuthorized();
NexusNode storage n = _nodes[nexusKey];
_hasQuit[nexusKey][rageQuitUrbitId] = true;
_isMember[nexusKey][rageQuitUrbitId] = false;
emit UrbitIdQuit(nexusKey, rageQuitUrbitId);
return true;
}
/////////////////////////
//// Per-nexus Admin ////
/////////////////////////
function allowManagementProxyAccess(bytes32 nexusKey, bool access)
external
onlyExisting(nexusKey)
onlyOwnerID(nexusKey)
returns (address managementAddress)
{
NexusNode storage n = _nodes[nexusKey];
n.allowManagementProxy = access;
if (access) {
managementAddress = i_azimuth.getManagementProxy(n.ownerId);
} else {
managementAddress = address(0);
}
emit ManagementProxyAccessSet(nexusKey, access, managementAddress);
return managementAddress;
}
function banUrbitId(bytes32 nexusKey, uint32 urbitId)
external
onlyExisting(nexusKey)
onlyController(nexusKey)
returns (bool)
{
_isBanned[nexusKey][urbitId] = true;
_isMember[nexusKey][urbitId] = false;
emit UrbitIdBanned(nexusKey, urbitId);
return true;
}
function unbanUrbitId(bytes32 nexusKey, uint32 urbitId)
external
onlyExisting(nexusKey)
onlyController(nexusKey)
returns (bool)
{
_isBanned[nexusKey][urbitId] = false;
emit UrbitIdUnbanned(nexusKey, urbitId);
return true;
}
function changeNexusInviter(bytes32 nexusKey, address newNexusInviter)
external
onlyExisting(nexusKey)
onlyController(nexusKey)
returns (bool)
{
address old = _nodes[nexusKey].nexusInviter;
_nodes[nexusKey].nexusInviter = newNexusInviter;
emit NexusInviterChanged(nexusKey, old, newNexusInviter);
if (newNexusInviter == address(0) && old != address(0)) {
emit NexusInviterRemoved(nexusKey, old);
} else if (newNexusInviter != address(0)) {
emit NexusInviterAdded(nexusKey, newNexusInviter);
}
return true;
}
function removeNexusInviter(bytes32 nexusKey)
external
onlyExisting(nexusKey)
onlyController(nexusKey)
returns (bool)
{
address old = _nodes[nexusKey].nexusInviter;
_nodes[nexusKey].nexusInviter = address(0);
if (old != address(0)) {
emit NexusInviterRemoved(nexusKey, old);
emit NexusInviterChanged(nexusKey, old, address(0));
}
return true;
}
function addNexusInviter(bytes32 nexusKey, address newInviter)
external
onlyExisting(nexusKey)
onlyController(nexusKey)
returns (bool)
{
if (newInviter == address(0)) revert BadInviter();
address old = _nodes[nexusKey].nexusInviter;
_nodes[nexusKey].nexusInviter = newInviter;
emit NexusInviterAdded(nexusKey, newInviter);
emit NexusInviterChanged(nexusKey, old, newInviter);
return true;
}
/////////////////////////
//// Hub Admin (owner) //
/////////////////////////
function proposeOwner(uint32 proposedOwnerId) external onlyHubOwner returns (uint32) {
_proposedOwnerId = proposedOwnerId;
emit OwnerProposed({ currentOwnerId: _ownerId, proposedOwnerId: proposedOwnerId });
return _proposedOwnerId;
}
function revokeOwnershipProposal() external onlyHubOwner returns (uint32) {
_proposedOwnerId = _ownerId;
emit OwnershipProposalRevoked({ currentOwnerId: _ownerId });
return _proposedOwnerId;
}
function acceptOwnership() external returns (uint32) {
if (!i_azimuth.isOwner(_proposedOwnerId, msg.sender)) revert CallerNotAuthorized();
_ownerId = _proposedOwnerId;
emit OwnershipAccepted({ newOwnerId: _ownerId });
return _ownerId;
}
function rejectOwnership() external returns (uint32) {
if (!i_azimuth.isOwner(_proposedOwnerId, msg.sender)) revert CallerNotAuthorized();
emit OwnershipRejected({ proposedOwnerId: _proposedOwnerId });
_proposedOwnerId = _ownerId;
return _ownerId;
}
function changePlanetMarket(address newMarket) external onlyHubOwner returns (bool) {
// Revoke approval from old market
if (_planetMarket != address(0)) {
IERC20(i_urbitToken).forceApprove(_planetMarket, 0);
}
_planetMarket = newMarket;
emit PlanetMarketUpdated(newMarket);
return true;
}
//////////////////////////
//// View / Getters ////
//////////////////////////
function nexusKeyOf(uint32 leaderUrbitId, string memory nexusId) public pure returns (bytes32) {
return keccak256(abi.encodePacked(leaderUrbitId, nexusId));
}
function getNode(bytes32 nexusKey)
external
view
returns (
uint32 ownerId,
address nexusInviter,
uint32 inviteCount,
uint8 freeMonths,
bool isDestroyed,
uint256 price,
string memory nexusId,
bool allowManagementProxy,
uint32 totalInvitesIssued,
uint256 escrowUrbit
)
{
NexusNode storage n = _nodes[nexusKey];
return (
n.ownerId,
n.nexusInviter,
n.inviteCount,
n.freeMonths,
n.isDestroyed,
n.price,
n.nexusId,
n.allowManagementProxy,
n.totalInvitesIssued,
n.escrowUrbit
);
}
function exists(bytes32 nexusKey) external view returns (bool) { return _nodes[nexusKey].exists; }
function isDestroyed(bytes32 nexusKey) external view returns (bool) { return _nodes[nexusKey].isDestroyed; }
function isMember(bytes32 nexusKey, uint32 urbitId) external view returns (bool) { return _isMember[nexusKey][urbitId]; }
function isBanned(bytes32 nexusKey, uint32 urbitId) external view returns (bool) { return _isBanned[nexusKey][urbitId]; }
function hasQuit(bytes32 nexusKey, uint32 urbitId) external view returns (bool) { return _hasQuit[nexusKey][urbitId]; }
function isValidAddress(
address userAddress,
uint32 hostUrbitId,
string memory nexusId,
uint32 urbitId
) external view returns (bool) {
bytes32 nexusKey = nexusKeyOf(hostUrbitId, nexusId);
return i_azimuth.canManage(urbitId, userAddress) && _isMember[nexusKey][urbitId];
}
function onboardNonce(bytes32 nexusKey, uint32 urbitId) external view returns (uint256) {
return _onboardNonce[nexusKey][urbitId];
}
function quitNonce(bytes32 nexusKey, uint32 urbitId) external view returns (uint256) {
return _quitNonce[nexusKey][urbitId];
}
function getOwnerId() external view returns (uint32) { return _ownerId; }
function getProposedOwnerId() external view returns (uint32) { return _proposedOwnerId; }
function getPlanetMarketContract() external view returns (address) { return _planetMarket; }
function getUrbitToken() external view returns (address) { return i_urbitToken; }
function getAzimuth() external view returns (address) { return address(i_azimuth); }
/////////////////
//// Receive ////
/////////////////
receive() external payable {
revert("Direct ETH transfers not accepted");
}
fallback() external payable {
revert("Function does not exist");
}
}
"
},
"src/Interfaces/IAzimuth.sol": {
"content": "// SPDX-License-Identifier: GPLv3
pragma solidity ^0.8.20;
interface IAzimuth {
event Activated(uint32 indexed point);
event BrokeContinuity(uint32 indexed point, uint32 number);
event ChangedDns(string primary, string secondary, string tertiary);
event ChangedKeys(
uint32 indexed point,
bytes32 encryptionKey,
bytes32 authenticationKey,
uint32 cryptoSuiteVersion,
uint32 keyRevisionNumber
);
event ChangedManagementProxy(
uint32 indexed point,
address indexed managementProxy
);
event ChangedSpawnProxy(uint32 indexed point, address indexed spawnProxy);
event ChangedTransferProxy(
uint32 indexed point,
address indexed transferProxy
);
event ChangedVotingProxy(uint32 indexed point, address indexed votingProxy);
event EscapeAccepted(uint32 indexed point, uint32 indexed sponsor);
event EscapeCanceled(uint32 indexed point, uint32 indexed sponsor);
event EscapeRequested(uint32 indexed point, uint32 indexed sponsor);
event LostSponsor(uint32 indexed point, uint32 indexed sponsor);
event OwnerChanged(uint32 indexed point, address indexed owner);
event OwnershipRenounced(address indexed previousOwner);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
event Spawned(uint32 indexed prefix, uint32 indexed child);
function activatePoint(uint32 _point) external;
function canManage(
uint32 _point,
address _who
) external view returns (bool result);
function canSpawnAs(
uint32 _point,
address _who
) external view returns (bool result);
function canTransfer(
uint32 _point,
address _who
) external view returns (bool result);
function canVoteAs(
uint32 _point,
address _who
) external view returns (bool result);
function cancelEscape(uint32 _point) external;
function dnsDomains(uint256) external view returns (string memory);
function doEscape(uint32 _point) external;
function escapeRequests(uint32, uint256) external view returns (uint32);
function escapeRequestsIndexes(
uint32,
uint32
) external view returns (uint256);
function getContinuityNumber(
uint32 _point
) external view returns (uint32 continuityNumber);
function getEscapeRequest(
uint32 _point
) external view returns (uint32 escape);
function getEscapeRequests(
uint32 _sponsor
) external view returns (uint32[] memory requests);
function getEscapeRequestsCount(
uint32 _sponsor
) external view returns (uint256 count);
function getKeyRevisionNumber(
uint32 _point
) external view returns (uint32 revision);
function getKeys(
uint32 _point
)
external
view
returns (bytes32 crypt, bytes32 auth, uint32 suite, uint32 revision);
function getManagementProxy(
uint32 _point
) external view returns (address manager);
function getManagerFor(
address _proxy
) external view returns (uint32[] memory mfor);
function getManagerForCount(
address _proxy
) external view returns (uint256 count);
function getOwnedPointAtIndex(
address _whose,
uint256 _index
) external view returns (uint32 point);
function getOwnedPointCount(
address _whose
) external view returns (uint256 count);
function getOwnedPoints(
address _whose
) external view returns (uint32[] memory ownedPoints);
function getOwner(uint32 _point) external view returns (address owner);
function getPointSize(uint32 _point) external pure returns (uint8 _size);
function getPrefix(uint32 _point) external pure returns (uint16 prefix);
function getSpawnCount(
uint32 _point
) external view returns (uint32 spawnCount);
function getSpawnProxy(
uint32 _point
) external view returns (address spawnProxy);
function getSpawned(
uint32 _point
) external view returns (uint32[] memory spawned);
function getSpawningFor(
address _proxy
) external view returns (uint32[] memory sfor);
function getSpawningForCount(
address _proxy
) external view returns (uint256 count);
function getSponsor(uint32 _point) external view returns (uint32 sponsor);
function getSponsoring(
uint32 _sponsor
) external view returns (uint32[] memory sponsees);
function getSponsoringCount(
uint32 _sponsor
) external view returns (uint256 count);
function getTransferProxy(
uint32 _point
) external view returns (address transferProxy);
function getTransferringFor(
address _proxy
) external view returns (uint32[] memory tfor);
function getTransferringForCount(
address _proxy
) external view returns (uint256 count);
function getVotingFor(
address _proxy
) external view returns (uint32[] memory vfor);
function getVotingForCount(
address _proxy
) external view returns (uint256 count);
function getVotingProxy(
uint32 _point
) external view returns (address voter);
function hasBeenLinked(uint32 _point) external view returns (bool result);
function hasSponsor(uint32 _point) external view returns (bool has);
function incrementContinuityNumber(uint32 _point) external;
function isActive(uint32 _point) external view returns (bool equals);
function isEscaping(uint32 _point) external view returns (bool escaping);
function isLive(uint32 _point) external view returns (bool result);
function isManagementProxy(
uint32 _point,
address _proxy
) external view returns (bool result);
function isOperator(
address _owner,
address _operator
) external view returns (bool result);
function isOwner(
uint32 _point,
address _address
) external view returns (bool result);
function isRequestingEscapeTo(
uint32 _point,
uint32 _sponsor
) external view returns (bool equals);
function isSpawnProxy(
uint32 _point,
address _proxy
) external view returns (bool result);
function isSponsor(
uint32 _point,
uint32 _sponsor
) external view returns (bool result);
function isTransferProxy(
uint32 _point,
address _proxy
) external view returns (bool result);
function isVotingProxy(
uint32 _point,
address _proxy
) external view returns (bool result);
function loseSponsor(uint32 _point) external;
function managerFor(address, uint256) external view returns (uint32);
function managerForIndexes(address, uint32) external view returns (uint256);
function operators(address, address) external view returns (bool);
function owner() external view returns (address);
function pointOwnerIndexes(address, uint32) external view returns (uint256);
function points(
uint32
)
external
view
returns (
bytes32 encryptionKey,
bytes32 authenticationKey,
bool hasSponsor,
bool active,
bool escapeRequested,
uint32 sponsor,
uint32 escapeRequestedTo,
uint32 cryptoSuiteVersion,
uint32 keyRevisionNumber,
uint32 continuityNumber
);
function pointsOwnedBy(address, uint256) external view returns (uint32);
function registerSpawned(uint32 _point) external;
function renounceOwnership() external;
function rights(
uint32
)
external
view
returns (
address owner,
address managementProxy,
address spawnProxy,
address votingProxy,
address transferProxy
);
function setDnsDomains(
string memory _primary,
string memory _secondary,
string memory _tertiary
) external;
function setEscapeRequest(uint32 _point, uint32 _sponsor) external;
function setKeys(
uint32 _point,
bytes32 _encryptionKey,
bytes32 _authenticationKey,
uint32 _cryptoSuiteVersion
) external;
function setManagementProxy(uint32 _point, address _proxy) external;
function setOperator(
address _owner,
address _operator,
bool _approved
) external;
function setOwner(uint32 _point, address _owner) external;
function setSpawnProxy(uint32 _point, address _proxy) external;
function setTransferProxy(uint32 _point, address _proxy) external;
function setVotingProxy(uint32 _point, address _proxy) external;
function spawningFor(address, uint256) external view returns (uint32);
function spawningForIndexes(
address,
uint32
) external view returns (uint256);
function sponsoring(uint32, uint256) external view returns (uint32);
function sponsoringIndexes(uint32, uint32) external view returns (uint256);
function transferOwnership(address _newOwner) external;
function transferringFor(address, uint256) external view returns (uint32);
function transferringForIndexes(
address,
uint32
) external view returns (uint256);
function votingFor(address, uint256) external view returns (uint32);
function votingForIndexes(address, uint32) external view returns (uint256);
}
"
},
"src/Interfaces/IPlanetDispenser.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IPlanetDispenser {
function redeemPlanet(uint32 planetId, address recipient) external;
function priceAndAllowance(
address buyer
) external view returns (uint256 price, uint256 allowance, uint256 balance);
function urbitToken() external view returns (address);
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(
bytes32 hash,
bytes memory signature
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly ("memory-safe") {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol)
pragma solidity ^0.8.20;
import {Strings} from "../Strings.sol";
/**
* @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
*
* The library provides methods for generating a hash of a message that conforms to the
* https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
* specifications.
*/
library MessageHashUtils {
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing a bytes32 `messageHash` with
* `"\x19Ethereum Signed Message:\
32"` and hashing the result. It corresponds with the
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
* keccak256, although any bytes32 value can be safely used because the final digest will
* be re-hashed.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, "\x19Ethereum Signed Message:\
32") // 32 is the bytes-length of messageHash
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
}
}
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing an arbitrary `message` with
* `"\x19Ethereum Signed Message:\
" + len(message)` and hashing the result. It corresponds with the
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\
", bytes(Strings.toString(message.length)), message));
}
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x00` (data with intended validator).
*
* The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
* `validator` address. Then hashing the result.
*
* See {ECDSA-recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(hex"19_00", validator, data));
}
/**
* @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.
*/
function toDataWithIntendedValidatorHash(
address validator,
bytes32 messageHash
) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, hex"19_00")
mstore(0x02, shl(96, validator))
mstore(0x16, messageHash)
digest := keccak256(0x00, 0x36)
}
}
/**
* @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).
*
* The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
* `\x19\x01` and hashing the result. It corresponds to the hash signed by the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
*
* See {ECDSA-recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the
Submitted on: 2025-10-16 09:57:19
Comments
Log in to comment.
No comments yet.