:::info The term "custom token" has been deprecated in favor of "contract token". View the conversation in the [Stellar Developer Discord](https://discord.com/channels/897514728459468821/966788672164855829/1359276952971640953). ::: # Stellar Asset Contract (SAC) The Stellar Asset Contract (SAC) is an implementation of [CAP-46-6 Smart Contract Standardized Asset] and [SEP-41 Token Interface] for Stellar [assets]. See examples of how to use the SAC in the [Tokens How-To Guides](https://developers.stellar.org/docs/build/guides/tokens.md). ## Overview :::note Stellar assets are issued by Stellar accounts. Issue an asset on Stellar by following the [Issue an Asset Tutorial](https://developers.stellar.org/docs/tokens/how-to-issue-an-asset.md). ::: 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 its 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 contract 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 [Stellar CLI] as shown [here](https://developers.stellar.org/docs/tools/cli/cookbook/deploy-stellar-asset-contract.md). Or the [Stellar SDK] can be used as shown [here](https://developers.stellar.org/docs/learn/fundamentals/contract-development/contract-interactions/stellar-transaction.md#xdr-usage) by calling `InvokeHostFunctionOp` with `HOST_FUNCTION_TYPE_CREATE_CONTRACT` and a `CONTRACT_ID_PREIMAGE_FROM_ASSET` contract ID preimage. The resulting token will have a deterministic identifier, which will be the sha256 hash of `HashIDPreimage::ENVELOPE_TYPE_CONTRACT_ID` xdr specified [here][contract_id]. Anyone can deploy the instances of Stellar Asset Contract. Note, that the initialization of the Stellar Asset Contracts happens automatically during the deployment. The asset issuer becomes the _initial_ administrator once the contract has been deployed; administrative authority is mutable and can be transferred afterwards with the SAC's `set_admin` function. A deployed SAC can be identified on-chain from its contract instance: its `SCContractInstance.executable` is `CONTRACT_EXECUTABLE_STELLAR_ASSET` rather than a Wasm hash. Once a contract is verified as a SAC this way, its built-in `name()` and `symbol()` methods report the wrapped asset's identity — there is no generic SEP-41 `asset()`/`issuer()` accessor to rely on. Note that a contract-address hash and the current `admin` value are not, by themselves, proof of an asset's provenance: verify the executable first, since the administrator may have been changed via `set_admin`. [contract_id]: https://github.com/stellar/stellar-xdr/blob/curr/Stellar-transaction.x [stellar cli]: ../tools/cli/stellar-cli.mdx [stellar sdk]: ../tools/sdks/README.mdx ## 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 initial administrator of the deployed contract (administrative authority can later be moved with `set_admin`). 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](https://developers.stellar.org/docs/tokens/control-asset-access.md#controlling-access-to-an-asset-with-flags). - 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 (as of Yardstick, Protocol 26, a contract can create the missing trustline itself by first calling the SAC's [`trust` function](#creating-trustlines-from-a-contract)). - 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](https://developers.stellar.org/docs/tokens/control-asset-access.md#clawback-enabled-0x8). ### 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](https://developers.stellar.org/docs/tokens/control-asset-access.md#authorization-required-0x1). ### 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. ### Creating trustlines from a contract As of Yardstick, Protocol 26 ([CAP-73]), the SAC's `trust` function allows a contract to create an asset's trustline for a `G...` address as part of a contract invocation. Before Yardstick, Protocol 26, a missing trustline could only be created with a separate `changeTrust` operation, so any contract function touching a trustline balance would fail until the account holder set up the trustline in a separate transaction. The `trust` function is useful any time a contract distributes an asset to accounts that may not hold it yet: for example, an airdrop or payout contract calling `trust` before `mint` or `transfer`, or bridging and chain abstraction flows that deliver assets to freshly created accounts. See [setting a custom SAC admin](https://developers.stellar.org/docs/build/guides/tokens/custom-sac-admin.md) for a worked example of a contract calling `trust` before minting to a recipient. A few things to keep in mind: - The call is a no-op if `addr` is a contract address, or if the trustline already exists, so it's safe to call unconditionally before a transfer or mint. - When a trustline is actually created, the SAC requires authorization from `addr`, preserving the opt-in nature of trustlines. - The account holding the new trustline must satisfy the network's [base reserve](https://developers.stellar.org/docs/learn/fundamentals/lumens.md#base-reserves) requirement for the additional ledger entry. ## Authorization semantics See the [authorization overview](https://developers.stellar.org/docs/learn/fundamentals/contract-development/authorization.md) and [auth example](https://developers.stellar.org/docs/build/smart-contracts/example-contracts/auth.md) for general information about authorization in Soroban. The token contract contains three kinds of operations that follow the token [interface](https://developers.stellar.org/docs/tokens/token-interface.md#code): - 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. ## Contract Interface The [`token` module] of the Rust SDK contains two traits, and corresponding client structs, for interacting with SACs. 1. The [`TokenInterface` trait] and [`TokenClient` struct] can be used to invoke the "basic subset" of the SAC functionality. They implement the common [SEP-41 Token Interface], and include functions like `transfer`, `burn`, `allowance`, etc. 2. The [`StellarAssetInterface` trait] and [`StellarAssetClient` struct] can be used to invoke the "extended subset" of SAC functionality. They _extend_ the common SEP-41 token interface with administrative functionality. In addition to all the `TokenInterface` functions, these traits contain admin functions like `set_admin`, `clawback`, `set_authorized`, etc. ```rust pub trait StellarAssetInterface { // All this is available in any SEP-41-compliant contract fn allowance(env: Env, from: Address, spender: Address) -> i128; fn approve(env: Env, from: Address, spender: Address, amount: i128, live_until_ledger: u32); fn balance(env: Env, id: Address) -> i128; fn transfer(env: Env, from: Address, to: MuxedAddress, amount: i128); fn transfer_from(env: Env, spender: Address, from: Address, to: Address, amount: i128); fn burn(env: Env, from: Address, amount: i128); fn burn_from(env: Env, spender: Address, from: Address, amount: i128); fn decimals(env: Env) -> u32; fn name(env: Env) -> String; fn symbol(env: Env) -> String; //! Everything below is specifically available to SAC instances /// Sets the administrator to the specified address `new_admin`. /// /// # Arguments /// /// * `new_admin` - The address which will henceforth be the administrator /// of this token contract. /// /// # Events /// /// Emits an event with topics `["set_admin", admin: Address, /// sep0011_asset: String], data = new_admin: Address` fn set_admin(env: Env, new_admin: Address); /// Returns the admin of the contract. /// /// # Panics /// /// If the admin is not set. fn admin(env: Env) -> Address; /// Sets whether the account is authorized to use its balance. If /// `authorized` is true, `id` should be able to use its balance. /// /// # Arguments /// /// * `id` - The address being (de-)authorized. /// * `authorize` - Whether or not `id` can use its balance. /// /// # Events /// /// Emits an event with topics `["set_authorized", id: Address, /// sep0011_asset: String], data = authorize: bool` fn set_authorized(env: Env, id: Address, authorize: bool); /// Returns true if `id` is authorized to use its balance. /// /// # Arguments /// /// * `id` - The address for which token authorization is being checked. fn authorized(env: Env, id: Address) -> bool; /// Mints `amount` to `to`. /// /// # Arguments /// /// * `to` - The address which will receive the minted tokens. /// * `amount` - The amount of tokens to be minted. /// /// # Events /// /// Emits an event with topics `["mint", to: Address, /// sep0011_asset: String], data = amount: i128` fn mint(env: Env, to: Address, amount: i128); /// Clawback `amount` from `from` account. `amount` is burned in the /// clawback process. /// /// # Arguments /// /// * `from` - The address holding the balance from which the clawback will /// take tokens. /// * `amount` - The amount of tokens to be clawed back. /// /// # Events /// /// Emits an event with topics `["clawback", from: Address, /// sep0011_asset: String], data = amount: i128` fn clawback(env: Env, from: Address, amount: i128); /// Creates this contract asset's unlimited trustline for the provided /// address. /// /// This is a no-op if the input address is a C-address, or if the /// provided G-address already has the respective trustline. /// /// If the trustline is actually created, this will require authorization /// from `addr` (i.e. `addr.require_auth()` will be called). /// /// # Arguments /// /// * `addr` - The address for which a trustline will be created. /// /// # Panics /// /// If the asset issuer does not exist, or if a new trustline cannot be /// created. fn trust(env: Env, addr: Address); } ``` ## Contract Errors All built-in smart contracts on the Stellar network share the same error types, outlined below. ```rust #[derive(Debug, FromPrimitive, PartialEq, Eq)] pub(crate) enum ContractError { // Indicates an internal error in protocol implementation, such as invalid // ledger state. This may not happen in the real networks, but might appear // when using malformed test data (such as malformed ledger snapshots). InternalError = 1, // Indicates an impossible function has been invoked, such as clawback for // an asset that does not have clawback enabled. Or, an operation has been // called affecting the issuer's trustline. OperationNotSupportedError = 2, // Indicates the SAC has already been initialized. This error may only occur // during initialization of an asset's SAC instance. AlreadyInitializedError = 3, // Unused = 4, - this error code is not used by SAC // Unused = 5, - this error code is not used by SAC // An account that would be modified by this transaction does not exist on // the network. AccountMissingError = 6, // Unused = 7, - this error code is not used by SAC // Indicates an amount less than zero was provided for a transfer amount. NegativeAmountError = 8, // Indicates an insufficient spender's available allowance amount. Also used // to indicate a problem with expiration ledger when creating an allowance. AllowanceError = 9, // Indicates too low of a balance to spend the requested amount, or a // balance as a result of this transaction would be too low or high, or a // problem with attempting a clawback on a non-clawback-enabled trustline. BalanceError = 10, // Indicates an address has had its balance authorization revoked by the // asset issuer. BalanceDeauthorizedError = 11, // Indicates this transaction would result in a spender's allowance // overflowing. OverflowError = 12, // Indicates a trustline entry does not exist for this address to hold this // asset. TrustlineMissingError = 13, } ``` Source: https://github.com/stellar/rs-soroban-env/blob/main/soroban-env-host/src/builtin_contracts/contract_error.rs [assets]: ../learn/fundamentals/stellar-data-structures/assets.mdx [`token` module]: https://docs.rs/soroban-sdk/latest/soroban_sdk/token/index.html [`TokenInterface` trait]: https://docs.rs/soroban-sdk/latest/soroban_sdk/token/trait.TokenInterface.html [`TokenClient` struct]: https://docs.rs/soroban-sdk/latest/soroban_sdk/token/struct.TokenClient.html [`StellarAssetInterface` trait]: https://docs.rs/soroban-sdk/latest/soroban_sdk/token/trait.StellarAssetInterface.html [`StellarAssetClient` struct]: https://docs.rs/soroban-sdk/latest/soroban_sdk/token/struct.StellarAssetClient.html [cap-46-6 smart contract standardized asset]: https://github.com/stellar/stellar-protocol/blob/master/core/cap-0046-06.md [sep-41 token interface]: ./token-interface.mdx [CAP-73]: https://github.com/stellar/stellar-protocol/blob/master/core/cap-0073.md