PHP SDK / Client Library to interact with the XRP Ledger and the Xahau Network. It offers all the functionality available in the JavaScript and Java Versions emphasizing robustness and code readability for those interested in looking under the hood and getting into the nitty-gritty of XRPL development.
- Managing & creating keys and wallets
- Submitting transactions to the XRP Ledger
- Sending requests to observe the ledger
- Creating and signing transactions (e.g. Payments) to modify the ledger state
- Parsing ledger data into more convenient formats
- Xahau Network Compatibility (Hooks, UNLReport, GenesisMint, etc.) — see Xahau support for the current scope
The library ships the Xahau transaction types alongside the XRP Ledger ones, but
the two networks have drifted apart: Xahau kept its URIToken types on the
ordinals 45–49 and moved everything the XRP Ledger added afterwards further up.
MPTokenIssuanceCreate, for instance, is 54 on the XRP Ledger and 63 on Xahau.
A single set of definitions cannot be correct for both networks, and this
library resolves the overlap in favour of the XRP Ledger.
Works on Xahau:
- All classic transaction types —
Payment,AccountSet,TrustSet,OfferCreate/OfferCancel,Escrow*,Check*,PaymentChannel*,NFToken*,AMM*,Clawback,TicketCreate,SignerListSet,DepositPreauth,AccountDelete,SetRegularKey. These carry the same ordinal on both networks. - The Xahau-specific types —
SetHook,Invoke,Import,ClaimReward,GenesisMint,UNLReport,URIToken*,TicketCancel.
Does not work on Xahau yet:
- Every type the XRP Ledger added from ordinal 41 onwards:
XChain*,DID*,Oracle*,MPToken*,Credential*,PermissionedDomain*,NFTokenModify. These encode with the XRP Ledger ordinal, which means a different transaction type on Xahau — without an error. Do not submit them to Xahau. - Decoding is ambiguous for the five shared ordinals: a Xahau
URITokenMintdecodes asXChainAddClaimAttestation, and the XahauBlobfield decodes asDIDDocument. The bytes are correct, only the names are read through the XRP Ledger definitions. hooksDefinitions.jsonpredates the current Xahau release and is missingRemit,SetRemarks,CronandCronSet.
Since 2.1.0 the codec works against definitions handed in from outside, so a
package for another network can supply its own definitions.json instead of the
bundled one. Every entry point takes an optional Definitions instance and
falls back to the XRP Ledger when it is omitted:
use Hardcastle\XRPL_PHP\Core\RippleBinaryCodec\BinaryCodec;
use Hardcastle\XRPL_PHP\Core\RippleBinaryCodec\Definitions\Definitions;
use Hardcastle\XRPL_PHP\Client\JsonRpcClient;
use Hardcastle\XRPL_PHP\Wallet\Wallet;
$definitions = Definitions::fromFile('/path/to/xahau-definitions.json');
// or Definitions::fromArray($decodedJson);
$codec = new BinaryCodec($definitions);
$wallet = Wallet::fromSeed($seed, $definitions);
$client = new JsonRpcClient('https://xahau.network', null, null, 3.0, $definitions);A node serves its own definitions, so they can be fetched rather than vendored:
curl -X POST https://xahau.network -H 'Content-Type: application/json' \
-d '{"method":"server_definitions","params":[{}]}'The definitions travel through the whole encode and decode, including nested objects and arrays. They do not touch the shared default instance, so a process can talk to both networks at once.
A dedicated Xahau package building on this is planned; the Xahau types will then move out of this library.
This library is installable via Composer:
composer require hardcastle/xrpl_php
This library requires PHP 8.2 or later and two PHP extensions:
- bcmath — used directly for the ledger's fixed point arithmetic.
- gmp — required by
simplito/elliptic-php, which does the secp256k1 signing. Composer will refuse to install without it.
These examples reproduce the functionality from the JavaScript quickstart examples:
php 1.get-accounts-send-xrp.php
php 2.create-trustline-send-currency.php
php 3.mint-nfts.phpThese examples show how to use key features:
php examples/client.php
php examples/faucet-wallet.php
php examples/payment.php
php examples/token-create.php // IOU + Token + CBDC - Wallet Matrix with Trustlines
php examples/mptoken.php // Multi-Purpose Token: issue, authorize, send, claw back
php examples/permissioned-domain.php // Credentials + PermissionedDomain + PermissionedDEX
php examples/amm-clawback.php // Claw a token back out of an AMM pool
php examples/nftoken-modify.php // Mint a mutable NFT and change its URI
etc...All of these run against the Testnet and fund their own wallets from the faucet.
These examples can be used to explore XRPL core functionality:
php examples/internal/address-codec.php
php examples/internal/binary-codec.php
etc...- Tell the container which user to run as, so the files it writes belong to
you. The values differ between Linux and macOS, so they come from
.env:
printf 'DOCKER_UID=%s\nDOCKER_GID=%s\n' "$(id -u)" "$(id -g)" > .env- Start the project and open a shell:
docker compose up -d
docker compose exec php bash- In the container shell, install the composer dependencies:
composer installThe image is built from docker/. Xdebug is preconfigured to reach the host on
port 9090 via host.docker.internal, which works on Linux as well because the
compose file maps it to the host gateway. For anything else that is specific to
your machine, add a docker-compose.override.yml; it is gitignored.
You can run the tests with the following command:
./vendor/bin/phpunit testsYou can perform static code analysis with psalm with the following command:
./vendor/bin/psalm --config=psalm.xmlIssuing an Account Info request:
require __DIR__.'/../vendor/autoload.php';
use Hardcastle\XRPL_PHP\Client\JsonRpcClient;
use Hardcastle\XRPL_PHP\Models\Account\AccountObjectsRequest;
// Those will be purged from the Testnet in regular intervals, you can use fundWallet()
// to generate prefunded Wallets on the Testnet
$testnetAccountAddress = 'raKXrkYfbh4Uzqc481jTXbaKsWnW5XRMjp';
$client = new JsonRpcClient("https://s.altnet.rippletest.net:51234");
$request = new AccountObjectsRequest(
account: $testnetAccountAddress,
ledgerIndex: 'validated',
deletionBlockersOnly: true
);
// Using synchronous request
$response = $client->syncRequest($request);
$json = json_decode($response->getBody());
print_r($json);
// Using asynchronous request
// $response = $client->request($request)->wait();
// $json = json_decode($response->getBody());
// print_r($json);// Use your own credentials here:
$testnetStandbyAccountSeed = 'sEdTcvQ9k4UUEHD9y947QiXEs93Fp2k';
$testnetStandbyAccountAddress = 'raJNboPDvjLrYZropPFrxvz2Qm7A9guEVd';
$standbyWallet = Wallet::fromSeed($testnetStandbyAccountSeed);
// Use your own credentials here:
$testnetOperationalAccountSeed = 'sEdVHf8rNEaRveJw4NdVKxm3iYWFuRb';
$testnetOperationalAccountAddress = 'rEQ3ik2kmAvajqpFweKgDghJFZQGpXxuRN';
$operationalWallet = Wallet::fromSeed($testnetStandbyAccountSeed);
$client = new JsonRpcClient("https://s.altnet.rippletest.net:51234");
$tx = [
"TransactionType" => "Payment",
"Account" => $testnetStandbyAccountAddress,
"Amount" => xrpToDrops("100"),
"Destination" => $testnetOperationalAccountAddress
];
$autofilledTx = $client->autofill($tx);
$signedTx = $standbyWallet->sign($autofilledTx);
$txResponse = $client->submitAndWait($signedTx['tx_blob']);
$result = $txResponse->getResult();
if ($result['meta']['TransactionResult'] === 'tecUNFUNDED_PAYMENT') {
print_r("Error: The sending account is unfunded! TxHash: {$result['hash']}" . PHP_EOL);
} else {
print_r("Token payment done! TxHash: {$result['hash']}" . PHP_EOL);
}