Writing the App Contract

Your app contract needs to inherit the BrevisApp abstract contract. You can manually copy the required contracts from the quickstart repo, or install Brevis contracts via

yarn add brevis-contracts
TokenTransfer.sol
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/access/Ownable.sol";

import "./lib/BrevisApp.sol";

// Accept both ZK- and OP-attested results.
contract TokenTransfer is BrevisApp, Ownable {
    event TransferAmountAttested(uint64 blockNum, address account, uint256 volume);

    bytes32 public vkHash;

    constructor(address _brevisRequest) BrevisApp(_brevisRequest) Ownable(msg.sender) {}

    // BrevisQuery contract will call our callback once Brevis backend submits the proof.
    // This method is called with once the proof is verified.
    function handleProofResult(bytes32 _vkHash, bytes calldata _circuitOutput) internal override {
        // We need to check if the verifying key that Brevis used to verify the proof
        // generated by our circuit is indeed our designated verifying key. This proves
        // that the _circuitOutput is authentic
        require(vkHash == _vkHash, "invalid vk");
        (address accountAddr, uint64 blockNum, uint256 volume) = decodeOutput(_circuitOutput);
        emit TransferAmountAttested(blockNum, accountAddr, volume);
    }

    function decodeOutput(bytes calldata o) internal pure returns (address, uint64, uint256) {
        uint64 blockNum = uint64(bytes8(o[0:8]));
        address userAddr = address(bytes20(o[8:28]));
        uint256 volume = uint256(bytes32(o[28:60]));
        return (userAddr, blockNum, volume);
    }

    // vkHash represents the unique circuit app logic
    function setVkHash(bytes32 _vkHash) external onlyOwner {
        vkHash = _vkHash;
    }

}

Checking the Verifying Key Hash

Notice that we have a vkHash storage variable in the contract:

bytes32 public vkHash;

By setting this variable to our previously computed vkhash, when Brevis calls our app contract, we can check the proof, that Brevis has verified before calling our contract, is indeed generated by our circuit.

Handling Proof Result

handleProofResult is where you process the computation result output from your circuit. There are two steps we must do before we use the proven data:

  1. Check if the param _vkHash matches our stored vkHash. This verifies the identity of the circuit which produced the computation result.

  2. Decode the param _circuitOutput.

The output values in your circuit definition are packed in the form of abi.encodePacked(...). The order of the variables is the same as the order you call the output APIs in the circuit.

We know in our circuit we did:

api.OutputUint(64, api.ToUint248(receipt.BlockNum))
api.OutputAddress(api.ToUint248(receipt.Fields[0].Value))
api.OutputBytes32(receipt.Fields[1].Value)

Therefore, this is how we decode _circuitOutput in the contract:

uint64 blockNum = uint64(bytes8(o[0:8]));
address userAddr = address(bytes20(o[8:28]));
uint256 volume = uint256(bytes32(o[28:60]));

If you are following this example to build the project, you can deploy this contract. If you are just doing research and would like to see a deployed version, see the deployed contract on Sepolia.

Last updated