Transfer metadata enables control of how CoinSpark assets flow across a bitcoin transaction.

By default, all units of all CoinSpark assets in the inputs of a bitcoin transaction automatically flow to the last non-OP_RETURN output of that bitcoin transaction, and no CoinSpark payment charges are applied to those units. As a rule, CoinSpark wallets must use the last output of a bitcoin transaction to send change back to the user. Combining these two facts means that CoinSpark assets are safely conserved in a user’s wallet unless explicitly sent elsewhere.

Asset units are sent to outputs other than the last by adding transfer metadata to a transaction, and ensuring that the transaction has a sufficient bitcoin transaction fee to make that metadata valid. The transfer metadata encodes one or more CoinSparkTransfers, each of which specifies how assets of a particular type should be sent from inputs to outputs. CoinSpark uses an efficient scheme to encode as many CoinSparkTransfers as possible within the 40 byte limit for OP_RETURNs.

The transfer metadata is encoded in a transaction’s first OP_RETURN output according to CoinSpark’s general metadata format. Unless it is combined with other CoinSpark metadata, the prefix will be SPKt (0x53 0x50 0x4B 0x74).

Any asset units sent to outputs other than the last may be subject to CoinSpark payment charges, which are chosen by the issuer of that asset. The payment charge for a particular asset type is applied to the final balance of that asset in each transaction output other than the last. It can be a fixed number of asset units or a proportion of the final balance, as set in the genesis metadata of the transaction which created that asset. (In order to facilitate peer-to-peer exchange, transfer metadata can also change the default output for some inputs, but this is not yet documented.)

You can easily encode and extract CoinSpark transfer metadata using the appropriate CoinSpark library for your programming language. There are also functions in the library for calculating payment charges and the transaction output balances for CoinSpark assets.

CoinSparkTransfer fields

A CoinSparkTransfer structure or object describes how assets of a particular type should be sent from inputs to outputs. It contains the following fields:

  • assetRef – the asset reference pointing to the genesis which created the asset.
  • inputs.first – index of the first transaction input from which units of this asset should be transferred.
  • inputs.count – number of consecutive inputs from which units of this asset should be transferred.
  • outputs.first – index of the first transaction output to which units of this asset should be transferred.
  • outputs.count – number of consecutive outputs to which units of this asset should be transferred.
  • qtyPerOutput – how many units of this asset should be transferred to each output (before possible payment charges).

As mentioned above, the transfer metadata can encode multiple CoinSparkTransfer structures. Some or all of these may refer to the same asset type via the assetRef field, in which case the asset reference is only encoded once in the metadata.

When transferring asset units from a transaction’s inputs to outputs, the CoinSparkTransfers are applied in the order in which they are encoded in the metadata. When applying a CoinSparkTransfer, asset units are taken from the first specified transaction input until is empty, then from the second input, etc.., until all specified outputs have received qtyPerOutput units. If the inputs do not have sufficient asset units for all of the outputs, each output receives up to qtyPerOutput units in turn until all of the available units have been used.

Payment charges, as specified in the genesis metadata of the transaction which created the asset, are applied to all outputs except the last once the transfers are complete.

To ensure consistency and ease of use, all of this logic is handled by the CoinSpark library – please see the example below.

Creating and encoding transfer metadata

C/C++

CoinSparkTransfer transfers[2];
char assetReference[]="456789-65432-23456";
int countTransfers, countInputs, countOutputs;
char metadata[40]; // assume 40 byte limit for OP_RETURNs
size_t metadataLen;

countInputs=3;
countOutputs=5;
countTransfers=sizeof(transfers)/sizeof(*transfers);

CoinSparkAssetRefDecode(&transfers[0].assetRef, assetReference, strlen(assetReference));
transfers[0].inputs.first=0;
transfers[0].inputs.count=2; // transfer from inputs 0 and 1
transfers[0].outputs.first=0;
transfers[0].outputs.count=1; // transfer to outputs 0 only
transfers[0].qtyPerOutput=123;

transfers[1].assetRef=transfers[0].assetRef; // second transfer is for same asset type
transfers[1].inputs.first=2;
transfers[1].inputs.count=1; // transfer from input 2 only
transfers[1].outputs.first=1;
transfers[1].outputs.count=3; // transfer to outputs 1, 2 and 3
transfers[1].qtyPerOutput=456;

metadataLen=CoinSparkTransfersEncode(transfers, countTransfers, countInputs, countOutputs,
    metadata, sizeof(metadata));

if (metadataLen)
    ; // use CoinSparkMetadataToScript() to embed metadata (length metadataLen) in an output script
else
    ; // handle error

Java

int countInputs=3;
int countOutputs=5;
CoinSparkTransferList transferList=new CoinSparkTransferList();

CoinSparkTransfer transfer=new CoinSparkTransfer();
CoinSparkAssetRef assetRef=new CoinSparkAssetRef();
assetRef.decode("456789-65432-23456");
transfer.setAssetRef(assetRef);
transfer.setInputs(new CoinSparkIORange(0, 2)); // transfer from inputs 0 and 1
transfer.setOutputs(new CoinSparkIORange(0, 1)); // transfer to outputs 0 only
transfer.setQtyPerOutput(123);
transferList.setTransfer(0, transfer);

transfer=new CoinSparkTransfer();
transfer.setAssetRef(transferList.getTransfer(0).getAssetRef()); // second transfer is for same asset type
transfer.setInputs(new CoinSparkIORange(2, 1)); // transfer from input 2 only
transfer.setOutputs(new CoinSparkIORange(1, 3)); // transfer to outputs 1, 2 and 3
transfer.setQtyPerOutput(456);    
transferList.setTransfer(1, transfer);

byte[] metadata=transferList.encode(countInputs, countOutputs, 40); // 40 byte limit for OP_RETURNs

if (metadata!=null)
    ; // use CoinSparkBase.metadataToScript() to embed metadata in an output script
else
    ; // handle error

Javascript

var countInputs=3;
var countOutputs=5;
var transferList=new CoinSparkTransferList();

var transfer=new CoinSparkTransfer();
transfer.assetRef.decode("456789-65432-23456");
transfer.inputs.first=0;
transfer.inputs.count=2; // transfer from inputs 0 and 1
transfer.outputs.first=0;
transfer.outputs.count=1; // transfer to outputs 0 only
transfer.qtyPerOutput=123;
transferList.transfers[0]=transfer;

transfer=new CoinSparkTransfer();
transfer.assetRef=transferList.transfers[0].assetRef; // second transfer is for same asset type
transfer.inputs.first=2;
transfer.inputs.count=1; // transfer from input 2 only
transfer.outputs.first=1;
transfer.outputs.count=3; // transfer to outputs 1, 2 and 3
transfer.qtyPerOutput=456;
transferList.transfers[1]=transfer;

var metadata=transferList.encode(countInputs, countOutputs, 40); // 40 byte limit for OP_RETURN

if (metadata)
    ; // use CoinSparkMetadataToScript() to embed metadata in an output script
else
    ; // handle error

PHP

$countInputs=3;
$countOutputs=5;
$transferList=new CoinSparkTransferList();

$transfer=new CoinSparkTransfer();
$transfer->assetRef->decode("456789-65432-23456");
$transfer->inputs->first=0;
$transfer->inputs->count=2; // transfer from inputs 0 and 1
$transfer->outputs->first=0;
$transfer->outputs->count=1; // transfer to outputs 0 only
$transfer->qtyPerOutput=123;
$transferList->transfers[0]=$transfer;

$transfer=new CoinSparkTransfer();
$transfer->assetRef=$transfers->transfers[0]->assetRef; // second transfer is for same asset type
$transfer->inputs->first=2;
$transfer->inputs->count=1; // transfer from input 2 only
$transfer->outputs->first=1;
$transfer->outputs->count=3; // transfer to outputs 1, 2 and 3
$transfer->qtyPerOutput=456;
$transferList->transfers[1]=$transfer;

$metadata=$transferList->encode($countInputs, $countOutputs, 40); // 40 byte limit for OP_RETURN

if (isset($metadata))
    ; // use CoinSparkMetadataToScript() to embed $metadata in an output script
else
    ; // handle error

Python

countInputs=3
countOutputs=5
transferList=CoinSparkTransferList()

transfer=CoinSparkTransfer()
transfer.assetRef.decode("456789-65432-23456")
transfer.inputs.first=0 # transfer from inputs 0 and 1
transfer.inputs.count=2
transfer.outputs.first=0 # transfer to outputs 0 only
transfer.outputs.count=1
transfer.qtyPerOutput=123
transferList.transfers.append(transfer)

transfer=CoinSparkTransfer()
transfer.assetRef=transferList.transfers[0].assetRef # second transfer is for same asset type
transfer.inputs.first=2 # transfer from input 2 only
transfer.inputs.count=1
transfer.outputs.first=1 # transfer to outputs 1, 2 and 3
transfer.outputs.count=3
transfer.qtyPerOutput=456
transferList.transfers.append(transfer)

metadata=transferList.encode(countInputs, countOutputs, 40) # 40 byte limit for OP_RETURNs

if not metadata is None:
     # use CoinSparkMetadataToScript() to embed metadata in an output script
else:
     # handle error

Ruby

countInputs=3
countOutputs=5
transferList=CoinSparkTransferList.new

transfer=CoinSparkTransfer.new
transfer.assetRef.decode("456789-65432-23456")
transfer.inputs.first=0 # transfer from inputs 0 and 1
transfer.inputs.count=2
transfer.outputs.first=0 # transfer to outputs 0 only
transfer.outputs.count=1
transfer.qtyPerOutput=123
transferList.transfers.push(transfer)

transfer=CoinSparkTransfer.new
transfer.assetRef=transferList.transfers[0].assetRef # second transfer is for same asset type
transfer.inputs.first=2 # transfer from input 2 only
transfer.inputs.count=1
transfer.outputs.first=1 # transfer to outputs 1, 2 and 3
transfer.outputs.count=3
transfer.qtyPerOutput=456
transferList.transfers.push(transfer)

metadata=transferList.encode(countInputs, countOutputs, 40) # 40 byte limit for OP_RETURNs

if metadata!=nil
     # use CoinSparkMetadataToScript() to embed metadata in an output script
else
     # handle error
end

Calculation asset transfer output balances

C/C++

void CalcTransactionOutputBalances(
    const CoinSparkAssetRef* assetRef, const CoinSparkGenesis* genesis,
    const CoinSparkTransfer* transfers, const int countTransfers,
    const CoinSparkSatoshiQty minFeeSatoshis, const CoinSparkSatoshiQty feeSatoshis,
    const CoinSparkAssetQty* inputBalances, const int countInputs,
    CoinSparkAssetQty* outputBalances, const bool* outputsRegular, const int countOutputs,
) {
    // assetRef and genesis provide information about the CoinSpark asset type of interest.
    // transfers is an array (size countTransfers) from the decoded transfer metadata -
    // If there is no transfer metadata, pass NULL and 0 respectively.
    // minFeeSatoshis is the minimum transaction fee required to make those transfers valid.
    // feeSatoshis is the quantity of bitcoin satoshis in this transaction's fee.
    // inputBalances is an array (size countInputs) of the asset's balances in the transaction's inputs.
    // outputsRegular (size countOutputs) indicates which outputs are regular, via CoinSparkScriptIsRegular().

    // Puts the balances of that asset in each output in outputBalances (size countOutputs)

    if (feeSatoshis>=minFeeSatoshis)
        CoinSparkTransfersApply(assetRef, genesis, transfers, countTransfers,
            inputBalances, countInputs, outputsRegular, outputBalances, countOutputs);
    else
        CoinSparkTransfersApplyNone(assetRef, genesis,
            inputBalances, countInputs, outputsRegular, outputBalances, countOutputs);
}

Java

public static long[] CalcTransactionOutputBalances(
    CoinSparkAssetRef assetRef, CoinSparkGenesis genesis, 
    CoinSparkTransferList transferList,
    long minFeeSatoshis, long feeSatoshis, 
    long[] inputBalances, boolean[] outputsRegular)
{
    // assetRef is a CoinSparkAssetRef object describing the CoinSpark asset type of interest.
    // genesis is a CoinSparkGenesis object describing the same asset type.
    // transferList is a CoinSparkTransferList object from the decoded transfer metadata -
    // If there is no transfer metadata, you can pass in a new CoinSparkTransferList().
    // minFeeSatoshis is the minimum transaction fee required to make those transfers valid.
    // feeSatoshis is the quantity of bitcoin satoshis in this transaction's fee.
    // inputBalances is an array of the asset's balances in the transaction's inputs.
    // outputsRegular is an array indicating which outputs are regular, via CoinSparkBase.scriptIsRegular().
   
    // Returns the balances of that asset in each output

    long[] outputBalances;

    if (feeSatoshis>=minFeeSatoshis)
        outputBalances=transferList.apply(assetRef, genesis, inputBalances, outputsRegular);
    else
        outputBalances=transferList.applyNone(assetRef, genesis, inputBalances, outputsRegular);

    return outputBalances;
}

Javascript

function CalcTransactionOutputBalances(assetRef, genesis, transferList,
    minFeeSatoshis, feeSatoshis, inputBalances, outputsRegular)
{
    // assetRef is a CoinSparkAssetRef object describing the CoinSpark asset type of interest.
    // genesis is a CoinSparkGenesis object describing the same asset type.
    // transferList is a CoinSparkTransferList object from the decoded transfer metadata -
    // If there is no transfer metadata, you can pass in a new CoinSparkTransferList().
    // minFeeSatoshis is the minimum transaction fee required to make those transfers valid.
    // feeSatoshis is the quantity of bitcoin satoshis in this transaction's fee.
    // inputBalances is an array of the asset's balances in the transaction's inputs.
    // outputsRegular is an array indicating which outputs are regular, via CoinSparkScriptIsRegular().

    // Returns the balances of that asset in each output

    if (feeSatoshis>=minFeeSatoshis)
        var outputBalances=transferList.apply(assetRef, genesis, inputBalances, outputsRegular);
    else
        var outputBalances=transferList.applyNone(assetRef, genesis, inputBalances, outputsRegular);

    return outputBalances;
}

PHP

function CalcTransactionOutputBalances($assetRef, $genesis, $transferList,
    $minFeeSatoshis, $feeSatoshis, $inputBalances, $outputsRegular)
{
    // $assetRef is a CoinSparkAssetRef object describing the CoinSpark asset type of interest.
    // $genesis is a CoinSparkGenesis object describing the same asset type.
    // $transferList is a CoinSparkTransferList object from the decoded transfer metadata -
    // If there is no transfer metadata, you can pass in a new CoinSparkTransferList().
    // $minFeeSatoshis is the minimum transaction fee required to make those transfers valid.
    // $feeSatoshis is the quantity of bitcoin satoshis in this transaction's fee.
    // $inputBalances is an array of the asset's balances in the transaction's inputs.
    // $outputsRegular is an array indicating which outputs are regular, via CoinSparkScriptIsRegular().

    // Returns the balances of that asset in each output

    if ($feeSatoshis>=$minFeeSatoshis)
        $outputBalances=$transferList->apply($assetRef, $genesis, $inputBalances, $outputsRegular);
    else
        $outputBalances=$transferList->applyNone($assetRef, $genesis, $inputBalances, $outputsRegular);

    return $outputBalances;
}

Python

def CalcTransactionOutputBalances(assetRef, genesis, transferList,
        minFeeSatoshis, feeSatoshis, inputBalances, outputsRegular):

    # assetRef is a CoinSparkAssetRef object describing the CoinSpark asset type of interest.
    # genesis is a CoinSparkGenesis object describing the same asset type.
    # transferList is a CoinSparkTransferList object from the decoded transfer metadata.
    # If there is no transfer metadata, you can pass in CoinSparkTransferList().
    # minFeeSatoshis is the minimum transaction fee required to make those transfers valid.
    # feeSatoshis is the quantity of bitcoin satoshis in this transaction's fee.
    # inputBalances is an array of the asset's balances in the transaction's inputs.
    # outputsRegular is an array indicating which outputs are regular, via CoinSparkScriptIsRegular().

    # Returns the balances of that asset in each output

    if feeSatoshis>=minFeeSatoshis:
        outputBalances=transferList.apply(assetRef, genesis, inputBalances, outputsRegular)
    else:
        outputBalances=transferList.applyNone(assetRef, genesis, inputBalances, outputsRegular)

    return outputBalances

Ruby

def CalcTransactionOutputBalances(assetRef, genesis, transferList,
        minFeeSatoshis, feeSatoshis, inputBalances, outputsRegular)

    # assetRef is a CoinSparkAssetRef object describing the CoinSpark asset type of interest.
    # genesis is a CoinSparkGenesis object describing the same asset type.
    # transferList is a CoinSparkTransferList object from the decoded transfer metadata.
    # If there is no transfer metadata, you can pass in CoinSparkTransferList.new
    # minFeeSatoshis is the minimum transaction fee required to make those transfers valid.
    # feeSatoshis is the quantity of bitcoin satoshis in this transaction's fee.
    # inputBalances is an array of the asset's balances in the transaction's inputs.
    # outputsRegular is an array indicating which outputs are regular, via CoinSparkScriptIsRegular().

    # Returns the balances of that asset in each output

    if feeSatoshis>=minFeeSatoshis
        outputBalances=transferList.apply(assetRef, genesis, inputBalances, outputsRegular)
    else
        outputBalances=transferList.applyNone(assetRef, genesis, inputBalances, outputsRegular)
    end

    return outputBalances

Extracting and decoding transfer metadata

Please see the general metadata sample code.