Emitir un activo Tutorial
En este tutorial, recorreremos los pasos para emitir un activo en la red de prueba Stellar.
Si deseas interactuar con un activo emitido en la red Stellar en contratos inteligentes, puedes crear o desplegar el Contrato de Activo Stellar para ese activo.
Requisitos previos
Debes asegurarte de tener la cantidad requerida de XLM para crear tus cuentas de emisión y distribución y cubrir el saldo mínimo y las tarifas de transacción. Si estás emitiendo un activo en la red de prueba, puedes financiar tu cuenta obteniendo XLM de prueba de friendbot. Si estás emitiendo un activo en producción, necesitarás adquirir XLM de otra billetera o exchange.
Si deseas evitar que tus usuarios tengan que lidiar con tarifas de transacción, considera usar transacciones de incremento de tarifas. Lee más en nuestra Entrada de Enciclopedia sobre Transacciones de Incremento de Tarifas.
Aprende sobre la red de prueba y la red principal en nuestra sección de Redes.
Aprende más sobre las tarifas en nuestra sección de Tarifas, Límites de Recursos y Medición.
Herramientas fundamentales
Keypair de cuenta emisora
Primero, debes generar un keypair único. La clave pública actuará como tu identidad de emisión en la red, mientras que utilizarás la clave secreta para firmar transacciones.
- JavaScript
- Python
- Java
- Go
const issuerKeypair = StellarSdk.Keypair.random();
console.log("Issuer Public Key:", issuerKeypair.publicKey());
console.log("Issuer Secret Key:", issuerKeypair.secret());
from stellar_sdk import Keypair
issuer_keypair = Keypair.random()
print("Issuer Public Key:", issuer_keypair.public_key)
print("Issuer Secret Key:", issuer_keypair.secret)
KeyPair issuerKeypair = KeyPair.random();
System.out.println("Issuer Public Key: " + issuerKeypair.getAccountId());
System.out.println("Issuer Secret Key: " + issuerKeypair.getSecretSeed());
import (
"github.com/stellar/go/keypair"
"fmt"
)
issuerKeypair := keypair.MustRandom()
fmt.Println("Issuer Public Key:", issuerKeypair.Address())
fmt.Println("Issuer Secret Key:", issuerKeypair.Seed())
Tu dirección de cuenta no cambiará una vez que emitas tu activo, incluso si modificas sus firmantes. Muchos emisores emplean claves públicas de vanity relacionadas con su activo. Por ejemplo, podrías configurar un firmante personalizado en base32 como GASTRO...USD
.
Muchos usuarios aseguran su cuenta emisora con técnicas de almacenamiento en frío, como una billetera de hardware o una configuración de múltiples firmas. Esto añade una capa adicional de protección al mantener tu clave secreta fuera de línea o requerir múltiples aprobaciones para transacciones.
Keypair de cuenta de distribución
Tu activo puede ser emitido y transferido entre cuentas a través de un pago, contrato, o saldo reclamable. Aunque no es necesario crear una cuenta de distribución, es una práctica recomendada, así que lo haremos en este ejemplo. Lee más en nuestra sección de Cuentas de Emisión y Distribución.
Tres Operaciones
Generar un nuevo keypair
- JavaScript
- Python
- Java
- Go
const distributorKeypair = StellarSdk.Keypair.random();
distributor_keypair = Keypair.random()
KeyPair distributorKeypair = KeyPair.random();
distributorKeypair := keypair.MustRandom()
Importar un keypair existente
- JavaScript
- Python
- Java
- Go
const distributorKeypair = StellarSdk.Keypair.fromSecret(‘SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4’)
distributor_keypair = Keypair.from_secret("SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4")
KeyPair distributorKeypair = KeyPair.fromSecretSeed("SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4");
distributorKeypair := keypair.MustParseFull("SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4")
Emplear múltiples firmas
Ten cuidado al trabajar con claves secretas en bruto. Si no tienes habilitada la recuperación de confianza del emisor, cualquier error aquí podría hacer que los activos se pierdan de manera permanente. Muchos usuarios colocan sus primeros proyectos en testnet o prueban Quests que ofrecen un entorno de baja presión para principiantes.
Objeto de activo local
El objeto activo es una combinación de tu código y tu clave pública de emisión. Después de tu emisión, cualquiera puede buscar en la red tu activo único.
- JavaScript
- Python
- Java
- Go
const astroDollar = new StellarSdk.Asset(
"AstroDollar",
issuerKeypair.publicKey(),
);
from stellar_sdk import Keypair, Asset
astro_dollar = Asset("AstroDollar", issuer_keypair.public_key)
Asset astroDollar = Asset.createNonNativeAsset("AstroDollar", issuerKeypair.getAccountId());
import (
"github.com/stellar/go/keypair"
"github.com/stellar/go/txnbuild"
)
astroDollar := txnbuild.CreditAsset{Code: "AstroDollar", Issuer: issuerKeypair.Address()}
Aunque cualquiera puede crear un activo, puede haber implicaciones de cumplimiento del mundo real relevantes para tu caso de uso.
Querrás asegurarte de publicar información sobre tu activo para establecer confianza con tus usuarios y prevenir errores. Aprende cómo hacerlo en nuestra sección de Publicar Información Sobre Tu Activo.
Transacciones de red
Establecer línea de confianza del distribuidor
Las cuentas deben establecer una línea de confianza con la cuenta emisora para tener el activo de ese emisor. Esto es cierto para todos los activos excepto para el token nativo de la red, Lumens.
Si deseas evitar que tus usuarios tengan que lidiar con líneas de confianza o XLM, considera usar reservas patrocinadas. Lee más en nuestra Entrada de Enciclopedia sobre Reservas Patrocinadas.
- JavaScript
- Python
- Java
- Go
const StellarSdk = require("stellar-sdk");
const server = new StellarSdk.Horizon.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
.addOperation(
StellarSdk.Operation.changeTrust({
asset: astroDollar,
limit: "1000", // optional
source: distributorKeypair.publicKey(),
}),
)
.setTimeout(100)
.build();
from stellar_sdk import Keypair, Asset, Network, Server, TransactionBuilder
server = Server("https://horizon-testnet.stellar.org")
distributor_account = server.load_account(distributor_keypair.public_key)
transaction = (
TransactionBuilder(
source_account=distributor_account,
network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE,
base_fee=100
)
.append_change_trust_op(asset=astro_dollar, limit="1000")
.set_timeout(100)
.build()
)
Server server = new Server("https://horizon-testnet.stellar.org");
AccountResponse distributorAccount = server.accounts().account(distributorKeypair.getAccountId());
Transaction transaction = new Transaction.Builder(distributorAccount, Network.TESTNET)
.addOperation(
new ChangeTrustOperation.Builder(astroDollar, "1000")
.setSourceAccount(distributorKeypair.getAccountId())
.build()
)
.setTimeout(100)
.build();
import (
"github.com/stellar/go/clients/horizonclient"
"github.com/stellar/go/keypair"
"github.com/stellar/go/network"
"github.com/stellar/go/txnbuild"
)
client := horizonclient.DefaultTestNetClient
distributorAccountRequest := horizonclient.AccountRequest{AccountID: distributorKeypair.Address()}
distributorAccount, _ := client.AccountDetail(distributorAccountRequest)
transaction, _ := txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: &distributorAccount,
IncrementSequenceNum: true,
BaseFee: txnbuild.MinBaseFee,
Operations: []txnbuild.Operation{
&txnbuild.ChangeTrust{
Line: astroDollar,
Limit: "1000",
},
},
Timebounds: txnbuild.NewInfiniteTimeout(),
},
)
Pago de emisor al distribuidor
Los pagos son la operación más popular para realmente emitir (o acuñar) tu activo, en comparación con otras emisiones. Un pago crea la cantidad de un activo especificado, hasta el máximo entero de 64 bits. Es relevante mencionar que no necesitas aumentar la cantidad de emisión de tu activo por el incremento mínimo de XDR.
- JavaScript
- Python
- Java
- Go
// We're using TransactionBuilder(...) as a short-hand here
// to show that these operations can be "chained" together.
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()
}))
# We're using TransactionBuilder(...) as a short-hand here
# to show that these operations can be "chained" together.
transaction = (
TransactionBuilder(...)
.append_payment_op(
destination=distributor_keypair.public_key,
asset=astro_dollar,
amount="1000"
)
)
// We're using TransactionBuilder(...) as a short-hand here
// to show that these operations can be "chained" together.
Transaction transaction = new Transaction.Builder(...)
.addOperation(new PaymentOperation.Builder(
distributorKeypair.getAccountId(),
astroDollar,
"1000"
)
.setSourceAccount(
issuerKeypair.getAccountId()
)
// We're using TransactionBuilder(...) as a short-hand here
// to show that these operations can be "chained" together.
transaction, _ := txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: &issuerAccount,
IncrementSequenceNum: true,
BaseFee: txnbuild.MinBaseFee,
Operations: []txnbuild.Operation{
&txnbuild.Payment{
Destination: distributorKeypair.Address(),
Amount: "1000",
Asset: astroDollar,
},
},
Timebounds: txnbuild.NewInfiniteTimeout(),
},
)
También puedes crear un mercado directamente desde la cuenta emisora y emitir tokens a través del comercio.
Transacciones opcionales
Configurar suministro máximo
Esta sección detalla cómo bloquear tu cuenta con el propósito de limitar el suministro de tu activo emitido. 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. Tu cuenta estará completamente congelada.
Puedes configurar permanentemente el número exacto de un activo que existirá alguna vez. Aprende más sobre el suministro de activos en nuestra sección sobre Limitando el Suministro de un Activo
- JavaScript
- Python
- Java
- Go
const lockAccountTransaction = new StellarSdk.TransactionBuilder(...)
// This `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()
}))
lock_account_transaction = (
TransactionBuilder(...)
.append_set_options_op(master_weight=0)
)
Transaction lockAccountTransaction = new Transaction.Builder(...)
.addOperation(new SetOptionsOperation.Builder()
.setMasterKeyWeight(0)
.setSourceAccount(issuerKeypair.getAccountId())
)
lockAccountTransaction, _ := txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: &issuerAccount,
IncrementSequenceNum: true,
BaseFee: txnbuild.MinBaseFee,
Operations: []txnbuild.Operation{
&txnbuild.SetOptions{
MasterWeight: txnbuild.NewThreshold(0),
},
},
Timebounds: txnbuild.NewInfiniteTimeout(),
},
)
Aprobar la línea de confianza del distribuidor
Si habilitas la bandera de autorización, la cuenta emisora también necesita aprobar la solicitud de línea de confianza de la cuenta distribuidora antes del pago de emisión. Necesitarás hacer esto para todas las nuevas cuentas cuando se configuró para tu activo.
- JavaScript
- Python
- Java
- Go
const issuingAccount = await server.loadAccount(issuerKeypair.publicKey());
const transaction = new StellarSdk.TransactionBuilder(...)
.addOperation(
StellarSdk.Operation.setTrustLineFlags({
trustor: distributorKeypair.publicKey(),
asset: astroDollar,
flags: {
authorized: true,
},
}),
)
.setTimeout(100)
.build();
from stellar_sdk import Server, Keypair, Network, TransactionBuilder, SetTrustLineFlags
server = Server("https://horizon-testnet.stellar.org")
issuer_account = server.load_account(issuer_keypair.public_key)
transaction = (
TransactionBuilder(...)
.append_set_trust_line_flags_op(
trustor=distributor_keypair.public_key,
asset=astro_dollar,
flags=SetTrustLineFlags.AUTHORIZED_FLAG
)
.set_timeout(100)
.build()
)
AccountResponse issuerAccount = server.accounts().account(issuerKeys.getAccountId());
Transaction transaction = new Transaction.Builder(...)
.addOperation(new SetTrustLineFlagsOperation.Builder(
distributorKeys.getAccountId(),
astroDollar
).setAuthorized(true).build())
.setTimeout(100)
.build();
issuerAccountRequest := horizonclient.AccountRequest{AccountID: issuerKeypair.Address()}
issuerAccount, _ := client.AccountDetail(issuerAccountRequest)
transaction, _ := txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: &issuerAccount,
IncrementSequenceNum: true,
BaseFee: txnbuild.MinBaseFee,
Operations: []txnbuild.Operation{
&txnbuild.SetTrustLineFlags{
Trustor: distributorKeypair.Address(),
Asset: astroDollar,
Authorized: txnbuild.AuthorizeFlag,
},
},
Timebounds: txnbuild.NewInfiniteTimeout(),
},
)
Ejemplo de código completo
- JavaScript
- Python
- Java
- Go
var StellarSdk = require("stellar-sdk");
var server = new StellarSdk.Horizon.Server(
"https://horizon-testnet.stellar.org",
);
// Keys for accounts to issue and receive the new asset
var issuerKeys = StellarSdk.Keypair.fromSecret(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4",
);
var receivingKeys = StellarSdk.Keypair.fromSecret(
"SDSAVCRE5JRAI7UFAVLE5IMIZRD6N6WOJUWKY4GFN34LOBEEUS4W2T2D",
);
// Create an object to represent the new asset
var astroDollar = new StellarSdk.Asset("AstroDollar", issuerKeys.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(issuerKeys.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(issuerKeys);
return server.submitTransaction(transaction);
})
.then(console.log)
.catch(function (error) {
console.error("Error!", error);
});
from stellar_sdk import Asset, Keypair, Network, Server, TransactionBuilder
# 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")
# 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
issuerKeypair = Keypair.from_secret(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4"
)
issuer_public = issuerKeypair.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", issuer_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}")
issuer_account = server.load_account(issuer_public)
# Second, the issuing account actually sends a payment using the asset.
payment_transaction = (
TransactionBuilder(
source_account=issuer_account,
network_passphrase=network_passphrase,
base_fee=100,
)
.append_payment_op(
destination=distributor_public,
asset=astro_dollar,
amount="10",
)
.build()
)
payment_transaction.sign(issuerKeypair)
payment_transaction_resp = server.submit_transaction(payment_transaction)
print(f"Payment Transaction Resp:\n{payment_transaction_resp}")
Server server = new Server("https://horizon-testnet.stellar.org");
// Keys for accounts to issue and receive the new asset
KeyPair issuerKeys = KeyPair
.fromSecretSeed("SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4");
KeyPair receivingKeys = KeyPair
.fromSecretSeed("SDSAVCRE5JRAI7UFAVLE5IMIZRD6N6WOJUWKY4GFN34LOBEEUS4W2T2D");
// Create an object to represent the new asset
Asset astroDollar = Asset.createNonNativeAsset("AstroDollar", issuerKeys.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 issuer = server.accounts().account(issuerKeys.getAccountId());
Transaction sendAstroDollars = new Transaction.Builder(issuer, Network.TESTNET)
.addOperation(
new PaymentOperation.Builder(receivingKeys.getAccountId(), astroDollar, "10").build())
.build();
sendAstroDollars.sign(issuerKeys);
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()}
issuer_account, 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: issuer_account.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)
}
}