PoH Quick Launch v1 · Base Sepolia

Build gates for humans,
not identity databases.

Integrate a minimal soulbound humanity credential, or require a narrowly scoped age, nationality, or sanctions Boolean. All public artifacts use pinned EIP-712 domains and contract-enforced bindings.

01

Trust model

Live v1 · issuer-attested

Self verifies passport facts. The issuer evaluates the private held credential and signs one Boolean attestation.

Release boundary · one testnet

Only the reviewed Base Sepolia contracts are accepted. Mainnet, custom v2 provers, and fabricated credentials fail closed.

Do not describe v1 predicate attestations as trustless ZK. Consumers trust the configured issuer to evaluate the fact correctly. They can independently verify the issuer, domain, bindings, and freshness.
02

Canonical v1 descriptors

Consumers require the hash of an exact canonical string. No spaces or aliases are accepted.

age>=180xe3e8342a70f40c3ef2dacba55a24b87789c9ddaf64d9d329e304d6478e856e96
age>=210xf46616a186a0a92a2ad1b0eb2ce5f36921c3801e79eb7f5d4bb4e1ed514de170
nationality=ARG0x8c8663bf87a0b160b7d8d5bf5e44cdafd54e219d069061ff778b2a9483798d44
sanctions-clear0xd414ccab0db9191e8802a047b1c0f135d0d054ed112952854cd54456966c47da

Nationality uses ISO 3166-1 alpha-3 uppercase codes: nationality=ARG, nationality=USA.

03

Request a v1 attestation

The holder calls the API from the verification app. Before signing, the server verifies the private credential signature and freshness, the subject's live SBT ownership, both contract issuer addresses, and the exact configured chain/verifier pair.

import { predicateContext } from "@ubi2/sdk";

const response = await fetch("https://proofofhumanity.org/api/predicate", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    credential,       // private, holder-controlled v1 credential
    credentialSig,
    predicate: "age>=18",
    consumer: APP_OR_CONTRACT_ADDRESS,
    context: predicateContext("community:season-1"),
    subject: connectedWallet,
    nonce: cryptoNonce.toString(),
    verifier: PREDICATE_VERIFIER_ADDRESS,
    chainId: 84532,
  }),
});

const { attestation, signature } = await response.json();

Read-only verification

import { checkPredicateArtifact } from "@ubi2/sdk";

const result = await checkPredicateArtifact({
  artifact: { chainId, verifier, attestation, signature },
  rpcUrl,
  presenter: connectedWallet,
  consumer: APP_OR_CONTRACT_ADDRESS,
});

if (!result) throw new Error("predicate not satisfied");
04

Consume v1 atomically on-chain

A state-changing consumer must call consume itself. The verifier requiresatt.consumer == msg.sender, checks subject and freshness, and marks the(subject, consumer, context, nonce) replay key as spent.

interface IPredicateVerifier {
    struct PredicateAttestation {
        address consumer;
        bytes32 context;
        bytes32 predicate;
        bool result;
        address subject;
        uint32 epoch;
        uint256 nonce;
    }

    function consume(
        PredicateAttestation calldata att,
        bytes calldata signature,
        address presenter
    ) external returns (bool result);
}

bytes32 constant AGE_18 = keccak256("age>=18");

function join(
    IPredicateVerifier.PredicateAttestation calldata att,
    bytes calldata signature
) external {
    require(att.consumer == address(this), "wrong consumer");
    require(att.context == keccak256("community:season-1"), "wrong context");
    require(att.predicate == AGE_18 && att.result, "18+ required");
    require(verifier.consume(att, signature, msg.sender), "not eligible");
    _addMember(msg.sender);
}
05

Release network

Quick Launch exposes only Base Sepolia and only when both contract addresses are configured.

NetworkChainPoHPredicateStatus
Base Sepolia845320x06BD250x2051D3configured
06

Security checklist

  • Require the exact descriptor hash, result == true, consumer, context, and subject your action expects.
  • Use consume inside state-changing flows; use check only when replay is harmless.
  • Never accept a chain ID or verifier address that is not in your own allowlist.
  • Keep the issuer key server-side in an HSM or managed signer; never expose it as NEXT_PUBLIC_*.
  • Keep owner and issuer roles distinct: owner is the governance multisig; issuer is the narrow operational signer.
  • Do not log held credentials, Self proof payloads, passport attributes, or issuer secrets.
  • Reject every non-Base-Sepolia chain and require PredicateVerifier.prover() == address(0).
07

Release boundary

This release is Base Sepolia only. Mainnet is intentionally unavailable, the predicate prover must stay unset, and a real Self callback-to-mint-to-predicate journey must be observed before launch readiness.

Research code can continue in the monorepo without becoming a route, API method, credential source, or configured release network.