Control access to an asset
When you issue an asset on the Bantu Blockchain, anyone can hold it by default. In general, that’s a good thing: easy access means better reach and better liquidity, and most of the time, issuers with KYC requirements can handle those when an asset moves onto or off of the network.
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:
Authorization Required
When AUTHORIZATION REQUIRED is enabled, an issuer must approve an account before that account can hold its asset. This setting allows issuers to vet potential token holders using whatever means they see fit, and to approve trustlines if and only if the holders pass muster.
To allow access, the user creates a trustline, and the issuer approves it by changing the AUTHORIZE flag with the allow_trust operation.
There are two levels of authorization an asset issuer can grant using the allow_trust operation:
AUTHORIZED: This 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: This flag denotes limited authorization that allows an account to maintain current orders, but not to otherwise transact with the asset.
Authorization Revocable
When AUTHORIZATION_REVOCABLE 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 transfering or trading the asset, and cancels the account’s open orders for the asset.
AUTHORIZATION_REVOCABLE 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 allow_trust operation.
To use this setting, AUTHORIZATION REQUIRED must also be enabled.
Authorization Immutable
With this setting, neither of the other authorization flags 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 ledger in an open and accessible state.
Example flow
To get a sense of how authorization flags work, let’s look at how an issuer of a regulated asset might use the AUTHORIZED_TO_MAINTAIN_LIABILITIES flag.
If the issuer wants to approve transactions on a case-by-base basis while allowing accounts to maintain offers, they can leave an account in the AUTHORIZED_TO_MAINTAIN_LIABILITIES state. That account can own offers, but cannot otherwise do anything with the asset.
To intitiate 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 allow_trust operations:
- Operation 1: Issuer uses
AllowTrustto fully authorize account A, asset X - Operation 2: Issuer uses
AllowTrustto fully authorize account B, asset X - Operation 3: Payment from A to B
- Operation 4: Issuer uses
AllowTrustto set account B, asset X toAUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAGstate - Operation 5: Issuer uses
AllowTrustto set account A, asset X toAUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAGstate
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
The following example sets authorization to be both required and revocable:
var StellarSdk = require("stellar-sdk");
var server = new StellarSdk.Server("https://expansion-testnet.bantu.network");
// 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: "Bantu 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://expansion-testnet.bantu.network");
// Keys for issuing account
KeyPair issuingKeys = KeyPair
.fromSecretSeed("SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4");
AccountResponse sourceAccount = server.accounts().account(issuingKeys.getAccountId());
Network network = new Network("Bantu Testnet");
Transaction setAuthorization = new Transaction.Builder(sourceAccount, network)
.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
from stellar_sdk.operation.set_options import Flag as AuthFlag
from stellar_sdk.exceptions import BaseHorizonError
server = Server(horizon_url="https://expansion-testnet.bantu.network/")
# Use the test network, if you want to use the live network, please set it to `Network.PUBLIC_NETWORK_PASSPHRASE`
network_passphrase = "Bantu Testnet"
# 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=AuthFlag.AUTHORIZATION_REVOCABLE | AuthFlag.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}")