Assets
Arkade supports programmable assets — tokens that live alongside BTC in VTXOs. Asset state is encoded in an AssetGroup entry inside an OP_RETURN output attached to each Arkade transaction (an "asset packet"). Asset IDs are derived from {txid, groupIndex} after submission.
Asset operations go through IAssetManager (registered by AddArkCoreServices).
Issuance
Create a new asset on first issuance:
var result = await assetManager.IssueAsync(walletId,
new IssuanceParams(Amount: 1_000_000));
// result.AssetId — the unique asset identifier
// result.ArkTxId — the Arkade transaction that created it
With metadata:
var result = await assetManager.IssueAsync(walletId,
new IssuanceParams(
Amount: 1_000_000,
Metadata: new Dictionary<string, string>
{
["name"] = "MyToken",
["ticker"] = "MTK",
}));
Metadata is preserved in insertion order (it is stored as a BIP-spec varint-length-prefixed list, not a hashmap).
Controlled Issuance & Reissuance
A control asset acts as a minting key — only the holder can issue more supply of the controlled asset:
// 1. Issue a control asset (amount=1, acts as the minting authority)
var control = await assetManager.IssueAsync(walletId,
new IssuanceParams(Amount: 1));
// 2. Issue a token controlled by that asset
var token = await assetManager.IssueAsync(walletId,
new IssuanceParams(Amount: 1_000_000, ControlAssetId: control.AssetId));
// 3. Reissue more supply later (requires holding the control asset VTXO)
await assetManager.ReissueAsync(walletId,
new ReissuanceParams(control.AssetId, Amount: 500_000));
Reissuance keeps the control asset VTXO intact — only the supply of the controlled token increases.
Transfer
Asset transfers go through the standard SpendingService.Spend() call — attach ArkTxOutAsset entries to an ArkTxOut:
var serverInfo = await clientTransport.GetServerInfoAsync(ct);
await spendingService.Spend(walletId, new[]
{
new ArkTxOut(ArkTxOutType.Vtxo, serverInfo.Dust, recipientAddress)
{
Assets = [new ArkTxOutAsset(assetId, Amount: 500)],
},
}, ct);
The BTC output amount can be the server's dust minimum — the VTXO's value is the asset payload, not the sats.
Burn
Send the asset to a burn output (an OP_RETURN with no destination contract):
await assetManager.BurnAsync(walletId,
new BurnParams(assetId, Amount: 100));
Querying Assets
var vtxos = await vtxoStorage.GetVtxos(walletIds: [walletId]);
foreach (var vtxo in vtxos.Where(v => v.Assets?.Any() == true))
{
foreach (var asset in vtxo.Assets!)
Console.WriteLine($"Asset: {asset.AssetId}, Amount: {asset.Amount}");
}
Asset metadata can be fetched from arkd's indexer:
var assetInfo = await clientTransport.GetAssetAsync(assetId, ct);
// assetInfo.Metadata is already decoded into a Dictionary<string, string>
Binary Format
Assets are encoded in OP_RETURN outputs using a compact binary format:
ARKmagic bytes + marker byte + groups- Presence byte bitfields:
0x01AssetId,0x02ControlAsset,0x04Metadata - Each
AssetOutputcarries a 0x01 type byte + uint16-LE vout + varint amount - Metadata is a varint-length-prefixed list preserving insertion order
- The asset Merkle tree is aligned with BIP-341 taptree ordering