diff --git a/src/electrumx.js b/src/electrumx.js index 361b3ed..8763df9 100644 --- a/src/electrumx.js +++ b/src/electrumx.js @@ -299,6 +299,98 @@ class ElectrumX { else throw error } } + + /** + * @api Electrumx.unconfirmed() unconfirmed() - Get a list of unconfirmed uxtos (mempool) for an address. + * @apiName ElectrumX Unconfirmed + * @apiGroup ElectrumX + * @apiDescription Return a list of unconfirmed uxtos (mempool) for an address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let mempool = await bchjs.Electrumx.unconfirmed('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'); + * console.log(mempool); + * } catch(error) { + * console.error(error) + * } + * })() + * + * mempool = { + * "success": true, + * "utxos": [ + * { + * "height": 602405, + * "tx_hash": "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", + * "fee": 24310 + * } + * ] + * } + * + * (async () => { + * try { + * let mempool = await bchjs.Electrumx.unconfirmed(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); + * console.log(mempool); + * } catch(error) { + * console.error(error) + * } + * })() + * + * mempool = { + * "success": true, + * "utxos": [ + * { + * "utxos": [ + * { + * "height": 604392, + * "tx_hash": "7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41", + * "fee": 24310 + * }, + * { + * "height": 630834, + * "tx_hash": "4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53", + * "fee": 3000 + * } + * ], + * "address": "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + * }, + * { + * "utxos": [], + * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + * } + * ] + * } + * + */ + async unconfirmed(address) { + try { + // Handle single address. + if (typeof address === "string") { + const response = await axios.get( + `${this.restURL}electrumx/unconfirmed/${address}`, + _this.axiosOptions + ) + return response.data + + // Handle array of addresses. + } else if (Array.isArray(address)) { + const response = await axios.post( + `${this.restURL}electrumx/unconfirmed`, + { + addresses: address + }, + _this.axiosOptions + ) + + return response.data + } + + throw new Error(`Input address must be a string or array of strings.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } } module.exports = ElectrumX diff --git a/test/integration/electrumx.js b/test/integration/electrumx.js index e7f9915..569ec05 100644 --- a/test/integration/electrumx.js +++ b/test/integration/electrumx.js @@ -178,4 +178,63 @@ describe(`#ElectrumX`, () => { } }) }) + + describe(`#unconfirmed`, () => { + it(`should GET unconfirmed UTXOs (mempool) for a single address`, async () => { + const addr = "bitcoincash:qqy6qwk3wpne95hhv8uzwr4cn8m7c06cqgchl77dnv" + + const result = await bchjs.Electrumx.unconfirmed(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "height") + assert.property(result.utxos[0], "tx_hash") + assert.property(result.utxos[0], "fee") + }) + + it(`should POST request for unconfirmed UTXOs (mempool) for an array of addresses`, async () => { + const addr = [ + "bitcoincash:qqy6qwk3wpne95hhv8uzwr4cn8m7c06cqgchl77dnv", + "bitcoincash:qpyrtsl9msdu2klgfpcmn2v5r22w9rk24g8pal74ts" + ] + + const result = await bchjs.Electrumx.unconfirmed(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "utxos") + assert.isArray(result.utxos[0].utxos) + assert.property(result.utxos[0], "address") + + assert.property(result.utxos[0].utxos[0], "height") + assert.property(result.utxos[0].utxos[0], "tx_hash") + assert.property(result.utxos[0].utxos[0], "fee") + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf") + + const result = await bchjs.Electrumx.unconfirmed(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) }) diff --git a/test/integration/testnet/electrumx.js b/test/integration/testnet/electrumx.js index aad0b39..2f4915e 100644 --- a/test/integration/testnet/electrumx.js +++ b/test/integration/testnet/electrumx.js @@ -182,4 +182,63 @@ describe(`#ElectrumX`, () => { } }) }) + + describe(`#unconfirmed`, () => { + it(`should GET unconfirmed UTXOs (mempool) for a single address`, async () => { + const addr = "bchtest:qp25k20dgcljrz4hkdz43partam3j5httyprjp23qd" + + const result = await bchjs.Electrumx.unconfirmed(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "height") + assert.property(result.utxos[0], "tx_hash") + assert.property(result.utxos[0], "fee") + }) + + it(`should POST unconfirmed UTXO details for an array of addresses`, async () => { + const addr = [ + "bchtest:qp25k20dgcljrz4hkdz43partam3j5httyprjp23qd", + "bchtest:qpkl3xylrjx4jup6m66e7zg7whlaucsxeudxeqawdj" + ] + + const result = await bchjs.Electrumx.unconfirmed(addr) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "utxos") + assert.isArray(result.utxos[0].utxos) + assert.property(result.utxos[0], "address") + + assert.property(result.utxos[0].utxos[0], "height") + assert.property(result.utxos[0].utxos[0], "tx_hash") + assert.property(result.utxos[0].utxos[0], "fee") + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2") + + const result = await bchjs.Electrumx.unconfirmed(addr) + //console.log(`result: ${util.inspect(result)}`) + + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) }) diff --git a/test/unit/electrumx.js b/test/unit/electrumx.js index 50fdd3e..1155fee 100644 --- a/test/unit/electrumx.js +++ b/test/unit/electrumx.js @@ -196,4 +196,68 @@ describe(`#ElectrumX`, () => { assert.property(result.transactions[0].transactions[0], "tx_hash") }) }) + + describe(`#unconfirmed`, () => { + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.Electrumx.unconfirmed(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + // console.log(`err: `, err) + assert.include( + err.message, + `Input address must be a string or array of strings` + ) + } + }) + + it(`should GET unconfirmed utxos for a single address`, async () => { + // Stub the network call. + sandbox.stub(axios, "get").resolves({ data: mockData.unconfirmed }) + + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.Electrumx.unconfirmed(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "height") + assert.property(result.utxos[0], "tx_hash") + assert.property(result.utxos[0], "fee") + }) + + it(`should POST unconfirmed utxo details for an array of addresses`, async () => { + // Stub the network call. + sandbox.stub(axios, "post").resolves({ data: mockData.unconfirmedArray }) + + const addr = [ + "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + ] + + const result = await bchjs.Electrumx.unconfirmed(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "utxos") + assert.isArray(result.utxos[0].utxos) + assert.property(result.utxos[0], "address") + + assert.property(result.utxos[0].utxos[0], "height") + assert.property(result.utxos[0].utxos[0], "tx_hash") + assert.property(result.utxos[0].utxos[0], "fee") + }) + }) }) diff --git a/test/unit/fixtures/electrumx-mock.js b/test/unit/fixtures/electrumx-mock.js index 8e951c7..f5ffe88 100644 --- a/test/unit/fixtures/electrumx-mock.js +++ b/test/unit/fixtures/electrumx-mock.js @@ -119,11 +119,52 @@ const transactions = { ] } +const unconfirmed = { + success: true, + utxos: [ + { + height: 602405, + tx_hash: + "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", + fee: 34100 + } + ] +} + +const unconfirmedArray = { + success: true, + utxos: [ + { + utxos: [ + { + height: 604392, + tx_hash: + "7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41", + fee: 34210 + }, + { + height: 630834, + tx_hash: + "4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53", + value: 3000 + } + ], + address: "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + }, + { + utxos: [], + address: "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + } + ] +} + module.exports = { utxo, utxos, balance, balances, transaction, - transactions + transactions, + unconfirmed, + unconfirmedArray }