Skip to content
Solana Devnet: test network. Documents and acceptances here are for testing, not production evidence.

On-chain reference

For your Solana program: the stele-gate crate, the two accounts it reads, and every error code. Step by step: Gate guide.

stele-gate (your program)

Add stele-gate = "1" to Cargo.toml (crates.io, needs anchor-lang 1.2+). Your program reads two Stele accounts and decides — no CPI, no Stele server in the transaction. On Solana Playground, copy the check from the Gate guide.

require_currentfn

rust
pub fn require_current(
    access_pass: &AccountInfo,
    policy: &AccountInfo,   // pin it: #[account(address = POLICY)]
    user: &AccountInfo,     // must sign
) -> Result<GrantedAccess, ProgramError>
Succeeds only if user signed and holds an AccessPass naming the policy's required version or a later one. Otherwise the transaction fails with a Gate error (below).
rust
pub const POLICY: Pubkey = pubkey!("YOUR_POLICY_ADDRESS");

pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
    stele_gate::require_current(&ctx.accounts.access_pass, &ctx.accounts.policy, &ctx.accounts.user)?;
    // … the protected action
    Ok(())
}

require_policiesfn

rust
pub fn require_policies(
    required: &[Pubkey],        // YOUR pinned list
    accounts: &[AccountInfo],   // [policy, access_pass, …] — e.g. ctx.remaining_accounts
    user: &AccountInfo,
) -> Result<Vec<GrantedAccess>, ProgramError>
Several documents for one action, any number. Fails if a required policy is missing from the accounts or not satisfied.

require_all_currentfn

rust
pub fn require_all_current(
    requirements: &[(&AccountInfo, &AccountInfo)],   // (access_pass, policy)
    user: &AccountInfo,
) -> Result<Vec<GrantedAccess>, ProgramError>
The same with named accounts.

access_pass_address · policy_addressfn

rust
pub fn access_pass_address(document: &Pubkey, holder: &Pubkey) -> (Pubkey, u8)
pub fn policy_address(document: &Pubkey) -> (Pubkey, u8)
The PDAs, under the Stele program.

GrantedAccessstruct

rust
pub struct GrantedAccess {
    pub organization: Pubkey, pub document: Pubkey, pub version: Pubkey,
    pub version_number: u32, pub version_hash: [u8; 32], pub required_version: u32,
    pub acceptance_id: [u8; 32],   // the Proof receipt of this acceptance
    pub accepted_at: i64,
}
Which acceptance allowed the action — useful in your own events.

Gate errors

CodeNameMeaning
7600InvalidPolicyThe policy is not a Stele policy at its canonical address
7601UserNotSignerThe user did not sign the transaction
7602AccessPassMissingThe user has not accepted the required terms
7603AccessPassInvalidThe account is not a Stele access pass at its canonical address
7604AccessPassMismatchThe access pass belongs to another wallet or document
7605AccessPassOutdatedThe access pass names an older version than the policy requires
7606FingerprintMismatchThe access pass names the required version with another fingerprint

Accounts

Owned by the Stele program 6sTKscWmUtBQRMpNCiEpurCJxpjx7GK5rzk7e265kpwV (same address on Devnet and Localnet). Offsets include Anchor's 8-byte discriminator.

PolicyPDA

text
seeds: ["policy", document]                 157 bytes
8   organization            40  document
72  required version hash   104 updated_by
152 required version (u32) 156 bump
One per document. Created and changed only by the organization; publishing a version does not change it.

AccessPassPDA

text
seeds: ["access-pass", document, wallet]      233 bytes
8   organization            40  document
72  holder (the wallet)     104 accepted version
136 version hash            168 acceptance_id
200 accepted_at (i64)       224 version number (u32)
232 bump
One per wallet and document. Created only from the wallet's own signature; never moves back to an older version; never closed.

Read them from TypeScript

npm install @stelehq/protocol — no Stele server involved.
ts
import { createRpc, fetchAccountData, decodeGatePolicy, decodeAccessPass, findAccessPassPda, findPolicyPda } from "@stelehq/protocol";

findAccessPassPda(document, wallet)function

ts
findAccessPassPda(document: Address, holder: Address): Promise<[Address, number]>
findPolicyPda(document: Address): Promise<[Address, number]>
The addresses your instruction needs: pass them as access_pass and policy.

decodeGatePolicy · decodeAccessPassfunction

ts
decodeGatePolicy(data: Uint8Array): GatePolicyAccount   // { document, requiredVersion, … }
decodeAccessPass(data: Uint8Array): AccessPassAccount   // { holder, versionNumber, … }
Decode the accounts.
ts
const rpc = createRpc("https://api.devnet.solana.com");
const policy = decodeGatePolicy((await fetchAccountData(rpc, POLICY))!);
const [pass] = await findAccessPassPda(policy.document, wallet);
const data = await fetchAccountData(rpc, pass);
const status = !data ? "MISSING"
  : decodeAccessPass(data).versionNumber >= policy.requiredVersion ? "VALID" : "OUTDATED";

The Stele program

You call these through the SDK, the dashboard or a relayer — integrators rarely build them by hand. Builders for each are in @stelehq/protocol (e.g. recordGatedAcceptanceInstruction). The demo vault program is A6DBoANAavtbG2ARK6PihqzMjhtbcZUbLnTMAWn339uR.
InstructionSigned byWhat it does
create_policyOrganization (owner, admin, publisher)Creates a document's Gate policy: the version a protected action requires.
set_policy_requirementOrganizationRequires another published version (higher or lower). Emits PolicyChanged.
record_gated_acceptanceAny relayer allowed by the shardRecords a wallet's signed acceptance and creates or advances its AccessPass. Refuses a version the pass already names (AccessPassAlreadyCurrent).
record_acceptanceRelayerRecords a signed acceptance (Stele Proof).
record_passkey_acceptanceRelayerRecords a native-passkey (P-256) acceptance.
anchor_acceptance_batchRelayerAnchors many verified acceptances under one Merkle root.
record_proofRelayerRecords a customer-confirmed proof (purchase, refund, consent).
create_organizationCreator walletCreates an organization.
create_documentOrganizationCreates a document.
publish_versionOrganizationPublishes an immutable version with its fingerprint.
create_nonce_shard / set_shard_relayerOrganizationReplay protection; sets who may submit acceptances — your relayer, or the zero key for anyone.
add_member · propose_owner · propose_recoveryOrganizationRoles, ownership and recovery.

Program errors

CodeNameMeaning
6000InvalidProgramDataProgram data account does not belong to this program
6001NotUpgradeAuthoritySigner is not the program upgrade authority
6002InvalidChainIdInvalid CAIP-2 chain id
6003InvalidNetworkLabelInvalid network label
6004UnauthorizedSigner is not authorized for this action
6005ProtocolPausedProtocol is paused
6006ZeroAddressAddress must not be the zero key
6007InvalidOrganizationNameInvalid organization name
6008OrganizationFrozenOrganization is frozen
6009OrganizationLockedByRecoveryOrganization is locked by its recovery authority
6010RecoveryEqualsOwnerRecovery authority must differ from the owner
6011NoPendingOwnerNo ownership transfer is pending
6012NoRecoveryAuthorityOrganization has no recovery authority
6013NoPendingRecoveryChangeNo recovery-authority change is pending
6014TimelockNotElapsedTime lock has not elapsed
6015NoPendingOwnerRecoveryNo owner recovery is pending
6016NotFrozenOrganization is not frozen
6017InvalidRolesInvalid role bits
6018CannotManageAdminOnly the owner can manage administrators
6019MemberRevokedMember has been revoked
6020MemberCompromisedA member reported as compromised cannot be reactivated
6021MemberOrganizationMismatchMember does not belong to this organization
6022InvalidCompromiseTimeInvalid compromise timestamp
6023UnauthorizedAttestorSigner is not the domain attestor
6024InvalidDomainInvalid domain
6025DomainHashMismatchDomain hash does not match the domain
6026OrganizationAlreadyHasDomainOrganization already has a verified domain
6027InvalidDomainExpiryInvalid domain attestation expiry
6028DomainOrganizationMismatchDomain record does not belong to this organization
6029InvalidSlugInvalid document slug
6030InvalidDocumentTypeInvalid document type
6031InvalidLocaleInvalid locale
6032DocumentOrganizationMismatchDocument does not belong to this organization
6033InvalidVersionNumberVersion number must be exactly the next version
6034PreviousVersionHashMismatchPrevious version hash does not match the document head
6035InvalidTitleInvalid title
6036InvalidVersionLabelInvalid version label
6037InvalidStorageUriInvalid storage URI
6038InvalidEffectiveAtEffective date must be upon publication or in the future (within the horizon)
6039VersionHashMismatchComputed version fingerprint does not match the expected fingerprint
6040VersionOrganizationMismatchVersion does not belong to this organization
6041VersionDocumentMismatchVersion does not belong to this document
6042VersionNotCurrentOnly the current version of a document can be accepted
6043ShardOrganizationMismatchNonce shard does not belong to this organization
6044UnauthorizedRelayerSigner is not the shard's relayer
6045NonceOutOfRangeNonce index out of range
6046NonceAlreadyUsedNonce already used
6047InvalidAcceptanceWindowInvalid acceptance validity window
6048AcceptanceExpiredAcceptance message has expired
6049IssuedInFutureAcceptance issued-at time is in the future
6050IssuedBeforePublicationAcceptance was issued before the version was published
6051InvalidTimestampTimestamp out of range
6052CpiNotAllowedThis instruction must be invoked at the top level, not via CPI
6053MissingEd25519InstructionThe preceding instruction must be an Ed25519 signature verification
6054InvalidEd25519InstructionEd25519 instruction is not in canonical single-signature layout
6055MessageMismatchSigned message does not match the reconstructed acceptance message
6056InvalidRequestDomainInvalid request domain in the signed message
6057ArithmeticOverflowArithmetic overflow
6058InvalidSponsorLimitsInvalid fee-sponsorship limits
6059SponsorshipAlreadyEnabledFee sponsorship is already enabled on this nonce shard
6060SponsorshipNotEnabledFee sponsorship is not enabled on this nonce shard
6061SponsorRequiresDesignatedRelayerFee sponsorship requires a nonce shard with a designated relayer
6062WithdrawalBelowRentMinimumA withdrawal must leave the shard rent-exempt
6063InvalidWithdrawalInvalid withdrawal amount or destination
6064InvalidProofTypeUnknown or reserved proof type
6065InvalidStatementHashThe statement hash must not be zero
6066MissingSecp256r1InstructionThe preceding instruction must be a secp256r1 signature verification
6067InvalidSecp256r1Instructionsecp256r1 instruction is not in canonical single-signature layout
6068InvalidSignerKeyThe signer key is not a compressed P-256 public key
6069InvalidClientDataWebAuthn client data is not an assertion for this acceptance
6070OriginMismatchThe browser-attested origin is not the requesting site
6071CrossOriginCeremonyThe passkey ceremony ran in a cross-origin frame
6072InvalidAuthenticatorDataWebAuthn authenticator data is malformed
6073RelyingPartyMismatchThe relying party does not match
6074UserNotVerifiedThe authenticator did not verify the user
6075ClientDataHashMismatchClient data does not match the signed hash
6076EnrollmentMismatchThe passkey enrollment does not belong to this organization or signer
6077EnrollmentNotActiveThe passkey enrollment is not active
6078InvalidAccountCommitmentInvalid account commitment
6079InvalidBatchA batch needs a non-zero root and 1 to 65,536 leaves
6080AccessPassMismatchThe access pass does not belong to this document and holder
6081InvalidPolicyRequirementThe required version must be a published version of the policy's document
6082PolicyMismatchThe policy does not belong to this document
6083AccessPassAlreadyCurrentThis wallet has already accepted this version