Skip to content

Commit

Permalink
🔎 Refresh: asset design considerations
Browse files Browse the repository at this point in the history
  • Loading branch information
JFWooten4 committed Aug 5, 2024
1 parent ee6175a commit 95ffe3c
Showing 1 changed file with 116 additions and 59 deletions.
175 changes: 116 additions & 59 deletions docs/tokens/control-asset-access.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,31 @@ sidebar_position: 40
import { CodeExample } from "@site/src/components/CodeExample";
import { Alert } from "@site/src/components/Alert";

## Issuing and distribution accounts
## Issuer and distributor 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.
It is best practice on the Stellar network to create two accounts when issuing an asset: 1) the issuer account and 2) the distributor 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](../learn/fundamentals/stellar-data-structures/accounts.mdx#trustlines).
The **issuer account** creates (or mints) the asset on the network by executing a payment operation. The issuer account will always be linked to the asset’s identity. Any account wanting to hold the asset must first establish a trustline with the issuer account. Read about trustlines in our [Trustlines section](../learn/fundamentals/stellar-data-structures/accounts.mdx#trustlines).

The **distribution account** is the first recipient of the issued asset and handles all other transactions.
The **distributor 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.
Note that you can also issue an asset by creating an offer or liquidity pool deposit with the issuer 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.
It is best practice to issue an asset by sending it from the issuer account to a distributor 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.
The distributor account is a warm/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.
For example, if the account you're distributing from is also the issuer 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 distributor account; if the distributor account is compromised, you can freeze the account’s asset balance and start with a new distributor account without changing the issuer 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.
Using a distributor account is better for auditing because an issuer account can’t actually hold a balance of its own asset. Sending an asset back to its issuer 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.
One thing you must decide when issuer 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](../learn/encyclopedia/sdex/liquidity-on-stellar-sdex-liquidity-pools.mdx).

Expand All @@ -42,13 +42,13 @@ Provided it falls into one of these buckets, you can choose any asset code you l

## 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.
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 issuer 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.
The ($0x\mathfrak{n}$) next to each flag type denotes the bit settings for each flag.

#### Authorization Required (0x1)

Expand All @@ -73,7 +73,7 @@ There are three levels of authorization an asset issuer can remove using the `Se

- `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](../learn/encyclopedia/transactions-specialized/clawbacks.mdx) for more information.
- `CLAWBACK_ENABLED`: enables the issuer account to take back (burning) all of the asset. See our [section on Clawbacks](../learn/encyclopedia/transactions-specialized/clawbacks.mdx) for more information.

#### Clawback Enabled (0x8)

Expand All @@ -83,11 +83,11 @@ 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.
With this setting, none of the other authorization flags (`AUTH_REQUIRED_FLAG`, `AUTH_REVOCABLE_FLAG`) can be set, and the issuer account can’t be merged. You set this flag to signal to potential token holders that your issuer 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.
The issuer 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

Expand Down Expand Up @@ -115,19 +115,59 @@ The following example sets authorization to be both required and revocable:

<CodeExample>

```python
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 SDF
# To use the live network, set the hostname to horizon_url for mainnet
server = Server(horizon_url="https://horizon-testnet.stellar.org")

# Using the test network. If you want to use the live network use `Network.PUBLIC_NETWORK_PASSPHRASE`
network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE

# Keys for accounts to issue and receive the new asset
issuer_keypair = Keypair.from_secret(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4"
)
issuer_public = issuer_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.
issuer_account = server.load_account(issuer_public)

transaction = (
TransactionBuilder(
source_account=issuer_account,
network_passphrase=network_passphrase,
base_fee=100,
)
.append_set_options_op(
set_flags=AuthorizationFlag.AUTH_REVOCABLE_FLAG | AuthorizationFlag.AUTHORIZATION_REQUIRED
)
.build()
)
transaction.sign(issuer_keypair)
try:
transaction_resp = server.submit_transaction(transaction)
print(f"Success: {transaction_resp}")
except BaseHorizonError as e:
print(f"Error: {e}")
```

```js
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(
// Keys for issuer account
var issuerKeys = StellarSdk.Keypair.fromSecret(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4",
);

server
.loadAccount(issuingKeys.publicKey())
.loadAccount(issuerKeys.publicKey())
.then(function (issuer) {
var transaction = new StellarSdk.TransactionBuilder(issuer, {
fee: 100,
Expand All @@ -141,7 +181,7 @@ server
// setTimeout is required for a transaction
.setTimeout(100)
.build();
transaction.sign(issuingKeys);
transaction.sign(issuerKeys);
return server.submitTransaction(transaction);
})
.then(console.log)
Expand All @@ -157,10 +197,10 @@ import org.stellar.sdk.responses.AccountResponse;

Server server = new Server("https://horizon-testnet.stellar.org");

// Keys for issuing account
KeyPair issuingKeys = KeyPair
// Keys for issuer account
KeyPair issuerKeys = KeyPair
.fromSecretSeed("SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4");
AccountResponse sourceAccount = server.accounts().account(issuingKeys.getAccountId());
AccountResponse sourceAccount = server.accounts().account(issuerKeys.getAccountId());

Transaction setAuthorization = new Transaction.Builder(sourceAccount, Network.TESTNET)
.addOperation(new SetOptionsOperation.Builder()
Expand All @@ -169,58 +209,75 @@ Transaction setAuthorization = new Transaction.Builder(sourceAccount, Network.TE
AccountFlag.AUTH_REVOCABLE_FLAG.getValue())
.build())
.build();
setAuthorization.sign(issuingKeys);
setAuthorization.sign(issuerKeys);
server.submitTransaction(setAuthorization);
```

```python
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
```go
package main

# Keys for accounts to issue and receive the new asset
issuing_keypair = Keypair.from_secret(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4"
import (
"fmt"
"github.com/stellar/go/clients/horizonclient"
"github.com/stellar/go/keypair"
"github.com/stellar/go/network"
"github.com/stellar/go/txnbuild"
)
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}")
func main() {
client := horizonclient.DefaultTestNetClient
issuerKeys, err := keypair.ParseFull(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4"
)
check(err)

issuerAccountRequest := horizonclient.AccountRequest{AccountID: issuerKeys.Address()}
issuerAccount, err := client.AccountDetail(issuerAccountRequest)
check(err)

tx, err := txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: &issuerAccount,
IncrementSequenceNum: true,
Operations: []txnbuild.Operation{
&txnbuild.SetOptions{
SetFlags: []txnbuild.AccountFlag{
txnbuild.AuthRevocable,
txnbuild.AuthRequired,
},
},
},
BaseFee: txnbuild.MinBaseFee,
Timebounds: txnbuild.NewInfiniteTimeout(),
},
)
if err != nil {
log.Fatalf("Failed to build transaction: %v", err)
}
tx, err = tx.Sign(network.TestNetworkPassphrase, issuerKeys)
check(err)

resp, err := client.SubmitTransaction(tx)
check(err)
fmt.Printf("Success: %s", resp.Hash)
}
```

</CodeExample>

## Limiting the supply of an asset

**Warning!** 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.
::::danger Warning

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&mdash;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.
It is possible to lock down the issuer account of an asset so that the asset’s supply cannot increase. To do this, first set the issuer account’s master weight to 0 using the Set Options operation.[^unless-regulated] This prevents the issuer 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 distributor 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 issuer account locked.

[^unless-regulated]: If you are issuing regulated assets, make sure to assign `low` signature threshold keys before removing access to `high` treshold master keypair. If you do intend to limit an asset's supply, then the `low` threshold signers should not be able to combine up to a `medium` threshold, which could issue new tokens. Relevantly, assuming `high` > `medium` weight for issuer account threholds, you will not be able to change the `low` threshold signers after locking.

Learn more about signature weights in the [Signature and Multisig Encyclopedia Entry](../learn/encyclopedia/security/signatures-multisig.mdx).

Expand Down

0 comments on commit 95ffe3c

Please sign in to comment.