PHPackages                             blockchainofthings/catenis-api-client - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. blockchainofthings/catenis-api-client

ActiveLibrary[API Development](/categories/api)

blockchainofthings/catenis-api-client
=====================================

Catenis API client library for PHP

v6.0.1(3y ago)025MITPHPPHP &gt;=5.6.0CI failing

Since Dec 7Pushed 3y ago2 watchersCompare

[ Source](https://github.com/blockchainofthings/CatenisAPIClientPHP)[ Packagist](https://packagist.org/packages/blockchainofthings/catenis-api-client)[ RSS](/packages/blockchainofthings-catenis-api-client/feed)WikiDiscussions master Synced 2mo ago

READMEChangelog (10)Dependencies (10)Versions (18)Used By (0)

Catenis API PHP Client
======================

[](#catenis-api-php-client)

This library is used to make it easier to access the Catenis API services from PHP applications.

This current release (6.0.1) targets version 0.12 of the Catenis API.

Installation
------------

[](#installation)

The recommended way to install the Catenis API PHP client is using [Composer](https://getcomposer.org).

To add Catenis API Client as a dependency to your project, follow these steps:

1. Add the following repository entries to your `composer.json` file:

```
{
  "repositories": [
    {
        "type": "vcs",
        "url": "https://github.com/claudiosdc/react-guzzle-psr7.git"
    },
    {
        "type": "vcs",
        "url": "https://github.com/claudiosdc/react-guzzle-http-client.git"
    },
    {
        "type": "vcs",
        "url": "https://github.com/claudiosdc/reactphp-buzz.git"
    }
  ]
}
```

2. Then add the required dependencies by either:

Issuing the following command:

```
composer require blockchainofthings/catenis-api-client:~3.0 wyrihaximus/react-guzzle-psr7:dev-decode-content wyrihaximus/react-guzzle-http-client:dev-decode-content clue/buzz-react:dev-decode-content
```

Or editing the `composer.json` file directly:

```
{
    "require": {
        "blockchainofthings/catenis-api-client": "~6.0",
        "wyrihaximus/react-guzzle-psr7": "dev-decode-content",
        "wyrihaximus/react-guzzle-http-client": "dev-decode-content",
        "clue/buzz-react": "dev-decode-content"
    }
}
```

> **Note**: normally, only the blockchainofthings/catenis-api-client package would need to be listed. However, since this release of the Catenis API library depends on special patched versions of the other three packages, they also need to be listed here.

Usage
-----

[](#usage)

Just include Composer's `vendor/autoload.php` file and Catenis API Client's components will be available to be used in your code.

```
require __DIR__ . 'vendor/autoload.php';
```

### Instantiate the client

[](#instantiate-the-client)

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

Optionally, the client can be instantiated without passing both the `$deviceId` and the `$apiAccessSecret` parameters or setting them to *null* as shown below. In this case, the resulting client object should be used to call only **public** API methods.

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

#### Constructor options

[](#constructor-options)

The following options can be used when instantiating the client:

- **host** \[string\] - (optional, default: ***'catenis.io'***) Host name (with optional port) of target Catenis API server.
- **environment** \[string\] - (optional, default: ***'prod'***) Environment of target Catenis API server. Valid values: *'prod'*, *'sandbox'*.
- **secure** \[boolean\] - (optional, default: ***true***) Indicates whether a secure connection (HTTPS) should be used.
- **version** \[string\] - (optional, default: ***'0.12'***) Version of Catenis API to target.
- **useCompression** \[boolean\] - (optional, default: ***true***) Indicates whether request/response body should be compressed.
- **compressThreshold** \[integer\] - (optional, default: ***1024***) Minimum size, in bytes, of request body for it to be compressed.
- **timeout** \[float|integer\] - (optional, default: ***0, no timeout***) Timeout, in seconds, to wait for a response.
- **eventLoop** \[React\\EventLoop\\LoopInterface\] - (optional) Event loop to be used for asynchronous API method calling mechanism.
- **pumpTaskQueue** \[boolean\] - (optional, default: ***true***) Indicates whether to force the promise task queue to be periodically run.
- **pumpInterval** \[integer\] - (optional, default: ***10***) Time, in milliseconds, specifying the interval for periodically running the task queue.

### Asynchronous method calls

[](#asynchronous-method-calls)

Each API method has an asynchronous counterpart method that has an *Async* suffix, e.g. `logMessageAsync`.

The asynchronous methods return a promise, and, when used with an event loop, can have their result processed in a asynchronous way.

To be used with an event loop, pass the event loop instance as an option when instantiating the *ApiClient* object.

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

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

Example of processing asynchronous API method calls.

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

> **Note**: for promises to be asynchronously resolved, the promise task queue should be periodically run (i.e. `\GuzzleHttp\Promise\queue()->run()`). By default, the Catenis API PHP client will run the promise task queue every 10 milliseconds. To set a different interval for running the promise task queue, pass the optional parameter ***pumpInterval*** with an integer value corresponding to the desired time in milliseconds when instantiating the Catenis API PHP client (e.g. `'pumpInterval' => 5`). Alternatively, to avoid that the promise task queue be run altogether, pass the optional parameter ***pumpTaskQueue*** set to *false* when instantiating the client (i.e. `'pumpTaskQueue' => false`). In that case, the end user shall be responsible to run the promise task queue.

To force the returned promise to complete and get the data returned by the API method, use its `wait()` method.

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

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

### Returned data

[](#returned-data)

On successful calls to the Catenis API, the data returned by the client library methods **only** include the `data`property of the JSON originally returned in response to a Catenis API request.

For example, you should expect the following data to be returned from a successful call to the `logMessage` method:

```
object(stdClass)#54 (1) {
  ["messageId"]=>
  string(20) "m57enyYQK7QmqSxgP94j"
}
```

### Logging (storing) a message to the blockchain

[](#logging-storing-a-message-to-the-blockchain)

#### Passing the whole message's contents at once

[](#passing-the-whole-messages-contents-at-once)

```
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
}
```

#### Passing the message's contents in chunks

[](#passing-the-messages-contents-in-chunks)

```
$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
}
```

#### Logging message asynchronously

[](#logging-message-asynchronously)

```
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
}
```

### Sending a message to another device

[](#sending-a-message-to-another-device)

#### Passing the whole message's contents at once

[](#passing-the-whole-messages-contents-at-once-1)

```
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
}
```

#### Passing the message's contents in chunks

[](#passing-the-messages-contents-in-chunks-1)

```
$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
}
```

#### Sending message asynchronously

[](#sending-message-asynchronously)

```
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
}
```

### Reading a message

[](#reading-a-message)

#### Retrieving the whole read message's contents at once

[](#retrieving-the-whole-read-messages-contents-at-once)

```
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
}
```

#### Retrieving the read message's contents in chunks

[](#retrieving-the-read-messages-contents-in-chunks)

```
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
}
```

#### Reading message asynchronously

[](#reading-message-asynchronously)

```
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
}
```

### Retrieving information about a message's container

[](#retrieving-information-about-a-messages-container)

```
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
}
```

### Retrieving information about a message's origin

[](#retrieving-information-about-a-messages-origin)

```
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
}
```

### Retrieving asynchronous message processing progress

[](#retrieving-asynchronous-message-processing-progress)

```
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
}
```

> **Note**: see the *Logging message asynchronously*, *Sending message asynchronously* and *Reading message asynchronously* sections above for more complete examples.

### Listing messages

[](#listing-messages)

```
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
}
```

> **Note**: the parameters taken by the *listMessages* method do not exactly match the parameters taken by the List Messages Catenis API method. Most of the parameters, except for the last two (`limit` and `skip`), are mapped to keys of the first parameter (`$selector`) of the *listMessages* method with a few singularities: parameters `fromDeviceIds` and `fromDeviceProdUniqueIds` and parameters `toDeviceIds` and `toDeviceProdUniqueIds` are replaced with keys `fromDevices` and `toDevices`, respectively. Those keys accept for value an indexed array of device ID associative arrays, which is the same type of associative array taken by the first parameter (`$targetDevice`) of the *sendMessage* method. Also, the date keys, `startDate` and `endDate`, accept for value not only strings containing ISO 8601 formatted dates/times but also *DateTime* objects.

### Issuing an amount of a new asset

[](#issuing-an-amount-of-a-new-asset)

```
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
}
```

### Issuing an additional amount of an existing asset

[](#issuing-an-additional-amount-of-an-existing-asset)

```
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
}
```

### Transferring an amount of an asset to another device

[](#transferring-an-amount-of-an-asset-to-another-device)

```
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
}
```

### Creating a new non-fungible asset and issuing its (initial) non-fungible tokens

[](#creating-a-new-non-fungible-asset-and-issuing-its-initial-non-fungible-tokens)

#### Passing non-fungible token contents in a single call

[](#passing-non-fungible-token-contents-in-a-single-call)

```
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
}
```

#### Passing non-fungible token contents in multiple calls

[](#passing-non-fungible-token-contents-in-multiple-calls)

```
$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
}
```

#### Doing issuance asynchronously

[](#doing-issuance-asynchronously)

```
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
}
```

### Issuing more non-fungible tokens for a previously created non-fungible asset

[](#issuing-more-non-fungible-tokens-for-a-previously-created-non-fungible-asset)

#### Passing non-fungible token contents in a single call

[](#passing-non-fungible-token-contents-in-a-single-call-1)

```
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
}
```

#### Passing non-fungible token contents in multiple calls

[](#passing-non-fungible-token-contents-in-multiple-calls-1)

```
$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
}
```

#### Doing issuance asynchronously

[](#doing-issuance-asynchronously-1)

```
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
}
```

### Retrieving the data associated with a non-fungible token

[](#retrieving-the-data-associated-with-a-non-fungible-token)

#### Doing retrieval synchronously

[](#doing-retrieval-synchronously)

```
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
}
```

#### Doing retrieval asynchronously

[](#doing-retrieval-asynchronously)

```
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
}
```

### Transferring a non-fungible token to another device

[](#transferring-a-non-fungible-token-to-another-device)

#### Doing transfer synchronously

[](#doing-transfer-synchronously)

```
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
}
```

#### Doing transfer asynchronously

[](#doing-transfer-asynchronously)

```
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
}
```

### Retrieving information about a given asset

[](#retrieving-information-about-a-given-asset)

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

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

### Getting the current balance of a given asset held by the device

[](#getting-the-current-balance-of-a-given-asset-held-by-the-device)

```
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
}
```

### Listing assets owned by the device

[](#listing-assets-owned-by-the-device)

```
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
}
```

### Listing assets issued by the device

[](#listing-assets-issued-by-the-device)

```
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
}
```

### Retrieving issuance history for a given asset

[](#retrieving-issuance-history-for-a-given-asset)

```
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
}
```

> **Note**: the parameters of the *retrieveAssetIssuanceHistory* method are slightly different from the ones taken by the Retrieve Asset Issuance History Catenis API method. In particular, the date parameters, `$startDate` and `$endDate`, accept not only strings containing ISO 8601 formatted dates/times but also *DateTime* objects.

### Listing devices that currently hold any amount of a given asset

[](#listing-devices-that-currently-hold-any-amount-of-a-given-asset)

```
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
}
```

### Exporting an asset to a foreign blockchain

[](#exporting-an-asset-to-a-foreign-blockchain)

#### Estimating the export cost in the foreign blockchain's native coin

[](#estimating-the-export-cost-in-the-foreign-blockchains-native-coin)

```
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
}
```

#### Doing the export

[](#doing-the-export)

```
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
}
```

### Migrating an asset amount to a foreign blockchain

[](#migrating-an-asset-amount-to-a-foreign-blockchain)

#### Estimating the migration cost in the foreign blockchain's native coin

[](#estimating-the-migration-cost-in-the-foreign-blockchains-native-coin)

```
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
}
```

#### Doing the migration

[](#doing-the-migration)

```
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
}
```

#### Reprocessing a (failed) migration

[](#reprocessing-a-failed-migration)

```
try {
    $foreignBlockchain = 'ethereum';

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

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

### Getting asset export outcome

[](#getting-asset-export-outcome)

```
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
}
```

### Getting asset migration outcome

[](#getting-asset-migration-outcome)

```
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
}
```

### Listing exported assets

[](#listing-exported-assets)

```
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
}
```

> **Note**: the parameters taken by the *listExportedAssets* method do not exactly match the parameters taken by the List Exported Assets Catenis API method. Most of the parameters, except for the last two (`limit` and `skip`), are mapped to keys of the first parameter (`$selector`) of the *listExportedAssets* method with a few singularities: the date keys, `startDate` and `endDate`, accept for value not only strings containing ISO 8601 formatted dates/times but also *DateTime* objects.

### Listing asset migrations

[](#listing-asset-migrations)

```
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
}
```

> **Note**: the parameters taken by the *listAssetMigrations* method do not exactly match the parameters taken by the List Asset Migrations Catenis API method. Most of the parameters, except for the last two (`limit` and `skip`), are mapped to keys of the first parameter (`$selector`) of the *listAssetMigrations* method with a few singularities: the date keys, `startDate` and `endDate`, accept for value not only strings containing ISO 8601 formatted dates/times but also *DateTime* objects.

### Listing system defined permission events

[](#listing-system-defined-permission-events)

```
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
}
```

### Retrieving permission rights currently set for a specified permission event

[](#retrieving-permission-rights-currently-set-for-a-specified-permission-event)

```
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
}
```

### Setting permission rights at different levels for a specified permission event

[](#setting-permission-rights-at-different-levels-for-a-specified-permission-event)

```
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
}
```

### Checking effective permission right applied to a given device for a specified permission event

[](#checking-effective-permission-right-applied-to-a-given-device-for-a-specified-permission-event)

```
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
}
```

### Retrieving identification information of a given device

[](#retrieving-identification-information-of-a-given-device)

```
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
}
```

### Listing system defined notification events

[](#listing-system-defined-notification-events)

```
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
}
```

Notifications
-------------

[](#notifications)

The Catenis API PHP client makes it easy for receiving notifications from the Catenis system by embedding a WebSocket client. All the end user needs to do is open a WebSocket notification channel for the desired Catenis notification event, and monitor the activity on that channel.

Notifications require that an event loop be used. You should then pass the event loop instance as an option when instantiating the *ApiClient* object, in the same way as when using the asynchronous API methods.

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

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

> **Note**: if no event loop instance is passed when instantiating the *ApiClient* object, an internal event loop is created. However, in that case, notifications will only be processed once the application is shut down (and the event loop is finally run).

### Receiving notifications

[](#receiving-notifications)

Instantiate WebSocket notification channel object.

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

Add listeners.

```
$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);
});
```

> **Note**: the `data` argument of the *notify* event contains the deserialized JSON notification message (a *stdClass*instance) of the corresponding notification event.

Open notification channel.

```
$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
    }
);
```

> **Note**: the `open()` method of the WebSocket notification channel object works in an asynchronous way, and as such it returns a promise like the asynchronous API methods do.

Close notification channel.

```
$wsNtfyChannel->close();
```

Error handling
--------------

[](#error-handling)

Error conditions are reported by means of exception objects, which are thrown, in case of synchronous methods, or passed as an argument, in case of asynchronous methods.

### API method exceptions

[](#api-method-exceptions)

The following exceptions can take place when calling API methods:

- **CatenisClientException** - Indicates that an error took place while trying to call the Catenis API endpoint.
- **CatenisApiException** - Indicates that an error was returned by the Catenis API endpoint.

> **Note**: these two exceptions derive from a single exception, namely **CatenisException**.

The CatenisApiException object provides custom methods that can be used to retrieve some specific data about the error condition, as follows:

- `getHttpStatusCode()` - Returns the numeric status code of the HTTP response received from the Catenis API endpoint.
- `getHttpStatusMessage()` - Returns the text associated with the status code of the HTTP response received from the Catenis API endpoint.
- `getCatenisErrorMessage()` - Returns the Catenis error message returned from the Catenis API endpoint.

Usage example:

```
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;
    }
}
```

Expected result:

```
HTTP status code: 400
HTTP status message: Bad Request
Catenis error message: Invalid message ID
Compiled error message: Error returned from Catenis API endpoint: [400] Invalid message ID

```

WebSocket notification exceptions
---------------------------------

[](#websocket-notification-exceptions)

The following exceptions can take place when opening a WebSocket notification channel:

- **OpenWsConnException** - Indicates that an error took place while establishing the underlying WebSocket connection.
- **WsNotifyChannelAlreadyOpenException** - Indicates that the WebSocket notification channel (for that device and notification event) is already open.

> **Note**: these two exceptions derive from a single exception, namely **WsNotificationException**, which in turn also derives from **CatenisException**.

Usage example:

```
$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
        }
    }
);
```

Catenis API Documentation
-------------------------

[](#catenis-api-documentation)

For further information on the Catenis API, please reference the [Catenis API Documentation](https://catenis.com/docs/api).

License
-------

[](#license)

This library is released under the [MIT License](LICENSE). Feel free to fork, and modify!

Copyright © 2018-2022, Blockchain of Things Inc.

###  Health Score

27

—

LowBetter than 49% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity6

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity64

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~87 days

Recently: every ~246 days

Total

17

Last Release

1312d ago

Major Versions

1.0.3 → 2.0.02019-05-09

2.1.3 → 3.0.02019-08-21

v3.0.1 → v4.0.02020-01-21

v4.1.0 → v5.0.02021-09-02

v5.0.0 → v6.0.02022-09-30

### Community

Maintainers

![](https://www.gravatar.com/avatar/3257008c43345c0301523f477c8ea104ece07e9dd9467f115c4e264a007f536b?d=identicon)[claudiosdc](/maintainers/claudiosdc)

---

Top Contributors

[![claudiosdc](https://avatars.githubusercontent.com/u/1312285?v=4)](https://github.com/claudiosdc "claudiosdc (98 commits)")

###  Code Quality

TestsPHPUnit

Code StylePHP\_CodeSniffer

### Embed Badge

![Health badge](/badges/blockchainofthings-catenis-api-client/health.svg)

```
[![Health](https://phpackages.com/badges/blockchainofthings-catenis-api-client/health.svg)](https://phpackages.com/packages/blockchainofthings-catenis-api-client)
```

###  Alternatives

[tencentcloud/tencentcloud-sdk-php

TencentCloudApi php sdk

3731.2M42](/packages/tencentcloud-tencentcloud-sdk-php)[zerodha/phpkiteconnect

The PHP client library for the Kite Connect trading APIs Resources

463.4k2](/packages/zerodha-phpkiteconnect)[convertkit/convertkitapi

Kit PHP SDK for the Kit API

2167.1k1](/packages/convertkit-convertkitapi)[mapado/rest-client-sdk

Rest Client SDK for hydra API

1125.9k2](/packages/mapado-rest-client-sdk)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
