> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kontor.network/llms.txt
> Use this file to discover all available pages before exploring further.

# 3. Shared Account

<Note>
  **Full working code**: The complete source code for this example is available in [`example-contracts/shared-account/`](https://github.com/KontorProtocol/Documentation/tree/main/example-contracts/shared-account)
</Note>

This example demonstrates a simple multi-tenant shared account introducing static contract imports, complex storage structs, authorization logic, ID generation, and cross-contract calls.

## WIT Interface

Exports functions for multi-tenant account management with token deposits and withdrawals:

```wit theme={null}
package root:component;

world root {
  include kontor:built-in/built-in;
  use kontor:built-in/context.{view-context, proc-context, signer};
  use kontor:built-in/error.{error};
  use kontor:built-in/foreign.{contract-address};
  use kontor:built-in/numbers.{integer, decimal};

  export init: func(ctx: borrow<proc-context>);

  export open: func(ctx: borrow<proc-context>, token: contract-address, n: integer, other-tenants: list<string>) -> result<string, error>;

  export deposit: func(ctx: borrow<proc-context>, token: contract-address, account-id: string, n: integer) -> result<_, error>;

  export withdraw: func(ctx: borrow<proc-context>, token: contract-address, account-id: string, n: integer) -> result<_, error>;

  export balance: func(ctx: borrow<view-context>, account-id: string) -> option<integer>;

  export token-balance: func(ctx: borrow<view-context>, token: contract-address, holder: string) -> option<integer>;

  export tenants: func(ctx: borrow<view-context>, account-id: string) -> option<list<string>>;
}
```

## Rust Implementation

* The `interface!` macro generates an interface for cross-contract calls to the `token` contract with a dynamic address (passed as a parameter). Use `import!` when the dependency contract has been previously published and the contract address is known at compile time.
* The `StorageRoot` macro, used for the root storage type, and the `Storage` macro, used for nested storage types in the `Account` struct, enable persistent storage.
* `other_tenants` uses `Map<String, bool>` because the storage layer does not currently support list types, and `Map` provides a limited interface with the `keys` method for iteration. Even with a `List` type using a `Map` here could make sense. Instead of `bool` an `enum` or `struct` that defines the their "role" in the account could be written.
* `authorized` Verifies procedure permissions
* `open`: Verifies token balance, generates an ID using `crypto::generate_id()`, sets the account, and transfers tokens to `ctx.contract_signer()`
* `deposit`/`withdraw`: Authorize the caller, verify balances, update storage, and call the token contract following CEI pattern
* `balance`/`tenants`: Query the storage for account details

```rust theme={null}
use stdlib::*;

contract!(name = "shared-account");

interface!(name = "token", path = "../../token/contract/wit");

#[derive(Clone, Default, Storage)]
struct Account {
    pub other_tenants: Map<String, bool>,
    pub balance: Integer,
    pub owner: String,
}

#[derive(Clone, Default, StorageRoot)]
struct SharedAccountStorage {
    pub accounts: Map<String, Account>,
}

fn authorized(signer: &Signer, account: &AccountModel) -> bool {
    account.owner() == signer.to_string()
        || account
            .other_tenants()
            .get(signer.to_string())
            .is_some_and(|b| b)
}

fn insufficient_balance_error() -> Error {
    Error::Message("insufficient balance".to_string())
}

fn unauthorized_error() -> Error {
    Error::Message("unauthorized".to_string())
}

fn unknown_error() -> Error {
    Error::Message("unknown account".to_string())
}

impl Guest for SharedAccount {
    fn init(ctx: &ProcContext) {
        SharedAccountStorage::default().init(ctx);
    }

    fn open(
        ctx: &ProcContext,
        token: ContractAddress,
        n: Integer,
        other_tenants: Vec<String>,
    ) -> Result<String, Error> {
        let signer = ctx.signer();
        let balance =
            token::balance(&token, &signer.to_string()).ok_or(insufficient_balance_error())?;
        if balance < n {
            return Err(insufficient_balance_error());
        }
        let account_id = ctx.generate_id();
        ctx.model().accounts().set(
            account_id.clone(),
            Account {
                balance: n,
                owner: ctx.signer().to_string(),
                other_tenants: Map::new(
                    &other_tenants
                        .into_iter()
                        .map(|t| (t, true))
                        .collect::<Vec<_>>(),
                ),
            },
        );
        token::transfer(&token, signer, &ctx.contract_signer().to_string(), n)?;
        Ok(account_id)
    }

    fn deposit(
        ctx: &ProcContext,
        token: ContractAddress,
        account_id: String,
        n: Integer,
    ) -> Result<(), Error> {
        let signer = ctx.signer();
        let balance =
            token::balance(&token, &signer.to_string()).ok_or(insufficient_balance_error())?;
        if balance < n {
            return Err(insufficient_balance_error());
        }
        let account = ctx
            .model()
            .accounts()
            .get(account_id)
            .ok_or(unknown_error())?;
        if !authorized(&signer, &account) {
            return Err(unauthorized_error());
        }
        account.update_balance(|b| b + n);
        token::transfer(&token, signer, &ctx.contract_signer().to_string(), n)
    }

    fn withdraw(
        ctx: &ProcContext,
        token: ContractAddress,
        account_id: String,
        n: Integer,
    ) -> Result<(), Error> {
        let signer = ctx.signer();
        let account = ctx
            .model()
            .accounts()
            .get(account_id)
            .ok_or(unknown_error())?;
        if !authorized(&signer, &account) {
            return Err(unauthorized_error());
        }
        let balance = account.balance();
        if balance < n {
            return Err(insufficient_balance_error());
        }
        account.set_balance(balance - n);
        token::transfer(&token, ctx.contract_signer(), &signer.to_string(), n)
    }

    fn balance(ctx: &ViewContext, account_id: String) -> Option<Integer> {
        ctx.model().accounts().get(account_id).map(|a| a.balance())
    }

    fn token_balance(
        _ctx: &ViewContext,
        token: ContractAddress,
        holder: String,
    ) -> Option<Integer> {
        token::balance(&token, &holder)
    }

    fn tenants(ctx: &ViewContext, account_id: String) -> Option<Vec<String>> {
        ctx.model().accounts().get(account_id).map(|a| {
            [a.owner()]
                .into_iter()
                .chain(a.other_tenants().keys())
                .collect()
        })
    }
}
```

**Key points:**

* Uses `interface!` not `import!` for dynamic token contract address
* `ctx.model()` for storage access
* Map operations: `accounts().get(id)` returns `Option<AccountModel>`
* Nested struct updates: `account.update_balance(|b| b + n)`
* Error helpers return `Error::Message(...to_string())`
* `authorized()` function takes `&AccountModel` not `&AccountWrapper`
* `ctx.generate_id()` creates unique account IDs
* Calls `token::balance()` and `token::transfer()` with contract addresses

## Testing

The test demonstrates creating accounts, depositing, withdrawing, and testing authorization:

```rust theme={null}
#[cfg(test)]
mod tests {
    use testlib::*;

    interface!(name = "token", path = "../../token/contract/wit");
    interface!(name = "shared-account");

    #[testlib::test(contracts_dir = "../../")]
    async fn test_shared_account_contract() -> Result<()> {
        let alice = runtime.identity().await?;
        let bob = runtime.identity().await?;
        let claire = runtime.identity().await?;
        let dara = runtime.identity().await?;

        let token = runtime.publish(&alice, "token").await?;
        let shared_account = runtime.publish(&alice, "shared-account").await?;

        token::mint(runtime, &token, &alice, 100.into()).await??;

        let account_id = shared_account::open(
            runtime,
            &shared_account,
            &alice,
            token.clone(),
            50.into(),
            vec![&bob, &dara],
        )
        .await??;

        let result = shared_account::balance(runtime, &shared_account, &account_id).await?;
        assert_eq!(result, Some(50.into()));

        shared_account::deposit(runtime, &shared_account, &alice, token.clone(), &account_id, 25.into())
            .await??;

        let result = shared_account::balance(runtime, &shared_account, &account_id).await?;
        assert_eq!(result, Some(75.into()));

        shared_account::withdraw(runtime, &shared_account, &bob, token.clone(), &account_id, 25.into())
            .await??;

        let result = shared_account::balance(runtime, &shared_account, &account_id).await?;
        assert_eq!(result, Some(50.into()));

        shared_account::withdraw(runtime, &shared_account, &alice, token.clone(), &account_id, 50.into())
            .await??;

        let result = shared_account::balance(runtime, &shared_account, &account_id).await?;
        assert_eq!(result, Some(0.into()));

        let result = shared_account::withdraw(runtime, &shared_account, &bob, token.clone(), &account_id, 1.into())
            .await?;
        assert_eq!(
            result,
            Err(Error::Message("insufficient balance".to_string()))
        );

        let result = shared_account::withdraw(runtime, &shared_account, &claire, token.clone(), &account_id, 1.into())
            .await?;
        assert_eq!(result, Err(Error::Message("unauthorized".to_string())));

        let result =
            shared_account::token_balance(runtime, &shared_account, token.clone(), &alice).await?;
        assert_eq!(result, Some(75.into()));

        let result = token::balance(runtime, &token, &bob).await?;
        assert_eq!(result, Some(25.into()));

        let result = shared_account::tenants(runtime, &shared_account, &account_id)
            .await?
            .unwrap();
        assert_eq!(result.iter().len(), 3);
        assert!(result.contains(&alice.to_string()));
        assert!(result.contains(&dara.to_string()));
        assert!(result.contains(&bob.to_string()));

        Ok(())
    }
}
```
