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.
No matching contracts or entrypoints.
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-41add_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-contractjurisdiction-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 codesset_jurisdiction(issuer, address, code)
is_permitted_jurisdiction(address, allowed_codes) -> bool
Interface spec →
audit-log
Records compliance decisions and operational events for auditability.
auditablerecord_event(admin, subject, decision)Interface spec →circuit-breaker
Provides an emergency stop when automated or governance-defined conditions are met.
emergency controlcompliance-aggregator
Combines multiple compliance checks into one composable decision point.
compositioncheck(address) -> boolInterface spec →multisig-admin
Protects administrative operations with multi-signature approval.
governancepausable
Exposes a controlled pause state for contracts that need operational safety controls.
emergency controlpolicy-engine
Evaluates configurable policy rules before an application permits a transfer.
rulesevaluate(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