Simple Account
This example implements the smallest possible contract account: each require_auth call delegates to one ed25519 public key. It shows how to store that key, run __check_auth, and surface authorization failures. Use this as the baseline before moving to the Complex Account example for multisig or policy enforcement.
Implementing a contract account requires a very good understanding of authentication and authorization and requires rigorous testing and review. The example here is not a full-fledged account contract - use it as an API reference only.
While contract accounts are supported by the Stellar protocol and Soroban SDK, the full client support (such as transaction simulation) is still under development.
Run the Example
- Finish the Setup checklist to install the Stellar CLI, Rust target, and required environment variables.
- Clone the
soroban-examplesrepository at thev23.0.0tag:
git clone -b v23.0.0 https://github.com/stellar/soroban-examples
- If you prefer not to install anything locally, launch the repo in GitHub Codespaces or Codeanywhere.
Run the tests from the simple_account directory:
cd simple_account
cargo test
Expected output:
running 1 test
test test::test_account ... ok
How it Works
Open simple_account/src/lib.rs. The contract keeps one piece of state: the owner's ed25519 public key.
Initialize the owner
#[contracttype]
#[derive(Clone)]
pub enum DataKey {
Owner,
}
#[contractimpl]
impl SimpleAccount {
pub fn init(env: Env, public_key: BytesN<32>) {
if env.storage().instance().has(&DataKey::Owner) {
panic!("owner is already set");
}
env.storage().instance().set(&DataKey::Owner, &public_key);
}
Call init once to persist the owner's public key. Subsequent calls panic to prevent replacement of the key.
Implement __check_auth
#[allow(non_snake_case)]
pub fn __check_auth(
env: Env,
signature_payload: BytesN<32>,
signature: BytesN<64>,
_auth_context: Vec<Context>,
) {
let public_key: BytesN<32> = env
.storage()
.instance()
.get(&DataKey::Owner)
.unwrap();
env.crypto()
.ed25519_verify(&public_key, &signature_payload.into(), &signature);
}
}
__check_auth runs whenever another contract invokes require_auth on this contract address. The implementation loads the stored key, verifies the signature, and panics on failure so the upstream require_auth call rejects. Once you need multiple keys or policy logic, follow the same pattern shown in Complex Account.
Tests
Open simple_account/src/test.rs. __check_auth is not exposed as a regular entry point, so tests call env.try_invoke_contract_check_auth to emulate the Soroban host and exercise the same path Soroban runs during require_auth.
#[test]
fn test_account() {
let env = Env::default();
let account_contract = SimpleAccountClient::new(&env, &env.register(SimpleAccount, ()));
let signer = Keypair::generate(&mut thread_rng());
account_contract.init(&signer.public.to_bytes().into_val(&env));
let payload = BytesN::random(&env);
env.try_invoke_contract_check_auth::<Error>(
&account_contract.address,
&payload,
sign(&env, &signer, &payload),
&vec![&env],
)
.unwrap();
assert!(env
.try_invoke_contract_check_auth::<Error>(
&account_contract.address,
&payload,
BytesN::<64>::random(&env).into(),
&vec![&env],
)
.is_err());
}
try_invoke_contract_check_auth mimics the host path for require_auth, so the test proves both the success case and a failure case with random bytes.
Follow the same structure for any account:
- create a keypair and store the expected signer (for example, with
init) - call
try_invoke_contract_check_authwith a valid signature and assert it succeeds - call it again with an invalid signature or payload and assert it fails
Build the Contract
To produce the Wasm executable, run:
stellar contract build
# add --package soroban-simple-account-contract when building inside the soroban-examples workspace
The compiled file appears at target/wasm32v1-none/release/simple_account.wasm (the exact filename depends on your crate name).
Further Reading
- Complex account example – adds multisig support and spend limits.
- BLS signature contract – demonstrates custom signature schemes inside
__check_auth.