Skip to content

Commit

Permalink
Add soulbound tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
ScreamingHawk committed Sep 10, 2024
1 parent 8eb8617 commit 26e3bd9
Show file tree
Hide file tree
Showing 5 changed files with 427 additions and 0 deletions.
55 changes: 55 additions & 0 deletions src/tokens/ERC721/presets/soulbound/ERC721Soulbound.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import {ERC721Items} from "@0xsequence/contracts-library/tokens/ERC721/presets/items/ERC721Items.sol";
import {IERC721Soulbound, IERC721SoulboundFunctions} from "@0xsequence/contracts-library/tokens/ERC721/presets/soulbound/IERC721Soulbound.sol";

/**
* An implementation of ERC-721 that prevents transfers.
*/
contract ERC721Soulbound is ERC721Items, IERC721Soulbound {

bytes32 public constant TRANSFER_ADMIN_ROLE = keccak256("TRANSFER_ADMIN_ROLE");

bool internal _transferLocked;

constructor() ERC721Items() {}

/// @inheritdoc ERC721Items
function initialize(
address owner,
string memory tokenName,
string memory tokenSymbol,
string memory tokenBaseURI,
string memory tokenContractURI,
address royaltyReceiver,
uint96 royaltyFeeNumerator
) public virtual override {
_transferLocked = true;
_grantRole(TRANSFER_ADMIN_ROLE, owner);
super.initialize(owner, tokenName, tokenSymbol, tokenBaseURI, tokenContractURI, royaltyReceiver, royaltyFeeNumerator);
}

/// @inheritdoc IERC721SoulboundFunctions
function setTransferLocked(bool locked) external override onlyRole(TRANSFER_ADMIN_ROLE) {
_transferLocked = locked;
}

/// @inheritdoc IERC721SoulboundFunctions
function getTransferLocked() external view override returns (bool) {
return _transferLocked;
}

function _beforeTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 quantity
) internal virtual override {
// Mint transactions allowed
if (_transferLocked && from != address(0)) {
revert TransfersLocked();
}
super._beforeTokenTransfers(from, to, startTokenId, quantity);
}
}
60 changes: 60 additions & 0 deletions src/tokens/ERC721/presets/soulbound/ERC721SoulboundFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import {IERC721SoulboundFactory, IERC721SoulboundFactoryFunctions} from
"@0xsequence/contracts-library/tokens/ERC721/presets/soulbound/IERC721SoulboundFactory.sol";
import {ERC721Soulbound} from "@0xsequence/contracts-library/tokens/ERC721/presets/soulbound/ERC721Soulbound.sol";
import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/SequenceProxyFactory.sol";

/**
* Deployer of ERC-721 Soulbound proxies.
*/
contract ERC721SoulboundFactory is IERC721SoulboundFactory, SequenceProxyFactory {
/**
* Creates an ERC-721 Soulbound Factory.
* @param factoryOwner The owner of the ERC-721 Soulbound Factory
*/
constructor(address factoryOwner) {
ERC721Soulbound impl = new ERC721Soulbound();
SequenceProxyFactory._initialize(address(impl), factoryOwner);
}

/// @inheritdoc IERC721SoulboundFactoryFunctions
function deploy(
address proxyOwner,
address tokenOwner,
string memory name,
string memory symbol,
string memory baseURI,
string memory contractURI,
address royaltyReceiver,
uint96 royaltyFeeNumerator
)
external
returns (address proxyAddr)
{
bytes32 salt =
keccak256(abi.encode(tokenOwner, name, symbol, baseURI, contractURI, royaltyReceiver, royaltyFeeNumerator));
proxyAddr = _createProxy(salt, proxyOwner, "");
ERC721Soulbound(proxyAddr).initialize(tokenOwner, name, symbol, baseURI, contractURI, royaltyReceiver, royaltyFeeNumerator);
emit ERC721SoulboundDeployed(proxyAddr);
return proxyAddr;
}

/// @inheritdoc IERC721SoulboundFactoryFunctions
function determineAddress(
address proxyOwner,
address tokenOwner,
string memory name,
string memory symbol,
string memory baseURI,
string memory contractURI,
address royaltyReceiver,
uint96 royaltyFeeNumerator
) external view returns (address proxyAddr)
{
bytes32 salt =
keccak256(abi.encode(tokenOwner, name, symbol, baseURI, contractURI, royaltyReceiver, royaltyFeeNumerator));
return _computeProxyAddress(salt, proxyOwner, "");
}
}
25 changes: 25 additions & 0 deletions src/tokens/ERC721/presets/soulbound/IERC721Soulbound.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

interface IERC721SoulboundFunctions {
/**
* Sets the transfer lock.
* @param locked Whether or not transfers are locked.
*/
function setTransferLocked(bool locked) external;

/**
* Gets the transfer lock.
* @return Whether or not transfers are locked.
*/
function getTransferLocked() external view returns (bool);
}

interface IERC721SoulboundSignals {
/**
* Transfers locked.
*/
error TransfersLocked();
}

interface IERC721Soulbound is IERC721SoulboundFunctions, IERC721SoulboundSignals {}
64 changes: 64 additions & 0 deletions src/tokens/ERC721/presets/soulbound/IERC721SoulboundFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

interface IERC721SoulboundFactoryFunctions {
/**
* Creates an ERC-721 Soulbound proxy.
* @param proxyOwner The owner of the ERC-721 Soulbound proxy
* @param tokenOwner The owner of the ERC-721 Soulbound implementation
* @param name The name of the ERC-721 Soulbound proxy
* @param symbol The symbol of the ERC-721 Soulbound proxy
* @param baseURI The base URI of the ERC-721 Soulbound proxy
* @param contractURI The contract URI of the ERC-721 Soulbound proxy
* @param royaltyReceiver Address of who should be sent the royalty payment
* @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500)
* @return proxyAddr The address of the ERC-721 Soulbound Proxy
*/
function deploy(
address proxyOwner,
address tokenOwner,
string memory name,
string memory symbol,
string memory baseURI,
string memory contractURI,
address royaltyReceiver,
uint96 royaltyFeeNumerator
)
external
returns (address proxyAddr);

/**
* Computes the address of a proxy instance.
* @param proxyOwner The owner of the ERC-721 Soulbound proxy
* @param tokenOwner The owner of the ERC-721 Soulbound implementation
* @param name The name of the ERC-721 Soulbound proxy
* @param symbol The symbol of the ERC-721 Soulbound proxy
* @param baseURI The base URI of the ERC-721 Soulbound proxy
* @param contractURI The contract URI of the ERC-721 Soulbound proxy
* @param royaltyReceiver Address of who should be sent the royalty payment
* @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500)
* @return proxyAddr The address of the ERC-721 Soulbound Proxy
*/
function determineAddress(
address proxyOwner,
address tokenOwner,
string memory name,
string memory symbol,
string memory baseURI,
string memory contractURI,
address royaltyReceiver,
uint96 royaltyFeeNumerator
)
external
returns (address proxyAddr);
}

interface IERC721SoulboundFactorySignals {
/**
* Event emitted when a new ERC-721 Soulbound proxy contract is deployed.
* @param proxyAddr The address of the deployed proxy.
*/
event ERC721SoulboundDeployed(address proxyAddr);
}

interface IERC721SoulboundFactory is IERC721SoulboundFactoryFunctions, IERC721SoulboundFactorySignals {}
Loading

0 comments on commit 26e3bd9

Please sign in to comment.