Issue an Asset Tutorial
In this tutorial, we will walk through the steps to issue an asset on the Stellar test network.
Prerequisites
You must ensure you have the required amount of XLM to create your issuing and distribution accounts and cover the minimum balance and transaction fees. If you’re issuing an asset on the testnet, you can fund your account by getting test XLM from friendbot. If you’re issuing an asset in production, you will need to acquire XLM from another wallet or exchange.
If you’d like to avoid your users having to deal with transaction fees, consider using fee-bump transactions. Read more in our Fee-Bump Transaction Encyclopedia Entry.
Learn about the testnet and pubnet in our Testnet and Pubnet section.
Learn more about fees in our Fees, Surge Pricing, and Fee Strategies section.
1. Create issuing account and an object to represent the new asset
- JavaScript
const issuerKeypair = StellarSdk.Keypair.random()
const astroDollar = new StellarSdk.Asset(‘AstroDollar’, issuerKeypair.publicKey()
2. Create distribution account
Although it is not required to create a distribution account, it is best practice, so we will do so in this example. Read more in our Issuing and Distribution Accounts section.
- JavaScript
// This generates a random keypair
const distributorKeypair = StellarSdk.Keypair.random()
// This loads a keypair from a secret key you already have
const distributorKeypair = StellarSdk.Keypair.fromSecret(‘SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4’)
3. Establish trustline between the two
An account must establish a trustline with the issuing account to hold that account’s asset. This is true for all assets except for Stellar’s native token, XLM.
Read more about trustlines in the Trustlines section.
If you’d like to avoid your users having to deal with trustlines or XLM, consider using sponsored reserves. Read more in our Sponsored Reserves Encyclopedia Entry.
- JavaScript
const server = new StellarSdk.Server("https://horizon-testnet.stellar.org");
const account = await server.loadAccount(distributorKeypair.publicKey());
const transaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
// The `changeTrust` operation creates (or alters) a trustline
// The `limit` parameter below is optional
.addOperation(
StellarSdk.Operation.changeTrust({
asset: astroDollar,
limit: "1000",
source: distributorKeypair.publicKey(),
}),
);
4. Make a payment from issuing to distribution account, issuing the asset
The payment operation is what actually issues (or mints) the asset.
- JavaScript
const transaction = new StellarSdk.TransactionBuilder(...)
// The `payment` operation sends the `amount` of the specified
// `asset` to our distributor account
.addOperation(StellarSdk.Operation.payment({
destination: distributorKeypair.publicKey(),
asset: astroDollar,
amount: '1000',
source: issuerKeypair.publicKey()
}))
5. Optionally , lock the issuing account down so the asset’s supply is permanently fixed
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.
Learn more about asset supply in our section on Limiting the Supply of an Asset
- JavaScript
const transaction = new StellarSdk.TransactionBuilder(...)
// This (optional) `setOptions` operation locks the issuer account
// so there can never be any more of the asset minted
.addOperation(StellarSdk.Operation.setOptions({
masterWeight: 0,
source: issuerKeypair.publicKey()
}))
Note that you’ll want to make sure you publish information about your asset to establish trust with your users. Learn how to do so with our Publish Information About Your Asset section.
Full Code Sample
- JavaScript
- Java
- Go
- Python
var StellarSdk = require("stellar-sdk");
var server = new StellarSdk.Server("https://horizon-testnet.stellar.org");
// Keys for accounts to issue and receive the new asset
var issuingKeys = StellarSdk.Keypair.fromSecret(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4",
);
var receivingKeys = StellarSdk.Keypair.fromSecret(
"SDSAVCRE5JRAI7UFAVLE5IMIZRD6N6WOJUWKY4GFN34LOBEEUS4W2T2D",
);
// Create an object to represent the new asset
var astroDollar = new StellarSdk.Asset("AstroDollar", issuingKeys.publicKey());
// First, the receiving account must trust the asset
server
.loadAccount(receivingKeys.publicKey())
.then(function (receiver) {
var transaction = new StellarSdk.TransactionBuilder(receiver, {
fee: 100,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
// The `changeTrust` operation creates (or alters) a trustline
// The `limit` parameter below is optional
.addOperation(
StellarSdk.Operation.changeTrust({
asset: astroDollar,
limit: "1000",
}),
)
// setTimeout is required for a transaction
.setTimeout(100)
.build();
transaction.sign(receivingKeys);
return server.submitTransaction(transaction);
})
.then(console.log)
// Second, the issuing account actually sends a payment using the asset
.then(function () {
return server.loadAccount(issuingKeys.publicKey());
})
.then(function (issuer) {
var transaction = new StellarSdk.TransactionBuilder(issuer, {
fee: 100,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.payment({
destination: receivingKeys.publicKey(),
asset: astroDollar,
amount: "10",
}),
)
// 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);
});
Server server = new Server("https://horizon-testnet.stellar.org");
// Keys for accounts to issue and receive the new asset
KeyPair issuingKeys = KeyPair
.fromSecretSeed("SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4");
KeyPair receivingKeys = KeyPair
.fromSecretSeed("SDSAVCRE5JRAI7UFAVLE5IMIZRD6N6WOJUWKY4GFN34LOBEEUS4W2T2D");
// Create an object to represent the new asset
Asset astroDollar = Asset.createNonNativeAsset("AstroDollar", issuingKeys.getAccountId());
// First, the receiving account must trust the asset
AccountResponse receiving = server.accounts().account(receivingKeys.getAccountId());
Transaction allowAstroDollars = new Transaction.Builder(receiving, Network.TESTNET)
.addOperation(
// The `ChangeTrust` operation creates (or alters) a trustline
// The second parameter limits the amount the account can hold
new ChangeTrustOperation.Builder(astroDollar, "1000").build())
.build();
allowAstroDollars.sign(receivingKeys);
server.submitTransaction(allowAstroDollars);
// Second, the issuing account actually sends a payment using the asset
AccountResponse issuing = server.accounts().account(issuingKeys.getAccountId());
Transaction sendAstroDollars = new Transaction.Builder(issuing, Network.TESTNET)
.addOperation(
new PaymentOperation.Builder(receivingKeys.getAccountId(), astroDollar, "10").build())
.build();
sendAstroDollars.sign(issuingKeys);
server.submitTransaction(sendAstroDollars);
package main
import (
"github.com/stellar/go/clients/horizonclient"
"github.com/stellar/go/keypair"
"github.com/stellar/go/network"
"github.com/stellar/go/txnbuild"
"log"
)
func main() {
client := horizonclient.DefaultTestNetClient
// Remember, these are just examples, so replace them with your own seeds.
issuerSeed := "SDR4C2CKNCVK4DWMTNI2IXFJ6BE3A6J3WVNCGR6Q3SCMJDTSVHMJGC6U"
distributorSeed := "SBUW3DVYLKLY5ZUJD5PL2ZHOFWJSVWGJA47F6FLO66UUFZLUUA2JVU5U"
/*
* We omit error checks here for brevity, but you should always check your
* return values.
*/
// Keys for accounts to issue and distribute the new asset.
issuer, err := keypair.ParseFull(issuerSeed)
distributor, err := keypair.ParseFull(distributorSeed)
request := horizonclient.AccountRequest{AccountID: issuer.Address()}
issuerAccount, err := client.AccountDetail(request)
request = horizonclient.AccountRequest{AccountID: distributor.Address()}
distributorAccount, err := client.AccountDetail(request)
// Create an object to represent the new asset
astroDollar := txnbuild.CreditAsset{Code: "AstroDollar", Issuer: issuer.Address()}
// First, the receiving (distribution) account must trust the asset from the
// issuer.
tx, err := txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: distributorAccount.AccountID,
IncrementSequenceNum: true,
BaseFee: txnbuild.MinBaseFee,
Timebounds: txnbuild.NewInfiniteTimeout(),
Operations: []txnbuild.Operation{
&txnbuild.ChangeTrust{
Line: astroDollar,
Limit: "5000",
},
},
},
)
signedTx, err := tx.Sign(network.TestNetworkPassphrase, distributor)
resp, err := client.SubmitTransaction(signedTx)
if err != nil {
log.Fatal(err)
} else {
log.Printf("Trust: %s\n", resp.Hash)
}
// Second, the issuing account actually sends a payment using the asset
tx, err = txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: issuerAccount.AccountID,
IncrementSequenceNum: true,
BaseFee: txnbuild.MinBaseFee,
Timebounds: txnbuild.NewInfiniteTimeout(),
Operations: []txnbuild.Operation{
&txnbuild.Payment{
Destination: distributor.Address(),
Asset: astroDollar,
Amount: "10",
},
},
},
)
signedTx, err = tx.Sign(network.TestNetworkPassphrase, issuer)
resp, err = client.SubmitTransaction(signedTx)
if err != nil {
log.Fatal(err)
} else {
log.Printf("Pay: %s\n", resp.Hash)
}
}
from stellar_sdk import Asset, Keypair, Network, Server, TransactionBuilder
# Configure Stellar SDK to talk to the horizon instance hosted by Stellar.org
# To use the live network, set the hostname to 'https://horizon.stellar.org'
server = Server(horizon_url="https://horizon-testnet.stellar.org")
# Use test network, if you need to use public 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
distributor_keypair = Keypair.from_secret(
"SDSAVCRE5JRAI7UFAVLE5IMIZRD6N6WOJUWKY4GFN34LOBEEUS4W2T2D"
)
distributor_public = distributor_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.
distributor_account = server.load_account(distributor_public)
# Create an object to represent the new asset
astro_dollar = Asset("AstroDollar", issuing_public)
# First, the receiving account must trust the asset
trust_transaction = (
TransactionBuilder(
source_account=distributor_account,
network_passphrase=network_passphrase,
base_fee=100,
)
# The `changeTrust` operation creates (or alters) a trustline
# The `limit` parameter below is optional
.append_change_trust_op(asset=astro_dollar, limit="1000")
.set_timeout(100)
.build()
)
trust_transaction.sign(distributor_keypair)
trust_transaction_resp = server.submit_transaction(trust_transaction)
print(f"Change Trust Transaction Resp:\n{trust_transaction_resp}")
issuing_account = server.load_account(issuing_public)
# Second, the issuing account actually sends a payment using the asset.
payment_transaction = (
TransactionBuilder(
source_account=issuing_account,
network_passphrase=network_passphrase,
base_fee=100,
)
.append_payment_op(
destination=distributor_public,
asset=astro_dollar,
amount="10",
)
.build()
)
payment_transaction.sign(issuing_keypair)
payment_transaction_resp = server.submit_transaction(payment_transaction)
print(f"Payment Transaction Resp:\n{payment_transaction_resp}")