This proposal is a security token standard that extends ERC-1155 to provide a flexible framework for managing compliant real-asset security tokens. It introduces the concept of partitions, where each tokenId represents a distinct partition with its own set of rights and privileges. This makes it suitable for various use cases, particularly semi-fungible asset management. The standard also includes features like token locking, forced transfers for recovery, address freezing, payouts, and dynamic compliance management using off-chain vouchers.
The growing demand for tokenized real-world assets necessitates a token standard that can accommodate the unique requirements of security tokens. Existing standards, while powerful, do not fully address the need for flexible partitioning and comprehensive compliance management.
This standard builds upon ERC-1155 by introducing partitions, allowing for the creation of semi-fungible tokens representing fractional ownership, different share classes, or other distinct units within a single token contract. This flexibility is crucial for tokenizing complex real-world assets like real estate or funds.
Furthermore, it includes features essential for security tokens, such as token locking for vesting or holding periods, forced transfers for recovery in case of lost keys, address freezing for regulatory compliance, efficient payout mechanisms, and dynamic compliance management using off-chain vouchers.
By providing a standardized interface for these features, this proposal aims to facilitate the development of interoperable and compliant security token ecosystems.
The partition paradigm offers significant flexibility and power in managing security tokens:
In this use case, a commercial real estate property with 100 floors is being tokenized using this proposal. Each floor is represented as a unique non-fungible token (NFT) partition, allowing for fractional ownership and separate management of individual floors.
Property Representation: The entire commercial property is tokenized using the proposed contract, with each floor being assigned a unique tokenId representing an NFT partition.
Fractional Ownership: Each floor's NFT partition can be divided into multiple fungible tokens, enabling fractional ownership. For instance, if a floor is divided into 100 tokens, multiple investors can own portions of that floor.
Dynamic Pricing: Since each floor is a separate partition, the pricing of tokens within a partition can be adjusted dynamically based on factors such as floor level, amenities, or market demand. This flexibility allows for accurate representation of the varying values of different floors.
Transfer of Ownership: The ownership of each floor's NFT partition can be transferred seamlessly to token holders using the safeTransferFrom function. This enables the seamless transfer of ownership rights for specific floors.
Compliance Management: Different compliance rules and transfer restrictions can be applied to each partition (floor) based on regulatory requirements or issuer-defined rules. The canTransfer function can be used to enforce these rules before allowing transfers.
Payouts: The payout and batchPayout functions can be used to distribute rental income, dividends, or other payouts to token holders of specific floor partitions efficiently.
By leveraging proposal, this use case demonstrates the ability to tokenize complex real estate assets while maintaining granular control over ownership, pricing, compliance, and payouts for individual units within the property.
In this use case, a company is tokenizing its securities and wants to comply with different regulations for U.S. accredited investors (Reg D) and non-U.S. investors (Reg S).
Initial Partitions: The company deploys an proposed standard and creates two partitions: one for Reg D investors (accredited U.S. investors) and another for Reg S investors (non-U.S. investors).
Dynamic Allocation: As the offering progresses, the company can dynamically mint tokens into the appropriate partition based on investor eligibility. For example, if a U.S. accredited investor wants to participate, tokens can be minted in the Reg D partition, while tokens for non-U.S. investors are minted in the Reg S partition.
Compliance Management: Each partition can have its own set of compliance rules and transfer restrictions. The canTransfer function can be integrated with off-chain compliance services to verify the eligibility of a transfer based on the specific rules for each partition.
Temporary Non-Fungibility: During the initial offering period, tokens in the Reg D and Reg S partitions may need to be treated as non-fungible due to different regulatory requirements. However, after the holding period, the company can create a new joint partition and allow token holders to deposit their old partitioned tokens to receive the new joint partition tokens, merging the two classes.
Payouts: The payout and batchPayout functions can be used to distribute dividends, interest payments, or other payouts to token holders in each partition based on their respective rights and privileges.
By utilizing the proposal, this use case demonstrates the ability to tokenize securities while maintaining compliance with different regulatory regimes, dynamically allocating tokens based on investor eligibility, and efficiently managing payouts and potential mergers of different share classes.
In the world of tokenized securities, maintaining compliance with regulatory requirements is of utmost importance. This proposal provides a robust mechanism to handle situations where an investor's tokens need to be forcibly transferred due to violations of Anti-Money Laundering (AML), Know Your Customer (KYC), or other compliance-related regulations.
Let's consider the scenario of Alice, an investor who holds tokens in the proposed token compliant security token contract. During the regular compliance checks conducted by the token issuer or a designated compliance service, it is discovered that Alice's wallet address is associated with suspicious activities related to money laundering or other financial crimes.
In such a situation, the regulatory authorities or the contract administrators may decide to freeze Alice's account and initiate a forced transfer of her tokens to a designated address controlled by the issuer or a recovery agent. The forceTransfer function in this proposal enables this process.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
pragma solidity ^0.8.0;
interface IERC7518 is IERC1155, IERC165{
event TokensLocked(address indexed account, uint indexed id, uint256 amount, uint256 releaseTime);
event TokenUnlocked(address indexed account, uint indexed id);
event TokensForceTransferred(address indexed from, address indexed to, uint indexed id, uint256 amount);
event AddressFrozen(address indexed account, bytes data);
event AddressUnfrozen(address indexed account, bytes data);
// Emitted when the transferability of tokens with a specific ID is restricted.
event TransferRestricted(uint indexed id);
// Emitted when the transferability restriction of tokens with a specific ID is removed.
event TransferRestrictionRemoved(uint indexed id);
event PayoutDelivered(address indexed from, address indexed to, uint256 amount);
/**
* @dev Retrieves the transferable balance of tokens for the specified account and ID.
* @param account The address of the account.
* @param id The token ID.
* @return The transferable balance of tokens.
*/
function transferableBalance(address account, uint id) external view returns (uint);
/**
* @dev Retrieves the locked balance of tokens for the specified account and ID.
* @param account The address of the account.
* @param id The token ID.
* @return The locked balance of tokens.
*/
function lockedBalanceOf(address account, uint256 id) external view returns (uint256);
/**
* @dev Restricts the transferability of tokens with the specified ID.
* @param id The token ID.
* @return A boolean value indicating whether the operation was successful.
*/
function restrictTransfer(uint id) external returns (bool);
/**
* @dev Removes the transferability restriction of tokens with the specified ID.
* @param id The token ID.
* @return A boolean value indicating whether the operation was successful.
*/
function removeRestriction(uint id) external returns (bool);
/**
* @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).
* @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard).
* After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard).
* @param _from Source address
* @param _to Target address
* @param _id ID of the token type
* @param _value Transfer amount
* @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`
*/
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) override external;
/**
* @dev Checks if a transfer is allowed.
* @param from The address to transfer tokens from.
* @param to The address to transfer tokens to.
* @param id The token ID.
* @param amount The amount of tokens to transfer.
* @param data Additional data related to the transfer.
* @return status A boolean value indicating whether the transfer is allowed.
*/
function canTransfer(address from, address to, uint id, uint amount, bytes calldata data) external view returns (bool status);
/**
* @dev lock token till a particular block time.
* @param account The address of the account for which tokens will be locked.
* @param id The token ID.
* @param amount The amount of tokens to be locked for the account.
* @param releaseTime The timestamp indicating when the locked tokens can be released.
* @return bool Returns true if the tokens are successfully locked, otherwise false.
*/
function lockTokens(address account, uint id, uint256 amount, uint256 releaseTime) external returns (bool);
/**
* @dev Unlocks tokens that have crossed the release time for a specific account and id.
* @param account The address of the account to unlock tokens for.
* @param id The token ID.
*/
function unlockToken(address account, uint256 id) external;
/**
* @dev Force transfer in cases like recovery of tokens.
* @param from The address to transfer tokens from.
* @param to The address to transfer tokens to.
* @param id The token ID.
* @param amount The amount of tokens to transfer.
* @param data Additional data related to the transfer.
* @return A boolean value indicating whether the operation was successful.
*/
function forceTransfer(address from, address to, uint256 id, uint256 amount, bytes memory data) external returns (bool);
/**
* @dev Freezes specified address.
* @param account The address to be frozen.
* @param data Additional data related to the freeze operation.
* @return A boolean value indicating whether the operation was successful.
*/
function freezeAddress(address account, bytes calldata data) external returns (bool);
/**
* @dev Unfreezes specified address.
* @param account The address to be unfrozen.
* @param data Additional data related to the unfreeze operation.
* @return A boolean value indicating whether the operation was successful.
*/
function unFreeze(address account, bytes memory data) external returns (bool);
/**
* @dev Sends payout to single address with corresponding amounts.
* @param to address to send the payouts to.
* @param amount amount representing the payouts to be sent.
* @return A boolean indicating whether the batch payouts were successful.
*/*
function payout(address calldata to, uint256 calldata amount) public returns (bool);
/**
* @dev Sends batch payouts to multiple addresses with corresponding amounts.
* @param to An array of addresses to send the payouts to.
* @param amount An array of amounts representing the payouts to be sent.
* @return A boolean indicating whether the batch payouts were successful.
*/
function batchPayout(address[] calldata to, uint256[] calldata amount) public returns (bool);
}
struct AssetRef {
address tokenContract;
uint256 tokenId;
bytes32 partitionId;
}
struct AssetProfile {
bytes32 assetId;
bytes32 policyId;
bytes32 profileHash;
string profileURI;
}
struct ActionRecord {
bytes32 actionId;
bytes32 actionType;
bytes32 assetId;
bytes32 assetKey;
uint64 recordTime;
uint64 effectiveTime;
bytes32 termsHash;
string termsURI;
uint8 status;
}
/**
* @notice Optional discovery extension for an asset profile.
* @dev The implementing token contract also implements IERC7518.
*/
interface IERC7518AssetProfile is IERC165 {
event AssetProfileUpdated(
bytes32 indexed assetKey,
bytes32 indexed assetId,
bytes32 indexed policyId,
bytes32 profileHash,
string profileURI
);
function assetKey(
AssetRef calldata asset
) external view returns (bytes32 key);
function assetProfile(
AssetRef calldata asset
) external view returns (AssetProfile memory profile);
function identifier(
AssetRef calldata asset,
bytes32 scheme
) external view returns (string memory value);
}
/**
* @notice Optional discovery extension for named operational roles.
* @dev The implementing token contract also implements IERC7518.
*/
interface IERC7518Roles is IERC165 {
event RoleUpdated(
bytes32 indexed role,
address indexed account,
bytes32 indexed identityId,
bool active
);
function roleHolder(
bytes32 role,
address account
) external view returns (bool active);
function roleIdentity(
bytes32 role,
address account
) external view returns (bytes32 identityId);
}
/**
* @notice Optional authorization extension for dynamic compliance vouchers.
* @dev The implementing token contract also implements IERC7518.
*/
interface IERC7518Authorization is IERC165 {
enum AuthorizationState {
Unused,
Consumed,
Revoked
}
struct ComplianceAuthorization {
address token;
address authority;
bytes32 operationHash;
bytes32 policyId;
bytes32 contextHash;
bytes32 nonce;
uint48 validAfter;
uint48 validBefore;
}
event AuthorizationConsumed(
bytes32 indexed authorizationHash,
address indexed authority,
bytes32 indexed policyId
);
event AuthorizationRevoked(
bytes32 indexed authorizationHash,
bytes32 indexed reasonCode
);
function authorizationHash(
ComplianceAuthorization calldata authorization
) external view returns (bytes32);
function validateAuthorization(
ComplianceAuthorization calldata authorization,
bytes calldata signature
) external view returns (bool);
function authorizationState(
bytes32 authorizationHash_
) external view returns (AuthorizationState);
function revokeAuthorization(
bytes32 authorizationHash_,
bytes32 reasonCode
) external;
}
/**
* @notice Optional record extension for asset actions.
* @dev The implementing token contract also implements IERC7518.
*/
interface IERC7518ActionRecord is IERC165 {
event ActionAnnounced(
bytes32 indexed actionId,
bytes32 indexed assetKey,
bytes32 indexed actionType,
bytes32 assetId,
uint64 recordTime,
uint64 effectiveTime,
bytes32 termsHash
);
event ActionStatusChanged(
bytes32 indexed actionId,
uint8 previousStatus,
uint8 newStatus,
bytes32 resultHash
);
function announceAction(ActionRecord calldata record) external;
function setActionStatus(
bytes32 actionId,
uint8 newStatus,
bytes32 resultHash
) external;
function actionRecord(
bytes32 actionId
) external view returns (ActionRecord memory record);
}
An IERC7518 implementation MUST implement ERC-1155 and ERC-165. It MUST return true from supportsInterface for:
0xd9b67a26, the ERC-1155 interface identifier.0x5a264a5d, the IERC7518 interface identifier defined above.Each extension below is OPTIONAL and has its own ERC-165 interface identifier. Support for one extension does not imply support for another.
IERC7518AssetProfile MUST return true for 0xe8aa3d31.IERC7518Roles MUST return true for 0x779da628.IERC7518Authorization MUST return true for 0x5b880cc8.IERC7518ActionRecord MUST return true for 0x9118aff3.An implementation of IERC7518Authorization MUST expose its EIP-712 domain through ERC-5267 and follow the authorization rules below. The IERC7518 interface identifier remains 0x5a264a5d.
transferableBalanceRetrieves the transferable balance of tokens for the specified account and ID.
function transferableBalance(address account,uint id) external view returns (uint)
balanceOf(account, id) - lockedBalanceOf(account, id).lockedBalanceOfRetrieves the locked balance of tokens for the specified account and ID.
function lockedBalanceOf(address account,uint256 id) external view returns (uint256)
account and id.restrictTransferRestricts the transferability of tokens with the specified ID.
function restrictTransfer(uint id) external returns (bool)
id.TransferRestricted.removeRestrictionRemoves the transferability restriction of tokens with the specified ID.
function removeRestriction(uint id) external returns (bool)
id. MUST revert if id was not previously restricted.TransferRestrictionRemoved.safeTransferFromfunction safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) override external;
_to is the zero address._id is lower than the _value sent.TransferSingle event to reflect the balance change (see "Safe Transfer Rules" section of the standard).canTransfer function to check if the transfer can proceedcanTransferDetermine transferring a specified amount of a token from one address to another.
function canTransfer(address from,address to,uint id,uint amount,bytes calldata data) external view returns (bool status);
to and from are not frozen address.id of the transfer should not be restrictedamount is a transferable balance.lockTokensLocks a specified amount of tokens from an account for a specified duration.
function lockTokens(address account,uint id,uint256 amount,uint256 releaseTime) external returns (bool);
TokensLocked.unlockTokenUnlocks tokens that have crossed the release time for a specific account and id.
function unlockToken(address account,uint256 id) external;
account address and id.block.timestampTokenUnlocked.forceTransferForce transfer in cases like recovery of tokens
function forceTransfer(address from,address to,uint256 id,uint256 amount,bytes memory data) external returns (bool);
from address is not Frozen.to address is Frozen.TokensForceTransferred.freezeFreezes specified address. The Freeze function takes in the account address to be frozen and additional data, and returns a boolean value indicating whether the operation was successful.
function freezeAddress(address account,bytes data) external returns (bool);
account to transfer and payout.AddressFrozen.unFreezeThe Unfreeze function takes in the account address to be unfrozen and additional data, and returns a boolean value indicating whether the operation was successful.
function unFreeze(address account,bytes memory data) external returns (bool);
accountAddressUnfrozen.payoutSend payouts to single address, receiver will be receiving a specific amount of tokens.
function payout(address calldata to,uint256 calldata amount) public returns (bool)
to address is frozen address.PayoutDelivered.batchPayoutSend payouts to multiple addresses at once, with each address receiving a specific amount of tokens. It can be used for various purposes such as distributing rewards, dividends, or interest payment.
function batchPayout(address[] calldata to,uint256[] calldata amount) public returns (bool)
to address is frozen address.PayoutDelivered.IERC7518AssetProfile provides discovery for the economic asset and its current policy. The extension is OPTIONAL.
AssetRef identifies a position by token contract, token identifier and partition identifier. An implementation MUST validate each reference before returning profile data. It MUST revert when a reference is invalid or unsupported.
assetKey MUST return:
keccak256(
abi.encode(
block.chainid,
asset.tokenContract,
asset.tokenId,
asset.partitionId
)
)
The value is a chain local key for the referenced position. The same valid reference MUST return the same key while the chain identifier and token contract remain unchanged.
assetProfile MUST return a nonzero assetId for a recognized position. assetId identifies the economic asset across contract upgrades or chain representations. policyId identifies the current policy used for new authorizations. profileHash MUST equal the keccak256 hash of the exact profile document bytes available through profileURI.
When any asset profile field changes, the implementation MUST emit AssetProfileUpdated. An implementation MUST NOT silently assign a different assetId to the same economic asset.
For identifier, scheme MUST equal keccak256(bytes(canonicalSchemeName)). A canonical scheme name SHOULD use uppercase ASCII such as ISIN, DTI or CUSIP. The function MUST return an empty string when a supported scheme has no assigned value.
IERC7518Roles provides discovery for named operational roles. The extension is OPTIONAL.
The following role keys are defined:
keccak256("REGISTRAR")
keccak256("TRANSFER_AGENT")
keccak256("PAYING_AGENT")
keccak256("COMPLIANCE_AUTHORITY")
An implementation MAY define additional role keys. roleHolder MUST return true only while the account holds the named role. roleIdentity MUST return the identity reference for an active holder and MUST return bytes32(0) when the account does not hold the role.
Every role assignment, removal or identity reference change MUST emit RoleUpdated. The method used to change role state is implementation specific and MUST use access control. An identity reference SHOULD be opaque and MUST NOT require personal data onchain.
IERC7518Authorization defines a typed compliance voucher with validation, replay state and revocation. The extension is OPTIONAL.
The EIP-712 type string MUST be:
ComplianceAuthorization(address token,address authority,bytes32 operationHash,bytes32 policyId,bytes32 contextHash,bytes32 nonce,uint48 validAfter,uint48 validBefore)
authorizationHash MUST return the EIP-712 digest for this value. The domain MUST bind the authorization to the current chainId and the implementing token as verifyingContract. The domain MUST be discoverable through ERC-5267.
The fields have the following meaning:
token is the token contract that may execute the authorized operation. It MUST equal address(this).authority is the approved signer for the authorization.operationHash binds the authorization to one exact token operation.policyId identifies the policy or rule set used by the authority.contextHash commits to application specific compliance context.nonce provides uniqueness within the signed authorization.validAfter is the first timestamp at which the authorization is valid, inclusive.validBefore is the timestamp at which the authorization expires, exclusive.validateAuthorization MUST return true only when all of the following are true:
block.timestamp is greater than or equal to validAfter and less than validBefore.authorizationState(authorizationHash(authorization)) is Unused.authority is approved by the implementation access control policy.authority and the EIP-712 digest.If authority is a contract, signature validation MUST use ERC-1271. The function MUST NOT change state and MUST return false for an invalid, expired, consumed or revoked authorization.
For safeTransferFrom, operationHash MUST be calculated from:
ERC7518SingleTransfer(address operator,address from,address to,uint256 id,uint256 amount,bytes32 applicationDataHash)
For safeBatchTransferFrom, operationHash MUST be calculated from:
ERC7518BatchTransfer(address operator,address from,address to,bytes32 idsHash,bytes32 amountsHash,bytes32 applicationDataHash)
The hashes MUST be calculated as follows:
bytes32 singleOperationHash = keccak256(
abi.encode(
keccak256("ERC7518SingleTransfer(address operator,address from,address to,uint256 id,uint256 amount,bytes32 applicationDataHash)"),
msg.sender,
from,
to,
id,
amount,
keccak256(applicationData)
)
);
bytes32 batchOperationHash = keccak256(
abi.encode(
keccak256("ERC7518BatchTransfer(address operator,address from,address to,bytes32 idsHash,bytes32 amountsHash,bytes32 applicationDataHash)"),
msg.sender,
from,
to,
keccak256(abi.encode(ids)),
keccak256(abi.encode(amounts)),
keccak256(applicationData)
)
);
Binding the operator, parties, asset identifiers, amounts and application data prevents use with another operation.
An authorized transfer MUST place the voucher in the current ERC-1155 data parameter. The standardized envelope is:
abi.encode(
bytes4(0x7518a001),
authorization,
signature,
applicationData
)
The first value is the ERC-7518 authorization magic. When this magic is absent, data remains application defined. When it is present, an implementation of IERC7518Authorization MUST decode the remaining values as ComplianceAuthorization, bytes and bytes. The complete original data value MUST be passed unaltered to the ERC-1155 receiver hook.
A state changing transfer using this envelope MUST:
operationHash.Consumed.AuthorizationConsumed.Consumption MUST occur in the same transaction as the transfer and before any external receiver hook. If the transfer reverts, the state change and event are reverted. Authorization consumption is available only through transfer execution.
revokeAuthorization MUST be access controlled. It MUST mark an Unused authorization hash as Revoked and emit AuthorizationRevoked. A Consumed authorization MUST remain Consumed and both terminal states MUST remain final.
IERC7518ActionRecord provides records for asset actions such as splits, tenders or votes. The extension is OPTIONAL.
The status values are:
| Value | Meaning |
|---|---|
0 |
Unknown |
1 |
Announced |
2 |
Active |
3 |
Completed |
4 |
Cancelled |
announceAction MUST require a nonzero action identifier that has not been used. It MUST store the record with status Announced and emit ActionAnnounced.
After announcement, actionId, actionType, assetId, assetKey, recordTime, effectiveTime, termsHash and termsURI MUST remain unchanged. recordTime and effectiveTime MUST use Unix time in seconds. termsHash MUST equal the keccak256 hash of the exact terms document bytes available through termsURI.
setActionStatus MUST emit ActionStatusChanged. A completed or cancelled action MUST NOT return to announced or active status. State changing functions in this extension MUST use access control.
actionRecord MUST return status Unknown for an action identifier that has not been announced. It MUST NOT return an announced record with a zero action identifier.
When IERC7518AssetProfile and IERC7518Authorization are both implemented, an authorization policyId MUST equal the current asset profile policyId when the authorization is consumed.
When IERC7518Roles and IERC7518Authorization are both implemented, an authorization authority MUST hold the COMPLIANCE_AUTHORITY role when the authorization is consumed.
When IERC7518AssetProfile and IERC7518ActionRecord are both implemented, the action assetKey and assetId MUST match the current asset profile when the action is announced.
This proposal facilitates interoperability with non-fungible and fungible tokens through a token wrapping method. The process involves two key components: the token contracts representing the original and the proposed token contract for the wrapped version. Users seeking to wrap their tokens interact with the wrapping contract, which securely locks their original tokens and mints an equivalent amount of the proposed tokens to their address. Conversely, unwrapping is achieved by calling the contract's withdraw function, resulting in the burning of the proposed tokens and the release of the corresponding original tokens. Events are emitted for transparency, and robust security measures are implemented to safeguard user assets and address any potential vulnerabilities in the contract code. With this design, this proposal ensures the seamless conversion and compatibility with fungible and non-fungible tokens, promoting greater utility and usability across the Ethereum ecosystem.
An ERC-20 issuance MAY map its complete fungible supply to one ERC-7518 partition. The default mapping SHOULD use tokenId = 0. The source contract address, source chain and mapped ERC-7518 contract MUST identify the partition.
An ERC-721 adapter MAY map each source tokenId to one ERC-7518 partition. An ERC-1155 adapter SHOULD map each source tokenId to the equivalent partition when the supply and rights are equivalent.
The adapter or wrapper MAY implement any optional extension on the mapped ERC-7518 path.
interface IERC1155Wrapper is IERC7518 {
/**
@dev Emitted when a new wrapped token address is added to the set.
@param wrappedTokenAddress The address of the wrapped token that was added.
*/
event WrappedTokenAddressSet(address wrappedTokenAddress);
/**
@dev Emitted when tokens are wrapped.
@param The ERC-1155 token ID of the wrapped tokens.
@param amount The amount of tokens that were wrapped.
*/
event TokensWrapped(uint indexed id, uint256 amount);
/**
@dev Emitted when tokens are unwrapped.
@param wrappedTokenId Is the ERC-1155 token ID of the wrapped tokens.
@param amount The amount of tokens that were unwrapped.
*/
event TokensUnwrapped(uint indexed wrappedTokenId, uint256 amount);
/**
* @dev Sets the wrapped token address and logic for deciding partitions.
* @param wrappedTokenAddress The address of the wrapped token contract.
* @return A boolean value indicating whether the operation was successful.
*/
function setWrappedToken(address token) external returns (bool);
/**
* @dev Wraps the specified amount of tokens by depositing the original tokens and receiving new standard tokens.
* @param amount The amount of tokens to wrap.
* @param data Additional data for partition.
* @return A boolean value indicating whether the operation was successful.
*/
function wrapToken(uint256 amount, bytes calldata data) external returns (bool);
/**
* @notice Wraps a specified amount of tokens from a given partition into the main balance.
* @dev This function allows users to convert tokens from a specific partition back to the main balance,making them fungible with tokens from other partitions.
* @param partitionId The unique identifier of the partition from which tokens will be wrapped.
* @param id The unique identifier of the token.
* @param amount The amount of tokens to be wrapped from the specified partition.
* @param data Additional data that may be used to handle the wrap process (optional).
* @return success A boolean indicating whether the wrapping operation was successful or not.
*/
function wrapTokenFromPartition(bytes32 partitionId, uint256 id, uint256 amount, bytes calldata data) external returns (bool);
/**
* @dev Unwraps the specified amount of wrapped tokens by depositing the current tokens and receiving the original tokens.
* @param wrappedTokenId internal partition id.
* @param amount The amount of wrapped tokens to unwrap.
* @param data Additional data for partition.
* @return A boolean value indicating whether the operation was successful.
*/
function unwrapToken(uint256 wrappedTokenId, uint256 amount, bytes calldata data) external returns (bool);
/**
* @dev Retrieves the balance of wrapped tokens for the specified account and ID.
* @param account The address of the account.
* @param id The token ID.
* @param data Additional data for partition.
* @return The balance of wrapped tokens.
*/
function wrappedBalanceOf(address account, uint256 id, bytes calldata data) external view returns (uint256);
/**
* @dev Retrieves the balance of original tokens for the specified account and ID.
* @param account The address of the account.
* @param id The token ID.
* @param data Additional data for partition.
* @return The balance of original tokens.
*/
function originalBalanceOf(address account, uint256 id, bytes calldata data) external view returns (uint256);
}
setWrappedTokenAddressfunction setWrappedTokenAddress(address token) external returns (bool);
token address could be any security token standard i.e ERC-3643.wrapTokenfunction wrapToken(uint256 amount, bytes calldata data) external returns (bool);
id with the corresponding ERC-20 compatible security token.wrapTokenFromPartitionfunction wrapTokenFromPartition(bytes32 partitionId, uint256 id, uint256 amount, bytes calldata data) external returns (bool);
id with the corresponding partially fungible security token partitionId.unwrapTokenfunction unwrapToken(uint256 wrappedTokenId, uint256 amount, bytes calldata data) external returns (bool);
The proposal leverages the tokenId feature of ERC-1155 to represent distinct partitions within a token contract. Each tokenId corresponds to a unique partition with its own set of rights, privileges, and compliance rules. This enables the creation of semi-fungible tokens representing fractional ownership, different share classes, or other granular units.
tokenId value. tokenId are fungible within that partition but non-fungible across partitions. tokenIds.This standard includes functions for managing token transfers in accordance with regulatory requirements and issuer-defined rules. The canTransfer function checks whether a transfer is allowed based on factors such as token restrictions, frozen addresses, transferable balances, and token locking.
To facilitate dynamic compliance management, the standard uses a voucher-based pattern. An authorized entity (e.g., the issuer or a designated compliance service) generates a signed off-chain voucher that may encapsulate results from on-chain rule evaluation, oracle responses, or other checks. The canTransfer verifies vouchers signature, expiry, and binding of from, to, id, and amount together with contract state to determine the eligibility of a transfer.
Here's an example of how off-chain vouchers can be used with the proposal:
safeTransferFrom function on the proposed contract.canTransfer function verifies the authenticity and validity of the voucher by checking the signature and ensuring that the voucher details match the transfer parameters.Implementations MAY accept a cryptographically signed compliance voucher in the data argument of canTransfer, and state changing transfer functions defined in this standard, for example safeTransferFrom. Voucher schema and policy content are application defined. Implementations MAY combine on chain rules, oracle responses, and off chain verification for the voucher.
In case of lost or compromised wallets, the proposal includes a forceTransfer function that allows authorized entities (e.g., the issuer or a designated recovery agent) to transfer tokens from one address to another. This function bypasses the usual transfer restrictions and can be used as a recovery mechanism.
Provides functions for efficient payout distribution to token holders. The payout function allows sending payouts to a single address, while batchPayout enables sending payouts to multiple addresses in a single transaction. These functions streamline the process of distributing dividends, interest, or other payments to token holders.
The canTransfer function facilitates compliance checks during token transfers, offering adaptability through diverse implementation methods such as on-chain storage, oracle utilization, or any off-chain methodologies. This versatility ensures seamless integration with existing compliance frameworks, particularly in enforcing regulatory standards like KYC/AML. Additionally, functionalities like freezeAddress, restrictTransfer, lockToken and forceTransfer empower entities to regulate token movements based on specified conditions or regulatory requirements. Complementing these, the unlockToken function enhances transparency and accountability by facilitating the release of tokens post-compliance actions.
The authorization extension defines a typed approval for one transfer. It binds the token, authority, operation, policy reference, context, nonce and validity period. The authorization is verified and consumed in the transfer transaction to prevent replay.
Fractionalization in this standard is realized natively through ERC-1155 balances. For a given partition identified by tokenId, each unit of amount represents a fungible fraction of that partition's underlying security. Issuers mint and burn units to manage supply. Transfers use safeTransferFrom and safeBatchTransferFrom, and must pass canTransfer. Locks and freezes reduce the transferable portion of balances, and forceTransfer operates proportionally on units. Partitions can be merged by issuing a new tokenId and allowing holders to deposit old partitions for one-to-one or proportional conversion, and can be split by minting new tokenIds with defined conversion rules. All compliance rules and restrictions apply identically to every fractional unit in a partition.
This standard builds directly on ERC-1155, which already models fractional supply, batching, and balance-based accounting with broad ecosystem support. ERC-3525 was carefully evaluated but intentionally not adopted as a base. Its slot-and-value abstraction overlaps almost exactly with the partition and amount semantics defined here, yet inherits ERC-721's single-token overhead and per-position statefulness, which are inefficient for large scale issuance and compliance checks. Implementers who require slot-style compatibility may optionally expose ERC-3525 through ERC-165 by mapping slot to partition and value to amount. ERC-6909, while a minimal multi-token variant, omits partition metadata and compliance hooks and is therefore unsuitable for regulated assets. ERC-7518 extends this lineage by representing partitions as ERC-1155 tokenIds, enabling dynamic minting, merging, and voucher based dynamic compliance that can evolve without redeployment. Overall, ERC-7518 uses ERC-1155 as its canonical substrate because it balances composability, efficiency, and regulatory flexibility, turning fractionalization into a compliance-preserving primitive rather than an auxiliary layer.
The functions wrapToken and wrapTokenFromPartition are essential for simplifying conversions within the token system. wrapToken is specifically designed for wrapping ERC-20-like tokens to this protocol, on the other hand, wrapTokenFromPartition is used when we want to convert tokens from non-fungible tokens or any multi-standard token into proposed protocol. It allows for more specialized conversions, ensuring tokens from different standards can work together smoothly.
The unwrapToken function is used to reverse the process of wrapping tokens. When tokens are wrapped, they're usually locked or held in a special way to ensure they're used correctly. users can unlock or release these tokens, returning them to their original standard, essentially, frees up tokens that were previously locked, giving users more control over their assets in the ecosystem.
The payout function enables direct payments to individual token holders for one-off or event-triggered distributions, facilitating targeted disbursements. Meanwhile, the batchPayout function processes multiple payments in a single transaction, optimizing efficiency for larger-scale or regular payouts on the blockchain
The proposal is fully compatible with ERC-1155 , and any ERC-1155 compliant wallet or marketplace can interact with the proposal's tokens. The additional functions introduced by this proposal do not conflict with the ERC-1155 interface, ensuring seamless integration with existing ecosystem tools and infrastructure.
Implementations MAY expose IERC7518AssetProfile, IERC7518Roles, IERC7518Authorization and IERC7518ActionRecord through ERC-165. Each extension is independent. Authorized transfers use the current ERC-1155 data parameter. Existing calls continue to follow the current transfer interface.
forceTransfer, freezeAddress, and lockTokens. It is crucial to implement proper access control mechanisms, such as role-based permissions, to ensure that only authorized entities can execute these functions.safeTransferFrom, lockTokens, and forceTransfer should validate input parameters to prevent unauthorized or unintended actions. This includes checking for valid addresses, sufficient balances, and appropriate permissions.payout and batchPayout functions should ensure that only authorized entities can initiate payouts and that the total payout amount does not exceed the available balance. Proper access control and input validation are essential to prevent unauthorized or fraudulent payouts.revokeAuthorization MUST use explicit access control. Revocation reason codes are identifiers for the related offchain process.profileHash before relying on profile document bytes. Implementations SHOULD use stable retrieval locations and preserve prior profile events for audit.Copyright and related rights waived via CC0.