added full usage documentation
This commit is contained in:
parent
005f132758
commit
9808302ba8
711
README.md
711
README.md
|
|
@ -1,18 +1,26 @@
|
||||||
# PHP Contractless RPC
|
# PHP Contractless RPC
|
||||||
|
|
||||||
`contractless-php-rpc` connects PHP applications directly to a Contractless
|
`contractless-php-rpc` allows PHP applications to connect directly to a
|
||||||
node. It implements the authenticated binary RPC protocol without invoking
|
Contractless node through its authenticated binary RPC protocol. It does not
|
||||||
Contractless CLI programs.
|
invoke Contractless CLI programs and does not depend on centralized blockchain
|
||||||
|
APIs.
|
||||||
|
|
||||||
The package is intended for faucets, block explorers, wallets, application
|
The package can be used to build faucets, block explorers, wallets, application
|
||||||
backends, and other public Contractless integrations.
|
backends, storage applications, and other public Contractless integrations.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
The client includes public blockchain lookups, wallet and address lookups,
|
The library exposes application-safe public RPC commands, including:
|
||||||
mempool lookups, asset lookups, loan lookups, storage lookups, governance
|
|
||||||
lookups, wallet registration, signed transaction submission, and an RPC
|
- Network and blockchain information
|
||||||
interface restricted to application-safe commands.
|
- 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
|
Miner relay commands, network mapping changes, monitor-state commands, node
|
||||||
setup synchronization, and server-owner IP controls are intentionally not
|
setup synchronization, and server-owner IP controls are intentionally not
|
||||||
|
|
@ -20,50 +28,32 @@ exposed.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- PHP 8.1 or newer, running as a 64-bit build
|
- PHP 8.1 or newer using a 64-bit build
|
||||||
- The PHP GD extension
|
- PHP GD
|
||||||
- The PHP OpenSSL extension
|
- PHP OpenSSL
|
||||||
- The `skein` module from `contractless-php-crypto`
|
- The `skein` module from Contractless PHP Modules
|
||||||
- The `oqsphp` module from `contractless-php-crypto`
|
- The `oqsphp` module from Contractless PHP Modules
|
||||||
|
|
||||||
Contractless uses the same Falcon key and signature sizes as
|
Contractless uses the same Falcon key and signature sizes as
|
||||||
`Falcon-padded-512`, but its FN-DSA signing mode requires a small message
|
`Falcon-padded-512`, but its FN-DSA signing mode requires a small message
|
||||||
adaptation. `NativeCrypto` performs that adaptation automatically.
|
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
|
## Installation
|
||||||
|
|
||||||
Install and enable `contractless-php-crypto` first. Its repository contains the
|
Install version `0.2` through Packagist:
|
||||||
native module build, installation, and compatibility-test instructions.
|
|
||||||
|
|
||||||
When this package is available through Packagist:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
composer require contractless/contractless-php-rpc
|
composer require contractless/contractless-php-rpc:^0.2.0 -W
|
||||||
```
|
```
|
||||||
|
|
||||||
Until the package is published, add it as a Composer path repository:
|
Composer installs the package under `vendor/` and generates the required
|
||||||
|
autoload files.
|
||||||
|
|
||||||
```json
|
## Create A Client
|
||||||
{
|
|
||||||
"repositories": [
|
|
||||||
{
|
|
||||||
"type": "path",
|
|
||||||
"url": "../contractless-php-rpc"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"contractless/contractless-php-rpc": "@dev"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then install it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer update contractless/contractless-php-rpc
|
|
||||||
```
|
|
||||||
|
|
||||||
## Basic Use
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
@ -75,112 +65,634 @@ use Contractless\Rpc\Crypto\NativeCrypto;
|
||||||
use Contractless\Rpc\Transport\StreamTransport;
|
use Contractless\Rpc\Transport\StreamTransport;
|
||||||
use Contractless\Rpc\Wallet\Credentials;
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
$credentials = Credentials::fromHex(
|
$credentials = Credentials::fromWalletFile(
|
||||||
getenv('CONTRACTLESS_PUBLIC_KEY'),
|
'/private/path/contractless.wallet',
|
||||||
getenv('CONTRACTLESS_PRIVATE_KEY'),
|
'wallet decryption key',
|
||||||
);
|
);
|
||||||
|
|
||||||
$crypto = new NativeCrypto();
|
$crypto = new NativeCrypto();
|
||||||
|
|
||||||
$client = new Client(
|
$client = new Client(
|
||||||
new StreamTransport('127.0.0.1', 50050),
|
new StreamTransport(
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 50050,
|
||||||
|
timeout: 10.0,
|
||||||
|
),
|
||||||
$crypto,
|
$crypto,
|
||||||
$credentials,
|
$credentials,
|
||||||
);
|
);
|
||||||
|
```
|
||||||
|
|
||||||
echo $client->totalBalance(
|
Use port `50050` for the default testnet RPC and `50055` for the default
|
||||||
'ab13318c26250b048db92920a80a86127c933b0c.cltc',
|
mainnet RPC unless the node operator configured another port.
|
||||||
|
|
||||||
|
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,
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
RPC replies are returned as their original bytes. Commands that return JSON can
|
Do not apply text conversion to raw blocks, headers, torrents, transactions, or
|
||||||
be decoded with `json_decode($reply, true, flags: JSON_THROW_ON_ERROR)`. Binary
|
binary integer replies.
|
||||||
block, torrent, header, and transaction replies remain available without a
|
|
||||||
lossy conversion.
|
|
||||||
|
|
||||||
## Transaction Submission
|
A coin balance is returned as an eight-byte little-endian unsigned integer:
|
||||||
|
|
||||||
`submitTransaction()` accepts the complete serialized signed transaction as a
|
```php
|
||||||
hexadecimal string:
|
$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
|
```php
|
||||||
$reply = $client->submitTransaction($signedTransactionHex);
|
$reply = $client->submitTransaction($signedTransactionHex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The first transaction builder covers the transfer used by faucets and ordinary
|
Submit a `SignedTransaction` produced by one of the included builders:
|
||||||
currency, token, NFT, and RWA transfers:
|
|
||||||
|
```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
|
```php
|
||||||
use Contractless\Rpc\Transaction\TransferBuilder;
|
use Contractless\Rpc\Transaction\TransferBuilder;
|
||||||
|
|
||||||
$transfer = (new TransferBuilder($crypto, $credentials))->create(
|
$transfer = (new TransferBuilder($crypto, $credentials))->create(
|
||||||
sender: 'sender-address.cltc',
|
sender: $sender,
|
||||||
receiver: 'receiver-address.cltc',
|
receiver: $receiver,
|
||||||
coin: 'CLTC',
|
coin: 'CLTC',
|
||||||
value: 2_500_000_000,
|
value: 2_500_000_000,
|
||||||
fee: 2_500,
|
fee: 25_000_000,
|
||||||
|
nftSeries: 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
$reply = $client->submitSignedTransaction($transfer);
|
$reply = $client->submitSignedTransaction($transfer);
|
||||||
```
|
```
|
||||||
|
|
||||||
Transaction builders are a separate layer over the protocol client. This keeps
|
The same builder transfers base currency, tokens, NFTs, and RWAs. Set
|
||||||
transport and node access stable when new transaction types are introduced.
|
`nftSeries` to the applicable series number for an NFT/RWA transfer.
|
||||||
|
|
||||||
## Transaction Builders
|
## Token, NFT, RWA, Burn, And Vanity Builders
|
||||||
|
|
||||||
- `TransferBuilder`: currency, token, NFT, and RWA transfers
|
|
||||||
- `AssetBuilder`: token creation, token issuance, NFT/RWA creation, burns, and vanity addresses
|
|
||||||
- `AgreementBuilder`: two-party swaps and loan contracts
|
|
||||||
- `MiscellaneousBuilder`: marketing records, loan payments, and collateral claims
|
|
||||||
- `StorageBuilder`: storage keys, bool, signed and unsigned integers, strings, and deletion
|
|
||||||
- `GovernanceBuilder`: proposal keys, proposal votes, and activation votes
|
|
||||||
|
|
||||||
Swaps and loans return `DualSignedTransaction`. The first signer exports it,
|
|
||||||
the second signer imports it and calls `signNext()`, and only a complete result
|
|
||||||
can be converted with `toSignedTransaction()` and broadcast.
|
|
||||||
|
|
||||||
The 128-bit storage methods accept decimal strings. This preserves the complete
|
|
||||||
Rust `u128` and `i128` ranges instead of silently losing precision through PHP
|
|
||||||
integers or floating-point numbers.
|
|
||||||
|
|
||||||
## Wallet Material
|
|
||||||
|
|
||||||
The library can load a normal Contractless wallet file directly:
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use Contractless\Rpc\Wallet\Credentials;
|
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(
|
$credentials = Credentials::fromWalletFile(
|
||||||
'/private/path/contractless.wallet',
|
'/private/path/contractless.wallet',
|
||||||
'wallet decryption key',
|
'wallet decryption key',
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
The loader decodes all supported Contractless private-key image orientations,
|
The loader:
|
||||||
verifies the encrypted payload HMAC, decrypts the Falcon private key, proves
|
|
||||||
that the public and private keys match, and verifies the wallet's canonical
|
- Decodes every supported Contractless private-key image orientation
|
||||||
short address.
|
- 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,
|
Wallet files and decryption keys must remain outside public web directories,
|
||||||
logs, repositories, and client-visible configuration.
|
logs, repositories, and client-visible configuration.
|
||||||
|
|
||||||
Verify a wallet file and decryption key before using them in an application:
|
Verify a wallet before using it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php examples/load_wallet.php /private/path/contractless.wallet 'wallet decryption key'
|
php vendor/contractless/contractless-php-rpc/examples/load_wallet.php \
|
||||||
|
/private/path/contractless.wallet \
|
||||||
|
'wallet decryption key'
|
||||||
```
|
```
|
||||||
|
|
||||||
Successful output confirms that the image was decoded, its encrypted payload
|
Applications that already manage protected raw keys may load hexadecimal
|
||||||
was authenticated and decrypted, the Falcon keypair matches, and the wallet
|
Falcon keys directly:
|
||||||
address is valid.
|
|
||||||
|
|
||||||
Raw Falcon key bytes remain supported for applications that already manage
|
|
||||||
their own protected key storage:
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$credentials = Credentials::fromHex($publicKeyHex, $privateKeyHex);
|
$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
|
## Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -188,17 +700,8 @@ composer install
|
||||||
composer test
|
composer test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Preparing The Composer Package
|
Validate package metadata before publishing a release:
|
||||||
|
|
||||||
Validate the package before publishing:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
composer validate --strict
|
composer validate --strict
|
||||||
composer install
|
|
||||||
composer test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Commit `composer.json`, `README.md`, `src/`, and `tests/` to the
|
|
||||||
`contractless-php-rpc` repository. Create a release tag such as `v0.1.0`, then
|
|
||||||
submit the repository URL to Packagist. Future version tags become installable
|
|
||||||
Composer releases.
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue