Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ members = [
"examples/ownable",
"examples/pausable",
"examples/rwa/*",
"examples/rwa-country-allow",
"examples/rwa-country-restrict",
"examples/sac-admin-generic",
"examples/sac-admin-wrapper",
"examples/multisig-smart-account/*",
Expand Down
15 changes: 15 additions & 0 deletions examples/rwa-country-allow/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "rwa-country-allow"
edition.workspace = true
license.workspace = true
repository.workspace = true
publish = false
version.workspace = true

[lib]
crate-type = ["cdylib", "rlib"]
doctest = false

[dependencies]
soroban-sdk = { workspace = true }
stellar-tokens = { workspace = true }
50 changes: 50 additions & 0 deletions examples/rwa-country-allow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Country Allow Module

Concrete deployable example of the `CountryAllow` compliance module for Stellar
RWA tokens.

## What it enforces

This module allows tokens to be minted or transferred only to recipients whose
registered identity has at least one country code that appears in the module's
per-token allowlist.

The country lookup is performed through the Identity Registry Storage (IRS), so
the module must be configured with an IRS contract for each token it serves.

## Authorization model

This example uses the bootstrap-admin pattern introduced in this port:

- The constructor stores a one-time `admin`
- Before `set_compliance_address`, privileged configuration calls require that
admin's auth
- After `set_compliance_address`, the same configuration calls require auth
from the bound Compliance contract
- `set_compliance_address` itself remains a one-time admin action

This lets the module be configured from the CLI before it is locked to the
Compliance contract.

## Main entrypoints

- `__constructor(admin)` initializes the bootstrap admin
- `set_identity_registry_storage(token, irs)` stores the IRS address for a
token
- `add_allowed_country(token, country)` adds an ISO 3166-1 numeric code to the
allowlist
- `remove_allowed_country(token, country)` removes a country code
- `batch_allow_countries(token, countries)` updates multiple entries
- `batch_disallow_countries(token, countries)` removes multiple entries
- `is_country_allowed(token, country)` reads the current allowlist state
- `set_compliance_address(compliance)` performs the one-time handoff to the
Compliance contract

## Notes

- Storage is token-scoped, so one deployed module can be reused across many
tokens
- This module validates on the compliance read hooks used for transfers and
mints; it does not require extra state-tracking hooks
- In the deploy example, the module is configured before binding and then wired
to the `CanTransfer` and `CanCreate` hooks
88 changes: 88 additions & 0 deletions examples/rwa-country-allow/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#![no_std]

use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, Vec};
use stellar_tokens::rwa::compliance::modules::{
country_allow::{
storage::{is_country_allowed, remove_country_allowed, set_country_allowed},
CountryAllow, CountryAllowed, CountryUnallowed,
},
storage::{set_compliance_address, set_irs_address, ComplianceModuleStorageKey},
};

#[contracttype]
enum DataKey {
Admin,
}

#[contract]
pub struct CountryAllowContract;

fn set_admin(e: &Env, admin: &Address) {
e.storage().instance().set(&DataKey::Admin, admin);
}

fn get_admin(e: &Env) -> Address {
e.storage().instance().get(&DataKey::Admin).expect("admin must be set")
}

fn require_module_admin_or_compliance_auth(e: &Env) {
if let Some(compliance) =
e.storage().instance().get::<_, Address>(&ComplianceModuleStorageKey::Compliance)
{
compliance.require_auth();
} else {
get_admin(e).require_auth();
}
}

#[contractimpl]
impl CountryAllowContract {
pub fn __constructor(e: &Env, admin: Address) {
set_admin(e, &admin);
}
}

#[contractimpl(contracttrait)]
impl CountryAllow for CountryAllowContract {
fn set_identity_registry_storage(e: &Env, token: Address, irs: Address) {
require_module_admin_or_compliance_auth(e);
set_irs_address(e, &token, &irs);
}

fn add_allowed_country(e: &Env, token: Address, country: u32) {
require_module_admin_or_compliance_auth(e);
set_country_allowed(e, &token, country);
CountryAllowed { token, country }.publish(e);
}

fn remove_allowed_country(e: &Env, token: Address, country: u32) {
require_module_admin_or_compliance_auth(e);
remove_country_allowed(e, &token, country);
CountryUnallowed { token, country }.publish(e);
}

fn batch_allow_countries(e: &Env, token: Address, countries: Vec<u32>) {
require_module_admin_or_compliance_auth(e);
for country in countries.iter() {
set_country_allowed(e, &token, country);
CountryAllowed { token: token.clone(), country }.publish(e);
}
}

fn batch_disallow_countries(e: &Env, token: Address, countries: Vec<u32>) {
require_module_admin_or_compliance_auth(e);
for country in countries.iter() {
remove_country_allowed(e, &token, country);
CountryUnallowed { token: token.clone(), country }.publish(e);
}
}

fn is_country_allowed(e: &Env, token: Address, country: u32) -> bool {
is_country_allowed(e, &token, country)
}

fn set_compliance_address(e: &Env, compliance: Address) {
get_admin(e).require_auth();
set_compliance_address(e, &compliance);
}
}
15 changes: 15 additions & 0 deletions examples/rwa-country-restrict/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "rwa-country-restrict"
edition.workspace = true
license.workspace = true
repository.workspace = true
publish = false
version.workspace = true

[lib]
crate-type = ["cdylib", "rlib"]
doctest = false

[dependencies]
soroban-sdk = { workspace = true }
stellar-tokens = { workspace = true }
50 changes: 50 additions & 0 deletions examples/rwa-country-restrict/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Country Restrict Module

Concrete deployable example of the `CountryRestrict` compliance module for
Stellar RWA tokens.

## What it enforces

This module blocks tokens from being minted or transferred to recipients whose
registered identity has a country code that appears in the module's per-token
restriction list.

The country lookup is performed through the Identity Registry Storage (IRS), so
the module must be configured with an IRS contract for each token it serves.

## Authorization model

This example uses the bootstrap-admin pattern introduced in this port:

- The constructor stores a one-time `admin`
- Before `set_compliance_address`, privileged configuration calls require that
admin's auth
- After `set_compliance_address`, the same configuration calls require auth
from the bound Compliance contract
- `set_compliance_address` itself remains a one-time admin action

This lets the module be configured from the CLI before it is locked to the
Compliance contract.

## Main entrypoints

- `__constructor(admin)` initializes the bootstrap admin
- `set_identity_registry_storage(token, irs)` stores the IRS address for a
token
- `add_country_restriction(token, country)` adds an ISO 3166-1 numeric code to
the restriction list
- `remove_country_restriction(token, country)` removes a country code
- `batch_restrict_countries(token, countries)` updates multiple entries
- `batch_unrestrict_countries(token, countries)` removes multiple entries
- `is_country_restricted(token, country)` reads the current restriction state
- `set_compliance_address(compliance)` performs the one-time handoff to the
Compliance contract

## Notes

- Storage is token-scoped, so one deployed module can be reused across many
tokens
- This module validates on the compliance read hooks used for transfers and
mints; it does not require extra state-tracking hooks
- In the deploy example, the module is configured before binding and then wired
to the `CanTransfer` and `CanCreate` hooks
88 changes: 88 additions & 0 deletions examples/rwa-country-restrict/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#![no_std]

use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, Vec};
use stellar_tokens::rwa::compliance::modules::{
country_restrict::{
storage::{is_country_restricted, remove_country_restricted, set_country_restricted},
CountryRestrict, CountryRestricted, CountryUnrestricted,
},
storage::{set_compliance_address, set_irs_address, ComplianceModuleStorageKey},
};

#[contracttype]
enum DataKey {
Admin,
}

#[contract]
pub struct CountryRestrictContract;

fn set_admin(e: &Env, admin: &Address) {
e.storage().instance().set(&DataKey::Admin, admin);
}

fn get_admin(e: &Env) -> Address {
e.storage().instance().get(&DataKey::Admin).expect("admin must be set")
}

fn require_module_admin_or_compliance_auth(e: &Env) {
if let Some(compliance) =
e.storage().instance().get::<_, Address>(&ComplianceModuleStorageKey::Compliance)
{
compliance.require_auth();
} else {
get_admin(e).require_auth();
}
}

#[contractimpl]
impl CountryRestrictContract {
pub fn __constructor(e: &Env, admin: Address) {
set_admin(e, &admin);
}
}

#[contractimpl(contracttrait)]
impl CountryRestrict for CountryRestrictContract {
fn set_identity_registry_storage(e: &Env, token: Address, irs: Address) {
require_module_admin_or_compliance_auth(e);
set_irs_address(e, &token, &irs);
}

fn add_country_restriction(e: &Env, token: Address, country: u32) {
require_module_admin_or_compliance_auth(e);
set_country_restricted(e, &token, country);
CountryRestricted { token, country }.publish(e);
}

fn remove_country_restriction(e: &Env, token: Address, country: u32) {
require_module_admin_or_compliance_auth(e);
remove_country_restricted(e, &token, country);
CountryUnrestricted { token, country }.publish(e);
}

fn batch_restrict_countries(e: &Env, token: Address, countries: Vec<u32>) {
require_module_admin_or_compliance_auth(e);
for country in countries.iter() {
set_country_restricted(e, &token, country);
CountryRestricted { token: token.clone(), country }.publish(e);
}
}

fn batch_unrestrict_countries(e: &Env, token: Address, countries: Vec<u32>) {
require_module_admin_or_compliance_auth(e);
for country in countries.iter() {
remove_country_restricted(e, &token, country);
CountryUnrestricted { token: token.clone(), country }.publish(e);
}
}

fn is_country_restricted(e: &Env, token: Address, country: u32) -> bool {
is_country_restricted(e, &token, country)
}

fn set_compliance_address(e: &Env, compliance: Address) {
get_admin(e).require_auth();
set_compliance_address(e, &compliance);
}
}
Loading
Loading