Solidity SDK

The Solidity SDK simplifies integrating with Relic Protocol on-chain from your smart contracts. This page serves as a developer guide for using the Solidity SDK.

You can also check out the full Solidity API documentation for the Solidity SDK.

Installation

npm install --save-dev @relicprotocol/contracts

Usage

To use the Relic Solidity SDK, simply import the desired contracts in your Solidity code. For example:

import '@relicprotocol/contracts/interfaces/IReliquary.sol';

Reliquary Queries

Most apps will want to import the IReliquary interface in order to query proven facts (the Reliquary contract stores the proven historical state facts, called "relics").

Fact Signatures

To query the Reliquary for a fact directly, you'll need to know its fact signature. The Solidity SDK defines helper functions for computing fact signatures for the relevant facts. For example, to query a storage slot fact:

pragma solidity ^0.8.0;
import '@relicprotocol/contracts/BirthCertificateVerifier.sol';
import '@relicprotocol/contracts/interfaces/IReliquary.sol';
contract StorageSlotQuerier {
// find the deployed Reliquary address at https://docs.relicprotocol.com/contracts
IReliquary reliquary = IReliquary(RELIQUARY_ADDRESS);
function querySlotFact(
address addr,
bytes32 slot,
uint256 blockNum
) internal view returns (bytes memory) {
(bool exists, , bytes memory data) = reliquary.verifyFactNoFee(
addr,
FactSigs.storageSlotFactSig(slot, blockNum)
);
require(exists, 'storage proof missing');
return data;
}
}

Notice that the fact signature for the slot depends only on the slot and block number: FactSigs.storageSlotFactSig(slot, blockNum). The address whose slot is being queried is passed in the first argument to reliquary.verifyFactNoFee(address, factSig).

See the Solidity API docs for the full list of fact signatures that can be queried.

Computing Storage Slots

To prove or query historical storage, you need to compute the storage slot you are interested in. Similar to the client SDK, our Solidity SDK has helper functions for computing storage slots. To find the base slot for your storage variable of interest, see our guide here.

import '@relicprotocol/contracts/lib/Storage.sol';
contract MyContract {
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
bytes32 constant WETH_BALANCEOF_BASE = bytes32(uint256(3));
function computeWETHBalanceSlot(
address account
) internal pure returns (bytes32) {
return
Storage.mapElemSlot(
WETH_BALANCEOF_BASE,
bytes32(uint256(uint160(account)))
);
}
}

See the Solidity API docs for the full list of storage slot helper functions.

Convenience Contracts

For some fact types, the Relic Solidity SDK includes some convenience code to simplify queries.

BirthCertificateVerifier

BirthCertificateVerifier is an easy-to-use base contract which provides internal functions and modifiers for validating accounts' birth certificates. The example below defines a function which reverts when called by an account which has not proven it is at least 1 year old:

pragma solidity ^0.8.0;
import '@relicprotocol/contracts/BirthCertificateVerifier.sol';
import '@relicprotocol/contracts/interfaces/IReliquary.sol';
contract MyContract is BirthCertificateVerifier {
// find the deployed Reliquary address at https://docs.relicprotocol.com/contracts
constructor() BirthCertificateVerifier(IReliquary(RELIQUARY_ADDRESS)) {}
function someFunction() external onlyOlderThan(365 days) {
// we know msg.sender's account is at least 1 year old
}
}

In order for users to call MyContract.someFunction(), they will have to first prove their birth certificate using the Relic frontend, or from your own app using the Relic client SDK.

See the Solidity API docs for the other functions and modifiers offered by BirthCertificateVerifier.

RelicReceiver

Extending the RelicReceiver contract is the easiest way to integrate Relic's ephemeral facts, which are especially useful for handling large facts such as logs and block headers. Your receiver contract can opt in to receiving different fact types simply by overriding a handler for each fact type:

import '@relicprotocol/contracts/RelicReceiver.sol';
contract MyReceiver is RelicReceiver {
// find the deployed EphemeralFacts address at https://docs.relicprotocol.com/contracts
constructor(IEphemeralFacts ephemeralFacts) RelicReceiver(ephemeralFacts) {}
// support receiving block header facts
function receiveBlockHeaderFact(
address initiator,
uint256 blockNum,
CoreTypes.BlockHeaderData memory header
) internal override {
// use proven block header info
}
// support receiving log facts
function receiveLogFact(
address initiator,
address account,
uint256 blockNum,
uint256 txIdx,
uint256 logIdx,
CoreTypes.LogData memory log
) internal override {
// use proven log info
}
}

Most use cases will only support handling one type of fact, but the example above demonstrates handling two. See the Solidity API docs for the full list of supported fact types. The first argument to these handlers is the initiator, i.e. the address which initated the fact request, which may be useful for authentication in some use cases.

See the client SDK docs for information on how to prove ephemeral facts and pass them to your contract.