# PHP Contractless RPC `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. The package can be used to build faucets, block explorers, wallets, application backends, storage applications, and other public Contractless integrations. ## Scope 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 Miner relay commands, network mapping changes, monitor-state commands, node setup synchronization, and server-owner IP controls are intentionally not exposed. ## Requirements - 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 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. Install and test the Contractless PHP Modules before installing this package: https://contractless.dev/contractless/Contractless-PHP-Modules ## Installation Install version `0.2` through Packagist: ```bash composer require contractless/contractless-php-rpc:^0.2.0 -W ``` Composer installs the package under `vendor/` and generates the required autoload files. ## Create A Client ```php networkInfo(), true, flags: JSON_THROW_ON_ERROR, ); ``` 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. ## Transaction And Mempool RPC ```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: ```php $reply = $client->submitTransaction($signedTransactionHex); ``` 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 ```php use Contractless\Rpc\Transaction\TransferBuilder; $transfer = (new TransferBuilder($crypto, $credentials))->create( sender: $sender, receiver: $receiver, coin: 'CLTC', value: 2_500_000_000, fee: 25_000_000, nftSeries: 0, ); $reply = $client->submitSignedTransaction($transfer); ``` The same builder transfers base currency, tokens, NFTs, and RWAs. Set `nftSeries` to the applicable series number for an NFT/RWA transfer. ## Token, NFT, RWA, Burn, And Vanity Builders ```php use Contractless\Rpc\Transaction\AssetBuilder; $assets = new AssetBuilder($crypto, $credentials); $createToken = $assets->createToken( creator: $address, ticker: 'TOKEN', number: 1_000_000_000, hardLimit: true, fee: 100_000_000, ); $issueToken = $assets->issueToken( creator: $address, ticker: 'TOKEN', number: 500_000_000, fee: 100_000_000, ); $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: ```php 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, ); $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 $credentials = Credentials::fromWalletFile( '/private/path/contractless.wallet', 'wallet decryption key', ); ``` 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 Wallet files and decryption keys must remain outside public web directories, logs, repositories, and client-visible configuration. Verify a wallet before using it: ```bash php vendor/contractless/contractless-php-rpc/examples/load_wallet.php \ /private/path/contractless.wallet \ 'wallet decryption key' ``` Applications that already manage protected raw keys may load hexadecimal Falcon keys directly: ```php $credentials = Credentials::fromHex($publicKeyHex, $privateKeyHex); ``` ## 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. ## Testing ```bash composer install composer test ``` Validate package metadata before publishing a release: ```bash composer validate --strict ```