Compare commits
No commits in common. "main" and "v0.2.0" have entirely different histories.
711
README.md
711
README.md
|
|
@ -1,26 +1,18 @@
|
||||||
# PHP Contractless RPC
|
# PHP Contractless RPC
|
||||||
|
|
||||||
`contractless-php-rpc` allows PHP applications to connect directly to a
|
`contractless-php-rpc` connects PHP applications directly to a Contractless
|
||||||
Contractless node through its authenticated binary RPC protocol. It does not
|
node. It implements the authenticated binary RPC protocol without invoking
|
||||||
invoke Contractless CLI programs and does not depend on centralized blockchain
|
Contractless CLI programs.
|
||||||
APIs.
|
|
||||||
|
|
||||||
The package can be used to build faucets, block explorers, wallets, application
|
The package is intended for faucets, block explorers, wallets, application
|
||||||
backends, storage applications, and other public Contractless integrations.
|
backends, and other public Contractless integrations.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
The library exposes application-safe public RPC commands, including:
|
The client includes public blockchain lookups, wallet and address lookups,
|
||||||
|
mempool lookups, asset lookups, loan lookups, storage lookups, governance
|
||||||
- Network and blockchain information
|
lookups, wallet registration, signed transaction submission, and an RPC
|
||||||
- Blocks, headers, and torrents
|
interface restricted to application-safe commands.
|
||||||
- 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
|
||||||
|
|
@ -28,32 +20,50 @@ exposed.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- PHP 8.1 or newer using a 64-bit build
|
- PHP 8.1 or newer, running as a 64-bit build
|
||||||
- PHP GD
|
- The PHP GD extension
|
||||||
- PHP OpenSSL
|
- The PHP OpenSSL extension
|
||||||
- The `skein` module from Contractless PHP Modules
|
- The `skein` module from `contractless-php-crypto`
|
||||||
- The `oqsphp` module from Contractless PHP Modules
|
- The `oqsphp` module from `contractless-php-crypto`
|
||||||
|
|
||||||
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 version `0.2` through Packagist:
|
Install and enable `contractless-php-crypto` first. Its repository contains the
|
||||||
|
native module build, installation, and compatibility-test instructions.
|
||||||
|
|
||||||
|
When this package is available through Packagist:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
composer require contractless/contractless-php-rpc:^0.2.0 -W
|
composer require contractless/contractless-php-rpc
|
||||||
```
|
```
|
||||||
|
|
||||||
Composer installs the package under `vendor/` and generates the required
|
Until the package is published, add it as a Composer path repository:
|
||||||
autoload files.
|
|
||||||
|
|
||||||
## Create A Client
|
```json
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
|
@ -65,634 +75,112 @@ 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::fromWalletFile(
|
$credentials = Credentials::fromHex(
|
||||||
'/private/path/contractless.wallet',
|
getenv('CONTRACTLESS_PUBLIC_KEY'),
|
||||||
'wallet decryption key',
|
getenv('CONTRACTLESS_PRIVATE_KEY'),
|
||||||
);
|
);
|
||||||
|
|
||||||
$crypto = new NativeCrypto();
|
$crypto = new NativeCrypto();
|
||||||
|
|
||||||
$client = new Client(
|
$client = new Client(
|
||||||
new StreamTransport(
|
new StreamTransport('127.0.0.1', 50050),
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 50050,
|
|
||||||
timeout: 10.0,
|
|
||||||
),
|
|
||||||
$crypto,
|
$crypto,
|
||||||
$credentials,
|
$credentials,
|
||||||
);
|
);
|
||||||
```
|
|
||||||
|
|
||||||
Use port `50050` for the default testnet RPC and `50055` for the default
|
echo $client->totalBalance(
|
||||||
mainnet RPC unless the node operator configured another port.
|
'ab13318c26250b048db92920a80a86127c933b0c.cltc',
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not apply text conversion to raw blocks, headers, torrents, transactions, or
|
RPC replies are returned as their original bytes. Commands that return JSON can
|
||||||
binary integer replies.
|
be decoded with `json_decode($reply, true, flags: JSON_THROW_ON_ERROR)`. Binary
|
||||||
|
block, torrent, header, and transaction replies remain available without a
|
||||||
|
lossy conversion.
|
||||||
|
|
||||||
A coin balance is returned as an eight-byte little-endian unsigned integer:
|
## Transaction Submission
|
||||||
|
|
||||||
```php
|
`submitTransaction()` accepts the complete serialized signed transaction as a
|
||||||
$reply = $client->coinBalance('CLTC', $address);
|
hexadecimal string:
|
||||||
$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);
|
||||||
```
|
```
|
||||||
|
|
||||||
Submit a `SignedTransaction` produced by one of the included builders:
|
The first transaction builder covers the transfer used by faucets and ordinary
|
||||||
|
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,
|
sender: 'sender-address.cltc',
|
||||||
receiver: $receiver,
|
receiver: 'receiver-address.cltc',
|
||||||
coin: 'CLTC',
|
coin: 'CLTC',
|
||||||
value: 2_500_000_000,
|
value: 2_500_000_000,
|
||||||
fee: 25_000_000,
|
fee: 2_500,
|
||||||
nftSeries: 0,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$reply = $client->submitSignedTransaction($transfer);
|
$reply = $client->submitSignedTransaction($transfer);
|
||||||
```
|
```
|
||||||
|
|
||||||
The same builder transfers base currency, tokens, NFTs, and RWAs. Set
|
Transaction builders are a separate layer over the protocol client. This keeps
|
||||||
`nftSeries` to the applicable series number for an NFT/RWA transfer.
|
transport and node access stable when new transaction types are introduced.
|
||||||
|
|
||||||
## Token, NFT, RWA, Burn, And Vanity Builders
|
## Transaction 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\Transaction\AssetBuilder;
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
$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:
|
The loader decodes all supported Contractless private-key image orientations,
|
||||||
|
verifies the encrypted payload HMAC, decrypts the Falcon private key, proves
|
||||||
- Decodes every supported Contractless private-key image orientation
|
that the public and private keys match, and verifies the wallet's canonical
|
||||||
- Verifies the encrypted payload HMAC
|
short address.
|
||||||
- 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 before using it:
|
Verify a wallet file and decryption key before using them in an application:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php vendor/contractless/contractless-php-rpc/examples/load_wallet.php \
|
php examples/load_wallet.php /private/path/contractless.wallet 'wallet decryption key'
|
||||||
/private/path/contractless.wallet \
|
|
||||||
'wallet decryption key'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Applications that already manage protected raw keys may load hexadecimal
|
Successful output confirms that the image was decoded, its encrypted payload
|
||||||
Falcon keys directly:
|
was authenticated and decrypted, the Falcon keypair matches, and the wallet
|
||||||
|
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
|
||||||
|
|
@ -700,8 +188,17 @@ composer install
|
||||||
composer test
|
composer test
|
||||||
```
|
```
|
||||||
|
|
||||||
Validate package metadata before publishing a release:
|
## Preparing The Composer Package
|
||||||
|
|
||||||
|
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