wishknish / knishio-client-php
Knish.IO PHP API Client.
Requires
- php: ^8.2
- ext-json: *
- ext-sodium: *
- guzzlehttp/guzzle: ^7.15.2
- guzzlehttp/psr7: ^2.13
- simplito/bigint-wrapper-php: ^1.0
- symfony/polyfill-mbstring: ^1.24
- tuupola/base58: ^2.1
- webonyx/graphql-php: ^15.32.3
Requires (Dev)
- jetbrains/phpstorm-attributes: ^1.0
- phpstan/phpstan: ^2
- phpunit/phpunit: ^11
- symfony/var-dumper: ^6.4
Suggests
- ext-ffi: Runs SHAKE256, WOTS+ and ML-KEM in-process through the bundled libkcore (kcore/<target>/); without it the SDK uses pure PHP and a persistent Node helper
Provides
None
Conflicts
None
Replaces
None
- dev-master
- 1.7.0
- 1.6.1
- 1.6.0
- 1.5.0
- 1.4.0
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.1
- 1.2.0
- 1.1.0
- 1.0.0
- 0.9.3
- 0.9.2
- 0.9.0
- 0.8.1
- 0.8.0
- 0.6.4
- 0.4.0
- 0.2.0
- 0.1.64
- 0.1.63
- 0.1.62
- 0.1.61
- 0.1.60
- 0.1.59
- 0.1.58
- 0.1.57
- 0.1.56
- 0.1.53
- 0.1.52
- 0.1.51
- 0.1.50
- 0.1.49
- 0.1.48
- 0.1.47
- 0.1.46
- 0.1.45
- 0.1.44
- 0.1.43
- 0.1.42
- 0.1.41
- 0.1.40
- 0.1.39
- 0.1.38
- 0.1.37
- 0.1.36
- 0.1.35
- 0.1.34
- 0.1.33
- 0.1.32
- 0.1.31
- 0.1.30
- 0.1.29
- dev-eugene-teplitsky-dev
- dev-ykizilov-dev
- dev-eugene-teplitsky-server-rebuild
- dev-staging
- dev-vladimir-makarov-major-restructuring
- dev-vladimir-makarov-staging
- dev-vladimir-makarov-remove-tokens-table
- dev-y-kizilov-dev
- dev-y-kizilov-sev
- dev-vladimir-makarov-molecule-collision
- dev-ilya-berzin-dev
- dev-y-kizilov-encryption
This package is auto-updated.
Last update: 2026-10-30 13:14:29 UTC
README
info@wishknish.com | https://wishknish.com
Knish.IO PHP Client SDK
This is the official PHP implementation of the Knish.IO client SDK. Its purpose is to expose class libraries for building and signing Knish.IO Molecules, composing Atoms, generating Wallets, and much more.
Installation
The SDK can be installed via Composer:
composer require wishknish/knishio-client-php
Requirements:
- PHP 8.2 or higher
- Required extensions:
ext-json,ext-sodium,ext-mbstring, andext-gmporext-bcmath - Composer for dependency management
- Recommended:
ext-ffi, which runs the crypto natively (below)
Native crypto: the bundled libkcore
The package ships the KnishIO Crypto Core (kcore/<target>/, for linux x64/arm64 glibc and
musl, macOS universal and Windows x64). With ext-ffi loaded, SHAKE256, the WOTS+ signature
chains and ML-KEM run in-process through it: deriving an OTS v2 wallet or signing a molecule
takes well under a millisecond instead of tens to hundreds. Nothing else is needed; results are
identical either way.
FFI must be allowed where the SDK runs. The default ffi.enable=preload allows it in the CLI but
not under php-fpm; there, set ffi.enable=true in php.ini to use the library.
KNISHIO_KCORE selects the backend: auto (default: use the library when it loads), off
(never) or require (throw KcoreUnavailableException if it cannot be loaded, so a deployment
cannot silently run the slow path). KNISHIO_KCORE_LIB points to a different copy of the library.
PostQuantumCrypto::backend() reports kcore or noble-bridge-persistent.
Without ext-ffi: the Node ML-KEM helper
Without the library, SHAKE256 and WOTS+ run in pure PHP, and ML-KEM runs in one long-lived
Node.js helper per PHP process (bin/noble-mlkem-bridge.js, started on first use through
proc_open). That path needs Node.js 20.19 or higher on the PATH and @noble/post-quantum in
the package's own node_modules, which Composer does not install. Run this once after
composer require (and after each update of the package), from your project root:
(cd vendor/wishknish/knishio-client-php && npm ci --omit=dev)
With neither the library nor the helper's dependencies, every ML-KEM operation fails, including the
wallet an authorization signs with: new Wallet($secret, 'AUTH') throws @noble/post-quantum not installed.
After installation, include the autoloader in your project:
<?php require_once 'vendor/autoload.php'; use WishKnish\KnishIO\Client\KnishIOClient; use WishKnish\KnishIO\Client\Libraries\Crypto; use WishKnish\KnishIO\Client\Wallet;
Basic Usage
The purpose of the Knish.IO SDK is to expose various ledger functions to new or existing applications.
There are two ways to take advantage of these functions:
-
The easy way: use the
KnishIOClientwrapper class -
The granular way: build
AtomandMoleculeinstances and broadcast GraphQL messages yourself
This document will explain both ways.
The Easy Way: KnishIOClient Wrapper
-
Include the wrapper class in your application code:
<?php use WishKnish\KnishIO\Client\KnishIOClient;
-
Instantiate the class with your node URI:
$client = new KnishIOClient('http://localhost:8000/graphql'); $client->setCellSlug('my-cell-slug');
-
Request authorization token from the node:
$response = $client->requestAuthToken($secret); if ($response->success()) { // Authentication successful $authToken = $client->getAuthToken(); echo "Authenticated successfully!\n"; } else { throw new Exception('Authentication failed: ' . $response->reason()); }
(Note: The
$secretparameter can be a salted combination of username + password, a biometric hash, an existing user identifier from an external authentication process, for example) -
Begin using
$clientto trigger commands described below...
KnishIOClient Methods
-
Query metadata for a Wallet Bundle. Omit the
$bundleHashparameter to query your own Wallet Bundle:$response = $client->queryBundle('c47e20f99df190e418f0cc5ddfa2791e9ccc4eb297cfa21bd317dc0f98313b1d'); if ($response->success()) { $bundleData = $response->data(); print_r($bundleData); // Raw Metadata }
-
Query metadata for a Meta Asset:
$result = $client->queryMeta( metaType: 'Vehicle', metaId: null, // Meta ID key: 'LicensePlate', value: '1H17P', latest: true, // Limit meta values to latest per key throughAtom: true // Optional, query through Atom (default: true) ); print_r($result); // Raw Metadata
-
Writing new metadata for a Meta Asset:
$response = $client->createMeta( metaType: 'Pokemon', metaId: 'Charizard', metadata: [ 'type' => 'fire', 'weaknesses' => [ 'rock', 'water', 'electric' ], 'immunities' => [ 'ground', ], 'hp' => 78, 'attack' => 84, ] ); if ($response->success()) { // Do things! echo "Metadata created successfully!\n"; } print_r($response->data()); // Raw response
-
Query Wallets associated with a Wallet Bundle:
$wallets = $client->queryWallets( bundleHash: 'c47e20f99df190e418f0cc5ddfa2791e9ccc4eb297cfa21bd317dc0f98313b1d', tokenSlug: 'FOO' // Optional, filter by token ); print_r($wallets); // Raw response
-
Declaring new Wallets:
(Note: If Tokens are sent to undeclared Wallets, Shadow Wallets will be used (placeholder Wallets that can receive, but cannot send) to store tokens until they are claimed.)
$response = $client->createWallet('FOO'); // Token Slug for the wallet we are declaring if ($response->success()) { // Do things! echo "Wallet created successfully!\n"; } print_r($response->data()); // Raw response
-
Issuing new Tokens:
$response = $client->createToken( tokenSlug: 'CRZY', // Token slug (ticker symbol) amount: 100000000, // Initial amount to issue meta: [ 'name' => 'CrazyCoin', // Public name for the token 'fungibility' => 'fungible', // Fungibility style (fungible / nonfungible / stackable) 'supply' => 'limited', // Supply style (limited / replenishable) 'decimals' => 2 // Decimal places ], units: [], // Optional, for stackable tokens batchId: null // Optional, for stackable tokens ); if ($response->success()) { // Do things! echo "Token created successfully!\n"; } print_r($response->data()); // Raw response
-
Transferring Tokens to other users:
$response = $client->transferToken( bundleHash: '7bf38257401eb3b0f20cabf5e6cf3f14c76760386473b220d95fa1c38642b61d', // Recipient's bundle hash tokenSlug: 'CRZY', // Token slug amount: 100, units: [], // Optional, for stackable tokens batchId: null // Optional, for stackable tokens ); if ($response->success()) { // Do things! echo "Token transferred successfully!\n"; } print_r($response->data()); // Raw response
-
Creating a new Rule:
$response = $client->createRule( metaType: 'MyMetaType', metaId: 'MyMetaId', rule: [ // Rule definition ], policy: [] // Optional policy object ); if ($response->success()) { // Do things! echo "Rule created successfully!\n"; } print_r($response->data()); // Raw response
-
Querying Atoms:
$response = $client->queryAtom([ 'molecularHash' => 'hash', 'bundleHash' => 'bundle', 'isotope' => 'V', 'tokenSlug' => 'CRZY', 'latest' => true, 'limit' => 15, 'offset' => 1 ]); print_r($response->data()); // Raw response
-
Working with Buffer Tokens:
// Deposit to buffer $depositResponse = $client->depositBufferToken( tokenSlug: 'CRZY', amount: 100, tradeRates: [ 'OTHER_TOKEN' => 0.5 ] ); // Withdraw from buffer $withdrawResponse = $client->withdrawBufferToken( tokenSlug: 'CRZY', amount: 50 ); print_r([$depositResponse->data(), $withdrawResponse->data()]); // Raw responses
-
Getting client fingerprint:
$fingerprint = $client->getFingerprint(); echo $fingerprint . "\n"; $fingerprintData = $client->getFingerprintData(); print_r($fingerprintData);
Advanced Usage: Working with Molecules
For more granular control, you can work directly with Molecules:
-
Create a new Molecule:
use WishKnish\KnishIO\Client\Molecule; $molecule = new Molecule($secret, $sourceWallet, $remainderWallet, $cellSlug);
-
Create a custom Mutation:
use WishKnish\KnishIO\Client\Mutation\MutationProposeMolecule; $mutation = new MutationProposeMolecule($client, $molecule);
-
Sign and check a Molecule:
$molecule->sign(); try { $molecule->check(); echo "Molecule validation passed!\n"; } catch (\Exception $e) { echo "Molecule validation failed: " . $e->getMessage() . "\n"; }
-
Execute a custom Query or Mutation:
$response = $client->executeQuery($mutation); if ($response->success()) { echo "Molecule executed successfully!\n"; }
The Hard Way: DIY Everything
This method involves individually building Atoms and Molecules, triggering the signature and validation processes, and communicating the resulting signed Molecule mutation or Query to a Knish.IO node via GraphQL.
-
Include the relevant classes in your application code:
<?php use WishKnish\KnishIO\Client\{Molecule, Wallet, Atom}; use WishKnish\KnishIO\Client\Libraries\Crypto;
-
Generate a 2048-symbol hexadecimal secret, either randomly, or via hashing login + password + salt, OAuth secret ID, biometric ID, or any other static value.
-
(optional) Initialize a signing wallet with:
$wallet = new Wallet( secret: $secret, token: $tokenSlug, position: $customPosition, // (optional) instantiate specific wallet instance vs. random characters: $characterSet // (optional) override the character set used by the wallet );
WARNING 1: If ContinuID is enabled on the node, you will need to use a specific wallet, and therefore will first need to query the node to retrieve the
positionfor that wallet.WARNING 2: The Knish.IO protocol mandates that all C and M transactions be signed with a
USERtoken wallet. -
Build your molecule with:
$molecule = new Molecule( secret: $secret, sourceWallet: $sourceWallet, // (optional) wallet for signing remainderWallet: $remainderWallet, // (optional) wallet to receive remainder tokens cellSlug: $cellSlug // (optional) used to point a transaction to a specific branch of the ledger );
-
Either use one of the shortcut methods provided by the
Moleculeclass (which will buildAtominstances for you), or createAtominstances yourself.DIY example:
// This example records a new Wallet on the ledger // Define metadata for our new wallet $newWalletMeta = [ 'address' => $newWallet->address, 'token' => $newWallet->token, 'bundle' => $newWallet->bundle, 'position' => $newWallet->position, 'batchId' => $newWallet->batchId, ]; // Build the C isotope atom $walletCreationAtom = new Atom( position: $sourceWallet->position, walletAddress: $sourceWallet->address, isotope: 'C', token: $sourceWallet->token, metaType: 'wallet', metaId: $newWallet->address, meta: $newWalletMeta, index: $molecule->generateIndex() ); // Add the atom to our molecule $molecule->addAtom($walletCreationAtom); // Adding a ContinuID / remainder atom $molecule->addContinuIdAtom();
Molecule shortcut method example:
// This example commits metadata to some Meta Asset // Defining our metadata $metadata = [ 'foo' => 'Foo', 'bar' => 'Bar' ]; $molecule->initMeta( meta: $metadata, metaType: 'MyMetaType', metaId: 'MetaId123' );
-
Sign the molecule with the stored user secret:
$molecule->sign();
-
Make sure everything checks out by verifying the molecule:
try { $molecule->check(); // If we're validating a V isotope transaction, // add the source wallet as a parameter $molecule->check($sourceWallet); echo "Molecule validation passed!\n"; } catch (\Exception $e) { echo "Molecule check failed: " . $e->getMessage() . "\n"; // Handle the error }
-
Broadcast the molecule to a Knish.IO node:
use WishKnish\KnishIO\Client\Mutation\MutationProposeMolecule; // Build our mutation object using the KnishIOClient wrapper $mutation = new MutationProposeMolecule($client, $molecule); // Send the mutation to the node and get a response $response = $client->executeQuery($mutation);
-
Inspect the response...
// For basic queries, we look at the data property: print_r($response->data()); // For mutations, check if the molecule was accepted by the ledger: echo $response->success() ? "Success" : "Failed"; // We can also check the reason for rejection echo $response->reason(); // Some queries may also produce a payload, with additional data: print_r($response->payload());
Payloads are provided by responses to the following queries:
QueryBalanceandQueryContinuId-> returns aWalletinstanceQueryWalletList-> returns a list ofWalletinstancesMutationProposeMolecule,MutationRequestAuthorization,MutationCreateIdentifier,MutationLinkIdentifier,MutationClaimShadowWallet,MutationCreateToken,MutationRequestTokens, andMutationTransferTokens-> returns molecule metadata
Network Freshness
The GraphQL transport (Guzzle) issues a fresh network request per query — there is no response caching — so a long-lived client never serves a stale read of ledger state. No fresh-read knob (e.g. a request policy) is required.
Secret storage
Master secrets are stored at rest in the cross-SDK AES-256-GCM envelope (PBKDF2-HMAC-SHA256 ×100000, camelCase metadata; frozen in sdks/shared-test-results/cross-platform-test-vectors.json). Every SDK decrypts every other SDK's envelope.
| Provider | providerType |
Custody (hardwareBacked) |
Where the key lives | Recovery passphrase |
|---|---|---|---|---|
MemorySecretStorageProvider |
memory |
Software (false) |
Process memory | Optional |
AesGcmSecretStorageProvider |
aes-gcm |
Software (false) |
Any StorageBackend (MemoryStorageBackend, FileStorageBackend [0o600]) |
Optional |
hardwareBacked is derived by the provider from the platform, never accepted from the caller; software providers always report false. A hardware provider refuses to store without a recovery passphrase unless allowUnrecoverable is set — the recovery envelope (knishio:recovery:<bundleHash>, providerType: "aes-gcm") is software custody whose strength is bounded by that passphrase: enforce passphrase entropy or keep it on a second device.
Client integration: KnishIOClient::setSecretStorage(SecretStorageProvider $storage, ?string $bundleHash) attaches a provider and persists the secret there; the client still holds the cleartext in memory.
Getting Help
Knish.IO is under active development, and our team is ready to assist with integration questions. The best way to seek help is to stop by our Telegram Support Channel. You can also send us a contact request via our website.