Skip to main content

Stellar Asset Contract

The Stellar Asset Contract (SAC) is an implementation of CAP-46-6 Smart Contract Standardized Asset and SEP-41 Token Interface for Stellar assets.

Overview

note

Stellar assets are issued by Stellar accounts. Issue an asset on Stellar by following the Issue an Asset Tutorial.

The Stellar Asset Contract allows users and contracts to make payments with, and interact with, assets. The SAC can interact with assets held by Stellar accounts or contracts.

The SAC is a special built-in contract that has access to functionality of the Stellar network that allows it to use Stellar assets directly.

Each Stellar asset has an instance of the SAC reserved on the network. To use the SAC reserved for an asset, the instance just needs to be deployed.

When the SAC transfers assets between accounts, the same debit and credits occur as they do when a Stellar payment operation is used, because the SAC interacts directly with Stellar account trust lines. When the SAC transfers assets between contracts, it uses Contract Data ledger entries to store the balances for contracts.

Stellar account balances for the native asset are always stored on the account, and Stellar contract balances for the native asset are always stored in a contract data entry.

Stellar account balances for issued assets are always stored in trust lines, and Stellar contract balances for issued assets are always stored in a contract data entry.

For example, when transferring from a Stellar account to a Stellar contract, the Stellar account's trust line entry is debited, and a contract data entry is credited.

And for example, when transferring from a Stellar contract to a Stellar account, a contract data entry is debited, and the account's trust line entry is credited.

In both those examples it is a single asset that is transferring from the account to the contract and back again. No bridging is required and no intermediary tokens are needed. An asset on Stellar and it's Stellar Asset Contract represent the same asset. The SAC for an asset is simply an API for interacting with the asset.

The SAC implements the SEP-41 Token Interface, which is similar to the widely used ERC-20 token standard. Contracts that depend on only the SEP-41 portion of the SAC's interface, are also compatible with any custom token that implements SEP-41.

Some functionality available on the Stellar network in transaction operations, such as the order book, do not have any functions exposed on the Stellar Asset Contract in the current protocol.

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 for contracts to interact with Stellar assets, either the native XLM asset, or those issued by Stellar accounts.

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.
      • 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.
    • 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.

Revoking Authorization

The admin can only revoke authorization from an Address, if the issuer of the asset has AUTH_REVOCABLE_FLAG set. The deauthorization will fail if the issuer is missing. This requirement is true for both the trustline balances of Address::Account and contract balances of Address:Contract. 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.

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 SEP-41 Token Interface.