Assets

Introduction

An asset is a type of value that can be issued on a blockchain. All units of a given asset are fungible.

Units of an asset can be transacted directly between parties without the involvement of the issuer.

Each asset has a globally unique asset ID that is derived from an issuance program. The issuance program typically defines a set of possible signing keys and a threshold number of signatures that must be provided to authorize issuance of new units of the asset. Chain Core automatically creates the issuance program when the asset is created. The issuer can issue as many units as they want, as many times as they want. Custom issuance programs are possible that enforce further limits on when, whether, and by whom new units may be issued.

Each asset can optionally include an asset definition consisting of arbitrary key-value data. The asset definition is committed to the blockchain for all participants to see. Additionally, an asset can be tagged locally with private data for convenient queries and operations. For more information, see Global vs. Local Data.

Overview

This guide will walk you through the basic functions of an asset:

This guide assumes you know the basic functions presented in the 5-Minute Guide.

Sample Code

All code samples in this guide can be viewed in a single, runnable script. Available languages:

Create asset

Creating an asset defines the asset object locally in the Chain Core. It does not exist on the blockchain until units are issued in a transaction.

  • The alias is an optional, user-supplied, unique identifier that you can use to operate on the asset. We will use this later to build a transaction issuing units of the asset.
  • The quorum is the threshold number of the possible signing keys that must sign a transaction to issue units of this asset.
  • The definition is global data about the asset that is visible in the blockchain. We will create several fields in the definition.
  • The tag is an optional key-value field used for arbitrary storage or queries. This data is local to the Chain Core and not visible in the blockchain. We will add several tags.

Create an asset for Acme Common stock:

new Asset.Builder()
  .setAlias("acme_common")
  .addRootXpub(assetKey.xpub)
  .setQuorum(1)
  .addTag("internal_rating", "1")
  .addDefinitionField("issuer", "Acme Inc.")
  .addDefinitionField("type", "security")
  .addDefinitionField("subtype", "private")
  .addDefinitionField("class", "common")
  .create(client);
chain.assets.create(
  alias: 'acme_common',
  root_xpubs: [asset_key.xpub],
  quorum: 1,
  tags: {
    internal_rating: '1',
  },
  definition: {
    issuer: 'Acme Inc.',
    type: 'security',
    subtype: 'private',
    class: 'common',
  },
)
client.assets.create({
  alias: 'acme_common',
  rootXpubs: [assetKey],
  quorum: 1,
  tags: {
    internalRating: '1',
  },
  definition: {
    issuer: 'Acme Inc.',
    type: 'security',
    subtype: 'private',
    class: 'common',
  },
})
  • Java
  • Node
  • Ruby

Create an asset for Acme Preferred stock:

new Asset.Builder()
  .setAlias("acme_preferred")
  .addRootXpub(assetKey.xpub)
  .setQuorum(1)
  .addTag("internal_rating", "2")
  .addDefinitionField("issuer", "Acme Inc.")
  .addDefinitionField("type", "security")
  .addDefinitionField("subtype", "private")
  .addDefinitionField("class", "preferred")
  .create(client);
chain.assets.create(
  alias: 'acme_preferred',
  root_xpubs: [asset_key.xpub],
  quorum: 1,
  tags: {
    internal_rating: '2',
  },
  definition: {
    issuer: 'Acme Inc.',
    type: 'security',
    subtype: 'private',
    class: 'preferred',
  },
)
client.assets.create({
  alias: 'acme_preferred',
  rootXpubs: [assetKey],
  quorum: 1,
  tags: {
    internalRating: '2',
  },
  definition: {
    issuer: 'Acme Inc.',
    type: 'security',
    subtype: 'private',
    class: 'preferred',
  },
})
  • Java
  • Node
  • Ruby

List assets

Chain Core keeps a list of all assets in the blockchain, whether or not they were issued by the local Chain Core. Each asset can be locally annotated with an alias and tags to enable efficient actions and intelligent queries. Note: local data is not present in the blockchain, see: Global vs Local Data.

To list all assets created in the local Core, we build an assets query, filtering on the is_local tag.

Asset.Items localAssets = new Asset.QueryBuilder()
  .setFilter("is_local=$1")
  .addFilterParameter("yes")
  .execute(client);

while (localAssets.hasNext()) {
  Asset asset = localAssets.next();
  System.out.println("Local asset: " + asset.alias);
}
chain.assets.query(
  filter: 'is_local=$1',
  filter_params: ['yes'],
).each do |a|
  puts "Local asset: #{a.alias}"
end
client.assets.queryAll({
  filter: 'is_local=$1',
  filterParams: ['yes'],
}, (asset, next) => {
  console.log('Local asset: ' + asset.alias)
  next()
})
  • Java
  • Node
  • Ruby

To list all assets defined as preferred stock of a private security, we build an assets query, filtering on several tags.

Asset.Items preferred = new Asset.QueryBuilder()
  .setFilter("definition.type=$1 AND definition.subtype=$2 AND definition.class=$3")
  .addFilterParameter("security")
  .addFilterParameter("private")
  .addFilterParameter("preferred")
  .execute(client);

while (preferred.hasNext()) {
  Asset asset = preferred.next();
  System.out.println("Private preferred security: " + asset.alias);
}
chain.assets.query(
  filter: 'definition.type=$1 AND definition.subtype=$2 AND definition.class=$3',
  filter_params: ['security', 'private', 'preferred'],
).each do |a|
  puts "Private preferred security: #{a.alias}"
end
client.assets.queryAll({
  filter: 'definition.type=$1 AND definition.subtype=$2 AND definition.class=$3',
  filterParams: ['security', 'private', 'preferred'],
}, (asset, next) => {
  console.log('Private preferred security: ' + asset.alias)
  next()
})
  • Java
  • Node
  • Ruby

Issue asset units to a local account

To issue units of an asset into an account within the Chain Core, we can build a transaction using an asset_alias and an account_alias.

We first build a transaction issuing 1000 units of Acme Common stock to the Acme treasury account.

Transaction.Template issuanceTransaction = new Transaction.Builder()
  .addAction(new Transaction.Action.Issue()
    .setAssetAlias("acme_common")
    .setAmount(1000)
  ).addAction(new Transaction.Action.ControlWithAccount()
    .setAccountAlias("acme_treasury")
    .setAssetAlias("acme_common")
    .setAmount(1000)
  ).build(client);
issuance_tx = chain.transactions.build do |b|
  b.issue asset_alias: 'acme_common', amount: 1000
  b.control_with_account account_alias: 'acme_treasury', asset_alias: 'acme_common', amount: 1000
end
const issuePromise = client.transactions.build(builder => {
  builder.issue({
    assetAlias: 'acme_common',
    amount: 1000
  })
  builder.controlWithAccount({
    accountAlias: 'acme_treasury',
    assetAlias: 'acme_common',
    amount: 1000
  })
})
  • Java
  • Node
  • Ruby

Once we have built the transaction, we need to sign it with the key used to create the Acme Common stock asset.

Transaction.Template signedIssuanceTransaction = HsmSigner.sign(issuanceTransaction);
signed_issuance_tx = signer.sign(issuance_tx)
const signingPromise = signer.sign(issueTx)
  • Java
  • Node
  • Ruby

Once we have signed the transaction, we can submit it for inclusion in the blockchain.

Transaction.submit(client, signedIssuanceTransaction);
chain.transactions.submit(signed_issuance_tx)
client.transactions.submit(signedIssueTx)
  • Java
  • Node
  • Ruby

Issue asset units to an external party

If you wish to issue asset units to an external party, you must first request a control program from them. You can then build, sign, and submit a transaction issuing asset units to their control program.

We will issue 2000 units of Acme Common stock to an external party.

Transaction.Template externalIssuance = new Transaction.Builder()
  .addAction(new Transaction.Action.Issue()
    .setAssetAlias("acme_preferred")
    .setAmount(2000)
  ).addAction(new Transaction.Action.ControlWithProgram()
    .setControlProgram(externalProgram)
    .setAssetAlias("acme_preferred")
    .setAmount(2000)
  ).build(client);

Transaction.submit(client, HsmSigner.sign(externalIssuance));
external_issuance = chain.transactions.build do |b|
  b.issue asset_alias: 'acme_preferred', amount: 2000
  b.control_with_program control_program: external_program, asset_alias: 'acme_preferred', amount: 2000
end

chain.transactions.submit(signer.sign(external_issuance))
client.transactions.build(builder => {
  builder.issue({
    assetAlias: 'acme_preferred',
    amount: 2000
  })
  builder.controlWithProgram({
    controlProgram: externalProgram.controlProgram,
    assetAlias: 'acme_preferred',
    amount: 2000
  })
}).then(template => {
  return signer.sign(template)
}).then(signed => {
  return client.transactions.submit(signed)
})
  • Java
  • Node
  • Ruby

Retire asset units

To retire units of an asset from an account, we can build a transaction using an account_alias and asset_alias.

We first build a transaction retiring 50 units of Acme Common stock from Acme’s treasury account.

Transaction.Template retirementTransaction = new Transaction.Builder()
  .addAction(new Transaction.Action.SpendFromAccount()
    .setAccountAlias("acme_treasury")
    .setAssetAlias("acme_common")
    .setAmount(50)
  ).addAction(new Transaction.Action.Retire()
    .setAssetAlias("acme_common")
    .setAmount(50)
  ).build(client);
retirement_tx = chain.transactions.build do |b|
  b.spend_from_account account_alias: 'acme_treasury', asset_alias: 'acme_common', amount: 50
  b.retire asset_alias: 'acme_common', amount: 50
end
const retirePromise = client.transactions.build(builder => {
  builder.spendFromAccount({
    accountAlias: 'acme_treasury',
    assetAlias: 'acme_common',
    amount: 50
  })
  builder.retire({
    assetAlias: 'acme_common',
    amount: 50
  })
})
  • Java
  • Node
  • Ruby

Once we have built the transaction, we need to sign it with the key used to create Acme’s treasury account.

Transaction.Template signedRetirementTransaction = HsmSigner.sign(retirementTransaction);
signed_retirement_tx = signer.sign(retirement_tx)
const signingPromise = signer.sign(retireTx)
  • Java
  • Node
  • Ruby

Once we have signed the transaction, we can submit it for inclusion in the blockchain. The asset units in this transaction become permanently unavailable for further spending.

Transaction.submit(client, signedRetirementTransaction);
chain.transactions.submit(signed_retirement_tx)
client.transactions.submit(signedRetireTx)
  • Java
  • Node
  • Ruby

List asset transactions

Chain Core keeps a time-ordered list of all transactions in the blockchain. These transactions are locally annotated with asset aliases and asset tags to enable intelligent queries. Note: local data is not present in the blockchain, see: Global vs Local Data.

Issuance transactions

To list transactions where Acme Common stock was issued, we build an assets query, filtering on inputs with the issue action and the Acme Common stock asset_alias.

Transaction.Items acmeCommonIssuances = new Transaction.QueryBuilder()
  .setFilter("inputs(type=$1 AND asset_alias=$2)")
  .addFilterParameter("issue")
  .addFilterParameter("acme_common")
  .execute(client);

while (acmeCommonIssuances.hasNext()) {
  Transaction tx = acmeCommonIssuances.next();
  System.out.println("Acme Common issued in tx " + tx.id);
}
chain.transactions.query(
  filter: 'inputs(type=$1 AND asset_alias=$2)',
  filter_params: ['issue', 'acme_common'],
).each do |t|
  puts "Acme Common issued in tx #{t.id}"
end
client.transactions.queryAll({
  filter: 'inputs(type=$1 AND asset_alias=$2)',
  filterParams: ['issue', 'acme_common'],
}, (tx, next) => {
  console.log('Acme Common issued in tx ' + tx.id)
  next()
})
  • Java
  • Node
  • Ruby

Transfer transactions

To list transactions where Acme Common stock was transferred, we build an assets query, filtering on inputs with the spend action and the Acme Common stock asset_alias.

Transaction.Items acmeCommonTransfers = new Transaction.QueryBuilder()
  .setFilter("inputs(type=$1 AND asset_alias=$2)")
  .addFilterParameter("spend")
  .addFilterParameter("acme_common")
  .execute(client);

while (acmeCommonTransfers.hasNext()) {
  Transaction tx = acmeCommonTransfers.next();
  System.out.println("Acme Common transferred in tx " + tx.id);
}
chain.transactions.query(
  filter: 'inputs(type=$1 AND asset_alias=$2)',
  filter_params: ['spend', 'acme_common'],
).each do |t|
  puts "Acme Common transferred in tx #{t.id}"
end
client.transactions.queryAll({
  filter: 'inputs(type=$1 AND asset_alias=$2)',
  filterParams: ['spend', 'acme_common'],
}, (tx, next) => {
  console.log('Acme Common transferred in tx ' + tx.id)
  next()
})
  • Java
  • Node
  • Ruby

Retirement transactions

To list transactions where Acme Common stock was retired, we build an assets query, filtering on outputs with the retire action and the Acme Common stock asset_alias.

Transaction.Items acmeCommonRetirements = new Transaction.QueryBuilder()
  .setFilter("outputs(type=$1 AND asset_alias=$2)")
  .addFilterParameter("retire")
  .addFilterParameter("acme_common")
  .execute(client);

while (acmeCommonRetirements.hasNext()) {
  Transaction tx = acmeCommonRetirements.next();
  System.out.println("Acme Common retired in tx " + tx.id);
}
chain.transactions.query(
  filter: 'outputs(type=$1 AND asset_alias=$2)',
  filter_params: ['retire', 'acme_common'],
).each do |t|
  puts "Acme Common retired in tx #{t.id}"
end
client.transactions.queryAll({
  filter: 'outputs(type=$1 AND asset_alias=$2)',
  filterParams: ['retire', 'acme_common'],
}, (tx, next) => {
  console.log('Acme Common retired in tx ' + tx.id)
  next()
})
  • Java
  • Node
  • Ruby

Get asset circulation

The circulation of an asset is the sum of all non-retired units of that asset existing in unspent transaction outputs in the blockchain, regardless of control program.

To list the circulation of Acme Common stock, we build a balance query, filtering on the Acme Common stock asset_alias.

Balance.Items acmeCommonBalances = new Balance.QueryBuilder()
  .setFilter("asset_alias=$1")
  .addFilterParameter("acme_common")
  .execute(client);

Balance acmeCommonBalance = acmeCommonBalances.next();
System.out.println("Total circulation of Acme Common: " + acmeCommonBalance.amount);
chain.balances.query(
  filter: 'asset_alias=$1',
  filter_params: ['acme_common'],
).each do |b|
  puts "Total circulation of Acme Common: #{b.amount}"
end
client.balances.queryAll({
  filter: 'asset_alias=$1',
  filterParams: ['acme_common'],
}, (balance, next) => {
  console.log('Total circulation of Acme Common: ' + balance.amount)
  next()
})
  • Java
  • Node
  • Ruby

To list the circulation of all classes of Acme stock, we build a balance query, filtering on the issuer field in the definition.

Balance.Items acmeAnyBalances = new Balance.QueryBuilder()
  .setFilter("asset_definition.issuer=$1")
  .addFilterParameter("Acme Inc.")
  .execute(client);

while (acmeAnyBalances.hasNext()) {
  Balance stockBalance = acmeAnyBalances.next();
  System.out.println(
    "Total circulation of Acme stock " + stockBalance.sumBy.get("asset_alias") +
    ": " + stockBalance.amount
  );
}
chain.balances.query(
  filter: 'asset_definition.issuer=$1',
  filter_params: ['Acme Inc.'],
).each do |b|
  puts "Total circulation of Acme stock #{b.sum_by['asset_alias']}: #{b.amount}"
end
client.balances.queryAll({
  filter: 'asset_definition.issuer=$1',
  filterParams: ['Acme Inc.'],
}, (balance, next) => {
  console.log('Total circulation of Acme stock ' + balance.sumBy.assetAlias + ': ' + balance.amount)
  next()
})
  • Java
  • Node
  • Ruby

To list all the control programs that hold a portion of the circulation of Acme Common stock, we build an unspent outputs query, filtering on the Acme Common stock asset_alias.

UnspentOutput.Items acmeCommonUnspentOutputs = new UnspentOutput.QueryBuilder()
  .setFilter("asset_alias=$1")
  .addFilterParameter("acme_common")
  .execute(client);

while (acmeCommonUnspentOutputs.hasNext()) {
  UnspentOutput utxo = acmeCommonUnspentOutputs.next();
  System.out.println("Acme Common held in output " + utxo.transactionId + ":" + utxo.position);
}
chain.unspent_outputs.query(
  filter: 'asset_alias=$1',
  filter_params: ['acme_common'],
).each do |u|
  puts "Acme Common held in output #{u.transaction_id}:#{u.position}"
end
client.unspentOutputs.queryAll({
  filter: 'asset_alias=$1',
  filterParams: ['acme_common'],
}, (unspent, next) => {
  console.log('Acme Common held in output ' + unspent.transactionId + ': ' + unspent.position)
  next()
})
  • Java
  • Node
  • Ruby