This proposal introduces fixed-income financial bonds with key characteristics defined to facilitate bond issuance in the primary market and enable buying or selling bonds in the secondary market. The standard also provides cross-chain functionalities for bonds operations and management accross multiple blockchains.
Motivation
Fixed-income instruments are a widely utilized asset class for corporations and other entities raising funds. However, transitioning to tokenized bonds is challenging due to existing standards like ERC-3475, which introduces unfamiliar concepts and leads to unnecessary gas consumption. Additionally, the lack of named variables like coupon, maturity date, and principal, makes it difficult to implement ERC-3475 since developers need to remember which metadata is assigned to each parameter.
Specification
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.
Every contract compliant with this ERC MUST implement the following Token Interface as well as the ERC-165 interface:
// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.0;/**
* @title ERC-7092 Financial Bonds Standard
*/interfaceIERC7092/** is ERC165 */{// events
/**
* @notice MUST be emitted when bond tokens are transferred, issued or redeemed, except during contract creation
* @param _from the account that owns bonds
* @param _to the account that receives the bond
* @param _amount amount of bond tokens to be transferred
*/eventTransfer(addressindexed_from,addressindexed_to,uint256_amount);/**
* @notice MUST be emitted when an account is approved or when the allowance is decreased
* @param _owner bond token's owner
* @param _spender the account to be allowed to spend bonds
* @param _amount amount of bond tokens allowed by _owner to be spent by `_spender`
* Or amount of bond tokens to decrease allowance from `_spender`
*/eventApproval(addressindexed_owner,addressindexed_spender,uint256_amount);/**
* @notice MUST be emitted when multiple bond tokens are transferred, issued or redeemed, with the exception being during contract creation
* @param _from array of bondholders accounts
* @param _to array of accounts to transfer bonds to
* @param _amount array of amounts of bond tokens to be transferred
*
** OPTIONAL - interfaces and other contracts MUST NOT expect this function to be present. MUST be emitted in `batchTransfer` and `batchTransferFrom` functions
*/eventTransferBatch(address[]_from,address[]_to,uint256[]_amount);/**
* @notice MUST be emitted when multiple accounts are approved or when the allowance is decreased from multiple accounts
* @param _owner bondholder account
* @param _spender array of accounts to be allowed to spend bonds, or to decrase the allowance from
* @param _amount array of amounts of bond tokens allowed by `_owner` to be spent by multiple accounts in `_spender`.
*
** OPTIONAL - interfaces and other contracts MUST NOT expect this function to be present. MUST be emitted in `batchApprove` and `batchDecreaseAllowance` functions
*/eventApprovalBatch(addressindexed_owner,address[]_spender,uint256[]_amount);// getter functions
/**
* @notice Returns the bond isin
*/functionisin()externalviewreturns(stringmemory);/**
* @notice Returns the bond name
*/functionname()externalviewreturns(stringmemory);/**
* @notice Returns the bond symbol
* It is RECOMMENDED to represent the symbol as a combination of the issuer Issuer'shorter name and the maturity date
* Ex: If a company named Green Energy issues bonds that will mature on october 25, 2030, the bond symbol could be `GE30` or `GE2030` or `GE102530`
*/functionsymbol()externalviewreturns(stringmemory);/**
* @notice Returns the bond currency. This is the contract address of the token used to pay and return the bond principal
*/functioncurrency()externalviewreturns(address);/**
* @notice Returns the bond denominiation. This is the minimum amount in which the Bonds may be issued. It must be expressend in unit of the principal currency
* ex: If the denomination is equal to 1,000 and the currency is USDC, then the bond denomination is equal to 1,000 USDC
*/functiondenomination()externalviewreturns(uint256);/**
* @notice Returns the issue volume (total debt amount). It is RECOMMENDED to express the issue volume in denomination unit.
*/functionissueVolume()externalviewreturns(uint256);/**
* @notice Returns the bond interest rate. It is RECOMMENDED to express the interest rate in basis point unit.
* 1 basis point = 0.01% = 0.0001
* ex: if interest rate = 5%, then coupon() => 500 basis points
*/functioncouponRate()externalviewreturns(uint256);/**
* @notice Returns the date when bonds were issued to investors. This is a Unix Timestamp like the one returned by block.timestamp
*/functionissueDate()externalviewreturns(uint256);/**
* @notice Returns the bond maturity date, i.e, the date when the pricipal is repaid. This is a Unix Timestamp like the one returned by block.timestamp
* The maturity date MUST be greater than the issue date
*/functionmaturityDate()externalviewreturns(uint256);/**
* @notice Returns the principal of an account. It is RECOMMENDED to express the principal in the bond currency unit (USDC, DAI, etc...)
* @param _account account address
*/functionprincipalOf(address_account)externalviewreturns(uint256);/**
* @notice Returns the amount of tokens the `_spender` account has been authorized by the `_owner``
* acount to manage their bonds
* @param _owner the bondholder address
* @param _spender the address that has been authorized by the bondholder
*/functionallowance(address_owner,address_spender)externalviewreturns(uint256);// setter functions
/**
* @notice Authorizes `_spender` account to manage `_amount`of their bond tokens
* @param _spender the address to be authorized by the bondholder
* @param _amount amount of bond tokens to approve
*/functionapprove(address_spender,uint256_amount)externalreturns(bool);/**
* @notice Lowers the allowance of `_spender` by `_amount`
* @param _spender the address to be authorized by the bondholder
* @param _amount amount of bond tokens to remove from allowance
*/functiondecreaseAllowance(address_spender,uint256_amount)externalreturns(bool);/**
* @notice Moves `_amount` bonds to address `_to`. This methods also allows to attach data to the token that is being transferred
* @param _to the address to send the bonds to
* @param _amount amount of bond tokens to transfer
* @param _data additional information provided by the token holder
*/functiontransfer(address_to,uint256_amount,bytescalldata_data)externalreturns(bool);/**
* @notice Moves `_amount` bonds from an account that has authorized the caller through the approve function
* This methods also allows to attach data to the token that is being transferred
* @param _from the bondholder address
* @param _to the address to transfer bonds to
* @param _amount amount of bond tokens to transfer.
* @param _data additional information provided by the token holder
*/functiontransferFrom(address_from,address_to,uint256_amount,bytescalldata_data)externalreturns(bool);// batch functions
/**
* @notice Authorizes multiple spender accounts to manage a specified `_amount` of the bondholder tokens
* @param _spender array of accounts to be authorized by the bondholder
* @param _amount array of amounts of bond tokens to approve
*
* OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability.
*/functionbatchApprove(address[]calldata_spender,uint256[]calldata_amount)externalreturns(bool);/**
* @notice Decreases the allowance of multiple spenders by corresponding amounts in `_amount`
* @param _spender array of accounts to be authorized by the bondholder
* @param _amount array of amounts of bond tokens to decrease the allowance from
*
* OPTIONAL - interfaces and other contracts MUST NOT expect this function to be present. The method is used to decrease token allowance.
*/functionbatchDecreaseAllowance(address[]calldata_spender,uint256[]calldata_amount)externalreturns(bool);/**
* @notice Transfers multiple bonds with amounts specified in the array `_amount` to the corresponding accounts in the array `_to`, with the option to attach additional data
* @param _to array of accounts to send the bonds to
* @param _amount array of amounts of bond tokens to transfer
* @param _data array of additional information provided by the token holder
*
* OPTIONAL - interfaces and other contracts MUST NOT expect this function to be present.
*/functionbatchTransfer(address[]calldata_to,uint256[]calldata_amount,bytes[]calldata_data)externalreturns(bool);/**
* @notice Transfers multiple bonds with amounts specified in the array `_amount` to the corresponding accounts in the array `_to` from an account that have been authorized by the `_from` account
* This method also allows to attach data to tokens that are being transferred
* @param _from array of bondholder accounts
* @param _to array of accounts to transfer bond tokens to
* @param _amount array of amounts of bond tokens to transfer.
* @param _data array of additional information provided by the token holder
*
** OPTIONAL - interfaces and other contracts MUST NOT expect this function to be present.
*/functionbatchTransferFrom(address[]calldata_from,address[]calldata_to,uint256[]calldata_amount,bytes[]calldata_data)externalreturns(bool);}
Additional bond parameters Interface
The IERC7092ESG interface is OPTIONAL for contracts implementing this proposal. This interface MAY be used to improve the standard usability.
The currencyOfCoupon The currency used for coupon payment may be different from the currency used to repay the principal
The couponType MAY be employed to signify the interest rate that the issuer has committed to paying to investors, which may take various forms such as zero coupon, fixed rate, floating rate, and more.
The couponFrequency refers to how often the bond pays interest to its bondholders, and is typically expressed in terms of time periods, such as: Annual, Semi-Annual, Quarterly, or Monthly.
The dayCountBasis is used to calculate the accrued interest on a bond between two coupon payment dates or other specific periods. Some of the day count basis are: Actual/Actual, 30/360, Actual/360, Actual/365, or 30/365
// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.0;interfaceIERC7092ESG/** is ERC165 */{/**
* @notice Returns the number of decimals used by the bond. For example, if it returns `10`, it means that the token amount MUST be multiplied by 10000000000 to get the standard representation.
*/functiondecimals()externalviewreturns(uint8);/**
* @notice Rreturns the coupon currency, which is represented by the contract address of the token used to pay coupons. It can be the same as the one used for the principal
*/functioncurrencyOfCoupon()externalviewreturns(address);/**
* @notice Returns the coupon type
* For example, 0 can denote Zero coupon, 1 can denote Fixed Rate, 2 can denote Floating Rate, and so on
*/functioncouponType()externalviewreturns(uint8);/**
* @notice Returns the coupon frequency, i.e. the number of times coupons are paid in a year.
*/functioncouponFrequency()externalviewreturns(uint256);/**
* @notice Returns the day count basis
* For example, 0 can denote actual/actual, 1 can denote actual/360, and so on
*/functiondayCountBasis()externalviewreturns(uint8);}
Cross-chain Interface
The standard permits the implementation of the IERC7092CrossChain interface for cross-chain management of bond tokens. This interface is OPTIONAL and may be used by applications to allow cross-chain transactions. Any function initiating a cross-chain transaction MUST explicitly define the destination chain identifier destinationChainID and specify the target smart contract destinationContract.
// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.0;interfaceIERC7092CrossChain/** is ERC165 */{// events
/**
* @notice MUST be emitted when bond tokens are transferred or redeemed in a cross-chain transaction
* @param _from bondholder account
* @param _to account the transfer bond tokens to
* @param _amount amount of bond tokens to be transferred
* @param _destinationChainID The unique ID that identifies the destination Chain
*/eventCrossChainTransfer(addressindexed_from,addressindexed_to,uint256_amount,bytes32_destinationChainID);/**
* @notice MUST be emitted when several bond tokens are transferred or redeemed in a cross-chain transaction
* @param _from array of bondholders accounts
* @param _to array of accounts that receive the bond
* @param _amount array of amount of bond tokens to be transferred
* @param _destinationChainID array of unique IDs that identify the destination Chain
*/eventCrossChainTransferBatch(address[]_from,address[]_to,uint256[]_amount,bytes32[]_destinationChainID);/**
* @notice MUST be emitted when an account is approved to spend the bondholder's tokens in a different chain than the current chain
* @param _owner the bondholder account
* @param _spender the account to be allowed to spend bonds
* @param _amount amount of bond tokens allowed by `_owner` to be spent by `_spender`
* @param _destinationChainID The unique ID that identifies the destination Chain
*/eventCrossChainApproval(addressindexed_owner,addressindexed_spender,uint256_amount,bytes32_destinationChainID);/**
* @notice MUST be emitted when multiple accounts in the array `_spender` are approved or when the allowances of multiple accounts in the array `_spender` are reduced on the destination chain which MUST be different than the current chain
* @param _owner bond token's owner
* @param _spender array of accounts to be allowed to spend bonds
* @param _amount array of amount of bond tokens allowed by _owner to be spent by _spender
* @param _destinationChainID array of unique IDs that identify the destination Chain
*/eventCrossChainApprovalBatch(addressindexed_owner,address[]_spender,uint256[]_amount,bytes32[]_destinationChainID);// functions
/**
* @notice Authorizes the `_spender` account to manage a specified `_amount`of the bondholder bond tokens on the destination Chain
* @param _spender account to be authorized by the bondholder
* @param _amount amount of bond tokens to approve
* @param _destinationChainID The unique ID that identifies the destination Chain.
* @param _destinationContract The smart contract to interact with in the destination Chain
*/functioncrossChainApprove(address_spender,uint256_amount,bytes32_destinationChainID,address_destinationContract)externalreturns(bool);/**
* @notice Authorizes multiple spender accounts in `_spender` to manage specified amounts in `_amount` of the bondholder tokens on the destination chain
* @param _spender array of accounts to be authorized by the bondholder
* @param _amount array of amounts of bond tokens to approve
* @param _destinationChainID array of unique IDs that identifies the destination Chain.
* @param _destinationContract array of smart contracts to interact with in the destination Chain in order to Deposit or Mint tokens that are transferred.
*/functioncrossChainBatchApprove(address[]calldata_spender,uint256[]calldata_amount,bytes32[]calldata_destinationChainID,address[]calldata_destinationContract)externalreturns(bool);/**
* @notice Decreases the allowance of `_spender` by a specified `_amount` on the destination Chain
* @param _spender the address to be authorized by the bondholder
* @param _amount amount of bond tokens to remove from allowance
* @param _destinationChainID The unique ID that identifies the destination Chain.
* @param _destinationContract The smart contract to interact with in the destination Chain in order to Deposit or Mint tokens that are transferred.
*/functioncrossChainDecreaseAllowance(address_spender,uint256_amount,bytes32_destinationChainID,address_destinationContract)externalreturns(bool);/**
* @notice Decreases the allowance of multiple spenders in `_spender` by corresponding amounts specified in the array `_amount` on the destination chain
* @param _spender array of accounts to be authorized by the bondholder
* @param _amount array of amounts of bond tokens to decrease the allowance from
* @param _destinationChainID array of unique IDs that identifies the destination Chain.
* @param _destinationContract array of smart contracts to interact with in the destination Chain in order to Deposit or Mint tokens that are transferred.
*/functioncrossChainBatchDecreaseAllowance(address[]calldata_spender,uint256[]calldata_amount,bytes32[]calldata_destinationChainID,address[]calldata_destinationContract)externalreturns(bool);/**
* @notice Moves `_amount` bond tokens to the address `_to` from the current chain to another chain (e.g., moving tokens from Ethereum to Polygon).
* This methods also allows to attach data to the token that is being transferred
* @param _to account to send bond tokens to
* @param _amount amount of bond tokens to transfer
* @param _data additional information provided by the bondholder
* @param _destinationChainID The unique ID that identifies the destination Chain.
* @param _destinationContract The smart contract to interact with in the destination Chain in order to Deposit or Mint bond tokens that are transferred.
*/functioncrossChainTransfer(address_to,uint256_amount,bytescalldata_data,bytes32_destinationChainID,address_destinationContract)externalreturns(bool);/**
* @notice Transfers multiple bond tokens with amounts specified in the array `_amount` to the corresponding accounts in the array `_to` from the current chain to another chain (e.g., moving tokens from Ethereum to Polygon).
* This methods also allows to attach data to the token that is being transferred
* @param _to array of accounts to send the bonds to
* @param _amount array of amounts of bond tokens to transfer
* @param _data array of additional information provided by the bondholder
* @param _destinationChainID array of unique IDs that identify the destination Chains.
* @param _destinationContract array of smart contracts to interact with in the destination Chains in order to Deposit or Mint bond tokens that are transferred.
*/functioncrossChainBatchTransfer(address[]calldata_to,uint256[]calldata_amount,bytes[]calldata_data,bytes32[]calldata_destinationChainID,address[]calldata_destinationContract)externalreturns(bool);/**
* @notice Transfers `_amount` bond tokens from the `_from`account to the `_to` account from the current chain to another chain. The caller must be approved by the `_from` address.
* This methods also allows to attach data to the token that is being transferred
* @param _from the bondholder address
* @param _to the account to transfer bonds to
* @param _amount amount of bond tokens to transfer
* @param _data additional information provided by the token holder
* @param _destinationChainID The unique ID that identifies the destination Chain.
* @param _destinationContract The smart contract to interact with in the destination Chain in order to Deposit or Mint tokens that are transferred.
*/functioncrossChainTransferFrom(address_from,address_to,uint256_amount,bytescalldata_data,bytes32_destinationChainID,address_destinationContract)externalreturns(bool);/**
* @notice Transfers several bond tokens with amounts specified in the array `_amount` from accounts in the array `_from` to accounts in the array `_to` from the current chain to another chain.
* The caller must be approved by the `_from` accounts to spend the corresponding amounts specified in the array `_amount`
* This methods also allows to attach data to the token that is being transferred
* @param _from array of bondholder addresses
* @param _to array of accounts to transfer bonds to
* @param _amount array of amounts of bond tokens to transfer
* @param _data array of additional information provided by the token holder
* @param _destinationChainID array of unique IDs that identifies the destination Chain.
* @param _destinationContract array of smart contracts to interact with in the destination Chain in order to Deposit or Mint tokens that are transferred.
*/functioncrossChainBatchTransferFrom(address[]calldata_from,address[]calldata_to,uint256[]calldata_amount,bytes[]calldata_data,bytes32[]calldata_destinationChainID,address[]calldata_destinationContract)externalreturns(bool);}
Rationale
The design of this ERC aims to simplify the migration to tokenized bonds by maintaining consistency with traditional bond standards. This approach allows fixed-income instruments to be represented as on-chain tokens, manageable through wallets, and utilized by applications like decentralized exchanges, while avoiding the complexities and inefficiencies associated with other standards. This ERC facilitates the creation of new bond tokens with characteristics akin to traditional bonds, enhancing accessibility, liquidity, and cost-efficiency in bond trading and management.
The use of traditional finance terminology, like issueVolume and principalOf, is aimed at maintaining consistency with traditional bond language, which eases the adaptation for traditional entities.
Total Supply and Account Balance
The totalSupply and balanceOf functions are not defined as they can be derived from issueVolume and principalOf, and denomination. However, these functions can be be added in any contract implementing this standard, ensuring the proper relationship between these values.
This ERC is not backwards compatible with existing standards like ERC-20 or ERC-1155 due to the absence of certain functions like totalSupply or balanceOf. A pure implementation of this standard is RECOMMENDED for issuing tokenized bonds, as any hybrid solution with other mentioned standards SHOULD fail.
Reference Implementation
The complete Reference Implementation can be found here.
Bonds with embedded options like callable, puttable, or convertible bonds can be created by inheriting from the reference ERC7092.sol that integrates the proposed interface.
CALLABLE BONDS:
pragmasolidity^0.8.0;import'ERC7092.sol';contractERC7092CallableisERC7092{// WRITE THE LOGIC TO ALLOW THE ISSUER TO CALL BONDS
// STATE VARIABLES AND FUNCTIONS NEEDED
/**
* @notice call bonds owned by `_investor`
* MUST be called by the issuer only
*/functioncall(address_investor)public{require(msg.sender==_issuer[bondISIN].issuerAddress,"ERC7092Callable: ONLY_ISSUER");require(_principals[_investor]>0,"ERC7092Callable: NO_BONDS");require(block.timestamp<_bond[bondISIN].maturityDate,"ERC7092Callable: BOND_MATURED");uint256principal=_principals[_investor];_principals[_investor]=0;// ADD LOGIC HERE
}}
PUTTABLE BONDS:
pragmasolidity^0.8.0;import'ERC7092.sol';contractERC7092PuttableisERC7092{// WRITE THE LOGIC TO ALLOW INVESTORS TO PUT BONDS
// STATE VARIABLES AND FUNCTIONS NEEDED
/**
* @notice put bonds
* MUST be called by investors who own bonds
*/functionput()public{require(_principals[msg.sender]>0,"ERC7092Puttable: ONLY_INVESTORS");require(block.timestamp<_bond[bondISIN].maturityDate,"ERC7092Puttable: BOND_MATURED");uint256principal=_principals[msg.sender];_principals[msg.sender]=0;// ADD LOGIC
}}
CONVERTIBLE BONDS:
pragmasolidity^0.8.0;import'ERC7092.sol';contractERC7092ConvertibleisERC7092{// WRITE THE LOGIC TO ALLOW INVESTOR OR ISSUER TO CONVERT BONDS TO EQUITY
// STATE VARIABLES AND FUNCTIONS NEEDED
/**
* @notice convert bonds to equity. Here we assumed that the investors must convert their bonds to equity
* Issuer can also convert invetsors bonds to equity.
*/functionconvert()public{require(_principals[msg.sender]>0,"ERC7092Convertible: ONLY_INVESTORS");require(block.timestamp<_bond[bondISIN].maturityDate,"ERC7092Convertible: BOND_MATURED");uint256principal=_principals[msg.sender];_principals[msg.sender]=0;// ADD LOGIC HERE
}}
Identity Registry
This standard is designed specifically for tokenizing bonds. It does not inherently manage information pertaining to bondholders’ identities. However, to enhance compliance with regulatory requirements and improve transparency, an identity registry can be added on top of this standard to store the identity of all authorized investors.
By maintaining an identity registry, issuers can ensure that bond tokens issued under the ERC7092 standard are transferred only to registered and authorized entities. This practice aligns with regulatory compliance measures and provides a structured way to manage and verify the identity of bondholders. It also helps prevent unauthorized or non-compliant transfers of bond tokens.
Security Considerations
Implementing this ERC requires careful consideration of security risks related to functions approving operators to manage owner’s bonds and functions allowing bond transfers. The use of these functions necessitates robust validation to ensure only the bond owner or approved accounts can call them.