Entros_docs
Concepts

Anchor PDA

The on-chain identity record and how to read it.

Each verified human has exactly one Anchor on Solana. The Anchor consists of two pieces of state: a non-transferable Token-2022 identity NFT held in the user's wallet, and a Program Derived Address called IdentityState that stores the score, fingerprint commitment, and verification metadata.

Program identity

The entros-anchor program owns the Anchor state. Devnet and mainnet share the same program ID:

GZYwTp2ozeuRA5Gof9vs4ya961aANcJBdUzB7LN6q4b2

PDA derivation

The IdentityState PDA is derived from two seeds: the literal identity and the user's wallet:

const [identityState] = PublicKey.findProgramAddressSync(
  [Buffer.from("identity"), wallet.toBuffer()],
  ENTROS_ANCHOR_PROGRAM_ID,
);

The associated mint PDA, which holds the non-transferable token, is derived from the seed mint plus the wallet:

const [mint] = PublicKey.findProgramAddressSync(
  [Buffer.from("mint"), wallet.toBuffer()],
  ENTROS_ANCHOR_PROGRAM_ID,
);

IdentityState layout

The account stores ownership, score, fingerprint commitments, and verification metadata. The Trust Score lives at offset 60 as a little-endian u16. For a one-line read of just the score, see the quickstart.

FieldTypeOffsetPurpose
Anchor discriminatoru640Account-type tag
ownerPubkey8Wallet that owns the Anchor
creation_timestampi6440Unix timestamp of first verification
last_verification_timestampi6448Unix timestamp of most recent verification
verification_countu3256Monotonic count of successful verifications
trust_scoreu1660Current Trust Score
current_commitment[u8; 32]62Latest behavioral fingerprint commitment
mintPubkey94Address of the Anchor's Token-2022 mint
bumpu8126PDA bump seed
recent_timestamps[i64; 52]127Rolling window of recent verification timestamps used by the score formula
last_reset_timestampi64543Unix timestamp of the most recent reset_identity_state
new_walletPubkey551Authorized successor wallet for migrate_identity; zero when no migration has been authorized

Field offsets are documented for direct deserialization. For typed access from a Rust client, the canonical IDL is published with the entros-anchor program.

Token-2022 identity NFT

The Anchor mint uses the Token-2022 NonTransferable and MintCloseAuthority extensions. It is a one-supply, one-decimal token held in the user's associated token account. There is no transfer hook, no royalty extension, no metadata pointer to off-chain content. The mint exists to make the Anchor visible in any Token-2022-aware wallet UI; the authoritative score and metadata live in IdentityState. MintCloseAuthority is held by the program so that migrate_identity can close the old mint when an identity moves to a new wallet.

Instructions

The entros-anchor program exposes five public instructions.

  • mint_anchor—Initializes a new Anchor on first verification. Caller must supply a valid ZK proof from entros-verifier. Charges the protocol fee.
  • update_anchor—Re-verification path. Validates a proof whose commitment_prev public input matches the stored current_commitment, advances the score, charges the fee, and overwrites current_commitment with the new value.
  • reset_identity_state—Recovery path. Zeroes the score and clears the fingerprint commitment after a 7-day cooldown, allowing a user who has lost their device drift envelope to start fresh. Charges the fee.
  • authorize_new_wallet—First step of the wallet-migration flow. The current wallet co-signs with the new wallet to record new_wallet on the IdentityState and delegate the identity token to it. Can be re-run to revise the authorization before the second step.
  • migrate_identity—Second step of the wallet-migration flow. The authorized new wallet signs to transfer the identity (Trust Score, verification history, recent timestamps) to a fresh IdentityState PDA derived from its own pubkey, and the old mint is closed.

Reading the Anchor

Three idiomatic patterns:

ContextPattern
React componentEntrosGate or EntrosBadge
Server / edge functionDirect getAccountInfo on the PDA
Anchor programCross-program account constraint with seeds = [b"identity", wallet.key().as_ref()]

All three read the same canonical state.

Where to look next

On this page