On-chain compliance primitives for Stellar — allowlist/denylist token gating and jurisdiction checks for RWA and stablecoin issuers, built on Soroban.
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. compliance-primitives
exists to give the ecosystem a small set of standardized, auditable,
independently testable contracts for exactly these checks.
This is a Cargo workspace with three focused Soroban contracts, each doing one job:
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.denylist-gate— a standalone denylist other contracts call via cross-contract invocation (check(address)) before executing a transfer. Not a token itself — meant to be composed.jurisdiction-flag— attaches an issuer-controlled jurisdiction code (e.g. an ISO country code) to an address, with a helper (is_permitted_jurisdiction) other contracts can call to check an address against a permitted-jurisdictions list.
/examples/denylist-gate-consumer is a
minimal reference token contract showing the cross-contract calling pattern
for denylist-gate — worth reading before wiring these into your own
contract.
# 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 testnetAlternatively, use scripts/deploy-testnet.sh to
build all contracts and deploy one to testnet in a single step:
STELLAR_SOURCE=<your-testnet-identity> ./scripts/deploy-testnet.sh denylist-gateYou don't need to build or test the whole workspace to work on one
contract. Each crate can be built and tested in isolation with Cargo's
-p <crate> flag, using the crate name from its Cargo.toml
(allowlist-token, denylist-gate, or jurisdiction-flag):
# Test just one contract
cargo test -p allowlist-token
# Lint just one contract
cargo clippy -p denylist-gate --all-targets -- -D warnings
# Build just one contract to wasm
cargo build -p jurisdiction-flag --target wasm32v1-none --releaseThese contracts are building blocks, not standalone products. They're
meant to be composed into a real token or RWA wrapper contract via
cross-contract calls — for example, a token's transfer function calling
denylist-gate.check(address) before moving funds, or checking
jurisdiction-flag.is_permitted_jurisdiction(address, allowed_codes)
before permitting a transaction. See
/examples/denylist-gate-consumer for
a worked example of that pattern. allowlist-token is the one exception —
it's a thin wrapper you can deploy in front of an existing SEP-41 token —
but even it delegates the real transfer to the underlying token contract
rather than reimplementing token logic itself.
See CONTRIBUTING.md for the fork → branch → PR flow.
This repo is part of the Drips Wave Stellar Program, and issues are
labeled by complexity (complexity: trivial, complexity: medium,
complexity: high) so you can find something that matches how deep you
want to go — issues tagged good first issue are a good place to start.