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/NexusFactory.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Nexus} from "../src/Nexus.sol";
import {INexusFactory} from "../src/Interfaces/INexusFactory.sol";
import {IAzimuth} from "../src/Interfaces/IAzimuth.sol";
import {IPlanetDispenser} from "../src/Interfaces/IPlanetDispenser.sol";
import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
contract NexusFactory is INexusFactory, ReentrancyGuard {
///////////////
// Errors ////
//////////////
error UnauthorizedOwnerOrProxy();
error UnauthorizedNexusContract();
error DeployerMustControlOwnershipPoint();
error CallerNotOwnerOfUrbitId();
error UrbitIdNotActive();
error CallerNotAuthorized();
///////////////////////
// Storage Variables //
///////////////////////
////////////////////
//// Immutables ////
////////////////////
/// @notice The immutable address of azimuth
/// @dev provided as an immutable instead of a constant to support deploying to different chains/instances
IAzimuth private immutable i_azimuth;
/// @notice The $URBIT ERC20 token used for invites
address private immutable i_urbitToken;
/////////////////////////////////
//// Regular State Variables ////
/////////////////////////////////
uint32 private _ownerId;
uint32 private _proposedOwnerId;
address private _planetMarket;
bool private _allowManagementProxy;
//////////////
// Mappings //
//////////////
mapping(address => bool) private _nexusContract;
///////////////
// Modifiers //
///////////////
// Owner required for executing transactions that update the planetMarket address.
modifier onlyOwnerID() {
if (!i_azimuth.isOwner(_ownerId, msg.sender) &&
!i_azimuth.isManagementProxy(_ownerId, msg.sender)) {
revert UnauthorizedOwnerOrProxy();
}
_;
}
// Possible function parameters here are controlled by the sending contract; this means to remain secure we must ensure that anything enabled to modify the _nexusContract mapping must only do so to add contract addresses which have been audited to properly control their external calls.
modifier onlyNexus() {
if (!_nexusContract[msg.sender]) {
revert UnauthorizedNexusContract();
}
_;
}
/////////////////
// Constructor //
/////////////////
constructor(
address azimuthAddress,
uint32 ownerId,
address urbitToken,
address planetMarket
) {
_ownerId = ownerId;
_planetMarket = planetMarket;
i_azimuth = IAzimuth(azimuthAddress);
i_urbitToken = urbitToken;
if (!i_azimuth.isOwner(ownerId, msg.sender)) {
revert DeployerMustControlOwnershipPoint();
}
}
///////////////
// Functions //
///////////////
////////////////////////////
//// External Functions ////
////////////////////////////
function createNexus(
uint32 urbitId,
address nexusInviter,
uint8 freeMonths,
uint256 price,
string memory nexusId
) external nonReentrant returns (address nexusContract) {
// check that caller owns the target Urbit ID and that it is an active point
if (!i_azimuth.isOwner(urbitId, msg.sender)) {
revert CallerNotOwnerOfUrbitId();
}
if (!i_azimuth.isActive(urbitId)) revert UrbitIdNotActive();
Nexus nexus = new Nexus(
address(i_azimuth),
address(this),
urbitId,
nexusInviter,
freeMonths,
price,
i_urbitToken,
nexusId
);
_nexusContract[address(nexus)] = true; // mark as trusted
emit NexusCreated({
leaderUrbitId: urbitId,
nexusAddress: address(nexus),
inviteKey: nexusInviter,
nexusId: nexusId
});
return address(nexus);
}
function destroyNexus() external onlyNexus returns (bool success) {
_nexusContract[msg.sender] = false;
emit NexusDestroyed({
nexusAddress: msg.sender
});
return true;
}
/////////////////////////
//// Admin Functions ////
/////////////////////////
function proposeOwner(
uint32 proposedOwnerId
) external onlyOwnerID returns (uint32 _proposedOwnerIdRet) {
_proposedOwnerId = proposedOwnerId;
emit OwnerProposed({
currentOwnerId: _ownerId,
proposedOwnerId: proposedOwnerId
});
return _proposedOwnerId;
}
function revokeOwnershipProposal()
external
onlyOwnerID
returns (uint32 _proposedOwnerIdRet)
{
_proposedOwnerId = _ownerId; // Just use current ownerId
emit OwnershipProposalRevoked({
currentOwnerId: _ownerId
});
return _proposedOwnerId;
}
function acceptOwnership() external returns (uint32 _ownerIdRet) {
if (!i_azimuth.isOwner(_proposedOwnerId, msg.sender)) {
revert CallerNotAuthorized();
}
_ownerId = _proposedOwnerId;
emit OwnershipAccepted({
newOwnerId: _ownerId
});
return _ownerId;
}
function rejectOwnership() external returns (uint32 _proposedOwnerIdRet) {
if (!i_azimuth.isOwner(_proposedOwnerId, msg.sender)) {
revert CallerNotAuthorized();
}
emit OwnershipRejected({
proposedOwnerId: _proposedOwnerId
});
_proposedOwnerId = _ownerId; // reset to current owner
return _ownerId;
}
function changePlanetMarket(
address newMarket
) external onlyOwnerID returns (bool success) {
_planetMarket = newMarket;
success = true;
emit PlanetMarketUpdated({
newMarket: newMarket
});
return success;
}
///////////////////////////////
//// Nexus Event Functions ////
///////////////////////////////
function updateInviteKeys(
address nexusContract,
address newPermissionedInviter,
uint32 urbitId
) external onlyNexus returns (bool success) {
emit NexusInviteKeysUpdated({
nexusContract: nexusContract,
urbitId: urbitId,
newPermissionedInviter: newPermissionedInviter
});
return true;
}
function urbitJoined(
address nexusContract,
uint32 leader,
uint32 participant
) external onlyNexus returns (bool success) {
emit NexusUrbitJoined({
nexusContract: nexusContract,
leader: leader,
participant: participant
});
return true;
}
function urbitDeparted(
address nexusContract,
uint32 leader,
uint32 participant
) external onlyNexus returns (bool success) {
emit NexusUrbitDeparted({
nexusContract: nexusContract,
leader: leader,
participant: participant
});
return true;
}
//////////////////////////
//// Getter Functions ////
//////////////////////////
function getOwnerId() external view returns (uint32 ownerId) {
return _ownerId;
}
function getUrbitToken() external view returns (address) {
return i_urbitToken;
}
function getProposedOwnerId()
external
view
returns (uint32 proposedOwnerId)
{
return _proposedOwnerId;
}
function isNexusContract(address candidate) external view returns (bool) {
return _nexusContract[candidate];
}
function getPlanetMarketContract()
external
view
returns (address planetMarketContract)
{
planetMarketContract = _planetMarket;
return planetMarketContract;
}
function getAzimuth() external view returns (address) {
return address(i_azimuth);
}
function isManagementProxyAllowed() external view returns (bool) {
return _allowManagementProxy;
}
////////////////////////
// Internal Functions //
////////////////////////
/////////////////
//// Receive ////
/////////////////
receive() external payable {
revert("Direct ETH transfers not accepted");
}
//////////////////
//// Fallback ////
//////////////////
fallback() external payable {
revert("Function does not exist");
}
}
"
},
"src/Nexus.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {INexus} from "../src/Interfaces/INexus.sol";
import {INexusFactory} from "../src/Interfaces/INexusFactory.sol";
import {IAzimuth} from "../src/Interfaces/IAzimuth.sol";
import {ECDSA} from "../lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
import {MessageHashUtils} from "../lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.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 {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {IPlanetDispenser} from "../src/Interfaces/IPlanetDispenser.sol";
import {SlugLib} from "../src/libraries/SlugLib.sol";
contract Nexus is INexus, ReentrancyGuard {
using SafeERC20 for IERC20;
///////////////
// Errors ////
//////////////
error PointNotOwnedByCaller();
error PointNotControlledByCaller();
error UnauthorizedAccess();
error NoInvitesRemaining();
error BadRecipient();
error InsufficientUrbit();
error UrbitIdAlreadyBanned(uint32 urbitId);
error RecipientNotOwner();
error SignerMismatch();
error InvalidSigner();
error NotAMember(uint32 urbitId);
error CallerNotAuthorized();
error InvalidCount();
error InsufficientInvites();
error InsufficientUrbitBalance();
error BadWithdrawalAddress();
error InvitesRemain();
error NoUrbitToRetrieve();
error BadInviter();
error FactoryDeregistrationFailed();
error EthTransferFailed();
///////////////////////
// Storage Variables //
///////////////////////
////////////////////
//// Immutables ////
////////////////////
INexusFactory private immutable i_nexusFactory;
IAzimuth private immutable i_azimuth;
uint32 private immutable i_urbitId;
address private immutable i_urbitToken;
/////////////////////////////////
//// Regular State Variables ////
/////////////////////////////////
NexusNode private _nexusNode;
bool private _allowManagementProxy;
uint32 private _totalInvitesIssued;
/// @dev keccak256("Onboard(uint32 nexusId,uint32 urbitId,address recipient,uint256 nonce)")
bytes32 private constant ONBOARD_TYPEHASH =
keccak256("Onboard(uint32 nexusId,uint32 urbitId,address recipient,uint256 nonce)");
/// @dev keccak256("Quit(uint32 nexusOwnerId,uint32 quittingUrbitId,uint256 nonce,address verifyingContract,uint256 chainId)")
bytes32 private constant QUIT_TYPEHASH =
keccak256(
"Quit(uint32 nexusOwnerId,uint32 quittingUrbitId,uint256 nonce,address verifyingContract,uint256 chainId)"
);
//////////////
// Mappings //
//////////////
mapping(uint32 => bool) private _isMember;
mapping(uint32 => bool) private _isBanned;
mapping(uint32 => bool) private _hasQuit;
// Anti-replay nonce for each point
mapping(uint32 => uint256) private _onboardNonce;
// Nonce for quitting
mapping(uint32 => uint256) private _quitNonce;
///////////////
// Modifiers //
///////////////
// All `urbitId` values are known to be active due to a check in the factory contract on creation of the Nexus
/// @notice Access controls for functions only callable by the contract owner, who is the owner of the azimuth point
modifier onlyOwnerID() {
if (!i_azimuth.isOwner(i_urbitId, msg.sender)) {
revert PointNotOwnedByCaller();
}
_;
}
modifier onlyController() {
if (!i_azimuth.isOwner(i_urbitId, msg.sender) &&
!(_allowManagementProxy == true &&
i_azimuth.isManagementProxy(i_urbitId, msg.sender))) {
revert PointNotControlledByCaller();
}
_;
}
modifier permissionedAccess() {
bool mgmtOk = _allowManagementProxy &&
i_azimuth.isManagementProxy(i_urbitId, msg.sender);
if (msg.sender != _nexusNode.nexusInviter &&
!i_azimuth.isOwner(i_urbitId, msg.sender) &&
!mgmtOk) {
revert UnauthorizedAccess();
}
_;
}
/////////////////
// Constructor //
/////////////////
constructor(
address azimuthAddress,
address nexusFactory,
uint32 urbitId,
address nexusInviter,
uint8 freeMonths,
uint256 price,
address urbitToken,
string memory nexusId
) {
SlugLib.assertValidSlug(nexusId);
i_azimuth = IAzimuth(azimuthAddress);
i_nexusFactory = INexusFactory(nexusFactory);
i_urbitId = urbitId;
i_urbitToken = urbitToken;
_nexusNode.ownerId = urbitId;
_nexusNode.inviteCount = 0;
_nexusNode.nexusInviter = nexusInviter;
_nexusNode.freeMonths = freeMonths;
_nexusNode.price = price;
_nexusNode.nexusId = nexusId;
_nexusNode.isDestroyed = false;
}
///////////////
// Functions //
///////////////
////////////////////////////
//// External Functions ////
////////////////////////////
function onboardNewUrbitID(
address recipient,
uint32 newUrbitId
) external nonReentrant permissionedAccess returns (bool success) {
if (_nexusNode.inviteCount == 0) revert NoInvitesRemaining();
if (recipient == address(0)) revert BadRecipient();
address dispenser = i_nexusFactory.getPlanetMarketContract();
IPlanetDispenser d = IPlanetDispenser(dispenser);
// Make sure this Nexus has enough allowance & balance in $URBIT
(uint256 price, uint256 allowance, uint256 balance) = d
.priceAndAllowance(address(this));
if (balance < price) revert InsufficientUrbit();
if (allowance < price) {
// Approve dispenser to pull $URBIT from THIS contract (approve max to avoid repeat approvals)
IERC20(i_urbitToken).forceApprove(dispenser, type(uint256).max);
}
d.redeemPlanet(newUrbitId, recipient);
_nexusNode.inviteCount--;
_isMember[newUrbitId] = true;
i_nexusFactory.urbitJoined(address(this), i_urbitId, newUrbitId);
emit InviteClaimed({
inviterUrbitId: i_urbitId,
invitedUrbitId: newUrbitId
});
return true;
}
// Match INexus signature (bytes memory)
function onboardExistingUrbitID(
address recipient,
uint32 existingUrbitId,
bytes memory signature
) external nonReentrant permissionedAccess returns (bool success) {
if (_nexusNode.inviteCount == 0) revert NoInvitesRemaining();
if (_isBanned[existingUrbitId]) revert UrbitIdAlreadyBanned(existingUrbitId);
if (recipient != i_azimuth.getOwner(existingUrbitId)) {
revert RecipientNotOwner();
}
// Reconstruct signed message (EIP-191 personal-sign)
bytes32 structHash = keccak256(
abi.encode(
ONBOARD_TYPEHASH,
i_urbitId,
existingUrbitId,
recipient,
_onboardNonce[existingUrbitId]
)
);
bytes32 digest = MessageHashUtils.toEthSignedMessageHash(structHash);
// Recover and check signer
address signer = ECDSA.recover(digest, signature);
if (signer != i_azimuth.getOwner(existingUrbitId) &&
!i_azimuth.isManagementProxy(existingUrbitId, signer)) {
revert InvalidSigner();
}
// Bump nonce to invalidate signature forever
unchecked {
_onboardNonce[existingUrbitId]++;
}
_nexusNode.inviteCount--;
_isMember[existingUrbitId] = true;
i_nexusFactory.urbitJoined(address(this), i_urbitId, existingUrbitId);
emit InviteClaimed({
inviterUrbitId: i_urbitId,
invitedUrbitId: existingUrbitId
});
return true;
}
function permissionedQuitNexus(
uint32 quittingUrbitId,
bytes memory signature
) external nonReentrant permissionedAccess returns (bool success) {
if (!_isMember[quittingUrbitId]) revert NotAMember(quittingUrbitId);
bytes32 structHash = keccak256(
abi.encode(
QUIT_TYPEHASH,
i_urbitId,
quittingUrbitId,
_quitNonce[quittingUrbitId],
address(this),
block.chainid
)
);
// EIP-191 personal_sign digest
bytes32 digest = MessageHashUtils.toEthSignedMessageHash(structHash);
// Signer must currently control the quitting ID
address signer = ECDSA.recover(digest, signature);
if (
signer != i_azimuth.getOwner(quittingUrbitId) &&
!i_azimuth.isManagementProxy(quittingUrbitId, signer)
) {
revert InvalidSigner();
}
unchecked { _quitNonce[quittingUrbitId]++; }
_hasQuit[quittingUrbitId] = true;
_isMember[quittingUrbitId] = false;
i_nexusFactory.urbitDeparted(address(this), i_urbitId, quittingUrbitId);
emit UrbitIdPermissionedQuit({ quittingUrbitId: quittingUrbitId });
return true;
}
function rageQuitNexus(
uint32 rageQuitUrbitId
) external nonReentrant returns (bool success) {
if (!_isMember[rageQuitUrbitId]) revert NotAMember(rageQuitUrbitId);
bool controls =
i_azimuth.isOwner(rageQuitUrbitId, msg.sender) ||
i_azimuth.isManagementProxy(rageQuitUrbitId, msg.sender);
if (!controls) revert CallerNotAuthorized();
_hasQuit[rageQuitUrbitId] = true;
_isMember[rageQuitUrbitId] = false;
i_nexusFactory.urbitDeparted(address(this), i_urbitId, rageQuitUrbitId);
emit UrbitIdRageQuit({ rageQuitUrbitId: rageQuitUrbitId });
return true;
}
/////////////////////////
//// Admin Functions ////
/////////////////////////
function allowManagementProxyAccess(
bool access
) external onlyOwnerID returns (address managementAddress) {
_allowManagementProxy = access;
if (access == true) {
managementAddress = i_azimuth.getManagementProxy(i_urbitId);
} else {
managementAddress = address(0);
}
emit ManagementProxyAccessSet(access, managementAddress);
return managementAddress;
}
// Match INexus signature (includes newNexusInviter)
function banUrbitId(
uint32 urbitId
) external onlyController returns (bool success) {
_isBanned[urbitId] = true;
_isMember[urbitId] = false;
emit UrbitIdBanned(urbitId);
return true;
}
function unbanUrbitId(
uint32 urbitId
) external onlyController returns (bool success) {
_isBanned[urbitId] = false;
emit UrbitIdUnbanned({
unbannedUrbitId: urbitId
});
return true;
}
function addInvites(
uint32 count
) external nonReentrant permissionedAccess returns (bool success) {
if (count == 0) revert InvalidCount();
// Get dispenser + $URBIT token address
address dispenser = i_nexusFactory.getPlanetMarketContract();
IPlanetDispenser d = IPlanetDispenser(dispenser);
(uint256 price, , ) = d.priceAndAllowance(address(this));
uint256 amount = price * uint256(count);
// Pull $URBIT from the controller into THIS contract
IERC20(i_urbitToken).safeTransferFrom(
msg.sender,
address(this),
amount
);
_nexusNode.inviteCount += count;
_totalInvitesIssued += count;
emit InviteTokenDeposited(msg.sender, count);
return true;
}
function removeInvites(
uint32 count
) external nonReentrant permissionedAccess returns (bool success) {
if (count == 0) revert InvalidCount();
if (_nexusNode.inviteCount < count) revert InsufficientInvites();
// Get dispenser + URBIT price
address dispenser = i_nexusFactory.getPlanetMarketContract();
IPlanetDispenser d = IPlanetDispenser(dispenser);
(uint256 price, , ) = d.priceAndAllowance(address(this));
uint256 amount = price * uint256(count);
// Ensure this contract actually holds enough $URBIT to refund
uint256 bal = IERC20(i_urbitToken).balanceOf(address(this));
if (bal < amount) revert InsufficientUrbitBalance();
// Effects
_nexusNode.inviteCount -= count;
// Interaction: return $URBIT to the controller
IERC20(i_urbitToken).safeTransfer(msg.sender, amount);
emit InviteTokenWithdrawn(msg.sender, count);
return true;
}
function retrieveUrbitTokens(
address withdrawalAddress
) external nonReentrant onlyController returns (bool success) {
if (withdrawalAddress == address(0)) revert BadWithdrawalAddress();
// Only when there are no outstanding invites
if (_nexusNode.inviteCount != 0) revert InvitesRemain();
uint256 bal = IERC20(i_urbitToken).balanceOf(address(this));
if (bal == 0) revert NoUrbitToRetrieve();
IERC20(i_urbitToken).safeTransfer(withdrawalAddress, bal);
emit UrbitTokensRetrieved(withdrawalAddress, bal);
return true;
}
function changeNexusInviter(
address newNexusInviter
) external onlyController returns (bool success) {
// Emits Changed; also emit Added/Removed for richer indexing
address old = _nexusNode.nexusInviter;
success = _changeNexusInviter(newNexusInviter);
if (newNexusInviter == address(0) && old != address(0)) {
emit NexusInviterRemoved(old);
} else if (newNexusInviter != address(0)) {
emit NexusInviterAdded(newNexusInviter);
}
return success;
}
function removeNexusInviter()
external
onlyController
returns (bool success)
{
address old = _nexusNode.nexusInviter;
success = _changeNexusInviter(address(0));
if (old != address(0)) {
emit NexusInviterRemoved(old);
}
return success;
}
function addNexusInviter(
address newInviter
) external onlyController returns (bool success) {
if (newInviter == address(0)) revert BadInviter();
success = _changeNexusInviter(newInviter);
emit NexusInviterAdded(newInviter);
return success;
}
function destroyNexus() external nonReentrant onlyOwnerID returns (bool success) {
// Close onboarding
address old = _nexusNode.nexusInviter;
_changeNexusInviter(address(0));
if (old != address(0)) {
emit NexusInviterRemoved(old);
}
// Withdraw all URBIT to the owner (caller)
uint256 bal = IERC20(i_urbitToken).balanceOf(address(this));
if (bal > 0) {
IERC20(i_urbitToken).safeTransfer(msg.sender, bal);
}
// Reset invites and mark destroyed
_nexusNode.inviteCount = 0;
_nexusNode.isDestroyed = true;
// Tell the factory to de-register this Nexus and emit destruction
bool dereg = i_nexusFactory.destroyNexus();
if (!dereg) revert FactoryDeregistrationFailed();
// Emit destruction event
emit NexusDestroyed(address(this), i_urbitId);
// Transfer any remaining ETH to the owner
if (address(this).balance > 0) {
(bool ethSuccess, ) = payable(msg.sender).call{
value: address(this).balance
}("");
if (!ethSuccess) revert EthTransferFailed();
}
return true;
}
//////////////////////////
//// Getter Functions ////
//////////////////////////
/// @notice Returns the entire NexusNode struct
function getNexusNode() external view returns (NexusNode memory) {
return _nexusNode;
}
/// @notice Returns core config fields individually
function getNexusInviter() external view returns (address) {
return _nexusNode.nexusInviter;
}
function getInviteCount() external view returns (uint32) {
return _nexusNode.inviteCount;
}
function getTotalInvitesIssued() external view returns (uint32) {
return _totalInvitesIssued;
}
function getFreeMonths() external view returns (uint8) {
return _nexusNode.freeMonths;
}
function isDestroyed() external view returns (bool) {
return _nexusNode.isDestroyed;
}
function getPrice() external view returns (uint256) {
return _nexusNode.price;
}
function getNexusId() external view returns (string memory) {
return _nexusNode.nexusId;
}
/// @notice Returns factory and protocol addresses
function getAzimuth() external view returns (address) {
return address(i_azimuth);
}
function getNexusFactory() external view returns (address) {
return address(i_nexusFactory);
}
function getNexusOwnerId() external view returns (uint32) {
return i_urbitId;
}
/// @notice Returns whether mgmt proxy is allowed and the current proxy address in Azimuth (if any)
function isManagementProxyAllowed() external view returns (bool) {
return _allowManagementProxy;
}
function getManagementProxy() external view returns (address) {
return i_azimuth.getManagementProxy(i_urbitId);
}
/// @notice Convenience getters related to dispenser/$URBIT
function getDispenser() public view returns (address) {
return i_nexusFactory.getPlanetMarketContract();
}
function getUrbitToken() public view returns (address) {
return i_urbitToken;
}
function urbitTokenBalance() external view returns (uint256) {
return IERC20(i_urbitToken).balanceOf(address(this));
}
function urbitTokenAllowance(
address spender
) external view returns (uint256) {
return IERC20(i_urbitToken).allowance(address(this), spender);
}
/// @notice Returns status flags for a given urbitId
function isMember(uint32 urbitId) external view returns (bool) {
return _isMember[urbitId];
}
function isBanned(uint32 urbitId) external view returns (bool) {
return _isBanned[urbitId];
}
function hasQuit(uint32 urbitId) external view returns (bool) {
return _hasQuit[urbitId];
}
function onboardNonce(uint32 urbitId) external view returns (uint256) {
return _onboardNonce[urbitId];
}
function rageQuitNonce(uint32 urbitId) external view returns (uint256) {
return _quitNonce[urbitId];
}
////////////////////////
// Internal Functions //
////////////////////////
function _changeNexusInviter(
address newNexusInviter
) internal returns (bool success) {
address oldNexusInviter = _nexusNode.nexusInviter;
_nexusNode.nexusInviter = newNexusInviter;
emit NexusInviterChanged({
oldNexusInviter: oldNexusInviter,
newNexusInviter: newNexusInviter
});
return true;
}
/////////////////
//// Receive ////
/////////////////
receive() external payable {
revert("Direct ETH transfers not accepted");
}
//////////////////
//// Fallback ////
//////////////////
fallback() external payable {
revert("Function does not exist");
}
}
"
},
"src/Interfaces/INexusFactory.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface INexusFactory {
////////////
// Events //
////////////
// Lifecycle of Nexus contracts
event NexusCreated(uint32 indexed leaderUrbitId, address indexed nexusAddress, address indexed inviteKey, string nexusId);
event NexusDestroyed(address indexed nexusAddress);
// 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);
// Nexus-sourced signals centralized through factory (for indexing)
event NexusInviteKeysUpdated(address indexed nexusContract, uint32 indexed urbitId, address indexed newPermissionedInviter);
event NexusUrbitInvited(address indexed nexusContract, uint32 indexed inviter, uint32 indexed invitee);
event NexusUrbitDeparted(address indexed nexusContract, uint32 indexed leader, uint32 indexed participant);
event NexusUrbitJoined(address indexed nexusContract, uint32 indexed leader, uint32 indexed participant);
////////////////////////
// External Functions //
////////////////////////
function changePlanetMarket(
address newMarket
) external returns (bool success);
function createNexus(
uint32 urbitId,
address nexusInviter,
uint8 freeMonths,
uint256 price,
string memory nexusId
) external returns (address nexusContract);
function destroyNexus() external returns (bool success);
function getOwnerId() external view returns (uint32 owner);
function getPlanetMarketContract()
external
view
returns (address planetMarketContract);
function getUrbitToken() external view returns (address);
function updateInviteKeys(
address nexusContract,
address newPermissionedInviter,
uint32 urbitId
) external returns (bool success);
function urbitDeparted(
address nexusContract,
uint32 leader,
uint32 participant
) external returns (bool success);
function urbitJoined(
address nexusContract,
uint32 leader,
uint32 participant
) external returns (bool success);
function proposeOwner(uint32 newOwnerId) external returns (uint32 proposedOwner);
function revokeOwnershipProposal() external returns (uint32 proposedOwner);
function acceptOwnership() external returns (uint32 owner);
function rejectOwnership() external returns (uint32 proposedOwner);
}
"
},
"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/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 actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
"
},
"src/Interfaces/INexus.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface INexus {
/////////////
// Structs //
/////////////
// parameter ordering done to allow efficient bit packing
struct NexusNode {
// @ud of owner's Urbit ID
uint32 ownerId; // 4 bytes
// public address of permissioned ticket
address nexusInviter; // 20 bytes
// count of available invites
uint32 inviteCount; // 4 bytes
// number of months before subscription cost kicks in
uint8 freeMonths; // 1 byte
// is the node deactivated?
bool isDestroyed; // 1 byte
// price of monthly subscription
uint256 price; // 32 bytes
// URL-safe identifier for this nexus
string nexusId;
}
////////////
// Events //
////////////
// Invite escrow/accounting
event InviteTokenDeposited(
address indexed depositor,
uint256 count
);
event InviteTokenWithdrawn(
address indexed withdrawer,
uint256 count
);
event UrbitTokensRetrieved(
address indexed to,
uint256 amount
);
// Inviter configuration
event NexusInviterAdded(address indexed nexusInviter);
event NexusInviterChanged(
address indexed oldNexusInviter,
address indexed newNexusInviter
);
event NexusInviterRemoved(
address indexed nexusInviter
);
// Membership lifecycle
event InviteClaimed(
uint32 indexed inviterUrbitId,
uint32 indexed invitedUrbitId
);
event UrbitIdBanned(uint32 indexed bannedUrbitId);
event UrbitIdUnbanned(uint32 indexed unbannedUrbitId);
event UrbitIdRageQuit(uint32 indexed rageQuitUrbitId);
event UrbitIdPermissionedQuit(
uint32 indexed quittingUrbitId
);
// Admin toggles / lifecycle
event ManagementProxyAccessSet(
bool allowed,
address indexed proxy
);
event NexusDestroyed(
address indexed nexus,
uint32 ownerId
);
////////////////////////
// External Functions //
////////////////////////
function addInvites(uint32 count) external returns (bool success);
function addNexusInviter(
address newInviter
) external returns (bool success);
function banUrbitId(uint32 urbitId) external returns (bool success);
function changeNexusInviter(
address newNexusInviter
) external returns (bool success);
function destroyNexus() external returns (bool success);
function onboardExistingUrbitID(
address recipient,
uint32 existingUrbitId,
bytes memory signature
) external returns (bool success);
function onboardNewUrbitID(
address recipient,
uint32 newUrbitId
) external returns (bool success);
function permissionedQuitNexus(
uint32 quittingUrbitId,
bytes memory signature
) external returns (bool success);
function rageQuitNexus(
uint32 rageQuitUrbitId
) external returns (bool success);
function removeInvites(uint32 count) external returns (bool success);
function removeNexusInviter() external returns (bool success);
function retrieveUrbitTokens(
address withdrawalAddress
) external returns (bool success);
function unbanUrbitId(uint32 urbitId) external returns (bool success);
function allowManagementProxyAccess(
bool access
) external returns (address managementAddress);
//////////////////////
// Getter Functions //
//////////////////////
function getNexusNode() external view returns (NexusNode memory);
function getNexusInviter() external view returns (address);
function getInviteCount() external view returns (uint32);
function getTotalInvitesIssued() external view returns (uint32);
function getFreeMonths() external view returns (uint8);
function isDestroyed() external view returns (bool);
function getPrice() external view returns (uint256);
function getNexusId() external view returns (string memory);
function getAzimuth() external view returns (address);
function getNexusFactory() external view returns (address);
function getNexusOwnerId() external view returns (uint32);
function isManagementProxyAllowed() external view returns (bool);
function getManagementProxy() external view returns (address);
function urbitTokenBalance() external view returns (uint256);
function isMember(uint32 urbitId) external view returns (bool);
function isBanned(uint32 urbitId) external view returns (bool);
function hasQuit(uint32 urbitId) external view returns (bool);
function onboardNonce(uint32 urbitId) external view returns (uint256);
function rageQuitNonce(uint32 urbitId) external view returns (uint256);
}
"
},
"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/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
Submitted on: 2025-09-26 20:54:05
Comments
Log in to comment.
No comments yet.