Skip to main content

Shield Contract

The Shield contract manages access policies on the Base blockchain.

Contract Address

NetworkAddress
Base Mainnet0x4b8F46e5E3d95D78f30F80F1280fE7e5F92c8ce8

Overview

The Shield contract stores access policies that define:
  • Who can access content (recipient)
  • When access expires (timestamp)
  • How many attempts are allowed (maxAttempts)
  • How many attempts have been made (attempts)

Data Structures

AccessPolicy

struct AccessPolicy {
    address sender;      // Creator of the policy
    address recipient;   // Authorized accessor
    uint64 expiry;       // Expiration timestamp
    uint32 maxAttempts;  // Maximum access attempts
    uint32 attempts;     // Current attempt count
    bool valid;          // Policy validity flag
}

Storage

mapping(bytes32 => AccessPolicy) public policies;
Policies are indexed by policyId (keccak256 hash).

Functions

createPolicy

Create a new access policy.
function createPolicy(
    bytes32 policyId,
    address recipient,
    uint256 expiry,
    uint256 maxAttempts
) external
Parameters:
ParameterTypeDescription
policyIdbytes32Unique identifier (hash)
recipientaddressWallet authorized to access
expiryuint256Unix timestamp of expiration
maxAttemptsuint256Maximum access attempts allowed
Requirements:
  • Policy doesn’t already exist
  • Recipient is not zero address
  • Expiry is in the future
  • maxAttempts > 0
Emits: PolicyCreated Example:
const policyId = keccak256(toUtf8Bytes(uniqueString));
const recipient = '0x742d...';
const expiry = Math.floor(Date.now() / 1000) + 86400; // 24 hours
const maxAttempts = 3;

await contract.createPolicy(policyId, recipient, expiry, maxAttempts);

logAttempt

Log an access attempt. Must be called by recipient.
function logAttempt(bytes32 policyId, bool success) external
Parameters:
ParameterTypeDescription
policyIdbytes32Policy to log attempt for
successboolWhether access was granted
Requirements:
  • Policy exists
  • Policy is valid
  • Caller is the recipient
  • Not expired
  • Attempts remaining
Emits: VerificationAttempt Example:
await contract.logAttempt(policyId, true);

isPolicyValid

Check if a policy is currently valid.
function isPolicyValid(bytes32 policyId) external view returns (bool)
Returns true if:
  • Policy exists
  • valid flag is true
  • Not expired
  • Attempts remaining
Example:
const isValid = await contract.isPolicyValid(policyId);

Events

PolicyCreated

event PolicyCreated(
    bytes32 indexed policyId,
    address indexed sender,
    address indexed recipient,
    uint256 expiry,
    uint256 maxAttempts
);
Emitted when a new policy is created.

VerificationAttempt

event VerificationAttempt(
    bytes32 indexed policyId,
    bool success
);
Emitted when an access attempt is logged.

Access Control

FunctionWho Can Call
createPolicyAnyone
logAttemptRecipient only
isPolicyValidAnyone (view)

Error Messages

ErrorMeaning
”Policy already exists”policyId is taken
”Policy does not exist”policyId not found
”Policy is not valid”valid flag is false
”Only the recipient can log an attempt”Wrong caller
”Policy has expired”expiry < block.timestamp
”Max attempts reached”attempts >= maxAttempts

Flow Example

// 1. Generate unique policyId
const uniqueString = `${address}-${Date.now()}-${randomBytes(8)}`;
const policyId = keccak256(toUtf8Bytes(uniqueString));

// 2. Create policy
const tx = await contract.createPolicy(
  policyId,
  recipientAddress,
  Math.floor(Date.now() / 1000) + 86400,
  3
);
await tx.wait();

// 3. Store metadata (off-chain)
await fetch('/api/storeMetadata', {
  method: 'POST',
  body: JSON.stringify({
    policyId,
    cid: ipfsCid,
    sender: address,
    recipient: recipientAddress,
    // ...
  })
});

Accessing Content

// 1. Verify SIWE
const token = await authenticate();

// 2. Check policy
const isValid = await contract.isPolicyValid(policyId);
if (!isValid) throw new Error('Policy invalid');

// 3. Log attempt
const tx = await contract.logAttempt(policyId, true);
await tx.wait();

// 4. Fetch and decrypt content
const content = await fetchFromIPFS(cid);
const decrypted = await decrypt(content, secretKey);

Security Considerations

  • Immutable policies: Once created, cannot be modified
  • Self-enforcing: Rules are enforced by contract, not trust
  • Transparent: All policies and attempts are public
  • Auditable: Complete history on-chain

Gas Costs

FunctionApproximate Gas
createPolicy~50,000
logAttempt~30,000
isPolicyValid~5,000 (view)

Source Code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Shield {
    struct AccessPolicy {
        address sender;
        address recipient;
        uint64 expiry;
        uint32 maxAttempts;
        uint32 attempts;
        bool valid;
    }

    mapping(bytes32 => AccessPolicy) public policies;

    event PolicyCreated(bytes32 indexed policyId, address indexed sender, address indexed recipient, uint256 expiry, uint256 maxAttempts);
    event VerificationAttempt(bytes32 indexed policyId, bool success);

    function createPolicy(bytes32 policyId, address recipient, uint256 expiry, uint256 maxAttempts) external {
        require(policies[policyId].sender == address(0), "Policy already exists");
        policies[policyId] = AccessPolicy({
            sender: msg.sender,
            recipient: recipient,
            expiry: uint64(expiry),
            maxAttempts: uint32(maxAttempts),
            attempts: 0,
            valid: true
        });
        emit PolicyCreated(policyId, msg.sender, recipient, expiry, maxAttempts);
    }

    function logAttempt(bytes32 policyId, bool success) external {
        AccessPolicy storage policy = policies[policyId];
        require(policy.sender != address(0), "Policy does not exist");
        require(policy.valid, "Policy is not valid");
        require(msg.sender == policy.recipient, "Only the recipient can log an attempt");
        require(block.timestamp < policy.expiry, "Policy has expired");
        require(policy.attempts < policy.maxAttempts, "Max attempts reached");

        policy.attempts++;

        if (policy.attempts >= policy.maxAttempts) {
            policy.valid = false;
        }

        emit VerificationAttempt(policyId, success);
    }

    function isPolicyValid(bytes32 policyId) external view returns (bool) {
        AccessPolicy storage policy = policies[policyId];
        return policy.valid && block.timestamp < policy.expiry && policy.attempts < policy.maxAttempts;
    }
}