Skip to content
Merged
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
7 changes: 7 additions & 0 deletions templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ To be valid, a template must contain:
- `src/` directory - Source code
- `src/lib.rs` - Main contract file

## Example Templates

Built-in example templates are provided under `templates/examples/`:

- `simple-counter`: A basic smart contract demonstrating storage usage by incrementing, getting, and resetting a counter.
- `token-allowlist`: A smart contract for managing an allowlist of approved addresses, controlled by an administrator.

## Template Placeholders

Templates can use placeholders that will be replaced during scaffolding:
Expand Down
23 changes: 23 additions & 0 deletions templates/examples/token-allowlist/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "{{PROJECT_NAME}}"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
soroban-sdk = "21.0.0"

[dev-dependencies]
soroban-sdk = { version = "21.0.0", features = ["testutils"] }

[profile.release]
opt-level = "z"
overflow-checks = true
debug = 0
strip = "symbols"
debug-assertions = false
panic = "abort"
codegen-units = 1
lto = true
56 changes: 56 additions & 0 deletions templates/examples/token-allowlist/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# {{PROJECT_NAME}}

A token allowlist smart contract for Soroban. It enables managing a list of approved addresses that are permitted to perform actions (like transfer/receive tokens, or participate in a DAO).

## Features

- Initialize contract with an admin
- Check if an address is allowlisted
- Add addresses to the allowlist (admin only)
- Remove addresses from the allowlist (admin only)
- Update admin address (admin only)

## Build

```bash
stellar contract build
```

## Test

```bash
cargo test
```

## Deploy

```bash
starforge deploy \
--wasm target/wasm32-unknown-unknown/release/{{PROJECT_NAME_SNAKE}}.wasm \
--network testnet
```

## Usage

```bash
# Initialize the contract
stellar contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- initialize \
--admin <ADMIN_ADDRESS>

# Add user to allowlist
stellar contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- add \
--address <USER_ADDRESS>

# Check allowlist status
stellar contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- is_allowed \
--address <USER_ADDRESS>
```
89 changes: 89 additions & 0 deletions templates/examples/token-allowlist/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
Admin,
Allowed(Address),
}

#[contract]
pub struct {{PROJECT_NAME_PASCAL}};

#[contractimpl]
impl {{PROJECT_NAME_PASCAL}} {
/// Initialize the contract with an admin address
pub fn initialize(env: Env, admin: Address) {
if env.storage().instance().has(&DataKey::Admin) {
panic!("already initialized");
}
env.storage().instance().set(&DataKey::Admin, &admin);
}

/// Check if an address is in the allowlist
pub fn is_allowed(env: Env, address: Address) -> bool {
env.storage().persistent().get(&DataKey::Allowed(address)).unwrap_or(false)
}

/// Add an address to the allowlist (admin only)
pub fn add(env: Env, address: Address) {
let admin: Address = env.storage().instance().get(&DataKey::Admin).expect("not initialized");
admin.require_auth();
env.storage().persistent().set(&DataKey::Allowed(address), &true);
}

/// Remove an address from the allowlist (admin only)
pub fn remove(env: Env, address: Address) {
let admin: Address = env.storage().instance().get(&DataKey::Admin).expect("not initialized");
admin.require_auth();
env.storage().persistent().set(&DataKey::Allowed(address), &false);
}

/// Update the admin address (admin only)
pub fn set_admin(env: Env, new_admin: Address) {
let admin: Address = env.storage().instance().get(&DataKey::Admin).expect("not initialized");
admin.require_auth();
env.storage().instance().set(&DataKey::Admin, &new_admin);
}
}

#[cfg(test)]
mod test {
use super::*;
use soroban_sdk::testutils::Address as _;

#[test]
fn test_allowlist_flow() {
let env = Env::default();
env.mock_all_auths();

let contract_id = env.register_contract(None, {{PROJECT_NAME_PASCAL}});
let client = {{PROJECT_NAME_PASCAL}}Client::new(&env, &contract_id);

let admin = Address::generate(&env);
let user1 = Address::generate(&env);
let user2 = Address::generate(&env);

// Initialize contract
client.initialize(&admin);

// Should not be allowed initially
assert!(!client.is_allowed(&user1));
assert!(!client.is_allowed(&user2));

// Add user1 to allowlist
client.add(&user1);
assert!(client.is_allowed(&user1));
assert!(!client.is_allowed(&user2));

// Add user2 to allowlist
client.add(&user2);
assert!(client.is_allowed(&user2));

// Remove user1
client.remove(&user1);
assert!(!client.is_allowed(&user1));
assert!(client.is_allowed(&user2));
}
}
Loading