{"id":13772549,"url":"https://github.com/msmolyakov/paddle","last_synced_at":"2025-08-20T20:31:17.641Z","repository":{"id":38189876,"uuid":"195812168","full_name":"msmolyakov/paddle","owner":"msmolyakov","description":"Java framework to write tests for your dApps and other smart contracts on Waves blockchain","archived":false,"fork":false,"pushed_at":"2022-06-09T17:00:04.000Z","size":1703,"stargazers_count":12,"open_issues_count":5,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-03T18:52:32.799Z","etag":null,"topics":["blockchain","dapps","ride","smart-contracts","testing","testing-tools","waves","wavesplatform"],"latest_commit_sha":null,"homepage":"https://msmolyakov.github.io/paddle","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/msmolyakov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"custom":"https://waves.exchange/#send/WAVES?attachment=Thanks for the Paddle!\u0026recipient=3PCHVcxi1v1PhkLhApQzfe7u9JgBUK2mXdr\u0026amount=10"}},"created_at":"2019-07-08T12:56:52.000Z","updated_at":"2023-06-04T08:45:22.000Z","dependencies_parsed_at":"2022-08-18T18:00:36.215Z","dependency_job_id":null,"html_url":"https://github.com/msmolyakov/paddle","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/msmolyakov/paddle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msmolyakov%2Fpaddle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msmolyakov%2Fpaddle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msmolyakov%2Fpaddle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msmolyakov%2Fpaddle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/msmolyakov","download_url":"https://codeload.github.com/msmolyakov/paddle/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msmolyakov%2Fpaddle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271378680,"owners_count":24749192,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-20T02:00:09.606Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blockchain","dapps","ride","smart-contracts","testing","testing-tools","waves","wavesplatform"],"created_at":"2024-08-03T17:01:05.292Z","updated_at":"2025-08-20T20:31:17.246Z","avatar_url":"https://github.com/msmolyakov.png","language":"Java","funding_links":["https://waves.exchange/#send/WAVES?attachment=Thanks for the Paddle!\u0026recipient=3PCHVcxi1v1PhkLhApQzfe7u9JgBUK2mXdr\u0026amount=10"],"categories":["Frameworks and tools"],"sub_categories":["The Ride programming language"],"readme":"[![Maven Central](https://img.shields.io/maven-central/v/im.mak/paddle.svg?label=Maven%20Central)](https://search.maven.org/artifact/im.mak/paddle)\n\n# Paddle for Waves\n\nPaddle is a Java library to write tests for your dApps and other smart contracts on Waves blockchain.\n\nЭта документация доступна на [русском языке](README_ru.md).\n\n## Table of Contents\n\n- [Getting started](#getting-started)\n  - [Requirements](#requirements)\n  - [Installation](#installation)\n  - [Simple usage](#simple-usage)\n  - [Example with JUnit 5](#example-with-junit-5)\n- [Test lifecycle](#test-lifecycle)\n- [Paddle configuration](#paddle-configuration)\n  - [Pre-configured profiles](#pre-configured-profiles)\n  - [Custom profiles](#custom-profiles)\n  - [Run node in Docker](#run-node-in-docker)\n- [Node instance](node-instance)\n- [Account](#account)\n  - [Retrieving information about account](#retrieving-information-about-account)\n  - [Signing data and sending transactions](#signing-data-and-sending-transactions)\n  - [Creating dApp and smart account](#creating-dapp-and-smart-account)\n- [Assertions](#assertions)\n  - [Transaction](#transaction)\n  - [State changes of InvokeScript transaction](#state-changes-of-invokescript-transaction)\n  - [Transactions rejection](#transactions-rejection)\n- [Other features](#other-features)\n  - [Waitings](#waitings)\n  - [Asynchronous actions](#asynchronous-actions)\n  - [RSA encryption](#rsa-encryption)\n  - [Merkle tree](#merkle-tree)\n- [What next?](#what-next)\n\n## Getting started\n\n### Requirements\n\n- Java 11 or higher;\n- Docker 17.03.1 or newer if you want to use Waves Node in Docker. On Windows, install the latest \"Docker for Windows\".\n\n### Installation\n\nThe easiest way to get started is to create a new project from the [paddle-example](https://github.com/msmolyakov/paddle-example) GitHub template.\n\nOr add Paddle as dependency to your existing project.\n\n#### Maven\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eim.mak\u003c/groupId\u003e\n    \u003cartifactId\u003epaddle\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.0-rc9\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n#### Gradle\n\nGroovy DSL:\n```groovy\nimplementation 'im.mak:paddle:1.0.0-rc9'\n```\n\nKotlin DSL:\n```kotlin\ncompile(\"im.mak:paddle:1.0.0-rc9\")\n```\n\n### Simple usage\n\n```java\nimport im.mak.paddle.Account;\npublic class Main {\n    public static void main(String[] args) {\n\n        // Create two accounts.\n        // At the time of creation first account,\n        // Paddle automatically downloaded and started Waves Node in Docker container!\n        Account alice = new Account(10_00000000); // account with 10 Waves\n        Account bob = new Account();              // account with no Waves\n\n        // Alice sends 3 Waves to Bob\n        // Paddle waits until the Transfer transaction appears in blockchain\n        alice.transfers(t -\u003e t.to(bob).amount(3_00000000));\n\n        System.out.println( bob.balance() ); // 300000000\n        \n        // When the program ends, Paddle will automatically turn off the Docker container\n    }\n}\n```\n\n### Example with JUnit 5\n\n```java\nimport im.mak.paddle.Account;\nimport org.junit.jupiter.api.*;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass FirstTest {\n\n    private Account alice, bob;\n    private String assetId;\n\n    @Test\n    void canSendSmartAsset() {\n        alice = new Account(10_00000000L);\n        bob = new Account();\n\n        assetId = alice.issues(a -\u003e a\n                .name(\"My Asset\")\n                .quantity(100)\n                .script(\"2 * 2 == 4\")\n        ).getId().toString();\n\n        alice.transfers(t -\u003e t\n                .to(bob)\n                .amount(42)\n                .asset(assetId)\n        );\n        \n        assertEquals(42, bob.balance(assetId));\n    }\n}\n```\n\n## Test lifecycle\n\nPaddle is framework agnostic, i.e. Paddle can be used independently, or with JUnit, TestNG and any other test framework familiar to you.\n\nBut in general, any test consists of the following steps:\n1. run or connect to node;\n2. create test accounts;\n3. send some transactions;\n4. perform assertions;\n5. shutdown docker node, if it's been used in step 1.\n\nSteps 1 and 5 are performed automatically by Paddle.\n\n## Paddle configuration\n\nPaddle needs some Waves node to execute test scenarios.\nBy default, Paddle starts a node in a Docker container, but it's possible to connect to any existing node.\n\nThe behaviour is set by parameters of selected profile, which can be declared in `paddle.conf` file.\\\nThe file `paddle.conf` can be created either in the project root or in the `resources` folder.\n\n### Pre-configured profiles\n\nPaddle has [predefined profiles](src/main/resources/reference.conf), that are enough for most purposes:\n* `docker` - default profile. Paddle starts local node on port `6869` and turns it down when tests are complete.\\\nIt uses [the official image of private Waves node](https://hub.docker.com/r/wavesplatform/waves-private-node) with clean blockchain and most frequent blocks generation. It allows to run your tests faster than in the Testnet or Mainnet.\n* `local` - Paddle connects to existing local node. For example, if docker container with a node is already started manually.\n* `stagenet`, `testnet`, `mainnet` - Paddle connects to a node in the specified Waves network.\\\nAdditionally, you must specify the seed of account which Waves will be taken to send transactions. This can be done through the launch command:\\\n`mvn test -Dpaddle.profile=testnet \"-Dpaddle.testnet.faucet-seed=some seed text\"`\\\nor set the seed in the file `paddle.conf`:\n```hocon\npaddle.profile = testnet\npaddle.testnet.faucet-seed = some seed text\n```\n\n### Custom profiles\n\nIn the `paddle.conf` you can create your own profiles:\n```hocon\npaddle.profile = my-profile\npaddle.my-profile {\n    api-url = \"http://localhost:8080/\"\n    chain-id = D\n    faucet-seed = some seed text\n}\npaddle.my-docker-profile = ${paddle.my-profile} {\n    docker-image = \"my-custom-image:latest\"\n    faucet-seed = another seed text\n    auto-shutdown = true\n}\n```\n\nIn this example, when using `my-profile`, Paddle will connect to an already running local node.\\\nAnd with `my-docker-profile` Paddle will launch a node container, because the `docker-image` field is specified.\n\n`${paddle.my-profile}` means that `my-docker-profile` is inherited from the `my-profile` profile with additional parameters.\n\n**Note!** Your custom docker image must expose port `6869` for REST API.\n\n### Run node in Docker\n\n#### Default official image\n\nIf the `docker` profile is selected, then to start the node, just create an account or refer to the node instance:\n```java\nimport im.mak.paddle.Account;\nimport static im.mak.paddle.Node.node;\n\nAccount alice = new Account(100_00000000); // account with initial balance\n// OR\nnode().height();\n```\nPaddle will start the node automatically!\n\nPaddle requires a `faucet` account - this account is available as `node().faucet()` field of the Node instance.\\\nIt's used as a bank for initial balances of other test accounts.\nWhen creating any new account, Waves tokens for its initial balance are transferred from the `faucet` account.\n\nPaddle turns off the node container itself when all tests are finished.\nBut if tests are interrupted urgently, then the container will most likely remain working, and you will have to turn it off yourself.\n\n## Node instance\n\n- `node().chainId()` - chain id of used blockchain;\n- `node().height()` - current height of blockchain;\n- `node().compileScript()` - compile RIDE script;\n- `node().isSmart(assetOrAddress)` - returns true if asset or account is scripted;\n- `node().send(...)` - send transaction;\n- `node().api.assetDetails(assetId)` - information about issued asset;\n- `node().api.nft(address)` - list of NFT for specified account;\n- `node().api.stateChanges(invokeTxId)` - result of specified InvokeScript transaction.\n\n## Account\n\nIn Paddle, Account is an actor of your test. It has all the Waves account information and can send transactions.\n\nTo create new account:\n\n```java\nAccount alice = new Account(10_00000000L);\n```\n\nOptionally, you can specify seed phrase, otherwise it will be randomly generated.\n\nAlso optionally, you can set initial Waves balance, otherwise account will not have Waves tokens at start.\\\nTechnically, when you specify initial balance, \"rich\" account sends Transfer transaction to the created account.\n\n### Retrieving information about account\n\nAccount can provide seed phrase, private and public keys, address. Account can check if it's scripted:\n\n```java\nalice.seed();\nalice.privateKey();\nalice.publicKey();\nalice.address();\n\nalice.isSmart();\n```\n\nAccount can get Waves or asset balance and read entries from its data storage:\n\n```java\nalice.balance();        // balance in Waves\nalice.balance(assetId); // balance in some asset\nalice.nft();            // list of non-fungible tokens on this account\n\nalice.data();           // collection of all entries in account data storage\nalice.dataByKey(key);   // entry of unknown type by specified key\n\nalice.dataBin(key);     // binary entry by specified key\nalice.dataBool(key);    // boolean entry by specified key\nalice.dataInt(key);     // integer entry by specified key\nalice.dataStr(key);     // string entry by specified key\n```\n\n### Signing data and sending transactions\n\nAccount can sign any bytes and send transactions:\n\n```java\nalice.sign(tx.getBodyBytes());\n\nalice.issues(...);\nalice.setsScript(...);\nalice.invokes(...);\n// and etc...\n```\n\nSending a transaction, you can specify only the fields important for your scenario - in most cases all other fields will be set by default or calculated automatically.\\\nFor example, you don't have to specify asset name and description for Issue transaction:\n\n```java\nalice.issues(a -\u003e a.quantity(1000).decimals(0));\n// only the number of tokens and decimals are specified here\n```\n\nAlso, _transaction fee will be calculated automatically_ too! Exceptions at now: sponsored fee in tokens and InvokeScript transactions with smart assets in ScriptTransfers of dApp contract.\n\nYou don't need sign transactions explicitly - Paddle does it automatically.\\\nYou don't need wait when transactions will be added to blockchain - Paddle does it automatically.\n\n### Creating dApp and smart account\n\nTo create dApp or smart asset, you can provide script source from file:\n\n```java\nalice.setsScript(s -\u003e s.script(Script.fromFile(\"wallet.ride\")));\nalice.issues(a -\u003e a.script(Script.fromFile(\"fixed-price.ride\")));\n```\n\nOr set script code directly as string:\n\n```java\nalice.setsScript(s -\u003e s.script(\"sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)\"));\nalice.issues(a -\u003e a.script(\"true\"));\n```\n\nIn both cases Paddle will compile your script automatically.\n\n## Assertions\n\n### Transaction\n\nEach account action returns an instance of the resulting transaction from blockchain.\n\n```java\nString assetId = alice.issues(a -\u003e a.quantity(1000)).getId().toString();\n\nassertEquals(1000, alice.balance(assetId));\n```\n\n### State changes of InvokeScript transaction\n\nIf connected node stores state changes of InvokeScript transactions (depends of the node config), you can check it:\n\n```java\nString txId = bob.invokes(i -\u003e i.dApp(alice)).getId().toString();\n\nStateChanges changes = node().api.stateChanges(txId);\n\nassertAll(\n    () -\u003e assertEquals(1, changes.data.size()),\n    () -\u003e assertEquals(bob.address(), changes.data.get(0).key),\n    () -\u003e assertEquals(100500, changes.data.get(0).asInteger())\n);\n```\n\n### Transactions rejection\n\nIf you expect that some transaction will return error and will not be in blockchain, you can check it by catching the NodeError exception.\n\nFor example, how it can look with JUnit 5:\n\n```java\nNodeError error = assertThrows(NodeError.class, () -\u003e\n        bob.invokes(i -\u003e i.dApp(alice).function(\"deposit\").payment(500, assetId))\n);\n\nassertTrue(error.getMessage().contains(\"can accept payment in waves tokens only!\"));\n```\n\n## Other features\n\n### Waitings\n\n#### Height\n\nPaddle allows to wait for the growth of the height of N blocks\n```java\nnode().waitNBlocks(2);\n```\nor until the height of the blockchain reaches the specified\n```java\nnode().waitForHeight(100);\n```\n\nThe both methods have \"soft\" timeouts. This means that they continue to wait until the height rises with the expected frequency. The expected frequency is a triple value of the `block-interval` field in Paddle config file, but can be redefined by optional argument:\n```java\nnode().waitNBlocks(2, waitingInSeconds);\nnode().waitForHeight(100, waitingInSeconds);\n```\n\n#### Transaction\n\nAlso Paddle can wait until transaction with specified id is in blockchain:\n```java\nnode().waitForTransaction(txId);\n```\nThe timeout value is specified in `block-interval` field in Paddle config file, but can be redefined by optional argument.\n\n### Asynchronous actions\n\nTo save execution time (or for other reasons in specific cases) it would be great to be able to perform some actions asynchronously.\n\nFor example, we want create some test account and each of them will issue asset:\n```java\nAccount alice = new Account(1_00000000);\nAccount bob = new Account(1_00000000);\nAccount carol = new Account(1_00000000);\nalice.issues(a -\u003e a.name(\"Asset 1\"));\nbob.issues(a -\u003e a.name(\"Asset 2\"));\ncarol.issues(a -\u003e a.name(\"Asset 3\"));\n```\nThese 6 transactions will be sent consecutively. But with `Async.async()` their asynchronous execution will be three times faster:\n```java\nasync(\n    () -\u003e {\n        Account alice = new Account(1_00000000);\n        alice.issues(a -\u003e a.name(\"Asset 1\"));\n    }, () -\u003e {\n        Account bob = new Account(1_00000000);\n        bob.issues(a -\u003e a.name(\"Asset 2\"));\n    }, () -\u003e {\n        Account carol = new Account(1_00000000);\n        carol.issues(a -\u003e a.name(\"Asset 3\"));\n    }\n);\n```\n\nAt now, all operations will be executed in three threads, and only dependent transactions will be sent consecutively.\n\n`Async` waits for all operations to be completed.\n\n### RSA Encryption\n\nFunction `rsaVerify()` in Ride checks that the RSA signature is valid, i.e. it was created by the owner of the public key.\n\nWith Paddle you can create a signature like this:\n```java\nRsa rsa = new Rsa(); // generated private and public keys pair\nbyte[] prKey = rsa.privateKey();\nbyte[] pubKey = rsa.publicKey();\n// signature created by the private key with SHA256 algorithm\nbyte[] signature = rsa.sign(HashAlg.SHA256, \"Hello!\".getBytes());\n```\n\n`HashAlg` contains all hashing algorithms supported in Ride:\n* NOALG\n* MD5\n* SHA1\n* SHA224\n* SHA256\n* SHA384\n* SHA512\n* SHA3_224\n* SHA3_256\n* SHA3_384\n* SHA3_512\n\n### Merkle Tree\n\nFunction `checkMerkleProof()` in Ride checks that the data is part of the Merkle tree.\n\nWith Paddle you can create such a Merkle tree and get data proofs:\n```java\nList\u003cbyte[]\u003e leafs = asList(\"one\".getBytes(), \"two\".getBytes(), \"three\".getBytes());\nMerkleTree tree = new MerkleTree(leafs);\nbyte[] rootHash = tree.rootHash();\nbyte[] proof = tree.proofByLeaf(\"two\".getBytes()).get();\n```\n\n## What next?\n\nSee [tests](https://github.com/msmolyakov/paddle/tree/master/src/test/java/im/mak/paddle) in the repository for examples how the Paddle can be used.\n\n[paddle-example](https://github.com/msmolyakov/paddle-example) - project boilerplate with example of Waves dApp and tests with Paddle.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsmolyakov%2Fpaddle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmsmolyakov%2Fpaddle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsmolyakov%2Fpaddle/lists"}