diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index e1b2dc12a..6e442514d 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -16,7 +16,9 @@ ** xref:erc20.adoc[ERC20] *** xref:/guides/erc20-supply.adoc[Creating Supply] *** xref:/api/erc20.adoc[API Reference] -// ** xref:erc721.adoc[ERC721] + +** xref:erc721.adoc[ERC721] +*** xref:/api/erc721.adoc[API Reference] // ** xref:erc1155.adoc[ERC1155] * xref:security.adoc[Security] diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc new file mode 100644 index 000000000..29192dad8 --- /dev/null +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -0,0 +1,599 @@ +:github-icon: pass:[] +:eip721: https://eips.ethereum.org/EIPS/eip-721[EIP721] +:receiving-tokens: xref:/erc721.adoc#receiving_tokens[Receiving Tokens] +:casing-discussion: https://github.com/OpenZeppelin/cairo-contracts/discussions/34[here] +:inner-src5: xref:api/introspection.adoc#ISRC5[SRC5 ID] + += ERC721 + +Reference of interfaces, presets, and utilities related to ERC721 contracts. + +TIP: For an overview of ERC721, read our xref:erc721.adoc[ERC721 guide]. + +== Core + +[.contract] +[[IERC721]] +=== `++IERC721++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/token/erc721/interface.cairo#L13-L31[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```javascript +use openzeppelin::token::erc721::interface::IERC721; +``` +Interface of the IERC721 standard as defined in {eip721}. + +[.contract-index] +.{inner-src5} +-- +0x33eb2f84c309543403fd69f0d0f363781ef06ef6faeb0131ff16ea3175bd943 +-- + +[.contract-index] +.Functions +-- +* xref:#IERC721-balance_of[`++balance_of(account)++`] +* xref:#IERC721-owner_of[`++owner_of(token_id)++`] +* xref:#IERC721-safe_transfer_from[`++safe_transfer_from(from, to, token_id, data)++`] +* xref:#IERC721-transfer_from[`++transfer_from(from, to, token_id)++`] +* xref:#IERC721-approve[`++approve(to, token_id)++`] +* xref:#IERC721-set_approval_for_all[`++set_approval_for_all(operator, approved)++`] +* xref:#IERC721-get_approved[`++get_approved(token_id)++`] +* xref:#IERC721-is_approved_for_all[`++is_approved_for_all(owner, operator)++`] +-- + +[.contract-index] +.Events +-- +* xref:#IERC721-Approval[`++Approval(owner, approved, token_id)++`] +* xref:#IERC721-ApprovalForAll[`++ApprovalForAll(owner, operator, approved)++`] +* xref:#IERC721-Transfer[`++Transfer(from, to, token_id)++`] +-- + +==== Functions + +[.contract-item] +[[IERC721-balance_of]] +==== `[.contract-item-name]#++balance_of++#++(account: ContractAddress) → u256++` [.item-kind]#external# + +Returns the number of NFTs owned by `account`. + +[.contract-item] +[[IERC721-owner_of]] +==== `[.contract-item-name]#++owner_of++#++(token_id: u256) → ContractAddress++` [.item-kind]#external# + +Returns the owner address of `token_id`. + +[.contract-item] +[[IERC721-safe_transfer_from]] +==== `[.contract-item-name]#++safe_transfer_from++#++(from: ContractAddress, to: ContractAddress, token_id: u256, data: Span)++` [.item-kind]#external# + +Transfer ownership of `token_id` from `from` to `to`, checking first that `to` is aware of the ERC721 protocol to prevent tokens being locked forever. +For information regarding how contracts communicate their awareness of the ERC721 protocol, see {receiving-tokens}. + +Emits a <> event. + +[.contract-item] +[[IERC721-transfer_from]] +==== `[.contract-item-name]#++transfer_from++#++(from: ContractAddress, to: ContractAddress, token_id: u256)++` [.item-kind]#external# + +Transfer ownership of `token_id` from `from` to `to`. + +Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 transfers or else they may be permanently lost. +Usage of <> prevents loss, though the caller must understand this adds an external call which potentially creates a reentrancy vulnerability. + +Emits a <> event. + +[.contract-item] +[[IERC721-approve]] +==== `[.contract-item-name]#++approve++#++(to: ContractAddress, token_id: u256)++` [.item-kind]#external# + +Change or reaffirm the approved address for an NFT. + +Emits an <> event. + +[.contract-item] +[[IERC721-set_approval_for_all]] +==== `[.contract-item-name]#++set_approval_for_all++#++(operator: ContractAddress, approved: bool)++` [.item-kind]#external# + +Enable or disable approval for `operator` to manage all of the caller's assets. + +Emits an <> event. + +[.contract-item] +[[IERC721-get_approved]] +==== `[.contract-item-name]#++get_approved++#++(token_id: u256) -> u256++` [.item-kind]#external# + +Returns the address approved for `token_id`. + +[.contract-item] +[[IERC721-is_approved_for_all]] +==== `[.contract-item-name]#++is_approved_for_all++#++(owner: ContractAddress, operator: ContractAddress) -> bool++` [.item-kind]#external# + +Query if `operator` is an authorized operator for `owner`. + +==== Events + +[.contract-item] +[[IERC721-Approval]] +==== `[.contract-item-name]#++Approval++#++(owner: ContractAddress, approved: ContractAddress, token_id: u256)++` [.item-kind]#event# + +Emitted when `owner` enables `approved` to manage the `token_id` token. + +[.contract-item] +[[IERC721-ApprovalForAll]] +==== `[.contract-item-name]#++ApprovalForAll++#++(owner: ContractAddress, operator: ContractAddress, approved: bool)++` [.item-kind]#event# + +Emitted when `owner` enables `approved` to manage the `token_id` token. + +[.contract-item] +[[IERC721-Transfer]] +==== `[.contract-item-name]#++Transfer++#++(from: ContractAddress, to: ContractAddress, token_id: u256)++` [.item-kind]#event# + +Emitted when `token_id` token is transferred from `from` to `to`. + +[.contract] +[[IERC721Metadata]] +=== `++IERC721Metadata++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/token/erc721/interface.cairo#L54-L59[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```javascript +use openzeppelin::token::erc721::interface::IERC721Metadata; +``` + +Interface for the optional metadata functions in {eip721}. + +[.contract-index] +.{inner-src5} +-- +0x6069a70848f907fa57668ba1875164eb4dcee693952468581406d131081bbd +-- + +[.contract-index] +.Functions +-- +* xref:#IERC721Metadata-name[`++name()++`] +* xref:#IERC721Metadata-symbol[`++symbol()++`] +* xref:#IERC721Metadata-token_uri[`++token_uri(token_id)++`] +-- + +==== Functions + +[.contract-item] +[[IERC721Metadata-name]] +==== `[.contract-item-name]#++name++#++() -> felt252++` [.item-kind]#external# + +Returns the NFT name. + +[.contract-item] +[[IERC721Metadata-symbol]] +==== `[.contract-item-name]#++symbol++#++() -> felt252++` [.item-kind]#external# + +Returns the NFT ticker symbol. + +[.contract-item] +[[IERC721Metadata-token_uri]] +==== `[.contract-item-name]#++token_uri++#++(token_id: u256) -> felt252++` [.item-kind]#external# + +Returns the Uniform Resource Identifier (URI) as a short string for the `token_id` token. +If the URI is not set for `token_id`, the return value will be `0`. + +[.contract] +[[ERC721Component]] +=== `++ERC721Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/token/erc721/erc721.cairo#L7[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```javascript +use openzeppelin::token::erc721::ERC721Component; +``` + +ERC721 component implementing <> and <>. + +NOTE: Implementing xref:api/introspection.adoc#SRC5Component[SRC5Component] is a requirement for this component to be implemented. + +[.contract-index] +.External functions +-- +.IERC721Impl +* xref:#IERC721-balance_of[`++balance_of(self, account)++`] +* xref:#IERC721-owner_of[`++owner_of(self, token_id)++`] +* xref:#IERC721-safe_transfer_from[`++safe_transfer_from(self, from, to, token_id, data)++`] +* xref:#IERC721-transfer_from[`++transfer_from(self, from, to, token_id)++`] +* xref:#IERC721-approve[`++approve(self, to, token_id)++`] +* xref:#IERC721-set_approval_for_all[`++set_approval_for_all(self, operator, approved)++`] +* xref:#IERC721-get_approved[`++get_approved(self, token_id)++`] +* xref:#IERC721-is_approved_for_all[`++is_approved_for_all(self, owner, operator)++`] + +.IERC721MetadataImpl +* xref:#IERC721Metadata-name[`++name(self)++`] +* xref:#IERC721Metadata-symbol[`++symbol(self)++`] +* xref:#IERC721Metadata-token_uri[`++token_uri(self, token_id)++`] +-- + +[.contract-index] +.camelCase support +-- +.ER721CamelOnlyImpl +* xref:#ERC721-balanceOf[`++balanceOf(self, account)++`] +* xref:#ERC721-ownerOf[`++ownerOf(self, tokenId)++`] +* xref:#ERC721-safeTransferFrom[`++safeTransferFrom(self, from, to, tokenId, data)++`] +* xref:#ERC721-transferFrom[`++transferFrom(self, from, to, tokenId)++`] +* xref:#ERC721-setApprovalForAll[`++setApprovalForAll(self, operator, approved)++`] +* xref:#ERC721-getApproved[`++getApproved(self, tokenId)++`] +* xref:#ERC721-isApprovedForAll[`++isApprovedForAll(self, owner, operator)++`] + +.ERC721MetadataCamelOnlyImpl +* xref:#ERC721-tokenURI[`++tokenURI(self, tokenId)++`] +-- + +[.contract-index] +.Internal Functions +-- +.InternalImpl +* xref:#ERC721-initializer[`++initializer(self, name_, symbol_)++`] +* xref:#ERC721-_owner_of[`++_owner_of(self, token_id)++`] +* xref:#ERC721-_exists[`++_exists(self, token_id)++`] +* xref:#ERC721-_is_approved_or_owner[`++_is_approved_or_owner(self, spender, token_id)++`] +* xref:#ERC721-_approve[`++_approve(self, to, token_id)++`] +* xref:#ERC721-_set_approval_for_all[`++_set_approval_for_all(self, owner, operator, approved)++`] +* xref:#ERC721-_mint[`++_mint(self, to, token_id)++`] +* xref:#ERC721-_transfer[`++_transfer(self, from, to, token_id)++`] +* xref:#ERC721-_burn[`++_burn(self, token_id)++`] +* xref:#ERC721-_safe_mint[`++_safe_mint(self, to, token_id, data)++`] +* xref:#ERC721-_safe_transfer[`++_safe_transfer(self, from, to, token_id, data)++`] +* xref:#ERC721-_set_token_uri[`++_set_token_uri(self, token_id, token_uri)++`] +-- + +[.contract-index] +.Events +-- +.IERC721 +* xref:#IERC721-Approval[`++Approval(owner, approved, token_id)++`] +* xref:#IERC721-ApprovalForAll[`++ApprovalForAll(owner, operator, approved)++`] +* xref:#IERC721-Transfer[`++Transfer(from, to, token_id)++`] +-- + +==== Embeddable functions + +[.contract-item] +[[ERC721-balance_of]] +==== `[.contract-item-name]#++balance_of++#++(self: @ContractState, account: ContractAddress) → u256++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-owner_of]] +==== `[.contract-item-name]#++owner_of++#++(self: @ContractState, token_id: u256) → ContractAddress++` [.item-kind]#external# + +See <>. + +Requirements: + +- `token_id` exists. + +[.contract-item] +[[ERC721-safe_transfer_from]] +==== `[.contract-item-name]#++safe_transfer_from++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, data: Span)++` [.item-kind]#external# + +See <>. + +Requirements: + +- Caller is either approved or the `token_id` owner. +- `to` is not the zero address. +- `from` is not the zero address. +- `token_id` exists. +- `to` is either an account contract or supports the <> interface. + +[.contract-item] +[[ERC721-transfer_from]] +==== `[.contract-item-name]#++transfer_from++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256)++` [.item-kind]#external# + +See <>. + +Requirements: + +- Caller either approved or the `token_id` owner. +- `to` is not the zero address. +- `from` is not the zero address. +- `token_id` exists. + +[.contract-item] +[[ERC721-approve]] +==== `[.contract-item-name]#++approve++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#external# + +See <>. + +Requirements: + +- The caller is either an approved operator or the `token_id` owner. +- `to` cannot be the token owner or the zero address. +- `token_id` exists. + +[.contract-item] +[[ERC721-set_approval_for_all]] +==== `[.contract-item-name]#++set_approval_for_all++#++(ref self: ContractState, operator: ContractAddress, approved: bool)++` [.item-kind]#external# + +See <>. + +Requirements: + +- `operator` cannot be the caller. + +[.contract-item] +[[ERC721-get_approved]] +==== `[.contract-item-name]#++get_approved++#++(self: @ContractState, token_id: u256) -> u256++` [.item-kind]#external# + +See <>. + +Requirements: + +- `token_id` exists. + +[.contract-item] +[[ERC721-is_approved_for_all]] +==== `[.contract-item-name]#++is_approved_for_all++#++(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-name]] +==== `[.contract-item-name]#++name++#++(self: @ContractState) -> felt252++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-symbol]] +==== `[.contract-item-name]#++symbol++#++(self: @ContractState) -> felt252++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-token_uri]] +==== `[.contract-item-name]#++token_uri++#++(self: @ContractState, token_id: u256) -> felt252++` [.item-kind]#external# + +See <>. + +==== camelCase Support + +[.contract-item] +[[ERC721-balanceOf]] +==== `[.contract-item-name]#++balanceOf++#++(self: @ContractState, account: ContractAddress) -> u256++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-ownerOf]] +==== `[.contract-item-name]#++ownerOf++#++(self: @ContractState, tokenId: u256) -> ContractAddress++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-safeTransferFrom]] +==== `[.contract-item-name]#++safeTransferFrom++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256, data: Span)++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-transferFrom]] +==== `[.contract-item-name]#++transferFrom++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256)++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-setApprovalForAll]] +==== `[.contract-item-name]#++setApprovalForAll++#++(ref self: ContractState, operator: ContractAddress, approved: bool)++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-getApproved]] +==== `[.contract-item-name]#++getApproved++#++(self: @ContractState, tokenId: u256) -> ContractAddress++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-isApprovedForAll]] +==== `[.contract-item-name]#++isApprovedForAll++#++(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721-tokenURI]] +==== `[.contract-item-name]#++tokenURI++#++(self: @ContractState, tokenId: u256) -> felt252++` [.item-kind]#external# + +See <>. + +==== Internal functions + +[.contract-item] +[[ERC721-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, name_: felt252, symbol_: felt252)++` [.item-kind]#internal# + +Initializes the contract by setting the token name and symbol. +This should be used inside the contract's constructor. + +[.contract-item] +[[ERC721-_owner_of]] +==== `[.contract-item-name]#++_owner_of++#++(self: @ContractState, token_id: felt252) -> ContractAddress++` [.item-kind]#internal# + +Internal function that returns the owner address of `token_id`. +This function will panic if the token does not exist. + +[.contract-item] +[[ERC721-_exists]] +==== `[.contract-item-name]#++_exists++#++(self: @ContractState, token_id: u256) -> bool++` [.item-kind]#internal# + +Internal function that returns whether `token_id` exists. + +Tokens start existing when they are minted (<>), and stop existing when they are burned (<>). + +[.contract-item] +[[ERC721-_is_approved_or_owner]] +==== `[.contract-item-name]#++_is_approved_or_owner++#++(ref self: ContractState, spender: ContractAddress, token_id: u256) -> bool++` [.item-kind]#internal# + +Internal function that returns whether `spender` is allowed to manage `token_id`. + +Requirements: + +- `token_id` exists. + +[.contract-item] +[[ERC721-_approve]] +==== `[.contract-item-name]#++_approve++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# + +Internal function that changes or reaffirms the approved address for an NFT. + +Emits an <> event. + +Requirements: + +- `token_id` exists. +- `to` is not the current token owner. + +[.contract-item] +[[ERC721-_set_approval_for_all]] +==== `[.contract-item-name]#++_set_approval_for_all++#++(ref self: ContractState, owner: ContractAddress, operator: ContractAddress, approved: bool)++` [.item-kind]#internal# + +Internal function that enables or disables approval for `operator` to manage all of the +`owner` assets. + +Emits an <> event. + +Requirements: + +- `operator` cannot be the caller. + +[.contract-item] +[[ERC721-_mint]] +==== `[.contract-item-name]#++_mint++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# + +Internal function that mints `token_id` and transfers it to `to`. + +Emits an <> event. + +Requirements: + +- `to` is not the zero address. +- `token_id` does not already exist. + +[.contract-item] +[[ERC721-_transfer]] +==== `[.contract-item-name]#++_transfer++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# + +Internal function that transfers `token_id` from `from` to `to`. + +Emits an <> event. + +Requirements: + +- `to` is not the zero address. +- `from` is the token owner. +- `token_id` exists. + +[.contract-item] +[[ERC721-_burn]] +==== `[.contract-item-name]#++_burn++#++(ref self: ContractState, token_id: u256)++` [.item-kind]#internal# + +Internal function that destroys `token_id`. +The approval is cleared when the token is burned. +This internal function does not check if the sender is authorized to operate on the token. + +Emits an <> event. + +Requirements: + +`token_id` exists. + +[.contract-item] +[[ERC721-_safe_mint]] +==== `[.contract-item-name]#++_safe_mint++#++(ref self: ContractState, to: ContractAddress, token_id: u256, data: Span)++` [.item-kind]#internal# + +Internal function that mints `token_id` and transfers it to `to`. +If `to` is not an account contract, `to` must support <>; otherwise, the transaction will fail. + +Emits an <> event. + +Requirements: + +- `token_id` does not already exist. +- `to` is either an account contract or supports the <> interface. + +[.contract-item] +[[ERC721-_safe_transfer]] +==== `[.contract-item-name]#++_safe_transfer++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, data: Span)++` [.item-kind]#internal# + +Internal function that transfers `token_id` token from `from` to `to`, checking first that contract recipients are aware of the ERC721 protocol to prevent tokens from being forever locked. + +`data` is additional data, it has no specified format and it is sent in call to `to`. + +This internal function does not include permissions but can be useful for instances like implementing alternative mechanisms to perform signature-based token transfers. + +Emits an <> event. + +Requirements: + +- `to` cannot be the zero address. +- `from` must be the token owner. +- `token_id` exists. +- `to` either is an account contract or supports the <> interface. + +[.contract-item] +[[ERC721-_set_token_uri]] +==== `[.contract-item-name]#++_set_token_uri++#++(ref self: ContractState, token_id: u256, token_uri: felt252)++` [.item-kind]#internal# + +Internal function that sets the `token_uri` of `token_id`. + +Requirements: + +- `token_id` exists. + +==== Events + +[.contract-item] +[[IERC721-Approval]] +==== `[.contract-item-name]#++Approval++#++(owner: ContractAddress, approved: ContractAddress, token_id: u256)++` [.item-kind]#event# + +See <>. + +[.contract-item] +[[IERC721-ApprovalForAll]] +==== `[.contract-item-name]#++ApprovalForAll++#++(owner: ContractAddress, operator: ContractAddress, approved: bool)++` [.item-kind]#event# + +See <>. + +[.contract-item] +[[IERC721-Transfer]] +==== `[.contract-item-name]#++Transfer++#++(from: ContractAddress, to: ContractAddress, token_id: u256)++` [.item-kind]#event# + +See <>. + +[.contract] +[[IERC721Receiver]] +=== `++IERC721Receiver++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/token/erc721/interface.cairo#L70-L79[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```javascript +use openzeppelin::token::erc721::interface::IERC721Receiver; +``` + +Interface for contracts that support receiving `safe_transfer_from` transfers. + +[.contract-index] +.{inner-src5} +-- +0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc +-- + +[.contract-index] +.Functions +-- +* xref:#IERC721Receiver-on_erc721_received[`++on_erc721_received(operator, from, token_id, data)++`] +-- + +==== Functions + +[.contract-item] +[[IERC721Receiver-on_erc721_received]] +==== `[.contract-item-name]#++on_erc721_received++#++(operator: ContractAddress, from: ContractAddress, token_id: u256, data Span) -> felt252++` [.item-kind]#external# + +Whenever an IERC721 `token_id` token is transferred to this non-account contract via <> by `operator` from `from`, this function is called. diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc index d5847ffbb..7f324c18b 100644 --- a/docs/modules/ROOT/pages/erc20.adoc +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -90,6 +90,8 @@ mod MyToken { impl ERC20Impl = ERC20Component::ERC20Impl; #[abi(embed_v0)] impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; impl ERC20InternalImpl = ERC20Component::InternalImpl; #[storage] @@ -179,6 +181,8 @@ mod MyToken { #[abi(embed_v0)] impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; impl ERC20InternalImpl = ERC20Component::InternalImpl; #[storage] diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc index 53d06d313..484feeea9 100644 --- a/docs/modules/ROOT/pages/erc721.adoc +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -1,772 +1,277 @@ = ERC721 -The ERC721 token standard is a specification for https://docs.openzeppelin.com/contracts/4.x/tokens#different-kinds-of-tokens[non-fungible tokens], or more colloquially: NFTs. -The `ERC721.cairo` contract implements an approximation of https://eips.ethereum.org/EIPS/eip-721[EIP-721] in Cairo for StarkNet. - -== Table of Contents - -* <> -* <> -* <> - ** <> - ** <> - ** <> - *** <> - ** <> - ** <> -* <> -* <> - ** <> - ** <> - ** <> - *** <> - ** <> - *** <> -* <> - ** <> -* <> - ** <> - *** <> - *** <> - *** <> - *** <> - *** <> - *** <> - *** <> - *** <> - ** <> - *** <> - *** <> - *** <> - ** <> - *** <> - *** <> - *** <> - ** <> - *** <> - *** <> - *** <> - ** <> - *** <> - -== IERC721 - -[,cairo] ----- -@contract_interface -namespace IERC721 { - func balanceOf(owner: felt) -> (balance: Uint256) { - } - - func ownerOf(tokenId: Uint256) -> (owner: felt) { - } - - func safeTransferFrom(from_: felt, to: felt, tokenId: Uint256, data_len: felt, data: felt*) { - } - - func transferFrom(from_: felt, to: felt, tokenId: Uint256) { - } - - func approve(approved: felt, tokenId: Uint256) { - } - - func setApprovalForAll(operator: felt, approved: felt) { - } - - func getApproved(tokenId: Uint256) -> (approved: felt) { - } - - func isApprovedForAll(owner: felt, operator: felt) -> (approved: felt) { - } - - --------------- IERC165 --------------- - - func supportsInterface(interfaceId: felt) -> (success: felt) { - } +:token-types: https://docs.openzeppelin.com/contracts/5.x/tokens#different-kinds-of-tokens[non-fungible tokens] +:eip721: https://eips.ethereum.org/EIPS/eip-721[EIP-721] + +The ERC721 token standard is a specification for {token-types}, or more colloquially: NFTs. +`token::erc721::ERC721Component` provides an approximation of {eip721} in Cairo for Starknet. + +== Interface + +:compatibility: xref:/erc721.adoc#erc721_compatibility[ERC721 Compatibility] +:ierc721-interface: xref:/erc721.adoc#ierc721[IERC721] +:ierc721metadata-interface: xref:/erc721.adoc#ierc721metadata[IERC721Metadata] +:dual-interfaces: xref:interfaces.adoc#dual_interfaces[Dual interfaces] + +The following interface represents the full ABI of the Contracts for Cairo `ERC721Component`. +The interface includes the <> standard interface and the optional <> interface. +To support older token deployments, as mentioned in {dual-interfaces}, the component also includes implementations of the interface written in camelCase. + +[,javascript] +---- +trait IERC721ABI { + // IERC721 + fn balance_of(account: ContractAddress) -> u256; + fn owner_of(token_id: u256) -> ContractAddress; + fn safe_transfer_from( + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ); + fn transfer_from(from: ContractAddress, to: ContractAddress, token_id: u256); + fn approve(to: ContractAddress, token_id: u256); + fn set_approval_for_all(operator: ContractAddress, approved: bool); + fn get_approved(token_id: u256) -> ContractAddress; + fn is_approved_for_all(owner: ContractAddress, operator: ContractAddress) -> bool; + + // IERC721Metadata + fn name() -> felt252; + fn symbol() -> felt252; + fn token_uri(token_id: u256) -> felt252; + + // IERC721CamelOnly + fn balanceOf(account: ContractAddress) -> u256; + fn ownerOf(tokenId: u256) -> ContractAddress; + fn safeTransferFrom( + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ); + fn transferFrom(from: ContractAddress, to: ContractAddress, tokenId: u256); + fn setApprovalForAll(operator: ContractAddress, approved: bool); + fn getApproved(tokenId: u256) -> ContractAddress; + fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; + + // IERC721MetadataCamelOnly + fn tokenURI(tokenId: u256) -> felt252; } ---- -=== ERC721 Compatibility - -Although StarkNet is not EVM compatible, this implementation aims to be as close as possible to the ERC721 standard in the following ways: +=== ERC721 compatibility -* It uses Cairo's `uint256` instead of `felt`. -* It returns `TRUE` as success. -* It makes use of Cairo's short strings to simulate `name` and `symbol`. +:erc165-storage: https://docs.openzeppelin.com/contracts/4.x/api/utils#ERC165Storage[ERC165Storage] +:src5-api: xref:introspection.adoc#src5[SRC5] +:eip165: https://eips.ethereum.org/EIPS/eip-165[EIP165] -But some differences can still be found, such as: +Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC721 standard by utilizing Cairo's short strings to simulate `name` and `symbol`. +This implementation does, however, include a few notable differences such as: -* `tokenURI` returns a felt representation of the queried token's URI. +* `token_uri` returns a felt252 representation of the queried token's URI. The EIP721 standard, however, states that the return value should be of type string. If a token's URI is not set, the returned value is `0`. -Note that URIs cannot exceed 31 characters. -See <>. +Note that URIs cannot exceed 31 characters at this time. +See <>. * ``interface_id``s are hardcoded and initialized by the constructor. -The hardcoded values derive from Solidity's selector calculations. -See <>. -* `safeTransferFrom` can only be expressed as a single function in Cairo as opposed to the two functions declared in EIP721. +The hardcoded values derive from Starknet's selector calculations. +See the {introspection} docs. +* `safe_transfer_from` can only be expressed as a single function in Cairo as opposed to the two functions declared in EIP721, because function overloading is currently not possible in Cairo. The difference between both functions consists of accepting `data` as an argument. -Because function overloading is currently not possible in Cairo, `safeTransferFrom` by default accepts the `data` argument. -If `data` is not used, simply insert `0`. -* `safeTransferFrom` is specified such that the optional `data` argument should be of type bytes. +`safe_transfer_from` by default accepts the `data` argument. +If `data` is not used, simply pass an empty array. +* `safe_transfer_from` is implemented such that the optional `data` argument mimics `bytes`. In Solidity, this means a dynamically-sized array. To be as close as possible to the standard, it accepts a dynamic array of felts. -In Cairo, arrays are expressed with the array length preceding the actual array; -hence, the method accepts `data_len` and `data` respectively as types `felt` and `felt*`. -* `ERC165.register_interface` allows contracts to set and communicate which interfaces they support. -This follows OpenZeppelin's https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v0.6.1/contracts/utils/introspection/ERC165Storage.sol[ERC165Storage]. -* `IERC721Receiver` compliant contracts (`ERC721Holder`) return a hardcoded selector id according to EVM selectors, since selectors are calculated differently in Cairo. -This is in line with the ERC165 interfaces design choice towards EVM compatibility. -See the xref:introspection.adoc[Introspection docs] for more info. -* `IERC721Receiver` compliant contracts (`ERC721Holder`) must support ERC165 by registering the `IERC721Receiver` selector id in its constructor and exposing the `supportsInterface` method. -In doing so, recipient contracts (both accounts and non-accounts) can be verified that they support ERC721 transfers. -* `ERC721Enumerable` tracks the total number of tokens with the `all_tokens` and `all_tokens_len` storage variables mimicking the array of the Solidity implementation. +* ERC721 utilizes {src5-api} to declare and query interface support on Starknet as opposed to Ethereum's {eip165}. +The design for `SRC5` is similar to OpenZeppelin's {erc165-storage}. +* `IERC721Receiver` compliant contracts return a hardcoded interface ID according to Starknet selectors (as opposed to selector calculation in Solidity). == Usage -Use cases go from artwork, digital collectibles, physical property, and many more. - -To show a standard use case, we'll use the `ERC721Mintable` preset which allows for only the owner to `mint` and `burn` tokens. -To create a token you need to first deploy both Account and ERC721 contracts respectively. -As most StarkNet contracts, ERC721 expects to be called by another contract and it identifies it through `get_caller_address` (analogous to Solidity's `this.address`). -This is why we need an Account contract to interact with it. - -Considering that the ERC721 constructor method looks like this: - -[,cairo] ----- -func constructor( - name: felt, // Token name as Cairo short string - symbol: felt, // Token symbol as Cairo short string - owner: felt // Address designated as the contract owner -) { -} ----- - -Deployment of both contracts looks like this: - -[,python] ----- -account = await starknet.deploy( - "contracts/Account.cairo", - constructor_calldata=[signer.public_key] -) - -erc721 = await starknet.deploy( - "contracts/token/erc721/presets/ERC721Mintable.cairo", - constructor_calldata=[ - str_to_felt("Token"), # name - str_to_felt("TKN"), # symbol - account.contract_address # owner - ] -) ----- - -To mint a non-fungible token, send a transaction like this: - -[,python] ----- -signer = MockSigner(PRIVATE_KEY) -tokenId = uint(1) - -await signer.send_transaction( - account, erc721.contract_address, 'mint', [ - recipient_address, - *tokenId - ] -) ----- - -=== Token Transfers - -This library includes `transferFrom` and `safeTransferFrom` to transfer NFTs. -If using `transferFrom`, *the caller is responsible to confirm that the recipient is capable of receiving NFTs or else they may be permanently lost.* - -The `safeTransferFrom` method incorporates the following conditional logic: - -. if the calling address is an account contract, the token transfer will behave as if `transferFrom` was called -. if the calling address is not an account contract, the safe function will check that the contract supports ERC721 tokens - -The current implementation of `safeTansferFrom` checks for `onERC721Received` and requires that the recipient contract supports ERC165 and exposes the `supportsInterface` method. -See <>. - -=== Interpreting ERC721 URIs - -Token URIs in Cairo are stored as single field elements. -Each field element equates to 252-bits (or 31.5 bytes) which means that a token's URI can be no longer than 31 characters. - -NOTE: Storing the URI as an array of felts was considered to accommodate larger strings. -While this approach is more flexible regarding URIs, a returned array further deviates from the standard set in https://eips.ethereum.org/EIPS/eip-721[EIP721]. -Therefore, this library's ERC721 implementation sets URIs as a single field element. - -The `utils.py` module includes utility methods for converting to/from Cairo field elements. -To properly interpret a URI from ERC721, simply trim the null bytes and decode the remaining bits as an ASCII string. -For example: - -[,python] ----- -# HELPER METHODS -def str_to_felt(text): - b_text = bytes(text, 'ascii') - return int.from_bytes(b_text, "big") - -def felt_to_str(felt): - b_felt = felt.to_bytes(31, "big") - return b_felt.decode() - -token_id = uint(1) -sample_uri = str_to_felt('mock://mytoken') - -await signer.send_transaction( - account, erc721.contract_address, 'setTokenURI', [ - *token_id, sample_uri] -) - -felt_uri = await erc721.tokenURI(first_token_id).call() -string_uri = felt_to_str(felt_uri) ----- - -=== ERC721Received - -In order to be sure a contract can safely accept ERC721 tokens, said contract must implement the `ERC721_Receiver` interface (as expressed in the EIP721 specification). -Methods such as `safeTransferFrom` and `safeMint` call the recipient contract's `onERC721Received` method. -If the contract fails to return the correct magic value, the transaction fails. - -StarkNet contracts that support safe transfers, however, must also support xref:introspection.adoc#erc165[ERC165] and include `supportsInterface` as proposed in https://github.com/OpenZeppelin/cairo-contracts/discussions/100[#100]. -`safeTransferFrom` requires a means of differentiating between account and non-account contracts. -Currently, StarkNet does not support error handling from the contract level; -therefore, the current ERC721 implementation requires that all contracts that support safe ERC721 transfers (both accounts and non-accounts) include the `supportsInterface` method. -Further, `supportsInterface` should return `TRUE` if the recipient contract supports the `IERC721Receiver` magic value `0x150b7a02` (which invokes `onERC721Received`). -If the recipient contract supports the `IAccount` magic value `0x50b70dcb`, `supportsInterface` should return `TRUE`. -Otherwise, `safeTransferFrom` should fail. - -==== IERC721Receiver - -Interface for any contract that wants to support safeTransfers from ERC721 asset contracts. - -[,cairo] ----- -@contract_interface -namespace IERC721Receiver { - func onERC721Received( - operator: felt, from_: felt, tokenId: Uint256, data_len: felt data: felt*) -> (selector: felt) { +:mint-api: xref:api/erc721.adoc#ERC721-_mint[_mint] + +Using Contracts for Cairo, constructing an ERC721 contract requires integrating both `ERC721Component` and `SRC5Component`. +The contract should also set up the constructor to initialize the token's name, symbol, and interface support. +Here's an example of a basic contract: + +[,javascript] +---- +#[starknet::contract] +mod MyNFT { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::ERC721Component; + use starknet::ContractAddress; + + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC721 + #[abi(embed_v0)] + impl ERC721Impl = ERC721Component::ERC721Impl; + #[abi(embed_v0)] + impl ERC721MetadataImpl = ERC721Component::ERC721MetadataImpl; + #[abi(embed_v0)] + impl ERC721CamelOnly = ERC721Component::ERC721CamelOnlyImpl; + #[abi(embed_v0)] + impl ERC721MetadataCamelOnly = + ERC721Component::ERC721MetadataCamelOnlyImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage } -} ----- - -=== Supporting Interfaces - -In order to ensure EVM/StarkNet compatibility, this ERC721 implementation does not calculate interface identifiers. -Instead, the interface IDs are hardcoded from their EVM calculations. -On the EVM, the interface ID is calculated from the selector's first four bytes of the hash of the function's signature while Cairo selectors are 252 bytes long. -Due to this difference, hardcoding EVM's already-calculated interface IDs is the most consistent approach to both follow the EIP165 standard and EVM compatibility. - -Further, this implementation stores supported interfaces in a mapping (similar to OpenZeppelin's https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v0.6.1/contracts/utils/introspection/ERC165Storage.sol[ERC165Storage]). - -=== Ready-to-Use Presets - -ERC721 presets have been created to allow for quick deployments as-is. -To be as explicit as possible, each preset includes the additional features they offer in the contract name. -For example: - -* `ERC721MintableBurnable` includes `mint` and `burn`. -* `ERC721MintablePausable` includes `mint`, `pause`, and `unpause`. -* `ERC721EnumerableMintableBurnable` includes `mint`, `burn`, and <> methods. - -Ready-to-use presets are a great option for testing and prototyping. -See <>. - -== Extensibility - -Following the xref:extensibility.adoc[contracts extensibility pattern], this implementation is set up to include all ERC721 related storage and business logic under a namespace. -Developers should be mindful of manually exposing the required methods from the namespace to comply with the standard interface. -This is already done in the <>; -however, additional functionality can be added. -For instance, you could: - -* Implement a pausing mechanism. -* Add roles such as _owner_ or _minter_. -* Modify the `transferFrom` function to mimic the https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L335[`_beforeTokenTransfer` and `_afterTokenTransfer` hooks]. -Just be sure that the exposed `external` methods invoke their imported function logic a la `approve` invokes `ERC721.approve`. -As an example, see below. - -[,python] ----- -from openzeppelin.token.erc721.library import ERC721 - -@external -func approve{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}( - to: felt, tokenId: Uint256 -) { - ERC721.approve(to, tokenId) - return() -} ----- - -== Presets - -The following contract presets are ready to deploy and can be used as-is for quick prototyping and testing. -Each preset includes a contract owner, which is set in the `constructor`, to offer simple access control on sensitive methods such as `mint` and `burn`. - -=== ERC721MintableBurnable - -The `ERC721MintableBurnable` preset offers a quick and easy setup for creating NFTs. -The contract owner can create tokens with `mint`, whereas token owners can destroy their tokens with `burn`. - -=== ERC721MintablePausable - -The `ERC721MintablePausable` preset creates a contract with pausable token transfers and minting capabilities. -This preset proves useful for scenarios such as preventing trades until the end of an evaluation period and having an emergency switch for freezing all token transfers in the event of a large bug. -In this preset, only the contract owner can `mint`, `pause`, and `unpause`. - -=== ERC721EnumerableMintableBurnable - -The `ERC721EnumerableMintableBurnable` preset adds enumerability of all the token ids in the contract as well as all token ids owned by each account. -This allows contracts to publish its full list of NFTs and make them discoverable. - -In regard to implementation, contracts should expose the following view methods: - -* `ERC721Enumerable.total_supply` -* `ERC721Enumerable.token_by_index` -* `ERC721Enumerable.token_of_owner_by_index` - -In order for the tokens to be correctly indexed, the contract should also use the following methods (which supersede some of the base `ERC721` methods): - -* `ERC721Enumerable.transfer_from` -* `ERC721Enumerable.safe_transfer_from` -* `ERC721Enumerable._mint` -* `ERC721Enumerable._burn` - -==== IERC721Enumerable - -[,cairo] ----- -@contract_interface -namespace IERC721Enumerable { - func totalSupply() -> (totalSupply: Uint256) { + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event } - func tokenByIndex(index: Uint256) -> (tokenId: Uint256) { + #[constructor] + fn constructor( + ref self: ContractState, + recipient: ContractAddress + ) { + let name = 'MyNFT'; + let symbol = 'NFT'; + let token_id = 1; + let token_uri = 'NFT_URI'; + + self.erc721.initializer(name, symbol); + self._mint_with_uri(recipient, token_id, token_uri); } - func tokenOfOwnerByIndex(owner: felt, index: Uint256) -> (tokenId: Uint256) { + #[generate_trait] + impl InternalImpl of InternalTrait { + fn _mint_with_uri( + ref self: ContractState, + recipient: ContractAddress, + token_id: u256, + token_uri: felt252 + ) { + // Initialize the ERC721 storage + self.erc721._mint(recipient, token_id); + // Mint the NFT to recipient and set the token's URI + self.erc721._set_token_uri(token_id, token_uri); + } } } ---- -=== ERC721Metadata - -The `ERC721Metadata` extension allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent. - -We follow OpenZeppelin's Solidity approach of integrating the Metadata methods `name`, `symbol`, and `tokenURI` into all ERC721 implementations. -If preferred, a contract can be created that does not import the Metadata methods from the `ERC721` library. -Note that the `IERC721Metadata` interface id should be removed from the constructor as well. - -==== IERC721Metadata - -[,cairo] ----- -@contract_interface -namespace IERC721Metadata { - func name() -> (name: felt) { - } - - func symbol() -> (symbol: felt) { - } - - func tokenURI(tokenId: Uint256) -> (tokenURI: felt) { - } -} ----- - -== Utilities - -=== ERC721Holder - -Implementation of the `IERC721Receiver` interface. - -Accepts all token transfers. -Make sure the contract is able to use its token with `IERC721.safeTransferFrom`, `IERC721.approve` or `IERC721.setApprovalForAll`. - -Also utilizes the ERC165 method `supportsInterface` to determine if the contract is an account. -See <> - -== API Specification - -=== IERC721 API - -[,cairo] ----- -func balanceOf(owner: felt) -> (balance: Uint256) { -} - -func ownerOf(tokenId: Uint256) -> (owner: felt) { -} - -func safeTransferFrom(from_: felt, to: felt, tokenId: Uint256, data_len: felt, data: felt*) { -} - -func transferFrom(from_: felt, to: felt, tokenId: Uint256) { -} - -func approve(approved: felt, tokenId: Uint256) { -} - -func setApprovalForAll(operator: felt, approved: felt) { -} - -func getApproved(tokenId: Uint256) -> (approved: felt) { -} - -func isApprovedForAll(owner: felt, operator: felt) -> (approved: felt) { -} ----- - -==== `balanceOf` - -Returns the number of tokens in ``owner``'s account. - -Parameters: - -[,cairo] ----- -owner: felt ----- - -Returns: - -[,cairo] ----- -balance: Uint256 ----- - -==== `ownerOf` - -Returns the owner of the `tokenId` token. - -Parameters: - -[,cairo] ----- -tokenId: Uint256 ----- - -Returns: - -[,cairo] ----- -owner: felt ----- - -==== `safeTransferFrom` - -Safely transfers `tokenId` token from `from_` to `to`, checking first that contract recipients are aware of the ERC721 protocol to prevent tokens from being forever locked. -For information regarding how contracts communicate their awareness of the ERC721 protocol, see <>. - -Emits a <> event. - -Parameters: - -[,cairo] ----- -from_: felt -to: felt -tokenId: Uint256 -data_len: felt -data: felt* ----- - -Returns: None. - -==== `transferFrom` - -Transfers `tokenId` token from `from_` to `to`. -*The caller is responsible to confirm that `to` is capable of receiving NFTs or else they may be permanently lost*. - -Emits a <> event. - -Parameters: - -[,cairo] ----- -from_: felt -to: felt -tokenId: Uint256 ----- - -Returns: None. - -==== `approve` - -Gives permission to `to` to transfer `tokenId` token to another account. -The approval is cleared when the token is transferred. - -Emits an <> event. - -Parameters: - -[,cairo] ----- -to: felt -tokenId: Uint256 ----- - -Returns: None. - -==== `getApproved` - -Returns the account approved for `tokenId` token. - -Parameters: - -[,cairo] ----- -tokenId: Uint256 ----- - -Returns: - -[,cairo] ----- -operator: felt ----- - -==== `setApprovalForAll` - -Approve or remove `operator` as an operator for the caller. -Operators can call `transferFrom` or `safeTransferFrom` for any token owned by the caller. - -Emits an <> event. - -Parameters: - -[,cairo] ----- -operator: felt ----- - -Returns: None. - -==== `isApprovedForAll` - -Returns if the `operator` is allowed to manage all of the assets of `owner`. - -Parameters: - -[,cairo] ----- -owner: felt -operator: felt ----- - -Returns: - -[,cairo] ----- -approved: felt ----- - -=== Events - -==== `Approval (Event)` - -Emitted when `owner` enables `approved` to manage the `tokenId` token. - -Parameters: - -[,cairo] ----- -owner: felt -approved: felt -tokenId: Uint256 ----- - -==== `ApprovalForAll (Event)` - -Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. - -Parameters: - -[,cairo] ----- -owner: felt -operator: felt -approved: felt ----- - -==== `Transfer (Event)` - -Emitted when `tokenId` token is transferred from `from_` to `to`. - -Parameters: - -[,cairo] ----- -from_: felt -to: felt -tokenId: Uint256 ----- - -''' - -=== IERC721Metadata API - -[,cairo] ----- -func name() -> (name: felt) { -} - -func symbol() -> (symbol: felt) { -} - -func tokenURI(tokenId: Uint256) -> (tokenURI: felt) { -} ----- - -==== `name` - -Returns the token collection name. - -Parameters: None. +=== Token transfers -Returns: +:transfer_from-api: xref:api/erc721.adoc#IERC721-transfer_from[transfer_from] +:safe_transfer_from-api: xref:api/erc721.adoc#IERC721-safe_transfer_from[safe_transfer_from] -[,cairo] ----- -name: felt ----- +This library includes {transfer_from-api} and {safe_transfer_from-api} to transfer NFTs. +If using `transfer_from`, *the caller is responsible to confirm that the recipient is capable of receiving NFTs or else they may be permanently lost.* +The `safe_transfer_from` method mitigates this risk by querying the recipient contract's interface support. -==== `symbol` +WARNING: Usage of `safe_transfer_from` prevents loss, though the caller must understand this adds an external call which potentially creates a reentrancy vulnerability. -Returns the token collection symbol. +=== Receiving tokens -Parameters: None. - -Returns: - -[,cairo] ----- -symbol: felt ----- +:src5: xref:introspection.adoc#src5[SRC5] +:on_erc721_received-api: xref:api/erc721.adoc#IERC721Receiver-on_erc721_received[on_erc721_received] +:computing-interface-id: xref:introspection.adoc#computing_the_interface_id[Computing the interface ID] +:safe_transfer_from-api: xref:api/erc721.adoc#IERC721-safe_transfer_from[safe_transfer_from] +:safe_mint-api: xref:api/erc721.adoc#ERC721-_safe_mint[_safe_mint] -==== `tokenURI` +In order to be sure a non-account contract can safely accept ERC721 tokens, said contract must implement the `IERC721Receiver` interface. +The recipient contract must also implement the {src5} interface which, as described earlier, supports interface introspection. -Returns the Uniform Resource Identifier (URI) for `tokenID` token. -If the URI is not set for the `tokenId`, the return value will be `0`. - -Parameters: - -[,cairo] ----- -tokenId: Uint256 ----- - -Returns: - -[,cairo] ----- -tokenURI: felt ----- - -''' +==== IERC721Receiver -=== IERC721Enumerable API +:receiver-id: xref:/api/erc721.adoc#IERC721Receiver[IERC721Receiver interface ID] -[,cairo] +[,javascript] ---- -func totalSupply() -> (totalSupply: Uint256) { -} - -func tokenByIndex(index: Uint256) -> (tokenId: Uint256) { -} - -func tokenOfOwnerByIndex(owner: felt, index: Uint256) -> (tokenId: Uint256) { +trait IERC721Receiver { + fn on_erc721_received( + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span + ) -> felt252; } ---- -==== `totalSupply` - -Returns the total amount of tokens stored by the contract. +Implementing the `IERC721Receiver` interface exposes the {on_erc721_received-api} method. +When safe methods such as {safe_transfer_from-api} and {safe_mint-api} are called, they invoke the recipient contract's `on_erc721_received` method which *must* return the {receiver-id}. +Otherwise, the transaction will fail. -Parameters: None +TIP: For information on how to calculate interface IDs, see {computing-interface-id}. -Returns: +==== Creating a token receiver contract -[,cairo] ----- -totalSupply: Uint256 ----- +The Contracts for Cairo `IERC721ReceiverImpl` already returns the correct interface ID for safe token transfers. +To integrate the `IERC721Receiver` interface into a contract, simply include the ABI embed directive to the implementation and add the `initializer` in the contract's constructor . +Here's an example of a simple token receiver contract: -==== `tokenByIndex` - -Returns a token ID owned by `owner` at a given `index` of its token list. -Use along with <> to enumerate all of ``owner``'s tokens. - -Parameters: - -[,cairo] ----- -index: Uint256 ----- - -Returns: - -[,cairo] ----- -tokenId: Uint256 +[,javascript] ---- +#[starknet::contract] +mod MyTokenReceiver { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::ERC721ReceiverComponent; + use starknet::ContractAddress; -==== `tokenOfOwnerByIndex` + component!(path: ERC721ReceiverComponent, storage: erc721_receiver, event: ERC721ReceiverEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); -Returns a token ID at a given `index` of all the tokens stored by the contract. -Use along with <> to enumerate all tokens. + // ERC721Receiver + #[abi(embed_v0)] + impl ERC721ReceiverImpl = ERC721ReceiverComponent::ERC721ReceiverImpl; + #[abi(embed_v0)] + impl ERC721ReceiverCamelImpl = ERC721ReceiverComponent::ERC721ReceiverCamelImpl; + impl ERC721ReceiverInternalImpl = ERC721ReceiverComponent::InternalImpl; -Parameters: + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; -[,cairo] ----- -owner: felt -index: Uint256 ----- - -Returns: - -[,cairo] ----- -tokenId: Uint256 ----- - -''' + #[storage] + struct Storage { + #[substorage(v0)] + erc721_receiver: ERC721ReceiverComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } -=== IERC721Receiver API + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721ReceiverEvent: ERC721ReceiverComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } -[,cairo] ----- -func onERC721Received( - operator: felt, from_: felt, tokenId: Uint256, data_len: felt data: felt* -) -> (selector: felt) { + #[constructor] + fn constructor(ref self: ContractState) { + self.erc721_receiver.initializer(); + } } ---- -==== `onERC721Received` - -Whenever an IERC721 `tokenId` token is transferred to this non-account contract via `safeTransferFrom` by `operator` from `from_`, this function is called. - -Parameters: +=== Storing ERC721 URIs -[,cairo] ----- -operator: felt -from_: felt -tokenId: Uint256 -data_len: felt -data: felt* ----- +:string-roadmap: https://github.com/orgs/starkware-libs/projects/1/views/1?pane=issue&itemId=28823165[here] -Returns: +Token URIs in Cairo are stored as single field elements (`felt252`). +Each field element equates to 252-bits (or 31.5 bytes) which means that a token's URI can be no longer than 31 characters. -[,cairo] ----- -selector: felt ----- +NOTE: Native string support in Cairo is currently in progress and tracked {string-roadmap}. +Once Cairo offers full string support, this will be revisited. diff --git a/docs/modules/ROOT/pages/guides/erc20-supply.adoc b/docs/modules/ROOT/pages/guides/erc20-supply.adoc index a51a2a0d2..46882c8c1 100644 --- a/docs/modules/ROOT/pages/guides/erc20-supply.adoc +++ b/docs/modules/ROOT/pages/guides/erc20-supply.adoc @@ -24,6 +24,8 @@ mod MyToken { impl ERC20Impl = ERC20Component::ERC20Impl; #[abi(embed_v0)] impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; impl InternalImpl = ERC20Component::InternalImpl; #[storage] @@ -79,6 +81,8 @@ mod MyToken { impl ERC20Impl = ERC20Component::ERC20Impl; #[abi(embed_v0)] impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; impl InternalImpl = ERC20Component::InternalImpl; #[storage]