From 9f008cf19bd74eeb727c08729e7974e92f1d1e1b Mon Sep 17 00:00:00 2001 From: Owen Voke Date: Wed, 29 May 2024 12:58:43 +0100 Subject: [PATCH] feat: rewrite in TypeScript --- .github/CODE_OF_CONDUCT.md | 74 ---- .github/CONTRIBUTING.md | 6 +- .github/FUNDING.yml | 4 + .github/ISSUE_TEMPLATE.md | 19 - .github/ISSUE_TEMPLATE/bug-report.yml | 53 +++ .github/ISSUE_TEMPLATE/feature-request.yml | 21 + .github/PULL_REQUEST_TEMPLATE.md | 20 +- .github/workflows/tests.yml | 58 ++- .gitignore | 1 + .nvmrc | 1 + .prettierignore | 1 + .prettierrc | 1 + CHANGELOG.md | 57 +-- LICENSE.md | 30 +- README.md | 29 +- docs/README.md | 193 --------- eslint.config.js | 28 ++ package.json | 61 ++- src/Arionum.js | 398 ------------------ src/Transaction.js | 134 ------ src/api/checkAddress.ts | 22 + src/api/checkSignature.ts | 21 + src/api/generateAccount.ts | 16 + src/api/getAddress.ts | 19 + src/api/getAlias.ts | 28 ++ src/api/getBalance.ts | 30 ++ src/api/getBase58.ts | 20 + src/api/getBlock.ts | 20 + src/api/getBlockTransactions.ts | 39 ++ src/api/getCurrentBlock.ts | 16 + src/api/getMasternodes.ts | 20 + src/api/getMempoolSize.ts | 15 + src/api/getNodeInfo.ts | 16 + src/api/getNodeSanityInfo.ts | 16 + src/api/getNodeVersion.ts | 15 + src/api/getPendingBalance.ts | 28 ++ src/api/getPublicKey.ts | 19 + src/api/getRandomNumber.ts | 30 ++ src/api/getTransaction.ts | 20 + src/api/getTransactions.ts | 35 ++ src/api/index.ts | 22 + src/api/models/account-keypair.model.ts | 5 + src/api/models/block.model.ts | 10 + src/api/models/index.ts | 7 + src/api/models/masternode.model.ts | 29 ++ src/api/models/node-info.model.ts | 27 ++ src/api/models/sanity.model.ts | 13 + src/api/models/transaction-type.model.ts | 1 + src/api/models/transaction.model.ts | 37 ++ src/api/sendTransaction.ts | 26 ++ src/index.js | 2 - src/index.ts | 2 + src/set-version.js | 6 + src/utils/internal/apiBaseUrl.ts | 19 + src/utils/internal/buildRequestUrl.ts | 24 ++ src/utils/internal/call.ts | 29 ++ src/utils/internal/index.ts | 3 + src/utils/public/buildNodeConfiguration.ts | 17 + src/utils/public/index.ts | 2 + src/utils/public/models/index.ts | 1 + .../public/models/node-configuration.model.ts | 3 + tsconfig.json | 9 + 62 files changed, 964 insertions(+), 964 deletions(-) delete mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/FUNDING.yml delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .prettierrc delete mode 100644 docs/README.md create mode 100644 eslint.config.js delete mode 100644 src/Arionum.js delete mode 100644 src/Transaction.js create mode 100644 src/api/checkAddress.ts create mode 100644 src/api/checkSignature.ts create mode 100644 src/api/generateAccount.ts create mode 100644 src/api/getAddress.ts create mode 100644 src/api/getAlias.ts create mode 100644 src/api/getBalance.ts create mode 100644 src/api/getBase58.ts create mode 100644 src/api/getBlock.ts create mode 100644 src/api/getBlockTransactions.ts create mode 100644 src/api/getCurrentBlock.ts create mode 100644 src/api/getMasternodes.ts create mode 100644 src/api/getMempoolSize.ts create mode 100644 src/api/getNodeInfo.ts create mode 100644 src/api/getNodeSanityInfo.ts create mode 100644 src/api/getNodeVersion.ts create mode 100644 src/api/getPendingBalance.ts create mode 100644 src/api/getPublicKey.ts create mode 100644 src/api/getRandomNumber.ts create mode 100644 src/api/getTransaction.ts create mode 100644 src/api/getTransactions.ts create mode 100644 src/api/index.ts create mode 100644 src/api/models/account-keypair.model.ts create mode 100644 src/api/models/block.model.ts create mode 100644 src/api/models/index.ts create mode 100644 src/api/models/masternode.model.ts create mode 100644 src/api/models/node-info.model.ts create mode 100644 src/api/models/sanity.model.ts create mode 100644 src/api/models/transaction-type.model.ts create mode 100644 src/api/models/transaction.model.ts create mode 100644 src/api/sendTransaction.ts delete mode 100644 src/index.js create mode 100644 src/index.ts create mode 100644 src/set-version.js create mode 100644 src/utils/internal/apiBaseUrl.ts create mode 100644 src/utils/internal/buildRequestUrl.ts create mode 100644 src/utils/internal/call.ts create mode 100644 src/utils/internal/index.ts create mode 100644 src/utils/public/buildNodeConfiguration.ts create mode 100644 src/utils/public/index.ts create mode 100644 src/utils/public/models/index.ts create mode 100644 src/utils/public/models/node-configuration.model.ts create mode 100644 tsconfig.json diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 7c78703..0000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,74 +0,0 @@ -# Contributor Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to make participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at `development@pxgamer.xyz`. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at [https://contributor-covenant.org/version/1/4][version] - -[homepage]: https://contributor-covenant.org -[version]: https://contributor-covenant.org/version/1/4 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 0e6f8ac..4941ba0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,11 +2,11 @@ Contributions are **welcome** and will be fully **credited**. -We accept contributions via Pull Requests on [Github](https://github.com/pxgamer/arionum-js). +We accept contributions via Pull Requests on [GitHub](https://github.com/owenvoke/arionum-js). ## Pull Requests -- **[StandardJS Coding Standard](https://standardjs.com)** - Check the code style with ``$ npm check-style`` and fix it with ``$ npm fix-style``. +- **Prettier Coding Standard** - This is automatically linted by Prettier. - **Add tests!** - If possible, try to add tests to support your patch. @@ -14,6 +14,6 @@ We accept contributions via Pull Requests on [Github](https://github.com/pxgamer - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org). Randomly breaking public APIs is not an option. -- **Create feature branches** - Don't ask us to pull from your master branch. +- **Create feature branches** - Don't ask us to pull from your main branch. - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..e51e359 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: owenvoke +custom: "https://ecologi.com/owenvoke?gift-trees" diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 544409e..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,19 +0,0 @@ - - -## Description - -Provide a detailed description of the change or addition you are proposing. - -Make it clear if the issue is a bug, an enhancement or just a question. - -## Possible implementation - -Not obligatory, but suggest an idea for implementing addition or change. - -## Your environment - -Include as many relevant details about the environment you experienced the bug in and how to reproduce it. - -* Version used (e.g. PHP 7.3): -* Operating system and version (e.g. Ubuntu 18.10, Windows 10): -* ... diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..bdec98c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,53 @@ +name: Bug Report +description: If something isn't working as expected. +labels: [ 'type: bug' ] + +body: + + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + - type: textarea + id: description + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + validations: + required: true + + - type: input + id: library-version + attributes: + label: Library version + description: What version of the library are you using? Please be as specific as possible. + validations: + required: true + + - type: input + id: php-version + attributes: + label: Node version + description: What version of PHP are you using? Please be as specific as possible. + placeholder: 21.x + validations: + required: true + + - type: dropdown + id: operating-system + attributes: + label: What operating systems are you seeing the problem on? + multiple: true + options: + - macOS + - Linux + - Windows + validations: + required: true + + - type: textarea + id: notes + attributes: + label: Notes + description: Use this field to provide any other notes that you feel might be relevant to the issue. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000..b1992ae --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,21 @@ +name: Feature Request +description: If you have a suggestion (and may want to implement it). +labels: [ 'type: discussion' ] + +body: + + - type: textarea + id: description + attributes: + label: Description + description: Explain the change or addition that you are proposing. + validations: + required: true + + - type: textarea + id: implementation + attributes: + label: Possible implementation + description: Not obligatory, but suggest an idea for implementing addition or change. + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e11dfb6..481c8b2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,23 +1,5 @@ -## Description - ... -## Types of changes - -Put an `x` in all the boxes that apply: - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to change) - -## Checklist - -Put an `x` in all the boxes that apply: - -- [ ] I have read the **[CONTRIBUTING](CONTRIBUTING.md)** document. -- [ ] My pull request addresses exactly one patch/feature. -- [ ] I have created a branch for this patch/feature. - -If you're unsure about any of these, don't hesitate to ask. We're here to help! +Please check the **[CONTRIBUTING](https://github.com/owenvoke/.github/blob/main/CONTRIBUTING.md)** document for more information. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3ee497e..fbf6a4e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,37 +1,29 @@ name: Tests -on: ['push', 'pull_request'] +on: ["push", "pull_request"] jobs: - ci: - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [ubuntu-latest] - node: ['15.x'] - - name: Node ${{ matrix.node }} - ${{ matrix.os }} - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: Setup Node - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node }} - - - name: Install npm dependencies - run: npm i - - - name: Run tests - run: npm test + ci: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest] + + name: Node - ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + cache: "npm" + + - name: Install npm dependencies + run: npm ci + + - name: Run tests + run: npm test diff --git a/.gitignore b/.gitignore index 3c5e5b6..1e575b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +dist package-lock.json yarn.lock node_modules diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..b009dfb --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +lts/* diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1521c8b --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +dist diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e4a582..8e88088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,60 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ## Unreleased -## [v1.3.4] - 2021-05-03 - -### Fixed -- Fix npm `test` script ([15b18c4](https://github.com/owenvoke/arionum-js/commit/15b18c48a66f53356591ae1456ad70dd95b1b25b)) - -### Changed -- Update dependencies and details ([8f35ae9](https://github.com/owenvoke/arionum-js/commit/8f35ae9af5d01c904ddb2e8419375ca222ff4f78)) - -## [v1.3.3] - 2019-06-03 - -### Fixed -- Fix vulnerability in axios using Snyk ([d1edbc8](https://github.com/owenvoke/arionum-js/commit/d1edbc86c379101bf1228bd215c8b7233226d70c)) - -## [v1.3.2] - 2019-05-07 - -### Fixed -- Fix vulnerability in axios using Snyk ([#3](https://github.com/owenvoke/arionum-js/pull/3)) - -## [v1.3.1] - 2019-02-28 - -### Changed -- Move documentation from the README to a `docs` directory ([0fb6a9d](https://github.com/owenvoke/arionum-js/commit/0fb6a9d57c0922499cf7b2055f4650d455b4ad6e)) - -### Removed -- Remove deprecated Travis containerisation ([141322e](https://github.com/owenvoke/arionum-js/commit/141322e0b8e8dc6ccc6e251347f8681fd5a409ea)) - -## [v1.3.0] - 2018-11-05 +## 2.0.0 ### Added -- Add the `checkSignature` method ([167c8468](https://github.com/owenvoke/arionum-js/commit/167c8468114de8d9dc67ee2d6fd8a14320e83118)) -- Add the `checkAddress` method ([610ffbf0](https://github.com/owenvoke/arionum-js/commit/610ffbf01c8017c6dda6df35c0ff532cc036be15)) -- Add the `getTransactionsByPublicKey` method ([162bd370](https://github.com/owenvoke/arionum-js/commit/162bd370054bf9702cc7418b16dc2d49468ffd48)) -- Add the `getBalanceByAlias` and `getBalanceByPublicKey` methods ([1afd3958](https://github.com/owenvoke/arionum-js/commit/1afd3958ee2ee9a939acac1fbcd807fd50232e0f)) - -## [v1.2.0] - 2018-10-15 - -### Added -- Add the `getSanityDetails` method ([5bbfb09](https://github.com/owenvoke/arionum-js/commit/5bbfb09da94028cf10e12e6002812e5138a7905d)) -- Add the `getNodeInfo` method ([f212124](https://github.com/owenvoke/arionum-js/commit/f212124fec5b04906b394cec697b59125f9113d2)) -- Add Travis configuration ([6dbb2d3](https://github.com/owenvoke/arionum-js/commit/6dbb2d35d8c4a3ebb27af5bd412c9b8b0a28aaa3)) - -## [v1.1.0] - 2018-09-27 - -### Added -- Add the `sendTransaction` method and `Transaction` class ([#1](https://github.com/owenvoke/arionum-js/issues/1)) - -## v1.0.0 - 2018-09-27 - -### Added -- Initial release - -[v1.3.3]: https://github.com/owenvoke/arionum-js/compare/v1.3.2...v1.3.3 -[v1.3.2]: https://github.com/owenvoke/arionum-js/compare/v1.3.1...v1.3.2 -[v1.3.1]: https://github.com/owenvoke/arionum-js/compare/v1.3.0...v1.3.1 -[v1.3.0]: https://github.com/owenvoke/arionum-js/compare/v1.2.0...v1.3.0 -[v1.2.0]: https://github.com/owenvoke/arionum-js/compare/v1.1.0...v1.2.0 -[v1.1.0]: https://github.com/owenvoke/arionum-js/compare/v1.0.0...v1.1.0 +- Rewrite using TypeScript diff --git a/LICENSE.md b/LICENSE.md index cd9451a..bf56adc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -2,20 +2,20 @@ Copyright (c) 2018 Owen Voke ->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: +> 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 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. +> 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 index 8927827..d7f93b5 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,30 @@ An API wrapper for the Arionum cryptocurrency node. Via npm -```bash -$ npm install arionum-js +```shell +npm install arionum-js ``` ## Usage -Please see the [usage docs](docs/README.md) directory for information on using this library. +Initialise the Arionum node configuration object: + +```typescript +import { buildNodeConfiguration } from "arionum-js"; + +const uri = 'http://peer1.arionum.com' + +const nodeConfiguration = buildNodeConfiguration({ uri }) +``` + +Then pass this to any of the SDK functions: + +```typescript +import { getBlock } from "arionum-js"; + +// This returns basic metadata about the specified block. +const block = getBlock(nodeConfiguration, { height: 12345 }); +``` ## Change log @@ -34,8 +51,8 @@ If you discover any security related issues, please email security@voke.dev inst ## Credits -- [Owen Voke][link-author] -- [All Contributors][link-contributors] +- [Owen Voke][link-author] +- [All Contributors][link-contributors] ## License @@ -53,7 +70,7 @@ Read more about Treeware at [treeware.earth][link-treeware]. [ico-version]: https://img.shields.io/npm/v/arionum-js.svg?style=flat-square [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square -[ico-github-actions]: https://img.shields.io/github/workflow/status/owenvoke/arionum-js/Tests.svg?style=flat-square +[ico-github-actions]: https://img.shields.io/github/actions/workflow/status/owenvoke/arionum-js/tests.yml?branch=main&style=flat-square [ico-downloads]: https://img.shields.io/npm/dt/arionum-js.svg?style=flat-square [ico-treeware-gifting]: https://img.shields.io/badge/Treeware-%F0%9F%8C%B3-lightgreen?style=flat-square diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 5e19b4c..0000000 --- a/docs/README.md +++ /dev/null @@ -1,193 +0,0 @@ -## Usage - -**Set the node base URI** - -```js -const arionum = new Arionum() -arionum.nodeAddress = 'https://node-uri-here' -``` - -**Get an address from a public key** - -```js -arionum.getAddress('public-key').then(address => { - console.log(address) -}) -``` - -**Get a Base58-encoded version of a string** - -```js -arionum.getBase58('string-data').then(base58 => { - console.log(base58) -}) -``` - -**Get the balance for an address** - -```js -arionum.getBalance('address').then(balance => { - console.log(balance) -}) -``` - -**Get the pending balance for an address** - -```js -arionum.getPendingBalance('address').then(pendingBalance => { - console.log(pendingBalance) -}) -``` - -**Get the transactions for an address** - -```js -arionum.getTransactions('address').then(transactions => { - console.log(transactions) -}) -``` - -**Get the transactions for a public key** - -```js -arionum.getTransactionsByPublicKey('public-key').then(transactions => { - console.log(transactions) -}) -``` - -**Get the transaction by its id** - -```js -arionum.getTransaction('transaction-id').then(transaction => { - console.log(transaction) -}) -``` - -**Get the public key for an address** - -```js -arionum.getPublicKey('address').then(publicKey => { - console.log(publicKey) -}) -``` - -**Generate a new account** - -```js -arionum.generateAccount().then(accountDetails => { - console.log(accountDetails) -}) -``` - -**Get the current block** - -```js -arionum.getCurrentBlock().then(block => { - console.log(block) -}) -``` - -**Get a specific block by its height** - -```js -arionum.getBlock(1).then(block => { - console.log(block) -}) -``` - -**Get transactions for a specific block** - -```js -arionum.getBlockTransactions('block-id').then(transactions => { - console.log(transactions) -}) -``` - -**Get version of the current node** - -```js -arionum.getNodeVersion().then(version => { - console.log(version) -}) -``` - -**Get the number of transactions in the mempool** - -```js -arionum.getMempoolSize().then(size => { - console.log(size) -}) -``` - -**Get a random number based on a specified block** - -```js -arionum.getRandomNumber(1, 1, 1000).then(number => { - console.log(number) -}) -``` - -**Get a list of available masternodes on the network** - -```js -arionum.getMasternodes().then(masternodes => { - console.log(masternodes) -}) -``` - -**Get the alias for a specific address** - -```js -arionum.getAlias('address').then(alias => { - console.log(alias) -}) -``` - -**Send a transaction** - -```js -let transaction = new Transaction() - -transaction.value = 1 -transaction.destinationAddress = '...' -transaction.publicKey = '...' -transaction.signature = '...' -transaction.date = 1 -transaction.message = '...' - -arionum.sendTransaction(transaction).then(result => { - console.log(result) -}) -``` - -**Get details about the nodes sanity process** - -```js -arionum.getSanityDetails().then(sanityDetails => { - console.log(sanityDetails) -}) -``` - -**Get details about the node** - -```js -arionum.getNodeInfo().then(nodeInfo => { - console.log(nodeInfo) -}) -``` - -**Check the validity of a signature** - -```js -arionum.checkSignature('signature', 'data', 'public-key').then(signatureStatus => { - console.log(signatureStatus) -}) -``` - -**Check the validity of an address** - -```js -arionum.checkAddress('address').then(addressStatus => { - console.log(addressStatus) -}) -``` diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..94fd64c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,28 @@ +import globals from "globals"; +import tseslint from "typescript-eslint"; +import * as parser from "@typescript-eslint/parser"; +import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; + +export default [ + { + ignores: ["dist/**/*"], + }, + ...tseslint.configs.recommended, + eslintPluginPrettierRecommended, + { + languageOptions: { + parser: parser, + sourceType: "module", + globals: { + ...globals.node, + ...globals.jest, + }, + }, + rules: { + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off", + }, + }, +]; diff --git a/package.json b/package.json index 4354ac5..67cd022 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,58 @@ { "name": "arionum-js", - "version": "1.3.4", + "type": "module", + "source": "src/index.ts", "description": "An API wrapper for the Arionum cryptocurrency node.", - "main": "src/index.js", + "version": "2.0.0", + "typings": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/api.cjs", + "default": "./dist/api.modern.js" + } + }, + "main": "./dist/api.cjs", + "types": "./dist/index.d.ts", + "module": "./dist/api.module.js", + "unpkg": "./dist/api.umd.js", + "license": "MIT", + "files": [ + "dist", + "src" + ], "scripts": { - "lint": "standard \"src/**/*.js\" --fix", - "test:lint": "standard \"src/**/*.js\"", - "test": "npm run test:lint" + "prebuild": "node src/set-version.js", + "build": "microbundle", + "dev": "microbundle watch", + "format": "prettier --write . '**/*.{json,md,js,ts,tsx}'", + "format:write": "prettier --write . '**/*.{json,md,js,ts,tsx}'", + "format:check": "prettier --check . '**/*.{json,md,js,ts,tsx}'", + "lint": "eslint \"{src,test}/**/*.{ts,js}\"", + "lint:fix": "eslint \"{src,test}/**/*.{ts,js}\" --fix", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + }, + "devDependencies": { + "@tsconfig/recommended": "^1.0.6", + "@types/node": "^20.12.12", + "@typescript-eslint/eslint-plugin": "^7.11.0", + "@typescript-eslint/parser": "^7.11.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-simple-import-sort": "^12.1.0", + "microbundle": "^0.15.1", + "prettier": "^3.2.5", + "typescript": "^5.4.5", + "typescript-eslint": "^7.11.0" }, "repository": { "type": "git", "url": "https://github.com/owenvoke/arionum-js.git" }, - "author": "Owen Voke ", - "license": "MIT", - "dependencies": { - "axios": "^0.21.1" - }, - "devDependencies": { - "standard": "^16.0" - } + "author": "Owen Voke " } diff --git a/src/Arionum.js b/src/Arionum.js deleted file mode 100644 index c798746..0000000 --- a/src/Arionum.js +++ /dev/null @@ -1,398 +0,0 @@ -'use strict' - -const axios = require('axios') - -/** The request endpoint for API calls. */ -const API_ENDPOINT = '/api.php' -/** The API status code for a successful response. */ -const API_STATUS_OK = 'ok' - -class Arionum { - /** - * Retrieve the address for a specified public key. - * - * @param {string} publicKey - * @return Promise - */ - getAddress (publicKey) { - return this - .getJson({ - q: 'getAddress', - public_key: publicKey - }) - } - - /** - * Convert a string to Base58. - * - * @param {string} data - * @return Promise - */ - getBase58 (data) { - return this - .getJson({ - q: 'base58', - data: data - }) - } - - /** - * Retrieve the balance of a specified address. - * - * @param {string} address - * @return Promise - */ - getBalance (address) { - return this - .getJson({ - q: 'getBalance', - account: address - }) - } - - /** - * Retrieve the balance of a specified alias. - * - * @param {string} alias - * @return Promise - */ - getBalanceByAlias (alias) { - return this - .getJson({ - q: 'getBalance', - alias: alias - }) - } - - /** - * Retrieve the balance of a specified public key. - * - * @param {string} publicKey - * @return Promise - */ - getBalanceByPublicKey (publicKey) { - return this - .getJson({ - q: 'getBalance', - public_key: publicKey - }) - } - - /** - * Retrieve the pending balance of a specified address (includes pending transactions). - * - * @param {string} address - * @return Promise - */ - getPendingBalance (address) { - return this - .getJson({ - q: 'getPendingBalance', - account: address - }) - } - - /** - * Retrieve the transactions of a specified address. - * - * @param {string} address - * @return Promise - */ - getTransactions (address) { - return this - .getJson({ - q: 'getTransactions', - account: address - }) - } - - /** - * Retrieve the transactions of a specified public key. - * - * @param {string} publicKey - * @return Promise - */ - getTransactionsByPublicKey (publicKey) { - return this - .getJson({ - q: 'getTransactions', - public_key: publicKey - }) - } - - /** - * Retrieve a specified transaction by its id. - * - * @param {string} transactionId - * @return Promise - */ - getTransaction (transactionId) { - return this - .getJson({ - q: 'getTransaction', - transaction: transactionId - }) - } - - /** - * Retrieve the public key of a specified address. - * - * @param {string} address - * @return Promise - */ - getPublicKey (address) { - return this - .getJson({ - q: 'getPublicKey', - account: address - }) - } - - /** - * Generate a new public/private key pair and return these with the address. - * - * @return Promise - */ - generateAccount () { - return this - .getJson({ - q: 'generateAccount' - }) - } - - /** - * Retrieve the current block as an object. - * - * @return Promise - */ - getCurrentBlock () { - return this - .getJson({ - q: 'currentBlock' - }) - } - - /** - * Retrieve a block by its height. - * - * @param {int} height - * @return Promise - */ - getBlock (height) { - return this - .getJson({ - q: 'getBlock', - height: height - }) - } - - /** - * Retrieve the transactions of a specified block. - * - * @param {string} blockId - * @return Promise - */ - getBlockTransactions (blockId) { - return this - .getJson({ - q: 'getBlockTransactions', - block: blockId - }) - } - - /** - * Retrieve the version of the node. - * - * @return Promise - */ - getNodeVersion () { - return this - .getJson({ - q: 'version' - }) - } - - /** - * Send a transaction. - * - * @param {Transaction} transaction - * @return Promise - */ - sendTransaction (transaction) { - return this - .getJson({ - q: 'send', - val: transaction.value, - dst: transaction.destinationAddress, - public_key: transaction.publicKey, - signature: transaction.signature, - private_key: transaction.privateKey, - date: transaction.date, - message: transaction.message, - version: transaction.version - }) - } - - /** - * Retrieve the number of transactions in the mempool. - * - * @return Promise - */ - getMempoolSize () { - return this - .getJson({ - q: 'mempoolSize' - }) - } - - /** - * Retrieve a random number based on a specified block. - * - * @param {int} height - * @param {int} minimum - * @param {int|null} maximum - * @param {string|null} seed - * @return Promise - */ - getRandomNumber (height, minimum, maximum, seed = null) { - return this - .getJson({ - q: 'randomNumber', - height: height, - min: minimum, - max: maximum, - seed: seed - }) - } - - /** - * Check that a signature is valid against a public key. - * - * @param {string} signature - * @param {string} data - * @param {string} publicKey - * @return Promise - */ - checkSignature (signature, data, publicKey) { - return this - .getJson({ - q: 'checkSignature', - signature: signature, - data: data, - public_key: publicKey - }) - .then(data => { - return data - }) - } - - /** - * Retrieve a list of registered masternodes on the network. - * - * @return Promise - */ - getMasternodes () { - return this - .getJson({ - q: 'masternodes' - }) - .then(data => { - return data.masternodes - }) - } - - /** - * Retrieve the alias for an account by it's address. - * - * @param {string} address - * @return Promise - */ - getAlias (address) { - return this - .getJson({ - q: 'getAlias', - account: address - }) - } - - /** - * Retrieve details about the nodes sanity process. - * - * @return Promise - */ - getSanityDetails () { - return this - .getJson({ - q: 'sanity' - }) - } - - /** - * Retrieve details about the node. - * - * @return Promise - */ - getNodeInfo () { - return this - .getJson({ - q: 'node-info' - }) - } - - /** - * Check that an address is valid. - * Optionally validate it against the corresponding public key. - * - * @param {string} address - * @param {string|null} publicKey - * @return Promise - */ - checkAddress (address, publicKey = null) { - return this - .getJson({ - q: 'checkAddress', - account: address, - public_key: publicKey - }) - } - - /** - * @returns void - */ - set nodeAddress (uri) { - this._nodeAddress = uri - } - - /** - * @returns string - */ - get nodeAddress () { - return this._nodeAddress - } - - /** - * @param {Object} params - * @return {*|Promise} - */ - async getJson (params) { - return axios - .get(this.nodeAddress + API_ENDPOINT, { - params - }) - .then(response => { - const data = response.data - - if (data.status === API_STATUS_OK) { - return data.data - } - - throw Error('An unknown API error occurred.') - }) - } -} - -// Export Arionum class -module.exports = Arionum - -// Allow use of default import syntax in TypeScript -module.exports.default = Arionum diff --git a/src/Transaction.js b/src/Transaction.js deleted file mode 100644 index edecd4d..0000000 --- a/src/Transaction.js +++ /dev/null @@ -1,134 +0,0 @@ -'use strict' - -class Transaction { - /** Transaction Constructor */ - constructor () { - this.version = 1 - } - - /** - * @param {number} value - * @return void - */ - set value (value) { - this._val = value - } - - /** - * @param {string} destinationAddress - * @return void - */ - set destinationAddress (destinationAddress) { - this._dst = destinationAddress - } - - /** - * @param {string} publicKey - * @return void - */ - set publicKey (publicKey) { - this._public_key = publicKey - } - - /** - * @param {string} signature - * @return void - */ - set signature (signature) { - this._signature = signature - } - - /** - * @param {string} privateKey - * @return void - */ - set privateKey (privateKey) { - this._private_key = privateKey - } - - /** - * @param {int} date - * @return void - */ - set date (date) { - this._date = date - } - - /** - * @param {string} message - * @return void - */ - set message (message) { - this._message = message - } - - /** - * @param {int} version - * @return void - */ - set version (version) { - this._version = version - } - - /** - * @return number - */ - get value () { - return this._val - } - - /** - * @return string - */ - get destinationAddress () { - return this._dst - } - - /** - * @return string - */ - get publicKey () { - return this._public_key - } - - /** - * @return string - */ - get signature () { - return this._signature - } - - /** - * @return string - */ - get privateKey () { - return this._private_key - } - - /** - * @return int - */ - get date () { - return this._date - } - - /** - * @return string - */ - get message () { - return this._message - } - - /** - * @return int - */ - get version () { - return this._version - } -} - -// Export Transaction class -module.exports = Transaction - -// Allow use of default import syntax in TypeScript -module.exports.default = Transaction diff --git a/src/api/checkAddress.ts b/src/api/checkAddress.ts new file mode 100644 index 0000000..1b33471 --- /dev/null +++ b/src/api/checkAddress.ts @@ -0,0 +1,22 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl, call } from "../utils/internal"; + +export const checkAddress = async ( + nodeConfiguration: NodeConfiguration, + payload: { address: string; publicKey?: string }, +): Promise => { + const { address, publicKey } = payload; + + const queryParams: Record = { + q: "checkAddress", + account: address, + }; + + if (publicKey) { + queryParams.public_key = publicKey; + } + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/checkSignature.ts b/src/api/checkSignature.ts new file mode 100644 index 0000000..a66dc47 --- /dev/null +++ b/src/api/checkSignature.ts @@ -0,0 +1,21 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; + +export const checkSignature = async ( + nodeConfiguration: NodeConfiguration, + payload: { data: string; signature: string; publicKey: string }, +): Promise => { + const { data, signature, publicKey } = payload; + + const queryParams: Record = { + q: "checkSignature", + data: data, + signature: signature, + public_key: publicKey, + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/generateAccount.ts b/src/api/generateAccount.ts new file mode 100644 index 0000000..1a928b5 --- /dev/null +++ b/src/api/generateAccount.ts @@ -0,0 +1,16 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; +import { AccountKeypair } from "./models"; + +export const generateAccount = async ( + nodeConfiguration: NodeConfiguration, +): Promise => { + const queryParams: Record = { + q: "generateAccount", + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getAddress.ts b/src/api/getAddress.ts new file mode 100644 index 0000000..6abaed9 --- /dev/null +++ b/src/api/getAddress.ts @@ -0,0 +1,19 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; + +export const getAddress = async ( + nodeConfiguration: NodeConfiguration, + payload: { publicKey: string }, +): Promise => { + const { publicKey } = payload; + + const queryParams: Record = { + q: "getAddress", + public_key: publicKey, + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getAlias.ts b/src/api/getAlias.ts new file mode 100644 index 0000000..25622f4 --- /dev/null +++ b/src/api/getAlias.ts @@ -0,0 +1,28 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; + +export const getAlias = async ( + nodeConfiguration: NodeConfiguration, + payload: { address: string; publicKey?: string }, +): Promise => { + const { address, publicKey } = payload; + + const queryParams: Record = { + q: "getAlias", + }; + + if (!publicKey && !address) { + throw new Error("At least one account identifier must be provided."); + } + + if (publicKey) { + queryParams.public_key = publicKey; + } else if (address) { + queryParams.account = address; + } + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getBalance.ts b/src/api/getBalance.ts new file mode 100644 index 0000000..80a4e55 --- /dev/null +++ b/src/api/getBalance.ts @@ -0,0 +1,30 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; + +export const getBalance = async ( + nodeConfiguration: NodeConfiguration, + payload: { address?: string; alias?: string; publicKey?: string }, +): Promise => { + const { address, alias, publicKey } = payload; + + const queryParams: Record = { + q: "getBalance", + }; + + if (!publicKey && !address && !alias) { + throw new Error("At least one account identifier must be provided."); + } + + if (publicKey) { + queryParams.public_key = publicKey; + } else if (address) { + queryParams.account = address; + } else if (alias) { + queryParams.alias = alias; + } + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getBase58.ts b/src/api/getBase58.ts new file mode 100644 index 0000000..96e12b5 --- /dev/null +++ b/src/api/getBase58.ts @@ -0,0 +1,20 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; + +/** Convert a string to Base58. */ +export const getBase58 = async ( + nodeConfiguration: NodeConfiguration, + payload: { data: string }, +): Promise => { + const { data } = payload; + + const queryParams: Record = { + q: "base58", + data: data, + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getBlock.ts b/src/api/getBlock.ts new file mode 100644 index 0000000..fdfa052 --- /dev/null +++ b/src/api/getBlock.ts @@ -0,0 +1,20 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; +import { Block } from "./models"; + +export const getBlock = async ( + nodeConfiguration: NodeConfiguration, + payload: { height: number }, +): Promise => { + const { height } = payload; + + const queryParams: Record = { + q: "getBlock", + height: height, + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getBlockTransactions.ts b/src/api/getBlockTransactions.ts new file mode 100644 index 0000000..8519cc4 --- /dev/null +++ b/src/api/getBlockTransactions.ts @@ -0,0 +1,39 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; +import { Transaction, transactionFromApi } from "./models"; + +export const getBlockTransactions = async ( + nodeConfiguration: NodeConfiguration, + payload: { + height?: number; + blockId?: string; + includeMiningRewards?: boolean; + }, +): Promise> => { + const { height, blockId, includeMiningRewards } = payload; + + const queryParams: Record = { + q: "getBlockTransactions", + }; + + if (!height && !blockId) { + throw new Error("At least one block identifier must be provided."); + } + + if (height) { + queryParams.height = height; + } else if (blockId) { + queryParams.block = blockId; + } + + if (includeMiningRewards) { + queryParams.includeMiningRewards = "true"; + } + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return (await call>({ url })).map((transaction: any) => + transactionFromApi(transaction), + ); +}; diff --git a/src/api/getCurrentBlock.ts b/src/api/getCurrentBlock.ts new file mode 100644 index 0000000..c6d7215 --- /dev/null +++ b/src/api/getCurrentBlock.ts @@ -0,0 +1,16 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; +import { Block } from "./models"; + +export const getCurrentBlock = async ( + nodeConfiguration: NodeConfiguration, +): Promise => { + const queryParams: Record = { + q: "currentBlock", + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getMasternodes.ts b/src/api/getMasternodes.ts new file mode 100644 index 0000000..34e6857 --- /dev/null +++ b/src/api/getMasternodes.ts @@ -0,0 +1,20 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; +import { Masternode, masternodesFromApi } from "./models"; + +export const getMasternodes = async ( + nodeConfiguration: NodeConfiguration, + payload: { id: string }, +): Promise> => { + const { id } = payload; + + const queryParams: Record = { + q: "masternodes", + transaction: id, + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return masternodesFromApi(await call>({ url })); +}; diff --git a/src/api/getMempoolSize.ts b/src/api/getMempoolSize.ts new file mode 100644 index 0000000..89ca07f --- /dev/null +++ b/src/api/getMempoolSize.ts @@ -0,0 +1,15 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; + +export const getMempoolSize = async ( + nodeConfiguration: NodeConfiguration, +): Promise => { + const queryParams: Record = { + q: "mempoolSize", + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getNodeInfo.ts b/src/api/getNodeInfo.ts new file mode 100644 index 0000000..6504908 --- /dev/null +++ b/src/api/getNodeInfo.ts @@ -0,0 +1,16 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; +import { NodeInfo, nodeInfoFromApi } from "./models"; + +export const getNodeInfo = async ( + nodeConfiguration: NodeConfiguration, +): Promise => { + const queryParams: Record = { + q: "node-info", + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return nodeInfoFromApi(await call({ url })); +}; diff --git a/src/api/getNodeSanityInfo.ts b/src/api/getNodeSanityInfo.ts new file mode 100644 index 0000000..27a194e --- /dev/null +++ b/src/api/getNodeSanityInfo.ts @@ -0,0 +1,16 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; +import { Sanity, sanityFromApi } from "./models"; + +export const getNodeSanityInfo = async ( + nodeConfiguration: NodeConfiguration, +): Promise => { + const queryParams: Record = { + q: "sanity", + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return sanityFromApi(await call({ url })); +}; diff --git a/src/api/getNodeVersion.ts b/src/api/getNodeVersion.ts new file mode 100644 index 0000000..6d02401 --- /dev/null +++ b/src/api/getNodeVersion.ts @@ -0,0 +1,15 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; + +export const getNodeVersion = async ( + nodeConfiguration: NodeConfiguration, +): Promise => { + const queryParams: Record = { + q: "version", + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getPendingBalance.ts b/src/api/getPendingBalance.ts new file mode 100644 index 0000000..d5af47e --- /dev/null +++ b/src/api/getPendingBalance.ts @@ -0,0 +1,28 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; + +export const getPendingBalance = async ( + nodeConfiguration: NodeConfiguration, + payload: { address?: string; publicKey?: string }, +): Promise => { + const { address, publicKey } = payload; + + const queryParams: Record = { + q: "getPendingBalance", + }; + + if (!publicKey && !address) { + throw new Error("At least one account identifier must be provided."); + } + + if (publicKey) { + queryParams.public_key = publicKey; + } else if (address) { + queryParams.account = address; + } + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getPublicKey.ts b/src/api/getPublicKey.ts new file mode 100644 index 0000000..2d2ea1e --- /dev/null +++ b/src/api/getPublicKey.ts @@ -0,0 +1,19 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; + +export const getPublicKey = async ( + nodeConfiguration: NodeConfiguration, + payload: { address: string }, +): Promise => { + const { address } = payload; + + const queryParams: Record = { + q: "getPublicKey", + account: address, + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getRandomNumber.ts b/src/api/getRandomNumber.ts new file mode 100644 index 0000000..fe92b7e --- /dev/null +++ b/src/api/getRandomNumber.ts @@ -0,0 +1,30 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; + +export const getRandomNumber = async ( + nodeConfiguration: NodeConfiguration, + payload: { + height: number; + minimum: number; + maximum: number; + seed?: string; + }, +): Promise => { + const { height, minimum, maximum, seed } = payload; + + const queryParams: Record = { + q: "randomNumber", + height: height, + min: minimum, + max: maximum, + }; + + if (seed) { + queryParams.seed = seed; + } + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/api/getTransaction.ts b/src/api/getTransaction.ts new file mode 100644 index 0000000..ada6765 --- /dev/null +++ b/src/api/getTransaction.ts @@ -0,0 +1,20 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; +import { Transaction, transactionFromApi } from "./models"; + +export const getTransaction = async ( + nodeConfiguration: NodeConfiguration, + payload: { id: string }, +): Promise => { + const { id } = payload; + + const queryParams: Record = { + q: "getTransaction", + transaction: id, + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return transactionFromApi(await call({ url })); +}; diff --git a/src/api/getTransactions.ts b/src/api/getTransactions.ts new file mode 100644 index 0000000..cf858e3 --- /dev/null +++ b/src/api/getTransactions.ts @@ -0,0 +1,35 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; +import { Transaction, transactionFromApi } from "./models"; + +export const getTransactions = async ( + nodeConfiguration: NodeConfiguration, + payload: { account?: string; publicKey?: string; limit?: number }, +): Promise> => { + const { account, limit, publicKey } = payload; + + const queryParams: Record = { + q: "getTransactions", + }; + + if (!publicKey && !account) { + throw new Error("At least one account identifier must be provided."); + } + + if (publicKey) { + queryParams.public_key = publicKey; + } else if (account) { + queryParams.account = account; + } + + if (limit) { + queryParams.limit = limit; + } + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return (await call>({ url })).map((transaction: any) => + transactionFromApi(transaction), + ); +}; diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..1490c75 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,22 @@ +export * from "./checkAddress"; +export * from "./checkSignature"; +export * from "./generateAccount"; +export * from "./getAddress"; +export * from "./getAlias"; +export * from "./getBalance"; +export * from "./getBase58"; +export * from "./getBlock"; +export * from "./getBlockTransactions"; +export * from "./getCurrentBlock"; +export * from "./getMasternodes"; +export * from "./getMempoolSize"; +export * from "./getNodeInfo"; +export * from "./getNodeSanityInfo"; +export * from "./getNodeVersion"; +export * from "./getPendingBalance"; +export * from "./getPublicKey"; +export * from "./getRandomNumber"; +export * from "./getTransaction"; +export * from "./getTransactions"; +export * from "./sendTransaction"; +export * from "./models"; diff --git a/src/api/models/account-keypair.model.ts b/src/api/models/account-keypair.model.ts new file mode 100644 index 0000000..391dda6 --- /dev/null +++ b/src/api/models/account-keypair.model.ts @@ -0,0 +1,5 @@ +export interface AccountKeypair { + address: string; + publicKey: string; + privateKey: string; +} diff --git a/src/api/models/block.model.ts b/src/api/models/block.model.ts new file mode 100644 index 0000000..0a1e2cd --- /dev/null +++ b/src/api/models/block.model.ts @@ -0,0 +1,10 @@ +export interface Block { + id: string; + generator: string; + height: number; + date: number; + nonce: string; + signature: string; + difficulty: number; + argon: string; +} diff --git a/src/api/models/index.ts b/src/api/models/index.ts new file mode 100644 index 0000000..1c682f2 --- /dev/null +++ b/src/api/models/index.ts @@ -0,0 +1,7 @@ +export * from "./account-keypair.model"; +export * from "./block.model"; +export * from "./masternode.model"; +export * from "./node-info.model"; +export * from "./sanity.model"; +export * from "./transaction.model"; +export * from "./transaction-type.model"; diff --git a/src/api/models/masternode.model.ts b/src/api/models/masternode.model.ts new file mode 100644 index 0000000..83d2f04 --- /dev/null +++ b/src/api/models/masternode.model.ts @@ -0,0 +1,29 @@ +export interface Masternode { + publicKey: string; + height: number; + ip: string; + lastWonBlock: number; + blacklist: number; + fails: number; + status: boolean; + voteKey: string | null; + coldLastWonBlock: number; + voted: number; +} + +export const masternodesFromApi = (data: any): Array => { + return data.masternodes.map((masternode: any): Masternode => { + return { + publicKey: masternode.public_key, + height: masternode.height, + ip: masternode.ip, + lastWonBlock: masternode.last_won, + blacklist: masternode.blacklist, + fails: masternode.fails, + status: masternode.status, + voteKey: masternode.vote_key, + coldLastWonBlock: masternode.cold_last_won, + voted: masternode.voted, + }; + }); +}; diff --git a/src/api/models/node-info.model.ts b/src/api/models/node-info.model.ts new file mode 100644 index 0000000..88e98d1 --- /dev/null +++ b/src/api/models/node-info.model.ts @@ -0,0 +1,27 @@ +export interface NodeInfo { + hostname: string; + version: string; + databaseVersion: string; + statistics: { + accounts: number; + transactions: number; + mempoolTransactions: number; + masternodes: number; + peers: number; + }; +} + +export const nodeInfoFromApi = (data: any): NodeInfo => { + return { + hostname: data.hostname, + version: data.version, + databaseVersion: data.dbversion, + statistics: { + accounts: data.accounts, + transactions: data.transactions, + mempoolTransactions: data.mempool, + masternodes: data.masternodes, + peers: data.peers, + }, + }; +}; diff --git a/src/api/models/sanity.model.ts b/src/api/models/sanity.model.ts new file mode 100644 index 0000000..aac0b6f --- /dev/null +++ b/src/api/models/sanity.model.ts @@ -0,0 +1,13 @@ +export interface Sanity { + isRunning: boolean; + isSynchronising: boolean; + lastRun: number; +} + +export const sanityFromApi = (data: any): Sanity => { + return { + isRunning: data.sanity_running, + isSynchronising: data.sanity_sync, + lastRun: data.last_sanity, + }; +}; diff --git a/src/api/models/transaction-type.model.ts b/src/api/models/transaction-type.model.ts new file mode 100644 index 0000000..004c31c --- /dev/null +++ b/src/api/models/transaction-type.model.ts @@ -0,0 +1 @@ +export type TransactionType = "debit" | "credit" | "mempool"; diff --git a/src/api/models/transaction.model.ts b/src/api/models/transaction.model.ts new file mode 100644 index 0000000..fff3b76 --- /dev/null +++ b/src/api/models/transaction.model.ts @@ -0,0 +1,37 @@ +import { TransactionType } from "./transaction-type.model"; + +export interface Transaction { + id: string; + block: string; + blockHeight: number; + confirmations: number; + sourceAddress: string; + destinationAddress: string; + value: number; + fee: number; + publicKey: string; + signature: string; + message: string; + type: TransactionType; + date: number; + version: number; +} + +export const transactionFromApi = (data: any): Transaction => { + return { + id: data.id, + block: data.block, + blockHeight: data.height, + confirmations: data.confirmation, + sourceAddress: data.src, + destinationAddress: data.dst, + value: data.val, + fee: data.fee, + publicKey: data.public_key, + signature: data.signature, + message: data.message, + type: data.type, + date: data.date, + version: data.version, + }; +}; diff --git a/src/api/sendTransaction.ts b/src/api/sendTransaction.ts new file mode 100644 index 0000000..05663e6 --- /dev/null +++ b/src/api/sendTransaction.ts @@ -0,0 +1,26 @@ +import { NodeConfiguration } from "../utils/public"; +import { buildRequestUrl } from "../utils/internal"; +import { call } from "../utils/internal"; +import { Transaction } from "./models"; + +export const sendTransaction = async ( + nodeConfiguration: NodeConfiguration, + payload: { transaction: Transaction }, +): Promise => { + const { transaction } = payload; + + const queryParams: Record = { + q: "send", + val: transaction.value, + dst: transaction.destinationAddress, + public_key: transaction.publicKey, + signature: transaction.signature, + date: transaction.date, + message: transaction.message, + version: transaction.version, + }; + + const url = buildRequestUrl(nodeConfiguration.url, "/api.php", queryParams); + + return await call({ url }); +}; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 4be1431..0000000 --- a/src/index.js +++ /dev/null @@ -1,2 +0,0 @@ -module.exports = require('./Arionum') -module.exports.Transaction = require('./Transaction') diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4cdb14a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export * from "./api"; +export * from "./utils/public"; diff --git a/src/set-version.js b/src/set-version.js new file mode 100644 index 0000000..4f6a161 --- /dev/null +++ b/src/set-version.js @@ -0,0 +1,6 @@ +import packageJson from "../package.json" assert { type: "json" }; + +const { version } = packageJson; + +process.env.PACKAGE_VERSION = version; +console.log(`ℹ️ Set User-Agent header version variable to ${version}`); diff --git a/src/utils/internal/apiBaseUrl.ts b/src/utils/internal/apiBaseUrl.ts new file mode 100644 index 0000000..0b4d73e --- /dev/null +++ b/src/utils/internal/apiBaseUrl.ts @@ -0,0 +1,19 @@ +export const defaultNodes = [ + "http://peer1.arionum.com", + "http://peer2.arionum.com", + "http://peer3.arionum.com", + "http://peer4.arionum.com", + "http://peer5.arionum.com", + "http://peer6.arionum.com", + "http://peer7.arionum.com", + "http://peer8.arionum.com", + "http://peer9.arionum.com", +]; + +export const apiUrlConfiguration: { url: string | undefined } = { + url: undefined, +}; + +export const apiBaseUrl = async () => + apiUrlConfiguration.url || + defaultNodes[Math.floor(Math.random() * defaultNodes.length)]; diff --git a/src/utils/internal/buildRequestUrl.ts b/src/utils/internal/buildRequestUrl.ts new file mode 100644 index 0000000..985598f --- /dev/null +++ b/src/utils/internal/buildRequestUrl.ts @@ -0,0 +1,24 @@ +export const buildRequestUrl = ( + baseUrl: string, + endpointUrl: string, + args: Record = {}, +) => { + const concatenated = `${baseUrl}/${endpointUrl}`; + const withoutDoubleSlashes = concatenated.replaceAll(/([^:]\/)\/+/g, "$1"); + + let withArgs = withoutDoubleSlashes; + + const queryParamValues: Record = {}; + + for (const [argKey, argValue] of Object.entries(args)) { + // "abc.com/some-route/:foo/some-path" & {"foo": 4} --> "abc.com/some-route/4/some-path" + if (withArgs.includes(`:${argKey}`)) { + withArgs = withArgs.replace(`:${argKey}`, String(argValue)); + } else if (argValue !== undefined) { + queryParamValues[argKey] = String(argValue); + } + } + + const queryString = new URLSearchParams(queryParamValues).toString(); + return `${withArgs}?${queryString}`; +}; diff --git a/src/utils/internal/call.ts b/src/utils/internal/call.ts new file mode 100644 index 0000000..47659cb --- /dev/null +++ b/src/utils/internal/call.ts @@ -0,0 +1,29 @@ +const packageVersion = process.env?.["PACKAGE_VERSION"] ?? "Unknown"; + +export const call = async < + T extends readonly any[] | Record | any, +>(config: { + url: string; +}) => { + const { url } = config; + + const headers = new Headers({ + "User-Agent": `arionum-js/${packageVersion}`, + }); + + const rawResponse = await fetch(url, { headers }); + + if (!rawResponse.ok) { + throw new Error( + `HTTP Error: Status ${rawResponse.status} ${rawResponse.statusText}`, + ); + } + + const jsonResponse = await rawResponse.json(); + + if (jsonResponse.status !== "ok" || jsonResponse.data === undefined) { + throw new Error("An unknown API error occurred."); + } + + return jsonResponse.data as T; +}; diff --git a/src/utils/internal/index.ts b/src/utils/internal/index.ts new file mode 100644 index 0000000..2bb1e72 --- /dev/null +++ b/src/utils/internal/index.ts @@ -0,0 +1,3 @@ +export * from "./apiBaseUrl"; +export * from "./buildRequestUrl"; +export * from "./call"; diff --git a/src/utils/public/buildNodeConfiguration.ts b/src/utils/public/buildNodeConfiguration.ts new file mode 100644 index 0000000..91feb30 --- /dev/null +++ b/src/utils/public/buildNodeConfiguration.ts @@ -0,0 +1,17 @@ +import { NodeConfiguration } from "./models"; + +export const buildNodeConfiguration = ( + config: NodeConfiguration, +): NodeConfiguration => { + if (!config.url) { + throw new Error(` + buildNodeConfiguration() requires an object containing a URL. e.g.: + + const nodeConfiguration = buildNodeConfiguration({ + url: "http://peer1.arionum.com", + }) + `); + } + + return config; +}; diff --git a/src/utils/public/index.ts b/src/utils/public/index.ts new file mode 100644 index 0000000..bf8d812 --- /dev/null +++ b/src/utils/public/index.ts @@ -0,0 +1,2 @@ +export * from "./buildNodeConfiguration"; +export * from "./models"; diff --git a/src/utils/public/models/index.ts b/src/utils/public/models/index.ts new file mode 100644 index 0000000..6e72483 --- /dev/null +++ b/src/utils/public/models/index.ts @@ -0,0 +1 @@ +export * from "./node-configuration.model"; diff --git a/src/utils/public/models/node-configuration.model.ts b/src/utils/public/models/node-configuration.model.ts new file mode 100644 index 0000000..b5ced59 --- /dev/null +++ b/src/utils/public/models/node-configuration.model.ts @@ -0,0 +1,3 @@ +export interface NodeConfiguration { + url: string; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..aa96088 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@tsconfig/recommended/tsconfig.json", + "include": ["src", "types"], + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "moduleResolution": "node" + } +}