Go to app

Configure your smart contract

Verifying attestations

Digital signatures

Spearmint produces digital signatures by encoding the user's address (along with extra attestation data if applicable) into a message following EIP-191.

The message is signed using a randomly generated signer, the address for which is available on the Signatures subtab in the Developers section. This is the signer address that you will use in your smart contract to verify Spearmint signatures.

Note: you can roll to a new signer address at any time you wish. This option may be helpful if you've accidentally exposed your API before things were ready, and you fear that some people have grabbed a hold of the signatures prematurely. This will happen (hopefully) rarely in practice, but it's a good idea to work in the ability to update the signer address on your contract just in case.

The following example shows one way to verify Spearmint signatures in your smart contract. The AttestationData struct used below can be conveniently copied from the Project subtab in the Developers section (see Set your attestation schema). The SignatureAttestable contract used below, along with accompanying tests, can be found in this example repo.

📘

Please Note!

This piece of code (and those in the example repo) is for demonstration purposes only and is not to be copied and pasted into production code without careful review.

// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import "./attestable/SignatureAttestable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract SignatureClaimWithAttestationData is ERC20, SignatureAttestable {
    struct AttestationData {
        uint256 validAt;
        uint256 quantity;
    }

    constructor(
        address signerAddress
    ) ERC20("Spearmint", "SPEAR") SignatureAttestable(signerAddress) {}

    function claim(
        AttestationData calldata data,
        bytes calldata signature
    ) external onlyValidSignature(_getHashedData(data), signature) {
        if (data.validAt > block.timestamp) {
            revert ClaimNotYetAvailable();
        }
        // TODO: Mark as claimed.
        _mint(msg.sender, data.quantity);
    }

    function _getHashedData(
        AttestationData calldata data
    ) internal view returns (bytes32 hashedData) {
        hashedData = keccak256(abi.encode(msg.sender, data));
    }

    error ClaimNotYetAvailable();
}

Merkle proofs

On Spearmint, you can generate Merkle trees against which an address on the allowlist will be able to generate a Merkle proof to send to your smart contract. Unlike the digital signatures approach, a generated Merkle tree will be an immutable snapshot of the addresses (along with extra attestation data if applicable) at the time of generation and any changes to the entries or attestation schema/data will require a generation of a new Merkle tree.

The root hash of the generated Merkle tree can be copied by expanding the accordion for the given tree under the Merkle trees subtab. This is the root hash that you will use in your smart contract to verify Spearmint Merkle proofs.

The following example shows one way to verify Spearmint Merkle proofs in your smart contract. The AttestationData struct used below can be conveniently copied from the Project subtab in the Developers section (see Set your attestation schema). The MerkleProofAttestable contract used below, along with accompanying tests, can be found in this example repo.

📘

Please Note!

This piece of code (and those in the example repo) is for demonstration purposes only and is not to be copied and pasted into production code without careful review.

// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import "./attestable/MerkleProofAttestable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MerkleProofClaimWithAttestationData is ERC20, MerkleProofAttestable {
    struct AttestationData {
        uint256 validAt;
        uint256 quantity;
    }

    constructor(
        bytes32 rootHash
    ) ERC20("Spearmint", "SPEAR") MerkleProofAttestable(rootHash) {}

    function claim(
        AttestationData calldata data,
        bytes32[] calldata proof
    ) external onlyValidProof(_getHashedData(data), proof) {
        if (data.validAt > block.timestamp) {
            revert ClaimNotYetAvailable();
        }
        // TODO: Mark as claimed.
        _mint(msg.sender, data.quantity);
    }

    function _getHashedData(
        AttestationData calldata data
    ) private view returns (bytes32 hashedData) {
        hashedData = keccak256(abi.encode(msg.sender, data));
    }

    error ClaimNotYetAvailable();
}