Skip to main content

Overview

Beacon can register agent identities on-chain using the ERC-7527 standard. This creates a verifiable, immutable link between your repository and an Ethereum wallet address, establishing ownership and provenance for AI agent capabilities.

What is ERC-7527?

ERC-7527 is an Ethereum standard for App-specific NFTs with dynamic metadata. Beacon uses it to:
  • Mint unique identity NFTs for repositories
  • Link wallet addresses to AGENTS.md files
  • Create verifiable on-chain records of agent capabilities
  • Enable payment and authentication flows tied to agent identities

Architecture

Beacon’s on-chain identity system consists of:
  1. BeaconApp: ERC-721 NFT contract (stores agent identities)
  2. BeaconAgency: Minting controller with bonding curve pricing
  3. Beacon CLI: Rust client that registers identities

Smart Contract Implementation

BeaconApp Contract

From BeaconIdentity.sol:28-62:
contract BeaconApp is ERC721Enumerable, Ownable {
    address public agency;
    uint256 public immutable maxSupply;

    constructor(string memory name, string memory symbol, uint256 _maxSupply) 
        ERC721(name, symbol) 
        Ownable(msg.sender)
    {
        maxSupply = _maxSupply;
    }

    modifier onlyAgency() {
        require(msg.sender == agency, "Only agency can mint");
        _;
    }

    function setAgency(address _agency) external onlyOwner {
        require(agency == address(0), "Agency already set");
        agency = _agency;
    }

    function mint(address to, bytes calldata data) external onlyAgency returns (uint256) {
        uint256 tokenId = abi.decode(data, (uint256));
        require(tokenId < maxSupply, "Max supply reached");
        _safeMint(to, tokenId);
        return tokenId;
    }
}
Key features:
  • ERC-721 compliance: Standard NFT functionality
  • Max supply cap: Prevents unlimited minting
  • Agency-only minting: Only the agency contract can create identities
  • Token ID encoding: Repository URL stored in mint data

BeaconAgency Contract

From BeaconIdentity.sol:64-115:
contract BeaconAgency is IERC7527Agency {
    BeaconApp public immutable app;
    address public immutable currency; // address(0) for native ETH
    uint256 public immutable basePremium;
    uint256 public immutable priceStep;

    event Wrap(address indexed to, uint256 indexed tokenId, uint256 premium);

    function getWrapOracle(uint256) public view override returns (Asset memory) {
        uint256 supply = app.totalSupply();
        // Linear bonding curve: Price = base + (supply * step)
        uint256 premium = basePremium + (supply * priceStep);
        return Asset(currency, premium, 0);
    }

    function wrap(address to, bytes calldata data) external payable override returns (uint256) {
        Asset memory asset = getWrapOracle(0);
        
        require(msg.value >= asset.premium, "Insufficient ETH sent");

        uint256 tokenId = app.totalSupply();
        bytes memory mintData = abi.encode(tokenId);
        
        app.mint(to, mintData);
        
        emit Wrap(to, tokenId, asset.premium);
        return tokenId;
    }
}
Key features:
  • Bonding curve pricing: Cost increases linearly with supply
  • ETH payments: Pay in native ETH (not USDC)
  • Automatic token ID: Uses current supply as next ID
  • Event emission: Tracks all registrations on-chain

Registering an Identity

Prerequisites

1

Have a Git repository

Your repository must have a .git/config file with a remote URL:
git remote -v
# origin  https://github.com/user/repo.git (fetch)
2

Generate AGENTS.md first

Run beacon generate to create the AGENTS.md file:
beacon generate ./my-project
3

Have ETH on Base

You need ETH on Base network to pay for minting. Get some from:
Cost varies based on bonding curve: basePremium + (supply * priceStep) wei

Using the CLI

# Register identity (will prompt for or generate wallet)
beacon register ./my-project

# Use existing wallet
export AGENT_PRIVATE_KEY="0x1234abcd..."
beacon register ./my-project

# Use custom agency contract
beacon register ./my-project --agency 0xCustomAgencyAddress

# Register on different chain (future)
beacon register ./my-project --chain ethereum

Registration Flow

From src/identity.rs:23-66:
pub async fn register_agent_identity(
    repo_path: &str, 
    _chain: &str, 
    agency_address: Option<&str>
) -> Result<()> {
    let agency_addr_str = agency_address.unwrap_or(CANONICAL_AGENCY);
    let agency_addr = agency_addr_str.parse::<Address>()?;
    
    let provider_url = std::env::var("BASE_RPC_URL")
        .unwrap_or_else(|_| BASE_RPC.to_string());
    let provider = Provider::<Http>::try_from(provider_url)?;
    
    // Load or generate wallet
    let wallet = match std::env::var("AGENT_PRIVATE_KEY") {
        Ok(key) => key.parse::<LocalWallet>()?,
        Err(_) => {
            let new_wallet = LocalWallet::new(&mut rand::thread_rng());
            println!("   🔑 Generated new agent wallet: {:?}", new_wallet.address());
            println!("   ⚠️  SAVE THIS PRIVATE KEY: 0x{}", hex::encode(new_wallet.signer().to_bytes()));
            new_wallet
        }
    };

    let client = Arc::new(SignerMiddleware::new(
        provider.clone(), 
        wallet.with_chain_id(8453u64) // Base chain ID
    ));
    let address = client.address();
    let agency = IERC7527Agency::new(agency_addr, client.clone());

    // Get repository URL from .git/config
    let repo_url = get_repo_url(repo_path)
        .context("Could not find repository URL in .git/config")?;
    let data = Bytes::from(repo_url.as_bytes().to_vec());

    // Check pricing
    let (premium, fee) = agency.get_wrap_oracle(data.clone()).call().await?;
    let total = premium + fee;
    
    let balance = provider.get_balance(address, None).await?;
    if balance < total {
        anyhow::bail!("Insufficient balance on Base. Need {} wei, have {} wei", total, balance);
    }

    println!("   💸 Registering identity via ERC-7527 (Cost: {} wei)...", total);
    
    // Submit transaction
    let tx = agency.wrap(address, data).value(total);
    let pending_tx = tx.send().await?;
    let receipt = pending_tx.await?.context("Transaction failed")?;
    
    println!("   ✅ Registration confirmed: {:?}", receipt.transaction_hash);

    // Update AGENTS.md with wallet address
    update_agents_md(repo_path, &format!("{:?}", address))?;

    Ok(())
}
Steps:
  1. Parse agency contract address
  2. Connect to Base RPC
  3. Load wallet from AGENT_PRIVATE_KEY or generate new one
  4. Extract repository URL from .git/config
  5. Query bonding curve price via getWrapOracle()
  6. Check wallet has sufficient ETH balance
  7. Call wrap(address, repoURL) with ETH value
  8. Wait for transaction confirmation
  9. Update AGENTS.md with registered address

Wallet Management

If AGENT_PRIVATE_KEY is not set, Beacon generates a new wallet:
let new_wallet = LocalWallet::new(&mut rand::thread_rng());
println!("   🔑 Generated new agent wallet: {:?}", new_wallet.address());
println!("   ⚠️  SAVE THIS PRIVATE KEY: 0x{}", hex::encode(new_wallet.signer().to_bytes()));
Save the private key immediately! Beacon does not store it. If you lose it, you lose access to the registered identity.

AGENTS.md Integration

After registration, Beacon updates your AGENTS.md with the wallet address:
# AGENTS.md — my-project

> My awesome AI-powered project

**Agent Identity:** `0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb`

**Version:** 1.0.0
From src/identity.rs:82-114:
fn update_agents_md(repo_path: &str, address: &str) -> Result<()> {
    let path = Path::new(repo_path).join("AGENTS.md");
    if !path.exists() {
        anyhow::bail!("AGENTS.md not found. Run 'generate' first.");
    }

    let content = fs::read_to_string(&path)?;
    let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
    
    // Try to update existing identity line
    let mut found = false;
    for line in &mut lines {
        if line.contains("Agent Identity:") {
            *line = format!("**Agent Identity:** `{}`", address);
            found = true;
            break;
        }
    }

    // If not found, insert after description block
    if !found {
        let mut insert_pos = 0;
        for (i, line) in lines.iter().enumerate() {
            if line.starts_with("> ") {
                insert_pos = i + 1;
            }
        }
        lines.insert(insert_pos, "".to_string());
        lines.insert(insert_pos + 1, format!("**Agent Identity:** `{}`", address));
    }

    fs::write(path, lines.join("\n"))?;
    println!("   📝 Updated AGENTS.md with Agent Identity.");
    Ok(())
}

Use Cases

1. Verifiable Ownership

Prove you control a repository by signing messages with the registered wallet:
// Verify signer owns the agent identity
function verifyOwnership(uint256 tokenId, bytes memory signature, bytes32 message) public view returns (bool) {
    address owner = beaconApp.ownerOf(tokenId);
    address signer = recoverSigner(message, signature);
    return owner == signer;
}

2. Payment & Monetization

Accept payments for agent API calls:
function payAgent(uint256 agentTokenId) external payable {
    address agentOwner = beaconApp.ownerOf(agentTokenId);
    payable(agentOwner).transfer(msg.value);
}

3. Reputation Systems

Build on-chain reputation tied to agent identities:
mapping(uint256 => uint256) public agentReputationScores;

function rateAgent(uint256 tokenId, uint256 score) external {
    require(hasUsedAgent(msg.sender, tokenId), "Must use agent first");
    agentReputationScores[tokenId] += score;
}

4. Composability

Other contracts can query agent capabilities:
function getAgentCapabilities(uint256 tokenId) external view returns (string memory) {
    address owner = beaconApp.ownerOf(tokenId);
    // Fetch AGENTS.md from IPFS or similar
    return fetchCapabilities(owner);
}

Pricing Model

The BeaconAgency uses a linear bonding curve:
premium = basePremium + (totalSupply * priceStep)
Example pricing (assuming basePremium = 0.001 ETH, priceStep = 0.0001 ETH):
Identity #SupplyPrice (ETH)Price (USD at $3000/ETH)
1st00.001$3.00
10th90.0019$5.70
100th990.0109$32.70
1000th9990.1009$302.70
Pricing parameters are set at deployment and cannot be changed. Check the agency contract for current values.

Configuration

Environment Variables

AGENT_PRIVATE_KEY
string
Private key for the wallet that will own the agent identity.Format: 0x + 64 hex charactersExample: 0x1234abcd...
BASE_RPC_URL
string
default:"https://mainnet.base.org"
RPC endpoint for Base network.Alternatives:
  • Alchemy: https://base-mainnet.g.alchemy.com/v2/YOUR_KEY
  • Infura: https://base-mainnet.infura.io/v3/YOUR_KEY

Contract Addresses

const CANONICAL_AGENCY: &str = "0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8";

Querying Identities

Get Token Owner

# Using cast (Foundry)
cast call 0xAppAddress "ownerOf(uint256)" 42 --rpc-url https://mainnet.base.org

Get Total Supply

cast call 0xAppAddress "totalSupply()" --rpc-url https://mainnet.base.org

Get Current Price

cast call 0xAgencyAddress "getWrapOracle(uint256)" 0 --rpc-url https://mainnet.base.org

Security Considerations

Never share your private key. Anyone with access to AGENT_PRIVATE_KEY can control your agent identity and transfer the NFT.
Use hardware wallets (Ledger, Trezor) for high-value agent identities. Beacon supports ethers-rs signer interfaces.

Best Practices

  1. Separate wallets: Use dedicated wallets for agent identities, not your main funds
  2. Backup keys: Store private keys securely (password manager, hardware wallet)
  3. Test on testnets: Try Base Sepolia before mainnet registration
  4. Monitor transactions: Watch for unauthorized transfers of your identity NFT
  5. Smart contract audits: Verify BeaconAgency and BeaconApp contracts before registering

Troubleshooting

”Could not find repository URL in .git/config”

Problem: Not running in a Git repository. Solution:
cd /path/to/your/git/repo
git remote -v  # Verify remote exists
beacon register .

“Insufficient balance on Base”

Problem: Wallet doesn’t have enough ETH to pay minting cost. Solution:
# Check current price
cast call 0xAgencyAddress "getWrapOracle(uint256)" 0 --rpc-url https://mainnet.base.org

# Bridge ETH to Base
# https://bridge.base.org

“AGENTS.md not found. Run ‘generate’ first.”

Problem: Trying to register before generating AGENTS.md. Solution:
beacon generate ./my-project
beacon register ./my-project

“Transaction failed”

Common causes:
  • Gas price too low (rare on Base)
  • Contract reverted (check Basescan for reason)
  • Network congestion
Solution: Check transaction on Basescan for revert reason.

Future Enhancements

Multi-Chain Support

Register identities on Ethereum, Arbitrum, Optimism

IPFS Integration

Store AGENTS.md content on IPFS, reference via NFT metadata

Dynamic Metadata

Update agent capabilities on-chain as repository evolves

Identity Delegation

Allow team members to act on behalf of registered agents

Next Steps

Beacon Cloud

Use Beacon with USDC payments instead of API keys

API Reference

Integrate on-chain identities into your API workflows

Build docs developers (and LLMs) love