Entros_docs
Reference

Errors

Error codes returned by each on-chain program.

Every program returns custom error codes via Anchor's #[error_code] macro. The codes start at 6000 and are assigned in declaration order; meanings are documented here. Declaration order can shift when a variant is added, so read the numbers from the program's IDL at the commit you build against rather than caching them across upgrades.

entros-anchor errors

CodeNameWhen
6000InvalidCommitmentSubmitted commitment is all zeros
6001UnauthorizedCaller is not the identity owner
6002ArithmeticOverflowInternal arithmetic operation overflowed
6003InvalidProtocolConfigProtocol config account owner or layout is wrong
6004InvalidIdentityStateIdentity state account failed to deserialize
6005IdentitySerializationFailedIdentity state account failed to serialize
6006VerificationResultWrongOwnerVerification result account is not owned by the verifier program
6007StaleVerificationResultVerification result account predates the cross-program binding patch
6008VerifierMismatchVerification result was signed by a different authority than the one in the request
6009ProofExpiredThe verification result is older than MAX_PROOF_AGE_SECS (10 minutes)
6010CommitmentMismatchThe proof's commitment_new does not match the submitted new commitment
6011PrevCommitmentMismatchThe proof's commitment_prev does not match the identity's current commitment
6012ResetCooldownActiveA reset_identity_state was attempted before the 7-day cooldown elapsed
6013RebaselineCooldownActiveA rebaseline_anchor was attempted before its cooldown elapsed
6014UnauthorizedNewWalletCaller is not authorized by the old identity to claim a new wallet
6015ProofFromFutureThe verification result's verified_at is in the future relative to the cluster clock
6016MissingValidatorReceiptmint_anchor expected a preceding Ed25519 validator-signed receipt instruction; none was found
6017ReceiptValidatorMismatchMint receipt was signed by a key that does not match the registered validator pubkey
6018ReceiptCommitmentMismatchMint receipt commitment does not match the mint_anchor commitment argument
6019ReceiptWalletMismatchMint receipt wallet does not match the mint signer
6020ReceiptExpiredMint receipt has aged past MAX_RECEIPT_AGE_SECS (5 minutes)
6021ReceiptFromFutureMint receipt validated_at is in the future relative to the cluster clock
6022MalformedReceiptMessageMint receipt message has malformed length, layout, or cross-instruction references
6023IdentityStateNotFoundThe expected IdentityState PDA was not supplied or does not exist
6024ValidatorNotConfiguredNo validator pubkey is registered in ProtocolConfig

The Receipt* family (6016–6022) only fires on the first-verification path. Re-verification (update_anchor) binds via the verifier-program VerificationResult PDA, not via the validator-signed mint receipt.

entros-verifier errors

CodeNameWhen
6000InvalidProofFormatProof bytes don't decode as a valid Groth16 proof
6001ProofVerificationFailedOn-chain pairing check failed
6002ChallengeExpiredThe associated challenge passed its expiry window
6003ChallengeAlreadyUsedThe challenge was already consumed by a successful verify_proof
6004InvalidPublicInputsPublic input count, encoding, or value bounds violated
6005ChallengeNotUsedA close_challenge was attempted on a challenge that has not been consumed
6006InvalidNonceThe challenge nonce is all zeros
6007ArithmeticOverflowInternal arithmetic operation overflowed

entros-registry errors

CodeNameWhen
6000InsufficientStakeValidator stake is below min_stake
6001ValidatorAlreadyRegisteredValidator state already exists for this authority
6002ValidatorNotActiveValidator state is registered but not currently active
6003UnauthorizedCaller is not the expected authority for this instruction
6004ArithmeticOverflowInternal arithmetic operation overflowed
6005InsufficientTreasuryBalanceWithdrawal amount exceeds the treasury's available balance
6006ProgramDataBytesProgramData account bytes are malformed or shorter than expected
6007WrongUpgradeAuthorityCaller is not the program's upgrade authority
6008InvalidProtocolConfigProtocol config bytes are malformed
6009InvalidValidatorPubkeyValidator pubkey supplied to set_validator_pubkey is the zero pubkey

entros-voter-weight errors

CodeNameWhen
6000InvalidRealmAuthorityCaller signing the registrar instruction is not the realm authority
6001MissingIdentityAccountVoter's identity account was not supplied via remaining_accounts
6002InvalidIdentityPdaIdentity account address does not match the expected PDA derivation
6003InvalidIdentityOwnerIdentity account is not owned by the Entros Anchor program
6004InvalidIdentityDataIdentity account data is shorter than the expected layout
6005InsufficientTrustScoreVoter's score is below the registrar's min_trust_score
6006VerificationExpiredVoter's last verification is older than max_verification_age
6007VoterWeightRecordRealmMismatchVoter weight record realm does not match the registrar
6008VoterWeightRecordMintMismatchVoter weight record mint does not match the registrar
6009VoterWeightRecordOwnerMismatchVoter weight record owner does not match the voter authority
6010RealmHasNoAuthorityThe realm has no authority configured; registrar updates require one
6011GoverningTokenOwnerSignerMismatchThe instruction's governing_token_owner does not match the signer
6012InvalidMaxVerificationAgemax_verification_age must be greater than zero
6013InvalidMaxVoterWeightmax_voter_weight must be greater than zero

SDK and HTTP errors

@entros/pulse-sdk returns a SubmissionResult from every submission helper:

type SubmissionResult = {
  success: boolean;
  txSignature?: string;
  attestationTx?: string;
  error?: string;
  // 4.1.0
  failedAt?: VerificationPhase;
  opaque?: boolean;
  baselineRecovery?: BaselineRecoveryReason;
  portableBaseline?: boolean;
};

On success, txSignature is the verification transaction signature. attestationTx, when present, is the SAS attestation write. On failure, error carries the user-surfaceable message.

Route on the phase, not on the message

failedAt names the stage that failed. Read it instead of matching words in error, which is what the SDK's own hosts used to do and what produced two mislabelled failures in production: an on-chain revert rendered as a validation rejection, because the matcher for a validator rejection also matched custom program error.

import { phaseChargesAttempt } from "@entros/pulse-sdk";

const result = await session.complete(wallet, connection);
if (!result.success) {
  switch (result.failedAt) {
    case "capture":
    case "extraction":
      return promptRecapture();
    case "validation":
      return showValidationOutcome(result);
    case "baseline":
      return showBaselineRecovery(result.baselineRecovery);
    case "signing":
      return showWalletCancelled();
    case "submission":
    case "confirmation":
      return showChainOutcome(result);
    default:
      return showGenericError(result.error);
  }
}

The eight phases are capture, extraction, validation, baseline, proving, signing, submission, confirmation.

Two rules across the wallet seam, because adapters merge signing and sending into one call and the outcome there is genuinely ambiguous. Only a declined prompt is signing, and only a failure the cluster reported is confirmation. Everything else, both SDK timeouts included, is submission, which says the outcome is unknown rather than claiming either way. phaseSpend returns none, possible or certain for the same reason.

Metering attempts

If you cap attempts, cap them on phaseChargesAttempt. Only validation evaluated whether a person was there, so only validation may charge for one. Charging every failure means three declined wallet prompts hard-fail someone whose capture passed each time.

if (phaseChargesAttempt(result.failedAt) && !isClientOriginReason(result.reason)) {
  attemptsUsed += 1;
}

Do not describe an opaque failure

opaque: true means the cause must not be shown. A replay-floor rejection in proving, an attack-signal rejection in validation and a program revert in confirmation have to render identically, or the difference tells an attacker which layer caught them.

You may still match a specific, user-actionable condition first, exactly as the Custom 6011 and Custom 6012 examples below do. The flag decides only what to show when nothing matched: render your generic rejection copy, not error.

Chain-side transaction failures

Since 1.5.0, the SDK reports a failure when an on-chain transaction reverts. Earlier versions silently returned success: true for transactions that were included on chain but failed during execution, an easy way to ship a "verified!" UI for a tx that mutated nothing. The error preserves the JSON InstructionError shape so callers can extract the Custom code.

Since 4.1.0 these arrive with failedAt: "confirmation" and opaque: true. Match the codes you act on first, then fall back to your generic rejection copy rather than to error:

const result = await session.complete(wallet, connection);
if (!result.success && result.failedAt === "confirmation") {
  // result.error is one of:
  //   "Transaction failed on chain: {\"InstructionError\":[0,{\"Custom\":6011}]} (sig=...)"  // PrevCommitmentMismatch
  //   "Transaction failed on chain: {\"InstructionError\":[1,{\"Custom\":6016}]} (sig=...)"  // MissingValidatorReceipt
  //   "Transaction failed on chain: {\"InstructionError\":[0,\"InsufficientFundsForRent\"]}"
  //   ...or a non-chain failure (network, wallet rejected, etc.)
  const customMatch = result.error?.match(/"Custom":(\d+)/);
  if (customMatch) {
    const code = Number(customMatch[1]);
    if (code === 6012) showCooldownMessage();
    else if (code === 6011) showStaleBaselinePrompt();
    else showOpaqueRejection();
  } else {
    showOpaqueRejection();
  }
}

/attest requires wallet ownership proof

A request to the executor's /attest endpoint now requires wallet_address, nonce, signature, and message together on every call. Missing any of those returns:

{ "error": "wallet_ownership_required", "_padding": "..." }

with HTTP 400. Omitting wallet_address is a different case: the request fails JSON extraction before the handler runs and returns HTTP 422 with a plain-text body, so match on the status code rather than the error field. The signed message format is Entros-ATTEST:{wallet_address}:{timestamp_secs}. The walletless attestation path was removed. Wallets that cannot sign should not call /attest at all.

Response padding

Most executor JSON responses include an opaque _padding field of repeated ASCII characters that pads the body toward a 256-byte floor. Bodies already over that floor ship an empty _padding, and /status is not padded at all. Treat it as forward-compatible noise: ignore it on parse, do not surface it to users, do not assert on its content or length. It exists to defeat byte-length analysis of response classes.

Where to look next

On this page