Skip to main content
Start with the Simple Account example to learn the single-signer basics. This Complex Account extends that baseline with multisig and customizable authorization policies. Any time an Address pointing at this contract instance is used, the logic defined here runs through the Soroban auth framework. Contract accounts are exclusive to Soroban and can’t be used to perform other Stellar operations.
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.
Open in Codespaces Open in Codeanywhere

Run the Example

First go through the Setup process to get your development environment configured, then clone the v23.0.0 tag of soroban-examples repository:
Or, skip the development environment setup and open this example in GitHub Codespaces or Code Anywhere. To run the tests for the example use cargo test.
You should see the output:

How it Works

Open the account/src/lib.rs file to follow along. Account contracts implement a special function __check_auth that takes the signature payload, signatures and authorization context. The function should error if auth is declined, otherwise auth will be approved. This example contract uses ed25519 keys for signature verification and supports multiple equally weighted signers. It also implements a policy that allows setting per-token limits on transfers. The token can be spent beyond the limit only if every signature is provided. For example, the user may initialize this contract with 2 keys and introduce 100 USDC spend limit. This way they can use a single key to sign their contract invocations and be sure that even if they sign a malicious transaction they won’t spend more than 100 USDC.

Initialization

account/src/lib.rs
This account contract needs to work with the public keys explicitly. Here we initialize the contract with ed25519 keys. We use constructor in order to ensure that the contract instance is created and initialized atomically (without constructor there is a risk that someone frontruns the initialization of the contract and sets their own public keys).

Policy modification

This function allows users to set and modify the per-token spend limit described above. The neat trick here is that require_auth can be used for the current_contract_address(), i.e. the account contract may be used to verify authorization for its own administrative functions. This way there is no need to write duplicate authorization and authentication logic.

__check_auth

__check_auth is a special function that account contracts implement. It will be called by the Soroban environment every time require_auth or require_auth_for_args is called for the address of the account contract. Here it is implemented in two steps. First, authentication is performed using the signature payload and a vector of signatures. Second, authorization policy is enforced using the auth_context vector. This vector contains all the contract calls that are being authorized by the provided signatures. __check_auth is a reserved function and can only be called by the Soroban environment in response to a call to require_auth. Any direct call to __check_auth will fail. This makes it safe to write to the account contract storage from __check_auth, as it’s guaranteed to not be called in unexpected context. In this example it’s possible to persist the spend limits without worrying that they’ll be exhausted via a bad actor calling __check_auth directly.

Authentication

Authentication here simply checks that the provided signatures are valid given the payload and also that they belong to the signers of this account contract.

Authorization policy

We verify the policy per Context. i.e. per one require_auth call for the address of this account. The policy for the account contract itself enforces every signer to have signed the method call.
Then we check for the standard token function names and verify that for these function we don’t exceed the spending limits.

Tests

Open the account/src/test.rs file to follow along. Refer to another examples for the general information on the test setup. Here we only look at some points specific to the account contracts.
Unlike most of the contracts that may simply use Address, account contracts deal with the signature verification and hence need to actually sign the payloads.
__check_auth can’t be called directly as regular contract functions, hence we need to use try_invoke_contract_check_auth testing utility that emulates being called by the Soroban host during a require_auth call.
Asserting the contract-specific error to try_invoke_contract_check_auth allows verifying the exact error code and makes sure that the verification has failed due to not having enough signers and not for any other reason. It’s a good idea for the account contract to have detailed error codes and verify that they are returned when they are expected.

Further Reading

  • Delegate Auth example — extends this example with CAP-71 auth delegation to registered delegate signers.