Skip to main content
Research Preview — Under active development.

EAS Resolvers

EAS Resolvers unlock location-based smart contracts. They allow you to execute arbitrary logic whenever an attestation is created — turning geospatial policy attestations into triggers for onchain actions.
For background on the Ethereum Attestation Service, see the EAS documentation.

What is a Resolver?

In the Ethereum Attestation Service, a resolver is a smart contract that gets called whenever an attestation is made against a specific schema. This enables:
  • Validation: Accept or reject attestations based on custom logic
  • Side Effects: Execute actions atomically with attestation creation
  • Composability: Combine attestations with any onchain logic

The Pattern

Basic Resolver Contract

Code snippets need testing — Verify against actual implementation before use.
About the Astral signer: The astralSigner address is the key that signs policy attestations inside the TEE. Signer management (multisig, key rotation, etc.) is on the roadmap. See Security for more details.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@eas/contracts/resolver/SchemaResolver.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract LocationGatedAction is SchemaResolver, Ownable {
    address public astralSigner;

    constructor(IEAS eas, address _astralSigner) SchemaResolver(eas) {
        astralSigner = _astralSigner;
    }

    function onAttest(
        Attestation calldata attestation,
        uint256 /* value */
    ) internal override returns (bool) {
        // 1. Verify from Astral
        require(attestation.attester == astralSigner, "Not from Astral");

        // 2. Decode policy result (BooleanPolicyAttestation)
        (
            bool result,
            bytes32[] memory inputRefs,
            uint64 timestamp,
            string memory operation
        ) = abi.decode(
            attestation.data,
            (bool, bytes32[], uint64, string)
        );

        // 3. Execute business logic
        require(result, "Policy check failed");
        _executeAction(attestation.recipient);

        return true;
    }

    function onRevoke(Attestation calldata, uint256)
        internal pure override returns (bool)
    {
        return false;  // Don't allow revocation
    }

    function _executeAction(address recipient) internal virtual {
        // Override in child contracts
    }

    // Owner-controlled signer update for key rotation
    function updateAstralSigner(address newSigner) external onlyOwner {
        astralSigner = newSigner;
    }
}

Common Patterns

NFT Minting

contract LocationNFT is LocationGatedAction, ERC721 {
    mapping(address => bool) public hasMinted;
    uint256 public nextTokenId = 1;

    function _executeAction(address recipient) internal override {
        require(!hasMinted[recipient], "Already minted");
        hasMinted[recipient] = true;
        _mint(recipient, nextTokenId++);
    }
}

Token Distribution

contract LocationAirdrop is LocationGatedAction {
    IERC20 public token;
    uint256 public amount;

    function _executeAction(address recipient) internal override {
        token.transfer(recipient, amount);
    }
}

Access Control

contract LocationGate is LocationGatedAction {
    mapping(address => bool) public hasAccess;

    function _executeAction(address recipient) internal override {
        hasAccess[recipient] = true;
    }

    modifier onlyVerified() {
        require(hasAccess[msg.sender], "Location not verified");
        _;
    }
}

Numeric Policies (Distance-Based)

Use numeric policy attestations (distance, area, length) for more sophisticated logic like spatial demurrage — where transfer fees vary based on distance from a target location.
// Decode numeric policy attestation
(
    uint256 distanceCm,
    string memory units,
    bytes32[] memory inputRefs,
    uint64 timestamp,
    string memory operation
) = abi.decode(attestation.data, (uint256, string, bytes32[], uint64, string));

// Apply distance-based fee (example: 0.1% per km from target)
uint256 distanceKm = distanceCm / 100000;
uint256 fee = (amount * distanceKm) / 1000;

Decoding Attestation Data

Boolean Policies

(
    bool result,
    bytes32[] memory inputRefs,
    uint64 timestamp,
    string memory operation
) = abi.decode(
    attestation.data,
    (bool, bytes32[], uint64, string)
);

Numeric Policies

(
    uint256 result,        // Scaled integer (centimeters)
    string memory units,   // "meters" or "square_meters"
    bytes32[] memory inputRefs,
    uint64 timestamp,
    string memory operation
) = abi.decode(
    attestation.data,
    (uint256, string, bytes32[], uint64, string)
);

// Convert back to meters
uint256 meters = result / 100;

Verification Best Practices

Always check attestation.attester == astralSigner:
require(attestation.attester == astralSigner, "Not from Astral");
Prevent replay of old attestations:
require(timestamp > block.timestamp - 1 hours, "Attestation too old");
Ensure the right locations were checked:
require(inputRefs[1] == EXPECTED_LANDMARK_UID, "Wrong location");
Prevent reuse of attestations:
mapping(bytes32 => bool) public usedAttestations;

function onAttest(...) {
    bytes32 attUid = keccak256(abi.encode(attestation));
    require(!usedAttestations[attUid], "Already used");
    usedAttestations[attUid] = true;
}

Registering Your Schema

import { SchemaRegistry } from '@ethereum-attestation-service/eas-sdk';

const schemaRegistry = new SchemaRegistry(SCHEMA_REGISTRY_ADDRESS);

// Boolean policy schema
const boolSchema = "bool result,bytes32[] inputRefs,uint64 timestamp,string operation";

const tx = await schemaRegistry.connect(signer).register({
  schema: boolSchema,
  resolverAddress: yourResolver.address,
  revocable: false
});

const receipt = await tx.wait();
const schemaUID = receipt.logs[0].args.uid;

Key Rotation

Resolver contracts should support updating the Astral signer address. For the research preview, we recommend a simple owner-controlled approach:
function updateAstralSigner(address newSigner) external onlyOwner {
    emit SignerUpdated(astralSigner, newSigner);
    astralSigner = newSigner;
}
For production deployments, you may want the owner to be a multisig. We plan to provide more graceful key rotation mechanisms in future releases.

Next: Verifiable Computation

Learn how EigenCompute provides trust