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

SDK reference

@stelehq/sdk for browsers, React and Node.js, and @stelehq/verifier. Step-by-step guides: Proof · Gate.

Setup

npm install @stelehq/sdk. In the browser the SDK talks to a Stele app; every answer it uses is checked against Solana (program, network, fingerprints).
ts
import { SteleClient } from "@stelehq/sdk";

new SteleClient(options)class

ts
new SteleClient({ apiBaseUrl: string; fetch?: typeof fetch })
The connection to a Stele app. Pass it to the functions below, or let them create one from apiBaseUrl.
ts
const client = new SteleClient({ apiBaseUrl: "https://stele.site/api" });

client.getNetwork()method

ts
getNetwork(): Promise<NetworkInfo>
// { cluster, chainId, label, walletChain, programId, relayer, publicRpcUrls, … }
The Solana network and Stele program this app uses.

SteleApiErrorclass

ts
class SteleApiError extends Error { status: number; code: string }
Thrown for API errors, with the API's code — for example already_accepted (409) when this signer already accepted this version.

Stele Gate

Run a protected action only after the wallet accepted the current terms. Your program enforces the same rule on-chain — see On-chain.
ts
import { createSteleGate } from "@stelehq/sdk";

createSteleGate(options)function

ts
createSteleGate({
  apiBaseUrl?: string;              // or client
  client?: SteleClient;
  wallet?: MessageSigner | (() => MessageSigner | Promise<MessageSigner>);
  ui?: "modal" | "none";            // default "modal"
  theme?: "auto" | "dark" | "light";
  requestDomain?: string;           // default location.hostname
  onStatus?: (status: GateStatus) => void;
}): SteleGate
Creates the gate. wallet is any object with address and signMessage (Wallet Standard wallets, Phantom, a passkey signer).
ts
const stele = createSteleGate({
  apiBaseUrl: "https://stele.site/api",
  wallet: {
    address: wallet.publicKey.toBase58(),
    signMessage: (message) => wallet.signMessage(message),
  },
});

stele.gate(request)method

ts
gate<T>({
  policy: string;                   // the policy address
  action: () => T | Promise<T>;     // your transaction
  acceptLabel?: string;             // default "Accept & Continue"
  wallet?: MessageSigner;
}): Promise<T>
If the wallet's AccessPass is current, runs action right away. Otherwise shows the exact required text, records the acceptance (free for the user), waits until the pass is on Solana, then runs it. Closing the window throws GateCancelledError and nothing runs.
ts
await stele.gate({
  policy: "YOUR_POLICY_ADDRESS",
  action: () => sendDepositTransaction(),
  acceptLabel: "Accept & Deposit",
});

stele.check(policy, wallet)method

ts
check(policy: string, holder: string): Promise<AccessCheck>
// { status: "VALID" | "MISSING" | "OUTDATED", requiredVersion, accessPass, pass, document }
The wallet's status without prompting — for your interface (badges, disabled buttons). The same data is at GET /v1/public/gate/access.

GateCancelledError · AccessRequiredError · StaleTermsErrorerrors

GateCancelledError: the user closed the window. AccessRequiredError: with ui: "none", acceptance is needed — show your own UI. StaleTermsError: the document changed while open; the SDK loads the new version.

Accept a document (Proof)

ts
import { loadVerifiedDocument, acceptDocument } from "@stelehq/sdk";

loadVerifiedDocument(options)function

ts
loadVerifiedDocument({
  client: SteleClient;
  organization: string;             // address or verified domain, e.g. "acme.com"
  document: string;                 // slug, e.g. "terms-of-service"
  loadPrevious?: boolean;           // also load the previous version (for "what changed")
}): Promise<VerifiedDocument>
Loads the current version and checks its text against the on-chain fingerprint before you show it. Throws IntegrityError if anything does not match.

acceptDocument(options)function

ts
acceptDocument({
  client: SteleClient;
  verified: VerifiedDocument;
  signer: MessageSigner;            // wallet or passkey
  requestDomain: string;            // location.hostname
  sessionToken?: string;            // from your server, links the acceptance to your user
  onStatus?: (s: "requesting" | "awaiting-signature" | "recording" | "recorded") => void;
}): Promise<AcceptanceResult>
// { acceptanceId, transactionSignature, message, receiptPath, evidenceMode, … }
The user signs the exact message naming this version; Stele records it on Solana and pays the fee. Each account accepts a version once — a repeat is refused before signing (already_accepted).
ts
const verified = await loadVerifiedDocument({ client, organization: "acme.com", document: "terms-of-service" });
const result = await acceptDocument({ client, verified, signer, requestDomain: location.hostname });

Widget & React

A ready acceptance step on your page: shows the terms, lets the user sign, records it.

<script> + data attributesHTML

html
<div data-stele-organization="acme.com" data-stele-document="terms-of-service"></div>
<script src="https://stele.site/sdk/v1/stele.js" defer></script>
No build step. The script also exposes window.Stele: mount, mountProof, createGate, client().

mount(target, options)function

ts
mount(target: string | Element, {
  apiBaseUrl: string;
  organization: string;
  document: string;
  onAccepted?: (result: AcceptanceResult) => void;
}): { destroy(): void }
The same widget from JavaScript.

<TermsAcceptance />React

tsx
import { TermsAcceptance } from "@stelehq/sdk/react";

<TermsAcceptance apiBaseUrl="https://stele.site/api" organization="acme.com" document="terms-of-service"
  onAccepted={(result) => …} />
The widget as a React component.

mountProof(target, options)function

ts
mountProof(target, { apiBaseUrl, requestId, token, onConfirmed? }): { destroy(): void }
A confirmation widget for a proof request (purchase, refund, consent).

Wallets & passkeys

Signers are objects with address and signMessage. These helpers make them.

watchWallets(chain, onChange)function

ts
watchWallets(chain: string, onChange: (wallets: AvailableWallet[]) => void): () => void
Lists installed Wallet Standard wallets (Phantom, Solflare, Backpack…) for a chain, e.g. solana:devnet. Returns an unsubscribe function.

connectWallet(wallet, chain)function

ts
connectWallet(wallet: AvailableWallet, chain: string): Promise<MessageSigner>
Connects a wallet and returns its signer.

identifyPasskeySigner(options)function

ts
identifyPasskeySigner({ client, organization }): Promise<NativePasskeySigner | PasskeySigner>
Signing with Face ID, Touch ID or Windows Hello — no wallet needed. Finds the user's passkey signer for this organization.

passkeySigningSupport() · nativePasskeySupport()functions

Whether this browser can sign with a passkey.

isUserRejection(error)function

ts
isUserRejection(error: unknown): boolean
True when the user declined in the wallet — show a calm message, not an error.

Proofs (purchases, refunds, consents)

ts
import { loadProofRequest, confirmProof } from "@stelehq/sdk";

loadProofRequest(options)function

ts
loadProofRequest({ client, requestId, token }): Promise<VerifiedProofRequest>
Loads a request your server created and checks the statement's hash before showing it.

confirmProof(options)function

ts
confirmProof({ client, request, signer, requestDomain, onStatus? }): Promise<ProofResult>
The customer signs the exact statement; it is recorded on Solana.

Receipts

downloadAcceptanceReceipt(client, acceptanceId)function

Saves the verified receipt (with the exact text) as a JSON file.

Server client & webhooks

Node.js, with an API key from the dashboard. Never ship the key to a browser.
ts
import { SteleServerClient, verifyWebhook } from "@stelehq/sdk/server";

new SteleServerClient(options)class

ts
new SteleServerClient({ apiBaseUrl: string; apiKey: string; fetch?: typeof fetch })
Server-to-server client.
ts
const stele = new SteleServerClient({ apiBaseUrl: "https://stele.site/api", apiKey: process.env.STELE_API_KEY! });

createAcceptanceSession(input)method

ts
createAcceptanceSession({ document?, externalUserRef?, ttlSeconds? }): Promise<{ sessionToken, expiresAt }>
Links the next acceptance to your user ID (kept off-chain). Pass sessionToken to the widget or acceptDocument.

hasAcceptedLatest(externalUserRef, slug)method

ts
hasAcceptedLatest(externalUserRef: string, slug: string): Promise<AcceptanceRecord | null>
Did this user accept the current version? The check before checkout or sign-up.

listAcceptances(query) · getAcceptance(id) · latestVersion(slug)methods

Search acceptances by user, document, version, signer or date; one acceptance; the current version of a document.

createProofRequest(input) · listProofs() · getProofReceipt(id)methods

Ask a customer to confirm a purchase, refund, consent or quote, and read the results.

listSigners(ref?) · revokeSigner(publicKey, reason)methods

Passkey signers linked to your users.

verifyWebhook(secret, signatureHeader, rawBody)function

ts
verifyWebhook(secret: string, signatureHeader: string | undefined, rawBody: string | Buffer): WebhookEvent
Checks the Stele-Signature header over the raw body (and its age); throws WebhookVerificationError otherwise. Ignore event IDs you have already processed.

Verify independently

npm install @stelehq/verifier — checks evidence against Solana without asking Stele.
ts
import { verifyAcceptance, verifyDocumentVersion, verifyProof, chainFromUrls } from "@stelehq/verifier";

verifyAcceptance(input, options)function

ts
verifyAcceptance(
  { receipt } | { transactionSignature } | { acceptanceId },
  { chain: chainFromUrls(["https://api.devnet.solana.com"]) },
): Promise<VerificationReport>   // { verdict: "VALID" | "VALID_WITH_WARNINGS" | "INVALID" | "INCONCLUSIVE", checks }
Re-checks fingerprint, signature, on-chain record and version history.

verifyDocumentVersion · verifyProoffunctions

The same for a published version and for a proof.