CoinSpark makes extensive use of metadata attached to bitcoin transactions.

CoinSpark metadata is encoded in an OP_RETURN transaction output, following the support added in Bitcoin Core 0.9. By design, OP_RETURNs contain no units of the bitcoin currency, cannot be spent, and are not stored in memory by bitcoin nodes. If a bitcoin transaction contains more than one OP_RETURN, the CoinSpark metadata must be embedded in the first such output, otherwise it is ignored.

The output script for CoinSpark metadata begins with the OP_RETURN opcode (0x6A) followed by a length byte between 0 and 40 (0x000x28). This is the pattern followed by all OP_RETURN scripts and is not specific to the CoinSpark protocol – see Coin Secrets for some examples.

For CoinSpark the next three bytes in the script contain the identifier SPK (0x53 0x50 0x4B). This is followed by one of:

In the event of multiple CoinSpark fields, the length byte defines the length of the first field, including its prefix character. The second field begins 1+length_byte bytes later, and should be treated as if it had immediately followed SPK. If the second field starts with a length byte, there can be a third field, etc…

You can easily extract and encode CoinSpark metadata using the appropriate CoinSpark library for your programming language.

Extracting CoinSpark metadata from a transaction

C/C++

void ProcessTransaction(const char* scriptPubKeys[], const size_t scriptPubKeyLens[],
    const bool scriptsAreHex, const int countInputs, const int countOutputs)
{
    // scriptPubKeys is an array of pointers to each output script of a transaction.
    // Depending on scriptsAreHex, scriptPubKeys contains hex strings or raw binary data.
    // scriptPubKeyLens is an array of byte lengths for each element of scriptPubKeys.

    char metadata[1024], debugString[1024];
    size_t metadataLen;
    CoinSparkGenesis genesis;
    int countTransfers, transferIndex;
    CoinSparkTransfer transfers;
    CoinSparkPaymentRef paymentRef;
    CoinSparkMessage message;
 
   metadataLen=CoinSparkScriptsToMetadata(scriptPubKeys, scriptPubKeyLens, scriptsAreHex,
        countOutputs, metadata, sizeof(metadata));

    if (metadataLen) {
        if (CoinSparkGenesisDecode(&genesis, metadata, metadataLen)) {
            CoinSparkGenesisToString(&genesis, debugString, sizeof(debugString));
            printf(debugString);
        }

        countTransfers=CoinSparkTransfersDecodeCount(metadata, metadataLen);
        if (transferCount) {
            transfers=(CoinSparkTransfer*)malloc(countTransfers*sizeof(CoinSparkTransfer));

            CoinSparkTransfersDecode(transfers, countTransfers, countInputs, countOutputs,
                metadata, metadataLen);

            for (transferIndex=0; transferIndex<countTransfers; transferIndex++) {
                CoinSparkTransferToString(transfers+transferIndex, debugString, sizeof(debugString));
                printf(debugString);
            }

            free(transfers);
        }

        if (CoinSparkPaymentRefDecode(&paymentRef, metadata, metadataLen)) {
            CoinSparkPaymentRefToString(paymentRef, debugString, sizeof(debugString));
            printf(debugString);
        }

        if (CoinSparkMessageDecode(&message, countOutputs, metadata, metadataLen)) {
            CoinSparkMessageToString(&message, debugString, sizeof(debugString));
            printf(debugString);
        }
    }
}

Java

// public static void ProcessTransaction(byte[][] scriptPubKeys, int countInputs)

public static void ProcessTransaction(String[] scriptPubKeys, int countInputs)
{
    // scriptPubKeys is an array containing each output script of a transaction as a hex string
    // or raw binary (commented above). The transaction has scriptPubKeys.length outputs and
    // countInputs inputs.

    byte[] metadata=CoinSparkBase.scriptsToMetadata(scriptPubKeys);
    
    if (metadata!=null) {
        CoinSparkGenesis genesis=new CoinSparkGenesis();                                
        if (genesis.decode(metadata))
            System.out.print(genesis.toString());
                    
        CoinSparkTransferList transferList=new CoinSparkTransferList();
        if (transferList.decode(metadata, countInputs, scriptPubKeys.length))
            System.out.print(transferList.toString());                    
    
        CoinSparkPaymentRef paymentRef=new CoinSparkPaymentRef();
        if (paymentRef.decode(metadata))
            System.out.print(paymentRef.toString());

        CoinSparkMessage message=new CoinSparkMessage();
        if (message.decode(metadata, scriptPubKeys.length))
            System.out.print(message.toString());
    }
}

Javascript

function ProcessTransaction(scriptPubKeys, scriptsAreHex, countInputs)
{
    // scriptPubKeys is an array containing each output script of a transaction, so that
    // the transaction has scriptPubKeys.length outputs and countInputs inputs.
    // Depending on scriptsAreHex, scriptPubKeys contains hex strings or Uint8Arrays of raw data.
    
    var metadata=CoinSparkScriptsToMetadata(scriptPubKeys, scriptsAreHex);

    if (metadata) {
        var genesis=new CoinSparkGenesis();
        if (genesis.decode(metadata))
            console.log(genesis.toString());

        var transferList=new CoinSparkTransferList();
        if (transferList.decode(metadata, countInputs, scriptPubKeys.length))
            console.log(transferList.toString());

        var paymentRef=new CoinSparkPaymentRef();
        if (paymentRef.decode(metadata))
            console.log(paymentRef.toString());

        var message=new CoinSparkMessage();
        if (message.decode(metadata, scriptPubKeys.length))
            console.log(message.toString());
    }
}

PHP

function ProcessTransaction($scriptPubKeys, $scriptsAreHex, $countInputs)
{
    // $scriptPubKeys is an array containing each output script of a transaction, so that
    // the transaction has count($scriptPubKeys) outputs and $countInputs inputs.
    // Depending on $scriptsAreHex, $scriptPubKeys contains hex strings or raw binary data.
    
    $metadata=CoinSparkScriptsToMetadata($scriptPubKeys, $scriptsAreHex);

    if (isset($metadata)) {
        $genesis=new CoinSparkGenesis();
        if ($genesis->decode($metadata))
            echo $genesis->toString();

        $transferList=new CoinSparkTransferList();
        if ($transferList->decode($metadata, $countInputs, count($scriptPubKeys)))
            echo $transferList->toString();

        $paymentRef=new CoinSparkPaymentRef();
        if ($paymentRef->decode($metadata))
            echo $paymentRef->toString();
 
        $message=new CoinSparkMessage();
        if ($message->decode($metadata, count($scriptPubKeys)))
            echo $message->toString();
   }
}

Python

def ProcessTransaction(scriptPubKeys, scriptsAreHex, countInputs):
    # scriptPubKeys is an array containing each output script of a transaction, so that
    # the transaction has len(scriptPubKeys) outputs and countInputs inputs.
    # Depending on scriptsAreHex, scriptPubKeys contains hex strings or raw binary data.

    metadata=CoinSparkScriptsToMetadata(scriptPubKeys, scriptsAreHex)

    if not metadata is None:
        genesis=CoinSparkGenesis()                                
        if genesis.decode(metadata):
            sys.stdout.write(genesis.toString())
            
        transferList=CoinSparkTransferList()
        if transferList.decode(metadata, countInputs, len(scriptPubKeys)):
            sys.stdout.write(transferList.toString())                    

        paymentRef=CoinSparkPaymentRef()
        if paymentRef.decode(metadata):
            sys.stdout.write(paymentRef.toString())

        message=CoinSparkMessage()
        if message.decode(metadata, len(scriptPubKeys)):
            sys.stdout.write(message.toString())

Ruby

def ProcessTransaction(scriptPubKeys, scriptsAreHex, countInputs)
    # scriptPubKeys is an array containing each output script of a transaction, so that
    # the transaction has len(scriptPubKeys) outputs and countInputs inputs.
    # Depending on scriptsAreHex, scriptPubKeys contains hex strings or raw binary data.

    metadata=CoinSparkScriptsToMetadata(scriptPubKeys, scriptsAreHex)

    if metadata!=nil
        genesis=CoinSparkGenesis.new                              
        print(genesis.toString) if genesis.decode(metadata)
            
        transferList=CoinSparkTransferList.new
        print(transferList.toString) if transferList.decode(metadata, countInputs, scriptPubKeys.length)>0   

        paymentRef=CoinSparkPaymentRef.new
        print(paymentRef.toString) if paymentRef.decode(metadata)

        message=CoinSparkMessage.new
        print(message.toString) if message.decode(metadata, scriptPubKeys.length)
    end
end     

Encoding CoinSpark metadata in a script

C/C++

    char metadata[1024], scriptPubKey[1024];
    size_t metadataLen, scriptPubKeyLen;

    // first get metadata and metadataLen from one of:
    // CoinSparkGenesisEncode(), CoinSparkTransfersEncode(), CoinSparkPaymentRefEncode()
    // or CoinSparkMessageEncode(). Or use CoinSparkMetadataAppend() to combine.

    if (metadataLen) {
        scriptPubKeyLen=CoinSparkMetadataToScript(metadata, metadataLen,
            scriptPubKey, sizeof(scriptPubKey), FALSE); // TRUE would encode as hex string
        
        if (scriptPubKeyLen)
            ; // embed the first scriptPubKeyLen bytes of scriptPubKey directly in a transaction output
        else
            ; // handle the error

    } else
        ; // handle the error

Java

    // first get metadata from the encode() method of a CoinSparkGenesis, CoinSparkTransferList
    // CoinSparkPaymentRef or CoinSparkMessage object. Or CoinSparkBase.metadataAppend() to combine.

    if (metadata!=null) {
        String scriptPubKey=CoinSparkBase.metadataToScriptHex(metadata);

        if (scriptPubKey!=null)
            System.out.println("Script: "+scriptPubKey);
        else
            System.out.println("Metadata encode failed!");

    } else
        ; // handle the error

Javascript

    // first get metadata from the encode() method of a CoinSparkGenesis, CoinSparkTransferList
    // CoinSparkPaymentRef or CoinSparkMessage object. Or CoinSparkMetadataAppend() to combine.

    if (metadata) {
        var scriptPubKey=CoinSparkMetadataToScript(metadata, false); // true would encode as hex string
        
        if (scriptPubKey)
            ; // now embed the scriptPubKey UInt8Array of bytes directly in a transaction output
        else
            ; // handle the error

    } else
        ; // handle the error

PHP

    // first get $metadata from the encode() method of a CoinSparkGenesis, CoinSparkTransferList
    // CoinSparkPaymentRef or CoinSparkMessage object. Or CoinSparkMetadataAppend() to combine.

    if (isset($metadata)) {
        $scriptPubKey=CoinSparkMetadataToScript($metadata, false); // true would encode as hex string
        
        if (isset($scriptPubKey))
            ; // now embed the raw bytes in $scriptPubKey directly in a transaction output
        else
            ; // handle the error

    } else
        ; // handle the error

Python

# first get metadata from the encode() method of a CoinSparkGenesis, CoinSparkTransferList
# CoinSparkPaymentRef or CoinSparkMessage object. Or CoinSparkMetadataAppend() to combine.

if not metadata is None:
    scriptPubKey=CoinSparkMetadataToScript(metadata, False) # True would encode as a hex string

    if not scriptPubKey is None:
        # now embed the raw bytes in scriptPubKey directly in a transaction output
    else:
        # handle the error

else:
     # handle the error

Ruby

# first get metadata from the encode() method of a CoinSparkGenesis, CoinSparkTransferList
# CoinSparkPaymentRef or CoinSparkMessage object. Or CoinSparkMetadataAppend() to combine.

if metadata!=nil
    scriptPubKey=CoinSparkMetadataToScript(metadata, false) # true would encode as a hex string

    if scriptPubKey!=nil
        # now embed the raw bytes in scriptPubKey directly in a transaction output
    else
        # handle the error
    end
else
     # handle the error
end

Example combining two types of metadata

C/C++

size_t CoinSparkPaymentRefTransfersEncode(const CoinSparkPaymentRef paymentRef,
    const CoinSparkTransfer* transfers, const int countTransfers,
    const int countInputs, const int countOutputs,
    char* metadata, const size_t metadataMaxLen)
{
    size_t metadataLen, appendMetadataMaxLen, appendMetadataLen;
    char *appendMetadata;

    metadataLen=CoinSparkPaymentRefEncode(paymentRef, metadata, metadataMaxLen);
    if (!metadataLen)
        return 0;

    appendMetadataMaxLen=CoinSparkMetadataMaxAppendLen(metadata, metadataLen, metadataMaxLen);
        // this is not simply metadataMaxLen-metadataLen since combining saves space

    appendMetadata=(char*)malloc(appendMetadataMaxLen);
    appendMetadataLen=CoinSparkTransfersEncode(transfers, countTransfers,
        countInputs, countOutputs, appendMetadata, appendMetadataMaxLen);

    if (appendMetadataLen)
        metadataLen=CoinSparkMetadataAppend(metadata, metadataLen, metadataMaxLen,
            appendMetadata, appendMetadataLen);
    else
        metadataLen=0;

    free(appendMetadata);

    return metadataLen;    
}

Java

public static byte[] CoinSparkPaymentRefTransfersEncode(CoinSparkPaymentRef paymentRef,
    CoinSparkTransferList transferList, int countInputs, int countOutputs, int metadataMaxLen)
{
    byte[] metadata=paymentRef.encode(metadataMaxLen);
    if (metadata==null)
        return null;

    int appendMetadataMaxLen=CoinSparkBase.metadataMaxAppendLen(metadata, metadataMaxLen);
        // this is not simply metadataMaxLen-metadata.length since combining saves space

    byte[] appendMetaData=transferList.encode(countInputs, countOutputs, appendMetadataMaxLen);
    if (appendMetaData==null)
        return null;
   
    return CoinSparkBase.metadataAppend(metadata, metadataMaxLen, appendMetaData);
}

Javascript

function CoinSparkPaymentRefTransfersEncode(paymentRef, transferList,
    countInputs, countOutputs, metadataMaxLen)
{
    var metadata=paymentRef.encode(metadataMaxLen);
    if (!metadata)
        return null;

    var appendMetadataMaxLen=CoinSparkMetadataMaxAppendLen(metadata, metadataMaxLen);
        // this is not simply metadataMaxLen-metadata.length since combining saves space

    appendMetadata=transferList.encode(countInputs, countOutputs, appendMetadataMaxLen);
    if (!appendMetadata)
        return null;

    return CoinSparkMetadataAppend(metadata, metadataMaxLen, appendMetadata);
}

PHP

function CoinSparkPaymentRefTransfersEncode($paymentRef, $transferList,
    $countInputs, $countOutputs, $metadataMaxLen)
{
    $metadata=$paymentRef->encode($metadataMaxLen);
    if (!isset($metadata))
        return null;

    $appendMetadataMaxLen=CoinSparkMetadataMaxAppendLen($metadata, $metadataMaxLen);
        // this is not simply $metadataMaxLen-strlen($metadata) since combining saves space

    $appendMetadata=$transferList->encode($countInputs, $countOutputs, $appendMetadataMaxLen);
    if (!isset($appendMetadata))
        return null;

    return CoinSparkMetadataAppend($metadata, $metadataMaxLen, $appendMetadata);
}

Python

def CoinSparkPaymentRefTransfersEncode(paymentRef, transferList, countInputs, countOutputs, metadataMaxLen):
    metadata=paymentRef.encode(metadataMaxLen)
    if metadata is None:
        return None

    appendMetadataMaxLen=CoinSparkMetadataMaxAppendLen(metadata, metadataMaxLen)
        # this is not simply metadataMaxLen-len(metadata) since combining saves space

    appendMetaData=transferList.encode(countInputs, countOutputs, appendMetadataMaxLen)
    if appendMetaData is None:
        return None

    return CoinSparkMetadataAppend(metadata, metadataMaxLen, appendMetaData)

Ruby

def CoinSparkPaymentRefTransfersEncode(paymentRef, transferList, countInputs, countOutputs, metadataMaxLen)
    metadata=paymentRef.encode(metadataMaxLen)
    return nil if metadata==nil

    appendMetadataMaxLen=CoinSparkMetadataMaxAppendLen(metadata, metadataMaxLen)
        # this is not simply metadataMaxLen-metadata.length since combining saves space

    appendMetaData=transferList.encode(countInputs, countOutputs, appendMetadataMaxLen)
    return nil if appendMetaData==nil

    return CoinSparkMetadataAppend(metadata, metadataMaxLen, appendMetaData)
end