Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ lcov.info
coverage/

**/test_snapshots/

# Compiled WASM artifacts and testnet state
examples/rwa-deploy/wasm/
examples/rwa-deploy/testnet-addresses.json
93 changes: 90 additions & 3 deletions Cargo.lock

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

18 changes: 17 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,23 @@ members = [
"examples/nft-sequential-minting",
"examples/ownable",
"examples/pausable",
"examples/rwa/*",
"examples/rwa/claim-issuer",
"examples/rwa/claim-topics-and-issuers",
"examples/rwa/identity",
"examples/rwa/identity-registry",
"examples/rwa/identity-verifier",
"examples/rwa/token",
"examples/rwa-country-allow",
"examples/rwa-country-restrict",
"examples/rwa-max-balance",
"examples/rwa-supply-limit",
"examples/rwa-time-transfers-limits",
"examples/rwa-transfer-restrict",
"examples/rwa-initial-lockup-period",
"examples/rwa-deploy/irs",
"examples/rwa-deploy/verifier",
"examples/rwa-deploy/compliance",
"examples/rwa-deploy/token",
"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
Loading
Loading