Skip to main content

Stellar Asset Contract

The Stellar Asset Contract is an implementation of CAP-46-6 Smart Contract Standardized Asset.

Overview

Stellar has numerous assets on its classic network, and being able to use them in Soroban would give users much more flexibility with how they can use their assets. For this reason, we introduced the Stellar Asset Contract, or SAC for short, which allows users to use their Stellar account and trustline balances in Soroban.

The SAC implements the SEP-41 Token Interface, which is similar to the widely used ERC-20 token standard. This should make it easier for existing smart contract developers to get started on Stellar.

Deployment

Every Stellar asset on Stellar has reserved a contract address that the Stellar Asset Contract can be deployed to. Anyone can initiate the deploy and the Stellar Asset issuer does not need to be involved.

It can be deployed using the InvokeHostFunctionOp with HOST_FUNCTION_TYPE_CREATE_CONTRACT and CONTRACT_ID_FROM_ASSET specified here. The resulting token will have a deterministic identifier, which will be the sha256 hash of HashIDPreimage::ENVELOPE_TYPE_CONTRACT_ID_FROM_ASSET xdr specified here.

Or the Soroban-CLI can be used as showed here.

Anyone can deploy the instances of Stellar Asset Contract. Note, that the initialization of the Stellar Asset Contracts happens automatically during the deployment. Asset Issuer will have the administrative permissions after the contract has been deployed.

Interacting with classic Stellar assets

The Stellar Asset Contract is the only way to interact with 'classic' Stellar assets in Soroban. 'Classic' assets include native Stellar token (lumens) and all the existing trustlines.

The issuer of the asset will be the administrator of the deployed contract. Because the Native Stellar token doesn't have an issuer, it will not have an administrator either. It also cannot be burned.

After the contract has been deployed, users can use their classic account (for lumens) or trustline (for other assets) balance. There are some differences depending on if you are using a classic account Address vs a contract Address (corresponding either to a regular contract or to a custom account contract). The following section references some issuer and trustline flags from Stellar classic, which you can learn more about here.

  • Using Address::Account
    • The balance must exist in a trustline (or an account for the native balance). This means the contract will not store the balance in ContractData. If the trustline or account is missing, any function that tries to interact with that balance will fail.
    • Classic trustline semantics will be followed.
      • Transfers will only succeed if the corresponding trustline(s) have the AUTHORIZED_FLAG set.
      • A trustline balance can only be clawed back using the clawback contract function if the trustline has TRUSTLINE_CLAWBACK_ENABLED_FLAG set.
      • The admin can only deauthorize a trustline if the issuer of the asset has AUTH_REVOCABLE_FLAG set. The deauthorization will fail if the issuer is missing. Note that when a trustline is deauthorized from Soroban, AUTHORIZED_FLAG is cleared and AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG is set to avoid having to pull offers and redeeming pool shares.
      • Transfers to the issuer account will burn the token, while transfers from the issuer account will mint.
      • Trustline balances are stored in a 64-bit signed integer even though the interface accepts 128-bit signed integers. Any operation that attempts to send or receive an amount more than the maximum amount that can be represented by a 64-bit signed integer will fail.
  • Using Address::Contract
    • The balance and authorization state will be stored in contract storage, as opposed to a trustline.
    • AUTH_REVOCABLE_FLAG is not required to be set on the issuer to deauthorize a balance.
    • Balances are stored in a 128-bit signed integer.
    • A balance can only be clawed back if the issuer account had the AUTH_CLAWBACK_ENABLED_FLAG set when the balance was created. A balance is created when either an Address::Contract is on the receiving end of a successful transfer, or if the admin sets the authorization state. Read more about AUTH_CLAWBACK_ENABLED_FLAG here.

Balance Authorization Required

In the Address::Contract case, if the issuer has AUTH_REQUIRED_FLAG set, then the specified Address::Contract will need to be explicitly authorized with set_auth before it can receive a balance. This logic lines up with how trustlines interact with the AUTH_REQUIRED_FLAG issuer flag, allowing asset issuers to have the same control in Soroban as they do in Stellar classic. Read more about AUTH_REQUIRED_FLAG here.

Authorization semantics

See the authorization overview and auth example for general information about authorization in Soroban.

The token contract contains three kinds of operations that follow the token interface:

  • getters, such as balance, which do not change the state of the contract
  • unprivileged mutators, such as incr_allow and xfer, which change the state of the contract but do not require special privileges
  • privileged mutators, such as clawback and set_admin, which change the state of the contract but require special privileges

Getters require no authorization because they do not change the state of the contract and all contract data is public. For example, balance simply returns the balance of the specified Address without changing it.

Unprivileged mutators require authorization from the Address that spends or allows spending their balance. The exceptions are xfer_from and burn_from operations where the Address that require authorization from the 'spender' entity that has got an allowance from another Address beforehand.

Priviliged mutators require authorization from a specific privileged identity, known as the "administrator". For example, only the administrator can mint more of the token. Similarly, only the administrator can appoint a new administrator.

Using Stellar Asset Contract with other contracts

From the contract perspective Stellar Asset Contract is not different from any other token that implements the Soroban token interface. The Rust SDK contains a pregenerated client for any contract that implements the token interface:

use soroban_sdk::token;

struct MyContract;

#[contractimpl]
impl MyContract {
fn token_fn(e: Env, id: Address) {
// Create a client instance for the provided token identifier. If the id
// value corresponds to an SAC contract, then SAC implementation is used.
let client = token::Client::new(&env, &id);
// Call token operations part of the SEP-41 token interface
client.transfer(...);
}
}
Clients

A client created by [token::Client] implements the functions defined by any contract that implements the SEP-41 Token Interface. But the Stellar Asset Contract exposes additional functions such as mint. To access the additional functions, another client needs to be used: token::StellarAssetClient. This client only implements the functions which are not part of the SEP-41.

let client = token::StellarAssetClient::new(&env, &id);
// Call token operations which are not part of the SEP-41 token interface
// but part of the CAP-46-6 Smart Contract Standardized Asset
client.mint(...);

Examples

See the full examples that utilize the token contract in various ways for more details:

Notice, that these examples don't do anything to support SAC specifically.

Testing

Soroban Rust SDK provides an easy way to instantiate a Stellar Asset Contract tokens using register_stellar_asset_contract. For example:

let admin = Address::random();
let user = Address::random();
let token = StellarAssetClient::new(e, &e.register_stellar_asset_contract(admin.clone()));
token.mint(&admin, &user, &1000);

See the tests in the examples above for the full test implementation.

Contract Interface

This interface can be found in the SDK. It extends the common Token Interface.