Soroban · Stellar

On-chain compliance primitives for Stellar

Allowlist/denylist token gating and jurisdiction checks for RWA and stablecoin issuers — a small set of standardized, auditable, independently testable Soroban contracts, so issuers stop hand-rolling compliance logic inside their own token contracts.

The problem

RWA and stablecoin issuers building on Stellar/Soroban all end up needing the same handful of compliance gates — allowlists, denylists, jurisdiction restrictions — before they can legally let a token move. Today every issuer hand-rolls this logic inside their own token contract, which means more audit surface, more chances for a subtle bug to become a compliance incident, and no shared, reviewed reference to build on.

Contract explorer

Each primitive does one job, and each is independently deployable and testable.

allowlist-token

A token wrapper that only permits transfers between two addresses that are both present on an on-chain allowlist. Admin-managed; forwards cleared transfers to an underlying SEP-41 token contract.

admin-managed wraps SEP-41
add_to_allowlist(admin, address, expiration_ledger) is_allowed(address) -> bool transfer(from, to, amount) -> bool Interface spec →

denylist-gate

A standalone denylist other contracts call via cross-contract invocation before executing a transfer. Not a token itself — meant to be composed into one.

composable cross-contract
add_to_denylist(admin, address) check(address) -> bool Interface spec →

jurisdiction-flag

Attaches an issuer-controlled jurisdiction code (e.g. an ISO country code) to an address, with a helper other contracts can call to check an address against a permitted-jurisdictions list.

issuer-controlled ISO codes
set_jurisdiction(issuer, address, code) is_permitted_jurisdiction(address, allowed_codes) -> bool Interface spec →

audit-log

Records compliance decisions and operational events for auditability.

auditable
record_event(admin, subject, decision)Interface spec →

circuit-breaker

Provides an emergency stop when automated or governance-defined conditions are met.

emergency control
trip(admin)is_tripped() -> boolInterface spec →

compliance-aggregator

Combines multiple compliance checks into one composable decision point.

composition
check(address) -> boolInterface spec →

multisig-admin

Protects administrative operations with multi-signature approval.

governance
approve(operation)execute(operation)Interface spec →

pausable

Exposes a controlled pause state for contracts that need operational safety controls.

emergency control
pause(admin)unpause(admin)Interface spec →

policy-engine

Evaluates configurable policy rules before an application permits a transfer.

rules
evaluate(subject, action) -> boolInterface spec →

Comparison table

A side-by-side view of the nine contracts' key properties to help you pick the right primitives for your use case.

Contract Composable Standalone token Upgradeable Pausable Compliance-officer role Audit-log hook
allowlist-token — ✓ — ✓ ✓ —
denylist-gate ✓ — — — ✓ —
jurisdiction-flag ✓ — ✓ — ✓ —
audit-log ✓ — — — ✓ ✓
circuit-breaker ✓ — — ✓ ✓ —
compliance-aggregator ✓ — — — ✓ —
multisig-admin ✓ — — — ✓ —
pausable ✓ — — ✓ ✓ —
policy-engine ✓ — — — ✓ ✓

✓ = supported  |  — = not applicable / not present

Building blocks, not a product

These contracts are meant to be composed into a real token or RWA wrapper via cross-contract calls — for example, a token's transfer calling denylist-gate.check(address) before moving funds, or checking jurisdiction-flag.is_permitted_jurisdiction(...) before permitting a transaction. allowlist-token is the one exception: a thin wrapper you deploy in front of an existing SEP-41 token, which still delegates the real transfer to the underlying token contract.

issuer's token contract
  transfer(from, to, amount)
    → denylist-gate.check(from)         (cross-contract call)
    → denylist-gate.check(to)
    → jurisdiction-flag.is_permitted_jurisdiction(to, allowed)
    → proceed with transfer, or reject

See /examples/denylist-gate-consumer for a minimal reference token contract showing this pattern end to end.

Live contract lookup

Paste a testnet contract ID and an address to query its current compliance state directly from your browser — no backend required. Uses the Soroban RPC endpoint for Stellar Testnet.

Queries are read-only simulateTransaction calls to Soroban RPC on Stellar Testnet. No wallet required.

Quick start

# Clone
git clone https://github.com/stellar-compliance-kit/compliance-primitives.git
cd compliance-primitives

# Run the full test suite (all three contracts + example)
cargo test --workspace

# Lint
cargo clippy --workspace --all-targets -- -D warnings

# Build a contract to wasm (requires the wasm32v1-none target:
# rustup target add wasm32v1-none)
stellar contract build

# Deploy a built contract to testnet, e.g. denylist-gate
stellar contract deploy \
  --wasm target/wasm32v1-none/release/denylist_gate.wasm \
  --source <your-testnet-identity> \
  --network testnet