Skip to content

selfxyz/self-integration-boilerplate

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

64 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Self Protocol Boilerplate Example with Hyperlane Bridging

Learn to build privacy-preserving identity verification with Self Protocol and bridge it cross-chain using Hyperlane - verify on Celo, use on Base!

πŸ“Ί New to Self? Watch the ETHGlobal Workshop first.

Branches

This branch demonstrates cross-chain verification bridging. For simple on-chain verification, check the main branch.

  • main: on chain verification
  • backend-verification: off chain/backend verification
  • hyperlane-example: onchain verification w/ Hyperlane bridging

πŸŒ‰ Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  CELO SEPOLIA (Source Chain)                                 β”‚
β”‚                                                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚  β”‚  ProofOfHumanSender                   β”‚                   β”‚
β”‚  β”‚  - Inherits SelfVerificationRoot     β”‚                   β”‚
β”‚  β”‚  - Verifies users via Self Protocol  β”‚                   β”‚
β”‚  β”‚  - Automatic cross-chain bridging    β”‚                   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚                   β”‚                                           β”‚
β”‚                   β”‚ Hyperlane Message                        β”‚
β”‚                   β–Ό                                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚
                    β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  BASE SEPOLIA (Destination Chain)                            β”‚
β”‚                   β”‚                                           β”‚
β”‚                   β–Ό                                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚  β”‚  ProofOfHumanReceiver                 β”‚                   β”‚
β”‚  β”‚  - Receives Hyperlane messages       β”‚                   β”‚
β”‚  β”‚  - Stores verification data          β”‚                   β”‚
β”‚  β”‚  - Simple & gas-efficient            β”‚                   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚                                                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Prerequisites

  • Node.js 20+
  • Self Mobile App
  • Celo Sepolia wallet with testnet funds
  • Base Sepolia wallet with testnet funds (for receiver deployment)

Integration Steps

Step 1: Repository Setup

# Clone the boilerplate repository
git clone https://github.com/selfxyz/self-integration-boilerplate.git
git switch hyperlane-example
cd workshop

# Install frontend dependencies
cd app
npm install

# Install contract dependencies
cd contracts
npm install
forge install foundry-rs/forge-std

Step 2: Deploy Receiver Contract (Base Sepolia)

First, deploy the receiver contract on Base Sepolia:

# Create copy of env
cp .env.example .env

Edit .env with your Base Sepolia wallet:

# Your private key (with 0x prefix)
PRIVATE_KEY=0xyour_private_key_here

Deploy the receiver:

# Make script executable
chmod +x script/deploy-proof-of-human-receiver.sh

# Deploy receiver on Base Sepolia
./script/deploy-proof-of-human-receiver.sh

The script will:

  • βœ… Deploy the receiver contract on Base Sepolia
  • βœ… Display the contract address
  • βœ… Provide instructions to update your .env files

Save the RECEIVER_ADDRESS for the next step!

Step 3: Deploy Sender Contract (Celo Sepolia)

Add the receiver address to your .env:

# Add this line to .env
RECEIVER_ADDRESS=0x... # Address from Step 2

# Optional: Customize scope seed
SCOPE_SEED="proof-of-human-hyperlane"

Deploy the sender:

# Make script executable
chmod +x script/deploy-proof-of-human-sender.sh

# Deploy contract
./script/deploy-proof-of-human.sh

The script will:

  • βœ… Deploy the sender contract on Celo Sepolia
  • βœ… Fund the contract with 0.01 CELO (~10 automatic bridges)
  • βœ… Display both contract addresses and contract balance
  • βœ… Provide complete frontend .env configuration
  • βœ… Show commands to automatically update your frontend .env

The script provides all the configuration you need and funds automatic bridging!

Step 4: Frontend Configuration

Navigate to app folder

# Create copy of env
cp .env.example .env
# Edit .env with the values from the deployment script output

Option B: Automatic Configuration (Recommended)

The deployment script shows commands to automatically update your .env:

cd ../app
echo 'NEXT_PUBLIC_SELF_ENDPOINT=0x...' > .env
echo 'NEXT_PUBLIC_RECEIVER_ADDRESS=0x...' >> .env
echo 'NEXT_PUBLIC_SELF_APP_NAME="Self + Hyperlane Workshop"' >> .env
echo 'NEXT_PUBLIC_SELF_SCOPE_SEED="proof-of-human-hyperlane"' >> .env

Just copy and paste the commands from the deployment script output!

Step 5: Start Development

# Start the Next.js development server
cd app
npm run dev

Visit http://localhost:3000 to see your verification application!


πŸ”„ How It Works

Automatic Cross-Chain Bridging

  1. User verifies on Celo Sepolia through your frontend
  2. Verification succeeds β†’ customVerificationHook is called
  3. Contract uses its balance β†’ Automatically pays Hyperlane gas & bridges to Base
  4. Data arrives on Base β†’ Receiver stores verification status (~2 minutes)

The deployment script automatically funds the sender contract with 0.01 CELO, enough for ~10 automatic bridges.

Manual Bridging (Optional)

If the contract runs out of funds or you need to re-send:

# Fund the contract for more automatic bridges
cast send <SENDER_ADDRESS> --value 0.01ether --rpc-url celo-sepolia --private-key $PRIVATE_KEY

# Or manually bridge a specific verification
export SENDER_ADDRESS=0x...
export RECEIVER_ADDRESS=0x...
forge script script/SendVerificationCrossChain.s.sol:SendVerificationCrossChain \
  --rpc-url celo-sepolia \
  --broadcast

πŸ› οΈ Detailed Configuration

Frontend SDK Configuration

The Self SDK is configured in your React components (app/app/page.tsx):

import { SelfAppBuilder, countries } from '@selfxyz/qrcode';

const app = new SelfAppBuilder({
    version: 2,                    // Always use V2
    appName: process.env.NEXT_PUBLIC_SELF_APP_NAME,
    scope: process.env.NEXT_PUBLIC_SELF_SCOPE_SEED,
    endpoint: process.env.NEXT_PUBLIC_SELF_ENDPOINT,  // Your contract address (must be lowercase)
    logoBase64: "https://i.postimg.cc/mrmVf9hm/self.png", // Logo URL or base64
    userId: userId,                // User's wallet address or identifier
    endpointType: "staging_celo",  // "staging_celo" for testnet, "celo" for mainnet
    userIdType: "hex",             // "hex" for Ethereum addresses, "uuid" for UUIDs
    userDefinedData: "Hola Buenos Aires!!!",  // Optional custom data
    
    disclosures: {
        minimumAge: 18,
        excludedCountries: [countries.UNITED_STATES],
    }
}).build();

Sender Contract (Celo Sepolia)

ProofOfHumanSender inherits from SelfVerificationRoot:

contract ProofOfHumanSender is SelfVerificationRoot {
    function customVerificationHook(
        ISelfVerificationRoot.GenericDiscloseOutputV2 memory output,
        bytes memory userData
    ) internal override {
        // Store verification data
        verificationSuccessful = true;
        lastOutput = output;
        lastUserAddress = address(uint160(output.userIdentifier));

        emit VerificationCompleted(output, userData);
        
        // Automatically bridge if ETH was sent with verification
        if (address(this).balance > 0) {
            bytes memory message = abi.encode(
                bytes32(output.userIdentifier),
                lastUserAddress,
                userData,
                block.timestamp
            );
            
            bytes32 messageId = MAILBOX.dispatch{value: address(this).balance}(
                DESTINATION_DOMAIN,
                defaultRecipient.addressToBytes32(),
                message
            );
            
            emit VerificationSentCrossChain(messageId, defaultRecipient, lastUserAddress, bytes32(output.userIdentifier));
        }
    }
}

Receiver Contract (Base Sepolia)

ProofOfHumanReceiver is a simple Hyperlane message receiver:

contract ProofOfHumanReceiver is IMessageRecipient, Ownable {
    function handle(
        uint32 _origin,
        bytes32 _sender,
        bytes calldata _message
    ) external override {
        // Verify caller is Hyperlane Mailbox
        if (msg.sender != address(MAILBOX)) revert NotMailbox();
        
        // Verify origin is Celo Sepolia
        if (_origin != SOURCE_DOMAIN) revert InvalidOrigin(_origin, SOURCE_DOMAIN);
        
        // Decode and store verification data
        (bytes32 userIdentifier, address userAddress, bytes memory userData, uint256 verifiedAt) 
            = abi.decode(_message, (bytes32, address, bytes, uint256));
        
        verifications[userAddress] = VerificationData({
            userIdentifier: userIdentifier,
            userAddress: userAddress,
            userData: userData,
            verifiedAt: verifiedAt,
            receivedAt: block.timestamp,
            exists: true,
            isVerified: true
        });
        
        emit VerificationReceived(userAddress, userIdentifier, block.timestamp);
    }
}

Network Configuration

Celo Sepolia (Source Chain)

  • Chain ID: 11142220
  • Identity Hub: 0x16ECBA51e18a4a7e61fdC417f0d47AFEeDfbed74
  • Hyperlane Mailbox: 0xD0680F80F4f947968206806C2598Cbc5b6FE5b03
  • RPC: https://forno.celo-sepolia.celo-testnet.org
  • Explorer: https://celo-sepolia.blockscout.com/

Base Sepolia (Destination Chain)

  • Chain ID: 84532
  • Hyperlane Mailbox: 0x6966b0E55883d49BFB24539356a2f8A673E02039
  • RPC: https://sepolia.base.org
  • Explorer: https://sepolia.basescan.org

πŸ§ͺ Testing

Run Contract Tests

cd contracts
forge test -vv

All 23 tests should pass:

  • ProofOfHumanSender: 8 tests
  • ProofOfHumanReceiver: 11 tests
  • HyperlaneCrossChain: 3 tests

Verify Cross-Chain Message

After a user verifies, check the message on Hyperlane Explorer:

# Get the message ID from transaction logs
cast logs --rpc-url celo-sepolia \
  --address <SENDER_ADDRESS> \
  --from-block <BLOCK_NUMBER>

# Track on Hyperlane Explorer
https://explorer.hyperlane.xyz/message/<MESSAGE_ID>

Check Verification on Base

# Check if user is verified on Base Sepolia
cast call <RECEIVER_ADDRESS> \
  "isVerified(address)(bool)" \
  <USER_ADDRESS> \
  --rpc-url base-sepolia

πŸ“ Project Structure

self-integration-example/
β”œβ”€β”€ app/                                 # Next.js frontend application
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ page.tsx              # QR code verification page
β”‚   β”‚   β”œβ”€β”€ verified/             # Success page
β”‚   β”‚   └── globals.css
β”‚   └── .env.example
β”‚
β”œβ”€β”€ contracts/                    # Foundry contracts
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ ProofOfHumanSender.sol      # Sender contract (Celo)
β”‚   β”‚   β”œβ”€β”€ ProofOfHumanReceiver.sol    # Receiver contract (Base)
β”‚   β”‚   β”œβ”€β”€ ProofOfHuman.sol            # Base implementation
β”‚   β”‚   └── IMailboxV3.sol              # Hyperlane interface
β”‚   β”‚
β”‚   β”œβ”€β”€ script/
β”‚   β”‚   β”œβ”€β”€ Base.s.sol                   # Base script utilities
β”‚   β”‚   β”œβ”€β”€ DeployProofOfHuman.s.sol     # Foundry deployment script
β”‚   β”‚   └── deploy-proof-of-human.sh     # Automated deployment script
β”‚   β”œβ”€β”€ lib/                             # Dependencies
β”‚   β”‚   β”œβ”€β”€ forge-std/                   # Foundry standard library
β”‚   β”‚   └── openzeppelin-contracts/      # OpenZeppelin contracts
β”‚   β”œβ”€β”€ .env.example                     # Contract environment template
β”‚   β”œβ”€β”€ foundry.toml                     # Foundry configuration
β”‚   β”œβ”€β”€ package.json                     # Contract dependencies
β”‚   β”‚   β”œβ”€β”€ deploy-proof-of-human-receiver.sh   # Deploy receiver
β”‚   β”‚   β”œβ”€β”€ deploy-proof-of-human-sender.sh     # Deploy sender
β”‚   β”‚   β”œβ”€β”€ DeployProofOfHumanReceiver.s.sol
β”‚   β”‚   β”œβ”€β”€ DeployProofOfHumanSender.s.sol
β”‚   β”‚   └── SendVerificationCrossChain.s.sol    # Manual bridging
β”‚   β”‚
β”‚   └── test/
β”‚       β”œβ”€β”€ ProofOfHumanSender.t.sol
β”‚       β”œβ”€β”€ ProofOfHumanReceiver.t.sol
β”‚       └── HyperlaneCrossChain.t.sol
β”‚
└── README.md                            # This file

πŸ”— Additional Resources

Documentation

Deployed Contracts (Example)

  • Sender (Celo Sepolia): 0x210cEb7F310197a3D4E83554086cCeD570314Ee4
  • Receiver (Base Sepolia): 0x0690e42FA30BcC48Dd0bf8BF926654e6efDFee89

Self App

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors