diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 02670ae..ece30f0 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -11,7 +11,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' tools: composer, phpunit extensions: ds, gmp, sockets coverage: none @@ -19,7 +19,7 @@ jobs: - name: Install composer dependencies uses: php-actions/composer@v6 with: - php_version: "8.1" + php_version: "8.2" php_extensions: gmp sockets bcmath version: latest diff --git a/composer.json b/composer.json index 740081d..1a4aace 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "config": { "optimize-autoloader": true, "platform": { - "php": "8.1" + "php": "8.2.27" } }, "autoload": { @@ -39,6 +39,8 @@ "src/Sugar/balances.php", "src/Sugar/fundWallet.php", "src/Sugar/getFeeXrp.php", + "src/Sugar/getOrderbook.php", + "src/Sugar/getTransactions.php", "src/Sugar/submit.php", "src/Sugar/xrpConversion.php" ] @@ -60,9 +62,10 @@ "vlucas/phpdotenv": "^5.6" }, "require-dev": { - "phpunit/phpunit": "^10.3.1", - "vimeo/psalm": "^5.14", - "donatj/mock-webserver": "^2.6.2" + "phpunit/phpunit": "^11.5", + "vimeo/psalm": "^6.8", + "donatj/mock-webserver": "^2.10.0", + "rector/rector": "^2.3" }, "scripts": { "psalm": "vendor/bin/psalm --config=psalm.xml" diff --git a/containers/php/Dockerfile b/containers/php/Dockerfile index 35917af..5b3ca3c 100644 --- a/containers/php/Dockerfile +++ b/containers/php/Dockerfile @@ -1,12 +1,12 @@ -FROM php:8.1.4-fpm-alpine +FROM php:8.2.27-fpm-alpine RUN apk update \ && apk upgrade \ && apk add gmp-dev \ - && apk add --no-cache bash $PHPIZE_DEPS \ + && apk add --no-cache bash $PHPIZE_DEPS linux-headers \ && docker-php-ext-install gmp \ && docker-php-ext-install sockets \ - && pecl install xdebug-3.1.3 \ + && pecl install xdebug \ && pecl install ds COPY --from=composer /usr/bin/composer /usr/bin/composer diff --git a/src/Client/JsonRpcClient.php b/src/Client/JsonRpcClient.php index e5cc9d6..a917496 100644 --- a/src/Client/JsonRpcClient.php +++ b/src/Client/JsonRpcClient.php @@ -31,6 +31,10 @@ use function Hardcastle\XRPL_PHP\Sugar\autofill; use function Hardcastle\XRPL_PHP\Sugar\fundWallet; use function Hardcastle\XRPL_PHP\Sugar\getXrpBalance; +use function Hardcastle\XRPL_PHP\Sugar\getBalances; +use function Hardcastle\XRPL_PHP\Sugar\getFeeXrp; +use function Hardcastle\XRPL_PHP\Sugar\getOrderbook; +use function Hardcastle\XRPL_PHP\Sugar\getTransactions; use function Hardcastle\XRPL_PHP\Sugar\submit; use function Hardcastle\XRPL_PHP\Sugar\submitAndWait; @@ -44,21 +48,19 @@ class JsonRpcClient private const NORMAL_DISCONNECT_CODE = 1000; - private Client $restClient; + private readonly Client $restClient; - private string $connectionUrl; + private readonly string $connectionUrl; - private float $feeCushion; + private readonly float $feeCushion; - private string $maxFeeXrp; - - private float $timeout; + private readonly string $maxFeeXrp; public function __construct( string $connectionUrl, ?float $feeCushion = null, ?string $maxFeeXrp = null, - ?float $timeout = 3.0 + private readonly float $timeout = 3.0 ) { $this->connectionUrl = $this->getNetworkUrl($connectionUrl); @@ -66,8 +68,6 @@ public function __construct( $this->maxFeeXrp = $maxFeeXrp ?? self::DEFAULT_MAX_FEE_XRP; - $this->timeout = $timeout; - $stack = HandlerStack::create(new CurlHandler()); $this->restClient = new Client( @@ -208,7 +208,7 @@ private function handleResponse(BaseRequest $request, ?ResponseInterface $respon ); } - $requestClassName = get_class($request); + $requestClassName = $request::class; /** @psalm-var class-string $responseClassName */ $responseClassName = str_replace('Request', 'Response', $requestClassName); /** @var BaseResponse $responseClass */ @@ -303,6 +303,87 @@ public function getXrpBalance(string $address): string return getXrpBalance($this, $address); } + /** + * @param string $address + * @param string|null $ledgerHash + * @param string|null $ledgerIndex + * @param string|null $peer + * @param int|null $limit + * @return array + * @throws Exception + */ + public function getBalances( + string $address, + ?string $ledgerHash = null, + ?string $ledgerIndex = 'validated', + ?string $peer = null, + ?int $limit = null + ): array + { + return getBalances($this, $address, $ledgerHash, $ledgerIndex, $peer, $limit); + } + + /** + * @param string $address + * @param int|null $ledgerIndexMin + * @param int|null $ledgerIndexMax + * @param string|null $ledgerHash + * @param string|null $ledgerIndex + * @param bool|null $binary + * @param bool|null $forward + * @param int|null $limit + * @param mixed|null $marker + * @return array + * @throws Exception + */ + public function getTransactions( + string $address, + ?int $ledgerIndexMin = null, + ?int $ledgerIndexMax = null, + ?string $ledgerHash = null, + ?string $ledgerIndex = 'validated', + ?bool $binary = null, + ?bool $forward = null, + ?int $limit = null, + mixed $marker = null + ): array + { + return getTransactions($this, $address, $ledgerIndexMin, $ledgerIndexMax, $ledgerHash, $ledgerIndex, $binary, $forward, $limit, $marker); + } + + /** + * @param array $takerGets + * @param array $takerPays + * @param string|null $ledgerHash + * @param string|null $ledgerIndex + * @param int|null $limit + * @param string|null $taker + * @return array + * @throws Exception + */ + public function getOrderbook( + array $takerGets, + array $takerPays, + ?string $ledgerHash = null, + ?string $ledgerIndex = 'validated', + ?int $limit = null, + ?string $taker = null + ): array + { + return getOrderbook($this, $takerGets, $takerPays, $ledgerHash, $ledgerIndex, $limit, $taker); + } + + /** + * @param int|null $cushion + * @return string + * @throws \Brick\Math\Exception\MathException + * @throws \Brick\Math\Exception\RoundingNecessaryException + */ + public function getFeeXrp(?int $cushion = null): string + { + return getFeeXrp($this, $cushion); + } + /** * * @@ -380,7 +461,7 @@ private function getNetworkUrl(string $connection): string try { $network = Networks::getNetwork($connection); return $network['jsonRpcUrl']; - } catch (Exception $e) { + } catch (Exception) { return $connection; } } diff --git a/src/Core/CoreUtilities.php b/src/Core/CoreUtilities.php index c6f0926..7ce087a 100644 --- a/src/Core/CoreUtilities.php +++ b/src/Core/CoreUtilities.php @@ -18,7 +18,7 @@ class CoreUtilities { private static ?CoreUtilities $instance = null; - private AddressCodec $addressCodec; + private readonly AddressCodec $addressCodec; public static function getInstance(): CoreUtilities { @@ -33,7 +33,7 @@ public static function ensureClassicAddress(string $account): string { $_this = self::getInstance(); if ($_this->addressCodec->isValidXAddress($account)) { - list($classicAddress, $tag) = $_this->addressCodec->xAddressToClassicAddress($account); + [$classicAddress, $tag] = $_this->addressCodec->xAddressToClassicAddress($account); /* * Except for special cases, X-addresses used for requests diff --git a/src/Core/Ctid.php b/src/Core/Ctid.php index 37ad2d7..4270839 100644 --- a/src/Core/Ctid.php +++ b/src/Core/Ctid.php @@ -20,7 +20,7 @@ class Ctid { private const FILLER = 0xc0000000; - private Buffer $internal; + private readonly Buffer $internal; /** * // https://github.com/XRPLF/XRPL-Standards/discussions/91 diff --git a/src/Core/RippleAddressCodec/AddressCodec.php b/src/Core/RippleAddressCodec/AddressCodec.php index 293be26..1ada61a 100644 --- a/src/Core/RippleAddressCodec/AddressCodec.php +++ b/src/Core/RippleAddressCodec/AddressCodec.php @@ -56,16 +56,14 @@ public function encodeXAddress(Buffer $accountId, $tag, bool $test = false): str 0, 0, 0, 0 ]); - $hex = array_map(function ($item) { - return sprintf('%02X', $item); - }, $bytes); + $hex = array_map(fn($item) => sprintf('%02X', $item), $bytes); - return $this->encodeChecked(Buffer::from(join($hex))); + return $this->encodeChecked(Buffer::from(join('', $hex))); } public function xAddressToClassicAddress(string $xAddress): array { - list($accountId, $tag, $test) = array_values($this->decodeXAddress($xAddress)); + [$accountId, $tag, $test] = array_values($this->decodeXAddress($xAddress)); $classicAddress = $this->encodeAccountID($accountId); return [ 'classicAddress' => $classicAddress, @@ -91,7 +89,7 @@ public function isValidXAddress(string $xAddress): bool { try { $this->decodeXAddress($xAddress); - } catch (\Throwable $e) { + } catch (\Throwable) { return false; } return true; diff --git a/src/Core/RippleAddressCodec/BaseX.php b/src/Core/RippleAddressCodec/BaseX.php index d32bb99..0ea2ae1 100644 --- a/src/Core/RippleAddressCodec/BaseX.php +++ b/src/Core/RippleAddressCodec/BaseX.php @@ -21,13 +21,13 @@ class BaseX private SplFixedArray $baseMap; - private int $base; + private readonly int $base; - private string $leader; + private readonly string $leader; - private float $factor; + private readonly float $factor; - private float $inverseFactor; + private readonly float $inverseFactor; public function __construct(string $alphabet) { @@ -170,9 +170,7 @@ public function decodeUnsafe(string $source): ?Buffer $vch[$j++] = $b256[$it4++]; } - $hexStr = join(array_map(function ($item) { - return sprintf('%02X', $item); - }, $vch)); + $hexStr = join('', array_map(fn($item) => sprintf('%02X', $item), $vch)); //decimalArrayToHexStr return Buffer::from($hexStr); diff --git a/src/Core/RippleAddressCodec/Codec.php b/src/Core/RippleAddressCodec/Codec.php index 16be13a..e0af11e 100644 --- a/src/Core/RippleAddressCodec/Codec.php +++ b/src/Core/RippleAddressCodec/Codec.php @@ -15,14 +15,11 @@ class Codec { - private string $alphabet; + private readonly BaseX $baseCodec; - private BaseX $baseCodec; - - public function __construct(string $alphabet) + public function __construct(private readonly string $alphabet) { - $this->alphabet = $alphabet; - $this->baseCodec = new BaseX($alphabet); + $this->baseCodec = new BaseX($this->alphabet); } public function encode(Buffer $bytes, array $options): string diff --git a/src/Core/RippleAddressCodec/CodecWithXrpAlphabet.php b/src/Core/RippleAddressCodec/CodecWithXrpAlphabet.php index cba7ddf..4f7b598 100644 --- a/src/Core/RippleAddressCodec/CodecWithXrpAlphabet.php +++ b/src/Core/RippleAddressCodec/CodecWithXrpAlphabet.php @@ -159,7 +159,7 @@ public function isValidClassicAddress(string $address): bool { try { $this->decodeAccountId($address); - } catch (\Throwable $e) { + } catch (\Throwable) { return false; } return true; diff --git a/src/Core/RippleBinaryCodec/BinaryCodec.php b/src/Core/RippleBinaryCodec/BinaryCodec.php index 3fc1295..6fde074 100644 --- a/src/Core/RippleBinaryCodec/BinaryCodec.php +++ b/src/Core/RippleBinaryCodec/BinaryCodec.php @@ -40,9 +40,14 @@ public function encode(string|array $jsonObject): string * * @param string $binaryString * @return array + * @throws Exception */ public function decode(string $binaryString): array { + if (str_starts_with(trim(bin2hex(hex2bin($binaryString))), "7b")) { + throw new Exception("The provided binary string appears to be a hex-encoded JSON object, not a binary-encoded transaction."); + } + return $this->binaryToJson($binaryString); } diff --git a/src/Core/RippleBinaryCodec/Definitions/Definitions.php b/src/Core/RippleBinaryCodec/Definitions/Definitions.php index 932a7c6..2c7c081 100644 --- a/src/Core/RippleBinaryCodec/Definitions/Definitions.php +++ b/src/Core/RippleBinaryCodec/Definitions/Definitions.php @@ -31,7 +31,7 @@ class Definitions */ public function __construct() { - $path = getenv('XRPL_PHP_DEFINITIONS_FILE_PATH') ?: dirname(__FILE__) . "/definitions.json"; + $path = getenv('XRPL_PHP_DEFINITIONS_FILE_PATH') ?: __DIR__ . "/definitions.json"; if (file_exists($path)) { $fileContents = file_get_contents($path); } else { @@ -40,6 +40,16 @@ public function __construct() $this->definitions = json_decode($fileContents, true); + $hooksPath = __DIR__ . "/../../../Hooks/hooksDefinitions.json"; + if (file_exists($hooksPath)) { + $hooksDefinitions = json_decode(file_get_contents($hooksPath), true); + $this->definitions['TYPES'] = array_merge($this->definitions['TYPES'], $hooksDefinitions['TYPES']); + $this->definitions['LEDGER_ENTRY_TYPES'] = array_merge($this->definitions['LEDGER_ENTRY_TYPES'], $hooksDefinitions['LEDGER_ENTRY_TYPES']); + $this->definitions['TRANSACTION_RESULTS'] = array_merge($this->definitions['TRANSACTION_RESULTS'], $hooksDefinitions['TRANSACTION_RESULTS'] ?? []); + $this->definitions['TRANSACTION_TYPES'] = array_merge($this->definitions['TRANSACTION_TYPES'], $hooksDefinitions['TRANSACTION_TYPES'] ?? []); + $this->definitions['FIELDS'] = array_merge($this->definitions['FIELDS'], $hooksDefinitions['FIELDS']); + } + $this->typeOrdinals = $this->definitions['TYPES']; $this->ledgerEntryTypes = $this->definitions['LEDGER_ENTRY_TYPES']; $this->transactionResults = $this->definitions['TRANSACTION_RESULTS']; @@ -57,7 +67,7 @@ public function __construct() $fieldHeader = new FieldHeader($this->typeOrdinals[$fieldInfo->getType()], $fieldInfo->getNth()); $this->fieldInfoMap[$fieldName] = $fieldInfo; - $this->fieldIdNameMap[md5(serialize($fieldHeader))] = $fieldName; + $this->fieldIdNameMap[$fieldHeader->getTypeCode() . ":" . $fieldHeader->getFieldCode()] = $fieldName; $this->fieldHeaderMap[$fieldName] = $fieldHeader; } } @@ -73,12 +83,22 @@ public static function getInstance(): Definitions public function getFieldHeaderFromName(string $fieldName): FieldHeader { + if (!isset($this->fieldHeaderMap[$fieldName])) { + throw new Exception("Field $fieldName not found in definitions."); + } + return $this->fieldHeaderMap[$fieldName]; } public function getFieldNameFromHeader(FieldHeader $fieldHeader): string { - return $this->fieldIdNameMap[md5(serialize($fieldHeader))]; + $key = $fieldHeader->getTypeCode() . ":" . $fieldHeader->getFieldCode(); + + if (!isset($this->fieldIdNameMap[$key])) { + throw new Exception("Field with header $key not found in definitions."); + } + + return $this->fieldIdNameMap[$key]; } public function getFieldInstance(string $fieldName): FieldInstance @@ -106,7 +126,7 @@ public function mapSpecificFieldFromValue(string $fieldName, string $value): int } //TODO: In case the value is not found, should an exception be thrown? - return (isset($lookup[$value])) ? $lookup[$value] : $value; + return $lookup[$value] ?? $value; } public function mapValueToSpecificField(string $fieldName, string|int $value): string @@ -125,6 +145,6 @@ public function mapValueToSpecificField(string $fieldName, string|int $value): s return ""; } - return (isset($lookup[(int)$value])) ? $lookup[(int)$value] : ""; + return $lookup[(int)$value] ?? ""; } } \ No newline at end of file diff --git a/src/Core/RippleBinaryCodec/Definitions/FieldHeader.php b/src/Core/RippleBinaryCodec/Definitions/FieldHeader.php index 0408433..f1fcc4d 100644 --- a/src/Core/RippleBinaryCodec/Definitions/FieldHeader.php +++ b/src/Core/RippleBinaryCodec/Definitions/FieldHeader.php @@ -7,14 +7,8 @@ class FieldHeader implements Hashable { - private int $typeCode; - - private int $fieldCode; - - public function __construct(int $typeCode, int $fieldCode) + public function __construct(private int $typeCode, private int $fieldCode) { - $this->typeCode = $typeCode; - $this->fieldCode = $fieldCode; } public function toBytes(): Buffer diff --git a/src/Core/RippleBinaryCodec/Definitions/FieldInfo.php b/src/Core/RippleBinaryCodec/Definitions/FieldInfo.php index 4df6931..78b2e54 100644 --- a/src/Core/RippleBinaryCodec/Definitions/FieldInfo.php +++ b/src/Core/RippleBinaryCodec/Definitions/FieldInfo.php @@ -7,29 +7,8 @@ */ class FieldInfo { - private int $nth; - - private bool $isVariableLengthEncoded; - - private bool $isSerialized; - - private bool $isSigningField; - - private string $type; - - public function __construct( - int $nth, - bool $isVariableLengthEncoded, - bool $isSerialized, - bool $isSigningField, - string $typeName - ) + public function __construct(private int $nth, private bool $isVariableLengthEncoded, private bool $isSerialized, private bool $isSigningField, private string $type) { - $this->nth = $nth; - $this->isVariableLengthEncoded = $isVariableLengthEncoded; - $this->isSerialized = $isSerialized; - $this->isSigningField = $isSigningField; - $this->type = $typeName; } /** diff --git a/src/Core/RippleBinaryCodec/Definitions/FieldInstance.php b/src/Core/RippleBinaryCodec/Definitions/FieldInstance.php index 15b6f33..4db00cd 100644 --- a/src/Core/RippleBinaryCodec/Definitions/FieldInstance.php +++ b/src/Core/RippleBinaryCodec/Definitions/FieldInstance.php @@ -21,29 +21,23 @@ class FieldInstance private readonly int $ordinal; - private readonly string $name; - - private FieldHeader $fieldHeader; - private readonly string $associatedType; /** * * * @param FieldInfo $fieldInfo - * @param string $fieldName + * @param string $name * @param FieldHeader $fieldHeader * @throws \Exception */ - public function __construct(FieldInfo $fieldInfo, string $fieldName, FieldHeader $fieldHeader) + public function __construct(FieldInfo $fieldInfo, private readonly string $name, private readonly FieldHeader $fieldHeader) { $this->nth = $fieldInfo->getNth(); $this->isVariableLengthEncoded = $fieldInfo->isVariableLengthEncoded(); $this->isSerialized = $fieldInfo->isSerialized(); $this->isSigningField = $fieldInfo->isSigningField(); $this->type = $fieldInfo->getType(); - $this->name = $fieldName; - $this->fieldHeader = $fieldHeader; $this->ordinal = $this->fieldHeader->getTypeCode() << 16 | $this->nth; $this->associatedType = SerializedType::getTypeByName($this->type)::class; } diff --git a/src/Core/RippleBinaryCodec/Definitions/definitions.json b/src/Core/RippleBinaryCodec/Definitions/definitions.json index 6907134..d8d3456 100644 --- a/src/Core/RippleBinaryCodec/Definitions/definitions.json +++ b/src/Core/RippleBinaryCodec/Definitions/definitions.json @@ -180,6 +180,16 @@ "type": "UInt16" } ], + [ + "ManagementFeeRate", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 22, + "type": "UInt16" + } + ], [ "NetworkID", { @@ -680,6 +690,166 @@ "type": "UInt32" } ], + [ + "MutableFlags", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 53, + "type": "UInt32" + } + ], + [ + "StartDate", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 54, + "type": "UInt32" + } + ], + [ + "PaymentInterval", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 55, + "type": "UInt32" + } + ], + [ + "GracePeriod", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 56, + "type": "UInt32" + } + ], + [ + "PreviousPaymentDueDate", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 57, + "type": "UInt32" + } + ], + [ + "NextPaymentDueDate", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 58, + "type": "UInt32" + } + ], + [ + "PaymentRemaining", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 59, + "type": "UInt32" + } + ], + [ + "PaymentTotal", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 60, + "type": "UInt32" + } + ], + [ + "LoanSequence", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 61, + "type": "UInt32" + } + ], + [ + "CoverRateMinimum", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 62, + "type": "UInt32" + } + ], + [ + "CoverRateLiquidation", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 63, + "type": "UInt32" + } + ], + [ + "OverpaymentFee", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 64, + "type": "UInt32" + } + ], + [ + "InterestRate", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 65, + "type": "UInt32" + } + ], + [ + "LateInterestRate", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 66, + "type": "UInt32" + } + ], + [ + "CloseInterestRate", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 67, + "type": "UInt32" + } + ], + [ + "OverpaymentInterestRate", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 68, + "type": "UInt32" + } + ], [ "IndexNext", { @@ -950,6 +1120,26 @@ "type": "UInt64" } ], + [ + "VaultNode", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 30, + "type": "UInt64" + } + ], + [ + "LoanBrokerNode", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 31, + "type": "UInt64" + } + ], [ "EmailHash", { @@ -1300,6 +1490,26 @@ "type": "Hash256" } ], + [ + "LoanBrokerID", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 37, + "type": "Hash256" + } + ], + [ + "LoanID", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 38, + "type": "Hash256" + } + ], [ "hash", { @@ -2080,6 +2290,26 @@ "type": "AccountID" } ], + [ + "Borrower", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 25, + "type": "AccountID" + } + ], + [ + "Counterparty", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 26, + "type": "AccountID" + } + ], [ "Number", { @@ -2130,6 +2360,136 @@ "type": "Number" } ], + [ + "DebtTotal", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 6, + "type": "Number" + } + ], + [ + "DebtMaximum", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 7, + "type": "Number" + } + ], + [ + "CoverAvailable", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 8, + "type": "Number" + } + ], + [ + "LoanOriginationFee", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 9, + "type": "Number" + } + ], + [ + "LoanServiceFee", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 10, + "type": "Number" + } + ], + [ + "LatePaymentFee", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 11, + "type": "Number" + } + ], + [ + "ClosePaymentFee", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 12, + "type": "Number" + } + ], + [ + "PrincipalOutstanding", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 13, + "type": "Number" + } + ], + [ + "PrincipalRequested", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 14, + "type": "Number" + } + ], + [ + "TotalValueOutstanding", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 15, + "type": "Number" + } + ], + [ + "PeriodicPayment", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 16, + "type": "Number" + } + ], + [ + "ManagementFeeOutstanding", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 17, + "type": "Number" + } + ], + [ + "LoanScale", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "Int32" + } + ], [ "TransactionMetaData", { @@ -2470,6 +2830,16 @@ "type": "STObject" } ], + [ + "CounterpartySignature", + { + "isSerialized": true, + "isSigningField": false, + "isVLEncoded": false, + "nth": 37, + "type": "STObject" + } + ], [ "Signers", { @@ -3076,6 +3446,8 @@ "FeeSettings": 115, "Invalid": -1, "LedgerHashes": 104, + "Loan": 137, + "LoanBroker": 136, "MPToken": 127, "MPTokenIssuance": 126, "NFTokenOffer": 55, @@ -3273,6 +3645,7 @@ "terNO_ACCOUNT": -96, "terNO_AMM": -87, "terNO_AUTH": -95, + "terNO_DELEGATE_PERMISSION": -85, "terNO_LINE": -94, "terNO_RIPPLE": -90, "terOWNERS": -93, @@ -3310,6 +3683,15 @@ "EscrowFinish": 2, "Invalid": -1, "LedgerStateFix": 53, + "LoanBrokerCoverClawback": 78, + "LoanBrokerCoverDeposit": 76, + "LoanBrokerCoverWithdraw": 77, + "LoanBrokerDelete": 75, + "LoanBrokerSet": 74, + "LoanDelete": 81, + "LoanManage": 82, + "LoanPay": 84, + "LoanSet": 80, "MPTokenAuthorize": 57, "MPTokenIssuanceCreate": 54, "MPTokenIssuanceDestroy": 55, @@ -3361,6 +3743,8 @@ "Hash160": 17, "Hash192": 21, "Hash256": 5, + "Int32": 10, + "Int64": 11, "Issue": 24, "LedgerEntry": 10002, "Metadata": 10004, diff --git a/src/Core/RippleBinaryCodec/Serdes/BinaryParser.php b/src/Core/RippleBinaryCodec/Serdes/BinaryParser.php index 291d744..55b55ca 100644 --- a/src/Core/RippleBinaryCodec/Serdes/BinaryParser.php +++ b/src/Core/RippleBinaryCodec/Serdes/BinaryParser.php @@ -96,7 +96,7 @@ public function read(int $number): Buffer */ public function readUIntN(int $number): Buffer //BigInteger { - if ($number > 0 && $number <= 8) { + if ($number > 0) { $stdArray = $this->read($number)->toArray(); return Buffer::from($stdArray); } diff --git a/src/Core/RippleBinaryCodec/Serdes/BinarySerializer.php b/src/Core/RippleBinaryCodec/Serdes/BinarySerializer.php index 57aa367..40ec496 100644 --- a/src/Core/RippleBinaryCodec/Serdes/BinarySerializer.php +++ b/src/Core/RippleBinaryCodec/Serdes/BinarySerializer.php @@ -17,11 +17,8 @@ class BinarySerializer { - private Buffer $bytes; - - public function __construct(Buffer $bytes) + public function __construct(private readonly Buffer $bytes) { - $this->bytes = $bytes; } public function put(string $hexBytes): void diff --git a/src/Core/RippleBinaryCodec/Types/Amount.php b/src/Core/RippleBinaryCodec/Types/Amount.php index b1c2c93..6b1c5d9 100644 --- a/src/Core/RippleBinaryCodec/Types/Amount.php +++ b/src/Core/RippleBinaryCodec/Types/Amount.php @@ -192,7 +192,7 @@ public static function isAmountValid(mixed $amount): bool self::assertIouIsValid($amount); // If no exception is thrown, it's a valid IOU/token amount return true; - } catch (Exception $exception) { + } catch (Exception) { // Do nothing } diff --git a/src/Core/RippleBinaryCodec/Types/Currency.php b/src/Core/RippleBinaryCodec/Types/Currency.php index 65aae58..a4296a3 100644 --- a/src/Core/RippleBinaryCodec/Types/Currency.php +++ b/src/Core/RippleBinaryCodec/Types/Currency.php @@ -91,9 +91,7 @@ private static function isoToBytes(string $iso): Buffer { $bytes = Buffer::alloc(20); if ($iso !== 'XRP') { - $isoBytes = array_map(function ($c) { - return ord($c); - }, str_split($iso)); + $isoBytes = array_map(ord(...), str_split($iso)); $bytes->set(12, $isoBytes); } @@ -112,7 +110,7 @@ private function isoCodeFromHex(Buffer $code): ?string return null; } - if ($this->isIsoCode($iso)) { + if (self::isIsoCode($iso)) { return $iso; } diff --git a/src/Core/RippleBinaryCodec/Types/SerializedType.php b/src/Core/RippleBinaryCodec/Types/SerializedType.php index 52e5327..ed9423e 100644 --- a/src/Core/RippleBinaryCodec/Types/SerializedType.php +++ b/src/Core/RippleBinaryCodec/Types/SerializedType.php @@ -115,6 +115,12 @@ public static function getTypeByName(string $name): SerializedType "UInt16" => UnsignedInt16::class, "UInt32" => UnsignedInt32::class, "UInt64" => UnsignedInt64::class, + "UInt96" => UnsignedInt96::class, + "UInt192" => UnsignedInt192::class, + "UInt384" => UnsignedInt384::class, + "UInt512" => UnsignedInt512::class, + "Int32" => SignedInt32::class, + "Int64" => SignedInt64::class, "Vector256" => Vector256::class, "XchainBridge" => XchainBridge::class ]; diff --git a/src/Core/RippleBinaryCodec/Types/SignedInt.php b/src/Core/RippleBinaryCodec/Types/SignedInt.php new file mode 100644 index 0000000..30cca46 --- /dev/null +++ b/src/Core/RippleBinaryCodec/Types/SignedInt.php @@ -0,0 +1,69 @@ +value = BigInteger::of(0); + } else { + $this->value = $this->fromTwoComplement($bytes); + } + } + + public function toBytes(): Buffer + { + return $this->toTwoComplement($this->value, static::WIDTH); + } + + public function toHex(): string + { + return strtoupper($this->toBytes()->toString()); + } + + public function toJson(): int|string + { + return $this->valueOf(); + } + + abstract public function valueOf(): int|string; + + protected function fromTwoComplement(Buffer $bytes): BigInteger + { + $hex = $bytes->toString(); + $val = BigInteger::fromBase($hex, 16); + $bitLength = $bytes->getLength() * 8; + $maxVal = BigInteger::of(2)->power($bitLength); + $midVal = BigInteger::of(2)->power($bitLength - 1); + + if ($val->isGreaterThanOrEqualTo($midVal)) { + return $val->minus($maxVal); + } + + return $val; + } + + protected function toTwoComplement(BigInteger $value, int $width): Buffer + { + $bitLength = $width * 8; + $maxVal = BigInteger::of(2)->power($bitLength); + + if ($value->isNegative()) { + $value = $maxVal->plus($value); + } + + $hex = $value->toBase(16); + $hex = str_pad($hex, $width * 2, "0", STR_PAD_LEFT); + + return Buffer::from($hex, 'hex'); + } +} diff --git a/src/Core/RippleBinaryCodec/Types/SignedInt32.php b/src/Core/RippleBinaryCodec/Types/SignedInt32.php new file mode 100644 index 0000000..db04f26 --- /dev/null +++ b/src/Core/RippleBinaryCodec/Types/SignedInt32.php @@ -0,0 +1,34 @@ +readUInt32()); + } + + public static function fromJson(string|int $serializedJson): SerializedType + { + if (is_string($serializedJson)) { + $serializedJson = (int) json_decode($serializedJson); + } + + $instance = new SignedInt32(); + $instance->value = BigInteger::of($serializedJson); + + return $instance; + } + + public function valueOf(): int|string + { + return $this->value->toInt(); + } +} diff --git a/src/Core/RippleBinaryCodec/Types/SignedInt64.php b/src/Core/RippleBinaryCodec/Types/SignedInt64.php new file mode 100644 index 0000000..9eca978 --- /dev/null +++ b/src/Core/RippleBinaryCodec/Types/SignedInt64.php @@ -0,0 +1,30 @@ +readUInt64()); + } + + public static function fromJson(string|int $serializedJson): SerializedType + { + $instance = new SignedInt64(); + $instance->value = BigInteger::of($serializedJson); + + return $instance; + } + + public function valueOf(): int|string + { + return (string) $this->value; + } +} diff --git a/src/Core/RippleBinaryCodec/Types/UnsignedInt192.php b/src/Core/RippleBinaryCodec/Types/UnsignedInt192.php new file mode 100644 index 0000000..1845e49 --- /dev/null +++ b/src/Core/RippleBinaryCodec/Types/UnsignedInt192.php @@ -0,0 +1,45 @@ +readUIntN(24); + return new UnsignedInt192($bytes); + } + + public static function fromJson(string|int $serializedJson): SerializedType + { + if (is_int($serializedJson)) { + $serializedJson = (string)$serializedJson; + } + + return new UnsignedInt192(Buffer::from($serializedJson)); + } + + public function toBytes(): Buffer + { + $hexStr = $this->value->toBase(16); + $uint192HexStr = str_pad($hexStr, 48, "0", STR_PAD_LEFT); + + return Buffer::from($uint192HexStr, 'hex'); + } + + public function valueOf(): int|string + { + return $this->value->toBase(10); + } +} diff --git a/src/Core/RippleBinaryCodec/Types/UnsignedInt384.php b/src/Core/RippleBinaryCodec/Types/UnsignedInt384.php new file mode 100644 index 0000000..ec1b685 --- /dev/null +++ b/src/Core/RippleBinaryCodec/Types/UnsignedInt384.php @@ -0,0 +1,45 @@ +readUIntN(48); + return new UnsignedInt384($bytes); + } + + public static function fromJson(string|int $serializedJson): SerializedType + { + if (is_int($serializedJson)) { + $serializedJson = (string)$serializedJson; + } + + return new UnsignedInt384(Buffer::from($serializedJson)); + } + + public function toBytes(): Buffer + { + $hexStr = $this->value->toBase(16); + $uint384HexStr = str_pad($hexStr, 96, "0", STR_PAD_LEFT); + + return Buffer::from($uint384HexStr, 'hex'); + } + + public function valueOf(): int|string + { + return $this->value->toBase(10); + } +} diff --git a/src/Core/RippleBinaryCodec/Types/UnsignedInt512.php b/src/Core/RippleBinaryCodec/Types/UnsignedInt512.php new file mode 100644 index 0000000..b855a46 --- /dev/null +++ b/src/Core/RippleBinaryCodec/Types/UnsignedInt512.php @@ -0,0 +1,45 @@ +readUIntN(64); + return new UnsignedInt512($bytes); + } + + public static function fromJson(string|int $serializedJson): SerializedType + { + if (is_int($serializedJson)) { + $serializedJson = (string)$serializedJson; + } + + return new UnsignedInt512(Buffer::from($serializedJson)); + } + + public function toBytes(): Buffer + { + $hexStr = $this->value->toBase(16); + $uint512HexStr = str_pad($hexStr, 128, "0", STR_PAD_LEFT); + + return Buffer::from($uint512HexStr, 'hex'); + } + + public function valueOf(): int|string + { + return $this->value->toBase(10); + } +} diff --git a/src/Core/RippleBinaryCodec/Types/UnsignedInt96.php b/src/Core/RippleBinaryCodec/Types/UnsignedInt96.php new file mode 100644 index 0000000..a74acfd --- /dev/null +++ b/src/Core/RippleBinaryCodec/Types/UnsignedInt96.php @@ -0,0 +1,45 @@ +readUIntN(12); + return new UnsignedInt96($bytes); + } + + public static function fromJson(string|int $serializedJson): SerializedType + { + if (is_int($serializedJson)) { + $serializedJson = (string)$serializedJson; + } + + return new UnsignedInt96(Buffer::from($serializedJson)); + } + + public function toBytes(): Buffer + { + $hexStr = $this->value->toBase(16); + $uint96HexStr = str_pad($hexStr, 24, "0", STR_PAD_LEFT); + + return Buffer::from($uint96HexStr, 'hex'); + } + + public function valueOf(): int|string + { + return $this->value->toBase(10); + } +} diff --git a/src/Core/RippleKeyPairs/Ed25519KeyPairService.php b/src/Core/RippleKeyPairs/Ed25519KeyPairService.php index feb047d..170c13b 100644 --- a/src/Core/RippleKeyPairs/Ed25519KeyPairService.php +++ b/src/Core/RippleKeyPairs/Ed25519KeyPairService.php @@ -10,7 +10,7 @@ class Ed25519KeyPairService extends AbstractKeyPairService implements KeyPairSer { private static ?Ed25519KeyPairService $instance = null; - private EdDSA $elliptic; + private readonly EdDSA $elliptic; public function __construct() { diff --git a/src/Core/RippleKeyPairs/KeyPair.php b/src/Core/RippleKeyPairs/KeyPair.php index a6e3d63..e78ccb6 100644 --- a/src/Core/RippleKeyPairs/KeyPair.php +++ b/src/Core/RippleKeyPairs/KeyPair.php @@ -10,14 +10,8 @@ class KeyPair public const EC = 'secp256k1'; - private string $publicKey; - - private string $privateKey; - - public function __construct(string $publicKey, string $privateKey) + public function __construct(private string $publicKey, private string $privateKey) { - $this->publicKey = $publicKey; - $this->privateKey = $privateKey; } /** diff --git a/src/Core/RippleKeyPairs/Secp256k1KeyPairService.php b/src/Core/RippleKeyPairs/Secp256k1KeyPairService.php index 50f1b4d..e473b16 100644 --- a/src/Core/RippleKeyPairs/Secp256k1KeyPairService.php +++ b/src/Core/RippleKeyPairs/Secp256k1KeyPairService.php @@ -12,7 +12,7 @@ class Secp256k1KeyPairService extends AbstractKeyPairService implements KeyPairS { private static ?Secp256k1KeyPairService $instance = null; - private EC $elliptic; + private readonly EC $elliptic; public function __construct() { @@ -135,7 +135,7 @@ private function deriveScalar(Buffer $seed, ?int $discriminator = null): BN $buffer->appendHex('00000000'); } - $seqHex = str_pad($seqBN->toString('hex'), 8, '00', STR_PAD_LEFT); + $seqHex = str_pad((string) $seqBN->toString('hex'), 8, '00', STR_PAD_LEFT); $buffer->appendHex($seqHex); $hash = MathUtilities::sha512Half($buffer); diff --git a/src/Exceptions/XrplException.php b/src/Exceptions/XrplException.php index 627d955..f1d435e 100644 --- a/src/Exceptions/XrplException.php +++ b/src/Exceptions/XrplException.php @@ -11,7 +11,6 @@ class XrplException extends Exception { protected string $name; - protected ?string $data; /** * Construct an XrplError. @@ -19,9 +18,8 @@ class XrplException extends Exception * @param string $message * @param string|null $data */ - public function __construct(string $message = '', ?string $data = '') { + public function __construct(string $message = '', protected ?string $data = '') { $this->name = self::class; - $this->data = $data; parent::__construct($message); } diff --git a/src/Hooks/Models/Transaction/TransactionTypes/Invoke.php b/src/Hooks/Models/Transaction/TransactionTypes/Invoke.php new file mode 100644 index 0000000..a0f64bc --- /dev/null +++ b/src/Hooks/Models/Transaction/TransactionTypes/Invoke.php @@ -0,0 +1,26 @@ + AccountId::class, + 'Blob' => Blob::class, + ]; +} diff --git a/src/Hooks/Models/Transaction/TransactionTypes/SetHook.php b/src/Hooks/Models/Transaction/TransactionTypes/SetHook.php new file mode 100644 index 0000000..f3396d4 --- /dev/null +++ b/src/Hooks/Models/Transaction/TransactionTypes/SetHook.php @@ -0,0 +1,24 @@ + StArray::class, + ]; +} diff --git a/src/Hooks/Models/Transaction/TransactionTypes/TicketCancel.php b/src/Hooks/Models/Transaction/TransactionTypes/TicketCancel.php new file mode 100644 index 0000000..5976679 --- /dev/null +++ b/src/Hooks/Models/Transaction/TransactionTypes/TicketCancel.php @@ -0,0 +1,24 @@ + UnsignedInt32::class, + ]; +} diff --git a/src/Hooks/Models/Transaction/TransactionTypes/URITokenBurn.php b/src/Hooks/Models/Transaction/TransactionTypes/URITokenBurn.php new file mode 100644 index 0000000..39409c6 --- /dev/null +++ b/src/Hooks/Models/Transaction/TransactionTypes/URITokenBurn.php @@ -0,0 +1,24 @@ + Hash256::class, + ]; +} diff --git a/src/Hooks/Models/Transaction/TransactionTypes/URITokenBuy.php b/src/Hooks/Models/Transaction/TransactionTypes/URITokenBuy.php new file mode 100644 index 0000000..98fefe4 --- /dev/null +++ b/src/Hooks/Models/Transaction/TransactionTypes/URITokenBuy.php @@ -0,0 +1,26 @@ + Hash256::class, + 'Amount' => Amount::class, + ]; +} diff --git a/src/Hooks/Models/Transaction/TransactionTypes/URITokenCancelSellOffer.php b/src/Hooks/Models/Transaction/TransactionTypes/URITokenCancelSellOffer.php new file mode 100644 index 0000000..9a14b53 --- /dev/null +++ b/src/Hooks/Models/Transaction/TransactionTypes/URITokenCancelSellOffer.php @@ -0,0 +1,24 @@ + Hash256::class, + ]; +} diff --git a/src/Hooks/Models/Transaction/TransactionTypes/URITokenCreateSellOffer.php b/src/Hooks/Models/Transaction/TransactionTypes/URITokenCreateSellOffer.php new file mode 100644 index 0000000..a876df8 --- /dev/null +++ b/src/Hooks/Models/Transaction/TransactionTypes/URITokenCreateSellOffer.php @@ -0,0 +1,28 @@ + Hash256::class, + 'Amount' => Amount::class, + 'Destination' => AccountId::class, + ]; +} diff --git a/src/Hooks/Models/Transaction/TransactionTypes/URITokenMint.php b/src/Hooks/Models/Transaction/TransactionTypes/URITokenMint.php new file mode 100644 index 0000000..420d544 --- /dev/null +++ b/src/Hooks/Models/Transaction/TransactionTypes/URITokenMint.php @@ -0,0 +1,26 @@ + Blob::class, + 'Destination' => AccountId::class, + ]; +} diff --git a/src/Models/ErrorResponse.php b/src/Models/ErrorResponse.php index d83dfe3..0878890 100644 --- a/src/Models/ErrorResponse.php +++ b/src/Models/ErrorResponse.php @@ -12,30 +12,10 @@ class ErrorResponse { - protected int|string|null $id = null; - - protected int $statusCode; - protected string $type = 'response'; - protected string $error; - - protected string|int|null $errorCode; - - protected ?string $errorMessage; - - public function __construct( - int|string|null $id, - int $statusCode, - string $error, - string|int|null $errorCode = null, - ?string $errorMessage = null - ) { - $this->id = $id; - $this->statusCode = $statusCode; - $this->error = $error; - $this->errorCode = $errorCode; - $this->errorMessage = $errorMessage; + public function __construct(protected int|string|null $id, protected int $statusCode, protected string $error, protected string|int|null $errorCode = null, protected ?string $errorMessage = null) + { } public function getStatus(): string diff --git a/src/Models/Transaction/TransactionTypes/AccountSet.php b/src/Models/Transaction/TransactionTypes/AccountSet.php index 7ba793c..a30ad6b 100644 --- a/src/Models/Transaction/TransactionTypes/AccountSet.php +++ b/src/Models/Transaction/TransactionTypes/AccountSet.php @@ -10,6 +10,7 @@ namespace Hardcastle\XRPL_PHP\Models\Transaction\TransactionTypes; +use Hardcastle\XRPL_PHP\Core\RippleBinaryCodec\Types\AccountId; use Hardcastle\XRPL_PHP\Core\RippleBinaryCodec\Types\Blob; use Hardcastle\XRPL_PHP\Core\RippleBinaryCodec\Types\Hash128; use Hardcastle\XRPL_PHP\Core\RippleBinaryCodec\Types\Hash256; @@ -27,7 +28,7 @@ class AccountSet extends BaseTransaction 'Domain' => Blob::class, 'EmailHash' => Hash128::class, 'MessageKey' => Blob::class, - 'NFTokenMinter' => Blob::class, + 'NFTokenMinter' => AccountId::class, 'SetFlag' => UnsignedInt32::class, 'TransferRate' => UnsignedInt32::class, 'TickSize' => UnsignedInt8::class, diff --git a/src/Models/Transaction/TransactionTypes/DIDDelete.php b/src/Models/Transaction/TransactionTypes/DIDDelete.php new file mode 100644 index 0000000..92ef96e --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/DIDDelete.php @@ -0,0 +1,20 @@ + Blob::class, + 'DIDDocument' => Blob::class, + 'URI' => Blob::class, + ]; +} diff --git a/src/Models/Transaction/TransactionTypes/OracleDelete.php b/src/Models/Transaction/TransactionTypes/OracleDelete.php new file mode 100644 index 0000000..a128b05 --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/OracleDelete.php @@ -0,0 +1,24 @@ + UnsignedInt32::class, + ]; +} diff --git a/src/Models/Transaction/TransactionTypes/OracleSet.php b/src/Models/Transaction/TransactionTypes/OracleSet.php new file mode 100644 index 0000000..c3fe1d2 --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/OracleSet.php @@ -0,0 +1,31 @@ + UnsignedInt32::class, + 'LastUpdateTime' => UnsignedInt32::class, + 'PriceDataSeries' => StArray::class, + 'AssetClass' => Blob::class, + 'Provider' => Blob::class, + 'URI' => Blob::class, + ]; +} diff --git a/src/Models/Transaction/TransactionTypes/XChainAccountCreateCommit.php b/src/Models/Transaction/TransactionTypes/XChainAccountCreateCommit.php new file mode 100644 index 0000000..ec883d4 --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/XChainAccountCreateCommit.php @@ -0,0 +1,29 @@ + XchainBridge::class, + 'SignatureReward' => Amount::class, + 'Destination' => AccountId::class, + 'Amount' => Amount::class, + ]; +} diff --git a/src/Models/Transaction/TransactionTypes/XChainAddAccountCreateAttestation.php b/src/Models/Transaction/TransactionTypes/XChainAddAccountCreateAttestation.php new file mode 100644 index 0000000..6b5368a --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/XChainAddAccountCreateAttestation.php @@ -0,0 +1,36 @@ + XchainBridge::class, + 'PublicKey' => Blob::class, + 'Signature' => Blob::class, + 'OtherChainSource' => AccountId::class, + 'Amount' => Amount::class, + 'AttestationRewardAccount' => AccountId::class, + 'AttestationSignerAccount' => AccountId::class, + 'WasLockingChainSend' => UnsignedInt8::class, + 'Destination' => AccountId::class, + ]; +} diff --git a/src/Models/Transaction/TransactionTypes/XChainAddClaimAttestation.php b/src/Models/Transaction/TransactionTypes/XChainAddClaimAttestation.php new file mode 100644 index 0000000..1e043a9 --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/XChainAddClaimAttestation.php @@ -0,0 +1,38 @@ + XchainBridge::class, + 'PublicKey' => Blob::class, + 'Signature' => Blob::class, + 'OtherChainSource' => AccountId::class, + 'Amount' => Amount::class, + 'AttestationRewardAccount' => AccountId::class, + 'AttestationSignerAccount' => AccountId::class, + 'WasLockingChainSend' => UnsignedInt8::class, + 'XChainClaimID' => UnsignedInt64::class, + 'Destination' => AccountId::class, + ]; +} diff --git a/src/Models/Transaction/TransactionTypes/XChainClaim.php b/src/Models/Transaction/TransactionTypes/XChainClaim.php new file mode 100644 index 0000000..d4f0484 --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/XChainClaim.php @@ -0,0 +1,30 @@ + XchainBridge::class, + 'XChainClaimID' => UnsignedInt64::class, + 'Amount' => Amount::class, + 'Destination' => AccountId::class, + ]; +} diff --git a/src/Models/Transaction/TransactionTypes/XChainCommit.php b/src/Models/Transaction/TransactionTypes/XChainCommit.php new file mode 100644 index 0000000..06cced2 --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/XChainCommit.php @@ -0,0 +1,30 @@ + XchainBridge::class, + 'XChainClaimID' => UnsignedInt64::class, + 'Amount' => Amount::class, + 'OtherChainDestination' => AccountId::class, + ]; +} diff --git a/src/Models/Transaction/TransactionTypes/XChainCreateBridge.php b/src/Models/Transaction/TransactionTypes/XChainCreateBridge.php new file mode 100644 index 0000000..72efbed --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/XChainCreateBridge.php @@ -0,0 +1,27 @@ + XchainBridge::class, + 'SignatureReward' => Amount::class, + 'MinAccountCreateAmount' => Amount::class, + ]; +} diff --git a/src/Models/Transaction/TransactionTypes/XChainCreateClaimID.php b/src/Models/Transaction/TransactionTypes/XChainCreateClaimID.php new file mode 100644 index 0000000..3d34b1d --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/XChainCreateClaimID.php @@ -0,0 +1,28 @@ + XchainBridge::class, + 'SignatureReward' => Amount::class, + 'OtherChainSource' => AccountId::class, + ]; +} diff --git a/src/Models/Transaction/TransactionTypes/XChainModifyBridge.php b/src/Models/Transaction/TransactionTypes/XChainModifyBridge.php new file mode 100644 index 0000000..b05e1b5 --- /dev/null +++ b/src/Models/Transaction/TransactionTypes/XChainModifyBridge.php @@ -0,0 +1,27 @@ + XchainBridge::class, + 'SignatureReward' => Amount::class, + 'MinAccountCreateAmount' => Amount::class, + ]; +} diff --git a/src/Sugar/balances.php b/src/Sugar/balances.php index bde365b..3cd44c2 100644 --- a/src/Sugar/balances.php +++ b/src/Sugar/balances.php @@ -41,7 +41,7 @@ function getXrpBalance( $xrpResponse = $client->request($accountInfoRequest)->wait(); - if(get_class($xrpResponse) === ErrorResponse::class) { + if($xrpResponse::class === ErrorResponse::class) { throw new Exception($xrpResponse->getError()); } @@ -51,33 +51,82 @@ function getXrpBalance( if (! function_exists('Hardcastle\XRPL_PHP\Sugar\getBalances')) { + /** + * @param JsonRpcClient $client + * @param string $address + * @param string|null $ledgerHash + * @param string|null $ledgerIndex + * @param string|null $peer + * @param int|null $limit + * @return array + * @throws Exception + */ function getBalances( JsonRpcClient $client, string $address, ?string $ledgerHash = null, - ?string $ledgerIndex = null, + ?string $ledgerIndex = 'validated', ?string $peer = null, ?int $limit = null ): array { - //TODO: Complete this function! - $balances = []; - $xrp = ''; - if(!$peer) { - $xrp = getXrpBalance($client, $address, $ledgerHash, $ledgerIndex); + // 1. Get XRP Balance (if no peer filter) + if (!$peer) { + try { + $xrpBalance = getXrpBalance($client, $address, $ledgerHash, $ledgerIndex); + $balances[] = [ + 'currency' => 'XRP', + 'value' => $xrpBalance + ]; + } catch (Exception $e) { + // If account not found, it might still have trustlines (rare but possible if deleted) + // or we just ignore and continue to trustlines + } } - $linesRequest = new AccountLinesRequest( - $address, - $ledgerHash, - $ledgerIndex, - $peer, - $limit - ); + // 2. Get Trustline Balances + $marker = null; + while (true) { + $linesRequest = new AccountLinesRequest( + account: $address, + ledgerHash: $ledgerHash, + ledgerIndex: $ledgerIndex, + peer: $peer, + limit: $limit, + marker: $marker + ); + + $response = $client->request($linesRequest)->wait(); + + if ($response::class === ErrorResponse::class) { + if ($response->getError() === 'actNotFound' && !empty($balances)) { + // We already have XRP balance, so we can return it even if actNotFound for lines + break; + } + throw new Exception($response->getError()); + } + + $result = $response->getResult(); + foreach ($result['lines'] as $line) { + $balances[] = [ + 'value' => $line['balance'], + 'currency' => $line['currency'], + 'issuer' => $line['account'] + ]; + } + + $marker = $result['marker'] ?? null; + if (!$marker || ($limit && count($balances) >= $limit)) { + break; + } + } + if ($limit && count($balances) > $limit) { + return array_slice($balances, 0, $limit); + } - return array_slice($balances, 0, $limit); + return $balances; } } \ No newline at end of file diff --git a/src/Sugar/fundWallet.php b/src/Sugar/fundWallet.php index bdd88e6..94967fc 100644 --- a/src/Sugar/fundWallet.php +++ b/src/Sugar/fundWallet.php @@ -28,7 +28,7 @@ function getUpdatedBalance(JsonRpcClient $client, string $address, float $origin $newBalance = null; try { $newBalance = (float)$client->getXrpBalance($address); - } catch (Exception $e) { + } catch (Exception) { //new Balance remains undefined } @@ -68,7 +68,7 @@ function fundWallet( $startingBalance = 0; try { $startingBalance = getXrpBalance($client, $walletToFund->getClassicAddress()); - } catch (Exception $e) { + } catch (Exception) { // startingBalance remains '0' } @@ -83,7 +83,7 @@ function fundWallet( body: $jsonData )->wait(); - $faucetWallet = json_decode($response->getBody(), true); + $faucetWallet = json_decode((string) $response->getBody(), true); if (!isset($faucetWallet['account']['address'])) { // error: 'The faucet account is undefined' @@ -101,7 +101,7 @@ function fundWallet( if ($updatedBalance > $startingBalance) { break; } - } catch (Exception $e) { + } catch (Exception) { } sleep($intervalSeconds); @@ -111,7 +111,7 @@ function fundWallet( return [ 'wallet' => $walletToFund, 'balance' => $updatedBalance, - 'fundWalletResponse' => json_decode($response->getBody(), true) + 'fundWalletResponse' => json_decode((string) $response->getBody(), true) ]; } } \ No newline at end of file diff --git a/src/Sugar/getOrderbook.php b/src/Sugar/getOrderbook.php new file mode 100644 index 0000000..9981453 --- /dev/null +++ b/src/Sugar/getOrderbook.php @@ -0,0 +1,58 @@ +request($request)->wait(); + + if ($response::class === ErrorResponse::class) { + throw new Exception($response->getError()); + } + + return $response->getResult()['offers']; + } +} diff --git a/src/Sugar/getTransactions.php b/src/Sugar/getTransactions.php index 8f1eaa7..c809b33 100644 --- a/src/Sugar/getTransactions.php +++ b/src/Sugar/getTransactions.php @@ -9,59 +9,72 @@ use Hardcastle\XRPL_PHP\Models\Account\AccountLinesRequest; -if (! function_exists('Hardcastle\XRPL_PHP\Sugar\getXrpBalance')) { +use Hardcastle\XRPL_PHP\Models\Account\AccountTxRequest; +use Hardcastle\XRPL_PHP\Models\ErrorResponse; + +if (! function_exists('Hardcastle\XRPL_PHP\Sugar\getTransactions')) { /** + * @param JsonRpcClient $client + * @param string $address + * @param int|null $ledgerIndexMin + * @param int|null $ledgerIndexMax + * @param string|null $ledgerHash + * @param string|null $ledgerIndex + * @param bool|null $binary + * @param bool|null $forward + * @param int|null $limit + * @param mixed|null $marker + * @return array * @throws Exception */ - function getXrpBalance( + function getTransactions( JsonRpcClient $client, string $address, + ?int $ledgerIndexMin = null, + ?int $ledgerIndexMax = null, ?string $ledgerHash = null, - ?string $ledgerIndex = null, - ): string + ?string $ledgerIndex = 'validated', + ?bool $binary = null, + ?bool $forward = null, + ?int $limit = null, + mixed $marker = null + ): array { - //$xrpRequest = new AccountInfoRequest($address, $ledgerIndex, $ledgerIndex || 'validated'); - $xrpRequest = new AccountInfoRequest($address); - $body = json_encode($xrpRequest->getBody()); - $response = $client->rawSyncRequest('POST', '', $body); + $transactions = []; - $content = $response->getBody()->getContents(); - $json = json_decode($content, true); + while (true) { + $request = new AccountTxRequest( + account: $address, + ledgerIndexMin: $ledgerIndexMin, + ledgerIndexMax: $ledgerIndexMax, + ledgerHash: $ledgerHash, + ledgerIndex: $ledgerIndex, + binary: $binary, + forward: $forward, + limit: $limit, + marker: $marker + ); - return dropsToXrp($json['result']['account_data']['Balance']); - } -} + $response = $client->request($request)->wait(); -if (! function_exists('Hardcastle\XRPL_PHP\Sugar\getBalances')) { + if ($response::class === ErrorResponse::class) { + throw new Exception($response->getError()); + } - function getBalances( - JsonRpcClient $client, - string $address, - ?string $ledgerHash = null, - ?string $ledgerIndex = null, - ?string $peer = null, - ?int $limit = null - ): array - { - //TODO: Complete this function! - - $balances = []; + $result = $response->getResult(); + $transactions = array_merge($transactions, $result['transactions']); - $xrp = ''; - if(!$peer) { - $xrp = getXrpBalance($client, $address, $ledgerHash, $ledgerIndex); + $marker = $result['marker'] ?? null; + if (!$marker || ($limit && count($transactions) >= $limit)) { + break; + } } - $linesRequest = new AccountLinesRequest( - $address, - $ledgerHash, - $ledgerIndex, - $peer, - $limit - ); - + if ($limit && count($transactions) > $limit) { + return array_slice($transactions, 0, $limit); + } - return array_slice($balances, 0, $limit); + return $transactions; } } \ No newline at end of file diff --git a/src/Utils/Hashes/HashLedger.php b/src/Utils/Hashes/HashLedger.php index fd921f7..c2ca68f 100644 --- a/src/Utils/Hashes/HashLedger.php +++ b/src/Utils/Hashes/HashLedger.php @@ -15,7 +15,7 @@ class HashLedger { private static ?HashLedger $instance = null; - private BinaryCodec $binaryCodec; + private readonly BinaryCodec $binaryCodec; public static function getInstance(): HashLedger { diff --git a/src/Utils/Utilities.php b/src/Utils/Utilities.php index 869be97..400a6c9 100644 --- a/src/Utils/Utilities.php +++ b/src/Utils/Utilities.php @@ -23,9 +23,7 @@ public static function isoToHex(string $iso): string { $bytes = Buffer::alloc(20); if ($iso !== 'XRP') { - $isoBytes = array_map(function ($c) { - return ord($c); - }, str_split($iso)); + $isoBytes = array_map(ord(...), str_split($iso)); $bytes->set(12, $isoBytes); } diff --git a/src/Wallet/Wallet.php b/src/Wallet/Wallet.php index 5b3e8b6..390776c 100644 --- a/src/Wallet/Wallet.php +++ b/src/Wallet/Wallet.php @@ -20,22 +20,18 @@ class Wallet public const DEFAULT_ALGORITHM = KeyPair::EDDSA; - private BinaryCodec $binaryCodec; + private readonly BinaryCodec $binaryCodec; private KeyPairServiceInterface $keyPairService; - private string $publicKey; - - private string $privateKey; + private readonly string $publicKey; private string $classicAddress; - private ?string $seed; - public function __construct( string $publicKey, - string $privateKey, - string $seed, + private readonly string $privateKey, + private readonly ?string $seed, ?string $masterAddress = null, ) { @@ -50,8 +46,6 @@ public function __construct( } $this->publicKey = $publicKey; - $this->privateKey = $privateKey; - $this->seed = $seed; if (is_string($masterAddress)) { $this->classicAddress = CoreUtilities::ensureClassicAddress($masterAddress); @@ -224,21 +218,21 @@ private function checkTxSerialisation(string $serializedTx, array $tx): void if(!XrplUtilities::isHex($memo['Memo']['MemoData'])) { throw new ValidationException('MemoData field must be a hex value'); } - $memo['Memo']['MemoData'] = strtoupper($memo['Memo']['MemoData']); + $memo['Memo']['MemoData'] = strtoupper((string) $memo['Memo']['MemoData']); } if (isset($memo['Memo']['MemoType'])) { if(!XrplUtilities::isHex($memo['Memo']['MemoType'])) { throw new ValidationException('MemoType field must be a hex value'); } - $memo['Memo']['MemoType'] = strtoupper($memo['Memo']['MemoType']); + $memo['Memo']['MemoType'] = strtoupper((string) $memo['Memo']['MemoType']); } if (isset($memo['Memo']['MemoFormat'])) { if(!XrplUtilities::isHex($memo['Memo']['MemoFormat'])) { throw new ValidationException('MemoFormat field must be a hex value'); } - $memo['Memo']['MemoFormat'] = strtoupper($memo['Memo']['MemoFormat']); + $memo['Memo']['MemoFormat'] = strtoupper((string) $memo['Memo']['MemoFormat']); } return $memo; @@ -259,12 +253,12 @@ private function checkTxSerialisation(string $serializedTx, array $tx): void $txAmount = $value; $txCurrency = $txAmount['currency']; - if (strlen($txCurrency) === XrplUtilities::ISSUED_CURRENCY_SIZE && strtoupper($txCurrency) === 'XRP') { + if (strlen((string) $txCurrency) === XrplUtilities::ISSUED_CURRENCY_SIZE && strtoupper((string) $txCurrency) === 'XRP') { throw new XrplException("Trying to sign an issued currency with a similar standard code to XRP (received '{$txCurrency}'). XRP is not an issued currency."); } - if(strlen($txCurrency) !== strlen($decodedTxCurrency)) { - if(strlen($decodedTxCurrency) === XrplUtilities::ISSUED_CURRENCY_SIZE) { + if(strlen((string) $txCurrency) !== strlen((string) $decodedTxCurrency)) { + if(strlen((string) $decodedTxCurrency) === XrplUtilities::ISSUED_CURRENCY_SIZE) { $decodedTx[$key]['currency'] = XrplUtilities::isoToHex($decodedTxCurrency); } else { $tx[$key]['currency'] = XrplUtilities::isoToHex($txCurrency); diff --git a/tests/Core/RippleBinaryCodec/Types/SignedIntTest.php b/tests/Core/RippleBinaryCodec/Types/SignedIntTest.php new file mode 100644 index 0000000..48a05fe --- /dev/null +++ b/tests/Core/RippleBinaryCodec/Types/SignedIntTest.php @@ -0,0 +1,60 @@ +assertEquals("00000005", SignedInt32::fromJson(5)->toHex()); + // Negative + $this->assertEquals("FFFFFFFB", SignedInt32::fromJson(-5)->toHex()); + // Max + $this->assertEquals("7FFFFFFF", SignedInt32::fromJson(2147483647)->toHex()); + // Min + $this->assertEquals("80000000", SignedInt32::fromJson(-2147483648)->toHex()); + } + + public function testSignedInt32Decoding(): void + { + // Positive + $this->assertEquals(5, SignedInt32::fromHex("00000005")->toJson()); + // Negative + $this->assertEquals(-5, SignedInt32::fromHex("FFFFFFFB")->toJson()); + // Max + $this->assertEquals(2147483647, SignedInt32::fromHex("7FFFFFFF")->toJson()); + // Min + $this->assertEquals(-2147483648, SignedInt32::fromHex("80000000")->toJson()); + } + + public function testSignedInt64Encoding(): void + { + // Positive + $this->assertEquals("0000000000000005", SignedInt64::fromJson(5)->toHex()); + $this->assertEquals("0000000000000005", SignedInt64::fromJson("5")->toHex()); + // Negative + $this->assertEquals("FFFFFFFFFFFFFFFB", SignedInt64::fromJson(-5)->toHex()); + $this->assertEquals("FFFFFFFFFFFFFFFB", SignedInt64::fromJson("-5")->toHex()); + // Max (9223372036854775807) + $this->assertEquals("7FFFFFFFFFFFFFFF", SignedInt64::fromJson("9223372036854775807")->toHex()); + // Min (-9223372036854775808) + $this->assertEquals("8000000000000000", SignedInt64::fromJson("-9223372036854775808")->toHex()); + } + + public function testSignedInt64Decoding(): void + { + // Positive + $this->assertEquals("5", SignedInt64::fromHex("0000000000000005")->toJson()); + // Negative + $this->assertEquals("-5", SignedInt64::fromHex("FFFFFFFFFFFFFFFB")->toJson()); + // Max + $this->assertEquals("9223372036854775807", SignedInt64::fromHex("7FFFFFFFFFFFFFFF")->toJson()); + // Min + $this->assertEquals("-9223372036854775808", SignedInt64::fromHex("8000000000000000")->toJson()); + } +} diff --git a/tests/Models/Transaction/TransactionTypesTest.php b/tests/Models/Transaction/TransactionTypesTest.php new file mode 100644 index 0000000..7b6b466 --- /dev/null +++ b/tests/Models/Transaction/TransactionTypesTest.php @@ -0,0 +1,263 @@ + 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe', + 'Fee' => '10', + 'Sequence' => 1, + ]; + + public function testPayment(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'Payment', + 'Amount' => '1000000', + 'Destination' => 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + ]); + $model = new Payment($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testAccountSet(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'AccountSet', + 'Domain' => '6578616d706c652e636f6d', + 'SetFlag' => 3, + ]); + $model = new AccountSet($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testTrustSet(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'TrustSet', + 'LimitAmount' => [ + 'currency' => 'USD', + 'issuer' => 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + 'value' => '100' + ], + ]); + $model = new TrustSet($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testOfferCreate(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'OfferCreate', + 'TakerGets' => '1000000', + 'TakerPays' => [ + 'currency' => 'USD', + 'issuer' => 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + 'value' => '100' + ], + ]); + $model = new OfferCreate($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testXChainCreateBridge(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'XChainCreateBridge', + 'XChainBridge' => [ + 'LockingChainDoor' => 'rMAXHh7Zf9VAC6P8atEksFrFX9oHTo4p6E', + 'LockingChainIssue' => ['currency' => 'XRP'], + 'IssuingChainDoor' => 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + 'IssuingChainIssue' => ['currency' => 'XRP'], + ], + 'SignatureReward' => '100', + 'MinAccountCreateAmount' => '1000', + ]); + $model = new XChainCreateBridge($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testDIDSet(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'DIDSet', + 'Data' => '64617461', + 'DIDDocument' => '646f63', + 'URI' => '757269', + ]); + $model = new DIDSet($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testOracleSet(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'OracleSet', + 'OracleDocumentID' => 1, + 'LastUpdateTime' => 12345678, + 'PriceDataSeries' => [ + [ + 'PriceData' => [ + 'BaseAsset' => 'XRP', + 'QuoteAsset' => 'USD', + 'AssetPrice' => 500000, + ] + ] + ], + 'AssetClass' => '63757272656e6379', + ]); + $model = new OracleSet($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testClawback(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'Clawback', + 'Amount' => [ + 'currency' => 'USD', + 'issuer' => 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + 'value' => '100' + ], + ]); + $model = new Clawback($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testTicketCancel(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'TicketCancel', + 'TicketSequence' => 123, + ]); + $model = new TicketCancel($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testURITokenMint(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'URITokenMint', + 'URI' => '687474703a2f2f6578616d706c652e636f6d', + 'Destination' => 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + ]); + $model = new URITokenMint($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testURITokenBurn(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'URITokenBurn', + 'URITokenID' => '0000000000000000000000000000000000000000000000000000000000000000', + ]); + $model = new URITokenBurn($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testURITokenBuy(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'URITokenBuy', + 'URITokenID' => '0000000000000000000000000000000000000000000000000000000000000000', + 'Amount' => '1000000', + ]); + $model = new URITokenBuy($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testURITokenCreateSellOffer(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'URITokenCreateSellOffer', + 'URITokenID' => '0000000000000000000000000000000000000000000000000000000000000000', + 'Amount' => '1000000', + 'Destination' => 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + ]); + $model = new URITokenCreateSellOffer($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testURITokenCancelSellOffer(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'URITokenCancelSellOffer', + 'URITokenID' => '0000000000000000000000000000000000000000000000000000000000000000', + ]); + $model = new URITokenCancelSellOffer($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testSetHook(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'SetHook', + 'Hooks' => [ + [ + 'Hook' => [ + 'HookHash' => '0000000000000000000000000000000000000000000000000000000000000000' + ] + ] + ], + ]); + $model = new SetHook($tx); + $this->assertEquals($tx, $model->toArray()); + } + + public function testInvoke(): void + { + $tx = array_merge($this->commonFields, [ + 'TransactionType' => 'Invoke', + 'Destination' => 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + 'Blob' => '64617461', + ]); + $model = new Invoke($tx); + $this->assertEquals($tx, $model->toArray()); + } +} diff --git a/tests/Sugar/GetFeeXrpTest.php b/tests/Sugar/GetFeeXrpTest.php new file mode 100644 index 0000000..a8e3d99 --- /dev/null +++ b/tests/Sugar/GetFeeXrpTest.php @@ -0,0 +1,43 @@ +start(); + } + + public function setUp(): void + { + $mockRippledUrl = self::$server->getServerRoot(); + $this->client = new JsonRpcClient($mockRippledUrl); + } + + public function testGetXrpBalance(): void + { + //TODO: Implement test + $this->assertEquals(true, true); + } + + public function testGetBalances(): void + { + //TODO: Implement test + $this->assertEquals(true, true); + } +} \ No newline at end of file