> ## 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.

# Issue an attestation

> End-to-end issuer flow, from attesting to a stealth identity to announcing for discovery.

## Prerequisites

* Funded issuer wallet authorized on the target schema (authority or delegate)
* Recipient meta-address hex for the full announce path
* `ethereumWalletClient` or `solanaWallet` configured

## Issue to a meta-address

When the recipient is a meta-address, `issueAttestation` resolves stealth material, submits `attest`, and publishes an announcement (default `announce: true`):

```ts theme={"dark"}
const { txHash, uid, stealthAddressHash } = await client.issueAttestation("ethereum", {
  schemaId,
  recipient: recipientMetaAddressHex,   // meta-address hex
  fieldValues: {
    passed: "true",
    jurisdiction: "US",
  },
  expiration: { slotOrBlock: 9_999_999 },  // optional
  refUid: undefined,                        // optional chained credential
  announce: true,                           // default when recipient is meta-address
});

console.log({ txHash, uid, stealthAddressHash });
```

The announcement uses the V2 `0xB2` metadata layout, so recipients discover it with
`discoverTraitsV2(rows, { chain })`.

## Issue to self (E2E test pattern)

```ts theme={"dark"}
const result = await client.issueAttestation("ethereum", {
  schemaId,
  recipient: client.getMetaAddressHex(),
  fieldValues: { passed: "true", note: "e2e" },
});

const issued = await client.getMyIssuedAttestations("ethereum");
const found = issued.find((a) => a.uid === result.uid);
```

## Recipient formats

```ts theme={"dark"}
// Meta-address: full announce path
recipient: "0x" + "04".repeat(66)  // 132 hex chars

// Raw EVM stealth address: resolves to stealth_address_hash
recipient: "0x" + "ab".repeat(20)

// Precomputed hash: no announce (no ephemeral key available)
recipient: "0x" + "cd".repeat(32)
```

## Legacy V1 issuer announce helpers

These helpers encode the older V1 `0xA7` attestation id marker. Keep them only for legacy
integrations. For schema-bound V2 attestations, prefer `issueAttestation`, which submits
the attestation and publishes the matching V2 announcement.

```ts theme={"dark"}
const prep = client.prepareReputationAssignment(recipientMeta, attestationId);
const announce = client.buildAssignReputationTransaction(recipientMeta, attestationId);
await walletClient.sendTransaction({ to: announce.to, data: announce.data });
```

Metadata encoding:

```ts theme={"dark"}
const metadata = client.encodeReputationMetadata(viewTag, attestationId);
```

## Verify issuance

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

const attestations = await client.getMyIssuedAttestations("ethereum");

for (const a of attestations) {
  // AttestationV2 carries the raw encoded payload; decode it with the schema's fields.
  const fields = decodeAttestationData(a.dataHex, parseFieldDefs(schema.fieldDefinitions));
  console.log(a.uid, a.schemaId, a.stealthAddressHash, fields);
}
```

## Solana

Same API; pass `chain: "solana"`:

```ts theme={"dark"}
await client.issueAttestation("solana", {
  schemaId,
  recipient: client.getMetaAddressHex(),
  fieldValues: { passed: "true", note: "devnet" },
});
```

## Error cases

| Error                    | Cause                                 |
| ------------------------ | ------------------------------------- |
| Schema not found         | Invalid `schemaId` or wrong chain     |
| Not authorized issuer    | Wallet is not authority/delegate      |
| Recipient not registered | EOA lookup failed (send flows)        |
| Expiry in the past       | Invalid `expiration` / `schemaExpiry` |
