BFS: Build an IPFS alternative

Let's build the Bitcoin File System (BFS), a decentralized, open, and permissionless file storage on Bitcoin.

  • write a file to Bitcoin

  • read a file from Bitcoin

  • support large files

Write the BFS smart contract

It turns out that writing the Bitcoin File System smart contract is very simple. Here is a basic contract to provide decentralized file storage on Bitcoin Virtual Machine.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Counters.sol";

error FileExists();

contract BFS {
        using Counters for Counters.Counter;
        Counters.Counter private idCounter;
        mapping(address => mapping(string => mapping(uint256 => bytes))) public dataStorage;
        mapping(address => mapping(string => uint256)) public chunks; // max chunk index
        mapping(address => mapping(string => uint256)) public bfsId;

        constructor () {
                idCounter.increment(); // start from 1
        }

        function store(string memory filename, uint256 chunkIndex, bytes memory _data) external {
                if (dataStorage[msg.sender][filename][chunkIndex].length > 0) {
                        revert FileExists();
                }
                dataStorage[msg.sender][filename][chunkIndex] = _data;
                if (chunks[msg.sender][filename] < chunkIndex) {
                        chunks[msg.sender][filename] = chunkIndex;
                }
                bfsId[msg.sender][filename] = idCounter.current();
                idCounter.increment();
        }

        function load(address addr, string memory filename, uint256 chunkIndex) public view returns (bytes memory, int256) {
                uint256 temp = chunkIndex + 1;
                int256 nextChunk = (temp > chunks[addr][filename]) ? -1 : int256(temp);
                return (dataStorage[addr][filename][chunkIndex], nextChunk);
        }

        function count(address addr, string memory filename) public view returns (uint256) {
                return chunks[addr][filename];
        }

        function getId(address addr, string memory filename) public view returns (uint256) {
                return bfsId[addr][filename];
        }
}

The store() function saves a chunk of a file. The load() function returns the data of a specific chunk. And the getId() function returns the ID of the file.

You can save a large file onto Bitcoin by splitting it into smaller chunks, committing them to Bitcoin, and retrieving & merging them back as needed.

BFS files are immutable.

Clone the smart contract examples

We've prepared a few different examples for you to get started. The BNS example is located at smart-contract-examples/contracts/BNS.sol.

git clone https://github.com/trustlesscomputer/smart-contract-examples.git

Compile the contracts

To compile your contracts, use the built-in hardhat compile task.

cd smart-contract-examples
npm install
npx hardhat compile

Deploy the contracts

Review config file hardhat.config.ts. The network configs should look like this.

  networks: {
    mynw: {
      url: "http://localhost:10002",
      accounts: {
        mnemonic: "<your mnemonic with funds>"
      },
      timeout: 100_000,
    },
    blockscoutVerify: {
      blockscoutURL: "http://localhost:4000", // your explorer URL
      ...
    }
  }

Run the deploy scripts using hardhat-deploy.

npx hardhat deploy --tags BFS

Make sure the accounts in hardhat.config.ts have some $BVM.

Interact with the contracts

Once the contracts are deployed, you can interact with them. We've prepared a few hardhat tasks to make it easy for you to interact with the contracts.

# store data
echo "this is some text" > data.txt
npx hardhat write-storage --filename ./data.txt
npx hardhat read-storage --filename ./data.txt # read data, print it as readable text

Last updated