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 CoinSparkTransfer
s 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.