PHP code example of blockchainofthings / catenis-api-client

1. Go to this page and download the library: Download blockchainofthings/catenis-api-client library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

blockchainofthings / catenis-api-client example snippets




$ctnApiClient = new \Catenis\ApiClient(
    $deviceId,
    $apiAccessSecret,
    [
        'environment' => 'sandbox'
    ]
);

$ctnApiClient = new \Catenis\ApiClient(null, null,
    [
        'environment' => 'sandbox'
    ]
);

$loop = \React\EventLoop\Factory::create();

$ctnApiClient = new \Catenis\ApiClient(
    $deviceId,
    $apiAccessSecret,
    [
        'environment' => 'sandbox'
        'eventLoop' => $loop
    ]
);

$ctnApiClient->logMessageAsync('My message')->then(
    function (stdClass $data) {
        // Process returned data
    },
    function (\Catenis\Exception\CatenisException $ex) {
        // Process exception
    }
);

try {
    $data = $ctnApiClient->logMessageAsync('My message')->wait();

    // Process returned data
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->logMessage('My message', [
        'encoding' => 'utf8',
        'encrypt' => true,
        'offChain' => true,
        'storage' => 'auto'
    ]);

    // Process returned data
    echo 'ID of logged message: ' . $data->messageId . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

$message = [
    'First part of message',
    'Second part of message',
    'Third and last part of message'
];

try {
    $continuationToken = null;

    foreach ($message as $chunk) {
        $data = $ctnApiClient->logMessage([
            'data' => $chunk,
            'isFinal' => false,
            'continuationToken' => $continuationToken
        ], [
            'encoding' => 'utf8'
        ]);

        $continuationToken = $data->continuationToken;
    }

    // Signal that message has ended and get result
    $data = $ctnApiClient->logMessage([
        'isFinal' => true,
        'continuationToken' => $continuationToken
    ], [
        'encrypt' => true,
        'offChain' => true,
        'storage' => 'auto'
    ]);

    echo 'ID of logged message: ' . $data->messageId . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->logMessage('My message', [
        'encoding' => 'utf8',
        'encrypt' => true,
        'offChain' => true,
        'storage' => 'auto',
        'async' => true
    ]);

    // Start pooling for asynchronous processing progress
    $provisionalMessageId = $data->provisionalMessageId;
    $done = false;
    $result = null;
    wait(1);

    do {
        $data = $ctnApiClient->retrieveMessageProgress($provisionalMessageId);

        // Process returned data
        echo 'Number of bytes processed so far: ' . $data->progress->bytesProcessed . PHP_EOL;
            
        if ($data->progress->done) {
            if ($data->progress->success) {
                // Get result
                $result = $data->result;
            } else {
                // Process error
                echo 'Asynchronous processing error: [' . $data->progress->error->code . '] - '
                    . $data->progress->error->message . PHP_EOL;
            }

            $done = true;
        } else {
            // Asynchronous processing not done yet. Wait before continuing pooling
            wait(3);
        }
    } while (!$done);

    if (!is_null($result)) {
        echo 'ID of logged message: ' . $result->messageId . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->sendMessage('My message', [
        'id' => $targetDeviceId,
        'isProdUniqueId' => false
    ], [
        'encoding' => 'utf8',
        'encrypt' => true,
        'offChain' => true,
        'storage' => 'auto',
        'readConfirmation' => true
    ]);

    // Process returned data
    echo 'ID of sent message: ' . $data->messageId . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

$message = [
    'First part of message',
    'Second part of message',
    'Third and last part of message'
];

try {
    $continuationToken = null;

    foreach ($message as $chunk) {
        $data = $ctnApiClient->sendMessage([
            'data' => $chunk,
            'isFinal' => false,
            'continuationToken' => $continuationToken
        ], [
            'id' => $targetDeviceId,
            'isProdUniqueId' => false
        ], [
            'encoding' => 'utf8'
        ]);

        $continuationToken = $data->continuationToken;
    }

    // Signal that message has ended and get result
    $data = $ctnApiClient->sendMessage([
        'isFinal' => true,
        'continuationToken' => $continuationToken
    ], [
        'id' => $targetDeviceId,
        'isProdUniqueId' => false
    ], [
        'encrypt' => true,
        'offChain' => true,
        'storage' => 'auto',
        'readConfirmation' => true
    ]);

    echo 'ID of sent message: ' . $data->messageId . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->sendMessage('My message', [
        'id' => $targetDeviceId,
        'isProdUniqueId' => false
    ], [
        'encoding' => 'utf8',
        'encrypt' => true,
        'offChain' => true,
        'storage' => 'auto',
        'readConfirmation' => true,
        'async' => true
    ]);

    // Start pooling for asynchronous processing progress
    $provisionalMessageId = $data->provisionalMessageId;
    $done = false;
    $result = null;
    wait(1);

    do {
        $data = $ctnApiClient->retrieveMessageProgress($provisionalMessageId);

        // Process returned data
        echo 'Number of bytes processed so far: ' . $data->progress->bytesProcessed . PHP_EOL;
            
        if ($data->progress->done) {
            if ($data->progress->success) {
                // Get result
                $result = $data->result;
            } else {
                // Process error
                echo 'Asynchronous processing error: [' . $data->progress->error->code . '] - '
                    . $data->progress->error->message . PHP_EOL;
            }

            $done = true;
        } else {
            // Asynchronous processing not done yet. Wait before continuing pooling
            wait(3);
        }
    } while (!$done);

    if (!is_null($result)) {
        echo 'ID of sent message: ' . $result->messageId . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->readMessage($messageId, 'utf8');

    // Process returned data
    if ($data->msgInfo->action === 'send') {
        echo 'Message sent from: ' . print_r($data->msgInfo->from, true);
    }

    echo 'Read message: ' . $data->msgData . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $continuationToken = null;
    $chunkCount = 1;

    do {
        $data = $ctnApiClient->readMessage($messageId, [
            'encoding' => 'utf8',
            'continuationToken' => $continuationToken,
            'dataChunkSize' => 1024
        ]);

        // Process returned data
        if (isset($data->msgInfo) && $data->msgInfo->action == 'send') {
            echo 'Message sent from: ' . $data->msgInfo->from);
        }
        
        echo 'Read message (chunk ' . $chunkCount . '): ' . $data->msgData);
        
        if (isset($data->continuationToken)) {
            // Get continuation token to continue reading message
            $continuationToken = $data->continuationToken;
            $chunkCount += 1;
        } else {
            $continuationToken = null;
        }
    } while (!is_null($continuationToken));
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    // Request to read message asynchronously
    $data = $ctnApiClient->readMessage($messageId, [
        'async' => true
    ]);

    // Start pooling for asynchronous processing progress
    $cachedMessageId = $data->cachedMessageId;
    $done = false;
    $result = null;
    wait(1);

    do {
        $data = $ctnApiClient->retrieveMessageProgress($cachedMessageId);

        // Process returned data
        echo 'Number of bytes processed so far: ' . $data->progress->bytesProcessed . PHP_EOL;
            
        if ($data->progress->done) {
            if ($data->progress->success) {
                // Get result
                $result = $data->result;
            } else {
                // Process error
                echo 'Asynchronous processing error: [' . $data->progress->error->code . '] - '
                    . $data->progress->error->message . PHP_EOL;
            }

            $done = true;
        } else {
            // Asynchronous processing not done yet. Wait before continuing pooling
            wait(3);
        }
    } while (!$done);

    if (!is_null($result)) {
        // Retrieve read message
        $data = $ctnApiClient->readMessage($messageId, [
            'encoding' => 'utf8',
            'continuationToken' => $result->continuationToken
        ]);

        if ($data->msgInfo->action === 'send') {
            echo 'Message sent from: ' . print_r($data->msgInfo->from, true);
        }

        echo 'Read message: ' . $data->msgData . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->retrieveMessageContainer($messageId);

    // Process returned data
    if (isset($data->offChain)) {
        echo 'IPFS CID of Catenis off-chain message envelope: ' . $data->offChain->cid . PHP_EOL;
    }
    
    if (isset($data->blockchain)) {
        echo 'ID of blockchain transaction containing the message: ' . $data->blockchain->txid . PHP_EOL;
    }

    if (isset($data->externalStorage)) {
        echo 'IPFS reference to message: ' . $data->externalStorage->ipfs . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->retrieveMessageOrigin($messageId, 'Any text to be signed');

    // Process returned data
    if (isset($data->tx)) {
        echo 'Catenis message transaction info: ' . print_r($data->tx, true);
    }
    
    if (isset($data->offChainMsgEnvelope)) {
        echo 'Off-chain message envelope info: ' . print_r($data->offChainMsgEnvelope, true);
    }

    if (isset($data->proof)) {
        echo 'Origin proof info: ' . print_r($data->proof, true);
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->retrieveMessageProgress($provisionalMessageId);

    // Process returned data
    echo 'Number of bytes processed so far: ' . $data->progress->bytesProcessed . PHP_EOL;

    if ($data->progress->done) {
        if ($data->progress->success) {
            // Get result
            echo 'Asynchronous processing result: ' . $data->result . PHP_EOL;
        }
        else {
            // Process error
            echo 'Asynchronous processing error: [' . $data->progress->error->code . '] - '
                . $data->progress->error->message . PHP_EOL;
        }
    } else {
        // Asynchronous processing not done yet. Continue pooling
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->listMessages([
        'action' => 'send',
        'direction' => 'inbound',
        'readState' => 'unread',
        'startDate' => new \DateTime('20170101T000000Z')
    ], 200, 0);

    // Process returned data
    if ($data->msgCount > 0) {
        echo 'Returned messages: ' . print_r($data->messages, true);
        
        if ($data->hasMore) {
            echo 'Not all messages have been returned' . PHP_EOL;
        }
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->issueAsset([
        'name' => 'XYZ001',
        'description' => 'My first test asset',
        'canReissue' => true,
        'decimalPlaces' => 2
    ], 1500.00, null);

    // Process returned data
    echo 'ID of newly issued asset: ' . $data->assetId . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->reissueAsset($assetId, 650.25, [
        'id' => $otherDeviceId,
        'isProdUniqueId' => false
    ]);

    // Process returned data
    echo 'Total existent asset balance (after issuance): ' . $data->totalExistentBalance . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->transferAsset($assetId, 50.75, [
        'id' => $otherDeviceId,
        'isProdUniqueId' => false
    ]);

    // Process returned data
    echo 'Remaining asset balance: ' . $data->remainingBalance . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->issueNonFungibleAsset([
        'assetInfo' => [
            'name' => 'Catenis NFA 1',
            'description' => 'Non-fungible asset #1 for testing',
            'canReissue' => true
        ]
    ], [
        [
            'metadata' => [
                'name' => 'NFA1 NFT 1',
                'description' => 'First token of Catenis non-fungible asset #1'
            ],
            'contents' => [
                'data' => 'Contents of first token of Catenis non-fungible asset #1',
                'encoding' => 'utf8'
            ]
        ],
        [
            'metadata' => [
                'name' => 'NFA1 NFT 2',
                'description' => 'Second token of Catenis non-fungible asset #1'
            ],
            'contents' => [
                'data' => 'Contents of second token of Catenis non-fungible asset #1',
                'encoding' => 'utf8'
            ]
        ]
    ]);

    // Process returned data
    echo 'ID of newly created non-fungible asset: ' . $data->assetId . PHP_EOL;
    echo 'IDs of newly issued non-fungible tokens: ' . implode(', ', $data->nfTokenIds) . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

$issuanceInfo = [
    'assetInfo' => [
        'name' => 'Catenis NFA 1',
        'description' => 'Non-fungible asset #1 for testing',
        'canReissue' => true
    ]
];
$nftMetadata = [
    [
        'name' => 'NFA1 NFT 1',
        'description' => 'First token of Catenis non-fungible asset #1'
    ],
    [
        'name' => 'NFA1 NFT 2',
        'description' => 'Second token of Catenis non-fungible asset #1'
    ]
];
$nftContents = [
    [
        [
            'data' => 'Contents of first token of Catenis non-fungible asset #1',
            'encoding' => 'utf8'
        ]
    ],
    [
        [
            'data' => 'Here is the contents of the second token of Catenis non-fungible asset #1 (part #1)',
            'encoding' => 'utf8'
        ],
        [
            'data' => '; and here is the last part of the contents of the second token of Catenis non-fungible asset #1.',
            'encoding' => 'utf8'
        ]
    ]
];

try {
    $continuationToken = null;
    $data = null;
    $nfTokens = null;
    $callIdx = -1;

    do {
        $nfTokens = null;
        $callIdx++;

        if ($continuationToken === null) {
            foreach ($nftMetadata as $tokenIdx => $metadata) {
                $nfToken = [
                    'metadata' => $metadata
                ];

                if (isset($nftContents[$tokenIdx])) {
                    $nfToken['contents'] = $nftContents[$tokenIdx][$callIdx];
                }

                $nfTokens[] = $nfToken;
            }
        }
        else {  // Continuation call
            foreach ($nftContents as $tokenIdx => $contents) {
                $nfTokens[] = isset($contents) && $callIdx < count($callIdx)
                    ? ['contents' => $contents[$callIdx]]
                    : null;
            }

            if (is_array($nfTokens)) {
                $allNull = true;

                foreach ($nfTokens as $tokenIdx => $nfToken) {
                    if ($nfToken !== null) {
                        $allNull = false;
                        break;
                    }
                }

                if ($allNull) {
                    $nfTokens = null;
                }
            }
        }

        $data = $ctnApiClient->issueNonFungibleAsset(
            $continuationToken !== null ? $continuationToken : $issuanceInfo,
            $nfTokens,
            !isset($nfTokens)
        );

        $continuationToken = isset($data->continuationToken)
            ? $data->continuationToken
            : null;
    } while ($continuationToken !== null);

    // Process returned data
    echo 'ID of newly created non-fungible asset: ' . $data->assetId . PHP_EOL;
    echo 'IDs of newly issued non-fungible tokens: ' . implode(', ', $data->nfTokenIds) . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->issueNonFungibleAsset([
        'assetInfo' => [
            'name' => 'Catenis NFA 1',
            'description' => 'Non-fungible asset #1 for testing',
            'canReissue' => true
        ],
        'async' => true
    ], [
        [
            'metadata' => [
                'name' => 'NFA1 NFT 1',
                'description' => 'First token of Catenis non-fungible asset #1'
            ],
            'contents' => [
                'data' => 'Contents of first token of Catenis non-fungible asset #1',
                'encoding' => 'utf8'
            ]
        ],
        [
            'metadata' => [
                'name' => 'NFA1 NFT 2',
                'description' => 'Second token of Catenis non-fungible asset #1'
            ],
            'contents' => [
                'data' => 'Contents of second token of Catenis non-fungible asset #1',
                'encoding' => 'utf8'
            ]
        ]
    ]);

    // Start pooling for asynchronous processing progress
    $assetIssuanceId = $data->assetIssuanceId;
    $done = false;
    $result = null;
    wait(1);

    do {
        $data = $ctnApiClient->retrieveNonFungibleAssetIssuanceProgress($assetIssuanceId);

        // Process returned data
        echo 'Percent processed: ', $data->progress->percentProcessed . PHP_EOL;
            
        if ($data->progress->done) {
            if ($data->progress->success) {
                // Get result
                $result = $data->result;
            } else {
                // Process error
                echo 'Asynchronous processing error: [' . $data->progress->error->code . '] - '
                    . $data->progress->error->message . PHP_EOL;
            }

            $done = true;
        } else {
            // Asynchronous processing not done yet. Wait before continuing pooling
            wait(3);
        }
    } while (!$done);

    if ($result !== null) {
        echo 'ID of newly created non-fungible asset: ' . $result->assetId . PHP_EOL;
        echo 'IDs of newly issued non-fungible tokens: ' . implode(', ', $result->nfTokenIds) . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->reissueNonFungibleAsset($assetId, null, [
        [
            'metadata' => [
                'name' => 'NFA1 NFT 3',
                'description' => 'Third token of Catenis non-fungible asset #1'
            ],
            'contents' => [
                'data' => 'Contents of third token of Catenis non-fungible asset #1',
                'encoding' => 'utf8'
            ]
        ],
        [
            'metadata' => [
                'name' => 'NFA1 NFT 4',
                'description' => 'Forth token of Catenis non-fungible asset #1'
            ],
            'contents' => [
                'data' => 'Contents of forth token of Catenis non-fungible asset #1',
                'encoding' => 'utf8'
            ]
        ]
    ]);

    // Process returned data
    echo 'IDs of newly issued non-fungible tokens: ' . $data->nfTokenIds . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

$nftMetadata = [
    [
        'name' => 'NFA1 NFT 3',
        'description' => 'Third token of Catenis non-fungible asset #1'
    ],
    [
        'name' => 'NFA1 NFT 4',
        'description' => 'Forth token of Catenis non-fungible asset #1'
    ]
];
$nftContents = [
    [
        [
            'data' => 'Contents of third token of Catenis non-fungible asset #1',
            'encoding' => 'utf8'
        ]
    ],
    [
        [
            'data' => 'Here is the contents of the forth token of Catenis non-fungible asset #1 (part #1)',
            'encoding' => 'utf8'
        ],
        [
            'data' => '; and here is the last part of the contents of the forth token of Catenis non-fungible asset #1.',
            'encoding' => 'utf8'
        ]
    ]
];

try {
    $continuationToken = null;
    $data = null;
    $nfTokens = null;
    $callIdx = -1;

    do {
        $nfTokens = null;
        $callIdx++;

        if ($continuationToken === null) {
            foreach ($nftMetadata as $tokenIdx => $metadata) {
                $nfToken = [
                    'metadata' => $metadata
                ];

                if (isset($nftContents[$tokenIdx])) {
                    $nfToken['contents'] = $nftContents[$tokenIdx][$callIdx];
                }

                $nfTokens[] = $nfToken;
            }
        }
        else {  // Continuation call
            foreach ($nftContents as $tokenIdx => $contents) {
                $nfTokens[] = isset($contents) && $callIdx < count($callIdx)
                    ? ['contents' => $contents[$callIdx]]
                    : null;
            }

            if (is_array($nfTokens)) {
                $allNull = true;

                foreach ($nfTokens as $tokenIdx => $nfToken) {
                    if ($nfToken !== null) {
                        $allNull = false;
                        break;
                    }
                }

                if ($allNull) {
                    $nfTokens = null;
                }
            }
        }

        $data = $ctnApiClient->reissueNonFungibleAsset(
            $assetId,
            $continuationToken,
            $nfTokens,
            !isset($nfTokens)
        );

        $continuationToken = isset($data->continuationToken)
            ? $data->continuationToken
            : null;
    } while ($continuationToken !== null);

    // Process returned data
    echo 'IDs of newly issued non-fungible tokens: ' . implode(', ', $data->nfTokenIds) . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->reissueNonFungibleAsset($assetId, [
        'async' => true
    ], [
        [
            'metadata' => [
                'name' => 'NFA1 NFT 3',
                'description' => 'Third token of Catenis non-fungible asset #1'
            ],
            'contents' => [
                'data' => 'Contents of third token of Catenis non-fungible asset #1',
                'encoding' => 'utf8'
            ]
        ],
        [
            'metadata' => [
                'name' => 'NFA1 NFT 4',
                'description' => 'Forth token of Catenis non-fungible asset #1'
            ],
            'contents' => [
                'data' => 'Contents of forth token of Catenis non-fungible asset #1',
                'encoding' => 'utf8'
            ]
        ]
    ]);

    // Start pooling for asynchronous processing progress
    $assetIssuanceId = $data->assetIssuanceId;
    $done = false;
    $result = null;
    wait(1);

    do {
        $data = $ctnApiClient->retrieveNonFungibleAssetIssuanceProgress($assetIssuanceId);

        // Process returned data
        echo 'Percent processed: ', $data->progress->percentProcessed . PHP_EOL;
            
        if ($data->progress->done) {
            if ($data->progress->success) {
                // Get result
                $result = $data->result;
            } else {
                // Process error
                echo 'Asynchronous processing error: [' . $data->progress->error->code . '] - '
                    . $data->progress->error->message . PHP_EOL;
            }

            $done = true;
        } else {
            // Asynchronous processing not done yet. Wait before continuing pooling
            wait(3);
        }
    } while (!$done);

    if ($result !== null) {
        echo 'IDs of newly issued non-fungible tokens: ' . implode(', ', $result->nfTokenIds) . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $continuationToken = null;
    $data = null;
    $nfTokenData = null;

    do {
        $data = $ctnApiClient->retrieveNonFungibleToken(
            $tokenId,
            isset($continuationToken) ? ['continuationToken' => $continuationToken] : null
        );

        if (!isset($nfTokenData)) {
            // Get token data
            $nfTokenData = (object)[
                'assetId' => $data->nonFungibleToken->assetId,
                'metadata' => $data->nonFungibleToken->metadata,
                'contents' => [$data->nonFungibleToken->contents->data]
            ];
        } else {
            // Add next contents part to token data
            $nfTokenData->contents[] = $data->nonFungibleToken->contents->data;
        }

        $continuationToken = isset($data->continuationToken)
            ? $data->continuationToken
            : null;
    } while ($continuationToken !== null);

    // Process returned data
    echo 'Non-fungible token data: ' . print_r($nfTokenData, true);
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->retrieveNonFungibleToken($tokenId, [
        'async' => true
    ]);

    // Start pooling for asynchronous processing progress
    $tokenRetrievalId = $data->tokenRetrievalId;
    $done = false;
    $continuationToken = null;
    wait(1);

    do {
        $data = $ctnApiClient->retrieveNonFungibleTokenRetrievalProgress($tokenId, $tokenRetrievalId);

        // Process returned data
        echo 'Bytes already retrieved: ', $data->progress->bytesRetrieved . PHP_EOL;
            
        if ($data->progress->done) {
            if ($data->progress->success) {
                // Prepare to finish retrieving the non-fungible token data
                $continuationToken = $data->continuationToken;
            } else {
                // Process error
                echo 'Asynchronous processing error: [' . $data->progress->error->code . '] - '
                    . $data->progress->error->message . PHP_EOL;
            }

            $done = true;
        } else {
            // Asynchronous processing not done yet. Wait before continuing pooling
            wait(3);
        }
    } while (!$done);

    if ($continuationToken !== null) {
        // Finish retrieving the non-fungible token data
        $nfTokenData = null;

        do {
            $data = $ctnApiClient->retrieveNonFungibleToken(
                $tokenId,
                $continuationToken
            );

            if (!isset($nfTokenData)) {
                // Get token data
                $nfTokenData = (object)[
                    'assetId' => $data->nonFungibleToken->assetId,
                    'metadata' => $data->nonFungibleToken->metadata,
                    'contents' => [$data->nonFungibleToken->contents->data]
                ];
            } else {
                // Add next contents part to token data
                $nfTokenData->contents[] = $data->nonFungibleToken->contents->data;
            }

            $continuationToken = isset($data->continuationToken)
                ? $data->continuationToken
                : null;
        } while ($continuationToken !== null);

        // Process returned data
        echo 'Non-fungible token data: ' . print_r($nfTokenData, true);
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->transferNonFungibleToken($tokenId, [
        'id' => $otherDeviceId,
        'isProdUniqueId' => false
    ]);

    // Process returned data
    echo 'Non-fungible token successfully transferred' . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->transferNonFungibleToken($tokenId, [
        'id' => $otherDeviceId,
        'isProdUniqueId' => false
    ], true);

    // Start pooling for asynchronous processing progress
    $tokenTransferId = $data->tokenTransferId;
    $done = false;
    wait(1);

    do {
        $data = $ctnApiClient->retrieveNonFungibleTokenTransferProgress($tokenId, $tokenTransferId);

        // Process returned data
        echo 'Current data manipulation: ', print_r($data->progress->dataManipulation, true);
            
        if ($data->progress->done) {
            if ($data->progress->success) {
                // Display result
                echo 'Non-fungible token successfully transferred' . PHP_EOL;
            } else {
                // Process error
                echo 'Asynchronous processing error: [' . $data->progress->error->code . '] - '
                    . $data->progress->error->message . PHP_EOL;
            }

            $done = true;
        } else {
            // Asynchronous processing not done yet. Wait before continuing pooling
            wait(3);
        }
    } while (!$done);
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->retrieveAssetInfo($assetId);

    // Process returned data
    echo 'Asset info:' . print_r($data, true);
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->getAssetBalance($assetId);

    // Process returned data
    echo 'Current asset balance: ' . $data->balance->total . PHP_EOL;
    echo 'Amount not yet confirmed: ' . $data->balance->unconfirmed . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->listOwnedAssets(200, 0);
    
    // Process returned data
    foreach ($data->ownedAssets as $idx => $ownedAsset) {
        echo 'Owned asset #' . ($idx + 1) . ':' . PHP_EOL;
        echo '  - asset ID: ' . $ownedAsset->assetId . PHP_EOL;
        echo '  - current asset balance: ' . $ownedAsset->balance->total . PHP_EOL;
        echo '  - amount not yet confirmed: ' . $ownedAsset->balance->unconfirmed . PHP_EOL;
    }

    if ($data->hasMore) {
        echo 'Not all owned assets have been returned' . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->listIssuedAssets(200, 0);
    
    // Process returned data
    foreach ($data->issuedAssets as $idx => $issuedAsset) {
        echo 'Issued asset #' . ($idx + 1) . ':' . PHP_EOL;
        echo '  - asset ID: ' . $issuedAsset->assetId . PHP_EOL;
        echo '  - total existent balance: ' . $issuedAsset->totalExistentBalance . PHP_EOL;
    }

    if ($data->hasMore) {
        echo 'Not all issued assets have been returned' . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->retrieveAssetIssuanceHistory($assetId, new \DateTime('20170101T000000Z'), null, 200, 0);
    
    // Process returned data
    foreach ($data->issuanceEvents as $idx => $issuanceEvent) {
        echo 'Issuance event #', ($idx + 1) . ':' . PHP_EOL;

        if (!isset($issuanceEvent->nfTokenIds)) {
            echo '  - issued amount: ' . $issuanceEvent->amount . PHP_EOL;
        }
        else {
            echo '  - IDs of issued non-fungible tokens:' . print_r($issuanceEvent->nfTokenIds, true);
        }

        if (!isset($issuanceEvent->holdingDevices)) {
            echo '  - device to which issued amount has been assigned: ' . print_r($issuanceEvent->holdingDevice, true);
        }
        else {
            echo '  - devices to which issued non-fungible tokens have been assigned:', print_r($issuanceEvent->holdingDevices, true);
        }

        echo '  - date of issuance: ' . $issuanceEvent->date . PHP_EOL;
    }

    if ($data->hasMore) {
        echo 'Not all asset issuance events have been returned' . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->listAssetHolders($assetId, 200, 0);
    
    // Process returned data
    foreach ($data->assetHolders as $idx => $assetHolder) {
        if (isset($assetHolder->holder)) {
            echo 'Asset holder #' . ($idx + 1) . ':' . PHP_EOL;
            echo '  - device holding an amount of the asset: ' . print_r($assetHolder->holder, true);
            echo '  - amount of asset currently held by device: ' . $assetHolder->balance->total . PHP_EOL;
            echo '  - amount not yet confirmed: ' . $assetHolder->balance->unconfirmed . PHP_EOL;
        } else {
            echo 'Migrated asset:' . PHP_EOL;
            echo '  - total migrated amount: ' . $assetHolder->balance->total . PHP_EOL;
            echo '  - amount not yet confirmed: ' . $assetHolder->balance->unconfirmed . PHP_EOL;
        }
    }

    if ($data->hasMore) {
        echo 'Not all asset holders have been returned' . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $foreignBlockchain = 'ethereum';

    $data = $ctnApiClient->exportAsset($assetId, $foreignBlockchain, [
        'name' => 'Test Catenis token #01',
        'symbol' => 'CTK01'
    ], [
        'estimateOnly' => true
    ]);

    // Process returned data
    echo 'Estimated foreign blockchain transaction execution price: ' . $data->estimatedPrice . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $foreignBlockchain = 'ethereum';

    $data = $ctnApiClient->exportAsset($assetId, $foreignBlockchain, [
        'name' => 'Test Catenis token #01',
        'symbol' => 'CTK01'
    ]);

    // Process returned data
    echo 'Foreign blockchain transaction ID (hash): ' . $data->foreignTransaction->id . PHP_EOL;

    // Start polling for asset export outcome
    $done = false;
    $tokenId = null;
    wait(1);

    do {
        $data = $ctnApiClient->assetExportOutcome($assetId, $foreignBlockchain);

        // Process returned data
        if ($data->status === 'success') {
            // Asset successfully exported
            $tokenId = $data->token->id;
            $done = true;
        } elseif ($data->status === 'pending') {
            // Final asset export state not yet reached. Wait before continuing pooling
            wait(3);
        } else {
            // Asset export has failed. Process error
            echo 'Error executing foreign blockchain transaction: ' . $data->foreignTransaction->error . PHP_EOL;
            $done = true;
        }
    } while (!$done);

    if (!is_null($tokenId)) {
        echo 'Foreign token ID (address): ' . $tokenId . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $foreignBlockchain = 'ethereum';

    $data = $ctnApiClient->migrateAsset($assetId, $foreignBlockchain, [
        'direction' => 'outward',
        'amount' => 50,
        'destAddress' => '0xe247c9BfDb17e7D8Ae60a744843ffAd19C784943'
    ], [
        'estimateOnly' => true
    ]);

    // Process returned data
    echo 'Estimated foreign blockchain transaction execution price: ' . $data->estimatedPrice . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $foreignBlockchain = 'ethereum';

    $data = $ctnApiClient->migrateAsset($assetId, $foreignBlockchain, [
        'direction' => 'outward',
        'amount' => 50,
        'destAddress' => '0xe247c9BfDb17e7D8Ae60a744843ffAd19C784943'
    ]);

    // Process returned data
    $migrationId = $data->migrationId;
    echo 'Asset migration ID: ' . $migrationId . PHP_EOL;

    // Start polling for asset migration outcome
    $done = false;
    wait(1);

    do {
        $data = $ctnApiClient->assetMigrationOutcome($migrationId);

        // Process returned data
        if ($data->status === 'success') {
            // Asset amount successfully migrated
            echo 'Asset amount successfully migrated' . PHP_EOL;
            $done = true;
        } elseif ($data->status === 'pending') {
            // Final asset migration state not yet reached. Wait before continuing pooling
            wait(3);
        } else {
            // Asset migration has failed. Process error
            if (isset($data->catenisService->error)) {
                echo 'Error executing Catenis service: ' . $data->catenisService->error . PHP_EOL;
            }

            if (isset($data->foreignTransaction->error)) {
                echo 'Error executing foreign blockchain transaction: ' . $data->foreignTransaction->error . PHP_EOL;
            }

            $done = true;
        }
    } while (!$done);
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $foreignBlockchain = 'ethereum';

    $data = $ctnApiClient->migrateAsset($assetId, $foreignBlockchain, $migrationId);

    // Start polling for asset migration outcome
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $foreignBlockchain = 'ethereum';

    $data = $ctnApiClient->assetExportOutcome($assetId, $foreignBlockchain);

    // Process returned data
    if ($data->status === 'success') {
        // Asset successfully exported
        echo 'Foreign token ID (address): ' . $data->token->id . PHP_EOL;
    } elseif ($data->status === 'pending') {
        // Final asset export state not yet reached
    } else {
        // Asset export has failed. Process error
        echo 'Error executing foreign blockchain transaction: ' . $data->foreignTransaction->error . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->assetMigrationOutcome($migrationId);

    // Process returned data
    if ($data->status === 'success') {
        // Asset amount successfully migrated
        echo 'Asset amount successfully migrated' . PHP_EOL;
    } elseif ($data->status === 'pending') {
        // Final asset migration state not yet reached
    } else {
        // Asset migration has failed. Process error
        if (isset($data->catenisService->error)) {
            echo 'Error executing Catenis service: ' . $data->catenisService->error . PHP_EOL;
        }

        if (isset($data->foreignTransaction->error)) {
            echo 'Error executing foreign blockchain transaction: ' . $data->foreignTransaction->error . PHP_EOL;
        }
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->listExportedAssets([
        'foreignBlockchain' => 'ethereum',
        'status' => 'success',
        'startDate' => new \DateTime('20210801T000000Z')
    ], 200, 0);

    // Process returned data
    if (count($data->exportedAssets) > 0) {
        echo 'Returned asset exports: ' . print_r($data->exportedAssets, true);
        
        if ($data->hasMore) {
            echo 'Not all asset exports have been returned' . PHP_EOL;
        }
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->listAssetMigrations([
        'foreignBlockchain' => 'ethereum',
        'direction' => 'outward',
        'status' => 'success',
        'startDate' => new \DateTime('20210801T000000Z')
    ], 200, 0);

    // Process returned data
    if (count($data->assetMigrations) > 0) {
        echo 'Returned asset migrations: ' . print_r($data->assetMigrations, true);
        
        if ($data->hasMore) {
            echo 'Not all asset migrations have been returned' . PHP_EOL;
        }
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->listPermissionEvents();

    // Process returned data
    foreach ($data as $eventName => $description) {
        echo 'Event name: ' . $eventName . '; event description: ' . $description . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->retrievePermissionRights('receive-msg');
    
    // Process returned data
    echo 'Default (system) permission right: ' . $data->system . PHP_EOL;
    
    if (isset($data->catenisNode)) {
        if (isset($data->catenisNode->allow)) {
            echo 'Index of Catenis nodes with \'allow\' permission right: ' . implode(', ', $data->catenisNode->allow)
                . PHP_EOL;
        }
        
        if (isset($data->catenisNode->deny)) {
            echo 'Index of Catenis nodes with \'deny\' permission right: ' . implode(', ', $data->catenisNode->deny)
                . PHP_EOL;
        }
    }
    
    if (isset($data->client)) {
        if (isset($data->client->allow)) {
            echo 'ID of clients with \'allow\' permission right: ' . implode(', ', $data->client->allow) . PHP_EOL;
        }
        
        if (isset($data->client->deny)) {
            echo 'ID of clients with \'deny\' permission right: ' . implode(', ', $data->client->deny) . PHP_EOL;
        }
    }
    
    if (isset($data->device)) {
        if (isset($data->device->allow)) {
            echo 'Devices with \'allow\' permission right: ' . print_r($data->device->allow, true);
        }
        
        if (isset($data->device->deny)) {
            echo 'Devices with \'deny\' permission right: ' . print_r($data->device->deny, true);
        }
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->setPermissionRights(
        'receive-msg',
        [
            'system' => 'deny',
            'catenisNode' => [
                'allow' => 'self'
            ],
            'client' => [
                'allow' => [
                    'self',
                    $clientId
                ]
            ],
            'device' => [
                'deny' => [[
                    'id' => $deviceId1
                ], [
                    'id' => 'ABCD001',
                    'isProdUniqueId' => true
                ]]
            ]
        ]
    );

    // Process returned data
    echo 'Permission rights successfully set' . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->checkEffectivePermissionRight('receive-msg', $deviceProdUniqueId, true);

    // Process returned data
    $deviceId = array_keys(get_object_vars($data))[0];
    echo 'Effective right for device ' . $deviceId . ': ' . $data->$deviceId . PHP_EOL;
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->retrieveDeviceIdentificationInfo($deviceId, false);
    
    // Process returned data
    echo 'Device\'s Catenis node ID info:' . print_r($data->catenisNode, true);
    echo 'Device\'s client ID info:' . print_r($data->client, true);
    echo 'Device\'s own ID info:' . print_r($data->device, true);
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

try {
    $data = $ctnApiClient->listNotificationEvents();

    // Process returned data
    foreach ($data as $eventName => $description) {
        echo 'Event name: ' . $eventName . '; event description: ' . $description . PHP_EOL;
    }
} catch (\Catenis\Exception\CatenisException $ex) {
    // Process exception
}

$loop = \React\EventLoop\Factory::create();

$ctnApiClient = new \Catenis\ApiClient(
    $deviceId,
    $apiAccessSecret, [
        'environment' => 'sandbox'
        'eventLoop' => $loop
    ]
);

$wsNtfyChannel = $ctnApiClient->createWsNotifyChannel($eventName);

$wsNtfyChannel->on('error', function ($error) {
    // Process error in the underlying WebSocket connection
});

$wsNtfyChannel->on('close', function ($code, $reason) {
    // Process indication that underlying WebSocket connection has been closed
});

$wsNtfyChannel->on('open', function () {
    // Process indication that notification channel is successfully open
    //  and ready to send notifications 
});

$wsNtfyChannel->on('notify', function ($data) {
    // Process received notification
    echo 'Received notification:' . PHP_EOL;
    print_r($data);
});

$wsNtfyChannel->open()->then(
    function () {
        // WebSocket client successfully connected. Wait for open event to make
        //  sure that notification channel is ready to send notifications
    },
    function (\Catenis\Exception\WsNotificationException $ex) {
        // Process exception
    }
);

$wsNtfyChannel->close();

try {
    $data = $ctnApiClient->readMessage('INVALID_MSG_ID', null);
    
    // Process returned data
} catch (\Catenis\Exception\CatenisException $ex) {
    if ($ex instanceof \Catenis\Exception\CatenisApiException) {
        // Catenis API error
        echo 'HTTP status code: ' . $ex->getHttpStatusCode() . PHP_EOL;
        echo 'HTTP status message: ' . $ex->getHttpStatusMessage() . PHP_EOL;
        echo 'Catenis error message: ' . $ex->getCatenisErrorMessage() . PHP_EOL;
        echo 'Compiled error message: ' . $ex->getMessage() . PHP_EOL;
    } else {
        // Client error
        echo $ex . PHP_EOL;
    }
}

$wsNtfyChannel->open()->then(
    function () {
        // WebSocket client successfully connected. Wait for open event to make
        //  sure that notification channel is ready to send notifications
    },
    function (\Catenis\Exception\WsNotificationException $ex) {
        if ($ex instanceof \Catenis\Exception\OpenWsConnException) {
            // Error opening WebSocket connection
            echo $ex . PHP_EOL;
        } else {
            // WebSocket nofitication channel already open
        }
    }
);