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

Stele Gate · for Solana applications

Investors can't deposit until they've accepted your current terms.

Stele Gate turns versioned document acceptance into a condition your Solana program can enforce. Your frontend checkbox can be skipped. Your program requirement can't.

Architecture

Frontend SDK = UX. On-chain check = enforcement.

The SDK makes acceptance smooth. The requirement itself lives in your program, where a custom client cannot route around it. Stele is never in your execution path.

Where Stele Gate sitsIllustration
UserDeposit 5,000 USDC
Your appfrontend
Stele SDKstele.gate()
AccessPasson Solana
Your programrequire_current()
Actiondeposit executes

Frontend SDK = UX

Decides when to show the modal, records the acceptance, continues the action. It can be skipped — and that is fine.

On-chain check = enforcement

Your program checks the AccessPass before it acts. A client that skips the SDK is rejected here.

1/6The investor starts a deposit in your app.

One call

require_current() checks everything.

Add the AccessPass and the policy to your protected instruction and make one call. You don't re-implement a list of checks:

  • The policy is a Stele policy at its canonical address
  • The investor signed the transaction
  • The AccessPass is a Stele account at its canonical address — not a look-alike
  • It belongs to this wallet, this document and this organization
  • It names the required version or a later one (same fingerprint at the required version)
rust
stele_gate::require_current(
    &ctx.accounts.access_pass, // the investor's AccessPass
    &ctx.accounts.policy,      // the Stele policy your program pins
    &ctx.accounts.investor,    // a Signer
)?;

Pin the policy (store it, or use a constant) — otherwise a caller could present a policy of their own. The demo vault does it with #[account(address = vault.policy)].

How it works

Protect. Check. Accept. Continue. Enforce.

Stele Gate in five stepsIllustration

Link the action to a Stele policy

Your program pins the policy and makes one call before it acts. The policy says which version of your terms is required.

Policy pinned

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

1/5Protect: Link the action to a Stele policy.

The demo

Atlas Real Estate Vault

A small permissioned investment vault — enough to show Gate, nothing more. No real tokens move.

  1. Deposit 5,000 USDCThe investor connects a wallet and taps Deposit in the Atlas Real Estate Vault.
  2. No AccessPass → the modal opensInvestor Offering Terms v1, hash-verified against Solana. “Accept & Deposit”.
  3. Acceptance recorded, deposit succeedsThe acceptance creates the AccessPass; the vault's require_current() passes.
  4. The fund publishes v2 and requires itThe investor's next deposit is rejected on-chain: their pass names v1.
  5. Accept v2, deposit succeedsThe same pass now names v2. The v1 acceptance stays verifiable forever.
  6. A direct CLI call failsNo frontend, no SDK — deposit() without a current pass is rejected by the program.

The whole integration in the vault program

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

Stele never holds the investor's funds and is not in the deposit's execution path: the vault program reads the AccessPass and the policy itself.

Run it on Devnet

Versioning

Publish v2 and old access no longer satisfies the requirement.

The policy names the required version. The organization raises it when the change matters — a typo fix doesn't have to lock every investor out.

When the policy changesIllustration

Published versions

Offering Terms v1Required

fingerprint 72d7 1ed9 83fa…

The wallet's AccessPass

AccessPass

accepted: Offering Terms v1

Valid

Protected deposit

deposit(5,000 USDC)

Waiting

—

The evidence for v1 remains. Access now requires v2.

1/6The investor accepted Offering Terms v1. Their AccessPass names v1 — and the policy requires v1.

Bypass resistance

A direct call without a current pass is rejected on-chain.

Can the requirement be skipped?Illustration

Without a program-level gate

Normal frontendAllowed
User
Checkboxfrontend
Program
Script / custom client
Script / CLI
Checkboxskipped
Programexecutes anyway

With Stele Gate

Script / custom client
Script / CLI
Vault program
require_current()AccessPass?
Investor who accepted the current terms
User
require_current()AccessPass valid
Vault programdeposit executes

The frontend is optional. The program check is not.

1/4Without program enforcement, the terms checkbox lives in the frontend. A normal investor passes through it.

Tested against the real program binaries: no pass, an older version, someone else's pass, a pass for another document or organization, another policy, an unsigned investor, look-alike and forged accounts, and a forged policy all fail.

Developer experience

Wrap the action you already have.

These samples are the real API used by the vault demo. Acceptance and action are two transactions; the investor sees one “Accept & Deposit” step.

ts
import { createSteleGate } from "@stelehq/sdk";

const stele = createSteleGate({
  apiBaseUrl: "https://stele.site/api",
  // Any Wallet Standard wallet: the key that signs the deposit.
  wallet: {
    address: wallet.publicKey.toBase58(),
    signMessage: (message) => wallet.signMessage(message),
  },
});

// Shows the Stele modal only if the investor's AccessPass is missing or
// outdated, records the acceptance, then sends the deposit.
const signature = await stele.gate({
  policy: OFFERING_TERMS_POLICY, // the policy address your program pins
  action: () => sendDepositTransaction(5_000_000_000n),
  acceptLabel: "Accept & Deposit",
});

Why Solana

The check has to run where the action executes.

A frontend can be bypassed: anyone can sign and send a transaction to your program from their own client.

A Solana program evaluates its own accounts, whatever the interface. The AccessPass is an account only the Stele program can write — and only after verifying the investor's signature over the exact terms — so your program can trust it in the same transaction as the deposit. No oracle, no off-chain lookup.

What it costs (measured)

First acceptance (creates the pass)
10,000 lamports fee (Stele, or the organization if it sponsors fees) + ≈0.0025 SOL pass rent paid by Stele
Later acceptances (new versions)
10,000 lamports; the pass is updated in place
The demo deposit, including require_current()
9,406 compute units
For the investor
Nothing: no fee for accepting

Architecture

Stele Core, Stele Gate, your program.

Client

@stelehq/sdk

  • Stele SDKgate(): read pass, modal, accept, continue
  • Embedded modalhash-verified text, wallet signature

Stele Core

programs/stele

  • Document registryorganizations, documents
  • Version registryfingerprints, hash chain
  • Acceptancesignature verified on-chain
  • Receipt & verificationopen-source verifier

Stele Gate

programs/stele · crates/stele-gate

  • Policyrequired version of a document, set by the organization
  • AccessPassper policy and wallet
  • Gate instructionscreate_policy, set_policy_requirement, record_gated_acceptance
  • require_current()one call in your program (stele-gate crate)

Third party

your program

  • Solana programpins the policy; require_current() before every protected action
Acceptances flow from the client into Stele Core; the Gate layer turns them into AccessPasses that your program checks before executing.

Proof vs Gate

Gate includes Proof.

Stele Proof compared with Stele Gate
FeatureProofGate
Exact document versionsIncludedIncluded
User acceptanceIncludedIncluded
ReceiptsIncludedIncluded
Independent verificationIncludedIncluded
PasskeysIncludedfor evidence; access needs the wallet
Wallet signaturesIncludedIncluded
AccessPass—Not includedIncluded
Program enforcement—Not includedIncluded
Protected actions—Not includedIncluded
Solana developer SDKoptionalIncluded
Direct bypass resistance—Not includedIncluded

Honest limits

What Gate does and doesn't do.

  • Gate enforces acceptance of a document version — not KYC, jurisdiction, age or accreditation (future integrations).
  • An AccessPass binds the wallet that signed; passkey acceptances create Proof evidence, not passes.
  • The SDK is user experience; protection exists only where your program calls require_current().
  • Accepting and acting are two transactions (not atomic).
  • Passes are public accounts: anyone can see that a wallet accepted a version.
  • Stele's program is upgradeable today (moving to a multisig); it runs on Devnet, not Mainnet yet.

Watch a direct deposit fail on Devnet.

The live demo runs the real programs: every rejection is a transaction you can open.