<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>FileBunnies</title>
        <link>https://paragraph.com/@filebunnies</link>
        <description>undefined</description>
        <lastBuildDate>Thu, 04 Jun 2026 11:23:08 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>FileBunnies</title>
            <url>https://storage.googleapis.com/papyrus_images/02feb902d03dbd65fa15feec180791ba6deb7c849d5bee62ae84462bae5e6a0f.png</url>
            <link>https://paragraph.com/@filebunnies</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[FileMarket Mainnet Alpha Test - FileMarket.xyz - Medium]]></title>
            <link>https://paragraph.com/@filebunnies/filemarket-mainnet-alpha-test-filemarket-xyz-medium</link>
            <guid>F99qQffLfCb9uFyQdaTu</guid>
            <pubDate>Wed, 03 May 2023 00:33:27 GMT</pubDate>
            <description><![CDATA[FileMarket Mainnet Alpha Test is now live! We are proud to share fantastic results of the previous phase of the Alpha Testnet campaign. During this stage, users will need to mint EFT on FVM Mainnet. Recently, there has been a big announcement in the Filecoin Ecosystem. FWS (Filecoin Web Services) is a set of compute and storage technologies built on top of the Filecoin Network. The components provided by Filecoin Web Services are designed to be scalable, flexible, and secure, making the offer...]]></description>
            <content:encoded><![CDATA[<figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f82116f6b46954fcb7221f4e8c0ec857a6879024388b9baae421e98aa4b2d713.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>FileMarket Mainnet Alpha Test</strong> <strong>is now live!</strong></p><p>We are proud to share <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/filemarket_xyz/status/1635720420635574303?s=46&amp;t=9Ipg9XxVavYsKinHryX6LA%20(https://twitter.com/filemarket_xyz/status/1635720420635574303?s=46&amp;t=9Ipg9XxVavYsKinHryX6LA)">fantastic results</a> of the previous phase of the Alpha Testnet campaign. During this stage, users will need to mint EFT on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://fvm.filecoin.io/">FVM</a> Mainnet.</p><p>Recently, there has been a big announcement in the Filecoin Ecosystem. FWS (Filecoin Web Services) is a set of compute and storage technologies built on top of the Filecoin Network. The components provided by Filecoin Web Services are designed to be scalable, flexible, and secure, making the offering suitable for a wide range of use cases.</p><blockquote><p><em>The Engineering team within the Filecoin ecosystem has reached a significant milestone in the Filecoin Web Services project. This platform leverages the potential of Filecoin, the world’s largest decentralized storage network.</em></p></blockquote><p>This announcement represents a crucial step towards offering a practical cloud alternative for developers around the world. If you are an individual or an organization with meaningful public data sets you may participate in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://storage.market/free-storage">data onboarding program</a> with hosted free storage.</p><p>Our event aims to support the FWS launch, allowing everyone to experience the simplicity of uploading data to Filecoin’s hosted decentralized storage in the form of <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/filemarket-xyz/how-to-attach-an-encrypted-file-to-your-nft-7d6232fd6d34">EFT</a>. This event will run from <strong><em>Tuesday May 4</em></strong>, to <strong>Friday June 4</strong>, 22:00 (GMT + 3).</p><p><strong>Gas expenses will be covered by FileMarket Foundation</strong>. Please fill out <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://filemarket.typeform.com/to/TAYF2jLW">this form</a> <strong>before the 15th of May</strong> 12:00 (GMT + 3) to get <strong>FIL airdropped and complete all tasks bellow.</strong> The limit for FIL gas airdrop is 7000 addresses. If you would like to be a part of the journey of FileMarket, here is a <strong>checklist</strong> of things to do:</p><ol><li><p>Get <strong>FilFam</strong> and <strong>FILearner</strong> role in Discord by completing tasks at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://guild.xyz/filemarketxyz#!">guild.xyz</a></p></li><li><p>Get <strong>File’n’thropist</strong> role in Discord by gaining <strong>lvl3</strong> in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://zealy.io/c/filemarketxyz/questboard">Zealy.io</a></p></li><li><p>Fill <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://filemarket.typeform.com/to/TAYF2jLW">this form</a> <strong>before 15th May</strong> to get FIL from the faucet</p></li><li><p>Connect your wallet to Filecoin Mainnet</p></li><li><p>Mint new collection</p></li><li><p>Mint your own NFT with hidden content, using Encrypted File Token Technology on Filecoin Storage. Upload any file that will be encrypted and hidden by EFT©.</p></li><li><p>List your EFT.</p></li></ol><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/02feb902d03dbd65fa15feec180791ba6deb7c849d5bee62ae84462bae5e6a0f.png" alt="FileMarket Bunny" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">FileMarket Bunny</figcaption></figure><p>At FileMarket, we are committed to providing the best EFT experience to Filecoin community. This event is also designed to battle test our smart contracts before the launch of “<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/filemarket-xyz/filebunnies-this-is-the-way-140c492e2a3d">Filebunnies</a>” — the First EFT collection on Filecoin.</p><blockquote><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/filemarket-xyz/filebunnies-this-is-the-way-140c492e2a3d"><strong><em>Filebunnies</em></strong></a> collection can be your <strong>key access point</strong> of exploring Filecoin Ecosystem and Filecoin Virtual Machine. <strong>Owning the FileBunny will later give Airdrop of FileMarket token.</strong> Snapshots of active addresses are taken monthly. Please see the rarity table to know more details about the future distribution:</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/525979f690185b71920dc0a2e263d47a42f70f3c90fbcd74ff35fd1ce09c9857.png" alt="Rarity table" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Rarity table</figcaption></figure><p>All users completed this FileMarket Mainnet Alpha Test (File’n’thropist, FILearner, or FilFam Discord role + FIL gas form completed + Mainnet activity) will be eligible for the the <strong>freemint</strong> of Filebunnies EFTs and bonuses from partners of this cross-promo event.</p><p>WAGMI, always!</p>]]></content:encoded>
            <author>filebunnies@newsletter.paragraph.com (FileBunnies)</author>
        </item>
        <item>
            <title><![CDATA[How to attach an encrypted file to your NFT | FileMarket.xyz]]></title>
            <link>https://paragraph.com/@filebunnies/how-to-attach-an-encrypted-file-to-your-nft-filemarket-xyz</link>
            <guid>CCjsMhZ52IITfrMa9RKm</guid>
            <pubDate>Tue, 02 May 2023 23:58:19 GMT</pubDate>
            <description><![CDATA[EFT Protocol definitionWhile starting FileMarket— a multifaceted platform that serves as a NFT2.0 storefront builder, cutting-edge marketplace, and a protocol for tokenizing, storing, and swapping files. At one point, we thought that it would be nice to have some private content linked to NFT to solve Ctrl+c — Ctrl+v problem. We’ve done some research and realized that there are no such technologies to have private content and the ability to transfer it while remaining hidden from the world. A...]]></description>
            <content:encoded><![CDATA[<h2 id="h-eft-protocol-definition" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">EFT Protocol definition</h2><p>While starting <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://filemarket.xyz/">FileMarket</a>— a multifaceted platform that serves as a NFT2.0 storefront builder, cutting-edge marketplace, and a protocol for tokenizing, storing, and swapping files. At one point, we thought that it would be nice to have some private content linked to NFT to solve Ctrl+c — Ctrl+v problem. We’ve done some research and realized that there are no such technologies to have private content and the ability to transfer it while remaining hidden from the world. After that, we decided to design and implement a protocol for private NFT using encryption.</p><p>This article will describe our path to the solution and include many technical details, so one reading this article should be familiar with basic encryption concepts like <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Symmetric-key_algorithm">symmetric</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Public-key_cryptography">asymmetric</a> encryption, NFT standards like <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-721">ERC721</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-1155">ERC1155</a>, and the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.soliditylang.org/en/v0.8.16/">Solidity</a> language because there will be a lot of code!</p><p>The entire code of the FileMarket project is open source, it can be found in our <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Filemarket-xyz">Github organization</a>. A <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://filemarket.xyz/">demo version</a> of our platform is also already available.</p><h2 id="h-basic-concept" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Basic concept</h2><p>Our main goal was to make the protocol completely decentralized, which leads us to the file immutability requirement because with re-encryption file contents can be changed after minting. So, we decided to encrypt the file with any symmetric encryption and store the result. Thus, the transfer process will consist of symmetric encryption key transfer.</p><p>Of course, we can’t transfer the key in a direct way, so it also must be encrypted. We thought that asymmetric encryption suits us the best as the receiver will have his private key hidden and the sender will be able to encrypt the symmetric encryption key with the only public key of the receiver.</p><p>So, our first basic concept looks like this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fa9b7bfe99a2f285de369fd3fe1385433ce9229b99c84e31337d0e4930e97725.png" alt="General protocol scheme" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">General protocol scheme</figcaption></figure><h2 id="h-fraud-verifying" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Fraud verifying</h2><p>The case of confirmation of the transfer by the receiver does not require additional attention, but the case of rejection implies that something is wrong with the file. There are a few possible options:</p><ol><li><p>The encrypted file key is not valid (for example, an empty string or decrypted data is not a valid key).</p></li><li><p>The encrypted key is valid, but the result of the decryption is not the same file that was originally encrypted. To verify this fact, the file is concatenated with its hash during encryption.</p></li></ol><p>If none of these conditions are met, but the receiver declined the transfer, then this is fraud on the part of the receiver because he gained access to the encrypted file but did not accept the transfer, and this transfer could be part of some kind of transaction like the sale of NFTs.</p><p>Hence, the protocol should provide for the resolution of disputes in case of rejection of the transfer. That is, some trusted third party is needed to carry out the verification in this case. Schematically it looks like this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3cda87aad611f8b9396acfef5628b9b449deda3f2ebc8f07ba07077ae3b92fc0.png" alt="Protocol scheme with defined 3rd-party" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Protocol scheme with defined 3rd-party</figcaption></figure><h2 id="h-decentralized-fraud-verifying" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Decentralized fraud verifying</h2><p>Our next problem is to make the trusted third-party decentralized. To do this, the implementation technology must have the following properties:</p><ol><li><p>Have access to the data storage;</p></li><li><p>Be able to perform encryption and decryption operations;</p></li><li><p>With its help, the implementation of NFT must be possible.</p></li></ol><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://filecoin.io/">Filecoin</a> has all these properties:</p><ol><li><p>From <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://fvm.filecoin.io/">FVM</a> actors there is access to the Filecoin data storage;</p></li><li><p>The implementation language of <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://fvm.filecoin.io/">FVM</a> actors — <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.rust-lang.org/">Rust</a> makes it convenient to use encryption protocols. Using Solidity to implement encryption is difficult and inefficient — such operations will have a high cost in gas and a long execution time due to a large number of operations with the memory (you can read more about this issue in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/better-programming/issues-of-returning-arrays-of-dynamic-size-in-solidity-smart-contracts-dd1e54424235">one of my previous articles</a>);</p></li><li><p>FEVM allows us to use Solidity implementations of NFT standards like ERC721 or ERC1155, while all encryption work will be done with <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://fvm.filecoin.io/">FVM</a> actors.</p></li></ol><p>With <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://filecoin.io/">Filecoin</a>, our protocol looks like this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b383e2ab1f0054ee37a9e18b55ff62c87cf70a5dfc489dfa5fb050f86dbc7a4c.png" alt="Protocol scheme" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Protocol scheme</figcaption></figure><h2 id="h-development-process" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Development process</h2><p>Initially, we decided to divide the development into two parts:</p><ol><li><p>FVM actors for working with files and encryption;</p></li><li><p>FEVM Solidity NFT smart contracts.</p></li></ol><p>We entered the FVM Early Builders program and participated in the FEVM hackathon (unfortunately, we did not make it to the final). At the time of the start of development (August 2022), FVM was not yet fully completed, so we decided that we should implement Solidity smart contracts that we will deploy in Polygon and a Rust server that would listen to the blockchain and wait for fraud reports, after which it would conduct decryption attempt. This Rust server will be able to call the smart contract methods responsible for resolving disputes. The choice of the Rust language here is not accidental, it is needed in order to make it easier for us to convert this server into a FVM actor in the future.</p><p>So, the Polygon version with the Rust server looks like this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6ab8179450c56f2087f5dd421aa74379cc957e6511ee8c83991b1e0f30dcbb04.png" alt="Protocol version with Polygon and Rust server as judge" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Protocol version with Polygon and Rust server as judge</figcaption></figure><p>And we decided to pack Solidity smart contracts into a standard and define them as interfaces. We named this standard <strong>Encrypted File Token</strong> or <strong>EFT</strong>. Next, we will consider the standard in more detail.</p><h2 id="h-encrypted-file-token-standard" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Encrypted File Token Standard</h2><p>So, the main requirements for our standard:</p><ol><li><p>It must implement the token transfer pipeline.</p></li><li><p>It must be possible to use an FVM actor for fraud verification when the result of the check is obtained in a single call and use a plain EVM smart contract (managed by a Rust server) as a source of dispute resolution.</p></li><li><p>It must be possible to trigger some events when a transfer is canceled or completed. This is necessary to create other high-level applications that interact with the EFT standard, since passing an EFT cannot be an atomic operation.</p></li></ol><p>To fulfill these requirements, we define 3 interfaces:</p><ol><li><p><code>IEncryptedFileToken</code> — main standard for Encrypted NFT.</p></li><li><p><code>IFraudDecider</code> — interface for fraud decider contract. It’s required to make this interface for the possibility of using FVM-actor and EVM contract (for Polygon version of our system), which will be called from the Rust server.</p></li><li><p><code>IEncryptedFileTokenCallbackReceiver</code> — interface for contracts to implement for the ability to make some actions on transfer cancellation of finish.</p></li></ol><p>Let’s start with <code>IEncryptedFileTokenCallbackReceiver</code> as it is the simplest standard.</p><pre data-type="codeBlock" text="
pragma solidity ^0.8.0;

/// @dev Interface for third party to receive transfer updates from token contract instances
interface IEncryptedFileTokenCallbackReceiver {
    /// @dev This function MUST be called if transfer is cancelled
    /// @param tokenId Id of token for which transfer was cancelled
    function transferCancelled(uint256 tokenId) external;

    /// @dev This function MUST be called if transfer is finished successfully
    /// @param tokenId Id of token for which transfer was finished
    function transferFinished(uint256 tokenId) external;

    /// @dev This function MUST be called if transfer is finished with fraud report
    /// @param tokenId Id of token for which transfer was finished
    /// @param approved indicates if there was really fact of the fraud
    function transferFraudDetected(uint256 tokenId, bool approved) external;
}
"><code>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-comment">/// @dev Interface for third party to receive transfer updates from token contract instances</span>
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IEncryptedFileTokenCallbackReceiver</span> </span>{
    <span class="hljs-comment">/// @dev This function MUST be called if transfer is cancelled</span>
    <span class="hljs-comment">/// @param tokenId Id of token for which transfer was cancelled</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferCancelled</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">/// @dev This function MUST be called if transfer is finished successfully</span>
    <span class="hljs-comment">/// @param tokenId Id of token for which transfer was finished</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferFinished</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">/// @dev This function MUST be called if transfer is finished with fraud report</span>
    <span class="hljs-comment">/// @param tokenId Id of token for which transfer was finished</span>
    <span class="hljs-comment">/// @param approved indicates if there was really fact of the fraud</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferFraudDetected</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">bool</span> approved</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;
}
</code></pre><p>The <code>IFraudDecider</code> interface is defined so that EFT instances can interact with both the FVM actor and to interact with the Polygon smart contract managed by our backend. To do this, the fraud check function returns two boolean values ​​ — the first determines whether a decision was made, and the second takes the value true if there is indeed a fraud act.</p><pre data-type="codeBlock" text="pragma solidity ^0.8.0;

interface IFraudDecider {
    /// @dev Decide if there was a fact of fraud
    function decide(
        uint256 tokenId,
        string calldata cid,
        bytes calldata publicKey,
        bytes calldata privateKey,
        bytes calldata encryptedPassword
    ) external returns (bool, bool);
}
"><code><span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IFraudDecider</span> </span>{
    <span class="hljs-comment">/// @dev Decide if there was a fact of fraud</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">decide</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId,
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">calldata</span> cid,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> publicKey,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> privateKey,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> encryptedPassword
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span>, <span class="hljs-keyword">bool</span></span>)</span>;
}
</code></pre><p>And finally, the token standard itself. We have omitted some obvious comments like “MUST revert if the token doesn’t exist” in this Gist to shorten it. Also, block with ERC721 transfers and instructions to make them revert always was omitted. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/mark3d-xyz/mark3d/blob/f6214709356dc9d01a58622438f6c6b7822d46aa/sol-contracts/contracts/IEncryptedFileToken.sol">Full interface definition</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/mark3d-xyz/mark3d/blob/f6214709356dc9d01a58622438f6c6b7822d46aa/sol-contracts/contracts/Mark3dCollection.sol">reference implementation</a> can be found in our repository.</p><pre data-type="codeBlock" text="pragma solidity ^0.8.0;

interface IEncryptedFileToken is IERC721 {
    /// @dev Init token transfer. Shortcut for draftTransfer+completeTransferDraft
    /// @param tokenId is id for token to transfer
    /// @param to token receiver
    /// @param data transfer data
    /// @param callbackReceiver is contract on which callbacks will be called
    function initTransfer(
        uint256 tokenId,
        address to,
        bytes calldata data,
        IEncryptedFileTokenCallbackReceiver callbackReceiver
    ) external;

    /// @dev Draft transfer - lock NFT before receiver will be defined
    /// @param tokenId is id for token to transfer
    /// @param callbackReceiver is contract on which callbacks will be called
    function draftTransfer(
        uint256 tokenId,
        IEncryptedFileTokenCallbackReceiver callbackReceiver
    ) external;

    /// @dev Complete transfer draft
    /// @param tokenId is id for token to transfer
    /// @param to is token receiver
    /// @param data is transfer data
    function completeTransferDraft(
        uint256 tokenId,
        address to,
        bytes calldata publicKey,
        bytes calldata data
    ) external;

    /// @dev Set receiver public key
    /// @notice publicKey asymmetric encryption public key
    /// @param tokenId is id for token to transfer
    /// @param publicKey is receiver public key
    function setTransferPublicKey(
        uint256 tokenId,
        bytes calldata publicKey
    ) external;

    /// @dev Approve transfer and save encrypted password
    /// @param tokenId is id for token to transfer
    /// @param encryptedSecret is encrypted by receiver public key 
    ///     symmetric encryption key
    function approveTransfer(
        uint256 tokenId,
        bytes calldata encryptedSecret
    ) external;

    /// @dev Finalize transfer
    /// @notice This function MUST call `transferFinished` callback function
    /// Following cases are allowed:
    /// 1. Transfer is finalized by receiver
    /// 2. Encrypted password was set more than 24 hours ago 
    ///   (or some other significant timeout) and transfer is finalized by sender
    /// @param tokenId is id for token to transfer
    function finalizeTransfer(
        uint256 tokenId
    ) external;

    /// @dev Report fraud
    /// @notice Within this function fraud MUST be checked by IFraudDecider 
    ///    and if there is and instant decision, abandon transfer and call the callback
    /// @notice This function MUST call `transferFraudDetected` 
    ///    callback function in case of instant decision
    /// @param tokenId is id for token to transfer
    function reportFraud(
        uint256 tokenId,
        bytes calldata privateKey
    ) external;

    /// @dev Apply fraud decision, abandon transfer and call the callback
    /// @notice MUST revert if fraud decision making instant for this token instance
    /// @notice MUST revert if called not by fraud decider linked to this token
    /// @notice This function MUST call `transferFraudDetected` callback function
    /// @param tokenId is id for token to transfer
    /// @param approve equals true if fact of fraud was proved
    function applyFraudDecision(
        uint256 tokenId,
        bool approve
    ) external;

    /// @dev Cancel transfer
    /// @notice This function MUST call `transferCancelled` callback function
    /// Following cases are allowed:
    /// 1. Current owner or transfer initiator cancels transfer before completing draft
    /// 2. Current owner cancels transfer before public key was set by receiver
    /// 3. Current owner cancels transfer before encrypted password was set
    /// 4. Current owner cancels transfer before receiver finalize it or report fraud
    /// 5. Receiver cancels transfer before encrypted password was set if 24 hours 
    ///     (or some other significant timeout) past after public key was set
    /// @param tokenId is id for token to transfer
    function cancelTransfer(
        uint256 tokenId
    ) external;
    
    /// @dev Function to detect if fraud decision instant for this instance. 
    ///     Should return false in EVM chains and true in Filecoin
    /// @return Boolean indicating if fraud decision will be instant
    function fraudDecisionInstant() external view returns (bool);

    /// @dev Function to get fraud decider instance for this token
    /// @return IFraudDecider instance
    function fraudDecider() external view returns (IFraudDecider);

    /// @dev Event emitted after transfer creation
    event TransferInit(uint256 indexed tokenId, address from, address to);

    /// @dev Event emitted after transfer draft creation
    event TransferDraft(uint256 indexed tokenId, address from);

    /// @dev Event emitted after transfer draft completion
    event TransferDraftCompletion(uint256 indexed tokenId, address to);

    /// @dev Event emitted after setting receiver&apos;s public key
    event TransferPublicKeySet(uint256 indexed tokenId, bytes publicKey);

    /// @dev Event emitted after setting encrypted file password
    event TransferPasswordSet(uint256 indexed tokenId, bytes encryptedPassword);

    /// @dev Event emitted after transfer successful finish
    event TransferFinished(uint256 indexed tokenId);

    /// @dev Event emitted after fraud was reported
    event TransferFraudReported(uint256 indexed tokenId);

    /// @dev Event emitted after fraud was decided
    event TransferFraudDecided(uint256 indexed tokenId, bool approved);

    /// @dev Event emitted after transfer was cancelled
    event TransferCancellation(uint256 indexed tokenId);
}
"><code><span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IEncryptedFileToken</span> <span class="hljs-keyword">is</span> <span class="hljs-title">IERC721</span> </span>{
    <span class="hljs-comment">/// @dev Init token transfer. Shortcut for draftTransfer+completeTransferDraft</span>
    <span class="hljs-comment">/// @param tokenId is id for token to transfer</span>
    <span class="hljs-comment">/// @param to token receiver</span>
    <span class="hljs-comment">/// @param data transfer data</span>
    <span class="hljs-comment">/// @param callbackReceiver is contract on which callbacks will be called</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initTransfer</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId,
        <span class="hljs-keyword">address</span> to,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data,
        IEncryptedFileTokenCallbackReceiver callbackReceiver
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">/// @dev Draft transfer - lock NFT before receiver will be defined</span>
    <span class="hljs-comment">/// @param tokenId is id for token to transfer</span>
    <span class="hljs-comment">/// @param callbackReceiver is contract on which callbacks will be called</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">draftTransfer</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId,
        IEncryptedFileTokenCallbackReceiver callbackReceiver
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">/// @dev Complete transfer draft</span>
    <span class="hljs-comment">/// @param tokenId is id for token to transfer</span>
    <span class="hljs-comment">/// @param to is token receiver</span>
    <span class="hljs-comment">/// @param data is transfer data</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">completeTransferDraft</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId,
        <span class="hljs-keyword">address</span> to,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> publicKey,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">/// @dev Set receiver public key</span>
    <span class="hljs-comment">/// @notice publicKey asymmetric encryption public key</span>
    <span class="hljs-comment">/// @param tokenId is id for token to transfer</span>
    <span class="hljs-comment">/// @param publicKey is receiver public key</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setTransferPublicKey</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> publicKey
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">/// @dev Approve transfer and save encrypted password</span>
    <span class="hljs-comment">/// @param tokenId is id for token to transfer</span>
    <span class="hljs-comment">/// @param encryptedSecret is encrypted by receiver public key </span>
    <span class="hljs-comment">///     symmetric encryption key</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">approveTransfer</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> encryptedSecret
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">/// @dev Finalize transfer</span>
    <span class="hljs-comment">/// @notice This function MUST call `transferFinished` callback function</span>
    <span class="hljs-comment">/// Following cases are allowed:</span>
    <span class="hljs-comment">/// 1. Transfer is finalized by receiver</span>
    <span class="hljs-comment">/// 2. Encrypted password was set more than 24 hours ago </span>
    <span class="hljs-comment">///   (or some other significant timeout) and transfer is finalized by sender</span>
    <span class="hljs-comment">/// @param tokenId is id for token to transfer</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">finalizeTransfer</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">/// @dev Report fraud</span>
    <span class="hljs-comment">/// @notice Within this function fraud MUST be checked by IFraudDecider </span>
    <span class="hljs-comment">///    and if there is and instant decision, abandon transfer and call the callback</span>
    <span class="hljs-comment">/// @notice This function MUST call `transferFraudDetected` </span>
    <span class="hljs-comment">///    callback function in case of instant decision</span>
    <span class="hljs-comment">/// @param tokenId is id for token to transfer</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reportFraud</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> privateKey
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">/// @dev Apply fraud decision, abandon transfer and call the callback</span>
    <span class="hljs-comment">/// @notice MUST revert if fraud decision making instant for this token instance</span>
    <span class="hljs-comment">/// @notice MUST revert if called not by fraud decider linked to this token</span>
    <span class="hljs-comment">/// @notice This function MUST call `transferFraudDetected` callback function</span>
    <span class="hljs-comment">/// @param tokenId is id for token to transfer</span>
    <span class="hljs-comment">/// @param approve equals true if fact of fraud was proved</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">applyFraudDecision</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId,
        <span class="hljs-keyword">bool</span> approve
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;

    <span class="hljs-comment">/// @dev Cancel transfer</span>
    <span class="hljs-comment">/// @notice This function MUST call `transferCancelled` callback function</span>
    <span class="hljs-comment">/// Following cases are allowed:</span>
    <span class="hljs-comment">/// 1. Current owner or transfer initiator cancels transfer before completing draft</span>
    <span class="hljs-comment">/// 2. Current owner cancels transfer before public key was set by receiver</span>
    <span class="hljs-comment">/// 3. Current owner cancels transfer before encrypted password was set</span>
    <span class="hljs-comment">/// 4. Current owner cancels transfer before receiver finalize it or report fraud</span>
    <span class="hljs-comment">/// 5. Receiver cancels transfer before encrypted password was set if 24 hours </span>
    <span class="hljs-comment">///     (or some other significant timeout) past after public key was set</span>
    <span class="hljs-comment">/// @param tokenId is id for token to transfer</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cancelTransfer</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;
    
    <span class="hljs-comment">/// @dev Function to detect if fraud decision instant for this instance. </span>
    <span class="hljs-comment">///     Should return false in EVM chains and true in Filecoin</span>
    <span class="hljs-comment">/// @return Boolean indicating if fraud decision will be instant</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fraudDecisionInstant</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>)</span>;

    <span class="hljs-comment">/// @dev Function to get fraud decider instance for this token</span>
    <span class="hljs-comment">/// @return IFraudDecider instance</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fraudDecider</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">IFraudDecider</span>)</span>;

    <span class="hljs-comment">/// @dev Event emitted after transfer creation</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">TransferInit</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">indexed</span> tokenId, <span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to</span>)</span>;

    <span class="hljs-comment">/// @dev Event emitted after transfer draft creation</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">TransferDraft</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">indexed</span> tokenId, <span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span></span>)</span>;

    <span class="hljs-comment">/// @dev Event emitted after transfer draft completion</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">TransferDraftCompletion</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">indexed</span> tokenId, <span class="hljs-keyword">address</span> to</span>)</span>;

    <span class="hljs-comment">/// @dev Event emitted after setting receiver's public key</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">TransferPublicKeySet</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">indexed</span> tokenId, <span class="hljs-keyword">bytes</span> publicKey</span>)</span>;

    <span class="hljs-comment">/// @dev Event emitted after setting encrypted file password</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">TransferPasswordSet</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">indexed</span> tokenId, <span class="hljs-keyword">bytes</span> encryptedPassword</span>)</span>;

    <span class="hljs-comment">/// @dev Event emitted after transfer successful finish</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">TransferFinished</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">indexed</span> tokenId</span>)</span>;

    <span class="hljs-comment">/// @dev Event emitted after fraud was reported</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">TransferFraudReported</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">indexed</span> tokenId</span>)</span>;

    <span class="hljs-comment">/// @dev Event emitted after fraud was decided</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">TransferFraudDecided</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">indexed</span> tokenId, <span class="hljs-keyword">bool</span> approved</span>)</span>;

    <span class="hljs-comment">/// @dev Event emitted after transfer was cancelled</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">TransferCancellation</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">indexed</span> tokenId</span>)</span>;
}
</code></pre><p><em>EFT Standard</em></p><h2 id="h-examples" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Examples</h2><p>For a better understanding, let’s take a look at a few important examples: the implementation of IFraudDecider in our current version, which is controlled by our Rust server, and the implementation of the simplest exchange contract for selling EFT.</p><h2 id="h-ifrauddecider" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">IFraudDecider</h2><p><code>FraudDeciderWeb2</code> — A contract that implements the <code>IFraudDecider</code> interface. It has a special <code>lateDecision</code> method that is called from our Rust server. It is called when an attempt has been made to decrypt a file, and the verdict is passed in the arguments.</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import &quot;@openzeppelin/contracts/access/AccessControl.sol&quot;;
import &quot;./IFraudDecider.sol&quot;;
import &quot;./FileMarketCollection.sol&quot;;

contract FraudDeciderWeb2 is IFraudDecider, AccessControl {
    event FraudReported(address collection, uint256 tokenId, string cid,
        bytes publicKey, bytes privateKey, bytes encryptedPassword);

    struct Report {
        FileMarketCollection tokenInstance;
        uint256 id;
        string cid;
        bytes publicKey;
        bytes privateKey;
        bytes encryptedPassword;
    }

    mapping(address =&gt; mapping(uint256 =&gt; Report)) public reports;

    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
    }

    function decide(
        uint256 tokenId,
        string calldata cid,
        bytes calldata publicKey,
        bytes calldata privateKey,
        bytes calldata encryptedPassword
    ) external returns (bool, bool) {
        reports[_msgSender()][tokenId] = Report(FileMarketCollection(_msgSender()),
            tokenId, cid, publicKey, privateKey, encryptedPassword);
        emit FraudReported(_msgSender(), tokenId, cid, publicKey,
            privateKey, encryptedPassword);
        return (false, false);
    }

    function lateDecision(address tokenInstance, uint256 tokenId,
        bool approve) external onlyRole(DEFAULT_ADMIN_ROLE) {
        Report storage report = reports[tokenInstance][tokenId];
        require(bytes(report.cid).length != 0,
            &quot;FraudDeciderWeb2: report doesn&apos;t exist&quot;);
        report.tokenInstance.applyFraudDecision(tokenId, approve);
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/AccessControl.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./IFraudDecider.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./FileMarketCollection.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">FraudDeciderWeb2</span> <span class="hljs-keyword">is</span> <span class="hljs-title">IFraudDecider</span>, <span class="hljs-title">AccessControl</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">FraudReported</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> collection, <span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">string</span> cid,
        <span class="hljs-keyword">bytes</span> publicKey, <span class="hljs-keyword">bytes</span> privateKey, <span class="hljs-keyword">bytes</span> encryptedPassword</span>)</span>;

    <span class="hljs-keyword">struct</span> <span class="hljs-title">Report</span> {
        FileMarketCollection tokenInstance;
        <span class="hljs-keyword">uint256</span> id;
        <span class="hljs-keyword">string</span> cid;
        <span class="hljs-keyword">bytes</span> publicKey;
        <span class="hljs-keyword">bytes</span> privateKey;
        <span class="hljs-keyword">bytes</span> encryptedPassword;
    }

    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">uint256</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> Report)) <span class="hljs-keyword">public</span> reports;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) </span>{
        _grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">decide</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> tokenId,
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">calldata</span> cid,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> publicKey,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> privateKey,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> encryptedPassword
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span>, <span class="hljs-keyword">bool</span></span>) </span>{
        reports[_msgSender()][tokenId] <span class="hljs-operator">=</span> Report(FileMarketCollection(_msgSender()),
            tokenId, cid, publicKey, privateKey, encryptedPassword);
        <span class="hljs-keyword">emit</span> FraudReported(_msgSender(), tokenId, cid, publicKey,
            privateKey, encryptedPassword);
        <span class="hljs-keyword">return</span> (<span class="hljs-literal">false</span>, <span class="hljs-literal">false</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">lateDecision</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> tokenInstance, <span class="hljs-keyword">uint256</span> tokenId,
        <span class="hljs-keyword">bool</span> approve</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyRole</span>(<span class="hljs-params">DEFAULT_ADMIN_ROLE</span>) </span>{
        Report <span class="hljs-keyword">storage</span> report <span class="hljs-operator">=</span> reports[tokenInstance][tokenId];
        <span class="hljs-built_in">require</span>(<span class="hljs-keyword">bytes</span>(report.cid).<span class="hljs-built_in">length</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>,
            <span class="hljs-string">"FraudDeciderWeb2: report doesn't exist"</span>);
        report.tokenInstance.applyFraudDecision(tokenId, approve);
    }
}
</code></pre><p><em>IFraudDecider implementation</em></p><h2 id="h-eft-exchange" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">EFT Exchange</h2><p><code>FileMarketExchange</code> — an example of a simple exchange for selling EFT. An order is created and at that moment an EFT transfer is created to lock the token (the creation of any other transfers will be impossible until the order completion of cancellation). Using the <code>fulfillOrder</code> function, the buyer fixes his address for receiving EFT and locks the funds, and the functions of the <code>IEncryptedFileTokenCallbackReceiver</code> interface allow the exchange contract to unlock the funds upon successful or unsuccessful completion of the transfer.</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import &quot;@openzeppelin/contracts/utils/Context.sol&quot;;
import &quot;./IEncryptedFileToken.sol&quot;;
import &quot;./IEncryptedFileTokenCallbackReceiver.sol&quot;;

contract FileMarketExchange is IEncryptedFileTokenCallbackReceiver, Context {
    struct Order {
        IEncryptedFileToken token;
        uint256 tokenId;
        uint256 price;
        address payable initiator;
        address payable receiver;
        bool fulfilled;
    }

    mapping(IEncryptedFileToken =&gt; mapping(uint256 =&gt; Order)) public orders;

    function placeOrder(
        IEncryptedFileToken token,
        uint256 tokenId,
        uint256 price
    ) external {
        require(price &gt; 0, &quot;FileMarketExchange: price must be positive&quot;);
        require(token.supportsInterface(type(IEncryptedFileToken).interfaceId));
        require(orders[token][tokenId].price == 0, &quot;FileMarketExchange: order exists&quot;);
        orders[token][tokenId] = Order(token, tokenId, price,
            payable(_msgSender()), payable(0), false);
        token.draftTransfer(tokenId, IEncryptedFileTokenCallbackReceiver(this));
    }

    function fulfillOrder(
        IEncryptedFileToken token,
        bytes calldata publicKey,
        uint256 tokenId
    ) external payable {
        Order storage order = orders[token][tokenId];
        require(order.price != 0, &quot;FileMarketExchange: order doesn&apos;t exist&quot;);
        require(!order.fulfilled, &quot;FileMarketExchange: order was already fulfilled&quot;);
        require(msg.value == order.price, &quot;FileMarketExchange: value must equal&quot;);
        order.receiver = payable(_msgSender());
        order.fulfilled = true;
        order.token.completeTransferDraft(order.tokenId, order.receiver,
            publicKey, bytes(&quot;&quot;));
    }

    function cancelOrder(
        IEncryptedFileToken token,
        uint256 tokenId
    ) external {
        Order storage order = orders[token][tokenId];
        require(order.price != 0, &quot;FileMarketExchange: order doesn&apos;t exist&quot;);
        require(!order.fulfilled, &quot;FileMarketExchange: order was fulfilled&quot;);
        order.token.cancelTransfer(tokenId);
    }

    function transferCancelled(uint256 tokenId) external {
        Order storage order = orders[IEncryptedFileToken(_msgSender())][tokenId];
        require(order.price != 0, &quot;FileMarketExchange: order doesn&apos;t exist&quot;);
        if (order.fulfilled) {
            order.receiver.transfer(order.price);
        }
        delete orders[IEncryptedFileToken(_msgSender())][tokenId];
    }

    function transferFinished(uint256 tokenId) external {
        Order storage order = orders[IEncryptedFileToken(_msgSender())][tokenId];
        require(order.price != 0, &quot;FileMarketExchange: order doesn&apos;t exist&quot;);
        require(order.fulfilled, &quot;FileMarketExchange: order wasn&apos;t fulfilled&quot;);
        order.initiator.transfer(order.price);
        delete orders[IEncryptedFileToken(_msgSender())][tokenId];
    }

    function transferFraudDetected(uint256 tokenId, bool approved) external {
        Order storage order = orders[IEncryptedFileToken(_msgSender())][tokenId];
        require(order.price != 0, &quot;FileMarketExchange: order doesn&apos;t exist&quot;);
        require(order.fulfilled, &quot;FileMarketExchange: order wasn&apos;t fulfilled&quot;);
        if (approved) {
            order.receiver.transfer(order.price);
        } else {
            order.initiator.transfer(order.price);
        }
        delete orders[IEncryptedFileToken(_msgSender())][tokenId];
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/utils/Context.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./IEncryptedFileToken.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./IEncryptedFileTokenCallbackReceiver.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">FileMarketExchange</span> <span class="hljs-keyword">is</span> <span class="hljs-title">IEncryptedFileTokenCallbackReceiver</span>, <span class="hljs-title">Context</span> </span>{
    <span class="hljs-keyword">struct</span> <span class="hljs-title">Order</span> {
        IEncryptedFileToken token;
        <span class="hljs-keyword">uint256</span> tokenId;
        <span class="hljs-keyword">uint256</span> price;
        <span class="hljs-keyword">address</span> <span class="hljs-keyword">payable</span> initiator;
        <span class="hljs-keyword">address</span> <span class="hljs-keyword">payable</span> receiver;
        <span class="hljs-keyword">bool</span> fulfilled;
    }

    <span class="hljs-keyword">mapping</span>(IEncryptedFileToken <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">uint256</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> Order)) <span class="hljs-keyword">public</span> orders;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">placeOrder</span>(<span class="hljs-params">
        IEncryptedFileToken token,
        <span class="hljs-keyword">uint256</span> tokenId,
        <span class="hljs-keyword">uint256</span> price
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-built_in">require</span>(price <span class="hljs-operator">></span> <span class="hljs-number">0</span>, <span class="hljs-string">"FileMarketExchange: price must be positive"</span>);
        <span class="hljs-built_in">require</span>(token.supportsInterface(<span class="hljs-keyword">type</span>(IEncryptedFileToken).<span class="hljs-built_in">interfaceId</span>));
        <span class="hljs-built_in">require</span>(orders[token][tokenId].price <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"FileMarketExchange: order exists"</span>);
        orders[token][tokenId] <span class="hljs-operator">=</span> Order(token, tokenId, price,
            <span class="hljs-keyword">payable</span>(_msgSender()), <span class="hljs-keyword">payable</span>(<span class="hljs-number">0</span>), <span class="hljs-literal">false</span>);
        token.draftTransfer(tokenId, IEncryptedFileTokenCallbackReceiver(<span class="hljs-built_in">this</span>));
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fulfillOrder</span>(<span class="hljs-params">
        IEncryptedFileToken token,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> publicKey,
        <span class="hljs-keyword">uint256</span> tokenId
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        Order <span class="hljs-keyword">storage</span> order <span class="hljs-operator">=</span> orders[token][tokenId];
        <span class="hljs-built_in">require</span>(order.price <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"FileMarketExchange: order doesn't exist"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>order.fulfilled, <span class="hljs-string">"FileMarketExchange: order was already fulfilled"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> order.price, <span class="hljs-string">"FileMarketExchange: value must equal"</span>);
        order.receiver <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(_msgSender());
        order.fulfilled <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;
        order.token.completeTransferDraft(order.tokenId, order.receiver,
            publicKey, <span class="hljs-keyword">bytes</span>(<span class="hljs-string">""</span>));
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cancelOrder</span>(<span class="hljs-params">
        IEncryptedFileToken token,
        <span class="hljs-keyword">uint256</span> tokenId
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        Order <span class="hljs-keyword">storage</span> order <span class="hljs-operator">=</span> orders[token][tokenId];
        <span class="hljs-built_in">require</span>(order.price <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"FileMarketExchange: order doesn't exist"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>order.fulfilled, <span class="hljs-string">"FileMarketExchange: order was fulfilled"</span>);
        order.token.cancelTransfer(tokenId);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferCancelled</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        Order <span class="hljs-keyword">storage</span> order <span class="hljs-operator">=</span> orders[IEncryptedFileToken(_msgSender())][tokenId];
        <span class="hljs-built_in">require</span>(order.price <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"FileMarketExchange: order doesn't exist"</span>);
        <span class="hljs-keyword">if</span> (order.fulfilled) {
            order.receiver.<span class="hljs-built_in">transfer</span>(order.price);
        }
        <span class="hljs-keyword">delete</span> orders[IEncryptedFileToken(_msgSender())][tokenId];
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferFinished</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        Order <span class="hljs-keyword">storage</span> order <span class="hljs-operator">=</span> orders[IEncryptedFileToken(_msgSender())][tokenId];
        <span class="hljs-built_in">require</span>(order.price <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"FileMarketExchange: order doesn't exist"</span>);
        <span class="hljs-built_in">require</span>(order.fulfilled, <span class="hljs-string">"FileMarketExchange: order wasn't fulfilled"</span>);
        order.initiator.<span class="hljs-built_in">transfer</span>(order.price);
        <span class="hljs-keyword">delete</span> orders[IEncryptedFileToken(_msgSender())][tokenId];
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferFraudDetected</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">bool</span> approved</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        Order <span class="hljs-keyword">storage</span> order <span class="hljs-operator">=</span> orders[IEncryptedFileToken(_msgSender())][tokenId];
        <span class="hljs-built_in">require</span>(order.price <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"FileMarketExchange: order doesn't exist"</span>);
        <span class="hljs-built_in">require</span>(order.fulfilled, <span class="hljs-string">"FileMarketExchange: order wasn't fulfilled"</span>);
        <span class="hljs-keyword">if</span> (approved) {
            order.receiver.<span class="hljs-built_in">transfer</span>(order.price);
        } <span class="hljs-keyword">else</span> {
            order.initiator.<span class="hljs-built_in">transfer</span>(order.price);
        }
        <span class="hljs-keyword">delete</span> orders[IEncryptedFileToken(_msgSender())][tokenId];
    }
}
</code></pre><p><em>Simple exchange contract implementation</em></p><h2 id="h-whats-next" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What’s next?</h2><p>After the MVP version, our two main goals are:</p><ol><li><p>To implement our protocol on FVM/FEVM.</p></li><li><p>To build developer tools for easy usage of our EFT standard.</p></li></ol><p>We will continue development and are excited to share results in future parts of this article series. Thank you for reading and may the force be with you!</p><p>Useful links:</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://filemarket.xyz/">FileMarket</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Filemarket-xyz/file-market">Our Github repository</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/filemarket_xyz">FileMarket Twitter</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://t.me/filemarketchat">FileMarket Telegram Group</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://discord.gg/9pe5CUqqz4">FileMarket Discord</a></p></li></ul>]]></content:encoded>
            <author>filebunnies@newsletter.paragraph.com (FileBunnies)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/86170dc7151b82a7daa9ac6bf1547dd636ef2b6919a658032707b5fddd055dbd.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>