Contractless-PHP-RPC/README.md

708 lines
18 KiB
Markdown
Raw Normal View History

2026-07-25 22:38:40 +00:00
# PHP Contractless RPC
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
`contractless-php-rpc` allows PHP applications to connect directly to a
Contractless node through its authenticated binary RPC protocol. It does not
invoke Contractless CLI programs and does not depend on centralized blockchain
APIs.
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
The package can be used to build faucets, block explorers, wallets, application
backends, storage applications, and other public Contractless integrations.
2026-07-25 21:23:28 +00:00
## Scope
2026-07-25 23:28:38 +00:00
The library exposes application-safe public RPC commands, including:
- Network and blockchain information
- Blocks, headers, and torrents
- Confirmed transactions and mempool records
- Wallet balances, registration, addresses, and history
- Tokens, NFTs, RWAs, swaps, and loans
- Marketing records
- On-chain data storage
- Governance proposals
- Transaction creation, signing, and broadcasting
2026-07-25 21:23:28 +00:00
Miner relay commands, network mapping changes, monitor-state commands, node
setup synchronization, and server-owner IP controls are intentionally not
exposed.
## Requirements
2026-07-25 23:28:38 +00:00
- PHP 8.1 or newer using a 64-bit build
- PHP GD
- PHP OpenSSL
- The `skein` module from Contractless PHP Modules
- The `oqsphp` module from Contractless PHP Modules
2026-07-25 21:23:28 +00:00
Contractless uses the same Falcon key and signature sizes as
`Falcon-padded-512`, but its FN-DSA signing mode requires a small message
adaptation. `NativeCrypto` performs that adaptation automatically.
2026-07-25 23:28:38 +00:00
Install and test the Contractless PHP Modules before installing this package:
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
https://contractless.dev/contractless/Contractless-PHP-Modules
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
## Installation
2026-07-25 22:38:40 +00:00
2026-07-25 23:28:38 +00:00
Install version `0.2` through Packagist:
2026-07-25 21:23:28 +00:00
```bash
2026-07-25 23:28:38 +00:00
composer require contractless/contractless-php-rpc:^0.2.0 -W
2026-07-25 21:23:28 +00:00
```
2026-07-25 23:28:38 +00:00
Composer installs the package under `vendor/` and generates the required
autoload files.
## Create A Client
2026-07-25 21:23:28 +00:00
```php
<?php
require __DIR__ . '/vendor/autoload.php';
use Contractless\Rpc\Client;
use Contractless\Rpc\Crypto\NativeCrypto;
use Contractless\Rpc\Transport\StreamTransport;
use Contractless\Rpc\Wallet\Credentials;
2026-07-25 23:28:38 +00:00
$credentials = Credentials::fromWalletFile(
'/private/path/contractless.wallet',
'wallet decryption key',
2026-07-25 21:23:28 +00:00
);
2026-07-25 23:28:38 +00:00
2026-07-25 21:23:28 +00:00
$crypto = new NativeCrypto();
$client = new Client(
2026-07-25 23:28:38 +00:00
new StreamTransport(
host: '127.0.0.1',
port: 50050,
timeout: 10.0,
),
2026-07-25 21:23:28 +00:00
$crypto,
$credentials,
);
2026-07-25 23:28:38 +00:00
```
Use port `50050` for the default testnet RPC and `50055` for the default
mainnet RPC unless the node operator configured another port.
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
Every RPC call performs a signed client handshake. The wallet used for the
handshake does not have to own an address being looked up.
## RPC Replies
RPC methods return the node's original reply as a PHP string. Depending on the
command, that string may contain text, JSON, or raw binary data.
Decode JSON replies when appropriate:
```php
$data = json_decode(
$client->networkInfo(),
true,
flags: JSON_THROW_ON_ERROR,
2026-07-25 21:23:28 +00:00
);
```
2026-07-25 23:28:38 +00:00
Do not apply text conversion to raw blocks, headers, torrents, transactions, or
binary integer replies.
A coin balance is returned as an eight-byte little-endian unsigned integer:
```php
$reply = $client->coinBalance('CLTC', $address);
$balanceAtomic = unpack('Pbalance', $reply)['balance'];
$balance = number_format($balanceAtomic / 100_000_000, 8, '.', '');
```
All transaction amounts and fees supplied to builders use atomic units.
Contractless has `100,000,000` atomic units per `CLC` or `CLTC`.
## Network And Chain RPC
```php
$network = $client->networkInfo();
$height = $client->blockHeight();
$nodeTime = $client->nodeTime();
$difficulty = $client->difficulty();
$latestBlock = $client->latestBlock();
$largestFee = $client->largestTransactionFee();
$confirmedCount = $client->totalConfirmedTransactions();
```
- `networkInfo()` returns the node's public network information.
- `blockHeight()` returns the current saved chain height.
- `nodeTime()` returns the node's current time.
- `difficulty()` returns the current mining difficulty.
- `latestBlock()` returns the latest saved block.
- `largestTransactionFee()` returns the largest eligible mempool fee.
- `totalConfirmedTransactions()` returns the confirmed transaction count.
## Block, Header, And Torrent RPC
Hashes passed to these methods are 64-character hexadecimal hashes:
```php
$block = $client->blockByHeight(1000);
$block = $client->blockByHash($blockHash);
$torrent = $client->torrentByHeight(1000);
$header = $client->headerByHeight(1000);
$header = $client->headerByHash($headerHash);
$blockHash = $client->blockHashAtHeight(1000);
$headers = $client->allHeaders();
```
- `blockByHeight()` returns the raw saved block at a height.
- `blockByHash()` returns the raw block matching a block hash.
- `torrentByHeight()` returns the raw torrent metadata for a block.
- `headerByHeight()` returns the raw block header at a height.
- `headerByHash()` returns the raw header matching a header hash.
- `blockHashAtHeight()` returns the hash recorded at a height.
- `allHeaders()` returns the saved block-header history.
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
## Transaction And Mempool RPC
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
```php
$mempoolCount = $client->mempoolCount();
$mempoolTransaction = $client->mempoolTransactionBySignature(
$signatureHex,
);
$addressMempool = $client->mempoolTransactionsByAddress($address);
$transaction = $client->transactionById($transactionId);
```
- `mempoolCount()` returns the number of pending transactions.
- `mempoolTransactionBySignature()` accepts a 1,332-character hexadecimal
Falcon signature and returns the matching pending transaction.
- `mempoolTransactionsByAddress()` returns pending transactions involving an
address.
- `transactionById()` accepts a 64-character hexadecimal transaction ID and
returns the confirmed transaction and its recorded location.
Submit a complete serialized signed transaction represented as hexadecimal:
2026-07-25 21:23:28 +00:00
```php
$reply = $client->submitTransaction($signedTransactionHex);
```
2026-07-25 23:28:38 +00:00
Submit a `SignedTransaction` produced by one of the included builders:
```php
$reply = $client->submitSignedTransaction($signedTransaction);
```
## Wallet And Address RPC
```php
$coinBalance = $client->coinBalance('CLTC', $address);
$allBalances = $client->totalBalance($address);
$addressValid = $client->validateAddress($address);
$registration = $client->walletRegistrationStatus($address);
$history = $client->addressHistory($address, skip: 0, limit: 100);
$latest = $client->latestAddressTransactions($address, limit: 25);
$canonical = $client->vanityLookup('my-vanity-address.cltc');
$vanityOwner = $client->vanityOwner('my-vanity-address.cltc');
```
- `coinBalance()` returns one confirmed asset balance as an eight-byte integer.
- `totalBalance()` returns all confirmed balances owned by an address.
- `validateAddress()` asks the node to validate an address.
- `walletRegistrationStatus()` returns whether the wallet is registered.
- `addressHistory()` returns a paginated confirmed transaction history.
- `latestAddressTransactions()` returns the newest confirmed transactions.
- `vanityLookup()` resolves a vanity address to its canonical wallet.
- `vanityOwner()` returns the canonical owner of a vanity address.
Verify a signed message:
```php
$result = $client->validateMessage(
message: 'Message to verify',
address: $address,
signature: $signatureHex,
);
```
Register the wallet loaded into `$credentials`:
```php
$result = $client->registerOwnedWallet($address);
```
The lower-level registration method is available when an application has
already created the required signature:
```php
$result = $client->registerWallet($address, $signatureHex);
```
## Token, NFT, RWA, Swap, And Loan RPC
```php
$tokens = $client->tokenList();
$tokenCatalog = $client->tokenCatalog();
$token = $client->tokenDetails('TOKEN');
$nfts = $client->nftList();
$nft = $client->nftDetails('ART', series: 1);
$loan = $client->loanByHash($loanHash);
$addressContracts = $client->contractsByAddress($address);
$collateral = $client->collateralStatus($loanHash);
```
- `tokenList()` returns the node's token list.
- `tokenCatalog()` returns token catalog data.
- `tokenDetails()` returns details for one token ticker.
- `nftList()` returns the node's NFT and RWA list.
- `nftDetails()` returns one NFT/RWA name and series record.
- `loanByHash()` returns a loan using its contract hash.
- `contractsByAddress()` returns loan contracts involving an address.
- `collateralStatus()` returns the collateral state of a loan.
## Marketing RPC
```php
$records = $client->marketingCampaignHistory(
advertiser: $address,
campaign: 42,
skip: 0,
limit: 100,
);
```
`marketingCampaignHistory()` returns paginated marketing records for one
advertiser and campaign number.
## Data Storage RPC
Get the cost of returning one data key:
```php
$quote = $client->storageLookupCost(
storageKey: $storageKeyHash,
dataKey: 'username',
address: $address,
);
```
Get the cost of returning every key stored under the storage key and address:
```php
$quote = $client->storageLookupCost(
storageKey: $storageKeyHash,
dataKey: 'all',
address: $address,
);
```
Perform a paid lookup using a complete signed type-2 transfer represented as
hexadecimal:
```php
$data = $client->storageLookup(
storageKey: $storageKeyHash,
dataKey: 'all',
address: $address,
paymentTransactionHex: $paymentTransactionHex,
);
```
The payment must match the quote returned by the selected node. A node operator
looking up data through their own node may receive a zero-cost quote.
## Governance RPC
```php
$proposal = $client->governanceProposal($proposalKeyHash);
```
`governanceProposal()` returns the recorded state of a Contractless proposal,
including its voting and activation information.
## Create And Broadcast A Transfer
2026-07-25 21:23:28 +00:00
```php
use Contractless\Rpc\Transaction\TransferBuilder;
$transfer = (new TransferBuilder($crypto, $credentials))->create(
2026-07-25 23:28:38 +00:00
sender: $sender,
receiver: $receiver,
2026-07-25 21:23:28 +00:00
coin: 'CLTC',
value: 2_500_000_000,
2026-07-25 23:28:38 +00:00
fee: 25_000_000,
nftSeries: 0,
2026-07-25 21:23:28 +00:00
);
$reply = $client->submitSignedTransaction($transfer);
```
2026-07-25 23:28:38 +00:00
The same builder transfers base currency, tokens, NFTs, and RWAs. Set
`nftSeries` to the applicable series number for an NFT/RWA transfer.
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
## Token, NFT, RWA, Burn, And Vanity Builders
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
```php
use Contractless\Rpc\Transaction\AssetBuilder;
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
$assets = new AssetBuilder($crypto, $credentials);
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
$createToken = $assets->createToken(
creator: $address,
ticker: 'TOKEN',
number: 1_000_000_000,
hardLimit: true,
fee: 100_000_000,
);
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
$issueToken = $assets->issueToken(
creator: $address,
ticker: 'TOKEN',
number: 500_000_000,
fee: 100_000_000,
);
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
$createNft = $assets->createNft(
creator: $address,
series: false,
fractionalOwnership: false,
name: 'ART',
ipfs: 'ipfs-content-identifier',
count: 1,
description: 'Description of the NFT or RWA',
fee: 100_000_000,
);
$burn = $assets->burn(
address: $address,
coin: 'TOKEN',
nftSeries: 0,
value: 100_000_000,
fee: 100_000_000,
);
$vanity = $assets->vanity(
address: $address,
vanityAddress: 'my-address.cltc',
fee: 100_000_000,
);
$client->submitSignedTransaction($createToken);
```
## Swap And Loan Builders
Swaps and loans require two wallet signatures. The first wallet creates and
signs the transaction:
2026-07-25 22:38:40 +00:00
```php
2026-07-25 23:28:38 +00:00
use Contractless\Rpc\Transaction\AgreementBuilder;
$agreements = new AgreementBuilder($crypto, $credentials);
$swap = $agreements->swap(
expiration: time() + 3600,
ticker1: 'CLTC',
series1: 0,
value1: 100_000_000,
ticker2: 'TOKEN',
series2: 0,
value2: 500_000_000,
sender1: $firstAddress,
sender2: $secondAddress,
tip1: 1_000_000,
tip2: 1_000_000,
fee1: 2_500,
fee2: 2_500,
);
$loan = $agreements->loan(
loanCoin: 'CLTC',
loanAmount: 1_000_000_000,
lender: $lender,
collateral: 'TOKEN',
collateralAmount: 2_000_000_000,
borrower: $borrower,
paymentPeriod: 'm',
paymentNumber: 12,
paymentAmount: 100_000_000,
gracePeriod: 2,
maxLateValue: 300_000_000,
fee: 100_000_000,
);
```
Export the partially signed transaction for the second party:
```php
$exported = json_encode(
$swap->export(),
JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR,
);
```
The second party imports and signs it using their own `$crypto` and
`$credentials`:
```php
use Contractless\Rpc\Transaction\DualSignedTransaction;
$swap = DualSignedTransaction::import(
json_decode($exported, true, flags: JSON_THROW_ON_ERROR),
);
$swap = $swap->signNext($crypto, $credentials);
if ($swap->isComplete()) {
$client->submitSignedTransaction($swap->toSignedTransaction());
}
```
## Loan Payment And Collateral Builders
```php
use Contractless\Rpc\Transaction\MiscellaneousBuilder;
$miscellaneous = new MiscellaneousBuilder($crypto, $credentials);
$payment = $miscellaneous->loanPayment(
amount: 100_000_000,
contractHash: $loanHash,
address: $borrower,
tip: 1_000_000,
fee: 2_500,
);
$claim = $miscellaneous->collateralClaim(
contractHash: $loanHash,
address: $lender,
fee: 100_000_000,
);
$client->submitSignedTransaction($payment);
```
## Marketing Transaction Builder
```php
$marketing = $miscellaneous->marketing(
campaign: 42,
adType: 'banner',
keyword: 'blockchain',
displayed: 'https://example.com/page',
impressions: 1,
clicks: 0,
impressionValue: 100,
clickValue: 500,
advertiser: $address,
fee: 100_000_000,
);
$client->submitSignedTransaction($marketing);
```
## Data Storage Transaction Builders
```php
use Contractless\Rpc\Transaction\StorageBuilder;
$storage = new StorageBuilder($crypto, $credentials);
$createKey = $storage->createKey(
address: $address,
fee: 50_000_000_000,
);
$boolean = $storage->boolean(
$storageKey, 'active', true, $address, 100_000_000,
);
$u8 = $storage->u8($storageKey, 'u8-key', 255, $address, 100_000_000);
$u16 = $storage->u16($storageKey, 'u16-key', 65535, $address, 100_000_000);
$u32 = $storage->u32($storageKey, 'u32-key', 100000, $address, 100_000_000);
$u64 = $storage->u64($storageKey, 'u64-key', 100000, $address, 100_000_000);
$u128 = $storage->u128(
$storageKey,
'u128-key',
'340282366920938463463374607431768211455',
$address,
100_000_000,
);
$i8 = $storage->i8($storageKey, 'i8-key', -128, $address, 100_000_000);
$i16 = $storage->i16($storageKey, 'i16-key', -32768, $address, 100_000_000);
$i32 = $storage->i32($storageKey, 'i32-key', -100000, $address, 100_000_000);
$i64 = $storage->i64($storageKey, 'i64-key', -100000, $address, 100_000_000);
$i128 = $storage->i128(
$storageKey,
'i128-key',
'-170141183460469231731687303715884105728',
$address,
100_000_000,
);
$string = $storage->string(
storageKey: $storageKey,
key: 'username',
value: 'contractless-user',
address: $address,
previousHash: str_repeat('0', 64),
fee: 100_000_000,
);
2026-07-25 22:38:40 +00:00
2026-07-25 23:28:38 +00:00
$delete = $storage->delete(
storageKey: $storageKey,
key: 'username',
address: $address,
fee: 100_000_000,
);
$client->submitSignedTransaction($string);
```
The `u128()` and `i128()` methods accept decimal strings so PHP does not lose
precision. String values may contain up to 180 bytes per linked transaction.
The first string uses 64 zeroes for `previousHash`; later chunks use the hash
of the preceding string transaction.
## Governance Transaction Builders
```php
use Contractless\Rpc\Transaction\GovernanceBuilder;
$governance = new GovernanceBuilder($crypto, $credentials);
$proposal = $governance->proposal(
proposalHash: $proposalDocumentHash,
address: $address,
fee: 100_000_000,
);
$proposalVote = $governance->proposalVote(
proposalKey: $proposalKey,
address: $address,
approve: true,
fee: 100_000_000,
);
$activationVote = $governance->activationVote(
proposalKey: $proposalKey,
developmentHash: $implementationDocumentHash,
developmentLocation: 'CLP-0001-IMPLEMENTATION.md',
address: $address,
approve: true,
fee: 100_000_000,
);
$client->submitSignedTransaction($proposalVote);
```
The network still determines whether the submitting wallet is an eligible node
for proposal and activation voting.
## Signed Transaction Data
Every single-signature builder returns `SignedTransaction`:
```php
$rawBytes = $transaction->bytes;
$hexadecimal = $transaction->toHex();
$signatureBytes = $transaction->signature;
$unsignedSigningJson = $transaction->unsignedJson;
```
Use `submitSignedTransaction()` to broadcast the exact bytes without converting
them to hexadecimal first.
## Wallet Files
Load a normal Contractless wallet:
```php
2026-07-25 22:38:40 +00:00
$credentials = Credentials::fromWalletFile(
'/private/path/contractless.wallet',
'wallet decryption key',
);
```
2026-07-25 23:28:38 +00:00
The loader:
- Decodes every supported Contractless private-key image orientation
- Verifies the encrypted payload HMAC
- Decrypts the Falcon private key
- Proves that the public and private keys match
- Verifies the wallet's canonical short address
2026-07-25 22:38:40 +00:00
Wallet files and decryption keys must remain outside public web directories,
logs, repositories, and client-visible configuration.
2026-07-25 23:28:38 +00:00
Verify a wallet before using it:
2026-07-25 22:38:40 +00:00
```bash
2026-07-25 23:28:38 +00:00
php vendor/contractless/contractless-php-rpc/examples/load_wallet.php \
/private/path/contractless.wallet \
'wallet decryption key'
2026-07-25 22:38:40 +00:00
```
2026-07-25 23:28:38 +00:00
Applications that already manage protected raw keys may load hexadecimal
Falcon keys directly:
2026-07-25 22:38:40 +00:00
```php
$credentials = Credentials::fromHex($publicKeyHex, $privateKeyHex);
```
2026-07-25 21:23:28 +00:00
2026-07-25 23:28:38 +00:00
## Direct Public Command Calls
The named methods above are wrappers around `call()`. Applications may invoke
an exposed public command directly when they need to construct its binary
payload themselves:
```php
use Contractless\Rpc\Protocol\Command;
$reply = $client->call(Command::BLOCK_HEIGHT);
```
With a payload:
```php
use Contractless\Rpc\Protocol\Binary;
$reply = $client->call(
Command::BLOCK_BY_HEIGHT,
Binary::u32(1000),
);
```
`call()` rejects internal, administrative, mining, monitor, and unsupported
command numbers. Prefer the named methods whenever one exists because they
validate and encode the command payload correctly.
## Error Handling
RPC transport, protocol, validation, and wallet failures throw exceptions:
```php
try {
$height = $client->blockHeight();
} catch (Throwable $error) {
error_log($error->getMessage());
}
```
Do not expose wallet errors, filesystem paths, private material, or internal
node replies directly to public users.
2026-07-25 21:23:28 +00:00
## Testing
```bash
composer install
composer test
```
2026-07-25 23:28:38 +00:00
Validate package metadata before publishing a release:
2026-07-25 21:23:28 +00:00
```bash
composer validate --strict
```