From 440d63c4880a58e8f0ec5b9c52550654f272fd1a Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 18 Jul 2020 19:58:18 -0700 Subject: [PATCH 1/4] Removing greenkeeper --- greenkeeper.json | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 greenkeeper.json diff --git a/greenkeeper.json b/greenkeeper.json deleted file mode 100644 index 2fbd26a..0000000 --- a/greenkeeper.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "groups": { - "default": { - "packages": [ - "examples/applications/wallet/check-balance/package.json", - "examples/applications/wallet/consolidate-dust/package.json", - "examples/applications/wallet/consolidate-utxos/package.json", - "examples/applications/wallet/create-wallet/package.json", - "examples/applications/wallet/send-WIF/package.json", - "examples/applications/wallet/send-all/package.json", - "examples/applications/wallet/send-bch/package.json", - "examples/low-level/OP_RETURN/package.json", - "examples/low-level/address-details/package.json", - "examples/low-level/utxo-address/package.json", - "package.json" - ] - } - } -} From 7fef149c94221574878d61fcff9484549a5f042c Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 18 Jul 2020 20:21:19 -0700 Subject: [PATCH 2/4] fix(generateSendOpReturnWeb): Removed generateSendOpReturnWeb() --- src/slp/tokentype1.js | 84 ------------------------- test/unit/slp-tokentype1.js | 119 ------------------------------------ 2 files changed, 203 deletions(-) diff --git a/src/slp/tokentype1.js b/src/slp/tokentype1.js index 061e672..b53744f 100644 --- a/src/slp/tokentype1.js +++ b/src/slp/tokentype1.js @@ -125,90 +125,6 @@ class TokenType1 { } } - /** - * @api SLP.TokenType1.generateSendOpReturnWeb() generateSendOpReturnWeb() - Web-friendly OP_RETURN code for SLP Send tx - * @apiName generateSendOpReturnWeb - * @apiGroup SLP - * @apiDescription Generate the OP_RETURN value needed to create an SLP Send transaction. - * It's assumed all elements in the tokenUtxos array belong to the same token. - * Returns an object with two properties: - * - script: an array of Bufers that is ready to fed into bchjs.Script.encode2() to be turned into a transaction output. - * - outputs: an integer with a value of 1 or 2. If 2, indicates there needs to be an extra output to send token change. - */ - generateSendOpReturnWeb(tokenUtxos, sendQty) { - try { - const tokenId = tokenUtxos[0].tokenId - const decimals = tokenUtxos[0].decimals - - // Calculate the total amount of tokens owned by the wallet. - let totalTokens = 0 - for (let i = 0; i < tokenUtxos.length; i++) - totalTokens += tokenUtxos[i].tokenQty - - const change = totalTokens - sendQty - // console.log(`change: ${change}`) - - let script - let outputs = 1 - - // The normal case, when there is token change to return to sender. - if (change > 0) { - outputs = 2 - - let baseQty = new BigNumber(sendQty).times(10 ** decimals) - baseQty = baseQty.absoluteValue() - baseQty = Math.floor(baseQty) - let baseQtyHex = baseQty.toString(16) - baseQtyHex = baseQtyHex.padStart(16, "0") - - let baseChange = new BigNumber(change).times(10 ** decimals) - baseChange = baseChange.absoluteValue() - baseChange = Math.floor(baseChange) - // console.log(`baseChange: ${baseChange.toString()}`) - - let baseChangeHex = baseChange.toString(16) - baseChangeHex = baseChangeHex.padStart(16, "0") - // console.log(`baseChangeHex padded: ${baseChangeHex}`) - - script = [ - this.Script.opcodes.OP_RETURN, - Buffer.from("534c5000", "hex"), - //BITBOX.Script.opcodes.OP_1, - Buffer.from("01", "hex"), - Buffer.from(`SEND`), - Buffer.from(tokenId, "hex"), - Buffer.from(baseQtyHex, "hex"), - Buffer.from(baseChangeHex, "hex") - ] - } else { - // Corner case, when there is no token change to send back. - - let baseQty = new BigNumber(sendQty).times(10 ** decimals) - baseQty = baseQty.absoluteValue() - baseQty = Math.floor(baseQty) - let baseQtyHex = baseQty.toString(16) - baseQtyHex = baseQtyHex.padStart(16, "0") - - // console.log(`baseQty: ${baseQty.toString()}`) - - script = [ - this.Script.opcodes.OP_RETURN, - Buffer.from("534c5000", "hex"), - //BITBOX.Script.opcodes.OP_1, - Buffer.from("01", "hex"), - Buffer.from(`SEND`), - Buffer.from(tokenId, "hex"), - Buffer.from(baseQtyHex, "hex") - ] - } - - return { script, outputs } - } catch (err) { - console.log(`Error in generateSendOpReturnWeb()`) - throw err - } - } - /** * @api SLP.TokenType1.generateBurnOpReturn() generateBurnOpReturn() * @apiName generateBurnOpReturn diff --git a/test/unit/slp-tokentype1.js b/test/unit/slp-tokentype1.js index 46fd5dc..b0edd6d 100644 --- a/test/unit/slp-tokentype1.js +++ b/test/unit/slp-tokentype1.js @@ -70,125 +70,6 @@ describe("#SLP TokenType1", () => { }) }) - describe("#generateSendOpReturnWeb", () => { - it("should generate send OP_RETURN code", async () => { - // Mock UTXO. - const tokenUtxos = [ - { - txid: - "a8eb788b8ddda6faea00e6e2756624b8feb97655363d0400dd66839ea619d36e", - vout: 2, - value: "546", - confirmations: 0, - satoshis: 546, - utxoType: "token", - transactionType: "send", - tokenId: - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", - tokenTicker: "TOK-CH", - tokenName: "TokyoCash", - tokenDocumentUrl: "", - tokenDocumentHash: "", - decimals: 8, - tokenQty: 7 - } - ] - - const result = await bchjs.SLP.TokenType1.generateSendOpReturnWeb( - tokenUtxos, - 1 - ) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAllKeys(result, ["script", "outputs"]) - assert.isNumber(result.outputs) - }) - - it("should handle problematic configuration", async () => { - // Mock UTXO. - const tokenUtxos = [ - { - txid: - "0bd6b874246202b4cbc2f501419a5ce6f9b01e8ba9521298afd15c7e8eac5951", - vout: 2, - value: "546", - confirmations: 0, - satoshis: 546, - utxoType: "token", - transactionType: "send", - tokenId: - "155784a206873c98acc09e8dabcccf6abf13c4c14d8662190534138a16bb93ce", - tokenTicker: "PSF", - tokenName: "PSF Testnet Token", - tokenDocumentUrl: "", - tokenDocumentHash: "", - decimals: 8, - tokenQty: 18004.71169917 - } - ] - - const result = await bchjs.SLP.TokenType1.generateSendOpReturnWeb( - tokenUtxos, - 5000 - ) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // console.log(`result.script: `, result.script) - // console.log(`result.script: `, result.script.toString("hex")) - - // This transaction failed due to a floating point error. This is expressed - // by the script[6] being length 2 (incorrect) instead of 8 (correct). - assert.equal(result.script[5].length, 8) - assert.equal(result.script[6].length, 8) - }) - - it("should generate good OP_RETURN for problematic TX", () => { - const utxo1 = { - txid: - "35287aa2aa7d3f1954fbbcb8a748d8773359b4f2771a851959a4a6116bcb552c", - vout: 1, - amount: 0.00000546, - satoshis: 546, - legacyAddress: "1MuLZ9LzLSTKioBHoPzeSfFANTEN2CZhuS", - cashAddress: "bitcoincash:qrj5sala6z53v559q54mekexru2xe34yrquhmpy5kx", - tokenId: - "c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479", - decimals: 2, - tokenQty: 1371.6 - } - - const utxo2 = { - txid: - "cb652ce62babc81936f0f1e1d5b80102cb44de99dece83f4bbd34cfaeef744b0", - vout: 2, - amount: 0.00000546, - satoshis: 546, - legacyAddress: "1LzjkvDVB16k6kcSz8hZodW9KPP2zxwmGh", - cashAddress: "bitcoincash:qrd4tj48lpf0wv36qt5mkd6c3n4h5xcfgqqw77s63d", - tokenId: - "c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479", - decimals: 2, - tokenQty: 3250.75 - } - - const utxos = [utxo1, utxo2] - - const { script } = bchjs.SLP.TokenType1.generateSendOpReturnWeb( - utxos, - 1400 - ) - - // console.log(`script: `, script) - // console.log(`outputs: `, outputs) - - // console.log(`script[6]: ${script[6].toString("hex")}`) - // console.log(`script[6].length: ${script[6].length}`) - - // This transaction failed due to a floating point error. This is expressed - // by the script[6] being length 2 (incorrect) instead of 8 (correct). - assert.equal(script[6].length, 8) - }) - }) - describe("#generateBurnOpReturn", () => { it("should generate burn OP_RETURN code", () => { // Mock UTXO. From 6d68e05a8f32064c6cd58804ab6371a694900daf Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 18 Jul 2020 20:45:17 -0700 Subject: [PATCH 3/4] fix(getHexOpReturn): Adding SLP.TokenType1.getHexOpReturn() --- src/slp/slp.js | 2 +- src/slp/tokentype1.js | 84 +++++++++++++++++++++++++++++++++++++ test/integration/slp.js | 28 +++++++++++++ test/unit/slp-tokentype1.js | 35 ++++++++++++++++ 4 files changed, 148 insertions(+), 1 deletion(-) diff --git a/src/slp/slp.js b/src/slp/slp.js index 29efa84..b91c366 100644 --- a/src/slp/slp.js +++ b/src/slp/slp.js @@ -31,7 +31,7 @@ class SLP { this.Address = new Address(tmp) this.ECPair = ECPair - this.TokenType1 = new TokenType1(this.restURL) + this.TokenType1 = new TokenType1(tmp) this.NFT1 = new NFT1(this.restURL) this.Utils = new Utils(tmp) } diff --git a/src/slp/tokentype1.js b/src/slp/tokentype1.js index b53744f..c30f1ee 100644 --- a/src/slp/tokentype1.js +++ b/src/slp/tokentype1.js @@ -6,20 +6,34 @@ const Script = require("../script") const BigNumber = require("bignumber.js") const slpMdm = require("slp-mdm") +const axios = require("axios") // const addy = new Address() let addy const TransactionBuilder = require("../transaction-builder") +let _this // local global + class TokenType1 { constructor(config) { this.restURL = config.restURL + this.apiToken = config.apiToken addy = new Address(config) this.Script = new Script() + this.axios = axios + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + // Instantiate the transaction builder. TransactionBuilder.setAddress(addy) + + _this = this } /** @@ -360,6 +374,76 @@ class TokenType1 { throw err } } + + /** + * @api SLP.TokenType1.getHexOpReturn() getOpReturnHex() + * @apiName getOpReturnHex + * @apiGroup SLP TokenType1 + * @apiDescription Get hex representation of an SLP OP_RETURN + * This command returns a hex encoded OP_RETURN for SLP Send (Token Type 1) + * transactions. Rather than computing it directly, it calls bch-api to do + * the heavy lifting. This is easier and lighter weight for web apps. + * + * @apiExample Example usage: + * + * const addr = "bitcoincash:qq6xz6wwcy78uh79vgjvfyahj4arq269w5an8pcjak" + * const utxos = await bchjs.Blockbook.utxos(addr) + * + * // Identify the SLP token UTXOs. + * let tokenUtxos = await bchjs.SLP.Utils.tokenUtxoDetails(utxos); + * + * // Filter out the minting baton. + * tokenUtxos = tokenUtxos.filter((utxo, index) => { + * if ( + * utxo && // UTXO is associated with a token. + * utxo.tokenId === TOKENID && // UTXO matches the token ID. + * utxo.utxoType === "minting-baton" // UTXO is not a minting baton. + * ) + * return true; + * }); + * + * // Generate the SLP OP_RETURN + * const slpData = bchjs.SLP.TokenType1.generateMintOpReturn( + * tokenUtxos, + * 100 // Mint 100 new tokens. + * ); + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/applications/slp/mint-token/mint-token.js + */ + // Gets an OP_RETURN object that has scripts and outputs + async getHexOpReturn(tokenUtxos, sendQty) { + try { + // TODO: Add input filtering. + + const data = { + tokenUtxos, + sendQty + } + + const result = await _this.axios.post( + `${this.restURL}slp/generatesendopreturn`, + data, + _this.axiosOptions + ) + + const slpSendObj = result.data + + // const script = _this.Buffer.from(slpSendObj.script) + // + // slpSendObj.script = script + // return slpSendObj + + return slpSendObj + } catch (err) { + console.log(err) + throw err + } + } } module.exports = TokenType1 diff --git a/test/integration/slp.js b/test/integration/slp.js index b55275d..a641a21 100644 --- a/test/integration/slp.js +++ b/test/integration/slp.js @@ -493,6 +493,34 @@ describe(`#SLP`, () => { }) }) }) + + describe("#tokentype1", () => { + describe("#getHexOpReturn", () => { + it("should return OP_RETURN object ", async () => { + const tokenUtxos = [ + { + tokenId: + "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1", + decimals: 0, + tokenQty: 2 + } + ] + const sendQty = 1.5 + + const result = await bchjs.SLP.TokenType1.getHexOpReturn( + tokenUtxos, + sendQty + ) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "script") + assert.isString(result.script) + + assert.property(result, "outputs") + assert.isNumber(result.outputs) + }) + }) + }) }) // Promise-based sleep function diff --git a/test/unit/slp-tokentype1.js b/test/unit/slp-tokentype1.js index b0edd6d..23fa94f 100644 --- a/test/unit/slp-tokentype1.js +++ b/test/unit/slp-tokentype1.js @@ -281,4 +281,39 @@ describe("#SLP TokenType1", () => { assert.equal(Buffer.isBuffer(result), true) }) }) + + describe("#getHexOpReturn", () => { + it("should return OP_RETURN object ", async () => { + const tokenUtxos = [ + { + tokenId: + "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1", + decimals: 0, + tokenQty: 2 + } + ] + + const sendQty = 1.5 + + sandbox.stub(bchjs.SLP.TokenType1.axios, "post").resolves({ + data: { + script: + "6a04534c500001010453454e44200a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1080000000000000001080000000000000000", + outputs: 2 + } + }) + + const result = await bchjs.SLP.TokenType1.getHexOpReturn( + tokenUtxos, + sendQty + ) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "script") + assert.isString(result.script) + + assert.property(result, "outputs") + assert.isNumber(result.outputs) + }) + }) }) From 264cc1b667e00f78c97dcb33fec105314a3de816 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 18 Jul 2020 20:50:00 -0700 Subject: [PATCH 4/4] Adding api docs for getHexOpReturn() --- src/slp/tokentype1.js | 42 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/src/slp/tokentype1.js b/src/slp/tokentype1.js index c30f1ee..a1fd3fd 100644 --- a/src/slp/tokentype1.js +++ b/src/slp/tokentype1.js @@ -376,8 +376,8 @@ class TokenType1 { } /** - * @api SLP.TokenType1.getHexOpReturn() getOpReturnHex() - * @apiName getOpReturnHex + * @api SLP.TokenType1.getHexOpReturn() getHexOpReturn() + * @apiName getHexOpReturn * @apiGroup SLP TokenType1 * @apiDescription Get hex representation of an SLP OP_RETURN * This command returns a hex encoded OP_RETURN for SLP Send (Token Type 1) @@ -386,36 +386,22 @@ class TokenType1 { * * @apiExample Example usage: * - * const addr = "bitcoincash:qq6xz6wwcy78uh79vgjvfyahj4arq269w5an8pcjak" - * const utxos = await bchjs.Blockbook.utxos(addr) + * const tokenUtxos = [{ + * tokenId: "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1", + * decimals: 0, + * tokenQty: 2 + * }] * - * // Identify the SLP token UTXOs. - * let tokenUtxos = await bchjs.SLP.Utils.tokenUtxoDetails(utxos); + * const sendQty = 1.5 * - * // Filter out the minting baton. - * tokenUtxos = tokenUtxos.filter((utxo, index) => { - * if ( - * utxo && // UTXO is associated with a token. - * utxo.tokenId === TOKENID && // UTXO matches the token ID. - * utxo.utxoType === "minting-baton" // UTXO is not a minting baton. - * ) - * return true; - * }); + * const result = await bchjs.SLP.TokenType1.getHexOpReturn(tokenUtxos, sendQty) * - * // Generate the SLP OP_RETURN - * const slpData = bchjs.SLP.TokenType1.generateMintOpReturn( - * tokenUtxos, - * 100 // Mint 100 new tokens. - * ); - * - * ... - * // Add OP_RETURN as first output. - * transactionBuilder.addOutput(slpData, 0); - * - * // See additional code here: - * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/applications/slp/mint-token/mint-token.js + * // result: + * { + * "script": "6a04534c500001010453454e44200a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1080000000000000001080000000000000000", + * "outputs": 2 + * } */ - // Gets an OP_RETURN object that has scripts and outputs async getHexOpReturn(tokenUtxos, sendQty) { try { // TODO: Add input filtering.