> ## Documentation Index
> Fetch the complete documentation index at: https://docs.opaque.cash/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate a reputation proof

> End-to-end flow from discovered traits to on-chain verification.

This guide covers trait discovery, Groth16 proof generation, simulation, and submission to `OpaqueReputationVerifier`.

## Prerequisites

* WASM module (`wasmModuleSpecifier`)
* Native announcement rows with V2 PSR attestation metadata
* `snarkjs` (pulled in by `@opaquecash/psr-prover`)
* Circuit artifacts (default: hosted on opaque.cash `/circuits/...`)

## Step 1: Discover traits

```ts theme={"dark"}
const chain = "ethereum" as const;
const rows = await client.fetchAnnouncementRows(chain);
const traits = await client.discoverTraitsV2(rows, { chain });

if (traits.length === 0) {
  throw new Error("No V2 reputation traits found in inbox");
}

const trait = traits[0];
```

`discoverTraitsV2` validates each `0xB2` marker against the chain's schema registry and
returns only traits issued by an authorized schema authority or delegate. Fetch rows from
the native chain, not UAB, because relayed payloads cannot carry the full V2 metadata.

## Step 2: Reconstruct the stealth signing key

```ts theme={"dark"}
const stealthPrivKeyBytes = client.getStealthSignerPrivateKeyForReputationTrait(trait);
// Or from a scan output:
// client.getStealthSignerPrivateKey(output)
```

## Step 3: Build the action scope

```ts theme={"dark"}
import { buildActionScope, externalNullifierFromScope } from "@opaquecash/opaque";

const scope = buildActionScope({
  chainId: 11155111,
  module: "my-app",
  actionId: "premium-gate",
});
const externalNullifier = externalNullifierFromScope(scope).toString();
```

Or use the equivalent static helpers on the client
(`OpaqueClient.buildReputationActionScope` / `reputationExternalNullifierFromScope`).

## Step 4: Generate the proof

```ts theme={"dark"}
const proofData = await client.generateReputationProof({
  trait,
  stealthPrivKeyBytes,
  externalNullifier,
  onProgress: (stage, pct) => console.log(stage, pct),
  // artifacts: { wasmPath, zkeyPath }  // override default hosted paths
});

// proofData: { proof, publicSignals, nullifier, attestationId }
```

When `trait` comes from `discoverTraitsV2`, the SDK uses the trait's V2 Merkle leaf
preimage fields automatically. You only need to pass `issuerPkX`, `traitDataHash`, or
`nonce` yourself for custom witness construction.

## Step 5: Fetch a valid Merkle root

```ts theme={"dark"}
const merkleRoot = await client.fetchLatestValidReputationRoot();
const valid = await client.isReputationRootValid(merkleRoot);

const history = await client.fetchReputationRootHistory();
```

## Step 6: Simulate (optional)

```ts theme={"dark"}
await client.simulateReputationVerification(walletClient, {
  proofData,
  merkleRoot,
  externalNullifier,
});
```

## Step 7: Verify view (read-only)

```ts theme={"dark"}
const ok = await client.verifyReputationProofView({
  proofData,
  merkleRoot,
  externalNullifier,
});
```

## Step 8: Submit on-chain

Consumes the nullifier on success:

```ts theme={"dark"}
const { txHash } = await client.submitReputationVerification("ethereum", {
  proofData,
  merkleRoot,
  externalNullifier,
});
```

Solana:

```ts theme={"dark"}
await client.submitReputationVerification("solana", {
  proofData,
  merkleRoot,
  externalNullifier,
});
```

<Note>
  The same Groth16 proof — from the same trusted setup — verifies on Ethereum, Solana, and
  Starknet. On Starknet (Sepolia, testnet preview) PSR is **verification and gating only**: the
  `OpaqueReputationVerifierV2` Groth16 verifier plus the `PsrGate` credential-gated-entry contract,
  via `@opaquecash/psr-chain-starknet` (`getStarknetDeployment("sepolia")`). Schema registration and
  attestation issuance stay on Ethereum and Solana. See [Starknet](/concepts/starknet) for the full
  flow and caveats.
</Note>

## Full flow diagram

```mermaid theme={"dark"}
flowchart TD
  A[fetch native announcements] --> B[discoverTraitsV2]
  B --> C[getStealthSignerPrivateKeyForReputationTrait]
  C --> D[generateReputationProof]
  D --> E[fetchLatestValidReputationRoot]
  E --> F[submitReputationVerification]
```

<Note>
  Each `(trait, externalNullifier)` pair can only be verified once. Choose scopes carefully per action you gate.
</Note>
