├── .github ├── dependabot.yml └── workflows │ └── codeql-analysis.yml ├── LICENSE ├── README.md ├── index.js └── package.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for npm 4 | - package-ecosystem: "npm" 5 | # Look for `package.json` and `lock` files in the `root` directory 6 | directory: "/" 7 | # Check the npm registry for updates every day (weekdays) 8 | schedule: 9 | interval: "daily" 10 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '39 13 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 The Circle foundation & Conceal Developers 4 | Copyright (c) 2018-2023 Conceal Network & Conceal Developers 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Conceal-API: Javascript/Node.js interface (RPC/API) 2 | Javascript/Node.js interface to Conceal cryptocurrency RPC/API. 3 | 4 | There are three RPC servers built in to the three programs *conceald*, *concealwallet* and *walletd*. 5 | They can each be started with the argument `--help` to display command line options. 6 | 7 | ### conceald 8 | A node on the P2P network (daemon) with no wallet functions; console interactive. To launch: 9 | ``` 10 | $ ./conceald 11 | ``` 12 | The default daemon RPC port is 16000 and the default P2P port is 15000. 13 | ### walletd 14 | A node on the P2P network (daemon) with wallet functions; console non-interactive. To launch, assuming that your `my.wallet` file is in the current directory: 15 | ``` 16 | $ ./walletd --container-file my.wallet --container-password PASSWD --local --bind-port 3333 17 | ``` 18 | The wallet functions RPC port is 3333. The default daemon P2P port is 15000. The default daemon RPC port is 16000. The `--local` option activates the daemon; otherwise, a remote daemon can be used. 19 | ### concealwallet 20 | A simple wallet; console interactive unless RPC server is running; requires access to a node daemon for full functionality. To launch, assuming that your `my.wallet` file is in the current directory: 21 | ``` 22 | $ ./concealwallet --rpc-bind-port 3333 --wallet-file my --password PASSWORD 23 | ``` 24 | The wallet functions RPC port is 3333. By default the wallet connects with the daemon on port 16000. It is possible to run several instances simultaneously using different wallets and ports. 25 | ## Quick start for node.js 26 | ``` 27 | $ npm install conceal-api 28 | $ ./conceald # launch the network daemon 29 | $ ./concealwallet --rpc-bind-port PORT --wallet-file my --password PASSWORD # launch the simple wallet 30 | ``` 31 | Create and run a test program. 32 | ``` 33 | $ node test.js 34 | ``` 35 | The test program could contain, for example, a payment via the simple wallet's RPC server 36 | ``` 37 | const CCX = require('conceal-api') 38 | const ccx = new CCX({ 39 | daemonHost: 'http://localhost', 40 | walletHost: 'http://localhost', 41 | daemonRpcPort: 16000, 42 | walletRpcPort: 3333 43 | }) 44 | 45 | ccx.send([{ 46 | address: 'ccx7Xd3NBbBiQNvv7vMLXmGMHyS8AVB6EhWoHo5EbGfR2Ki9pQnRTfEBt3YxYEVqpUCyJgvPjBYHp8N2yZwA7dqb4PjaGWuvs4', 47 | amount: 1234567 48 | }]) 49 | .then((res) => { console.log(res) }) // display tx hash upon success 50 | .catch((err) => { console.log(err) }) // display error message upon failure 51 | ``` 52 | ## API 53 | ``` 54 | const CCX = require('conceal-api') 55 | const ccx = new CCX({ 56 | daemonHost: , 57 | walletHost: , 58 | walletPath: , 59 | daemonRpcPort: , // port for daemon 60 | walletRpcPort: , // port for walletd 61 | walletRpcUser: , // optional, if not set no authentication will be made 62 | walletRpcPass: , // optional, if not set no authentication will be made 63 | timeout: // timeout for RPC calls 64 | }) 65 | ``` 66 | ccx.rpc returns a promise, where *rpc* is any of the methods below: 67 | 68 | * [Wallet RPC (must provide walletRpcPort)](#wallet) 69 | * concealwallet 70 | * [Get height](#height) 71 | * [Get balance](#balance) 72 | * [Get messages](#messages) 73 | * [Get incoming payments](#payments) 74 | * [Get transfers](#transfers) 75 | * [Get number of unlocked outputs](#outputs) 76 | * [Reset wallet](#reset) 77 | * [Store wallet](#store) 78 | * [Export wallet](#export-wallet-concealwallet) 79 | * [Export wallet keys](#export-wallet-keys-concealwallet) 80 | * [Optimize wallet](#optimize) 81 | * [Send transfers](#send) 82 | * walletd 83 | * [Reset or replace wallet](#resetOrReplace) 84 | * [Get status](#status) 85 | * [Get balance](#get-balance-walletd) 86 | * [Create address](#create-address-walletd) 87 | * [Create address list](#create-address-list-walletd) 88 | * [Delete address](#delete-address-walletd) 89 | * [Get addresses](#get-addresses-walletd) 90 | * [Create integrated](#create-integrated-address-walletd) 91 | * [Split integrated](#split-integrated-address-walletd) 92 | * [Get view secret Key](#get-view-secret-key-walletd) 93 | * [Get spend keys](#get-spend-keys-walletd) 94 | * [Get block hashes](#get-block-hashes-walletd) 95 | * [Get transaction](#get-transaction-walletd) 96 | * [Get unconfirmed transactions](#get-unconfirmed-transactions-walletd) 97 | * [Get transaction hashes](#get-transaction-hashes-walletd) 98 | * [Get transactions](#get-transactions-walletd) 99 | * [Send transaction](#send-transaction-walletd) 100 | * [Create delayed transaction](#create-delayed-transaction-walletd) 101 | * [Get delayed transaction hashes](#get-delayed-transaction-hashes-walletd) 102 | * [Delete delayed transaction](#delete-delayed-transaction-walletd) 103 | * [Send delayed transaction](#send-delayed-transation-walletd) 104 | * [Get incoming messages from transaction extra field](#get-incoming-messages-from-transaction-extra-field-walletd) 105 | * [Create Deposit](#create-deposit-walletd) 106 | * [Send Deposit](#send-deposit-walletd) 107 | * [Get Deposit](#get-deposit-walletd) 108 | * [Withdraw Deposit](#withdraw-deposit-walletd) 109 | * [Daemon RPC (must provide daemonRpcPort)](#daemon) 110 | * [Get info](#info) 111 | * [Get index](#index) 112 | * [Get count](#count) 113 | * [Get currency ID](#currencyId) 114 | * [Get block hash by height](#blockHashByHeight) 115 | * [Get block header by height](#blockHeaderByHeight) 116 | * [Get block header by hash](#blockHeaderByHash) 117 | * [Get last block header](#lastBlockHeader) 118 | * [Get block](#block) 119 | * [Get blocks](#blocks) 120 | * [Get block template](#blockTemplate) 121 | * [Submit block](#submitBlock) 122 | * [Get transaction](#transaction) 123 | * [Get transactions](#transactions) 124 | * [Get transaction pool](#transactionPool) 125 | * [Send raw transaction](#sendRawTransaction) 126 | 127 | ### Wallet RPC (must provide walletRpcPort) 128 | 129 | #### Get height (concealwallet) 130 | ``` 131 | ccx.height() // get last block height 132 | ``` 133 | #### Get balance (concealwallet) 134 | ``` 135 | ccx.balance() // get wallet balances 136 | ``` 137 | #### Get messages (concealwallet) 138 | ``` 139 | const opts = { 140 | firstTxId: FIRST_TX_ID, // (integer, optional), ex: 10 141 | txLimit: TX_LIMIT // maximum number of messages (integer, optional), ex: 10 142 | } 143 | ccx.messages(opts) // opts can be omitted 144 | ``` 145 | #### Get incoming payments (concealwallet) 146 | ``` 147 | const paymentId = PAYMENT_ID // (64-digit hex string, required), ex: '0ab1...3f4b' 148 | ccx.payments(paymentId) 149 | ``` 150 | #### Get transfers (concealwallet) 151 | ``` 152 | ccx.transfers() // gets all transfers 153 | ``` 154 | #### Get number of unlocked outputs (concealwallet) 155 | ``` 156 | ccx.outputs() // gets outputs available as inputs for a new transaction 157 | ``` 158 | #### Reset wallet (concealwallet) 159 | ``` 160 | ccx.reset() // discard wallet cache and resync with block chain 161 | ``` 162 | #### Store wallet (concealwallet) 163 | ``` 164 | ccx.store() // save wallet cache to disk 165 | ``` 166 | #### Export wallet (concealwallet) 167 | ``` 168 | const exportFilename = FILE_NAME // (string, required), ex: 'wallet.dat' 169 | ccx.exportWallet(exportFilename) // save wallet cache to disk 170 | ``` 171 | #### Export wallet keys (concealwallet) 172 | ``` 173 | const exportFilename = FILE_NAME // (string, required), ex: 'walletKeys.dat' 174 | ccx.exportWalletKeys(exportFilename) // save wallet cache to disk 175 | ``` 176 | #### Optimize wallet (concealwallet) 177 | ``` 178 | ccx.optimize() // combines many available outputs into a few by sending to self 179 | ``` 180 | #### Send transfers (concealwallet) 181 | ``` 182 | const transfers = [{ address: ADDRESS, amount: AMOUNT, message: MESSAGE }, ...] // ADDRESS = destination address (string, required), AMOUNT = raw CCX (integer, required), MESSAGE = transfer message to be encrypted (string, optional) 183 | const opts = { 184 | transfers: transfers, // (array, required), ex: [{ address: 'ccx7Xd...', amount: 1000, message: 'refund' }] 185 | fee: FEE, // (raw CCX integer, optional, default is minimum required), ex: 10 186 | anonimity: MIX_IN, // input mix count (integer, optional, default 2), ex: 6 187 | paymentId: PAYMENT_ID, // (64-digit hex string, optional), ex: '0ab1...3f4b' 188 | unlockHeight: UNLOCK_HEIGHT // block height to unlock payment (integer, optional), ex: 12750 189 | } 190 | ccx.send(opts) 191 | ``` 192 | #### Reset or replace wallet (walletd) 193 | ``` 194 | const viewSecretKey = VIEW_SECRET_KEY // (64-digit hex string, optional), ex: '0ab1...3f4b' 195 | ccx.resetOrReplace(viewSecretKey) // If no key, wallet is re-synced. If key, a new address is created from the key for a new wallet. 196 | ``` 197 | #### Get status (walletd) 198 | ``` 199 | ccx.status() 200 | ``` 201 | #### Get balance (walletd) 202 | ``` 203 | const address = ADDRESS // (string, required), ex: 'ccx7Xd...' 204 | ccx.getBalance(address) 205 | ``` 206 | #### Create address (walletd) 207 | ``` 208 | ccx.createAddress() 209 | ``` 210 | #### Create address (walletd) 211 | ``` 212 | const opts = { 213 | privateSpendKeys: [PRIVATE_SPEND_KEY], // Private spend keys to import (array, 64-digit hex string), ex: '0ab1...3f4b' 214 | reset: RESET, //Determines whether reset wallet or not. Defaults to false 215 | } 216 | ccx.createAddressList(opts) 217 | ``` 218 | #### Delete address (walletd) 219 | ``` 220 | const address = ADDRESS // (string, required), ex: 'ccx7Xd...' 221 | ccx.deleteAddress(address) 222 | ``` 223 | #### Get addresses (walletd) 224 | ``` 225 | ccx.getAddresses() 226 | ``` 227 | #### Create integrated address (walletd) 228 | ``` 229 | const address = ADDRESS // (string, required), ex: 'ccx7Xd...' 230 | const paymentId = PAYMENT_ID // (64-digit hex string, optional), ex: '0ab1...3f4b' 231 | ccx.createIntegrated(address,paymentId) // If no key, wallet is re-synced. If key, a new address is created from the key for a new wallet. 232 | ``` 233 | #### Split integrated address (walletd) 234 | ``` 235 | const address = ADDRESS // (string, required), ex: 'ccx7Xd...' 236 | ccx.splitIntegrated(address) // If no key, wallet is re-synced. If key, a new address is created from the key for a new wallet. 237 | ``` 238 | #### Get view secret key (walletd) 239 | ``` 240 | ccx.getViewSecretKey() 241 | ``` 242 | #### Get spend keys (walletd) 243 | ``` 244 | const address = ADDRESS // (string, required), ex: 'ccx7Xd...' 245 | ccx.getSpendKeys(address) 246 | ``` 247 | #### Get block hashes (walletd) 248 | ``` 249 | const firstBlockIndex = FIRST_BLOCK_INDEX // index of first block (integer, required), ex: 12750 250 | const blockCount = BLOCK_COUNT // number of blocks to include (integer, required), ex: 30 251 | ccx.getBlockHashes(firstBlockIndex, blockCount) 252 | ``` 253 | #### Get transaction (walletd) 254 | ``` 255 | const hash = HASH // (64-digit hex string, required), ex: '0ab1...3f4b' 256 | ccx.getTransaction(hash) // get transaction details given hash 257 | ``` 258 | #### Get unconfirmed transactions (walletd) 259 | ``` 260 | const addresses = [ADDRESS1, ADDRESS2, ...] // ADDRESS = address string; address to include 261 | ccx.getUnconfirmedTransactions(addresses) // addresses can be omitted 262 | ``` 263 | #### Get transactionHashes (walletd) 264 | ``` 265 | const opts = { // either blockHash or firstBlockIndex is required 266 | blockHash: BLOCK_HASH, // hash of first block (64-digit hex string, see comment above), ex: '0ab1...3f4b' 267 | firstBlockIndex: FIRST_BLOCK_INDEX, // index of first block (integer, see comment above), ex: 12750 268 | blockCount: BLOCK_COUNT, // number of blocks to include (integer, required), ex: 30 269 | addresses: [ADDRESS, ...], filter (array of address strings, optional), ex: ['ccx7Xd...'] 270 | paymentId: PAYMENT_ID // filter (64-digit hex string, optional), ex: '0ab1...3f4b' 271 | } 272 | ccx.getTransactionHashes(opts) 273 | ``` 274 | #### Get transactions (walletd) 275 | ``` 276 | const opts = { // either blockHash or firstBlockIndex is required 277 | blockHash: BLOCK_HASH, // hash of first block (64-digit hex string, see comment above), ex: '0ab1...3f4b' 278 | firstBlockIndex: FIRST_BLOCK_INDEX, // index of first block (integer, see comment above), ex: 12750 279 | blockCount: BLOCK_COUNT, // number of blocks to include (integer, required), ex: 30 280 | addresses: [ADDRESS, ...], filter (array of address strings, optional), ex: ['ccx7Xd...'] 281 | paymentId: PAYMENT_ID // filter (64-digit hex string, optional), ex: '0ab1...3f4b' 282 | } 283 | ccx.getTransactions(opts) 284 | ``` 285 | #### Send transaction (walletd) 286 | ``` 287 | const transfers = [{ address: ADDRESS, amount: AMOUNT, message: MESSAGE }, ...] // ADDRESS = destination address (string, required), AMOUNT = raw CCX (integer, required), MESSAGE = transfer message to be encrypted (string, optional) 288 | const addresses = [ADDRESS1, ADDRESS2, ...] // ADDRESS = source address string; address in wallet to take funds from 289 | const opts = { 290 | transfers: transfers, // (array, required), ex: [{ address: 'ccx7Xd...', amount: 1000, message: 'tip' }] 291 | addresses: addresses, // (array, optional), ex: ['ccx7Xd...', 'ccx7Xe...'] 292 | changeAddress: ADDRESS, // change return address (address string, optional if only one address in wallet or only one source address given), ex: 'ccx7Xd...' 293 | paymentId: PAYMENT_ID, // filter (64-digit hex string, optional), ex: '0ab1...3f4b' 294 | anonimity: MIX_IN, // input mix count (integer, optional, default 2), ex: 6 295 | fee: FEE, // (raw CCX integer, optional, default is minimum required), ex: 10 296 | unlockHeight: UNLOCK_HEIGHT, // block height to unlock payment (integer, optional), ex: 12750 297 | extra: EXTRA // (variable length string, optional), ex: '123abc' 298 | } 299 | ccx.sendTransaction(opts) 300 | ``` 301 | #### Create delayed transaction (walletd) 302 | ``` 303 | const transfers = [{ address: ADDRESS, amount: AMOUNT, message: MESSAGE }, ...] // ADDRESS = destination address (string, required), AMOUNT = raw CCX (integer, required), MESSAGE = transfer message to be encrypted (string, optional) 304 | const addresses = [ADDRESS1, ADDRESS2, ...] // ADDRESS = source address string; address in wallet to take funds from 305 | const opts = { 306 | transfers: transfers, // (array, required), ex: [{ address: 'ccx7Xd...', amount: 1000, message: 'tip' }] 307 | addresses: addresses, // (array, optional), ex: ['ccx7Xd...', 'ccx7Xe...'] 308 | changeAddress: ADDRESS, // change return address (address string, optional if only one address in wallet or only one source address given), ex: 'ccx7Xd...' 309 | paymentId: PAYMENT_ID, // filter (64-digit hex string, optional), ex: '0ab1...3f4b' 310 | anonimity: MIX_IN, // input mix count (integer, optional, default 2), ex: 6 311 | fee: FEE, // (raw CCX integer, optional, default is minimum required), ex: 10 312 | unlockHeight: UNLOCK_HEIGHT, // block height to unlock payment (integer, optional), ex: 12750 313 | extra: EXTRA // (variable length string, optional), ex: '123abc' 314 | } 315 | ccx.createDelayedTransaction(opts) // create but do not send transaction 316 | ``` 317 | #### Get delayed transaction hashes (walletd) 318 | ``` 319 | ccx.getDelayedTransactionHashes() 320 | ``` 321 | #### Delete delayed transaction (walletd) 322 | ``` 323 | const hash = HASH // (64-digit hex string, required), ex: '0ab1...3f4b' 324 | ccx.deleteDelayedTransaction(hash) 325 | ``` 326 | #### Send delayed transaction (walletd) 327 | ``` 328 | const hash = HASH // (64-digit hex string, required), ex: '0ab1...3f4b' 329 | ccx.sendDelayedTransaction(hash) 330 | ``` 331 | #### Get incoming messages from transaction extra field (walletd) 332 | ``` 333 | const extra = EXTRA // (hex string, required), ex: '0199...c3ca' 334 | ccx.getMessagesFromExtra(extra) 335 | ``` 336 | #### Create Deposit (walletd) 337 | ``` 338 | const opts = { 339 | sourceAddress: ADDRESS, // Wallet address (string), ex: 'ccx7Xd...' 340 | amount: AMOUNT, //The amount to deposit (integer), ex: 12750 341 | term: TERM, // The length of the deposit (integer, minimum 21,900) ex: 5600 342 | } 343 | ccx.createDeposit(opts) 344 | ``` 345 | #### Send Deposit (walletd) 346 | ``` 347 | const opts = { 348 | sourceAddress: ADDRESS, // Wallet address (string), ex: 'ccx7Xd...' 349 | amount: AMOUNT, //The amount to deposit (integer), ex: 12750 350 | term: TERM, // The length of the deposit (integer, minimum 21,900) ex: 5600, 351 | destinationAddress: ADDRESS // Wallet address of receiver (string), ex: 'ccx7Xd...' 352 | } 353 | ccx.sendDeposit(opts) 354 | ``` 355 | #### Get Deposit (walletd) 356 | ``` 357 | const id = DEPOSIT_ID // Id of the deposit (integer, required), ex: '1' 358 | ccx.getDeposit(id) 359 | ``` 360 | #### Withdraw Deposit (walletd) 361 | ``` 362 | const id = DEPOSIT_ID // Id of the deposit (integer, required), ex: '1' 363 | ccx.withdrawDeposit(id) 364 | ``` 365 | ### Daemon RPC (must provide daemonRpcPort) 366 | 367 | #### Get info 368 | ``` 369 | ccx.info() // get information about the block chain, including next block height 370 | ``` 371 | #### Get index 372 | ``` 373 | ccx.index() // get next block height 374 | ``` 375 | #### Get count 376 | ``` 377 | ccx.count() // get next block height 378 | ``` 379 | #### Get currency ID 380 | ``` 381 | ccx.currencyId() 382 | ``` 383 | #### Get block hash by height 384 | ``` 385 | const height = HEIGHT // (integer, required), ex: 12750 386 | ccx.blockHashByHeight(height) // get block hash given height 387 | ``` 388 | #### Get block header by height 389 | ``` 390 | const height = HEIGHT // (integer, required), ex: 12750 391 | ccx.blockHeaderByHeight(height) // get block header given height 392 | ``` 393 | #### Get block header by hash 394 | ``` 395 | const hash = HASH // (64-digit hex string, required), ex: '0ab1...3f4b' 396 | ccx.blockHeaderByHash(hash) // get block header given hash 397 | ``` 398 | #### Get last block header 399 | ``` 400 | ccx.lastBlockHeader() 401 | ``` 402 | #### Get block 403 | ``` 404 | const hash = HASH // (64-digit hex string, required), ex: '0ab1...3f4b' 405 | ccx.block(hash) 406 | ``` 407 | #### Get blocks 408 | ``` 409 | const height = HEIGHT // (integer, required), ex: 12750 410 | ccx.blocks(height) // returns 31 blocks up to and including HEIGHT 411 | ``` 412 | #### Get block template 413 | ``` 414 | const address = ADDRESS // destination address (string, required), ex: 'ccx7Xd...' 415 | const reserveSize = RESERVE_SIZE // bytes to reserve in block for work, etc. (integer < 256, optional, default 14), ex: 255 416 | const opts = { 417 | address: address, 418 | reserveSize: reserveSize 419 | } 420 | ccx.blockTemplate(opts) 421 | ``` 422 | #### Submit block 423 | ``` 424 | const block = BLOCK // block blob (hex string, required), ex: '0300cb9eb...' 425 | ccx.submitBlock(block) 426 | ``` 427 | #### Get transaction 428 | ``` 429 | const hash = HASH // (64-digit hex string, required), ex: '0ab1...3f4b' 430 | ccx.transaction(hash) 431 | ``` 432 | #### Get transactions 433 | ``` 434 | const arr = [HASH1, HASH2, ...] // (array of 64-digit hex strings, required), ex: ['0ab1...3f4b'] 435 | ccx.transactions(arr) 436 | ``` 437 | #### Get transaction pool 438 | ``` 439 | ccx.transactionPool() 440 | ``` 441 | #### Send raw transaction 442 | ``` 443 | const transaction = TRANSACTION // transaction blob (hex string, required), ex: ''01d86301...' 444 | ccx.sendRawTransaction(transaction) 445 | ``` 446 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = CCX; 2 | 3 | const { http, https } = require('follow-redirects'); 4 | const url = require('url'); 5 | 6 | const MAX_MIXIN = 5; 7 | const MIN_MIXIN = 3; 8 | const DEFAULT_UNLOCK_HEIGHT = 10; 9 | const DEFAULT_FEE = 1000; // raw X 10 | const DEFAULT_CHARACTER_FEE = 10; // raw X 11 | 12 | const err = { 13 | nonNeg: ' must be a non-negative integer', 14 | hex: ' must be a hexadecimal string', 15 | opts: 'opts must be object', 16 | hex64: ' must be 64-digit hexadecimal string', 17 | addr: ' must be 98-character string beginning with ccx', 18 | intAddr: ' must be 186-character string beginning with ccx', 19 | raw: ' must be a raw amount of CCX (X)', 20 | privKey: ' must be a 64-character string', 21 | trans: ' must be a transfer object { address: 98-character string beginning with ccx, amount: raw amount of CCX (X), message: optional string }', 22 | arr: ' must be an array', 23 | str: ' must be a string' 24 | }; 25 | 26 | function CCX(params) { 27 | if (!params) throw 'parameters are required'; 28 | if (typeof params != 'object') throw 'parameters must be a JSON object'; 29 | 30 | // parse both daemon and wallet urls 31 | const parseDaemon = params.daemonHost ? new URL(params.daemonHost) : null; 32 | const parseWallet = params.walletHost ? new URL(params.walletHost) : null; 33 | 34 | if (parseDaemon) { 35 | if (parseDaemon.protocol === 'http:') this.daemonProtocol = http; 36 | else if (parseDaemon.protocol === 'https:') this.daemonProtocol = https; 37 | else throw 'Daemon host must begin with http(s)://'; 38 | } 39 | 40 | if (parseWallet) { 41 | if (parseWallet.protocol === 'http:') this.walletProtocol = http; 42 | else if (parseWallet.protocol === 'https:') this.walletProtocol = https; 43 | else throw 'Wallet host must begin with http(s)://'; 44 | } 45 | 46 | this.daemonHost = parseDaemon ? parseDaemon.host : null; 47 | this.walletHost = parseWallet ? parseWallet.host : null; 48 | this.daemonPath = parseDaemon ? parseDaemon.pathname : null; 49 | this.walletPath = parseWallet ? parseWallet.pathname : null; 50 | this.walletRpcPort = params.walletRpcPort; 51 | this.daemonRpcPort = params.daemonRpcPort; 52 | this.walletRpcUser = params.walletRpcUser; 53 | this.walletRpcPass = params.walletRpcPass; 54 | this.timeout = params.timeout || 5000; 55 | 56 | // check daemon path and if its empty set it to emty string 57 | if ((this.daemonPath == '/') || (!this.daemonPath)) { 58 | this.daemonPath = ""; 59 | } 60 | 61 | // check wallet path and if its empty set it to emty string 62 | if ((this.walletPath == '/') || (!this.walletPath)) { 63 | this.walletPath = ""; 64 | } 65 | 66 | // check daemon port and if its empty set it to 80 or 433 67 | if (!this.daemonRpcPort) { 68 | if (this.daemonProtocol == http) { 69 | this.daemonRpcPort = 80; 70 | } else if (this.daemonProtocol == https) { 71 | this.daemonRpcPort = 443; 72 | } 73 | } 74 | 75 | console.log() 76 | } 77 | 78 | // Wallet RPC -- concealwallet 79 | 80 | function wrpc(that, method, params, resolve, reject) { 81 | request(that.walletProtocol, that.walletHost, that.walletRpcPort, 'POST', that.timeout, buildRpc(method, params), that.walletPath + '/json_rpc', that.walletRpcUser, that.walletRpcPass, resolve, reject); 82 | } 83 | 84 | CCX.prototype.outputs = function () { 85 | return new Promise((resolve, reject) => { 86 | wrpc(this, 'get_outputs', {}, resolve, reject); 87 | }); 88 | }; 89 | 90 | CCX.prototype.height = function () { 91 | return new Promise((resolve, reject) => { 92 | wrpc(this, 'get_height', {}, resolve, reject); 93 | }); 94 | }; 95 | 96 | CCX.prototype.balance = function () { 97 | return new Promise((resolve, reject) => { 98 | wrpc(this, 'getbalance', {}, resolve, reject); 99 | }); 100 | }; 101 | 102 | CCX.prototype.messages = function (opts) { 103 | return new Promise((resolve, reject) => { 104 | if (!isObject(opts)) opts = {}; 105 | else if (!isUndefined(opts.firstTxId) && !isNonNegative(opts.firstTxId)) reject('firstTxId' + err.nonNeg); 106 | else if (!isUndefined(opts.txLimit) && !isNonNegative(opts.txLimit)) reject('txLimit' + err.nonNeg); 107 | else { 108 | obj = { 109 | first_tx_id: opts.firstTxId, 110 | tx_limit: opts.txLimit 111 | }; 112 | wrpc(this, 'get_messages', obj, resolve, reject); 113 | } 114 | }); 115 | }; 116 | 117 | CCX.prototype.payments = function (paymentId) { // incoming payments 118 | return new Promise((resolve, reject) => { 119 | if (!isHex64String(paymentId)) reject('paymentId' + err.hex64); 120 | else wrpc(this, 'get_payments', { payment_id: paymentId }, resolve, reject); 121 | }); 122 | }; 123 | 124 | CCX.prototype.transfers = function () { 125 | return new Promise((resolve, reject) => { 126 | wrpc(this, 'get_transfers', {}, resolve, reject); 127 | }); 128 | }; 129 | 130 | CCX.prototype.store = function () { 131 | return new Promise((resolve, reject) => { 132 | wrpc(this, 'store', {}, resolve, reject); 133 | }); 134 | }; 135 | 136 | CCX.prototype.reset = function () { 137 | return new Promise((resolve, reject) => { 138 | wrpc(this, 'reset', {}, resolve, reject); 139 | }); 140 | }; 141 | 142 | CCX.prototype.save = function () { 143 | return new Promise((resolve, reject) => { 144 | wrpc(this, 'save', {}, resolve, reject); 145 | }); 146 | }; 147 | 148 | CCX.prototype.optimize = function () { 149 | return new Promise((resolve, reject) => { 150 | wrpc(this, 'optimize', {}, resolve, reject); 151 | }); 152 | }; 153 | 154 | CCX.prototype.send = function (opts) { 155 | return new Promise((resolve, reject) => { 156 | if (!isObject(opts)) reject(err.opts); 157 | else if (isUndefined(opts.transfers) || !arrayTest(opts.transfers, isTransfer)) reject('transfers' + err.arr + ' of transfers each of which' + err.trans); 158 | else if (!isUndefined(opts.paymentId) && !isHex64String(opts.paymentId)) reject('paymentId' + err.hex64); 159 | else { 160 | if (isUndefined(opts.mixIn)) opts.mixIn = MIN_MIXIN; 161 | if (!(opts.mixIn >= MIN_MIXIN && opts.mixIn <= MAX_MIXIN)) reject(MIN_MIXIN + ' <= mixIn <= ' + MAX_MIXIN); 162 | else { 163 | if (isUndefined(opts.unlockHeight)) opts.unlockHeight = DEFAULT_UNLOCK_HEIGHT; 164 | if (!isNonNegative(opts.unlockHeight)) reject('unlockHeight' + err.nonNeg); 165 | else { 166 | if (isUndefined(opts.fee)) { 167 | opts.fee = DEFAULT_FEE; 168 | opts.transfers.forEach((transfer) => { 169 | opts.fee += (!isUndefined(transfer.message) ? transfer.message.length * DEFAULT_CHARACTER_FEE : 0); 170 | }); 171 | } 172 | if (!isNonNegative(opts.fee)) reject('fee' + err.raw); 173 | else { 174 | const obj = { 175 | destinations: opts.transfers, 176 | mixin: opts.mixIn, 177 | fee: opts.fee, 178 | unlock_time: opts.unlockHeight, 179 | payment_id: opts.paymentId 180 | }; 181 | wrpc(this, 'transfer', obj, resolve, reject); 182 | } 183 | } 184 | } 185 | } 186 | }); 187 | }; 188 | 189 | // Wallet RPC -- walletd 190 | 191 | CCX.prototype.resetOrReplace = function (viewSecretKey) { 192 | return new Promise((resolve, reject) => { 193 | if (!isUndefined(viewSecretKey) && !isHex64String(viewSecretKey)) reject('viewSecretKey' + err.hex64); 194 | else wrpc(this, 'reset', { viewSecretKey: viewSecretKey }, resolve, reject); 195 | }); 196 | }; 197 | 198 | CCX.prototype.status = function () { 199 | return new Promise((resolve, reject) => { 200 | wrpc(this, 'getStatus', {}, resolve, reject); 201 | }); 202 | }; 203 | 204 | CCX.prototype.getBalance = function (address) { 205 | return new Promise((resolve, reject) => { 206 | if (isUndefined(address) || !isAddress(address)) reject('address' + err.addr); 207 | else wrpc(this, 'getBalance', { address: address }, resolve, reject); 208 | }); 209 | }; 210 | 211 | CCX.prototype.createAddress = function () { 212 | return new Promise((resolve, reject) => { 213 | wrpc(this, 'createAddress', {}, resolve, reject); 214 | }); 215 | }; 216 | 217 | CCX.prototype.createAddressList = function () { 218 | return new Promise((resolve, reject) => { 219 | if (!isObject(opts)) reject(err.opts); 220 | else if (isUndefined(opts.privateSpendKeys) || !arrayTest(opts.transfers, isPrivateKey)) reject('privateSpendKeys' + err.arr + ' of keys each of which' + err.privKey); 221 | else { 222 | wrpc(this, 'createAddressList', opts, resolve, reject); 223 | } 224 | }); 225 | }; 226 | 227 | CCX.prototype.createIntegrated = function (address, paymentId) { 228 | return new Promise((resolve, reject) => { 229 | if (isUndefined(address) || !isAddress(address)) reject('address' + err.addr); 230 | if (isUndefined(paymentId) || !isHex64String(paymentId)) reject('paymentId' + err.hex64); 231 | wrpc(this, 'createIntegrated', { address: address, payment_id: paymentId }, resolve, reject); 232 | }); 233 | }; 234 | 235 | CCX.prototype.splitIntegrated = function (address) { 236 | return new Promise((resolve, reject) => { 237 | if (isUndefined(address) || !isIntAddress(address)) reject('address' + err.intAddr); 238 | wrpc(this, 'createIntegrated', { integrated_address: address }, resolve, reject); 239 | }); 240 | }; 241 | 242 | CCX.prototype.deleteAddress = function (address) { 243 | return new Promise((resolve, reject) => { 244 | if (isUndefined(address) || !isAddress(address)) reject('address' + err.addr); 245 | wrpc(this, 'deleteAddress', { address: address }, resolve, reject); 246 | }); 247 | }; 248 | 249 | CCX.prototype.getAddresses = function () { 250 | return new Promise((resolve, reject) => { 251 | wrpc(this, 'getAddresses', {}, resolve, reject); 252 | }); 253 | }; 254 | 255 | CCX.prototype.getViewSecretKey = function () { 256 | return new Promise((resolve, reject) => { 257 | wrpc(this, 'getViewKey', {}, resolve, reject); 258 | }); 259 | }; 260 | 261 | CCX.prototype.getSpendKeys = function (address) { 262 | return new Promise((resolve, reject) => { 263 | if (isUndefined(address) || !isAddress(address)) reject('address' + err.addr); 264 | else wrpc(this, 'getSpendKeys', { address: address }, resolve, reject); 265 | }); 266 | }; 267 | 268 | CCX.prototype.getBlockHashes = function (firstBlockIndex, blockCount) { 269 | return new Promise((resolve, reject) => { 270 | if (isUndefined(firstBlockIndex) || !isNonNegative(firstBlockIndex)) reject('firstBlockIndex' + err.nonNeg); 271 | else if (isUndefined(blockCount) || !isNonNegative(blockCount)) reject('blockCount' + err.nonNeg); 272 | else wrpc(this, 'getBlockHashes', { firstBlockIndex: firstBlockIndex, blockCount: blockCount }, resolve, reject); 273 | }); 274 | }; 275 | 276 | CCX.prototype.getTransaction = function (hash) { 277 | return new Promise((resolve, reject) => { 278 | if (!isHex64String(hash)) reject('hash' + err.hex64); 279 | else wrpc(this, 'getTransaction', { transactionHash: hash }, resolve, reject); 280 | }); 281 | }; 282 | 283 | CCX.prototype.getUnconfirmedTransactionHashes = function (addresses) { 284 | return new Promise((resolve, reject) => { 285 | if (!isUndefined(addresses) && !arrayTest(addresses, isAddress)) reject('addresses' + err.arr + ' of addresses each of which' + err.addr); 286 | else wrpc(this, 'getUnconfirmedTransactionHashes', { addresses: addresses }, resolve, reject); 287 | }); 288 | }; 289 | 290 | CCX.prototype.getTransactionHashes = function (opts) { 291 | return new Promise((resolve, reject) => { 292 | if (!isObject(opts)) reject(err.opts); 293 | else if (!isNonNegative(opts.blockCount)) reject('blockCount' + err.nonNeg); 294 | else if (isUndefined(opts.firstBlockIndex) && isUndefined(opts.blockHash)) reject('either firstBlockIndex or blockHash is required'); 295 | else if (!isUndefined(opts.firstBlockIndex) && !isNonNegative(opts.firstBlockIndex)) reject('firstBlockIndex' + err.nonNeg); 296 | else if (!isUndefined(opts.blockHash) && !isHex64String(opts.blockHash)) reject('blockHash' + err.hex64); 297 | else if (!isUndefined(opts.paymentId) && !isHex64String(opts.paymentId)) reject('paymentId' + err.hex64); 298 | else if (!isUndefined(opts.addresses) && !arrayTest(opts.addresses, isAddress)) reject('addresses' + err.arr + ' of addresses each of which' + err.addr); 299 | else wrpc(this, 'getTransactionHashes', opts, resolve, reject); 300 | }); 301 | }; 302 | 303 | CCX.prototype.getTransactions = function (opts) { 304 | return new Promise((resolve, reject) => { 305 | if (!isObject(opts)) reject(err.opts); 306 | else if (!isNonNegative(opts.blockCount)) reject('blockCount' + err.nonNeg); 307 | else if (isUndefined(opts.firstBlockIndex) && isUndefined(opts.blockHash)) reject('either firstBlockIndex or blockHash is required'); 308 | else if (!isUndefined(opts.firstBlockIndex) && !isNonNegative(opts.firstBlockIndex)) reject('firstBlockIndex' + err.nonNeg); 309 | else if (!isUndefined(opts.blockHash) && !isHex64String(opts.blockHash)) reject('blockHash' + err.hex64); 310 | else if (!isUndefined(opts.paymentId) && !isHex64String(opts.paymentId)) reject('paymentId' + err.hex64); 311 | else if (!isUndefined(opts.addresses) && !arrayTest(opts.addresses, isAddress)) reject('addresses' + err.arr + ' of addresses each of which' + err.addr); 312 | else wrpc(this, 'getTransactions', opts, resolve, reject); 313 | }); 314 | }; 315 | 316 | CCX.prototype.sendTransaction = function (opts) { 317 | return new Promise((resolve, reject) => { 318 | if (!isObject(opts)) reject(err.opts); 319 | else if (isUndefined(opts.transfers) || !arrayTest(opts.transfers, isTransfer)) reject('transfers' + err.arr + ' of transfers each of which' + err.trans); 320 | else if (!isUndefined(opts.addresses) && !arrayTest(opts.addresses, isAddress)) reject('addresses' + err.arr + ' of addresses each of which' + err.addr); 321 | else if (!isUndefined(opts.changeAddress) && !isAddress(opts.changeAddress)) reject('changeAddress' + err.addr); 322 | else if (!isUndefined(opts.paymentId) && !isHex64String(opts.paymentId)) reject('paymentId' + err.hex64); 323 | else if (!isUndefined(opts.extra) && !isString(opts.extra)) reject('extra' + err.str); 324 | else { 325 | opts.sourceAddresses = opts.addresses; delete opts.addresses; 326 | if (isUndefined(opts.mixIn)) opts.mixIn = MIN_MIXIN; 327 | if (!(opts.mixIn >= MIN_MIXIN && opts.mixIn <= MAX_MIXIN)) reject(MIN_MIXIN + ' <= mixIn <= ' + MAX_MIXIN); 328 | else { 329 | opts.anonymity = opts.mixIn; delete opts.mixIn; 330 | if (isUndefined(opts.unlockHeight)) opts.unlockHeight = DEFAULT_UNLOCK_HEIGHT; 331 | if (!isNonNegative(opts.unlockHeight)) reject('unlockHeight' + err.nonNeg); 332 | else { 333 | opts.unlockTime = opts.unlockHeight; delete opts.unlockHeight; 334 | if (isUndefined(opts.fee)) { 335 | opts.fee = DEFAULT_FEE; 336 | opts.transfers.forEach((transfer) => { 337 | opts.fee += (!isUndefined(transfer.message) ? transfer.message.length * DEFAULT_CHARACTER_FEE : 0); 338 | }); 339 | } 340 | if (!isNonNegative(opts.fee)) reject('fee' + err.raw); 341 | else wrpc(this, 'sendTransaction', opts, resolve, reject); 342 | } 343 | } 344 | } 345 | }); 346 | }; 347 | 348 | CCX.prototype.estimateFusion = function (opts) { 349 | return new Promise((resolve, reject) => { 350 | if (!isObject(opts)) reject(err.opts); 351 | else if (isUndefined(opts.threshold)) reject('treshold param is mandatory'); 352 | else if (!isUndefined(opts.addresses) && !arrayTest(opts.addresses, isAddress)) reject('addresses' + err.arr + ' of addresses each of which' + err.addr); 353 | else { 354 | if (Number.isInteger(opts.threshold)) { 355 | wrpc(this, 'estimateFusion', opts, resolve, reject); 356 | } else { 357 | reject('treshold must be an integer'); 358 | } 359 | } 360 | }); 361 | }; 362 | 363 | CCX.prototype.sendFusionTransaction = function (opts) { 364 | return new Promise((resolve, reject) => { 365 | if (!isObject(opts)) reject(err.opts); 366 | else if (isUndefined(opts.threshold)) reject('treshold param is mandatory'); 367 | else if (!isUndefined(opts.addresses) && !arrayTest(opts.addresses, isAddress)) reject('addresses' + err.arr + ' of addresses each of which' + err.addr); 368 | else { 369 | opts.sourceAddresses = opts.addresses; delete opts.addresses; 370 | if (isUndefined(opts.mixIn)) opts.mixIn = MIN_MIXIN; 371 | if (!(opts.mixIn >= MIN_MIXIN && opts.mixIn <= MAX_MIXIN)) reject(MIN_MIXIN + ' <= mixIn <= ' + MAX_MIXIN); 372 | else { 373 | if ((opts.addresses.length == 1) && (!opts.destinationAddress)) { 374 | opts.destinationAddress = opts.addresses[0]; 375 | } 376 | 377 | if (!opts.destinationAddress) { 378 | reject('destinationAddress must be specified in case you have more then one source address'); 379 | } else { 380 | wrpc(this, 'sendFusionTransaction', opts, resolve, reject); 381 | } 382 | } 383 | } 384 | }); 385 | }; 386 | 387 | CCX.prototype.createDeposit = function (opts) { 388 | return new Promise((resolve, reject) => { 389 | if (!isObject(opts)) reject(err.opts); 390 | else if (isUndefined(opts.sourceAddress)) reject('sourceAddress param is mandatory'); 391 | else if (isUndefined(opts.amount)) reject('amount param is mandatory'); 392 | else if (isUndefined(opts.term)) reject('term param is mandatory'); 393 | else { 394 | if (!isAddress(opts.sourceAddress)) reject('sourceAddress is not a valid address'); 395 | else if (!Number.isInteger(opts.term)) reject('term is not a valid integer'); 396 | else if (!isNumeric(opts.amount)) reject('amount is not a valid number'); 397 | else { 398 | wrpc(this, 'createDeposit', opts, resolve, reject); 399 | } 400 | } 401 | }); 402 | }; 403 | 404 | CCX.prototype.sendDeposit = function (opts) { 405 | return new Promise((resolve, reject) => { 406 | if (!isObject(opts)) reject(err.opts); 407 | else if (isUndefined(opts.destinationAddress)) reject('destinationAddress param is mandatory'); 408 | else if (isUndefined(opts.sourceAddress)) reject('sourceAddress param is mandatory'); 409 | else if (isUndefined(opts.amount)) reject('amount param is mandatory'); 410 | else if (isUndefined(opts.term)) reject('term param is mandatory'); 411 | else { 412 | if (!isAddress(opts.destinationAddress)) reject('destinationAddress is not a valid address'); 413 | else if (!isAddress(opts.sourceAddress)) reject('sourceAddress is not a valid address'); 414 | else if (!Number.isInteger(opts.term)) reject('term is not a valid integer'); 415 | else if (!isNumeric(opts.amount)) reject('amount is not a valid number'); 416 | else { 417 | wrpc(this, 'sendDeposit', opts, resolve, reject); 418 | } 419 | } 420 | }); 421 | }; 422 | 423 | CCX.prototype.getDeposit = function (id) { 424 | return new Promise((resolve, reject) => { 425 | if (isUndefined(id)) reject('depositId param is mandatory'); 426 | else { 427 | if (!Number.isInteger(id)) reject('depositId is not a valid integer'); 428 | else { 429 | wrpc(this, 'getDeposit', { depositId: id }, resolve, reject); 430 | } 431 | } 432 | }); 433 | }; 434 | 435 | CCX.prototype.withdrawDeposit = function (id) { 436 | return new Promise((resolve, reject) => { 437 | if (isUndefined(id)) reject('depositId param is mandatory'); 438 | else { 439 | if (!Number.isInteger(id)) reject('depositId is not a valid integer'); 440 | else { 441 | wrpc(this, 'withdrawDeposit', { depositId: id }, resolve, reject); 442 | } 443 | } 444 | }); 445 | }; 446 | 447 | CCX.prototype.createDelayedTransaction = function (opts) { 448 | return new Promise((resolve, reject) => { 449 | if (!isObject(opts)) reject(err.opts); 450 | else if (isUndefined(opts.transfers) || !arrayTest(opts.transfers, isTransfer)) reject('transfers' + err.arr + ' of transfers each of which' + err.trans); 451 | else if (!isUndefined(opts.addresses) && !arrayTest(opts.addresses, isAddress)) reject('addresses' + err.arr + ' of addresses each of which' + err.addr); 452 | else if (!isUndefined(opts.changeAddress) && !isAddress(opts.changeAddress)) reject('changeAddress' + err.addr); 453 | else if (!isUndefined(opts.paymentId) && !isHex64String(opts.paymentId)) reject('paymentId' + err.hex64); 454 | else if (!isUndefined(opts.extra) && !isString(opts.extra)) reject('extra' + err.str); 455 | else { 456 | if (isUndefined(opts.mixIn)) opts.mixIn = MIN_MIXIN; 457 | if (!(opts.mixIn >= MIN_MIXIN && opts.mixIn <= MAX_MIXIN)) reject(MIN_MIXIN + ' <= mixIn <= ' + MAX_MIXIN); 458 | else { 459 | opts.anonymity = opts.mixIn; delete opts.mixIn; 460 | if (isUndefined(opts.unlockHeight)) opts.unlockHeight = DEFAULT_UNLOCK_HEIGHT; 461 | if (!isNonNegative(opts.unlockHeight)) reject('unlockHeight' + err.nonNeg); 462 | else { 463 | opts.unlockTime = opts.unlockHeight; delete opts.unlockHeight; 464 | if (isUndefined(opts.fee)) opts.fee = DEFAULT_FEE * opts.transfers.length; 465 | if (!isNonNegative(opts.fee)) reject('fee' + err.raw); 466 | else wrpc(this, 'createDelayedTransaction', opts, resolve, reject); 467 | } 468 | } 469 | } 470 | }); 471 | }; 472 | 473 | CCX.prototype.getDelayedTransactionHashes = function () { 474 | return new Promise((resolve, reject) => { 475 | wrpc(this, 'getDelayedTransactionHashes', {}, resolve, reject); 476 | }); 477 | }; 478 | 479 | CCX.prototype.deleteDelayedTransaction = function (hash) { 480 | return new Promise((resolve, reject) => { 481 | if (!isHex64String(hash)) reject('hash' + err.hex64); 482 | else wrpc(this, 'deleteDelayedTransaction', { transactionHash: hash }, resolve, reject); 483 | }); 484 | }; 485 | 486 | CCX.prototype.sendDelayedTransaction = function (hash) { 487 | return new Promise((resolve, reject) => { 488 | if (!isHex64String(hash)) reject('hash' + err.hex64); 489 | else wrpc(this, 'sendDelayedTransaction', { transactionHash: hash }, resolve, reject); 490 | }); 491 | }; 492 | 493 | CCX.prototype.getMessagesFromExtra = function (extra) { 494 | return new Promise((resolve, reject) => { 495 | if (!isHexString(extra)) reject('extra' + err.hex); 496 | else wrpc(this, 'getMessagesFromExtra', { extra: extra }, resolve, reject); 497 | }); 498 | }; 499 | 500 | CCX.prototype.exportWallet = function (opts) { 501 | return new Promise((resolve, reject) => { 502 | if (!isObject(opts)) reject(err.opts); 503 | else if (isUndefined(opts.exportFilename)) reject('exportFilename is mandatory'); 504 | else { 505 | wrpc(this, 'exportWallet', opts, resolve, reject); 506 | } 507 | }); 508 | }; 509 | 510 | CCX.prototype.exportWalletKeys = function (opts) { 511 | return new Promise((resolve, reject) => { 512 | if (!isObject(opts)) reject(err.opts); 513 | else if (isUndefined(opts.exportFilename)) reject('exportFilename is mandatory'); 514 | else { 515 | wrpc(this, 'exportWalletKeys', opts, resolve, reject); 516 | } 517 | }); 518 | }; 519 | 520 | // Daemon RPC - JSON RPC 521 | 522 | function drpc(that, method, params, resolve, reject) { 523 | request(that.daemonProtocol, that.daemonHost, that.daemonRpcPort, 'POST', that.timeout, buildRpc(method, params), that.daemonPath + '/json_rpc', null, null, resolve, reject); 524 | } 525 | 526 | CCX.prototype.count = function () { 527 | return new Promise((resolve, reject) => { 528 | drpc(this, 'getblockcount', {}, resolve, reject); 529 | }); 530 | }; 531 | 532 | CCX.prototype.blockHashByHeight = function (height) { 533 | return new Promise((resolve, reject) => { 534 | if (!isNonNegative(height)) reject('height' + err.nonNeg); 535 | else drpc(this, 'on_getblockhash', [height], resolve, reject); 536 | }); 537 | }; 538 | 539 | CCX.prototype.blockHeaderByHash = function (hash) { 540 | return new Promise((resolve, reject) => { 541 | if (!isHex64String(hash)) reject('hash' + err.hex64); 542 | else drpc(this, 'getblockheaderbyhash', { hash: hash }, resolve, reject); 543 | }); 544 | }; 545 | 546 | CCX.prototype.blockHeaderByHeight = function (height) { 547 | return new Promise((resolve, reject) => { 548 | if (!isNonNegative(height)) reject('height' + err.nonNeg); 549 | else drpc(this, 'getblockheaderbyheight', { height: height }, resolve, reject); 550 | }); 551 | }; 552 | 553 | CCX.prototype.lastBlockHeader = function () { 554 | return new Promise((resolve, reject) => { 555 | drpc(this, 'getlastblockheader', {}, resolve, reject); 556 | }); 557 | }; 558 | 559 | CCX.prototype.block = function (hash) { 560 | return new Promise((resolve, reject) => { 561 | if (!isHex64String(hash)) reject('hash' + err.hex64); 562 | else drpc(this, 'f_block_json', { hash: hash }, resolve, reject); 563 | }); 564 | }; 565 | 566 | CCX.prototype.blocks = function (height) { 567 | return new Promise((resolve, reject) => { 568 | if (!isNonNegative(height)) reject('height' + err.nonNeg); 569 | else drpc(this, 'f_blocks_list_json', { height: height }, resolve, reject); 570 | }); 571 | }; 572 | 573 | CCX.prototype.transaction = function (hash) { 574 | return new Promise((resolve, reject) => { 575 | if (!isHex64String(hash)) reject('hash' + err.hex64); 576 | else drpc(this, 'f_transaction_json', { hash: hash }, resolve, reject); 577 | }); 578 | }; 579 | 580 | CCX.prototype.transactionPool = function () { 581 | return new Promise((resolve, reject) => { 582 | drpc(this, 'f_on_transactions_pool_json', {}, resolve, reject); 583 | }); 584 | }; 585 | 586 | CCX.prototype.currencyId = function () { 587 | return new Promise((resolve, reject) => { 588 | drpc(this, 'getcurrencyid', {}, resolve, reject); 589 | }); 590 | }; 591 | 592 | CCX.prototype.blockTemplate = function (opts) { 593 | return new Promise((resolve, reject) => { 594 | if (!isObject(opts)) reject(err.opts); 595 | else if (!isAddress(opts.address)) reject('address' + err.addr); 596 | else if (!isNonNegative(opts.reserveSize) || opts.reserveSize > 255) reject('0 <= reserveSize <= 255'); 597 | else drpc(this, 'getblocktemplate', { wallet_address: opts.address, reserve_size: opts.reserveSize }, resolve, reject); 598 | }); 599 | }; 600 | 601 | CCX.prototype.submitBlock = function (block) { 602 | return new Promise((resolve, reject) => { 603 | if (!isHexString(block)) reject('block' + err.hex); 604 | else drpc(this, 'submitblock', [block], resolve, reject); 605 | }); 606 | }; 607 | 608 | // Daemon RPC - JSON handlers 609 | 610 | function hrpc(that, params, path, resolve, reject) { 611 | request(that.daemonProtocol, that.daemonHost, that.daemonRpcPort, 'GET', that.timeout, JSON.stringify(params), that.daemonPath + path, null, null, resolve, reject); 612 | } 613 | 614 | CCX.prototype.info = function () { 615 | return new Promise((resolve, reject) => { 616 | hrpc(this, {}, '/getinfo', resolve, reject); 617 | }); 618 | }; 619 | 620 | CCX.prototype.index = function () { 621 | return new Promise((resolve, reject) => { 622 | hrpc(this, {}, '/getheight', resolve, reject); 623 | }); 624 | }; 625 | /* 626 | CCX.prototype.startMining = function (opts) { 627 | return new Promise((resolve, reject) => { 628 | if (!isObject(opts)) reject(err.opts) 629 | else if (!isAddress(opts.address)) reject('address' + err.addr) 630 | else if (!isNonNegative(opts.threads)) reject('unlockHeight' + err.nonNeg) 631 | else hrpc(this, { miner_address: opts.address, threads_count: opts.threads }, '/start_mining', resolve, reject) 632 | }) 633 | } 634 | 635 | CCX.prototype.stopMining = function () { 636 | return new Promise((resolve, reject) => { 637 | hrpc(this, { }, '/stop_mining', resolve, reject) 638 | }) 639 | } 640 | */ 641 | CCX.prototype.transactions = function (txs) { 642 | return new Promise((resolve, reject) => { 643 | if (!arrayTest(txs, isHex64String)) reject('txs' + err.arr + ' of transactions each of which ' + err.hex64); 644 | else hrpc(this, { txs_hashes: txs }, '/gettransactions', resolve, reject); 645 | }); 646 | }; 647 | 648 | CCX.prototype.sendRawTransaction = function (rawTx) { 649 | return new Promise((resolve, reject) => { 650 | if (!isHexString(rawTx)) reject('rawTx' + err.hex); 651 | else hrpc(this, { tx_as_hex: rawTx }, '/sendrawtransaction', resolve, reject); 652 | }); 653 | }; 654 | 655 | // Utilities 656 | 657 | function arrayTest(arr, test) { 658 | if (!Array.isArray(arr)) return false; 659 | let i; 660 | for (i = 0; i < arr.length; i++) { if (!test(arr[i])) break; } 661 | if (i < arr.length) return false; 662 | return true; 663 | } 664 | 665 | function isObject(obj) { return typeof obj === 'object'; } 666 | 667 | function isUndefined(obj) { return typeof obj === 'undefined'; } 668 | 669 | function isString(obj) { return typeof obj === 'string'; } 670 | 671 | function isTransfer(obj) { 672 | if (!isObject(obj) || !isAddress(obj.address) || !isNonNegative(obj.amount)) return false; 673 | if (typeof obj.message !== 'undefined' && !isString(obj.message)) return false; 674 | return true; 675 | } 676 | 677 | function isNonNegative(n) { return (Number.isInteger(n) && n >= 0); } 678 | 679 | function isNumeric(n) { return !isNaN(parseFloat(n)) && isFinite(n); } 680 | 681 | function isAddress(str) { return (typeof str === 'string' && str.length === 98 && str.slice(0, 3) === 'ccx'); } 682 | 683 | function isIntAddress(str) { return (typeof str === 'string' && str.length === 186 && str.slice(0, 3) === 'ccx'); } 684 | 685 | function isPrivateKey(str) { return (typeof str === 'string' && str.length === 64); } 686 | 687 | function isHex64String(str) { return (typeof str === 'string' && /^[0-9a-fA-F]{64}$/.test(str)); } 688 | 689 | function isHexString(str) { return (typeof str === 'string' && !/[^0-9a-fA-F]/.test(str)); } 690 | 691 | function buildRpc(method, params) { return '{"jsonrpc":"2.0","id":"0","method":"' + method + '","params":' + JSON.stringify(params) + '}'; } 692 | 693 | function request(protocol, host, port, method, timeout, post, path, user, pass, resolve, reject) { 694 | let obj = { 695 | hostname: host, 696 | port: port, 697 | method: method, 698 | timeout: timeout, 699 | path: path, 700 | headers: { 701 | 'Content-Type': 'application/json', 702 | 'Content-Length': post.length, 703 | } 704 | }; 705 | 706 | if (user && pass) { 707 | obj.headers["Authorization"] = `Basic ${Buffer.from(user + ':' + pass).toString('base64')}`; 708 | } 709 | 710 | var doRequest = protocol.request( 711 | obj, 712 | (res) => { 713 | if ((Math.floor(res.statusCode / 100) !== 2) && (Math.floor(res.statusCode / 100) !== 3)) { 714 | if (res.statusCode === 401) { 715 | reject('Authorization failed'); 716 | } else { 717 | reject('RPC server error'); 718 | } 719 | } else { 720 | let data = Buffer.alloc(0); 721 | res.on('data', (chunk) => { 722 | data = Buffer.concat([data, chunk]); 723 | }); 724 | res.on('end', () => { 725 | try { 726 | data = JSON.parse(data.toString()); 727 | if (data.error) { reject(data.error.message); return; } 728 | } catch (error) { reject(error.message); return; } 729 | if (data.result) data = data.result; 730 | resolve(data); 731 | }); 732 | } 733 | } 734 | ); 735 | 736 | doRequest.on('error', (error) => { 737 | reject('RPC server error'); 738 | }); 739 | 740 | doRequest.on('timeout', () => { 741 | reject("RFC timeout"); 742 | doRequest.abort(); 743 | }); 744 | 745 | doRequest.end(post); 746 | } 747 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conceal-api", 3 | "version": "0.8.9", 4 | "description": "CJSI: Conceal Javascript Interface - RPC/API", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ConcealNetwork/conceal-api.git" 12 | }, 13 | "keywords": [ 14 | "conceal", 15 | "cryptocurrency", 16 | "rpc", 17 | "api" 18 | ], 19 | "author": "", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/ConcealNetwork/conceal-api/issues" 23 | }, 24 | "homepage": "https://github.com/ConcealNetwork/conceal-api#readme", 25 | "dependencies": { 26 | "follow-redirects": "^1.15.2" 27 | } 28 | } 29 | --------------------------------------------------------------------------------