DEV Community

Cover image for a16z "Can't be Evil" licenses - a practical guide
Jonas Pauli for Casper Blockchain

Posted on

a16z "Can't be Evil" licenses - a practical guide

This guide provides detailed documentation on how to install and use a16z “Can’t be Evil” licenses on Casper.

Introduction
There are many uncertainties regarding copyright and ownership in the NFT space. Users purchase tokenized avatars and artworks, whilst unaware of the legal situation. Resarachers at the venture capital firm “a16z” have made an effort to solve these issues with their “Can’t be evil” NFT licenses. According to a16z, NFTs need to be licensed, as owning the token Id on the blockchain may not be enough to legally reproduce or display an artwork.

The a16z “Can’t be evil” NFT licenses include sets of rules regarding ownership and copyright. Rights and permissions granted through the license only apply to the owner of the NFT. Licenses make it easier to clearly communicate which rights are or are not granted to the customer.

Read more on a16z “Can’t be Evil” licenses.

Smart Contracts
a16z has already published their licenses on Arweave. Additionally, a16z released this Solidity smart contract:

// SPDX-License-Identifier: MIT
// a16z Contracts v0.0.1 (CantBeEvil.sol)
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "./ICantBeEvil.sol";

enum LicenseVersion {
    CBE_CC0,
    CBE_ECR,
    CBE_NECR,
    CBE_NECR_HS,
    CBE_PR,
    CBE_PR_HS
}

contract CantBeEvil is ERC165, ICantBeEvil {
    using Strings for uint;
    string internal constant _BASE_LICENSE_URI = "ar://_D9kN1WrNWbCq55BSAGRbTB4bS3v8QAPTYmBThSbX3A/";
    LicenseVersion public licenseVersion; // return string
    constructor(LicenseVersion _licenseVersion) {
        licenseVersion = _licenseVersion;
    }

    function getLicenseURI() public view returns (string memory) {
        return string.concat(_BASE_LICENSE_URI, uint(licenseVersion).toString());
    }

    function getLicenseName() public view returns (string memory) {
        return _getLicenseVersionKeyByValue(licenseVersion);
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165) returns (bool) {
        return
            interfaceId == type(ICantBeEvil).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    function _getLicenseVersionKeyByValue(LicenseVersion _licenseVersion) internal pure returns (string memory) {
        require(uint8(_licenseVersion) <= 6);
        if (LicenseVersion.CBE_CC0 == _licenseVersion) return "CBE_CC0";
        if (LicenseVersion.CBE_ECR == _licenseVersion) return "CBE_ECR";
        if (LicenseVersion.CBE_NECR == _licenseVersion) return "CBE_NECR";
        if (LicenseVersion.CBE_NECR_HS == _licenseVersion) return "CBE_NECR_HS";
        if (LicenseVersion.CBE_PR == _licenseVersion) return "CBE_PR";
        else return "CBE_PR_HS";
    }
}
Enter fullscreen mode Exit fullscreen mode

In order to support “Can’t be Evil” licenses in Casper smart contracts, the above contract has been translated to Rust:

#![no_std]
#![no_main]

#[cfg(not(target_arch = "wasm32"))]
compile_error!("target arch should be wasm32: compile with '--target wasm32-unknown-unknown'");
extern crate alloc;

use alloc::{
    string::{String, ToString},
    vec,
};

use casper_types::{
    contracts::NamedKeys, runtime_args, ApiError, CLType, CLValue, ContractHash, EntryPoint,
    EntryPointAccess, EntryPointType, EntryPoints, Parameter, RuntimeArgs, URef,
};

use casper_contract::{
    contract_api::{
        runtime,
        storage::{self},
    },
    unwrap_or_revert::UnwrapOrRevert,
};

#[repr(u16)]
enum A16ZError {
    InvalidLicenseVersion = 0,
}
impl From<A16ZError> for ApiError {
    fn from(e: A16ZError) -> Self {
        ApiError::User(e as u16)
    }
}

const _BASE_LICENSE_URI: &str = "ar://_D9kN1WrNWbCq55BSAGRbTB4bS3v8QAPTYmBThSbX3A/";
const CONTRACT_PACKAGE_HASH: &str = "a16z-contract-hash";
const CONTRACT_HASH_KEY_NAME: &str = "contract_hash";

#[no_mangle]
pub extern "C" fn getLicenseURI() {
    let licenseVersion: u64 = runtime::get_named_arg("_licenseVersion");
    match licenseVersion {
        0 => licenseVersion,
        1 => licenseVersion,
        2 => licenseVersion,
        3 => licenseVersion,
        4 => licenseVersion,
        5 => licenseVersion,
        _ => runtime::revert(A16ZError::InvalidLicenseVersion),
    };

    let licenseURI: String = String::from(_BASE_LICENSE_URI) + &licenseVersion.to_string();
    let licenseUriCLValue: CLValue = CLValue::from_t(licenseURI).unwrap_or_revert();
    runtime::ret(licenseUriCLValue);
}

#[no_mangle]
pub extern "C" fn getLicenseName() {
    let licenseVersion: u64 = runtime::get_named_arg("_licenseVersion");
    match licenseVersion {
        0 => runtime::ret(CLValue::from_t(String::from("CBE_CC0")).unwrap_or_revert()),
        1 => runtime::ret(CLValue::from_t(String::from("CBE_ECR")).unwrap_or_revert()),
        2 => runtime::ret(CLValue::from_t(String::from("CBE_NECR")).unwrap_or_revert()),
        3 => runtime::ret(CLValue::from_t(String::from("CBE_NECR_HS")).unwrap_or_revert()),
        4 => runtime::ret(CLValue::from_t(String::from("CBE_PR")).unwrap_or_revert()),
        5 => runtime::ret(CLValue::from_t(String::from("CBE_PR_HS")).unwrap_or_revert()),
        _ => runtime::revert(A16ZError::InvalidLicenseVersion),
    };
}

#[no_mangle]
pub extern "C" fn call() {
    let entry_points = {
        let mut entry_points = EntryPoints::new();
        let getLicenseURI = EntryPoint::new(
            "getLicenseURI",
            vec![Parameter::new("_licenseVersion", CLType::U64)],
            CLType::String,
            EntryPointAccess::Public,
            EntryPointType::Contract,
        );
        let getLicenseName = EntryPoint::new(
            "getLicenseName",
            vec![Parameter::new("_licenseVersion", CLType::U64)],
            CLType::String,
            EntryPointAccess::Public,
            EntryPointType::Contract,
        );
        entry_points.add_entry_point(getLicenseURI);
        entry_points.add_entry_point(getLicenseName);
        entry_points
    };
    let named_keys = {
        let mut named_keys = NamedKeys::new();
        named_keys
    };
    storage::new_contract(
        entry_points,
        Some(named_keys),
        Some(String::from(CONTRACT_PACKAGE_HASH)),
        Some(String::from(CONTRACT_HASH_KEY_NAME)),
    );
}
Enter fullscreen mode Exit fullscreen mode

a16z license URIs ( for NFT-developers )
Platforms like Opensea comply with the eip-721 metadata standard. Learn more here .

Where the “Can’t be Evil” license URI is stored depends on which metadata schema or standard makes the most sense for a project to follow. Most NFT platforms on Casper will likely comply with the CEP-78 metadata standard. As a metadata URI is a link to a server that returns JSON data, it makes the most sense to include the license in the data returned by the backend.

Important: The NFT metadata URI is not the same as the License URI. The License URI is an arweave link to a .doc file, whilst the NFT URI fetches JSON, such as the name, description and image URL from a backend.

As a16z licenses are quite novel, there is not yet a standardized schema to follow. Depending on the legal situation, it might be appropriate to include a license URI or URL in an NFT’s “description”. Casper supports custom metadata schemata, therefore the license URI could be included in the NFT metadata on mint. However, when using custom metadata, other platforms may not be able to integrate the NFTs without manually adding support for a collection / metadata schema.

Read more about metadata on Casper here.
Casper a16z Installation guide
Follow the steps below to compile, and deploy the a16z “Can’t be Evil” contract on casper.

  • Clone the git repository
chef@jonas:~/Desktop$ git clone https://github.com/jonas089/a16z-casper
chef@jonas:~/Desktop$ cd a16z-casper/contract
Enter fullscreen mode Exit fullscreen mode
  • Compile the a16z contract
chef@jonas:~/Desktop/a16z-casper/contract$ cargo build --release --target wasm32-unknown-unknown
Enter fullscreen mode Exit fullscreen mode

=> compiles contract.wasm to ./target/wasm32-unknown-unknown/release

project structure

  • Deploy the a16z contract ( to Casper Testnet )
chef@jonas:~/Desktop/a16z-casper/contract/target/wasm32-unknown-unknown/release$ casper-client put-deploy --node-address NODE_ADDRESS --secret-key PRIVATE_KEY_PATH --payment-amount GAS --chain-name casper-test --session-path contract.wasm
Enter fullscreen mode Exit fullscreen mode

Example:
explorer snapshot
Verify the deploy was successful using the explorer:
Image description

  • Query the contract hash of the installed contract
chef@jonas:~/Desktop$ casper-client get-state-root-hash -n NODE_ADDRESS
Enter fullscreen mode Exit fullscreen mode

=> outputs a state root hash.

chef@jonas:~/Desktop$ casper-client query-global-state -n NODE_ADDRESS -s STATE_ROOT_HASH --key YOUR_ACCOUNT_HASH
Enter fullscreen mode Exit fullscreen mode

=> outputs account information, including the contract hash.

Call the a16z Contract to get a License URI
Query the URI for a “Can’t be Evil” license by calling the “getLicenseURI” endpoint, passing the a16z contract hash and the license version as runtime arguments:

...
#[no_mangle]
pub extern "C" fn simulateThirdParty(){
    let _licenseVersion:u64 = 0;
    let contract_hash:ContractHash = runtime::get_named_arg("CONTRACT_HASH");
    let licenseURI:String = runtime::call_contract::<String>(
        contract_hash,
        "getLicenseURI",
        runtime_args!{
            "_licenseVersion" => _licenseVersion
        }
    );
    // do something with licenseURI
}
...
Enter fullscreen mode Exit fullscreen mode

runtime::call_contract takes in the contract hashto call the “getLicenseURI” entry point of the a16z contract.
The “getLicenseURI”entry point will return a URI for the “CBE_CC0” license ( licenseVersion 0 ).

list of all a16z license versions

0 => CBE_CC0
1 => CBE_ECR
2 => CBE_NECR
3 => CBE_NECR_HS
4 => CBE_PR
5 => CBE_PR_HS
Enter fullscreen mode Exit fullscreen mode

Summary
Thanks to a16z, licensing NFTs is simple and straightforward. Both a Solidity and Rust smart contract is available today and users can start using “Can’t be Evil” licenses on various blockchains now. Licenses are relevant, as the current legal situation regarding NFT copyrights is unclear and users should have an interest in knowing which data they actually own and to what extent they can use it.

Top comments (0)