.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/contracts/tokens/plot_02_erc721_safe_transfers.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code or to run this example in your browser via JupyterLite. .. rst-class:: sphx-glr-example-title .. _sphx_glr_api_gallery_contracts_tokens_plot_02_erc721_safe_transfers.py: ERC-721 non-fungible tokens and safe transfers (2018) ===================================================== ERC-721, by William Entriken, Dieter Shirley, Jacob Evans and Nastassia Sachs, gave each token an identity: a mapping from token ids to owners, .. math:: \mathrm{owner}: \mathrm{id} \mapsto \mathrm{address}, \qquad \mathrm{balance}(a) = |\{\mathrm{id} : \mathrm{owner}(\mathrm{id}) = a\}|, so that collectibles, game items and deeds could be owned and traded like coins. CryptoKitties had shown the demand in late 2017. A token sent to a contract that has no code to move it is locked forever. ``safeTransferFrom`` therefore calls ``onERC721Received`` on a contract recipient and reverts unless the recipient answers with the agreed value. Here a plain ``transfer_from`` to a token contract strands an NFT, while ``safe_transfer_from`` refuses the same transfer and delivers to a vault that knows how to hold and send tokens. .. GENERATED FROM PYTHON SOURCE LINES 25-29 .. code-block:: Python import matplotlib.pyplot as plt import blockchainkit as bk .. GENERATED FROM PYTHON SOURCE LINES 30-32 Mint, then send to three kinds of recipient ------------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 32-75 .. code-block:: Python world = bk.contracts.World() nft = world.deploy("artist", bk.contracts.ERC721, name="kitties") for token_id in range(1, 5): world.transact("artist", nft, "mint", "alice", token_id) stranger = world.deploy("someone", bk.contracts.ERC20, 1, name="a token contract") vault = world.deploy("alice", bk.contracts.NFTVault, name="vault") attempts = { "transfer_from\nto token contract": ("transfer_from", stranger, 1), "safe_transfer_from\nto token contract": ("safe_transfer_from", stranger, 2), "safe_transfer_from\nto vault": ("safe_transfer_from", vault, 3), "safe_transfer_from\nto a person": ("safe_transfer_from", "bob", 4), } results = {} for label, (function, to, token_id) in attempts.items(): receipt = world.transact("alice", nft, function, "alice", to, token_id) owner = world.name(world.view(nft, "owner_of", token_id)) results[label] = (receipt.success, owner) print(f"{label.replace(chr(10), ' '):40s} success={receipt.success} owner={owner}") assert results["transfer_from\nto token contract"] == (True, "a token contract") assert results["safe_transfer_from\nto token contract"] == (False, "alice") assert world.transact("alice", vault, "send_token", nft, "carol", 3).success assert world.view(nft, "owner_of", 3) == "carol" # The vault can move what it holds. fig, ax = plt.subplots(figsize=(9, 3.8)) colors = {"a token contract": "#dc2626", "alice": "#64748b", "vault": "#16a34a", "bob": "#16a34a"} for row, (success, owner) in enumerate(results.values()): ax.barh(row, 1, color=colors[owner]) verdict = ( "delivered" if success and owner != "a token contract" else ("stuck forever" if success else "refused: stays with alice") ) ax.text(0.03, row, f"{verdict} (owner: {owner})", va="center", color="white") ax.set_yticks(range(len(results)), list(results)) ax.set(xticks=[], title="Where token ids 1-4 ended up") ax.invert_yaxis() fig.tight_layout() plt.show() .. image-sg:: /api/gallery/contracts/tokens/images/sphx_glr_plot_02_erc721_safe_transfers_001.png :alt: Where token ids 1-4 ended up :srcset: /api/gallery/contracts/tokens/images/sphx_glr_plot_02_erc721_safe_transfers_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none transfer_from to token contract success=True owner=a token contract safe_transfer_from to token contract success=False owner=alice safe_transfer_from to vault success=True owner=vault safe_transfer_from to a person success=True owner=bob .. GENERATED FROM PYTHON SOURCE LINES 76-83 Exercise -------- ``safe_transfer_from`` calls the recipient *after* updating ownership. The recipient's hook can call back into the NFT contract. Write a receiver whose ``on_erc721_received`` tries to transfer the token onward at once: does it work, and why is a callback in the middle of a transfer the same risk as the DAO's reentrancy? .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.038 seconds) .. _sphx_glr_download_api_gallery_contracts_tokens_plot_02_erc721_safe_transfers.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: lite-badge .. image:: images/jupyterlite_badge_logo.svg :target: ../../../../lite/lab/index.html?path=api/gallery/contracts/tokens/plot_02_erc721_safe_transfers.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_02_erc721_safe_transfers.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_02_erc721_safe_transfers.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_02_erc721_safe_transfers.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_