Solidity API

Overview

Witness's system is comprised of a few contracts, each playing different roles in producing and consuming provenance. This document aims to give a high level overview of the contracts and the functionalities they provide.

Witness

Witness.sol is the contract responsible for checkpointing merkle roots and verifying their consistency. It also includes a few helper functions for things like verifying proofs. Notably, clients can use Witness.sol to get the current merkle root or verify proofs they have stored locally without needing to rely on the Witness server.

The remaining contracts illustrate patterns for consuming provenance proofs from Witness onchain.

WitnessConsumer

WitnessConsumer.sol establishes the core pattern for verifying the provenance of data onchain; if you're writing a contract that has a general need to verify provenance without falling into one of the more specific patterns below, this is the contract you should use.

Implementing a contract on top of WitnessConsumer.sol requires the following steps:

  1. Overriding getProvenanceHash: this function is used to map the data you're verifying the provenance of to its hash representation which was actually checkpointed on Witness. As an example, if my data is a string, the corresponding getProvenanceHash implementation may look like this:
function getProvenanceHash(bytes calldata bridgeData) public view virtual override returns (bytes32) {
  // We can recover the underlying string value like this if we want to manipulate it to produce the leaf hash.
  (string memory str) = abi.decode(bridgeData, (string));
  // Different usecases may map the data to a leaf differently; in this case we simply append a suffix to the string.
  bytes memory preimage = abi.encode(string.concat(str, "my suffix"));
  return keccak256(preimage);
}
  1. Initialize your contract by passing in the correct Witness.sol address to the constructor. This will be the address of the Witness instance you want to verify the provenance of your data against.

  2. Consume the provenance! WitnessConsumer.sol provides verifyProvenance and safeVerifyProof methods which your contract can call into to verify proofs.

IERC721P

IERC721P.sol is an interface for a contract which implements the ERC721P standard. This standard is an extension of ERC721 which adds methods related to verifying and minting NFTs based off of provenance proofs. This interface inherits from WitnessConsumer.sol and adds the following additional methods to be overwritten if you chose to inherit from this:

  1. getBridgedOwner: this function is used to map the data you're verifying the provenance of to the address of the owner of the NFT, without needing to pay gas to submit the transaction onchain. Based on the logic your contract uses to mint NFTs, this may be as simple as returning the address of the user in the committed data payload, or conducting some more complex logic to determine the owner.

  2. bridgedTokenURI: this function is used to map the data you're verifying the provenance of to the URI of the NFT, without needing to pay gas to submit the transaction onchain.

  3. bridge: this function is the ERC721P equivalent of a mint function. The main difference from a simpler mint method is that this method should verify the provenance of the data its being provided before minting the NFT.

While IERC721P.sol is abstract and unopinionated in defining these methods, we provide a simple default flavor of this with ERC721P.sol. Overall, the goal of these methods is to create a dynamic where NFTs can be counterfactually minted by way of verifying the existance of their initial provenance with a contract onchain which would support full-fledged stateful tokenization of the data at any future point in time. Supporting reading things like the URI or owner of an NFT before it has to be fully minted is a key part of this, as it allows for things like marketplaces to display NFTs without users needing to pay the gas to mint them.

ERC721P

ERC721P.sol is a sample implementation of IERC721P.sol which provides simple implementations of the methods defined in IERC721P.sol. This contract can be used as a starting point for applications that want to build on the ERC721P standard.

Specifically, ERC721P.sol is built around a simple data schema involving the committed data payload entailing a string which acts as the metadata URI, as well as an address which represents the owner of the NFT. The getBridgedOwner and bridgedTokenURI methods are implemented to simply return these values, getProvenanceHash does a simple hash over the abi encoding of these values, and bridge simply mints an NFT to the owner with the given URI.

While this aims to be a simple implementation, it's important to keep in mind that these methods can include much more complex logic. For example, bridge could enforce some fee payment in order to promote the provenant data into a stateful NFT, or it could support handling signed messages from the original owner of the data to endorse another party bridging it.