diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
new file mode 100644
index 0000000..78a4bee
--- /dev/null
+++ b/.github/workflows/codecov.yml
@@ -0,0 +1,44 @@
+name: Codecov
+
+on: [ push, pull_request ]
+
+jobs:
+ codecov:
+ name: Codecov
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ ubuntu-latest ]
+ php-versions: [ '8.1' ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 1
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-versions }}
+ extensions: gmp, pdo_sqlite, xdebug
+
+ - name: PHP version
+ run: php -v
+
+ - name: Composer install
+ run: composer install
+
+ - name: PHPUnit coverage
+ env:
+ XDEBUG_MODE: coverage
+ WAVES_CONFIG: 7b2257415645535f4e4f4445223a2268747470733a5c2f5c2f73746167652d6e6f64652e77382e696f222c2257415645535f464155434554223a2277617665732070726976617465206e6f64652073656564207769746820776176657320746f6b656e73227d
+ run: php vendor/bin/phpunit tests --coverage-clover ./coverage.xml
+
+ - name: Codecov
+ uses: codecov/codecov-action@v2
+ with:
+ files: ./coverage.xml
+
\ No newline at end of file
diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml
new file mode 100644
index 0000000..8cfb19e
--- /dev/null
+++ b/.github/workflows/phpstan.yml
@@ -0,0 +1,35 @@
+name: PHPStan
+
+on: [ push, pull_request ]
+
+jobs:
+ phpstan:
+ name: PHPStan
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ ubuntu-latest ]
+ php-versions: [ '8.1' ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 1
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-versions }}
+ extensions: gmp, pdo_sqlite
+
+ - name: PHP version
+ run: php -v
+
+ - name: Composer install
+ run: composer install
+
+ - name: PHPStan analyse
+ run: php vendor/bin/phpstan analyse src tests --level 9
diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml
new file mode 100644
index 0000000..42798d8
--- /dev/null
+++ b/.github/workflows/phpunit.yml
@@ -0,0 +1,40 @@
+name: PHPUnit
+
+on: [ push, pull_request ]
+
+jobs:
+ tests:
+ name: Tests
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ ubuntu-latest, windows-latest, macos-latest ]
+ php-versions: [ '7.4', '8.1' ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 1
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-versions }}
+ extensions: gmp, pdo_sqlite
+
+ - name: PHP version
+ run: php -v
+
+ - name: Composer validate
+ run: composer validate
+
+ - name: Composer install
+ run: composer install
+
+ - name: PHPUnit tests
+ env:
+ WAVES_CONFIG: 7b2257415645535f4e4f4445223a2268747470733a5c2f5c2f73746167652d6e6f64652e77382e696f222c2257415645535f464155434554223a2277617665732070726976617465206e6f64652073656564207769746820776176657320746f6b656e73227d
+ run: php vendor/bin/phpunit tests
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..af12b5d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+/vendor
+/.phpunit.cache
+/.vscode
+/coverage.xml
+/composer.lock
+/tests/config.php
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..00b396c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 WavesPlatform
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9a09a44
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+# Waves-PHP
+
+PHP client library for interacting with Waves blockchain platform.
+
+## Installation
+```bash
+composer require waves/client
+```
+
+## Usage
+- Transfer:
+```php
+$account = PrivateKey::fromSeed( 'manage manual recall harvest series desert melt police rose hollow moral pledge kitten position add' );
+$tx = TransferTransaction::build( $account->publicKey(), Recipient::fromAddressOrAlias( 'test' ), Amount::of( 1 ) );
+$txId = Node::TESTNET()->broadcast( $tx->addProof( $account ) )->id();
+$txOnChain = Node::TESTNET()->waitForTransaction( $txId );
+```
+
+## Requirements
+- [PHP](http://php.net) >= 7.4
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..0b32b00
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "waves/client",
+ "description": "PHP client library for interacting with Waves blockchain platform",
+ "keywords": [ "waves", "wavesplatform", "blockchain", "client" ],
+ "homepage": "https://github.com/wavesplatform/waves-php",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Dmitrii Pichulin",
+ "email": "deem@deem.ru"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Waves\\": "src"
+ }
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5",
+ "phpstan/phpstan": "^1.8"
+ },
+ "require": {
+ "deemru/waveskit": "^1.0",
+ "deemru/abcode": "^1.0",
+ "waves/protobuf": "^1.4"
+ }
+}
diff --git a/example.php b/example.php
new file mode 100644
index 0000000..12ea856
--- /dev/null
+++ b/example.php
@@ -0,0 +1,18 @@
+publicKey(), Recipient::fromAddressOrAlias( 'test' ), Amount::of( 1 ) );
+$txId = Node::TESTNET()->broadcast( $tx->addProof( $account ) )->id();
+$txOnChain = Node::TESTNET()->waitForTransaction( $txId );
diff --git a/phpunit.php b/phpunit.php
new file mode 100644
index 0000000..6ca8d3c
--- /dev/null
+++ b/phpunit.php
@@ -0,0 +1,3 @@
+
+
+
+
+ tests
+
+
+
+
+
+ src
+
+
+
diff --git a/src/API/Node.php b/src/API/Node.php
new file mode 100644
index 0000000..7a9a49b
--- /dev/null
+++ b/src/API/Node.php
@@ -0,0 +1,734 @@
+uri = $uri;
+ $this->wk = new \deemru\WavesKit( '?', function( string $wklevel, string $wkmessage )
+ {
+ $this->wklevel = $wklevel;
+ $this->wkmessage = $wkmessage;
+ } );
+ $this->wk->setNodeAddress( $uri, 0 );
+
+ if( !isset( $chainId ) )
+ {
+ if( $uri === Node::MAINNET )
+ $this->chainId = ChainId::MAINNET();
+ else
+ if( $uri === Node::TESTNET )
+ $this->chainId = ChainId::TESTNET();
+ else
+ if( $uri === Node::STAGENET )
+ $this->chainId = ChainId::STAGENET();
+ else
+ $this->chainId = $this->getAddresses()[0]->chainId();
+ }
+ else
+ {
+ $this->chainId = $chainId;
+ }
+
+ $this->wk->chainId = $this->chainId->asString(); // @phpstan-ignore-line // accept workaround
+ }
+
+ static function MAINNET(): Node
+ {
+ return new Node( Node::MAINNET );
+ }
+
+ static function TESTNET(): Node
+ {
+ return new Node( Node::TESTNET );
+ }
+
+ static function STAGENET(): Node
+ {
+ return new Node( Node::STAGENET );
+ }
+
+ static function LOCAL(): Node
+ {
+ return new Node( Node::LOCAL );
+ }
+
+ function chainId(): ChainId
+ {
+ return $this->chainId;
+ }
+
+ function uri(): string
+ {
+ return $this->uri;
+ }
+
+ /**
+ * Fetches a custom REST API request
+ *
+ * @param string $uri
+ * @param Json|string|null $data
+ * @return Json
+ */
+ private function fetch( string $uri, $data = null )
+ {
+ if( isset( $data ) )
+ {
+ if( is_string( $data ) )
+ $fetch = $this->wk->fetch( $uri, true, $data, null, [ 'Content-Type: text/plain', 'Accept: application/json' ] );
+ else
+ $fetch = $this->wk->fetch( $uri, true, $data->toString() );
+ }
+ else
+ $fetch = $this->wk->fetch( $uri );
+ if( $fetch === false )
+ {
+ $message = __FUNCTION__ . ' failed at `' . $uri . '`';
+ if( $this->wklevel === 'e' )
+ $message .= ' (' . $this->wkmessage . ')';
+ throw new Exception( $message, ExceptionCode::FETCH_URI );
+ }
+ $fetch = $this->wk->json_decode( $fetch );
+ if( $fetch === false )
+ throw new Exception( __FUNCTION__ . ' failed to decode `' . $uri . '`', ExceptionCode::JSON_DECODE );
+ return Json::as( $fetch );
+ }
+
+ /**
+ * GETs a custom REST API request
+ *
+ * @param string $uri
+ * @return Json
+ */
+ function get( string $uri ): Json
+ {
+ return $this->fetch( $uri );
+ }
+
+ /**
+ * POSTs a custom REST API request
+ *
+ * @param string $uri
+ * @param Json|string $data
+ * @return Json
+ */
+ function post( string $uri, $data ): Json
+ {
+ return $this->fetch( $uri, $data );
+ }
+
+ //===============
+ // ADDRESSES
+ //===============
+
+ /**
+ * Return addresses of the node
+ *
+ * @return array
+ */
+ function getAddresses(): array
+ {
+ return $this->get( '/addresses' )->asArrayAddress();
+ }
+
+ /**
+ * Return addresses of the node by indexes
+ *
+ * @return array
+ */
+ function getAddressesByIndexes( int $fromIndex, int $toIndex ): array
+ {
+ return $this->get( '/addresses/seq/' . $fromIndex . '/' . $toIndex )->asArrayAddress();
+ }
+
+ function getBalance( Address $address, int $confirmations = null ): int
+ {
+ $uri = '/addresses/balance/' . $address->toString();
+ if( isset( $confirmations ) )
+ $uri .= '/' . $confirmations;
+ return $this->get( $uri )->get( 'balance' )->asInt();
+ }
+
+ /**
+ * Gets addresses balances
+ *
+ * @param array $addresses
+ * @param int|null $height (default: null)
+ * @return array
+ */
+ function getBalances( array $addresses, int $height = null ): array
+ {
+ $json = Json::emptyJson();
+
+ $array = [];
+ foreach( $addresses as $address )
+ $array[] = $address->toString();
+ $json->put( 'addresses', $array );
+
+ if( isset( $height ) )
+ $json->put( 'height', $height );
+
+ return $this->post( '/addresses/balance', $json )->asArrayBalance();
+ }
+
+ function getBalanceDetails( Address $address ): BalanceDetails
+ {
+ return $this->get( '/addresses/balance/details/' . $address->toString() )->asBalanceDetails();
+ }
+
+ /**
+ * Gets DataEntry array of address
+ *
+ * @param Address $address
+ * @param string|null $regex (default: null)
+ * @return array
+ */
+ function getData( Address $address, string $regex = null ): array
+ {
+ $uri = '/addresses/data/' . $address->toString();
+ if( isset( $regex ) )
+ $uri .= '?matches=' . urlencode( $regex );
+ return $this->get( $uri )->asArrayDataEntry();
+ }
+
+ /**
+ * Gets DataEntry array of address by keys
+ *
+ * @param Address $address
+ * @param array $keys
+ * @return array
+ */
+ function getDataByKeys( Address $address, array $keys ): array
+ {
+ $json = Json::emptyJson();
+
+ $array = [];
+ foreach( $keys as $key )
+ $array[] = $key;
+ $json->put( 'keys', $array );
+
+ return $this->post( '/addresses/data/' . $address->toString(), $json )->asArrayDataEntry();
+ }
+
+ /**
+ * Gets a single DataEntry of address by a key
+ *
+ * @param Address $address
+ * @param string $key
+ * @return DataEntry
+ */
+ function getDataByKey( Address $address, string $key ): DataEntry
+ {
+ return $this->get( '/addresses/data/' . $address->toString() . '/' . $key )->asDataEntry();
+ }
+
+ function getScriptInfo( Address $address ): ScriptInfo
+ {
+ return $this->get( '/addresses/scriptInfo/' . $address->toString() )->asScriptInfo();
+ }
+
+ function getScriptMeta( Address $address ): ScriptMeta
+ {
+ $json = $this->get( '/addresses/scriptInfo/' . $address->toString() . '/meta' );
+ if( !$json->exists( 'meta' ) )
+ $json->put( 'meta', [ 'version' => 0, 'callableFuncTypes' => [] ] );
+ return $json->get( 'meta' )->asJson()->asScriptMeta();
+ }
+
+ //===============
+ // ALIAS
+ //===============
+
+ /**
+ * Gets an array of aliases by address
+ *
+ * @param Address $address
+ * @return array
+ */
+ function getAliasesByAddress( Address $address ): array
+ {
+ return $this->get( '/alias/by-address/' . $address->toString() )->asArrayAlias();
+ }
+
+ function getAddressByAlias( Alias $alias ): Address
+ {
+ return $this->get( '/alias/by-alias/' . $alias->name() )->get( 'address' )->asAddress();
+ }
+
+ //===============
+ // ASSETS
+ //===============
+
+ function getAssetDistribution( AssetId $assetId, int $height, int $limit = 1000, string $after = null ): AssetDistribution
+ {
+ $uri = '/assets/' . $assetId->toString() . '/distribution/' . $height . '/limit/' . $limit;
+ if( isset( $after ) )
+ $uri .= '?after=' . $after;
+ return $this->get( $uri )->asAssetDistribution();
+ }
+
+ /**
+ * Gets an array of AssetBalance for an address
+ *
+ * @param Address $address
+ * @return array
+ */
+ function getAssetsBalance( Address $address ): array
+ {
+ return $this->get( '/assets/balance/' . $address->toString() )->get( 'balances' )->asJson()->asArrayAssetBalance();
+ }
+
+ function getAssetBalance( Address $address, AssetId $assetId ): int
+ {
+ return $assetId->isWaves() ?
+ $this->getBalance( $address ) :
+ $this->get( '/assets/balance/' . $address->toString() . '/' . $assetId->toString() )->get( 'balance' )->asInt();
+ }
+
+ function getAssetDetails( AssetId $assetId ): AssetDetails
+ {
+ return $this->get( '/assets/details/' . $assetId->toString() . '?full=true' )->asAssetDetails();
+ }
+
+ /**
+ * @param array $assetIds
+ * @return array
+ */
+ function getAssetsDetails( array $assetIds ): array
+ {
+ $json = Json::emptyJson();
+
+ $array = [];
+ foreach( $assetIds as $assetId )
+ $array[] = $assetId->toString();
+ $json->put( 'ids', $array );
+
+ return $this->post( '/assets/details?full=true', $json )->asArrayAssetDetails();
+ }
+
+ /**
+ * @return array
+ */
+ function getNft( Address $address, int $limit = 1000, AssetId $after = null ): array
+ {
+ $uri = '/assets/nft/' . $address->toString() . '/limit/' . $limit;
+ if( isset( $after ) )
+ $uri .= '?after=' . $after->toString();
+ return $this->get( $uri )->asArrayAssetDetails();
+ }
+
+ //===============
+ // BLOCKCHAIN
+ //===============
+
+ function getBlockchainRewards( int $height = null ): BlockchainRewards
+ {
+ $uri = '/blockchain/rewards';
+ if( isset( $height ) )
+ $uri .= '/' . $height;
+ return $this->get( $uri )->asBlockchainRewards();
+ }
+
+ //===============
+ // BLOCKS
+ //===============
+
+ function getHeight(): int
+ {
+ return $this->get( '/blocks/height' )->get( 'height' )->asInt();
+ }
+
+ function getBlockHeightById( string $blockId ): int
+ {
+ return $this->get( '/blocks/height/' . $blockId )->get( 'height' )->asInt();
+ }
+
+ function getBlockHeightByTimestamp( int $timestamp ): int
+ {
+ return $this->get( "/blocks/heightByTimestamp/" . $timestamp )->get( "height" )->asInt();
+ }
+
+ function getBlocksDelay( string $startBlockId, int $blocksNum ): int
+ {
+ return $this->get( "/blocks/delay/" . $startBlockId . "/" . $blocksNum )->get( "delay" )->asInt();
+ }
+
+ function getBlockHeadersByHeight( int $height ): BlockHeaders
+ {
+ return $this->get( "/blocks/headers/at/" . $height )->asBlockHeaders();
+ }
+
+ function getBlockHeadersById( string $blockId ): BlockHeaders
+ {
+ return $this->get( "/blocks/headers/" . $blockId )->asBlockHeaders();
+ }
+
+ /**
+ * Get an array of BlockHeaders from fromHeight to toHeight
+ *
+ * @param integer $fromHeight
+ * @param integer $toHeight
+ * @return array
+ */
+ function getBlocksHeaders( int $fromHeight, int $toHeight ): array
+ {
+ return $this->get( "/blocks/headers/seq/" . $fromHeight . "/" . $toHeight )->asArrayBlockHeaders();
+ }
+
+ function getLastBlockHeaders(): BlockHeaders
+ {
+ return $this->get( "/blocks/headers/last" )->asBlockHeaders();
+ }
+
+ function getBlockByHeight( int $height ): Block
+ {
+ return $this->get( '/blocks/at/' . $height )->asBlock();
+ }
+
+ function getBlockById( Id $id ): Block
+ {
+ return $this->get( '/blocks/' . $id->toString() )->asBlock();
+ }
+
+ /**
+ * @return array
+ */
+ function getBlocks( int $fromHeight, int $toHeight ): array
+ {
+ return $this->get( '/blocks/seq/' . $fromHeight . '/' . $toHeight )->asArrayBlock();
+ }
+
+ function getGenesisBlock(): Block
+ {
+ return $this->get( '/blocks/first' )->asBlock();
+ }
+
+ function getLastBlock(): Block
+ {
+ return $this->get( '/blocks/last' )->asBlock();
+ }
+
+ /**
+ * @return array
+ */
+ function getBlocksGeneratedBy( Address $generator, int $fromHeight, int $toHeight ): array
+ {
+ return $this->get( '/blocks/address/' . $generator->toString() . '/' . $fromHeight . '/' . $toHeight )->asArrayBlock();
+ }
+
+ //===============
+ // NODE
+ //===============
+
+ function getVersion(): string
+ {
+ return $this->get( '/node/version')->get( 'version' )->asString();
+ }
+
+ //===============
+ // DEBUG
+ //===============
+
+ /**
+ * @param Address $address
+ * @return array
+ */
+ function getBalanceHistory( Address $address ): array
+ {
+ return $this->get( '/debug/balances/history/' . $address->toString() )->asArrayHistoryBalance();
+ }
+
+ function validateTransaction( Transaction $transaction ): Validation
+ {
+ return $this->post( '/debug/validate', $transaction->json() )->asValidation();
+ }
+
+ //===============
+ // LEASING
+ //===============
+
+ /**
+ * @return array
+ */
+ function getActiveLeases( Address $address ): array
+ {
+ return $this->get( '/leasing/active/' . $address->toString() )->asArrayLeaseInfo();
+ }
+
+ function getLeaseInfo( Id $leaseId ): LeaseInfo
+ {
+ return $this->get( '/leasing/info/' . $leaseId->toString() )->asLeaseInfo();
+ }
+
+ /**
+ * @param array $leaseIds
+ * @return array
+ */
+ function getLeasesInfo( array $leaseIds ): array
+ {
+ $json = Json::emptyJson();
+
+ $array = [];
+ foreach( $leaseIds as $leaseId )
+ $array[] = $leaseId->toString();
+ $json->put( 'ids', $array );
+
+ return $this->post( '/leasing/info', $json )->asArrayLeaseInfo();
+ }
+
+ //===============
+ // TRANSACTIONS
+ //===============
+
+ function calculateTransactionFee( Transaction $transaction ): Amount
+ {
+ $json = $this->post( '/transactions/calculateFee', $transaction->json() );
+ return Amount::fromJson( $json, 'feeAmount', 'feeAssetId' );
+ }
+
+ function serializeTransaction( Transaction $transaction ): string
+ {
+ $json = $this->post( '/utils/transactionSerialize', $transaction->json() );
+ $bytes = '';
+ foreach( $json->get( 'bytes' )->asArrayInt() as $byte )
+ $bytes .= chr( $byte );
+ return $bytes;
+ }
+
+ function broadcast( Transaction $transaction ): Transaction
+ {
+ return $this->post( '/transactions/broadcast', $transaction->json() )->asTransaction();
+ }
+
+ function getTransactionInfo( Id $txId ): TransactionInfo
+ {
+ return $this->get( '/transactions/info/' . $txId->toString() )->asTransactionInfo();
+ }
+
+ /**
+ * @return array
+ */
+ function getTransactionsByAddress( Address $address, int $limit = 100, Id $afterTxId = null ): array
+ {
+ $uri = '/transactions/address/' . $address->toString() . '/limit/' . $limit;
+ if( isset( $afterTxId ) )
+ $uri .= '?after=' . $afterTxId->toString();
+ return $this->get( $uri )->get( 0 )->asJson()->asArrayTransactionInfo();
+ }
+
+ function getTransactionStatus( Id $txId ): TransactionStatus
+ {
+ return $this->get( '/transactions/status?id=' . $txId->toString() )->get( 0 )->asJson()->asTransactionStatus();
+ }
+
+ /**
+ * @param array $txIds
+ * @return array
+ */
+ function getTransactionsStatus( array $txIds ): array
+ {
+ $json = Json::emptyJson();
+
+ $array = [];
+ foreach( $txIds as $txId )
+ $array[] = $txId->toString();
+ $json->put( 'ids', $array );
+
+ return $this->post( '/transactions/status', $json )->asArrayTransactionStatus();
+ }
+
+ function getUnconfirmedTransaction( Id $txId ): Transaction
+ {
+ return $this->get( '/transactions/unconfirmed/info/' . $txId->toString() )->asTransaction();
+ }
+
+ /**
+ * @return array
+ */
+ function getUnconfirmedTransactions(): array
+ {
+ return $this->get( '/transactions/unconfirmed' )->asArrayTransaction();
+ }
+
+ function getUtxSize(): int
+ {
+ return $this->get( '/transactions/unconfirmed/size' )->get( 'size' )->asInt();
+ }
+
+ //===============
+ // UTILS
+ //===============
+
+ function compileScript( string $source, bool $enableCompaction = null ): ScriptInfo
+ {
+ $uri = '/utils/script/compileCode';
+ if( isset( $enableCompaction ) )
+ $uri .= '?compact=' . ( $enableCompaction ? 'true' : 'false' );
+ return $this->post( $uri, $source )->asScriptInfo();
+ }
+
+ function ethToWavesAsset( string $asset ): string
+ {
+ return $this->get( '/eth/assets?id=' . $asset )->get( 0 )->asJson()->asAssetDetails()->assetId()->encoded();
+ }
+
+ //===============
+ // WAITINGS
+ //===============
+
+ const blockInterval = 60;
+
+ function waitForTransaction( Id $id, int $waitingInSeconds = Node::blockInterval ): TransactionInfo
+ {
+ if( $waitingInSeconds < 1 )
+ $waitingInSeconds = 1;
+
+ $pollingIntervalInMillis = 100;
+ $pollingIntervalInMicros = $pollingIntervalInMillis * 1000;
+ $waitingInMillis = $waitingInSeconds * 1000;
+
+ for( $spentMillis = 0; $spentMillis < $waitingInMillis; $spentMillis += $pollingIntervalInMillis )
+ {
+ try
+ {
+ return $this->getTransactionInfo( $id );
+ }
+ catch( Exception $e )
+ {
+ if( $e->getCode() !== ExceptionCode::FETCH_URI )
+ throw new Exception( __FUNCTION__ . ' unexpected exception `' . $e->getCode() . '`:`' . $e->getMessage() . '`', ExceptionCode::UNEXPECTED );
+
+ usleep( $pollingIntervalInMicros );
+ }
+ }
+
+ throw new Exception( __FUNCTION__ . ' could not wait for transaction `' . $id->toString() . '` in ' . $waitingInSeconds . ' seconds', ExceptionCode::TIMEOUT );
+ }
+
+ /**
+ * @param array $ids
+ * @param int $waitingInSeconds
+ * @return void
+ */
+ function waitForTransactions( array $ids, int $waitingInSeconds = Node::blockInterval ): void
+ {
+ if( $waitingInSeconds < 1 )
+ $waitingInSeconds = 1;
+
+ $pollingIntervalInMillis = 1000;
+ $pollingIntervalInMicros = $pollingIntervalInMillis * 1000;
+ $waitingInMillis = $waitingInSeconds * 1000;
+
+ for( $spentMillis = 0; $spentMillis < $waitingInMillis; $spentMillis += $pollingIntervalInMillis )
+ {
+ try
+ {
+ $isOK = true;
+ $statuses = $this->getTransactionsStatus( $ids );
+ foreach( $statuses as $status )
+ if( $status->status() !== Status::CONFIRMED )
+ {
+ $isOK = false;
+ break;
+ }
+
+ if( $isOK )
+ return;
+
+ usleep( $pollingIntervalInMicros );
+ }
+ catch( Exception $e )
+ {
+ if( $e->getCode() !== ExceptionCode::FETCH_URI )
+ throw new Exception( __FUNCTION__ . ' unexpected exception `' . $e->getCode() . '`:`' . $e->getMessage() . '`', ExceptionCode::UNEXPECTED );
+
+ usleep( $pollingIntervalInMicros );
+ }
+ }
+
+ throw new Exception( __FUNCTION__ . ' could not wait for transactions', ExceptionCode::TIMEOUT );
+ }
+
+ function waitForHeight( int $target, int $waitingInSeconds = Node::blockInterval * 3 ): int
+ {
+ $start = $this->getHeight();
+ $prev = $start;
+
+ if( $waitingInSeconds < 1 )
+ $waitingInSeconds = 1;
+
+ $pollingIntervalInMillis = 100;
+ $pollingIntervalInMicros = $pollingIntervalInMillis * 1000;
+ $waitingInMillis = $waitingInSeconds * 1000;
+
+ $current = $start;
+ for( $spentMillis = 0; $spentMillis < $waitingInMillis; $spentMillis += $pollingIntervalInMillis )
+ {
+ if( $current >= $target )
+ return $current;
+ else if( $current > $prev )
+ {
+ $prev = $current;
+ $spentMillis = 0;
+ }
+
+ usleep( $pollingIntervalInMicros );
+ $current = $this->getHeight();
+ }
+
+ throw new Exception( __FUNCTION__ . ' could not wait for height `' . $target . '` in ' . $waitingInSeconds . ' seconds', ExceptionCode::TIMEOUT );
+ }
+
+ function waitBlocks( int $blocksCount, int $waitingInSeconds = Node::blockInterval * 3 ): int
+ {
+ return $this->waitForHeight( $this->getHeight() + $blocksCount, $waitingInSeconds );
+ }
+}
\ No newline at end of file
diff --git a/src/Account/Address.php b/src/Account/Address.php
new file mode 100644
index 0000000..b1522a8
--- /dev/null
+++ b/src/Account/Address.php
@@ -0,0 +1,72 @@
+address = Base58String::fromString( $encoded );
+ return $address;
+ }
+
+ static function fromBytes( string $bytes ): Address
+ {
+ if( strlen( $bytes ) !== Address::BYTE_LENGTH )
+ throw new Exception( __FUNCTION__ . ' bad address length: ' . strlen( $bytes ), ExceptionCode::BAD_ADDRESS );
+ $address = new Address;
+ $address->address = Base58String::fromBytes( $bytes );
+ return $address;
+ }
+
+ static function fromPublicKey( PublicKey $publicKey, ChainId $chainId = null ): Address
+ {
+ $address = new Address;
+ $wk = new \deemru\WavesKit( ( isset( $chainId ) ? $chainId : WavesConfig::chainId() )->asString() );
+ $wk->setPublicKey( $publicKey->bytes(), true );
+ $address->address = Base58String::fromBytes( $wk->getAddress( true ) );
+ return $address;
+ }
+
+ function chainId(): ChainId
+ {
+ return ChainId::fromString( $this->bytes()[1] );
+ }
+
+ function bytes(): string
+ {
+ $bytes = $this->address->bytes();
+ if( strlen( $bytes ) !== Address::BYTE_LENGTH )
+ throw new Exception( __FUNCTION__ . ' bad address length: ' . strlen( $bytes ), ExceptionCode::BAD_ADDRESS );
+ return $bytes;
+ }
+
+ function encoded(): string
+ {
+ return $this->address->encoded();
+ }
+
+ function toString(): string
+ {
+ return $this->encoded();
+ }
+
+ function publicKeyHash(): string
+ {
+ return substr( $this->bytes(), 2, 20 );
+ }
+}
diff --git a/src/Account/PrivateKey.php b/src/Account/PrivateKey.php
new file mode 100644
index 0000000..e4384ca
--- /dev/null
+++ b/src/Account/PrivateKey.php
@@ -0,0 +1,53 @@
+key = Base58String::fromBytes( ( new \deemru\WavesKit )->getPrivateKey( true, $seed, pack( 'N', $nonce ) ) );
+ return $privateKey;
+ }
+
+ static function fromBytes( string $key ): PrivateKey
+ {
+ $privateKey = new PrivateKey;
+ $privateKey->key = Base58String::fromBytes( $key );
+ return $privateKey;
+ }
+
+ static function fromString( string $key ): PrivateKey
+ {
+ $privateKey = new PrivateKey;
+ $privateKey->key = Base58String::fromString( $key );
+ return $privateKey;
+ }
+
+ function publicKey(): PublicKey
+ {
+ if( !isset( $this->publicKey ) )
+ $this->publicKey = PublicKey::fromPrivateKey( $this );
+ return $this->publicKey;
+ }
+
+ function bytes(): string
+ {
+ return $this->key->bytes();
+ }
+
+ function toString(): string
+ {
+ return $this->key->toString();
+ }
+}
diff --git a/src/Account/PublicKey.php b/src/Account/PublicKey.php
new file mode 100644
index 0000000..4716754
--- /dev/null
+++ b/src/Account/PublicKey.php
@@ -0,0 +1,62 @@
+key = Base58String::fromBytes( $key );
+ return $publicKey;
+ }
+
+ static function fromString( string $key ): PublicKey
+ {
+ $publicKey = new PublicKey;
+ $publicKey->key = Base58String::fromString( $key );
+ return $publicKey;
+ }
+
+ static function fromPrivateKey( PrivateKey $key ): PublicKey
+ {
+ $publicKey = new PublicKey;
+ $wk = new \deemru\WavesKit;
+ $wk->setPrivateKey( $key->bytes(), true );
+ $publicKey->key = Base58String::fromBytes( $wk->getPublicKey( true ) );
+ return $publicKey;
+ }
+
+ function address( ChainId $chainId = null ): Address
+ {
+ if( !isset( $this->address ) )
+ $this->address = Address::fromPublicKey( $this, $chainId );
+ return $this->address;
+ }
+
+ function attachAddress( Address $address ): void
+ {
+ $this->address = $address;
+ }
+
+ function bytes(): string
+ {
+ return $this->key->bytes();
+ }
+
+ function toString(): string
+ {
+ return $this->key->toString();
+ }
+}
diff --git a/src/Common/Base58String.php b/src/Common/Base58String.php
new file mode 100644
index 0000000..d9bce6a
--- /dev/null
+++ b/src/Common/Base58String.php
@@ -0,0 +1,54 @@
+bytes = '';
+ $base58String->encoded = '';
+ return $base58String;
+ }
+
+ static function fromString( string $encoded ): Base58String
+ {
+ $base58String = new Base58String;
+ $base58String->encoded = $encoded;
+ return $base58String;
+ }
+
+ static function fromBytes( string $bytes ): Base58String
+ {
+ $base58String = new Base58String;
+ $base58String->bytes = $bytes;
+ return $base58String;
+ }
+
+ function bytes(): string
+ {
+ if( !isset( $this->bytes ) )
+ $this->bytes = Functions::base58Decode( $this->encoded );
+ return $this->bytes;
+ }
+
+ function encoded(): string
+ {
+ if( !isset( $this->encoded ) )
+ $this->encoded = Functions::base58Encode( $this->bytes );
+ return $this->encoded;
+ }
+
+ function toString(): string
+ {
+ return $this->encoded();
+ }
+}
diff --git a/src/Common/Base64String.php b/src/Common/Base64String.php
new file mode 100644
index 0000000..e9cef13
--- /dev/null
+++ b/src/Common/Base64String.php
@@ -0,0 +1,77 @@
+bytes = '';
+ $base64String->encoded = '';
+ return $base64String;
+ }
+
+ static function fromString( string $encoded ): Base64String
+ {
+ if( substr( $encoded, 0, 7 ) === Base64String::PROLOG )
+ $encoded = substr( $encoded, 7 );
+
+ $base64String = new Base64String;
+ $base64String->encoded = $encoded;
+ return $base64String;
+ }
+
+ static function fromBytes( string $bytes ): Base64String
+ {
+ $base64String = new Base64String;
+ $base64String->bytes = $bytes;
+ return $base64String;
+ }
+
+ function bytes(): string
+ {
+ if( !isset( $this->bytes ) )
+ {
+ $this->bytes = base64_decode( $this->encoded );
+ if( !is_string( $this->bytes ) )
+ throw new Exception( __FUNCTION__ . ' failed to decode string: ' . $this->encoded, ExceptionCode::BASE64_DECODE );
+ }
+ return $this->bytes;
+ }
+
+ function encoded(): string
+ {
+ if( !isset( $this->encoded ) )
+ $this->encoded = base64_encode( $this->bytes );
+ return $this->encoded;
+ }
+
+ function encodedWithPrefix(): string
+ {
+ return Base64String::PROLOG . $this->encoded();
+ }
+
+ function toString(): string
+ {
+ return $this->encodedWithPrefix();
+ }
+
+ /**
+ * @return string|null
+ */
+ function toJsonValue()
+ {
+ if( $this->bytes() === '' )
+ return null;
+ return $this->encodedWithPrefix();
+ }
+}
diff --git a/src/Common/ExceptionCode.php b/src/Common/ExceptionCode.php
new file mode 100644
index 0000000..4107b22
--- /dev/null
+++ b/src/Common/ExceptionCode.php
@@ -0,0 +1,26 @@
+
+ */
+ private array $data;
+
+ /**
+ * Json constructor
+ *
+ * @param array $data
+ */
+ private function __construct( array $data = [] )
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * Json function constructor
+ *
+ * @param array $data
+ * @return Json
+ */
+ static function as( array $data ): Json
+ {
+ return new Json( $data );
+ }
+
+ static function emptyJson(): Json
+ {
+ return new Json;
+ }
+
+ /**
+ * Gets native underlying data array
+ *
+ * @return array
+ */
+ function data(): array
+ {
+ return $this->data;
+ }
+
+ function toString(): string
+ {
+ $string = json_encode( $this->data );
+ if( $string === false )
+ throw new Exception( __FUNCTION__ . ' failed to encode internal array `' . serialize( $this->data) . '`', ExceptionCode::JSON_ENCODE );
+ return $string;
+ }
+
+ /**
+ * Gets Value by key
+ *
+ * @param mixed $key
+ * @return Value
+ */
+ function get( $key ): Value
+ {
+ if( !isset( $this->data[$key] ) )
+ throw new Exception( __FUNCTION__ . ' failed to find key `' . $key . '`', ExceptionCode::KEY_MISSING );
+ return Value::as( $this->data[$key] );
+ }
+
+ /**
+ * Gets Value by key or returns fallback value
+ *
+ * @param mixed $key
+ * @param mixed $value
+ * @return Value
+ */
+ function getOr( $key, $value ): Value
+ {
+ return $this->exists( $key ) ? $this->get( $key ) : Value::as( $value );
+ }
+
+ /**
+ * Checks key exists
+ *
+ * @param mixed $key
+ * @return bool
+ */
+ function exists( $key ): bool
+ {
+ return isset( $this->data[$key] );
+ }
+
+ /**
+ * Puts value by key
+ *
+ * @param mixed $key
+ * @param mixed $value
+ * @return Json
+ */
+ function put( $key, $value ): Json
+ {
+ $this->data[$key] = $value;
+ return $this;
+ }
+
+ /**
+ * Gets a BlockHeaders value
+ *
+ * @return BlockHeaders
+ */
+ function asBlockHeaders(): BlockHeaders
+ {
+ return new BlockHeaders( $this );
+ }
+
+ /**
+ * Gets a Balance value
+ *
+ * @return Balance
+ */
+ function asBalance(): Balance
+ {
+ return new Balance( $this );
+ }
+
+ function asHistoryBalance(): HistoryBalance
+ {
+ return new HistoryBalance( $this );
+ }
+
+ /**
+ * Gets a AssetBalance value
+ *
+ * @return AssetBalance
+ */
+ function asAssetBalance(): AssetBalance
+ {
+ return new AssetBalance( $this );
+ }
+
+ function asAssetDetails(): AssetDetails
+ {
+ return new AssetDetails( $this );
+ }
+
+ function asAssetDistribution(): AssetDistribution
+ {
+ return new AssetDistribution( $this );
+ }
+
+ /**
+ * Gets a BalanceDetails value
+ *
+ * @return BalanceDetails
+ */
+ function asBalanceDetails(): BalanceDetails
+ {
+ return new BalanceDetails( $this );
+ }
+
+ function asBlockchainRewards(): BlockchainRewards
+ {
+ return new BlockchainRewards( $this );
+ }
+
+ function asBlock(): Block
+ {
+ return new Block( $this );
+ }
+
+ /**
+ * Gets a DataEntry value
+ *
+ * @return DataEntry
+ */
+ function asDataEntry(): DataEntry
+ {
+ return new DataEntry( $this );
+ }
+
+ function asLeaseInfo(): LeaseInfo
+ {
+ return new LeaseInfo( $this );
+ }
+
+ function asScriptMeta(): ScriptMeta
+ {
+ return new ScriptMeta( $this );
+ }
+
+ function asScriptInfo(): ScriptInfo
+ {
+ return new ScriptInfo( $this );
+ }
+
+ function asScriptDetails(): ScriptDetails
+ {
+ return new ScriptDetails( $this );
+ }
+
+ function asTransactionInfo(): TransactionInfo
+ {
+ return new TransactionInfo( $this );
+ }
+
+ function asTransactionWithStatus(): TransactionWithStatus
+ {
+ return new TransactionWithStatus( $this );
+ }
+
+ function asTransactionStatus(): TransactionStatus
+ {
+ return new TransactionStatus( $this );
+ }
+
+ function asTransaction(): Transaction
+ {
+ return new Transaction( $this );
+ }
+
+ function asValidation(): Validation
+ {
+ return new Validation( $this );
+ }
+
+ function asVotes(): Votes
+ {
+ return new Votes( $this );
+ }
+
+ /**
+ * Gets an array of BlockHeaders value
+ *
+ * @return array
+ */
+ function asArrayBlockHeaders(): array
+ {
+ $array = [];
+ foreach( $this->data as $headers )
+ $array[] = Value::as( $headers )->asJson()->asBlockHeaders();
+ return $array;
+ }
+
+ /**
+ * Gets an array of Block value
+ *
+ * @return array
+ */
+ function asArrayBlock(): array
+ {
+ $array = [];
+ foreach( $this->data as $headers )
+ $array[] = Value::as( $headers )->asJson()->asBlock();
+ return $array;
+ }
+
+ /**
+ * Gets an array of LeaseInfo value
+ *
+ * @return array
+ */
+ function asArrayLeaseInfo(): array
+ {
+ $array = [];
+ foreach( $this->data as $info )
+ $array[] = Value::as( $info )->asJson()->asLeaseInfo();
+ return $array;
+ }
+
+ /**
+ * Gets an array value
+ *
+ * @return array
+ */
+ function asArrayAddress(): array
+ {
+ $array = [];
+ foreach( $this->data as $address )
+ $array[] = Address::fromString( Value::as( $address )->asString() );
+ return $array;
+ }
+
+ /**
+ * Gets an array value
+ *
+ * @return array
+ */
+ function asArrayAlias(): array
+ {
+ $array = [];
+ foreach( $this->data as $alias )
+ $array[] = Alias::fromFullAlias( Value::as( $alias )->asString() );
+ return $array;
+ }
+
+ /**
+ * Gets an array value
+ *
+ * @return array
+ */
+ function asArrayBalance(): array
+ {
+ $array = [];
+ foreach( $this->data as $balance )
+ $array[] = Value::as( $balance )->asJson()->asBalance();
+ return $array;
+ }
+
+ /**
+ * Gets an array value
+ *
+ * @return array
+ */
+ function asArrayHistoryBalance(): array
+ {
+ $array = [];
+ foreach( $this->data as $balance )
+ $array[] = Value::as( $balance )->asJson()->asHistoryBalance();
+ return $array;
+ }
+
+ /**
+ * Gets an array value
+ *
+ * @return array
+ */
+ function asArrayAssetBalance(): array
+ {
+ $array = [];
+ foreach( $this->data as $assetBalance )
+ $array[] = Value::as( $assetBalance )->asJson()->asAssetBalance();
+ return $array;
+ }
+
+ /**
+ * Gets an array value
+ *
+ * @return array
+ */
+ function asArrayAssetDetails(): array
+ {
+ $array = [];
+ foreach( $this->data as $assetDetails )
+ $array[] = Value::as( $assetDetails )->asJson()->asAssetDetails();
+ return $array;
+ }
+
+ /**
+ * Gets an array value
+ *
+ * @return array
+ */
+ function asArrayDataEntry(): array
+ {
+ $array = [];
+ foreach( $this->data as $data )
+ $array[] = Value::as( $data )->asJson()->asDataEntry();
+ return $array;
+ }
+
+ /**
+ * Gets an array value
+ *
+ * @return array
+ */
+ function asArrayTransactionWithStatus(): array
+ {
+ $array = [];
+ foreach( $this->data as $tx )
+ $array[] = Value::as( $tx )->asJson()->asTransactionWithStatus();
+ return $array;
+ }
+
+ /**
+ * Gets an array value
+ *
+ * @return array
+ */
+ function asArrayTransactionInfo(): array
+ {
+ $array = [];
+ foreach( $this->data as $tx )
+ $array[] = Value::as( $tx )->asJson()->asTransactionInfo();
+ return $array;
+ }
+
+ /**
+ * @return array
+ */
+ function asArrayTransactionStatus(): array
+ {
+ $array = [];
+ foreach( $this->data as $tx )
+ $array[] = Value::as( $tx )->asJson()->asTransactionStatus();
+ return $array;
+ }
+
+ /**
+ * @return array
+ */
+ function asArrayTransaction(): array
+ {
+ $array = [];
+ foreach( $this->data as $tx )
+ $array[] = Value::as( $tx )->asJson()->asTransaction();
+ return $array;
+ }
+}
diff --git a/src/Common/JsonBase.php b/src/Common/JsonBase.php
new file mode 100644
index 0000000..6651af9
--- /dev/null
+++ b/src/Common/JsonBase.php
@@ -0,0 +1,25 @@
+json = $json;
+ }
+
+ function toString(): string
+ {
+ return $this->json->toString();
+ }
+
+ function json(): Json
+ {
+ return $this->json;
+ }
+}
diff --git a/src/Common/Value.php b/src/Common/Value.php
new file mode 100644
index 0000000..8d4a37a
--- /dev/null
+++ b/src/Common/Value.php
@@ -0,0 +1,258 @@
+value = $value;
+ }
+
+ /**
+ * Value function constructor
+ *
+ * @param mixed $value
+ * @return Value
+ */
+ static function as( $value ): Value
+ {
+ return new Value( $value );
+ }
+
+ /**
+ * Gets an boolean value
+ *
+ * @return bool
+ */
+ function asBoolean(): bool
+ {
+ if( !is_bool( $this->value ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect boolean at `' . json_encode( $this->value ) . '`', ExceptionCode::BOOL_EXPECTED );
+ return $this->value;
+ }
+
+ /**
+ * Gets an integer value
+ *
+ * @return int
+ */
+ function asInt(): int
+ {
+ if( !is_int( $this->value ) )
+ {
+ if( is_string( $this->value ) )
+ {
+ $intval = intval( $this->value );
+ if( strval( $intval ) === $this->value )
+ return $intval;
+ }
+ throw new Exception( __FUNCTION__ . ' failed to detect integer at `' . json_encode( $this->value ) . '`', ExceptionCode::INT_EXPECTED );
+ }
+ return $this->value;
+ }
+
+ /**
+ * Gets a string value
+ *
+ * @return string
+ */
+ function asString(): string
+ {
+ if( !is_string( $this->value ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect string at `' . json_encode( $this->value ) . '`', ExceptionCode::STRING_EXPECTED );
+ return $this->value;
+ }
+
+ function asBase64String(): Base64String
+ {
+ if( !is_string( $this->value ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect string at `' . json_encode( $this->value ) . '`', ExceptionCode::STRING_EXPECTED );
+ return Base64String::fromString( $this->value );
+ }
+
+ function asChainId(): ChainId
+ {
+ if( is_int( $this->value ) )
+ return ChainId::fromInt( $this->value );
+ return ChainId::fromString( $this->asString() );
+ }
+
+ /**
+ * Gets a base64 decoded string value
+ *
+ * @return string
+ */
+ function asBase64Decoded(): string
+ {
+ if( !is_string( $this->value ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect string at `' . json_encode( $this->value ) . '`', ExceptionCode::STRING_EXPECTED );
+ if( substr( $this->value, 0, 7 ) !== 'base64:' )
+ throw new Exception( __FUNCTION__ . ' failed to detect base64 `' . $this->value . '`', ExceptionCode::BASE64_DECODE );
+ $decoded = base64_decode( substr( $this->value, 7 ) );
+ if( !is_string( $decoded ) )
+ throw new Exception( __FUNCTION__ . ' failed to decode base64 `' . substr( $this->value, 7 ) . '`', ExceptionCode::BASE64_DECODE );
+ return $decoded;
+ }
+
+ function asBase58String(): Base58String
+ {
+ return Base58String::fromString( $this->asString() );
+ }
+
+ /**
+ * Gets a Json value
+ *
+ * @return Json
+ */
+ function asJson(): Json
+ {
+ if( !is_array( $this->value ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect Json at `' . json_encode( $this->value ) . '`', ExceptionCode::ARRAY_EXPECTED );
+ return Json::as( $this->value );
+ }
+
+ /**
+ * Gets an array value
+ *
+ * @return array
+ */
+ function asArray(): array
+ {
+ if( !is_array( $this->value ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect array at `' . json_encode( $this->value ) . '`', ExceptionCode::ARRAY_EXPECTED );
+ return $this->value;
+ }
+
+ /**
+ * Gets an array of integers value
+ *
+ * @return array
+ */
+ function asArrayInt(): array
+ {
+ if( !is_array( $this->value ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect array at `' . json_encode( $this->value ) . '`', ExceptionCode::ARRAY_EXPECTED );
+ $ints = [];
+ foreach( $this->value as $value )
+ $ints[] = Value::as( $value )->asInt();
+ return $ints;
+ }
+
+ /**
+ * @return array
+ */
+ function asArrayString(): array
+ {
+ if( !is_array( $this->value ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect array at `' . json_encode( $this->value ) . '`', ExceptionCode::ARRAY_EXPECTED );
+ $strings = [];
+ foreach( $this->value as $value )
+ $strings[] = Value::as( $value )->asString();
+ return $strings;
+ }
+
+ /**
+ * Gets an array of string to integer map
+ *
+ * @return array
+ */
+ function asMapStringInt(): array
+ {
+ if( !is_array( $this->value ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect array at `' . json_encode( $this->value ) . '`', ExceptionCode::ARRAY_EXPECTED );
+ $ints = [];
+ foreach( $this->value as $key => $value )
+ $ints[Value::as( $key )->asString()] = Value::as( $value )->asInt();
+ return $ints;
+ }
+
+ function asArgMeta(): ArgMeta
+ {
+ return new ArgMeta( $this->asJson() );
+ }
+
+ /**
+ * Gets an Address value
+ *
+ * @return Address
+ */
+ function asAddress(): Address
+ {
+ return Address::fromString( $this->asString() );
+ }
+
+ /**
+ * @return Recipient
+ */
+ function asRecipient(): Recipient
+ {
+ return Recipient::fromAddressOrAlias( $this->asString() );
+ }
+
+ function asPublicKey(): PublicKey
+ {
+ return PublicKey::fromString( $this->asString() );
+ }
+
+ /**
+ * Gets an AssetId value
+ *
+ * @return AssetId
+ */
+ function asAssetId(): AssetId
+ {
+ return isset( $this->value ) ? AssetId::fromString( $this->asString() ) : AssetId::WAVES();
+ }
+
+ /**
+ * Gets an Id value
+ *
+ * @return Id
+ */
+ function asId(): Id
+ {
+ return Id::fromString( $this->asString() );
+ }
+
+ function asApplicationStatus(): int
+ {
+ switch( $this->asString() )
+ {
+ case ApplicationStatus::SUCCEEDED_S: return ApplicationStatus::SUCCEEDED;
+ case ApplicationStatus::SCRIPT_EXECUTION_FAILED_S: return ApplicationStatus::SCRIPT_EXECUTION_FAILED;
+ default: return ApplicationStatus::UNKNOWN;
+ }
+ }
+
+ function asStatus(): int
+ {
+ switch( $this->asString() )
+ {
+ case Status::CONFIRMED_S: return Status::CONFIRMED;
+ case Status::UNCONFIRMED_S: return Status::UNCONFIRMED;
+ case Status::NOT_FOUND_S: return Status::NOT_FOUND;
+ default: return Status::UNKNOWN;
+ }
+ }
+}
diff --git a/src/Model/Alias.php b/src/Model/Alias.php
new file mode 100644
index 0000000..a13b597
--- /dev/null
+++ b/src/Model/Alias.php
@@ -0,0 +1,73 @@
+name = $alias;
+ $this->fullAlias = Alias::PREFIX . $chainId->asString() . ':' . $alias;
+ }
+
+ static function fromString( string $alias, ChainId $chainId = null ): Alias
+ {
+ return new Alias( $alias, $chainId );
+ }
+
+ static function fromFullAlias( string $fullAlias ): Alias
+ {
+ if( strlen( $fullAlias ) >= 12 )
+ {
+ $prefix = substr( $fullAlias, 0, strlen( Alias::PREFIX ) );
+ if( $prefix === Alias::PREFIX && $fullAlias[7] === ':' )
+ {
+ $chainId = ChainId::fromString( $fullAlias[6] );
+ $alias = substr( $fullAlias, 8 );
+ return new Alias( $alias, $chainId );
+ }
+ }
+
+ throw new Exception( __FUNCTION__ . ' bad alias name = `' . serialize( $fullAlias ) . '`', ExceptionCode::BAD_ALIAS );
+ }
+
+ static function isValid( string $alias, ChainId $chainId = null ): bool
+ {
+ return $alias === (new Alias( $alias, $chainId ))->name();
+ }
+
+ function chainId(): ChainId
+ {
+ return ChainId::fromString( $this->fullAlias[6] );
+ }
+
+ function name(): string
+ {
+ return $this->name;
+ }
+
+ function toString(): string
+ {
+ return $this->fullAlias;
+ }
+}
diff --git a/src/Model/ApplicationStatus.php b/src/Model/ApplicationStatus.php
new file mode 100644
index 0000000..2b98333
--- /dev/null
+++ b/src/Model/ApplicationStatus.php
@@ -0,0 +1,13 @@
+json->get( 'name' )->asString(); }
+ function type(): string { return $this->json->get( 'type' )->asString(); }
+}
diff --git a/src/Model/AssetBalance.php b/src/Model/AssetBalance.php
new file mode 100644
index 0000000..67e7392
--- /dev/null
+++ b/src/Model/AssetBalance.php
@@ -0,0 +1,17 @@
+json->get( 'assetId' )->asAssetId(); }
+ function balance(): int { return $this->json->get( 'balance' )->asInt(); }
+ function isReissuable(): bool { return $this->json->get( 'reissuable' )->asBoolean(); }
+ function quantity(): int { return $this->json->get( 'quantity' )->asInt(); }
+ function minSponsoredAssetFee(): int { return $this->json->getOr( 'minSponsoredAssetFee', 0 )->asInt(); }
+ function sponsorBalance(): int { return $this->json->getOr( 'sponsorBalance', 0 )->asInt(); }
+ function issueTransaction(): Transaction { return $this->json->get( 'issueTransaction' )->asJson()->asTransaction(); }
+}
diff --git a/src/Model/AssetDetails.php b/src/Model/AssetDetails.php
new file mode 100644
index 0000000..e3fcdf2
--- /dev/null
+++ b/src/Model/AssetDetails.php
@@ -0,0 +1,25 @@
+json->get( 'assetId' )->asAssetId(); }
+ function issueHeight(): int { return $this->json->get( 'issueHeight' )->asInt(); }
+ function issueTimestamp(): int { return $this->json->get( 'issueTimestamp' )->asInt(); }
+ function issuer(): Address { return $this->json->get( 'issuer' )->asAddress(); }
+ function issuerPublicKey(): PublicKey { return $this->json->get( 'issuerPublicKey' )->asPublicKey(); }
+ function name(): string { return $this->json->get( 'name' )->asString(); }
+ function description(): string { return $this->json->get( 'description' )->asString(); }
+ function decimals(): int { return $this->json->get( 'decimals' )->asInt(); }
+ function isReissuable(): bool { return $this->json->get( 'reissuable' )->asBoolean(); }
+ function quantity(): int { return $this->json->get( 'quantity' )->asInt(); }
+ function isScripted(): bool { return $this->json->get( 'scripted' )->asBoolean(); }
+ function minSponsoredAssetFee(): int { return $this->json->getOr( 'minSponsoredAssetFee', 0 )->asInt(); }
+ function originTransactionId(): Id { return $this->json->get( 'originTransactionId' )->asId(); }
+ function scriptDetails(): ScriptDetails { return $this->json->getOr( 'scriptDetails', ScriptDetails::EMPTY )->asJson()->asScriptDetails(); }
+}
diff --git a/src/Model/AssetDistribution.php b/src/Model/AssetDistribution.php
new file mode 100644
index 0000000..c971469
--- /dev/null
+++ b/src/Model/AssetDistribution.php
@@ -0,0 +1,15 @@
+
+ */
+ function items(): array { return $this->json->get( 'items' )->asMapStringInt(); }
+ function lastItem(): string { return $this->json->get( 'lastItem' )->asString(); }
+ function hasNext(): bool { return $this->json->getOr( 'hasNext', false )->asBoolean(); }
+}
diff --git a/src/Model/AssetId.php b/src/Model/AssetId.php
new file mode 100644
index 0000000..e087449
--- /dev/null
+++ b/src/Model/AssetId.php
@@ -0,0 +1,77 @@
+assetId = Base58String::fromString( $encoded );
+ return $assetId;
+ }
+
+ static function fromBytes( string $bytes ): AssetId
+ {
+ if( $bytes === '' )
+ return AssetId::WAVES();
+
+ if( strlen( $bytes ) !== AssetId::BYTE_LENGTH )
+ throw new Exception( __FUNCTION__ . ' bad asset length: ' . strlen( $bytes ), ExceptionCode::BAD_ASSET );
+ $assetId = new AssetId;
+ $assetId->assetId = Base58String::fromBytes( $bytes );
+ return $assetId;
+ }
+
+ function isWaves(): bool
+ {
+ return !isset( $this->assetId );
+ }
+
+ function bytes(): string
+ {
+ if( $this->isWaves() )
+ return '';
+ $bytes = $this->assetId->bytes();
+ if( strlen( $bytes ) !== AssetId::BYTE_LENGTH )
+ throw new Exception( __FUNCTION__ . ' bad asset length: ' . strlen( $bytes ), ExceptionCode::BAD_ASSET );
+ return $bytes;
+ }
+
+ function encoded(): string
+ {
+ return $this->isWaves() ? AssetId::WAVES_STRING : $this->assetId->encoded();
+ }
+
+ function toString(): string
+ {
+ return $this->encoded();
+ }
+
+ /**
+ * @return string|null
+ */
+ function toJsonValue()
+ {
+ return $this->isWaves() ? null : $this->encoded();
+ }
+}
diff --git a/src/Model/Balance.php b/src/Model/Balance.php
new file mode 100644
index 0000000..d2d7b63
--- /dev/null
+++ b/src/Model/Balance.php
@@ -0,0 +1,11 @@
+json->get( 'id' )->asString(); }
+ function getBalance(): int { return $this->json->get( 'balance' )->asInt(); }
+}
diff --git a/src/Model/BalanceDetails.php b/src/Model/BalanceDetails.php
new file mode 100644
index 0000000..21e4ef5
--- /dev/null
+++ b/src/Model/BalanceDetails.php
@@ -0,0 +1,14 @@
+json->get( 'address' )->asString(); }
+ function available(): int { return $this->json->get( 'available' )->asInt(); }
+ function regular(): int { return $this->json->get( 'regular' )->asInt(); }
+ function generating(): int { return $this->json->get( 'generating' )->asInt(); }
+ function effective(): int { return $this->json->get( 'effective' )->asInt(); }
+}
diff --git a/src/Model/Block.php b/src/Model/Block.php
new file mode 100644
index 0000000..9cf7bc0
--- /dev/null
+++ b/src/Model/Block.php
@@ -0,0 +1,12 @@
+
+ */
+ function transactions(): array { return $this->json->get( 'transactions' )->asJson()->asArrayTransactionWithStatus(); }
+ function fee(): int { return $this->json->get( 'fee' )->asInt(); }
+}
diff --git a/src/Model/BlockHeaders.php b/src/Model/BlockHeaders.php
new file mode 100644
index 0000000..9384581
--- /dev/null
+++ b/src/Model/BlockHeaders.php
@@ -0,0 +1,30 @@
+
+ */
+ function features(): array { return $this->json->get( 'features' )->asArrayInt(); }
+ function version(): int { return $this->json->get( 'version' )->asInt(); }
+ function timestamp(): int { return $this->json->get( 'timestamp' )->asInt(); }
+ function reference(): string { return $this->json->get( 'reference' )->asString(); }
+ function baseTarget(): int { return $this->json->get( 'nxt-consensus' )->asJson()->get( 'base-target' )->asInt(); }
+ function generationSignature(): string { return $this->json->get( 'nxt-consensus' )->asJson()->get( 'generation-signature' )->asString(); }
+ function transactionsRoot(): string { return $this->json->get( 'transactionsRoot' )->asString(); }
+ function id(): Id { return $this->json->get( 'id' )->asId(); }
+ function desiredReward(): int { return $this->json->get( 'desiredReward' )->asInt(); }
+ function generator(): Address { return $this->json->get( 'generator' )->asAddress(); }
+ function signature(): string { return $this->json->get( 'signature' )->asString(); }
+ function size(): int { return $this->json->get( 'blocksize' )->asInt(); }
+ function transactionsCount(): int { return $this->json->get( 'transactionCount' )->asInt(); }
+ function height(): int { return $this->json->get( 'height' )->asInt(); }
+ function totalFee(): int { return $this->json->get( 'totalFee' )->asInt(); }
+ function reward(): int { return $this->json->get( 'reward' )->asInt(); }
+ function vrf(): string { return $this->json->get( 'VRF' )->asString(); }
+}
diff --git a/src/Model/BlockchainRewards.php b/src/Model/BlockchainRewards.php
new file mode 100644
index 0000000..66ae7a8
--- /dev/null
+++ b/src/Model/BlockchainRewards.php
@@ -0,0 +1,19 @@
+json->get( 'height' )->asInt(); }
+ function currentReward(): int { return $this->json->get( 'currentReward' )->asInt(); }
+ function totalWavesAmount(): int { return $this->json->get( 'totalWavesAmount' )->asInt(); }
+ function minIncrement(): int { return $this->json->get( 'minIncrement' )->asInt(); }
+ function term(): int { return $this->json->get( 'term' )->asInt(); }
+ function nextCheck(): int { return $this->json->get( 'nextCheck' )->asInt(); }
+ function votingIntervalStart(): int { return $this->json->get( 'votingIntervalStart' )->asInt(); }
+ function votingInterval(): int { return $this->json->get( 'votingInterval' )->asInt(); }
+ function votingThreshold(): int { return $this->json->get( 'votingThreshold' )->asInt(); }
+ function votes(): Votes { return $this->json->get( 'votes' )->asJson()->asVotes(); }
+}
diff --git a/src/Model/ChainId.php b/src/Model/ChainId.php
new file mode 100644
index 0000000..1d6a994
--- /dev/null
+++ b/src/Model/ChainId.php
@@ -0,0 +1,78 @@
+ 255 )
+ throw new Exception( __FUNCTION__ . ' bad chainId value: ' . $int, ExceptionCode::BAD_CHAINID );
+ $chainId = new ChainId;
+ $chainId->chainId = chr( $int );
+ return $chainId;
+ }
+
+ static function fromString( string $string ): ChainId
+ {
+ if( strlen( $string ) !== 1 )
+ throw new Exception( __FUNCTION__ . ' bad chainId value: ' . strlen( $string ), ExceptionCode::BAD_CHAINID );
+ $chainId = new ChainId;
+ $chainId->chainId = $string;
+ return $chainId;
+ }
+
+ static function MAINNET(): ChainId
+ {
+ static $chainId;
+ if( !isset( $chainId ) )
+ $chainId = ChainId::fromString( ChainId::MAINNET );
+ return $chainId;
+ }
+
+ static function TESTNET(): ChainId
+ {
+ static $chainId;
+ if( !isset( $chainId ) )
+ $chainId = ChainId::fromString( ChainId::TESTNET );
+ return $chainId;
+ }
+
+ static function STAGENET(): ChainId
+ {
+ static $chainId;
+ if( !isset( $chainId ) )
+ $chainId = ChainId::fromString( ChainId::STAGENET );
+ return $chainId;
+ }
+
+ static function PRIVATE(): ChainId
+ {
+ static $chainId;
+ if( !isset( $chainId ) )
+ $chainId = ChainId::fromString( ChainId::PRIVATE );
+ return $chainId;
+ }
+
+ function asInt(): int
+ {
+ return ord( $this->chainId );
+ }
+
+ function asString(): string
+ {
+ return $this->chainId;
+ }
+}
diff --git a/src/Model/DataEntry.php b/src/Model/DataEntry.php
new file mode 100644
index 0000000..ea0c55f
--- /dev/null
+++ b/src/Model/DataEntry.php
@@ -0,0 +1,140 @@
+ $key, 'type' => null ];
+ else
+ if( !isset( $value ) )
+ throw new Exception( __FUNCTION__ . ' value expected but not set', ExceptionCode::UNEXPECTED );
+ else
+ if( $type === EntryType::BINARY )
+ $json = [ 'key' => $key, 'type' => 'binary', 'value' => Base64String::fromBytes( Value::as( $value )->asString() )->toString() ];
+ else
+ $json = [ 'key' => $key, 'type' => DataEntry::typeToString( $type ), 'value' => $value ];
+ return new DataEntry( Value::as( $json )->asJson() );
+ }
+
+ static function binary( string $key, string $value ): DataEntry
+ {
+ return DataEntry::build( $key, EntryType::BINARY, $value );
+ }
+
+ static function string( string $key, string $value ): DataEntry
+ {
+ return DataEntry::build( $key, EntryType::STRING, $value );
+ }
+
+ static function int( string $key, int $value ): DataEntry
+ {
+ return DataEntry::build( $key, EntryType::INTEGER, $value );
+ }
+
+ static function boolean( string $key, bool $value ): DataEntry
+ {
+ return DataEntry::build( $key, EntryType::BOOLEAN, $value );
+ }
+
+ static function delete( string $key ): DataEntry
+ {
+ return DataEntry::build( $key, EntryType::DELETE );
+ }
+
+ static function stringToType( string $stringType ): int
+ {
+ switch( $stringType )
+ {
+ case 'binary': return EntryType::BINARY;
+ case 'boolean': return EntryType::BOOLEAN;
+ case 'integer': return EntryType::INTEGER;
+ case 'string': return EntryType::STRING;
+ default: throw new Exception( __FUNCTION__ . ' failed to detect type `' . serialize( $stringType ) . '`', ExceptionCode::UNKNOWN_TYPE );
+ }
+ }
+
+ static function typeToString( int $type ): string
+ {
+ switch( $type )
+ {
+ case EntryType::BINARY: return 'binary';
+ case EntryType::BOOLEAN: return 'boolean';
+ case EntryType::INTEGER: return 'integer';
+ case EntryType::STRING: return 'string';
+ default: throw new Exception( __FUNCTION__ . ' failed to detect type `' . serialize( $type ) . '`', ExceptionCode::UNKNOWN_TYPE );
+ }
+ }
+
+ function key(): string { return $this->json->get( 'key' )->asString(); }
+
+ function type(): int
+ {
+ if( !$this->json->exists( 'type' ) )
+ return EntryType::DELETE;
+ return $this->stringToType( $this->json->get( 'type' )->asString() );
+ }
+
+ /**
+ * Returns value of native type
+ *
+ * @return bool|int|string|null
+ */
+ function value()
+ {
+ switch( $this->type() )
+ {
+ case EntryType::BINARY: return $this->json->get( 'value' )->asBase64Decoded();
+ case EntryType::BOOLEAN: return $this->json->get( 'value' )->asBoolean();
+ case EntryType::INTEGER: return $this->json->get( 'value' )->asInt();
+ case EntryType::STRING: return $this->json->get( 'value' )->asString();
+ case EntryType::DELETE: return null;
+ default: throw new Exception( __FUNCTION__ . ' failed to detect type `' . serialize( $this->type() ) . '`', ExceptionCode::UNKNOWN_TYPE ); // @codeCoverageIgnore
+ }
+ }
+
+ function stringValue(): string
+ {
+ return Value::as( $this->value() )->asString();
+ }
+
+ function intValue(): int
+ {
+ return Value::as( $this->value() )->asInt();
+ }
+
+ function booleanValue(): bool
+ {
+ return Value::as( $this->value() )->asBoolean();
+ }
+
+ function toProtobuf(): \Waves\Protobuf\DataTransactionData\DataEntry
+ {
+ $pb_DataEntry = new \Waves\Protobuf\DataTransactionData\DataEntry;
+ $pb_DataEntry->setKey( $this->key() );
+ switch( $this->type() )
+ {
+ case EntryType::BINARY: $pb_DataEntry->setBinaryValue( $this->json->get( 'value' )->asBase64Decoded() ); break;
+ case EntryType::BOOLEAN: $pb_DataEntry->setBoolValue( $this->json->get( 'value' )->asBoolean() ); break;
+ case EntryType::INTEGER: $pb_DataEntry->setIntValue( $this->json->get( 'value' )->asInt() ); break;
+ case EntryType::STRING: $pb_DataEntry->setStringValue( $this->json->get( 'value' )->asString() ); break;
+ case EntryType::DELETE: break;
+ default: throw new Exception( __FUNCTION__ . ' failed to detect type `' . serialize( $this->type() ) . '`', ExceptionCode::UNKNOWN_TYPE ); // @codeCoverageIgnore
+ }
+ return $pb_DataEntry;
+ }
+}
diff --git a/src/Model/EntryType.php b/src/Model/EntryType.php
new file mode 100644
index 0000000..4320ee2
--- /dev/null
+++ b/src/Model/EntryType.php
@@ -0,0 +1,12 @@
+json->get( 'height' )->asInt(); }
+ function balance(): int { return $this->json->get( 'balance' )->asInt(); }
+}
diff --git a/src/Model/Id.php b/src/Model/Id.php
new file mode 100644
index 0000000..4caa091
--- /dev/null
+++ b/src/Model/Id.php
@@ -0,0 +1,50 @@
+id = Base58String::fromString( $encoded );
+ return $id;
+ }
+
+ static function fromBytes( string $bytes ): Id
+ {
+ if( strlen( $bytes ) !== Id::BYTE_LENGTH )
+ throw new Exception( __FUNCTION__ . ' bad id length: ' . strlen( $bytes ), ExceptionCode::BAD_ASSET );
+ $id = new Id;
+ $id->id = Base58String::fromBytes( $bytes );
+ return $id;
+ }
+
+ function bytes(): string
+ {
+ $bytes = $this->id->bytes();
+ if( strlen( $bytes ) !== Id::BYTE_LENGTH )
+ throw new Exception( __FUNCTION__ . ' bad id length: ' . strlen( $bytes ), ExceptionCode::BAD_ASSET );
+ return $bytes;
+ }
+
+ function encoded(): string
+ {
+ return $this->id->encoded();
+ }
+
+ function toString(): string
+ {
+ return $this->encoded();
+ }
+}
diff --git a/src/Model/LeaseInfo.php b/src/Model/LeaseInfo.php
new file mode 100644
index 0000000..62a839a
--- /dev/null
+++ b/src/Model/LeaseInfo.php
@@ -0,0 +1,29 @@
+json->get( 'id' )->asId(); }
+ function originTransactionId(): Id { return $this->json->get( 'originTransactionId' )->asId(); }
+ function sender(): Address { return $this->json->get( 'sender' )->asAddress(); }
+ function recipient(): Recipient { return $this->json->get( 'recipient' )->asRecipient(); }
+ function amount(): int { return $this->json->get( 'amount' )->asInt(); }
+ function height(): int { return $this->json->get( 'height' )->asInt(); }
+ function status(): int
+ {
+ $status = $this->json->getOr( 'status', LeaseStatus::UNKNOWN_S )->asString();
+ switch( $status )
+ {
+ case LeaseStatus::ACTIVE_S: return LeaseStatus::ACTIVE;
+ case LeaseStatus::CANCELED_S: return LeaseStatus::CANCELED;
+ default: return LeaseStatus::UNKNOWN;
+ }
+ }
+ function cancelHeight(): int { return $this->json->get( 'cancelHeight' )->asInt(); }
+ function cancelTransactionId(): Id { return $this->json->get( 'cancelTransactionId' )->asId(); }
+}
diff --git a/src/Model/LeaseStatus.php b/src/Model/LeaseStatus.php
new file mode 100644
index 0000000..3a94df2
--- /dev/null
+++ b/src/Model/LeaseStatus.php
@@ -0,0 +1,13 @@
+ '', 'scriptComplexity' => 0 ];
+
+ function script(): Base64String { return $this->json->get( 'script' )->asBase64String(); }
+ function complexity(): int { return $this->json->get( 'scriptComplexity' )->asInt(); }
+}
diff --git a/src/Model/ScriptInfo.php b/src/Model/ScriptInfo.php
new file mode 100644
index 0000000..2fd14ad
--- /dev/null
+++ b/src/Model/ScriptInfo.php
@@ -0,0 +1,20 @@
+json->get( 'script' )->asBase64String(); }
+ function complexity(): int { return $this->json->get( 'complexity' )->asInt(); }
+ function verifierComplexity(): int { return $this->json->get( 'verifierComplexity' )->asInt(); }
+ function extraFee(): int { return $this->json->get( 'extraFee' )->asInt(); }
+ /**
+ * Gets a map of callable functions with their complexities
+ *
+ * @return array
+ */
+ function callableComplexities(): array { return $this->json->get( 'callableComplexities' )->asMapStringInt(); }
+}
diff --git a/src/Model/ScriptMeta.php b/src/Model/ScriptMeta.php
new file mode 100644
index 0000000..8224489
--- /dev/null
+++ b/src/Model/ScriptMeta.php
@@ -0,0 +1,32 @@
+json->get( 'version' )->asInt(); }
+ /**
+ * Gets a map of callable functions with their arguments as ArgMeta
+ *
+ * @return array>
+ */
+ function callableFunctions(): array
+ {
+ $map = [];
+ $arrayFuncs = $this->json->get( 'callableFuncTypes' )->asArray();
+ foreach( $arrayFuncs as $key => $value )
+ {
+ $function = Value::as( $key )->asString();
+ $args = [];
+ $arrayArgs = Value::as( $value )->asArray();
+ foreach( $arrayArgs as $arg )
+ $args[] = Value::as( $arg )->asArgMeta();
+ $map[$function] = $args;
+ }
+
+ return $map;
+ }
+}
diff --git a/src/Model/Status.php b/src/Model/Status.php
new file mode 100644
index 0000000..09ceaed
--- /dev/null
+++ b/src/Model/Status.php
@@ -0,0 +1,15 @@
+json->get( 'height' )->asInt(); }
+}
diff --git a/src/Model/TransactionStatus.php b/src/Model/TransactionStatus.php
new file mode 100644
index 0000000..867d031
--- /dev/null
+++ b/src/Model/TransactionStatus.php
@@ -0,0 +1,14 @@
+json->get( 'id' )->asId(); }
+ function status(): int { return $this->json->get( 'status' )->asStatus(); }
+ function applicationStatus(): int { return $this->json->get( 'applicationStatus' )->asApplicationStatus(); }
+ function height(): int { return $this->json->getOr( 'height', 0 )->asInt(); }
+ function confirmations(): int { return $this->json->getOr( 'confirmations', 0 )->asInt(); }
+}
diff --git a/src/Model/TransactionWithStatus.php b/src/Model/TransactionWithStatus.php
new file mode 100644
index 0000000..ca29c3a
--- /dev/null
+++ b/src/Model/TransactionWithStatus.php
@@ -0,0 +1,13 @@
+json->getOr( 'applicationStatus', ApplicationStatus::SUCCEEDED_S )->asApplicationStatus();
+ }
+}
diff --git a/src/Model/Validation.php b/src/Model/Validation.php
new file mode 100644
index 0000000..dbf69f2
--- /dev/null
+++ b/src/Model/Validation.php
@@ -0,0 +1,12 @@
+json->get( 'valid' )->asBoolean(); }
+ function validationTime(): int { return $this->json->get( 'validationTime' )->asInt(); }
+ function error(): string { return $this->json->get( 'error' )->asString(); }
+}
diff --git a/src/Model/Votes.php b/src/Model/Votes.php
new file mode 100644
index 0000000..d348278
--- /dev/null
+++ b/src/Model/Votes.php
@@ -0,0 +1,11 @@
+json->get( 'increase' )->asInt(); }
+ function decrease(): int { return $this->json->get( 'decrease' )->asInt(); }
+}
diff --git a/src/Model/WavesConfig.php b/src/Model/WavesConfig.php
new file mode 100644
index 0000000..e1db042
--- /dev/null
+++ b/src/Model/WavesConfig.php
@@ -0,0 +1,17 @@
+amount = $amount;
+ $this->assetId = $assetId ?? AssetId::WAVES();
+ }
+
+ static function of( int $amount, AssetId $assetId = null ): Amount
+ {
+ return new Amount( $amount, $assetId );
+ }
+
+ static function fromJson( Json $json, string $amountKey = 'amount', string $assetIdKey = ' assetId' ): Amount
+ {
+ return Amount::of( $json->get( $amountKey )->asInt(), $json->getOr( $assetIdKey, AssetId::WAVES_STRING )->asAssetId() );
+ }
+
+ function value(): int
+ {
+ return $this->amount;
+ }
+
+ function assetId(): AssetId
+ {
+ return $this->assetId;
+ }
+
+ function toString(): string
+ {
+ return serialize( $this );
+ }
+
+ function toProtobuf(): \Waves\Protobuf\Amount
+ {
+ $pb_Amount = new \Waves\Protobuf\Amount;
+ $pb_Amount->setAmount( $this->value() );
+ if( !$this->assetId()->isWaves() )
+ $pb_Amount->setAssetId( $this->assetId()->bytes() );
+ return $pb_Amount;
+ }
+}
diff --git a/src/Transactions/BurnTransaction.php b/src/Transactions/BurnTransaction.php
new file mode 100644
index 0000000..4c95415
--- /dev/null
+++ b/src/Transactions/BurnTransaction.php
@@ -0,0 +1,166 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // BURN TRANSACTION
+ {
+ $tx->setAmount( $amount );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // BURN TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\BurnTransactionData;
+ // AMOUNT
+ {
+ $pb_TransactionData->setAssetAmount( $this->amount()->toProtobuf() );
+ }
+ }
+
+ // BURN TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setBurn( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function amount(): Amount
+ {
+ if( !isset( $this->amount ) )
+ $this->amount = Amount::fromJson( $this->json, 'quantity' );
+ return $this->amount;
+ }
+
+ function setAmount( Amount $amount ): CurrentTransaction
+ {
+ $this->amount = $amount;
+ $this->json->put( 'quantity', $amount->value() );
+ $this->json->put( 'assetId', $amount->assetId()->toJsonValue() );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/CreateAliasTransaction.php b/src/Transactions/CreateAliasTransaction.php
new file mode 100644
index 0000000..fde39ab
--- /dev/null
+++ b/src/Transactions/CreateAliasTransaction.php
@@ -0,0 +1,166 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // ALIAS TRANSACTION
+ {
+ $tx->setAlias( $alias );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // ALIAS TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\CreateAliasTransactionData;
+ // ID
+ {
+ $pb_TransactionData->setAlias( $this->alias()->name() );
+ }
+ }
+
+ // ALIAS TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setCreateAlias( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function alias(): Alias
+ {
+ if( !isset( $this->alias ) )
+ $this->alias = Alias::fromString( $this->json->get( 'alias' )->asString() );
+ return $this->alias;
+ }
+
+ function setAlias( Alias $alias ): CurrentTransaction
+ {
+ $this->alias = $alias;
+ $this->json->put( 'alias', $alias->name() );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/DataTransaction.php b/src/Transactions/DataTransaction.php
new file mode 100644
index 0000000..6d11e4e
--- /dev/null
+++ b/src/Transactions/DataTransaction.php
@@ -0,0 +1,196 @@
+
+ */
+ private array $data;
+
+ /**
+ * @param PublicKey $sender
+ * @param array $data
+ * @return CurrentTransaction
+ */
+ static function build( PublicKey $sender, array $data ): CurrentTransaction
+ {
+ $tx = new CurrentTransaction;
+ $tx->setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // DATA TRANSACTION
+ {
+ $tx->setData( $data );
+ }
+
+ // ADDITIONAL FEE CALCULATION
+ $tx->setFee( Amount::of( CurrentTransaction::calculateFee( strlen( $tx->bodyBytes() ) ) ) );
+
+ return $tx;
+ }
+
+ static function calculateFee( int $bodyBytesLen ): int
+ {
+ return 100_000 * ( 1 + intdiv( $bodyBytesLen - 1, 1024 ) );
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // DATA TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\DataTransactionData;
+ // DATA
+ {
+ $pb_Data = [];
+ foreach( $this->data() as $dataEntry )
+ $pb_Data[] = $dataEntry->toProtobuf();
+ $pb_TransactionData->setData( $pb_Data );
+ }
+ }
+
+ // DATA TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setDataTransaction( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ function data(): array
+ {
+ if( !isset( $this->data ) )
+ $this->data = $this->json->get( 'data' )->asJson()->asArrayDataEntry();
+ return $this->data;
+ }
+
+ /**
+ * @param array $data
+ * @return CurrentTransaction
+ */
+ function setData( array $data ): CurrentTransaction
+ {
+ $this->data = $data;
+
+ $data = [];
+ foreach( $this->data as $dataEntry )
+ $data[] = $dataEntry->json()->data();
+ $this->json->put( 'data', $data );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/Invocation/Arg.php b/src/Transactions/Invocation/Arg.php
new file mode 100644
index 0000000..7888d8c
--- /dev/null
+++ b/src/Transactions/Invocation/Arg.php
@@ -0,0 +1,127 @@
+type = $type;
+ $arg->value = $value;
+ return $arg;
+ }
+
+ static function fromJson( Json $json ): Arg
+ {
+ $type = Arg::stringToType( $json->get( 'type' )->asString() );
+ if( $type === Arg::LIST )
+ {
+ $args = [];
+ foreach( $json->get( 'value' )->asArray() as $arg )
+ $args[] = Arg::fromJson( Value::as( $arg )->asJson() );
+ $value = Value::as( $args );
+ }
+ else
+ if( $type === Arg::BINARY )
+ {
+ $value = Value::as( $json->get( 'value' )->asBase64Decoded() );
+ }
+ else
+ {
+ $value = $json->get( 'value' );
+ }
+
+ return Arg::as( $type, $value );
+ }
+
+ static function stringToType( string $stringType ): int
+ {
+ switch( $stringType )
+ {
+ case 'binary': return Arg::BINARY;
+ case 'boolean': return Arg::BOOLEAN;
+ case 'integer': return Arg::INTEGER;
+ case 'string': return Arg::STRING;
+ case 'list': return Arg::LIST;
+ default: throw new Exception( __FUNCTION__ . ' failed to detect type `' . serialize( $stringType ) . '`', ExceptionCode::UNKNOWN_TYPE );
+ }
+ }
+
+ static function typeToString( int $type ): string
+ {
+ switch( $type )
+ {
+ case Arg::BINARY: return 'binary';
+ case Arg::BOOLEAN: return 'boolean';
+ case Arg::INTEGER: return 'integer';
+ case Arg::STRING: return 'string';
+ case Arg::LIST: return 'list';
+ default: throw new Exception( __FUNCTION__ . ' failed to detect type `' . serialize( $type ) . '`', ExceptionCode::UNKNOWN_TYPE );
+ }
+ }
+
+ function type(): int
+ {
+ return $this->type;
+ }
+
+ function typeAsString(): string
+ {
+ return Arg::typeToString( $this->type() );
+ }
+
+ function value(): Value
+ {
+ return $this->value;
+ }
+
+ /**
+ * @return mixed
+ */
+ private function valueAsJson()
+ {
+ switch( $this->type() )
+ {
+ case Arg::BINARY: return Base64String::fromBytes( $this->value()->asString() )->encodedWithPrefix();
+ case Arg::BOOLEAN: return $this->value()->asBoolean();
+ case Arg::INTEGER: return $this->value()->asInt();
+ case Arg::STRING: return $this->value()->asString();
+ case Arg::LIST:
+ {
+ $values = [];
+ foreach( $this->value()->asArray() as $arg )
+ {
+ if( !( $arg instanceof Arg ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect Arg class', ExceptionCode::UNEXPECTED );
+ $values[] = $arg->toJsonValue();
+ }
+ return $values;
+ }
+ default: throw new Exception( __FUNCTION__ . ' failed to detect type `' . serialize( $this->type() ) . '`', ExceptionCode::UNKNOWN_TYPE );
+ }
+ }
+
+ /**
+ * @return array
+ */
+ function toJsonValue(): array
+ {
+ return [ 'type' => $this->typeAsString(), 'value' => $this->valueAsJson() ];
+ }
+}
diff --git a/src/Transactions/Invocation/FunctionCall.php b/src/Transactions/Invocation/FunctionCall.php
new file mode 100644
index 0000000..ea5fe6c
--- /dev/null
+++ b/src/Transactions/Invocation/FunctionCall.php
@@ -0,0 +1,128 @@
+
+ */
+ private array $args;
+
+ /**
+ * @param string|null $name
+ * @param array|null $args
+ * @return FunctionCall
+ */
+ static function as( string $name = null, array $args = null ): FunctionCall
+ {
+ $func = new FunctionCall;
+ $func->name = $name ?? FunctionCall::DEFAULT_NAME;
+ $func->args = $args ?? [];
+ return $func;
+ }
+
+ static function fromJson( Json $json ): FunctionCall
+ {
+ $name = $json->getOr( 'function', FunctionCall::DEFAULT_NAME )->asString();
+ $args = [];
+ foreach( $json->get( 'args' )->asArray() as $arg )
+ $args[] = Arg::fromJson( Value::as( $arg )->asJson() );
+ return FunctionCall::as( $name, $args );
+ }
+
+ function name(): string
+ {
+ return $this->name;
+ }
+
+ function isDefault(): bool
+ {
+ return $this->name == FunctionCall::DEFAULT_NAME;
+ }
+
+ /**
+ * @return array
+ */
+ function args(): array
+ {
+ return $this->args;
+ }
+
+ /**
+ * @return array
+ */
+ function toJsonValue(): array
+ {
+ $args = [];
+ foreach( $this->args() as $arg )
+ $args[] = $arg->toJsonValue();
+ return
+ [
+ 'function' => $this->name(),
+ 'args' => $args,
+ ];
+ }
+
+ /**
+ * @param array $args
+ * @return string
+ */
+ static function argsBodyBytes( array $args ): string
+ {
+ $bytes = pack( 'N', count( $args ) );
+ foreach( $args as $arg )
+ {
+ if( !( $arg instanceof Arg ) )
+ throw new Exception( __FUNCTION__ . ' failed to detect Arg class', ExceptionCode::UNEXPECTED );
+ $value = $arg->value();
+ switch( $arg->type() )
+ {
+ case Arg::INTEGER:
+ $bytes .= chr( 0 ) . pack( 'J', $value->asInt() );
+ break;
+
+ case Arg::BINARY:
+ $value = $value->asString();
+ $bytes .= chr( 1 ) . pack( 'N', strlen( $value ) ) . $value;
+ break;
+
+ case Arg::STRING:
+ $value = $value->asString();
+ $bytes .= chr( 2 ) . pack( 'N', strlen( $value ) ) . $value;
+ break;
+
+ case Arg::BOOLEAN:
+ $bytes .= chr( $value->asBoolean() ? 6 : 7 );
+ break;
+
+ case Arg::LIST:
+ $bytes .= chr( 11 ) . FunctionCall::argsBodyBytes( $value->asArray() );
+ break;
+
+ default:
+ throw new Exception( __FUNCTION__ . ' failed to detect type `' . serialize( $arg->type() ) . '`', ExceptionCode::UNKNOWN_TYPE );
+ }
+ }
+ return $bytes;
+ }
+
+ function toBodyBytes(): string
+ {
+ if( $this->isDefault() )
+ return chr( 0 );
+
+ $bytes = chr( 1 ) . chr( 9 ). chr( 1 );
+ $bytes .= pack( 'N', strlen( $this->name() ) ) . $this->name();
+ $bytes .= FunctionCall::argsBodyBytes( $this->args() );
+ return $bytes;
+ }
+}
diff --git a/src/Transactions/InvokeScriptTransaction.php b/src/Transactions/InvokeScriptTransaction.php
new file mode 100644
index 0000000..3b35e0b
--- /dev/null
+++ b/src/Transactions/InvokeScriptTransaction.php
@@ -0,0 +1,238 @@
+
+ */
+ private array $payments;
+
+ /**
+ * @param PublicKey $sender
+ * @param Recipient $dApp
+ * @param FunctionCall|null $function
+ * @param array|null $payments
+ * @return CurrentTransaction
+ */
+ static function build( PublicKey $sender, Recipient $dApp, FunctionCall $function = null, array $payments = null ): CurrentTransaction
+ {
+ $tx = new CurrentTransaction;
+ $tx->setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // INVOKE TRANSACTION
+ {
+ $tx->setDApp( $dApp );
+ $tx->setFunction( $function );
+ $tx->setPayments( $payments );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // INVOKE TRANSACTION
+ {
+
+ $pb_TransactionData = new \Waves\Protobuf\InvokeScriptTransactionData;
+ // DAPP
+ {
+ $pb_TransactionData->setDApp( $this->dApp()->toProtobuf() );
+ }
+ // FUNCTION
+ {
+ $pb_TransactionData->setFunctionCall( $this->function()->toBodyBytes() );
+ }
+ // PAYMENTS
+ {
+ $pb_Payments = [];
+ foreach( $this->payments() as $payment )
+ $pb_Payments[] = $payment->toProtobuf();
+ $pb_TransactionData->setPayments( $pb_Payments );
+ }
+ }
+
+ // INVOKE TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setInvokeScript( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function dApp(): Recipient
+ {
+ if( !isset( $this->dApp ) )
+ $this->dApp = $this->json->get( 'dApp' )->asRecipient();
+ return $this->dApp;
+ }
+
+ function setDApp( Recipient $dApp ): CurrentTransaction
+ {
+ $this->dApp = $dApp;
+ $this->json->put( 'dApp', $dApp->toString() );
+ return $this;
+ }
+
+ function function(): FunctionCall
+ {
+ if( !isset( $this->function ) )
+ $this->function = FunctionCall::fromJson( $this->json->get( 'call' )->asJson() );
+ return $this->function;
+ }
+
+ function setFunction( FunctionCall $function = null ): CurrentTransaction
+ {
+ $function = $function ?? FunctionCall::as();
+ $this->function = $function;
+ $this->json->put( 'call', $function->toJsonValue() );
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ function payments(): array
+ {
+ if( !isset( $this->payments ) )
+ {
+ $payments = [];
+ foreach( $this->json->get( 'payment' )->asArray() as $value )
+ $payments[] = Amount::fromJson( Value::as( $value )->asJson() );
+ $this->payments = $payments;
+ }
+ return $this->payments;
+ }
+
+ /**
+ * @param array|null $payments
+ * @return CurrentTransaction
+ */
+ function setPayments( array $payments = null ): CurrentTransaction
+ {
+ $this->payments = $payments ?? [];
+
+ $payments = [];
+ foreach( $this->payments as $payment )
+ $payments[] = [ 'amount' => $payment->value(), 'assetId' => $payment->assetId()->toJsonValue() ];
+ $this->json->put( 'payment', $payments );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/IssueTransaction.php b/src/Transactions/IssueTransaction.php
new file mode 100644
index 0000000..aa9aaf5
--- /dev/null
+++ b/src/Transactions/IssueTransaction.php
@@ -0,0 +1,271 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, $minFee );
+
+ // ISSUE TRANSACTION
+ {
+ $tx->setName( $name );
+ $tx->setDescription( $description );
+ $tx->setQuantity( $quantity );
+ $tx->setDecimals( $decimals );
+ $tx->setIsReissuable( $isReissuable );
+ $tx->setScript( $script );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // ISSUE TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\IssueTransactionData;
+ // NAME
+ {
+ $pb_TransactionData->setName( $this->name() );
+ }
+ // DESCRIPTION
+ {
+ $pb_TransactionData->setDescription( $this->description() );
+ }
+ // QUANTITY
+ {
+ $pb_TransactionData->setAmount( $this->quantity() );
+ }
+ // DECIMALS
+ {
+ $pb_TransactionData->setDecimals( $this->decimals() );
+ }
+ // REISSUABLE
+ {
+ $pb_TransactionData->setReissuable( $this->isReissuable() );
+ }
+ // SCRIPT
+ {
+ $pb_TransactionData->setScript( $this->script()->bytes() );
+ }
+ }
+
+ // ISSUE TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setIssue( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function name(): string
+ {
+ if( !isset( $this->name ) )
+ $this->name = $this->json->get( 'name' )->asString();
+ return $this->name;
+ }
+
+ function setName( string $name ): CurrentTransaction
+ {
+ $this->name = $name;
+ $this->json->put( 'name', $name );
+ return $this;
+ }
+
+ function description(): string
+ {
+ if( !isset( $this->description ) )
+ $this->description = $this->json->get( 'description' )->asString();
+ return $this->description;
+ }
+
+ function setDescription( string $description ): CurrentTransaction
+ {
+ $this->description = $description;
+ $this->json->put( 'description', $description );
+ return $this;
+ }
+
+ function quantity(): int
+ {
+ if( !isset( $this->quantity ) )
+ $this->quantity = $this->json->get( 'quantity' )->asInt();
+ return $this->quantity;
+ }
+
+ function setQuantity( int $quantity ): CurrentTransaction
+ {
+ $this->quantity = $quantity;
+ $this->json->put( 'quantity', $quantity );
+ return $this;
+ }
+
+ function decimals(): int
+ {
+ if( !isset( $this->decimals ) )
+ $this->decimals = $this->json->get( 'decimals' )->asInt();
+ return $this->decimals;
+ }
+
+ function setDecimals( int $decimals ): CurrentTransaction
+ {
+ $this->decimals = $decimals;
+ $this->json->put( 'decimals', $decimals );
+ return $this;
+ }
+
+ function isReissuable(): bool
+ {
+ if( !isset( $this->isReissuable ) )
+ $this->isReissuable = $this->json->get( 'reissuable' )->asBoolean();
+ return $this->isReissuable;
+ }
+
+ function setIsReissuable( bool $isReissuable ): CurrentTransaction
+ {
+ $this->isReissuable = $isReissuable;
+ $this->json->put( 'reissuable', $isReissuable );
+ return $this;
+ }
+
+ function script(): Base64String
+ {
+ if( !isset( $this->script ) )
+ $this->script = $this->json->exists( 'script' ) ? $this->json->get( 'script' )->asBase64String() : Base64String::emptyString();
+ return $this->script;
+ }
+
+ function setScript( Base64String $script = null ): CurrentTransaction
+ {
+ $script = $script ?? Base64String::emptyString();
+ $this->script = $script;
+ $this->json->put( 'script', $script->toJsonValue() );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/LeaseCancelTransaction.php b/src/Transactions/LeaseCancelTransaction.php
new file mode 100644
index 0000000..6e38e0d
--- /dev/null
+++ b/src/Transactions/LeaseCancelTransaction.php
@@ -0,0 +1,166 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // LEASE_CANCEL TRANSACTION
+ {
+ $tx->setLeaseId( $leaseId );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // LEASE_CANCEL TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\LeaseCancelTransactionData;
+ // ID
+ {
+ $pb_TransactionData->setLeaseId( $this->leaseId()->bytes() );
+ }
+ }
+
+ // LEASE_CANCEL TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setLeaseCancel( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function leaseId(): Id
+ {
+ if( !isset( $this->leaseId ) )
+ $this->leaseId = $this->json->get( 'leaseId' )->asId();
+ return $this->leaseId;
+ }
+
+ function setLeaseId( Id $leaseId ): CurrentTransaction
+ {
+ $this->leaseId = $leaseId;
+ $this->json->put( 'leaseId', $leaseId->toString() );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/LeaseTransaction.php b/src/Transactions/LeaseTransaction.php
new file mode 100644
index 0000000..a88f18b
--- /dev/null
+++ b/src/Transactions/LeaseTransaction.php
@@ -0,0 +1,185 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // LEASE TRANSACTION
+ {
+ $tx->setRecipient( $recipient );
+ $tx->setAmount( $amount );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // LEASE TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\LeaseTransactionData;
+ // RECIPIENT
+ {
+ $pb_TransactionData->setRecipient( $this->recipient()->toProtobuf() );
+ }
+ // AMOUNT
+ {
+ $pb_TransactionData->setAmount( $this->amount() );
+ }
+ }
+
+ // LEASE TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setLease( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function recipient(): Recipient
+ {
+ if( !isset( $this->recipient ) )
+ $this->recipient = $this->json->get( 'recipient' )->asRecipient();
+ return $this->recipient;
+ }
+
+ function setRecipient( Recipient $recipient ): CurrentTransaction
+ {
+ $this->recipient = $recipient;
+ $this->json->put( 'recipient', $recipient->toString() );
+ return $this;
+ }
+
+ function amount(): int
+ {
+ if( !isset( $this->amount ) )
+ $this->amount = $this->json->get( 'amount' )->asInt();
+ return $this->amount;
+ }
+
+ function setAmount( int $amount ): CurrentTransaction
+ {
+ $this->amount = $amount;
+ $this->json->put( 'amount', $amount );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/Mass/Transfer.php b/src/Transactions/Mass/Transfer.php
new file mode 100644
index 0000000..3a1625b
--- /dev/null
+++ b/src/Transactions/Mass/Transfer.php
@@ -0,0 +1,27 @@
+recipient = $recipient;
+ $this->amount = $amount;
+ }
+
+ function recipient(): Recipient
+ {
+ return $this->recipient;
+ }
+
+ function amount(): int
+ {
+ return $this->amount;
+ }
+}
diff --git a/src/Transactions/MassTransferTransaction.php b/src/Transactions/MassTransferTransaction.php
new file mode 100644
index 0000000..8716a8f
--- /dev/null
+++ b/src/Transactions/MassTransferTransaction.php
@@ -0,0 +1,254 @@
+
+ */
+ private array $transfers;
+ private AssetId $assetId;
+ private Base58String $attachment;
+
+ /**
+ * @param PublicKey $sender
+ * @param AssetId $assetId
+ * @param array $transfers
+ * @param Base58String $attachment
+ * @return CurrentTransaction
+ */
+ static function build( PublicKey $sender, AssetId $assetId, array $transfers, Base58String $attachment = null ): CurrentTransaction
+ {
+ $tx = new CurrentTransaction;
+ $tx->setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::calculateFee( count( $transfers ) ) );
+
+ // MASS_TRANSFER TRANSACTION
+ {
+ $tx->setAssetId( $assetId );
+ $tx->setTransfers( $transfers );
+ $tx->setAttachment( $attachment );
+ }
+
+ return $tx;
+ }
+
+ static function calculateFee( int $transfersCount ): int
+ {
+ return 100_000 + ( $transfersCount + ( $transfersCount & 1 ) ) * 50_000;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // MASS_TRANSFER TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\MassTransferTransactionData;
+ // TRANSFERS
+ {
+ $pb_Transfers = [];
+ foreach( $this->transfers() as $transfer )
+ {
+ $pb_Transfer = new \Waves\Protobuf\MassTransferTransactionData\Transfer;
+ $pb_Transfer->setRecipient( $transfer->recipient()->toProtobuf() );
+ $pb_Transfer->setAmount( $transfer->amount() );
+ $pb_Transfers[] = $pb_Transfer;
+ }
+
+ $pb_TransactionData->setTransfers( $pb_Transfers );
+ }
+ // ASSET
+ {
+ $pb_TransactionData->setAssetId( $this->assetId()->bytes() );
+ }
+ // ATTACHMENT
+ {
+ $pb_TransactionData->setAttachment( $this->attachment()->bytes() );
+ }
+ }
+
+ // MASS_TRANSFER TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setMassTransfer( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function assetId(): AssetId
+ {
+ if( !isset( $this->assetId ) )
+ $this->assetId = $this->json->get( 'assetId' )->asAssetId();
+ return $this->assetId;
+ }
+
+ function setAssetId( AssetId $assetId ): CurrentTransaction
+ {
+ $this->assetId = $assetId;
+ $this->json->put( 'assetId', $assetId->toJsonValue() );
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ function transfers(): array
+ {
+ if( !isset( $this->transfers ) )
+ {
+ $transfers = [];
+ foreach( $this->json->get( 'amount' )->asArray() as $value )
+ {
+ $json = Value::as( $value )->asJson();
+ $recipient = $json->get( 'recipient' )->asRecipient();
+ $amount = $json->get( 'amount' )->asInt();
+ $transfers[] = new Transfer( $recipient, $amount );
+ }
+ $this->transfers = $transfers;
+ }
+ return $this->transfers;
+ }
+
+ /**
+ * @param array $transfers
+ * @return CurrentTransaction
+ */
+ function setTransfers( array $transfers ): CurrentTransaction
+ {
+ $this->transfers = $transfers;
+
+ $transfers = [];
+ foreach( $this->transfers as $transfer )
+ $transfers[] = [ 'recipient' => $transfer->recipient()->toString(), 'amount' => $transfer->amount() ];
+ $this->json->put( 'transfers', $transfers );
+ return $this;
+ }
+
+ function attachment(): Base58String
+ {
+ if( !isset( $this->attachment ) )
+ $this->attachment = $this->json->get( 'attachment' )->asBase58String();
+ return $this->attachment;
+ }
+
+ function setAttachment( Base58String $attachment = null ): CurrentTransaction
+ {
+ $attachment = $attachment ?? Base58String::emptyString();
+ $this->attachment = $attachment;
+ $this->json->put( 'attachment', $attachment->toString() );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/Recipient.php b/src/Transactions/Recipient.php
new file mode 100644
index 0000000..d137ab4
--- /dev/null
+++ b/src/Transactions/Recipient.php
@@ -0,0 +1,76 @@
+address = $address;
+ return $recipient;
+ }
+
+ static function fromAlias( Alias $alias ): Recipient
+ {
+ $recipient = new Recipient;
+ $recipient->alias = $alias;
+ return $recipient;
+ }
+
+ static function fromAddressOrAlias( string $addressOrAlias ): Recipient
+ {
+ if( strlen( $addressOrAlias ) === Address::STRING_LENGTH )
+ return Recipient::fromAddress( Address::fromString( $addressOrAlias ) );
+ try
+ {
+ return Recipient::fromAlias( Alias::fromFullAlias( $addressOrAlias ) );
+ }
+ catch( Exception $e )
+ {
+ return Recipient::fromAlias( Alias::fromString( $addressOrAlias ) );
+ }
+ }
+
+ function isAlias(): bool
+ {
+ return isset( $this->alias );
+ }
+
+ function toString(): string
+ {
+ if( $this->isAlias() )
+ return $this->alias->toString();
+ return $this->address->toString();
+ }
+
+ function address(): Address
+ {
+ return $this->address;
+ }
+
+ function alias(): Alias
+ {
+ return $this->alias;
+ }
+
+ function toProtobuf(): \Waves\Protobuf\Recipient
+ {
+ $pb_Recipient = new \Waves\Protobuf\Recipient;
+ if( $this->isAlias() )
+ $pb_Recipient->setAlias( $this->alias()->name() );
+ else
+ $pb_Recipient->setPublicKeyHash( $this->address()->publicKeyHash() );
+ return $pb_Recipient;
+ }
+}
diff --git a/src/Transactions/ReissueTransaction.php b/src/Transactions/ReissueTransaction.php
new file mode 100644
index 0000000..5b381f2
--- /dev/null
+++ b/src/Transactions/ReissueTransaction.php
@@ -0,0 +1,186 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // REISSUE TRANSACTION
+ {
+ $tx->setAmount( $amount );
+ $tx->setIsReissuable( $isReissuable );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // REISSUE TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\ReissueTransactionData;
+ // AMOUNT
+ {
+ $pb_TransactionData->setAssetAmount( $this->amount()->toProtobuf() );
+ }
+ // REISSUABLE
+ {
+ $pb_TransactionData->setReissuable( $this->isReissuable() );
+ }
+ }
+
+ // REISSUE TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setReissue( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function amount(): Amount
+ {
+ if( !isset( $this->amount ) )
+ $this->amount = Amount::fromJson( $this->json, 'quantity' );
+ return $this->amount;
+ }
+
+ function setAmount( Amount $amount ): CurrentTransaction
+ {
+ $this->amount = $amount;
+ $this->json->put( 'quantity', $amount->value() );
+ $this->json->put( 'assetId', $amount->assetId()->toJsonValue() );
+ return $this;
+ }
+
+ function isReissuable(): bool
+ {
+ if( !isset( $this->isReissuable ) )
+ $this->isReissuable = $this->json->get( 'reissuable' )->asBoolean();
+ return $this->isReissuable;
+ }
+
+ function setIsReissuable( bool $isReissuable ): CurrentTransaction
+ {
+ $this->isReissuable = $isReissuable;
+ $this->json->put( 'reissuable', $isReissuable );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/SetAssetScriptTransaction.php b/src/Transactions/SetAssetScriptTransaction.php
new file mode 100644
index 0000000..8cbc245
--- /dev/null
+++ b/src/Transactions/SetAssetScriptTransaction.php
@@ -0,0 +1,188 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // SET_ASSET_SCRIPT TRANSACTION
+ {
+ $tx->setAssetId( $assetId );
+ $tx->setScript( $script );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // SET_ASSET_SCRIPT TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\SetAssetScriptTransactionData;
+ // ASSET
+ {
+ $pb_TransactionData->setAssetId( $this->assetId()->bytes() );
+ }
+ // SCRIPT
+ {
+ $pb_TransactionData->setScript( $this->script()->bytes() );
+ }
+ }
+
+ // SET_ASSET_SCRIPT TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setSetAssetScript( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function assetId(): AssetId
+ {
+ if( !isset( $this->assetId ) )
+ $this->assetId = $this->json->get( 'assetId' )->asAssetId();
+ return $this->assetId;
+ }
+
+ function setAssetId( AssetId $assetId ): CurrentTransaction
+ {
+ $this->assetId = $assetId;
+ $this->json->put( 'assetId', $assetId->toJsonValue() );
+ return $this;
+ }
+
+ function script(): Base64String
+ {
+ if( !isset( $this->script ) )
+ $this->script = $this->json->exists( 'script' ) ? $this->json->get( 'script' )->asBase64String() : Base64String::emptyString();
+ return $this->script;
+ }
+
+ function setScript( Base64String $script = null ): CurrentTransaction
+ {
+ $script = $script ?? Base64String::emptyString();
+ $this->script = $script;
+ $this->json->put( 'script', $script->toJsonValue() );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/SetScriptTransaction.php b/src/Transactions/SetScriptTransaction.php
new file mode 100644
index 0000000..814479b
--- /dev/null
+++ b/src/Transactions/SetScriptTransaction.php
@@ -0,0 +1,175 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // SET_SCRIPT TRANSACTION
+ {
+ $tx->setScript( $script );
+ }
+
+ // ADDITIONAL FEE CALCULATION
+ $tx->setFee( Amount::of( CurrentTransaction::calculateFee( strlen( $tx->bodyBytes() ) ) ) );
+
+ return $tx;
+ }
+
+ static function calculateFee( int $bodyBytesLen ): int
+ {
+ return 100_000 * ( 1 + intdiv( $bodyBytesLen - 1, 1024 ) );
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // SET_SCRIPT TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\SetScriptTransactionData;
+ // SCRIPT
+ {
+ $pb_TransactionData->setScript( $this->script()->bytes() );
+ }
+ }
+
+ // SET_SCRIPT TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setSetScript( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function script(): Base64String
+ {
+ if( !isset( $this->script ) )
+ $this->script = $this->json->exists( 'script' ) ? $this->json->get( 'script' )->asBase64String() : Base64String::emptyString();
+ return $this->script;
+ }
+
+ function setScript( Base64String $script = null ): CurrentTransaction
+ {
+ $script = $script ?? Base64String::emptyString();
+ $this->script = $script;
+ $this->json->put( 'script', $script->toJsonValue() );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/SponsorFeeTransaction.php b/src/Transactions/SponsorFeeTransaction.php
new file mode 100644
index 0000000..8f3d768
--- /dev/null
+++ b/src/Transactions/SponsorFeeTransaction.php
@@ -0,0 +1,182 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // SPONSORSHIP TRANSACTION
+ {
+ $tx->setAssetId( $assetId );
+ $tx->setMinSponsoredFee( $minSponsoredFee );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // SPONSORSHIP TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\SponsorFeeTransactionData;
+ // MINFEE
+ {
+ $pb_TransactionData->setMinFee( Amount::of( $this->minSponsoredFee(), $this->assetId() )->toProtobuf() );
+ }
+ }
+
+ // SPONSORSHIP TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setSponsorFee( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function assetId(): AssetId
+ {
+ if( !isset( $this->assetId ) )
+ $this->assetId = $this->json->get( 'assetId' )->asAssetId();
+ return $this->assetId;
+ }
+
+ function setAssetId( AssetId $assetId ): CurrentTransaction
+ {
+ $this->assetId = $assetId;
+ $this->json->put( 'assetId', $assetId->toJsonValue() );
+ return $this;
+ }
+
+ function minSponsoredFee(): int
+ {
+ if( !isset( $this->minSponsoredFee ) )
+ $this->minSponsoredFee = $this->json->get( 'minSponsoredAssetFee' )->asInt();
+ return $this->minSponsoredFee;
+ }
+
+ function setMinSponsoredFee( int $minSponsoredFee ): CurrentTransaction
+ {
+ $this->minSponsoredFee = $minSponsoredFee;
+ $this->json->put( 'minSponsoredAssetFee', $minSponsoredFee );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/Transaction.php b/src/Transactions/Transaction.php
new file mode 100644
index 0000000..8970acb
--- /dev/null
+++ b/src/Transactions/Transaction.php
@@ -0,0 +1,51 @@
+type ) )
+ $this->type = $this->json->get( 'type' )->asInt();
+ return $this->type;
+ }
+
+ /**
+ * @return mixed
+ */
+ function setType( int $type )
+ {
+ $this->type = $type;
+ $this->json->put( 'type', $type );
+ return $this;
+ }
+
+ protected function setBase( PublicKey $sender, int $type, int $version, int $minFee ): void
+ {
+ $this->setSender( $sender );
+ $this->setType( $type );
+ $this->setVersion( $version );
+ $this->setFee( Amount::of( $minFee ) );
+
+ $this->setChainId();
+ $this->setTimestamp();
+ $this->setProofs();
+ }
+
+ function getProtobufTransactionBase(): \Waves\Protobuf\Transaction
+ {
+ $pb_Transaction = new \Waves\Protobuf\Transaction();
+ $pb_Transaction->setSenderPublicKey( $this->sender()->bytes() );
+ $pb_Transaction->setVersion( $this->version() );
+ $pb_Transaction->setFee( $this->fee()->toProtobuf() );
+ $pb_Transaction->setChainId( $this->chainId()->asInt() );
+ $pb_Transaction->setTimestamp( $this->timestamp() );
+
+ return $pb_Transaction;
+ }
+}
diff --git a/src/Transactions/TransactionOrOrder.php b/src/Transactions/TransactionOrOrder.php
new file mode 100644
index 0000000..da629e4
--- /dev/null
+++ b/src/Transactions/TransactionOrOrder.php
@@ -0,0 +1,178 @@
+
+ */
+ private array $proofs;
+ private string $bodyBytes;
+
+ function id(): Id
+ {
+ if( !isset( $this->id ) )
+ {
+ if( !$this->json()->exists( 'id' ) && isset( $this->bodyBytes ) )
+ $this->setId( Functions::calculateTransactionId( $this->bodyBytes ) );
+ else
+ $this->id = $this->json->get( 'id' )->asId();
+ }
+ return $this->id;
+ }
+
+ function setId( Id $id ): void
+ {
+ $this->id = $id;
+ $this->json->put( 'id', $id->toString() );
+ }
+
+ function version(): int
+ {
+ if( !isset( $this->version ) )
+ $this->version = $this->json->get( 'version' )->asInt();
+ return $this->version;
+ }
+
+ /**
+ * @return mixed
+ */
+ function setVersion( int $version )
+ {
+ $this->version = $version;
+ $this->json->put( 'version', $version );
+ return $this;
+ }
+
+ function chainId(): ChainId
+ {
+ if( !isset( $this->chainId ) )
+ {
+ if( $this->json->exists( 'chainId' ) )
+ $this->chainId = $this->json->get( 'chainId' )->asChainId();
+ else if( $this->json->exists( 'sender' ) )
+ $this->chainId = $this->json->get( 'sender' )->asAddress()->chainId();
+ else
+ $this->chainId = WavesConfig::chainId();
+ }
+ return $this->chainId;
+ }
+
+ /**
+ * @return mixed
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ if( !isset( $chainId ) )
+ $chainId = WavesConfig::chainId();
+ $this->chainId = $chainId;
+ $this->json->put( 'chainId', $chainId->asInt() );
+ return $this;
+ }
+
+ function sender(): PublicKey
+ {
+ if( !isset( $this->sender ) )
+ {
+ $this->sender = $this->json->get( 'senderPublicKey' )->asPublicKey();
+ if( $this->json->exists( 'sender' ) )
+ $this->sender->attachAddress( $this->json->get( 'sender' )->asAddress() );
+ }
+ return $this->sender;
+ }
+
+ /**
+ * @return mixed
+ */
+ function setSender( PublicKey $sender )
+ {
+ $this->sender = $sender;
+ $this->json->put( 'senderPublicKey', $sender->toString() );
+ $this->json->put( 'sender', $sender->address()->toString() );
+ return $this;
+ }
+
+ function timestamp(): int
+ {
+ if( !isset( $this->timestamp ) )
+ $this->timestamp = $this->json->get( 'timestamp' )->asInt();
+ return $this->timestamp;
+ }
+
+ /**
+ * @return mixed
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ if( !isset( $timestamp ) )
+ $timestamp = intval( microtime( true ) * 1000 );
+ $this->timestamp = $timestamp;
+ $this->json->put( 'timestamp', $timestamp );
+ return $this;
+ }
+
+ function fee(): Amount
+ {
+ if( !isset( $this->fee ) )
+ $this->fee = Amount::fromJson( $this->json, 'fee', 'feeAssetId' );
+ return $this->fee;
+ }
+
+ /**
+ * @return mixed
+ */
+ function setFee( Amount $fee )
+ {
+ $this->fee = $fee;
+ $this->json->put( 'fee', $fee->value() );
+ $this->json->put( 'feeAssetId', $fee->assetId()->toJsonValue() );
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ function proofs(): array
+ {
+ if( !isset( $this->proofs ) )
+ $this->proofs = $this->json->getOr( 'proofs', [] )->asArrayString();
+ return $this->proofs;
+ }
+
+ /**
+ * @param array $proofs
+ * @return mixed
+ */
+ function setProofs( array $proofs = null )
+ {
+ if( !isset( $proofs ) )
+ $proofs = [];
+ $this->proofs = $proofs;
+ $this->json->put( 'proofs', $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ return $this->bodyBytes;
+ }
+
+ protected function setBodyBytes( string $bodyBytes ): void
+ {
+ $this->bodyBytes = $bodyBytes;
+ }
+}
diff --git a/src/Transactions/TransferTransaction.php b/src/Transactions/TransferTransaction.php
new file mode 100644
index 0000000..dfbfea1
--- /dev/null
+++ b/src/Transactions/TransferTransaction.php
@@ -0,0 +1,207 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // TRANSFER TRANSACTION
+ {
+ $tx->setRecipient( $recipient );
+ $tx->setAmount( $amount );
+ $tx->setAttachment( $attachment );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // TRANSFER TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\TransferTransactionData;
+ // RECIPIENT
+ {
+ $pb_TransactionData->setRecipient( $this->recipient()->toProtobuf() );
+ }
+ // AMOUNT
+ {
+ $pb_TransactionData->setAmount( $this->amount()->toProtobuf() );
+ }
+ // ATTACHMENT
+ {
+ $pb_TransactionData->setAttachment( $this->attachment()->bytes() );
+ }
+ }
+
+ // TRANSFER TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setTransfer( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function recipient(): Recipient
+ {
+ if( !isset( $this->recipient ) )
+ $this->recipient = $this->json->get( 'recipient' )->asRecipient();
+ return $this->recipient;
+ }
+
+ function setRecipient( Recipient $recipient ): CurrentTransaction
+ {
+ $this->recipient = $recipient;
+ $this->json->put( 'recipient', $recipient->toString() );
+ return $this;
+ }
+
+ function amount(): Amount
+ {
+ if( !isset( $this->amount ) )
+ $this->amount = Amount::fromJson( $this->json );
+ return $this->amount;
+ }
+
+ function setAmount( Amount $amount ): CurrentTransaction
+ {
+ $this->amount = $amount;
+ $this->json->put( 'amount', $amount->value() );
+ $this->json->put( 'assetId', $amount->assetId()->toJsonValue() );
+ return $this;
+ }
+
+ function attachment(): Base58String
+ {
+ if( !isset( $this->attachment ) )
+ $this->attachment = $this->json->get( 'attachment' )->asBase58String();
+ return $this->attachment;
+ }
+
+ function setAttachment( Base58String $attachment = null ): CurrentTransaction
+ {
+ $attachment = $attachment ?? Base58String::emptyString();
+ $this->attachment = $attachment;
+ $this->json->put( 'attachment', $attachment->toString() );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Transactions/UpdateAssetInfoTransaction.php b/src/Transactions/UpdateAssetInfoTransaction.php
new file mode 100644
index 0000000..d659900
--- /dev/null
+++ b/src/Transactions/UpdateAssetInfoTransaction.php
@@ -0,0 +1,206 @@
+setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::MIN_FEE );
+
+ // RENAME TRANSACTION
+ {
+ $tx->setAssetId( $assetId );
+ $tx->setName( $name );
+ $tx->setDescription( $description );
+ }
+
+ return $tx;
+ }
+
+ function getUnsigned(): CurrentTransaction
+ {
+ // VERSION
+ if( $this->version() !== CurrentTransaction::LATEST_VERSION )
+ throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
+
+ // BASE
+ $pb_Transaction = $this->getProtobufTransactionBase();
+
+ // RENAME TRANSACTION
+ {
+ $pb_TransactionData = new \Waves\Protobuf\UpdateAssetInfoTransactionData;
+ // ASSET
+ {
+ $pb_TransactionData->setAssetId( $this->assetId()->bytes() );
+ }
+ // NAME
+ {
+ $pb_TransactionData->setName( $this->name() );
+ }
+ // DESCRIPTION
+ {
+ $pb_TransactionData->setDescription( $this->description() );
+ }
+ }
+
+ // RENAME TRANSACTION
+ $this->setBodyBytes( $pb_Transaction->setUpdateAssetInfo( $pb_TransactionData )->serializeToString() );
+ return $this;
+ }
+
+ function assetId(): AssetId
+ {
+ if( !isset( $this->assetId ) )
+ $this->assetId = $this->json->get( 'assetId' )->asAssetId();
+ return $this->assetId;
+ }
+
+ function setAssetId( AssetId $assetId ): CurrentTransaction
+ {
+ $this->assetId = $assetId;
+ $this->json->put( 'assetId', $assetId->toJsonValue() );
+ return $this;
+ }
+
+ function name(): string
+ {
+ if( !isset( $this->name ) )
+ $this->name = $this->json->get( 'name' )->asString();
+ return $this->name;
+ }
+
+ function setName( string $name ): CurrentTransaction
+ {
+ $this->name = $name;
+ $this->json->put( 'name', $name );
+ return $this;
+ }
+
+ function description(): string
+ {
+ if( !isset( $this->description ) )
+ $this->description = $this->json->get( 'description' )->asString();
+ return $this->description;
+ }
+
+ function setDescription( string $description ): CurrentTransaction
+ {
+ $this->description = $description;
+ $this->json->put( 'description', $description );
+ return $this;
+ }
+
+ // COMMON
+
+ function __construct( Json $json = null )
+ {
+ parent::__construct( $json );
+ }
+
+ function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
+ {
+ $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
+ if( $proof === false )
+ throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
+ $proof = Base58String::fromBytes( $proof )->encoded();
+
+ $proofs = $this->proofs();
+ if( !isset( $index ) )
+ $proofs[] = $proof;
+ else
+ $proofs[$index] = $proof;
+ return $this->setProofs( $proofs );
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setType( int $type )
+ {
+ parent::setType( $type );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setSender( PublicKey $sender )
+ {
+ parent::setSender( $sender );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setVersion( int $version )
+ {
+ parent::setVersion( $version );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setFee( Amount $fee )
+ {
+ parent::setFee( $fee );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setChainId( ChainId $chainId = null )
+ {
+ parent::setChainId( $chainId );
+ return $this;
+ }
+
+ /**
+ * @return CurrentTransaction
+ */
+ function setTimestamp( int $timestamp = null )
+ {
+ parent::setTimestamp( $timestamp );
+ return $this;
+ }
+
+ /**
+ * @param array $proofs
+ * @return CurrentTransaction
+ */
+ function setProofs( array $proofs = null )
+ {
+ parent::setProofs( $proofs );
+ return $this;
+ }
+
+ function bodyBytes(): string
+ {
+ if( !isset( $this->bodyBytes ) )
+ $this->getUnsigned();
+ return parent::bodyBytes();
+ }
+}
diff --git a/src/Util/Functions.php b/src/Util/Functions.php
new file mode 100644
index 0000000..640c72e
--- /dev/null
+++ b/src/Util/Functions.php
@@ -0,0 +1,44 @@
+decode( $string );
+ if( $decoded === false )
+ throw new Exception( __FUNCTION__ . ' failed to decode string: ' . $string, ExceptionCode::BASE58_DECODE );
+ return $decoded;
+ }
+
+ /**
+ * Encodes binary data to base58 string
+ *
+ * @param string $bytes
+ * @return string
+ */
+ static function base58Encode( string $bytes ): string
+ {
+ $encoded = \deemru\ABCode::base58()->encode( $bytes );
+ if( $encoded === false )
+ // Unreachable for binary encodings
+ throw new Exception( __FUNCTION__ . ' failed to encode bytes: ' . bin2hex( $bytes ), ExceptionCode::BASE58_ENCODE ); // @codeCoverageIgnore
+ return $encoded;
+ }
+
+ static function calculateTransactionId( string $bodyBytes ): Id
+ {
+ return Id::fromBytes( (new \deemru\WavesKit)->blake2b256( $bodyBytes ) );
+ }
+}
diff --git a/tests/NodeTest.php b/tests/NodeTest.php
new file mode 100644
index 0000000..0e6f250
--- /dev/null
+++ b/tests/NodeTest.php
@@ -0,0 +1,430 @@
+fail( 'Failed to catch exception with code:' . $code );
+ }
+ catch( Exception $e )
+ {
+ $this->assertEquals( $code, $e->getCode(), $e->getMessage() );
+ }
+ }
+
+ function testNode(): void
+ {
+ $nodeW = Node::MAINNET();
+ $nodeT = Node::TESTNET();
+ $nodeS = Node::STAGENET();
+
+ $version = $nodeS->getVersion();
+ $nodeS->waitBlocks( 0 );
+
+ $ethAsset = $nodeS->ethToWavesAsset( '0x7a087b3384447a48393eda243e630b07db443597' );
+ $this->assertEquals( '9DNEvLFSSnSSaNCb5WEYMz64hsadDjx1THZw3z2hiyJe', $ethAsset );
+
+ $someScript = file_get_contents( 'https://raw.githubusercontent.com/waves-exchange/neutrino-defo-contract/df334ea97952692983d1038a4818626ee01bfea6/factory.ride' );
+ if( $someScript === false )
+ {
+ $this->assertNotEquals( $someScript, false );
+ return;
+ }
+
+ $scriptInfo = $nodeW->compileScript( $someScript );
+ $script1 = $scriptInfo->script();
+ $scriptInfo = $nodeW->compileScript( $someScript, true );
+ $script2 = $scriptInfo->script();
+ $this->assertNotEquals( $script1, $script2 );
+ $this->assertLessThan( strlen( $script1->bytes() ), strlen( $script2->bytes() ) );
+
+ $address = Address::fromString( '3P5dg6PtSAQmdH1qCGKJWu7bkzRG27mny5i' );
+ $historyBalances = $nodeW->getBalanceHistory( $address );
+ foreach( $historyBalances as $historyBalance )
+ {
+ $historyBalance->height();
+ $historyBalance->balance();
+ }
+
+ $txs = $nodeW->getTransactionsByAddress( $address, 2 );
+ foreach( $txs as $tx )
+ {
+ $status = $nodeW->getTransactionStatus( $tx->id() );
+ $status->status();
+ $status->confirmations();
+ $this->assertSame( $status->id()->toString(), $tx->id()->toString() );
+ $this->assertSame( $status->applicationStatus(), $tx->applicationStatus() );
+ $this->assertSame( $status->height(), $tx->height() );
+ }
+
+ if( isset( $txs[1] ) )
+ {
+ $tx = $txs[1];
+ $txs2 = $nodeW->getTransactionsByAddress( $address, 2, $tx->id() );
+ foreach( $txs2 as $tx2 )
+ $this->assertNotEquals( $tx->id()->toString(), $tx2->id()->toString() );
+
+ $statuses = $nodeW->getTransactionsStatus( [ $tx->id() ] );
+ foreach( $statuses as $status )
+ $this->assertSame( $status->id()->toString(), $tx->id()->toString() );
+ }
+
+ $doUtx = 0;
+ for( ; $doUtx; )
+ {
+ $utxSize = $nodeW->getUtxSize();
+ if( $utxSize > 0 )
+ {
+ $txs = $nodeW->getUnconfirmedTransactions();
+ if( isset( $txs[0] ) )
+ {
+ $tx = $txs[0];
+ try
+ {
+ $txUnconfirmed = $nodeW->getUnconfirmedTransaction( $tx->id() );
+ $this->assertSame( $tx->id()->toString(), $txUnconfirmed->id()->toString() );
+ }
+ catch( Exception $e )
+ {
+ $this->assertEquals( ExceptionCode::FETCH_URI, $e->getCode(), $e->getMessage() );
+ }
+ }
+
+ break;
+ }
+
+ sleep( 1 );
+ }
+
+
+ $leases = $nodeW->getActiveLeases( $address );
+ foreach( $leases as $lease )
+ {
+ $lease->amount();
+ $lease->height();
+ $lease->id();
+ $lease->originTransactionId();
+ $lease->recipient();
+ $lease->sender();
+ if( $lease->status() == LeaseStatus::CANCELED )
+ {
+ $lease->cancelHeight();
+ $lease->cancelTransactionId();
+ }
+ }
+
+ if( isset( $lease ) )
+ {
+ $this->assertSame( $lease->toString(), $nodeW->getLeaseInfo( $lease->id() )->toString() );
+ $this->assertSame( $lease->toString(), $nodeW->getLeasesInfo( [ $lease->id() ] )[0]->toString() );
+ }
+
+ $leaseId = Id::fromString( '45uZvPeDva4CyXXTsTkh7fhzqTJCe2eqnz1HFt4aYNdZ' );
+ $lease = $nodeW->getLeaseInfo( $leaseId );
+ if( $lease->status() == LeaseStatus::CANCELED )
+ {
+ $lease->cancelHeight();
+ $lease->cancelTransactionId();
+ $transactionInfo = $nodeW->getTransactionInfo( $lease->cancelTransactionId() );
+ $this->assertSame( $lease->cancelHeight(), $transactionInfo->height() );
+ }
+
+ $heightT = $nodeT->getHeight();
+ $heightW = $nodeW->getHeight();
+
+ $block = $nodeW->getGenesisBlock();
+ $block = $nodeW->getLastBlock();
+ $blocks = $nodeW->getBlocksGeneratedBy( $block->generator(), $heightW - 10, $heightW );
+
+ $blocks = $nodeW->getBlocks( $heightW - 4, $heightW );
+ $this->assertSame( 5, count( $blocks ) );
+ foreach( $blocks as $block )
+ foreach( $block->transactions() as $tx )
+ {
+ $tx->applicationStatus();
+ $tx->chainId();
+ $tx->fee();
+ $tx->id();
+ $tx->proofs();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->type();
+ $tx->version();
+ }
+
+ $block1 = $nodeT->getBlockByHeight( 2126170 );
+ $block2 = $nodeT->getBlockById( $block1->id() );
+ $this->assertSame( $block1->toString(), $block2->toString() );
+ $block1->fee();
+ $txs = $block1->transactions();
+ foreach( $txs as $tx )
+ {
+ $tx->applicationStatus();
+ $tx->chainId();
+ $tx->fee();
+ $tx->id();
+ $tx->proofs();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->type();
+ $tx->version();
+
+ if( !isset( $validation ) )
+ {
+ $validation = $nodeT->validateTransaction( $tx );
+ $validation->isValid();
+ $validation->validationTime();
+ $validation->error();
+ }
+ }
+
+ $blockchainRewards1 = $nodeT->getBlockchainRewards();
+ $blockchainRewards2 = $nodeT->getBlockchainRewards( 1600000 );
+ $this->assertNotEquals( $blockchainRewards1->toString(), $blockchainRewards2->toString() );
+ $blockchainRewards1->currentReward();
+ $blockchainRewards1->height();
+ $blockchainRewards1->minIncrement();
+ $blockchainRewards1->nextCheck();
+ $blockchainRewards1->term();
+ $blockchainRewards1->totalWavesAmount();
+ $blockchainRewards1->votes()->increase();
+ $blockchainRewards1->votes()->decrease();
+ $blockchainRewards1->votingInterval();
+ $blockchainRewards1->votingIntervalStart();
+ $blockchainRewards1->votingThreshold();
+
+ $addressT = Address::fromString( '3N4q2D5bh5sAL3b4PighYyKw2WshKCiFD4F' );
+ $nfts1 = $nodeT->getNft( $addressT, 10 );
+ $nfts2 = $nodeT->getNft( $addressT, 10, $nfts1[0]->assetId() );
+ $this->assertSame( $nfts1[1]->toString(), $nfts2[0]->toString() );
+
+ $addressT = Address::fromString( '3N9WtaPoD1tMrDZRG26wA142Byd35tLhnLU' );
+ $assetId = AssetId::WAVES();
+
+ $assetIds = [];
+ $balances = $nodeT->getAssetsBalance( $addressT );
+ $max = 10;
+ foreach( $balances as $balance )
+ {
+ $assetId = $balance->assetId();
+ $assetIds[] = $assetId;
+ $addressBalance = $nodeT->getAssetBalance( $addressT, $assetId );
+ $this->assertSame( $addressBalance, $balance->balance() );
+ $balance->isReissuable();
+ $balance->quantity();
+ $balance->minSponsoredAssetFee();
+ $balance->sponsorBalance();
+ $balance->issueTransaction();
+
+ $distribution = $nodeT->getAssetDistribution( $assetId, $nodeT->getHeight() - 10, 10 );
+ if( $distribution->hasNext() )
+ $distribution = $nodeT->getAssetDistribution( $assetId, $nodeT->getHeight() - 10, 10, $distribution->lastItem() );
+
+ $details = $nodeT->getAssetDetails( $assetId );
+ $this->assertSame( $assetId->bytes(), $details->assetId()->bytes() );
+ $details->decimals();
+ $details->description();
+ $details->isReissuable();
+ $details->isScripted();
+ $details->issueHeight();
+ $details->issuer();
+ $details->issuerPublicKey();
+ $details->issueTimestamp();
+ $details->minSponsoredAssetFee();
+ $details->name();
+ $details->originTransactionId();
+ $details->quantity();
+ $scriptDetails = $details->scriptDetails();
+ $scriptDetails->complexity();
+ $scriptDetails->script();
+ if( --$max === 0 )
+ break;
+ }
+
+ if( isset( $details ) )
+ {
+ $assetsDetails = $nodeT->getAssetsDetails( $assetIds );
+ foreach( $assetsDetails as $assetDetails )
+ if( $assetDetails->assetId()->toString() === $details->assetId()->toString() )
+ $this->assertSame( $assetDetails->toString(), $details->toString() );
+ }
+
+ $aliases = $nodeT->getAliasesByAddress( $addressT );
+ foreach( $aliases as $alias )
+ {
+ $alias->name();
+ $alias->toString();
+ $this->assertSame( $addressT->encoded(), $nodeT->getAddressByAlias( $alias )->encoded() );
+ }
+
+ $addressT = Address::fromString( '3N7uoMNjqNt1jf9q9f9BSr7ASk1QtzJABEY' );
+
+ $scriptInfo = $nodeT->getScriptInfo( $addressT );
+ $scriptInfo->script();
+ $scriptInfo->complexity();
+ $scriptInfo->extraFee();
+ $scriptInfo->verifierComplexity();
+ $mapComplexities = $scriptInfo->callableComplexities();
+
+ $scriptMeta = $nodeT->getScriptMeta( $addressT );
+ $version = $scriptMeta->metaVersion();
+ $funcs = $scriptMeta->callableFunctions();
+ foreach( $funcs as $func => $args )
+ foreach( $args as $arg )
+ {
+ $arg->name();
+ $arg->type();
+ }
+
+ $addressT = Address::fromString( '3NAV8CuN5Zn6TT1gChFM2wXRtdhUBDUtCVt' );
+
+ $dataEntries1 = $nodeT->getData( $addressT, 'key_\d' );
+ $dataEntries2 = $nodeT->getData( $addressT );
+
+ $this->assertLessThan( count( $dataEntries2 ), count( $dataEntries1 ) );
+
+ $keys = [];
+ foreach( $dataEntries1 as $dataEntry )
+ $keys[] = $dataEntry->key();
+
+ $dataEntries2 = $nodeT->getDataByKeys( $addressT, $keys );
+ $this->assertSame( count( $dataEntries1 ), count( $dataEntries2 ) );
+
+ $n = count( $dataEntries1 );
+ for( $i = 0; $i < $n; ++$i )
+ $this->assertSame( $dataEntries1[$i]->value(), $dataEntries2[$i]->value() );
+
+ $this->assertSame( ChainId::MAINNET, $nodeW->chainId()->asString() );
+ $this->assertSame( ChainId::TESTNET, $nodeT->chainId()->asString() );
+ $this->assertSame( ChainId::STAGENET, $nodeS->chainId()->asString() );
+
+ $this->assertSame( $nodeW->uri(), Node::MAINNET );
+ $this->assertSame( $nodeT->uri(), Node::TESTNET );
+ $this->assertSame( $nodeS->uri(), Node::STAGENET );
+
+ $heightW = $nodeW->getHeight();
+ $heightT = $nodeT->getHeight();
+ $heightS = $nodeS->getHeight();
+
+ $this->assertLessThan( $heightW, $heightT );
+ $this->assertLessThan( $heightT, $heightS );
+ $this->assertLessThan( $heightS, 1 );
+
+ $addresses = $nodeW->getAddresses();
+
+ $address1 = $addresses[0];
+ $address2 = $nodeW->getAddressesByIndexes( 0, 1 )[0];
+
+ $this->assertSame( $address1->encoded(), Functions::base58Encode( $address2->bytes() ) );
+
+ $balance1 = $nodeW->getBalance( $address1 );
+ $balance2 = $nodeW->getBalance( $address2, 0 );
+
+ $this->assertSame( $balance1, $balance2 );
+
+ $balances = $nodeW->getBalances( $addresses );
+
+ $balance1 = $balances[0];
+ $balance2 = $nodeW->getBalances( $addresses, $heightW )[0];
+
+ $this->assertSame( $balance1->getAddress(), $balance2->getAddress() );
+ $this->assertSame( $balance1->getBalance(), $balance2->getBalance() );
+
+ $balanceDetails = $nodeW->getBalanceDetails( $address1 );
+
+ $this->assertSame( $balanceDetails->address(), $address1->toString() );
+ $this->assertSame( $balanceDetails->available(), $balance1->getBalance() );
+ $balanceDetails->effective();
+ $balanceDetails->generating();
+ $balanceDetails->regular();
+
+ $headers = $nodeW->getLastBlockHeaders();
+ $headers = $nodeW->getBlockHeadersByHeight( $headers->height() - 10 );
+
+ $headers->baseTarget();
+ $headers->desiredReward();
+ $headers->features();
+ $headers->generationSignature();
+ $headers->generator();
+ $headers->height();
+ $headers->id();
+ $headers->reference();
+ $headers->reward();
+ $headers->signature();
+ $headers->size();
+ $headers->timestamp();
+ $headers->totalFee();
+ $headers->transactionsCount();
+ $headers->transactionsRoot();
+ $headers->version();
+ $headers->vrf();
+
+ $height1 = $nodeW->getBlockHeightById( $headers->id()->encoded() );
+ $height2 = $nodeW->getBlockHeightByTimestamp( $headers->timestamp() );
+
+ $this->assertSame( $headers->height(), $height1 );
+ $this->assertSame( $headers->height(), $height2 );
+
+ $headers1 = $nodeW->getBlockHeadersByHeight( $headers->height() );
+ $headers2 = $nodeW->getBlockHeadersById( $headers->id()->encoded() );
+ $headers3 = $nodeW->getBlocksHeaders( $headers->height() - 1, $headers->height() )[1];
+ $this->assertSame( $headers->toString(), $headers1->toString() );
+ $this->assertSame( $headers->toString(), $headers2->toString() );
+ $this->assertSame( $headers->toString(), $headers3->toString() );
+
+ $delay = $nodeW->getBlocksDelay( $nodeW->getBlockHeadersByHeight( $headers->height() - 200 )->id()->encoded(), 100 );
+ $this->assertLessThan( 70 * 1000, $delay );
+ $this->assertLessThan( $delay, 50 * 1000 );
+ }
+
+ function testMoreCoverage(): void
+ {
+ $node1 = new Node( Node::MAINNET );
+ $node2 = new Node( Node::MAINNET, ChainId::MAINNET() );
+ $node3 = new Node( str_replace( 'https', 'http', Node::MAINNET ) );
+ $this->assertSame( $node1->chainId()->asString(), $node2->chainId()->asString() );
+ $this->assertSame( $node2->chainId()->asString(), $node3->chainId()->asString() );
+ }
+
+ function testExceptions(): void
+ {
+ $node = new Node( Node::MAINNET );
+ $json = $node->get( '/blocks/headers/last' );
+
+ $this->catchExceptionOrFail( ExceptionCode::FETCH_URI, function() use ( $node ){ $node->get( '/test' ); } );
+ $this->catchExceptionOrFail( ExceptionCode::JSON_DECODE, function() use ( $node ){ $node->get( '/api-docs/favicon-16x16.png' ); } );
+ $this->catchExceptionOrFail( ExceptionCode::KEY_MISSING, function() use ( $json ){ $json->get( 'x' ); } );
+ $this->catchExceptionOrFail( ExceptionCode::INT_EXPECTED, function() use ( $json ){ $json->get( 'signature' )->asInt(); } );
+ $this->catchExceptionOrFail( ExceptionCode::STRING_EXPECTED, function() use ( $json ){ $json->get( 'height' )->asString(); } );
+ $this->catchExceptionOrFail( ExceptionCode::ARRAY_EXPECTED, function() use ( $json ){ $json->get( 'height' )->asArrayInt(); } );
+
+ $this->catchExceptionOrFail( ExceptionCode::BAD_ALIAS, function(){ Recipient::fromAddressOrAlias( '123' ); } );
+ $this->catchExceptionOrFail( ExceptionCode::BASE58_DECODE, function(){ Functions::base58Decode( 'ill' ); } );
+ }
+}
+
+if( !defined( 'PHPUNIT_RUNNING' ) )
+{
+ $test = new NodeTest;
+ $test->testNode();
+ $test->testMoreCoverage();
+ $test->testExceptions();
+}
diff --git a/tests/TransactionsTest.php b/tests/TransactionsTest.php
new file mode 100644
index 0000000..5534270
--- /dev/null
+++ b/tests/TransactionsTest.php
@@ -0,0 +1,1032 @@
+chainId ) )
+ return;
+
+ if( !defined( 'WAVES_NODE' ) || !defined( 'WAVES_FAUCET' ) )
+ throw new Exception( 'Missing WAVES_NODE and WAVES_FAUCET definitions', ExceptionCode::UNEXPECTED );
+
+ $WAVES_NODE = constant( 'WAVES_NODE' );
+ $WAVES_FAUCET = constant( 'WAVES_FAUCET' );
+
+ if( !is_string( $WAVES_NODE ) || !is_string( $WAVES_FAUCET ) )
+ throw new Exception( '$WAVES_NODE and $WAVES_FAUCET should be strings', ExceptionCode::UNEXPECTED );
+
+ $account = getenv( 'WAVES_ACCOUNT' );
+ if( is_string( $account ) )
+ $account = PrivateKey::fromString( $account );
+ else
+ {
+ $account = PrivateKey::fromBytes( random_bytes( 32 ) );
+ putenv( 'WAVES_ACCOUNT=' . $account->toString() );
+ }
+
+ $node = new Node( $WAVES_NODE );
+ $chainId = $node->chainId();
+
+ WavesConfig::chainId( $chainId );
+ $faucet = PrivateKey::fromSeed( $WAVES_FAUCET );
+
+ $publicKey = PublicKey::fromPrivateKey( $account );
+ $address = Address::fromPublicKey( $publicKey );
+
+ $this->assertSame( $account->publicKey()->toString(), $publicKey->toString() );
+ $this->assertSame( $account->publicKey()->address()->toString(), $address->toString() );
+
+ $this->node = $node;
+ $this->faucet = $faucet;
+ $this->account = $account;
+ $this->chainId = $chainId;
+
+ $this->prepareRoot();
+ $this->prepareFunds();
+ $this->prepareSponsor();
+ $this->prepareToken();
+ }
+
+ private function prepareRoot(): void
+ {
+ $node = $this->node;
+ $faucet = $this->faucet;
+
+ $address = $this->fetchOr( function(){ return $this->node->getAddressByAlias( Alias::fromString( 'root' ) ); }, false );
+ if( $address === false )
+ {
+ $node->waitForTransaction(
+ $node->broadcast(
+ CreateAliasTransaction::build( $faucet->publicKey(), Alias::fromString( 'root' ) )->addProof( $faucet )
+ )->id()
+ );
+ }
+
+ $scriptCode = file_get_contents( __DIR__ . '/retransmit.ride' );
+ if( $scriptCode === false )
+ throw new Exception( 'Missing `retransmit.ride` file', ExceptionCode::UNEXPECTED );
+ $scriptCompiled = $node->compileScript( $scriptCode );
+
+ $script = $this->fetchOr( function(){ return $this->node->getScriptInfo( $this->faucet->publicKey()->address() ); }, false );
+ if( !( $script instanceof ScriptInfo ) || $script->script()->bytes() !== $scriptCompiled->script()->bytes() )
+ {
+ $node->waitForTransaction(
+ $node->broadcast(
+ SetScriptTransaction::build( $faucet->publicKey(), $scriptCompiled->script() )->addProof( $faucet )
+ )->id()
+ );
+ }
+
+ $assets = $node->getAssetsBalance( $faucet->publicKey()->address() );
+ foreach( $assets as $asset )
+ {
+ $amount = Amount::of( $asset->balance(), $asset->assetId() );
+ $this->fetchOr( function() use ( $node, $faucet, $amount ){ $node->broadcast( BurnTransaction::build( $faucet->publicKey(), $amount )->addProof( $faucet ) ); }, false );
+ }
+ }
+
+ private function prepareFunds(): void
+ {
+ $node = $this->node;
+ $address = $this->account->publicKey()->address();
+ $faucet = $this->faucet;
+
+ $balance = $this->node->getBalance( $address );
+ if( $balance < self::WAVES_FOR_TEST )
+ $node->waitForTransaction(
+ $node->broadcast(
+ TransferTransaction::build( $faucet->publicKey(), Recipient::fromAddress( $address ), Amount::of( self::WAVES_FOR_TEST * 2 ) )->addProof( $faucet )
+ )->id()
+ );
+
+ $this->assertGreaterThan( self::WAVES_FOR_TEST, $this->node->getBalance( $address ) );
+ }
+
+ /**
+ * @param callable $block
+ * @param mixed $default
+ * @return mixed
+ */
+ private function fetchOr( callable $block, $default )
+ {
+ try
+ {
+ return $block();
+ }
+ catch( Exception $e )
+ {
+ if( $e->getCode() & ExceptionCode::BASE )
+ return $default;
+ throw $e;
+ }
+ }
+
+ private function prepareSponsor(): void
+ {
+ $sponsorId = $this->fetchOr( function(){ return $this->node->getDataByKey( $this->account->publicKey()->address(), self::SPONSOR_ID )->stringValue(); }, false );
+ if( is_string( $sponsorId ) )
+ $this->sponsorId = AssetId::fromString( $sponsorId );
+ else
+ {
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $tx = $node->waitForTransaction( $node->broadcast( IssueTransaction::build( $sender, 'SPONSOR', '', 1000, 0, false )->addProof( $account ) )->id() );
+ $this->sponsorId = AssetId::fromString( $tx->id()->toString() );
+ $tx = $node->waitForTransaction( $node->broadcast( SponsorFeeTransaction::build( $sender, $this->sponsorId, 1 )->addProof( $account ) )->id() );
+
+ $node->waitForTransaction( $node->broadcast( DataTransaction::build( $sender, [ DataEntry::string( self::SPONSOR_ID, $this->sponsorId->toString() ) ] )->addProof( $account ) )->id() );
+ }
+ }
+
+ private function prepareToken(): void
+ {
+ $tokenId = $this->fetchOr( function(){ return $this->node->getDataByKey( $this->account->publicKey()->address(), self::TOKEN_ID )->stringValue(); }, false );
+ if( is_string( $tokenId ) )
+ $this->tokenId = AssetId::fromString( $tokenId );
+ else
+ {
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $tx = $node->waitForTransaction( $node->broadcast( IssueTransaction::build( $sender, 'TOKEN', '', 1000000, 6, true )->addProof( $account ) )->id() );
+ $this->tokenId = AssetId::fromString( $tx->id()->toString() );
+
+ $node->waitForTransaction( $node->broadcast( DataTransaction::build( $sender, [ DataEntry::string( self::TOKEN_ID, $this->tokenId->toString() ) ] )->addProof( $account ) )->id() );
+ }
+ }
+
+ function testAlias(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $tx = CreateAliasTransaction::build(
+ $sender,
+ Alias::fromString( 'name-' . mt_rand( 10000000000, 99999999999 ) )
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->alias();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new CreateAliasTransaction)
+ ->setAlias( Alias::fromString( 'name-' . mt_rand( 10000000000, 99999999999 ) ) )
+
+ ->setSender( $sender )
+ ->setType( CreateAliasTransaction::TYPE )
+ ->setVersion( CreateAliasTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( CreateAliasTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testLeaseAndLeaseCancel(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $recipient = Recipient::fromAddressOrAlias( 'root' );
+
+ // LEASE
+
+ $tx = LeaseTransaction::build(
+ $sender,
+ $recipient,
+ 10_000_000
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->recipient();
+ $tx->amount();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new LeaseTransaction)
+ ->setRecipient( Recipient::fromAddress( $node->getAddressByAlias( $recipient->alias() ) ) )
+ ->setAmount( 20_000_000 )
+
+ ->setSender( $sender )
+ ->setType( LeaseTransaction::TYPE )
+ ->setVersion( LeaseTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( LeaseTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ // LEASE_CANCEL
+
+ $tx = LeaseCancelTransaction::build(
+ $sender,
+ $tx1->id()
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->leaseId();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new LeaseCancelTransaction)
+ ->setLeaseId( $tx2->id() )
+
+ ->setSender( $sender )
+ ->setType( LeaseCancelTransaction::TYPE )
+ ->setVersion( LeaseCancelTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( LeaseCancelTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testSetScript(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $script = Base64String::fromString( 'AAIFAAAAAAAAAAcIAhIDCgEfAAAAAAAAAAEAAAABaQEAAAAEY2FsbAAAAAEAAAAEbGlzdAUAAAADbmlsAAAAAQAAAAJ0eAEAAAAGdmVyaWZ5AAAAAAkACcgAAAADCAUAAAACdHgAAAAJYm9keUJ5dGVzCQABkQAAAAIIBQAAAAJ0eAAAAAZwcm9vZnMAAAAAAAAAAAAIBQAAAAJ0eAAAAA9zZW5kZXJQdWJsaWNLZXmQFHRt' );
+
+ $tx = SetScriptTransaction::build(
+ $sender,
+ $script
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->script();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new SetScriptTransaction)
+ ->setScript() // remove script
+
+ ->setSender( $sender )
+ ->setType( SetScriptTransaction::TYPE )
+ ->setVersion( SetScriptTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( SetScriptTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testSetAssetScript(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $script = Base64String::fromString( 'BQbtKNoM' );
+
+ $tx = $node->waitForTransaction( $node->broadcast( IssueTransaction::build( $sender, 'SCRIPTED', '', 1, 0, false, $script )->addProof( $account ) )->id() );
+ $scriptedId = AssetId::fromString( $tx->id()->toString() );
+
+ $tx = SetAssetScriptTransaction::build(
+ $sender,
+ $scriptedId,
+ $script
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->assetId();
+ $tx->script();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new SetAssetScriptTransaction)
+ ->setAssetId( $scriptedId )
+ ->setScript( $script )
+
+ ->setSender( $sender )
+ ->setType( SetAssetScriptTransaction::TYPE )
+ ->setVersion( SetAssetScriptTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( SetAssetScriptTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testReissue(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $tx = ReissueTransaction::build(
+ $sender,
+ Amount::of( 1000_000_000, $this->tokenId ),
+ true
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->amount();
+ $tx->isReissuable();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new ReissueTransaction)
+ ->setAmount( Amount::of( 2000_000_000, $this->tokenId ) )
+ ->setIsReissuable( true )
+
+ ->setSender( $sender )
+ ->setType( ReissueTransaction::TYPE )
+ ->setVersion( ReissueTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( ReissueTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testBurn(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $tx = BurnTransaction::build(
+ $sender,
+ Amount::of( 100_000_000, $this->tokenId )
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->amount();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new BurnTransaction)
+ ->setAmount( Amount::of( 10_000_000, $this->tokenId ) )
+
+ ->setSender( $sender )
+ ->setType( BurnTransaction::TYPE )
+ ->setVersion( BurnTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( BurnTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testIssue(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $tx = IssueTransaction::build(
+ $sender,
+ 'NFT-' . mt_rand( 100000, 999999 ),
+ 'test description',
+ 1,
+ 0,
+ false
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->name();
+ $tx->description();
+ $tx->quantity();
+ $tx->decimals();
+ $tx->isReissuable();
+ $tx->script();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new IssueTransaction)
+ ->setName( 'NFT-' . mt_rand( 100000, 999999 ) )
+ ->setDescription( 'test description' )
+ ->setQuantity( 1 )
+ ->setDecimals( 0 )
+ ->setIsReissuable( false )
+
+ ->setSender( $sender )
+ ->setType( IssueTransaction::TYPE )
+ ->setVersion( IssueTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( IssueTransaction::NFT_MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testRename(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $tokenId = $this->tokenId;
+
+ $assetInfo = $node->getAssetDetails( $tokenId );
+ $node->waitForHeight( $assetInfo->issueHeight() + 2 );
+
+ $tx = UpdateAssetInfoTransaction::build(
+ $sender,
+ $tokenId,
+ 'TOKEN-RENAMED',
+ 'renamed description'
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->assetId();
+ $tx->name();
+ $tx->description();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $node->waitBlocks( 2 );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new UpdateAssetInfoTransaction)
+ ->setAssetId( $this->sponsorId )
+ ->setName( 'SPONSOR-RENAMED' )
+ ->setDescription( 'renamed description' )
+
+ ->setSender( $sender )
+ ->setType( UpdateAssetInfoTransaction::TYPE )
+ ->setVersion( UpdateAssetInfoTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( UpdateAssetInfoTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testSponsorship(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $sponsorId = $this->sponsorId;
+
+ $tx = SponsorFeeTransaction::build(
+ $sender,
+ $sponsorId,
+ 1
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->assetId();
+ $tx->minSponsoredFee();
+
+ $tx1 = $node->broadcast( $tx->addProof( $account ) );
+ $node->waitForTransactions( [ $tx1->id() ] );
+
+ $tx1 = $node->waitForTransaction( $tx1->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new SponsorFeeTransaction())
+ ->setAssetId( $sponsorId )
+ ->setMinSponsoredFee( 1 )
+
+ ->setSender( $sender )
+ ->setType( SponsorFeeTransaction::TYPE )
+ ->setVersion( SponsorFeeTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( SponsorFeeTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testData(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $data = [];
+ $data[] = DataEntry::build( 'key_string', EntryType::STRING, '123' );
+ $data[] = DataEntry::build( 'key_binary', EntryType::BINARY, '123' );
+ $data[] = DataEntry::build( 'key_boolean', EntryType::BOOLEAN, true );
+ $data[] = DataEntry::build( 'key_integer', EntryType::INTEGER, 123 );
+ $data[] = DataEntry::build( 'key_delete', EntryType::DELETE );
+
+ $tx = DataTransaction::build(
+ $sender,
+ $data
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->data();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new DataTransaction)
+ ->setData( $data )
+
+ ->setSender( $sender )
+ ->setType( DataTransaction::TYPE )
+ ->setVersion( DataTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( DataTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testMassTransfer(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $transfers = [];
+ $transfers[] = new Transfer( Recipient::fromAlias( Alias::fromString( 'root' ) ), 1 );
+ $transfers[] = new Transfer( Recipient::fromAddress( $sender->address() ), 2 );
+
+ $attachment = Base58String::fromBytes( 'root' );
+
+ $tx = MassTransferTransaction::build(
+ $sender,
+ $this->tokenId,
+ $transfers,
+ $attachment,
+ );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->assetId();
+ $tx->transfers();
+ $tx->attachment();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new MassTransferTransaction)
+ ->setAssetId( AssetId::WAVES() )
+ ->setTransfers( $transfers )
+ ->setAttachment( $attachment )
+
+ ->setSender( $sender )
+ ->setType( MassTransferTransaction::TYPE )
+ ->setVersion( MassTransferTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( MassTransferTransaction::calculateFee( count( $transfers ) ) ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testTransfer(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $recipient = Recipient::fromAlias( Alias::fromString( 'root' ) );
+ $amount = new Amount( 1, AssetId::WAVES() );
+ $attachment = Base58String::fromBytes( 'root' );
+
+ $tx = TransferTransaction::build(
+ $sender,
+ $recipient,
+ $amount,
+ )->setFee( Amount::of( 1, $this->sponsorId ) );
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->recipient();
+ $tx->amount();
+ $tx->attachment();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new TransferTransaction)
+ ->setRecipient( Recipient::fromAddress( $node->getAddressByAlias( $recipient->alias() ) ) )
+ ->setAmount( Amount::of( 1000, $this->tokenId ) )
+ ->setAttachment( $attachment )
+
+ ->setSender( $sender )
+ ->setType( TransferTransaction::TYPE )
+ ->setVersion( TransferTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( TransferTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+
+ function testInvoke(): void
+ {
+ $this->prepare();
+ $chainId = $this->chainId;
+ $node = $this->node;
+ $account = $this->account;
+ $sender = $account->publicKey();
+
+ $dApp = Recipient::fromAddressOrAlias( 'root' );
+ $args = [];
+ $args[] = Arg::as( Arg::STRING, Value::as( $sender->address()->toString() ) );
+ $args[] = Arg::as( Arg::INTEGER, Value::as( 1000 ) );
+ $args[] = Arg::as( Arg::BINARY, Value::as( '' ) );
+ $args[] = Arg::as( Arg::BOOLEAN, Value::as( true ) );
+ $list = [];
+ $list[] = Arg::as( Arg::STRING, Value::as( '0' ) );
+ $list[] = Arg::as( Arg::STRING, Value::as( '1' ) );
+ $list[] = Arg::as( Arg::STRING, Value::as( '2' ) );
+ $args[] = Arg::as( Arg::LIST, Value::as( $list ) );
+ $function = FunctionCall::as( 'retransmit', $args );
+ $payments = [];
+ $payments[] = Amount::of( 1000 );
+
+ $tx = InvokeScriptTransaction::build(
+ $sender,
+ $dApp,
+ $function,
+ $payments
+ )->setFee( Amount::of( 5, $this->sponsorId ) );;
+
+ $tx->bodyBytes();
+
+ $id = $tx->id();
+ $tx->version();
+ $tx->chainId();
+ $tx->sender();
+ $tx->timestamp();
+ $tx->fee();
+ $tx->proofs();
+
+ $tx->dApp();
+ $tx->function();
+ $tx->payments();
+
+ $tx1 = $node->waitForTransaction( $node->broadcast( $tx->addProof( $account ) )->id() );
+
+ $this->assertSame( $id->toString(), $tx1->id()->toString() );
+ $this->assertSame( $tx1->applicationStatus(), ApplicationStatus::SUCCEEDED );
+
+ $txFromJson = new InvokeScriptTransaction( $tx1->json() );
+ $this->assertSame( $tx->dApp()->toString(), $txFromJson->dApp()->toString() );
+ $this->assertSame( serialize( $tx->payments() ), serialize( $txFromJson->payments() ) );
+ $this->assertSame( serialize( $tx->function() ), serialize( $txFromJson->function() ) );
+
+ $tx2 = $node->waitForTransaction(
+ $node->broadcast(
+ (new InvokeScriptTransaction)
+ ->setDApp( $dApp )
+ ->setFunction( $function )
+ ->setPayments( $payments )
+
+ ->setSender( $sender )
+ ->setType( InvokeScriptTransaction::TYPE )
+ ->setVersion( InvokeScriptTransaction::LATEST_VERSION )
+ ->setFee( Amount::of( InvokeScriptTransaction::MIN_FEE ) )
+ ->setChainId( $chainId )
+ ->setTimestamp()
+
+ ->addProof( $account )
+ )->id()
+ );
+
+ $this->assertNotSame( $tx1->id(), $tx2->id() );
+ $this->assertSame( $tx2->applicationStatus(), ApplicationStatus::SUCCEEDED );
+ }
+}
+
+if( !defined( 'PHPUNIT_RUNNING' ) )
+{
+ $test = new TransactionsTest;
+ $test->testInvoke();
+ $test->testSponsorship();
+ $test->testRename();
+ $test->testSetScript();
+ $test->testData();
+ $test->testMassTransfer();
+ $test->testTransfer();
+ $test->testAlias();
+ $test->testLeaseAndLeaseCancel();
+ $test->testIssue();
+ $test->testReissue();
+ $test->testBurn();
+ $test->testSetAssetScript();
+}
diff --git a/tests/common.php b/tests/common.php
new file mode 100644
index 0000000..c810398
--- /dev/null
+++ b/tests/common.php
@@ -0,0 +1,32 @@
+ $value )
+ if( is_string( $key ) )
+ define( $key, $value );
+}
+
+prepare();
diff --git a/tests/retransmit.ride b/tests/retransmit.ride
new file mode 100644
index 0000000..30d8485
--- /dev/null
+++ b/tests/retransmit.ride
@@ -0,0 +1,12 @@
+{-# STDLIB_VERSION 6 #-}
+{-# CONTENT_TYPE DAPP #-}
+{-# SCRIPT_TYPE ACCOUNT #-}
+
+@Callable(i)
+func retransmit( address: String, amount: Int, asset: ByteVector, bool: Boolean, list: List[String] ) =
+[
+ StringEntry( "LIST_0", list[0] ),
+ StringEntry( "LIST_1", list[1] ),
+ StringEntry( "LIST_2", list[2] ),
+ ScriptTransfer( addressFromStringValue( address ), amount, if( asset == base58'' ) then unit else asset )
+]