Asset Design Considerations
Issuing and distribution accounts
It is best practice on the Stellar network to create two accounts when issuing an asset: 1) the issuing account and 2) the distribution account.
The issuing account creates (or mints) the asset on the network by executing a payment operation. The issuing account will always be linked to the asset’s identity. Any account wanting to hold the asset must first establish a trustline with the issuing account. Read about trustlines in our Trustlines section.
The distribution account is the first recipient of the issued asset and handles all other transactions.
Note that you can also issue an asset by creating an offer or liquidity pool deposit with the issuing account.
It is best practice to issue an asset by sending it from the issuing account to a distribution account for two main reasons: security and auditing.
Security
The distribution account will be a hot account, meaning that some web service out there has direct access to sign its transactions.
For example, if the account you're distributing from is also the issuing account and it is compromised by a malicious actor, the actor can now issue as much of the asset as they want. If the malicious actor redeems the newly issued tokens with an anchor service, the anchor may not have the liquidity to support the customer withdrawals. Stakes are lower if you use a distribution account- if the distribution account is compromised, you can freeze the account’s asset balance and start with a new distribution account without changing the issuing account.
Auditing
Using a distribution account is better for auditing because an issuing account can’t actually hold a balance of its own asset. Sending an asset back to its issuing account burns (deletes) the asset. If you have a standing inventory of the issued asset in a separate account, it’s easier to track and can help with bookkeeping.
Naming an asset
One thing you must decide when issuing an asset is what to call it. An asset code is the asset’s identifying code. There are three possible formats: Alphanumeric 4, Alphanumeric 12, and liquidity pool shares.
Learn about liquidity pool shares in the Liquidity Pool Encyclopedia Entry.
- Alphanumeric 4-character maximum: Any characters from the set a-z, A-Z, 0-9 are allowed. The code can be shorter than 4 characters, but the trailing characters must all be empty.
- Alphanumeric 12-character maximum: Any characters from the set a-z, A-Z, 0-9 are allowed. The code can be any number of characters from 5 to 12, but the trailing characters must all be empty.
- The pool share asset is defined by the liquidity pool identifier (PoolID), which in turn is defined by the two assets its reserves are composed of.
Provided it falls into one of these buckets, you can choose any asset code you like. That said, if you’re issuing a currency, you should use the appropriate ISO 4217 code, and if you’re issuing a stock or bond, the appropriate ISIN number. Doing so makes it easier for Stellar interfaces to properly display and sort your token in their listings and allows potential token holders to understand what your token represents.
Controlling access to an asset with flags
When you issue an asset on Stellar, anyone can hold it by default. In general, that’s a good thing: easy access means better reach and better liquidity. However, if you need to control access to an asset to comply with regulations (or for any other reason), you can easily do so by enabling flags on your issuing account.
Flags are created on the account level using a set_options
operation. They can be set at any time in the life cycle of an asset, not just when you issue it.
Flag types
The (0xn) next to each flag type denotes the bit settings for each flag.
Authorization Required (0x1)
When AUTH_REQUIRED_FLAG
is enabled, an issuer must approve an account before that account can hold its asset. This setting allows issuers to vet potential token holders and to approve trustlines.
To allow access, the user creates a trustline, and the issuer approves it by changing the AUTHORIZE
flag with the Set_Trust_Line_Flag
operation.
There are two levels of authorization an asset issuer can grant using the Set_Trust_Line_Flag
operation:
AUTHORIZED_FLAG
: signifies complete authorization allowing an account to transact freely with the asset to make and receive payments, place orders, and deposit into a liquidity pool.AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG
: denotes limited authorization that allows an account to maintain current orders, withdraw from a liquidity pool, or cancel current orders - but not to otherwise transact with the asset.
Authorization Revocable (0x2)
When AUTH_REVOCABLE_FLAG
is enabled, an issuer can revoke an existing trustline’s authorization, thereby freezing the asset held by an account. Doing so prevents that account from transferring or trading the asset and cancels the account’s open orders for the asset.
AUTH_REVOCABLE_FLAG
also allows an issuer to reduce authorization from complete to limited, which prevents the account from transferring or trading the asset but does not cancel the account’s open orders for the asset. This setting is useful for issuers of regulated assets who need to authorize transactions on a case-by-case basis to ensure each conforms to certain requirements.
All changes to asset authorization are performed with the Set Trustline Flags operation.
There are three levels of authorization an asset issuer can remove using the Set_Trust_Line_Flag
operation:
AUTHORIZED_FLAG
: signifies complete authorization allowing an account to transact freely with the asset to make and receive payments and place orders.AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG
: denotes limited authorization that allows an account to maintain current orders but not to otherwise transact with the asset.CLAWBACK_ENABLED
: enables the issuing account to take back (burning) all of the asset. See our section on Clawbacks for more information.
Clawback Enabled (0x8)
With the AUTH_CLAWBACK_ENABLED_FLAG
flag set, any subsequent trustlines established with this account will have clawbacks enabled. You can read more about clawbacks (and selectively controlling them on a per-trustline basis) here.
Note that this flag requires that revocable is also set.
Authorization Immutable (0x4)
With this setting, none of the other authorization flags (AUTH_REQUIRED_FLAG
, AUTH_REVOCABLE_FLAG
) can be set, and the issuing account can’t be merged. You set this flag to signal to potential token holders that your issuing account and its assets will persist on the ledger in an open and accessible state.
Set Trustline Flag operation
The issuing account can configure various authorization and trustline flags for individual trustlines to an asset. The asset parameter is of the TrustLineAsset type. If you are modifying a trustline to a regular asset (i.e. one in a Code:Issuer format), this is equivalent to the asset type. If you are modifying a trustline to a pool share, this is the liquidity pool’s unique ID.
Example flow
Let’s look at how an issuer of a regulated asset might use the AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG
flag.
If the issuer wants to approve transactions on a case-by-case basis while allowing accounts to maintain offers, they can leave an account in the AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG
state. That account can own offers but cannot do anything else with the asset.
To initiate a new operation, the holding account requests that the issuer approve and sign a transaction. Once the issuer inspects the operation and decides to approve it, they sandwich it between a set of operations, first granting authorization, then reducing it.
Here’s a payment from A to B sandwiched between set_trust_line_flags
operations:
- Operation 1: Issuer uses
SetTrustLineFlags
to fully authorize account A, asset X - Operation 2: Issuer uses
SetTrustLineFlags
to fully authorize account B, asset X - Operation 3: Payment from A to B
- Operation 4: Issuer uses
SetTrustLineFlags
to set account B, asset X toAUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG
state - Operation 5: Issuer uses
SetTrustLineFlags
to set account A, asset X toAUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG
state
The authorization sandwich allows the issuer to inspect the specific payment and to grant authorization for it and it alone. Since operations bundled in a transaction are simultaneous, A and B are only authorized for the specific, pre-approved payment operation. Complete authorization does not extend beyond the specific transaction.
Sample code
In the following code samples, proper error checking is omitted. However, you should always validate your results, as there are many ways that requests can fail. Refer to the Error Handling page for tips on error management strategies.
The following example sets authorization to be both required and revocable:
- JavaScript
- Java
- Python
var StellarSdk = require("stellar-sdk");
var server = new StellarSdk.Horizon.Server(
"https://horizon-testnet.stellar.org",
);
// Keys for issuing account
var issuingKeys = StellarSdk.Keypair.fromSecret(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4",
);
server
.loadAccount(issuingKeys.publicKey())
.then(function (issuer) {
var transaction = new StellarSdk.TransactionBuilder(issuer, {
fee: 100,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.setOptions({
setFlags: StellarSdk.AuthRevocableFlag | StellarSdk.AuthRequiredFlag,
}),
)
// setTimeout is required for a transaction
.setTimeout(100)
.build();
transaction.sign(issuingKeys);
return server.submitTransaction(transaction);
})
.then(console.log)
.catch(function (error) {
console.error("Error!", error);
});
import org.stellar.sdk.*;
import org.stellar.sdk.Network;
import org.stellar.sdk.responses.AccountResponse;
Server server = new Server("https://horizon-testnet.stellar.org");
// Keys for issuing account
KeyPair issuingKeys = KeyPair
.fromSecretSeed("SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4");
AccountResponse sourceAccount = server.accounts().account(issuingKeys.getAccountId());
Transaction setAuthorization = new Transaction.Builder(sourceAccount, Network.TESTNET)
.addOperation(new SetOptionsOperation.Builder()
.setSetFlags(
AccountFlag.AUTH_REQUIRED_FLAG.getValue() |
AccountFlag.AUTH_REVOCABLE_FLAG.getValue())
.build())
.build();
setAuthorization.sign(issuingKeys);
server.submitTransaction(setAuthorization);
from stellar_sdk import Keypair, Network, Server, TransactionBuilder, AuthorizationFlag
from stellar_sdk.exceptions import BaseHorizonError
# Configure Stellar SDK to talk to the horizon instance hosted by Stellar.org
# To use the live network, set the hostname to horizon_url for mainnet
server = Server(horizon_url="https://horizon-testnet.stellar.org")
# Use the test network, if you want to use the live network, please set it to `Network.PUBLIC_NETWORK_PASSPHRASE`
network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE
# Keys for accounts to issue and receive the new asset
issuing_keypair = Keypair.from_secret(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4"
)
issuing_public = issuing_keypair.public_key
# Transactions require a valid sequence number that is specific to this account.
# We can fetch the current sequence number for the source account from Horizon.
issuing_account = server.load_account(issuing_public)
transaction = (
TransactionBuilder(
source_account=issuing_account,
network_passphrase=network_passphrase,
base_fee=100,
)
.append_set_options_op(
set_flags=AuthorizationFlag.AUTH_REVOCABLE_FLAG | AuthorizationFlag.AUTHORIZATION_REQUIRED
)
.build()
)
transaction.sign(issuing_keypair)
try:
transaction_resp = server.submit_transaction(transaction)
print(f"Transaction Resp:\n{transaction_resp}")
except BaseHorizonError as e:
print(f"Error: {e}")
Limiting the supply of an asset
This section details how to lock your account with the purpose of limiting the supply of your issued asset. However, locking your account means you’ll never be able to do anything with it ever again- whether that’s adjusting signers, changing the home domain, claiming any held XLM, or any other operation. Your account will be completely frozen.
With that warning in mind, it is possible to lock down the issuing account of an asset so that the asset’s supply cannot increase. To do this, first set the issuing account’s master weight to 0 using the Set Options operation. This prevents the issuing account from being able to sign transactions and therefore, making the issuer unable to issue any more assets. Be sure to do this only after you’ve issued all desired assets to the distribution account. If the asset has a Stellar Asset Contract, also make sure the admin for the contract was not updated from the default (which is the issuer) using the set_admin
contract call. If the admin was not the issuer, then the admin would be able to mint the asset even with the issuing account locked.
Learn more about signature weights in the Signature and Multisig Encyclopedia Entry.
See how to do this in the optional steps of the Issuing an Asset Tutorial.