diff --git a/src/slp/nft1.js b/src/slp/nft1.js new file mode 100644 index 0000000..13b4ffe --- /dev/null +++ b/src/slp/nft1.js @@ -0,0 +1,126 @@ +/* + This library wraps the slp-mdm library to generate the OP_RETURN for NFT1 tokens. + + NFT Group tokens (Parents) are generated and minted. They are like amorphous + NFTs; like stem cells that haven't specialized yet. + + NFT 'Children' are the 'real' NFTs. They are created by burning an NFT Group + (Parent) token. +*/ + +const Address = require("./address") + +const BigNumber = require("bignumber.js") +const slpMdm = require("slp-mdm") + +// const addy = new Address() +let addy +const TransactionBuilder = require("../transaction-builder") + +class TokenType1 { + constructor(config) { + this.restURL = config.restURL + + addy = new Address(config) + + // Instantiate the transaction builder. + TransactionBuilder.setAddress(addy) + } + + // New NFT Group 'Parent' + newNFTGroupOpReturn(configObj) { + try { + // TODO: Add input validation. + + // Prevent error if user fails to add the document hash. + if (!configObj.documentHash) configObj.documentHash = "" + + // If mint baton is not specified, then replace it with null. + if (!configObj.mintBatonVout) configObj.mintBatonVout = null + + const script = slpMdm.NFT1.Group.genesis( + configObj.ticker, + configObj.name, + configObj.documentUrl, + configObj.documentHash, + 0, + configObj.mintBatonVout, + new slpMdm.BN("1") + ) + + return script + } catch (err) { + console.log(`Error in generateNFTParentOpReturn()`) + throw err + } + } + + // Mint additional NFT Group 'Parent' tokens. + mintNFTGroupOpReturn(tokenUtxos, mintQty, destroyBaton = false) { + try { + // Throw error if input is not an array. + if (!Array.isArray(tokenUtxos)) + throw new Error(`tokenUtxos must be an array.`) + + // Loop through the tokenUtxos array and find the minting baton. + let mintBatonUtxo + for (let i = 0; i < tokenUtxos.length; i++) { + if (tokenUtxos[i].utxoType === "minting-baton") + mintBatonUtxo = tokenUtxos[i] + } + + // Throw an error if the minting baton could not be found. + if (!mintBatonUtxo) + throw new Error(`Minting baton could not be found in tokenUtxos array.`) + + const tokenId = mintBatonUtxo.tokenId + + if (!tokenId) + throw new Error(`tokenId property not found in mint-baton UTXO.`) + + // Signal that the baton should be passed or detroyed. + let batonVout = 2 + if (destroyBaton) batonVout = null + + const script = slpMdm.NFT1.Group.mint( + tokenId, + batonVout, + new slpMdm.BN(mintQty) + ) + + return script + } catch (err) { + // console.log(`Error in generateMintOpReturn()`) + throw err + } + } + + generateNFTChildOpReturn(configObj) { + try { + // TODO: Add input validation. + + // Prevent error if user fails to add the document hash. + if (!configObj.documentHash) configObj.documentHash = "" + + // If mint baton is not specified, then replace it with null. + if (!configObj.mintBatonVout) configObj.mintBatonVout = null + + const script = slpMdm.NFT1.Group.genesis( + configObj.ticker, + configObj.name, + configObj.documentUrl, + configObj.documentHash, + 0, + configObj.mintBatonVout, + new slpMdm.BN("1") + ) + + return script + } catch (err) { + console.log(`Error in generateNFTChildOpReturn()`) + throw err + } + } +} + +module.exports = TokenType1 diff --git a/src/slp/slp.js b/src/slp/slp.js index 4b67564..29efa84 100644 --- a/src/slp/slp.js +++ b/src/slp/slp.js @@ -12,6 +12,7 @@ const Address = require("./address") const ECPair = require("./ecpair") // const HDNode = require("./hdnode") const TokenType1 = require("./tokentype1") +const NFT1 = require("./nft1") const Utils = require("./utils") // SLP is a superset of BITBOX @@ -31,6 +32,7 @@ class SLP { this.Address = new Address(tmp) this.ECPair = ECPair this.TokenType1 = new TokenType1(this.restURL) + this.NFT1 = new NFT1(this.restURL) this.Utils = new Utils(tmp) } } diff --git a/src/slp/tokentype1.js b/src/slp/tokentype1.js index 969b7c9..1f434b2 100644 --- a/src/slp/tokentype1.js +++ b/src/slp/tokentype1.js @@ -2,7 +2,6 @@ //const bchjs = new BCHJS() const Address = require("./address") -const Script = require("../script") const BigNumber = require("bignumber.js") const slpMdm = require("slp-mdm") @@ -15,8 +14,6 @@ class TokenType1 { constructor(config) { this.restURL = config.restURL - this.Script = new Script() - addy = new Address(config) // Instantiate the transaction builder. @@ -361,61 +358,6 @@ class TokenType1 { throw err } } - - // Parent NFT - generateNFTGenesisOpReturn(configObj) { - try { - // TODO: Add input validation. - - // Prevent error if user fails to add the document hash. - if (!configObj.documentHash) configObj.documentHash = "" - - // If mint baton is not specified, then replace it with null. - if (!configObj.mintBatonVout) configObj.mintBatonVout = null - - const script = slpMdm.NFT1.Group.genesis( - configObj.ticker, - configObj.name, - configObj.documentUrl, - configObj.documentHash, - 0, - configObj.mintBatonVout, - new slpMdm.BN("1") - ) - - return script - } catch (err) { - console.log(`Error in generateNFTGenesisOpReturn()`) - throw err - } - } - - generateNFTChildOpReturn(configObj) { - try { - // TODO: Add input validation. - - // Prevent error if user fails to add the document hash. - if (!configObj.documentHash) configObj.documentHash = "" - - // If mint baton is not specified, then replace it with null. - if (!configObj.mintBatonVout) configObj.mintBatonVout = null - - const script = slpMdm.NFT1.Group.genesis( - configObj.ticker, - configObj.name, - configObj.documentUrl, - configObj.documentHash, - 0, - configObj.mintBatonVout, - new slpMdm.BN("1") - ) - - return script - } catch (err) { - console.log(`Error in generateNFTChildOpReturn()`) - throw err - } - } } module.exports = TokenType1 diff --git a/src/slp/utils.js b/src/slp/utils.js index 0cbb832..8f59d8d 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -1167,6 +1167,7 @@ class Utils { utxo.tokenDocumentUrl = slpData.documentUri utxo.tokenDocumentHash = slpData.documentHash utxo.decimals = slpData.decimals + utxo.tokenType = slpData.tokenType // something outAry[i] = utxo @@ -1201,6 +1202,7 @@ class Utils { // Hydrate the UTXO object with information about the SLP token. utxo.transactionType = "mint" utxo.tokenId = slpData.tokenId + utxo.tokenType = slpData.tokenType utxo.tokenTicker = genesisData.ticker utxo.tokenName = genesisData.name @@ -1249,6 +1251,7 @@ class Utils { utxo.tokenDocumentUrl = genesisData.documentUri utxo.tokenDocumentHash = genesisData.documentHash utxo.decimals = genesisData.decimals + utxo.tokenType = slpData.tokenType // Calculate the real token quantity. utxo.tokenQty = tokenQty / Math.pow(10, genesisData.decimals) diff --git a/test/unit/fixtures/slp/mock-utils.js b/test/unit/fixtures/slp/mock-utils.js index ea1b233..8e145f0 100644 --- a/test/unit/fixtures/slp/mock-utils.js +++ b/test/unit/fixtures/slp/mock-utils.js @@ -1031,6 +1031,82 @@ const mockInvalidSlpSend = { blocktime: 1535577007 } +const txDetailsSLPNftGenesis = { + txid: "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + hash: "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + version: 2, + size: 344, + locktime: 0, + vin: [ + { + txid: "4041bc842554b796de1dbb625bbb6994379bc5132a47522677bef7323f3e5051", + vout: 2, + scriptSig: { + asm: + "3045022100f8d678e7cd6a2fc317fe4c0c8b633f5941aa40bceb6d7472559955908869544b02206d82e45a6658bb9b5ad39734aa93832b1de2d3c9650e1b59c2ee2f15aa26f3bb[ALL|FORKID] 029547345d63f86ea89ea92ea4ed26386c4493ff60c78d871cc7b38f517f3fd72c", + hex: + "483045022100f8d678e7cd6a2fc317fe4c0c8b633f5941aa40bceb6d7472559955908869544b02206d82e45a6658bb9b5ad39734aa93832b1de2d3c9650e1b59c2ee2f15aa26f3bb4121029547345d63f86ea89ea92ea4ed26386c4493ff60c78d871cc7b38f517f3fd72c" + }, + sequence: 4294967295 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + "OP_RETURN 5262419 -1 47454e45534953 4e46545454 4e4654205465737420546f6b656e 68747470733a2f2f46756c6c537461636b2e63617368 0 0 2 0000000000000001", + hex: + "6a04534c500001810747454e45534953054e465454540e4e4654205465737420546f6b656e1668747470733a2f2f46756c6c537461636b2e636173684c0001000102080000000000000001", + type: "nulldata" + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 6011206cd60db8b634f85edf46da22a6d1351e54 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a9146011206cd60db8b634f85edf46da22a6d1351e5488ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qpspzgrv6cxm3d35lp0d73k6y2ndzdg72s2304ttr8"] + } + }, + { + value: 0.00000546, + n: 2, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 6011206cd60db8b634f85edf46da22a6d1351e54 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a9146011206cd60db8b634f85edf46da22a6d1351e5488ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qpspzgrv6cxm3d35lp0d73k6y2ndzdg72s2304ttr8"] + } + }, + { + value: 0.0001562, + n: 3, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 6011206cd60db8b634f85edf46da22a6d1351e54 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a9146011206cd60db8b634f85edf46da22a6d1351e5488ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qpspzgrv6cxm3d35lp0d73k6y2ndzdg72s2304ttr8"] + } + } + ], + hex: + "020000000151503e3f32f7be772652472a13c59b379469bb5b62bb1dde96b7542584bc4140020000006b483045022100f8d678e7cd6a2fc317fe4c0c8b633f5941aa40bceb6d7472559955908869544b02206d82e45a6658bb9b5ad39734aa93832b1de2d3c9650e1b59c2ee2f15aa26f3bb4121029547345d63f86ea89ea92ea4ed26386c4493ff60c78d871cc7b38f517f3fd72cffffffff0400000000000000004b6a04534c500001810747454e45534953054e465454540e4e4654205465737420546f6b656e1668747470733a2f2f46756c6c537461636b2e636173684c000100010208000000000000000122020000000000001976a9146011206cd60db8b634f85edf46da22a6d1351e5488ac22020000000000001976a9146011206cd60db8b634f85edf46da22a6d1351e5488ac043d0000000000001976a9146011206cd60db8b634f85edf46da22a6d1351e5488ac00000000", + blockhash: "000000000000000002405a44302888bc29bf0e9f4d99b97303038bcc83c15e33", + confirmations: 2, + time: 1591329189, + blocktime: 1591329189 +} + module.exports = { mockList, mockToken, @@ -1051,6 +1127,7 @@ module.exports = { txDetailsSLPSend, txDetailsSLPGenesisNoBaton, txDetailsSLPSendAlt, + txDetailsSLPNftGenesis, mockTxDetails, mockDualValidation, mockDualOpData, diff --git a/test/unit/slp-nft1.js b/test/unit/slp-nft1.js new file mode 100644 index 0000000..46ac0f6 --- /dev/null +++ b/test/unit/slp-nft1.js @@ -0,0 +1,127 @@ +/* + Unit tests for the TokenType1 library. +*/ + +const assert = require("chai").assert +const nock = require("nock") // http call mocking +const sinon = require("sinon") +// const axios = require("axios") + +// Default to unit tests unless some other value for TEST is passed. +if (!process.env.TEST) process.env.TEST = "unit" +// const SERVER = bchjs.restURL + +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +// Mock data used for unit tests +// const mockData = require("./fixtures/slp/mock-utils") + +// Default to unit tests unless some other value for TEST is passed. +if (!process.env.TEST) process.env.TEST = "unit" + +describe("#SLP NFT1", () => { + let sandbox + + beforeEach(() => { + // Activate nock if it's inactive. + if (!nock.isActive()) nock.activate() + + sandbox = sinon.createSandbox() + }) + + afterEach(() => { + // Clean up HTTP mocks. + nock.cleanAll() // clear interceptor list. + nock.restore() + + sandbox.restore() + }) + + describe("#newNFTGroupOpReturn", () => { + it("should generate new NFT Group OP_RETURN code", () => { + const configObj = { + name: "SLP Test Token", + ticker: "SLPTEST", + documentUrl: "https://bchjs.cash", + documentHash: "" + } + + const result = bchjs.SLP.NFT1.newNFTGroupOpReturn(configObj) + // console.log(`result: `, result) + + assert.equal(Buffer.isBuffer(result), true) + }) + }) + + describe("#mintNFTGroupOpReturn", () => { + it("should generate NFT Group Mint OP_RETURN code", () => { + const tokenUtxoData = [ + { + txid: + "3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76", + vout: 2, + value: "546", + height: 638207, + confirmations: 63, + satoshis: 546, + utxoType: "minting-baton", + transactionType: "mint", + tokenId: + "680967b3f6fe080dbca8dbd370665bd29742e3490db24e2f28d08b424511807e", + tokenType: 129, + tokenTicker: "NFTP", + tokenName: "NFT Parent", + tokenDocumentUrl: "FullStack.cash", + tokenDocumentHash: "", + decimals: 0, + mintBatonVout: 2, + isValid: true + }, + { + txid: + "3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76", + vout: 1, + value: "546", + height: 638207, + confirmations: 63, + satoshis: 546, + utxoType: "token", + tokenQty: 10, + transactionType: "mint", + tokenId: + "680967b3f6fe080dbca8dbd370665bd29742e3490db24e2f28d08b424511807e", + tokenType: 129, + tokenTicker: "NFTP", + tokenName: "NFT Parent", + tokenDocumentUrl: "FullStack.cash", + tokenDocumentHash: "", + decimals: 0, + mintBatonVout: 2, + isValid: true + } + ] + + const result = bchjs.SLP.NFT1.mintNFTGroupOpReturn(tokenUtxoData, 10) + // console.log(`result: `, result) + + assert.equal(Buffer.isBuffer(result), true) + }) + }) + + describe("#generateNFTChildOpReturn", () => { + it("should generate NFT Genesis OP_RETURN code", () => { + const configObj = { + name: "SLP Test Token", + ticker: "SLPTEST", + documentUrl: "https://bchjs.cash", + documentHash: "" + } + + const result = bchjs.SLP.NFT1.generateNFTChildOpReturn(configObj) + // console.log(`result: `, result) + + assert.equal(Buffer.isBuffer(result), true) + }) + }) +}) diff --git a/test/unit/slp-tokentype1.js b/test/unit/slp-tokentype1.js index b7f150d..b0edd6d 100644 --- a/test/unit/slp-tokentype1.js +++ b/test/unit/slp-tokentype1.js @@ -38,127 +38,8 @@ describe("#SLP TokenType1", () => { sandbox.restore() }) - // CT 5/31/2020 This is old code for the older version of generateSendOpReturn() - // This code can be delete in a couple weeks once things are stable. - // - // describe("#generateSendOpReturn", () => { - // 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.generateSendOpReturn( - // 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.generateSendOpReturn( - // 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.generateSendOpReturn(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("#generateSendOpReturn", () => { - it("should generate send OP_RETURN code", async () => { + it("should generate send OP_RETURN code", () => { // Mock UTXO. const tokenUtxos = [ { @@ -181,10 +62,7 @@ describe("#SLP TokenType1", () => { } ] - const result = await bchjs.SLP.TokenType1.generateSendOpReturn( - tokenUtxos, - 1 - ) + const result = bchjs.SLP.TokenType1.generateSendOpReturn(tokenUtxos, 1) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.hasAllKeys(result, ["script", "outputs"]) @@ -193,7 +71,7 @@ describe("#SLP TokenType1", () => { }) describe("#generateBurnOpReturn", () => { - it("should generate burn OP_RETURN code", async () => { + it("should generate burn OP_RETURN code", () => { // Mock UTXO. const tokenUtxos = [ { @@ -216,10 +94,7 @@ describe("#SLP TokenType1", () => { } ] - const result = await bchjs.SLP.TokenType1.generateBurnOpReturn( - tokenUtxos, - 1 - ) + const result = bchjs.SLP.TokenType1.generateBurnOpReturn(tokenUtxos, 1) // console.log(`result: ${JSON.stringify(result, null, 2)}`) // console.log(`result: `, result) @@ -228,7 +103,7 @@ describe("#SLP TokenType1", () => { }) describe("#generateGenesisOpReturn", () => { - it("should generate genesis OP_RETURN code", async () => { + it("should generate genesis OP_RETURN code", () => { const configObj = { name: "SLP Test Token", ticker: "SLPTEST", @@ -238,15 +113,13 @@ describe("#SLP TokenType1", () => { initialQty: 10 } - const result = await bchjs.SLP.TokenType1.generateGenesisOpReturn( - configObj - ) + const result = bchjs.SLP.TokenType1.generateGenesisOpReturn(configObj) // console.log(`result: `, result) assert.equal(Buffer.isBuffer(result), true) }) - it("should work if user does not specify doc hash", async () => { + it("should work if user does not specify doc hash", () => { const configObj = { name: "SLP Test Token", ticker: "SLPTEST", @@ -255,9 +128,7 @@ describe("#SLP TokenType1", () => { initialQty: 10 } - const result = await bchjs.SLP.TokenType1.generateGenesisOpReturn( - configObj - ) + const result = bchjs.SLP.TokenType1.generateGenesisOpReturn(configObj) // console.log(`result: `, result) assert.equal(Buffer.isBuffer(result), true) @@ -265,9 +136,9 @@ describe("#SLP TokenType1", () => { }) describe("#generateMintOpReturn", () => { - it("should throw error if tokenUtxos is not an array.", async () => { + it("should throw error if tokenUtxos is not an array.", () => { try { - await bchjs.SLP.TokenType1.generateMintOpReturn({}, 100) + bchjs.SLP.TokenType1.generateMintOpReturn({}, 100) assert.equal(true, false, "Unexpected result.") } catch (err) { @@ -279,7 +150,7 @@ describe("#SLP TokenType1", () => { } }) - it("should throw error if minting baton is not in UTXOs.", async () => { + it("should throw error if minting baton is not in UTXOs.", () => { try { const utxos = [ { @@ -303,7 +174,7 @@ describe("#SLP TokenType1", () => { } ] - await bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100) + bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100) assert.equal(true, false, "Unexpected result.") } catch (err) { @@ -315,7 +186,7 @@ describe("#SLP TokenType1", () => { } }) - it("should throw error if tokenId is not included in minting-baton UTXO.", async () => { + it("should throw error if tokenId is not included in minting-baton UTXO.", () => { try { const utxos = [ { @@ -336,7 +207,7 @@ describe("#SLP TokenType1", () => { } ] - await bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100) + bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100) assert.equal(true, false, "Unexpected result.") } catch (err) { @@ -348,7 +219,7 @@ describe("#SLP TokenType1", () => { } }) - it("should throw error if decimals is not included in minting-baton UTXO.", async () => { + it("should throw error if decimals is not included in minting-baton UTXO.", () => { try { const utxos = [ { @@ -370,7 +241,7 @@ describe("#SLP TokenType1", () => { } ] - await bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100) + bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100) assert.equal(true, false, "Unexpected result.") } catch (err) { @@ -382,7 +253,7 @@ describe("#SLP TokenType1", () => { } }) - it("should generate genesis OP_RETURN code", async () => { + it("should generate genesis OP_RETURN code", () => { tokenUtxo = [ { txid: @@ -404,46 +275,7 @@ describe("#SLP TokenType1", () => { } ] - const result = await bchjs.SLP.TokenType1.generateMintOpReturn( - tokenUtxo, - 100 - ) - // console.log(`result: `, result) - - assert.equal(Buffer.isBuffer(result), true) - }) - }) - - describe("#generateNFTOpReturn", () => { - it("should generate NFT Genesis OP_RETURN code", async () => { - const configObj = { - name: "SLP Test Token", - ticker: "SLPTEST", - documentUrl: "https://bchjs.cash", - documentHash: "" - } - - const result = await bchjs.SLP.TokenType1.generateNFTGenesisOpReturn( - configObj - ) - // console.log(`result: `, result) - - assert.equal(Buffer.isBuffer(result), true) - }) - }) - - describe("#generateNFTChildOpReturn", () => { - it("should generate NFT Genesis OP_RETURN code", async () => { - const configObj = { - name: "SLP Test Token", - ticker: "SLPTEST", - documentUrl: "https://bchjs.cash", - documentHash: "" - } - - const result = await bchjs.SLP.TokenType1.generateNFTChildOpReturn( - configObj - ) + const result = bchjs.SLP.TokenType1.generateMintOpReturn(tokenUtxo, 100) // console.log(`result: `, result) assert.equal(Buffer.isBuffer(result), true) diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index 9923956..f87c763 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -354,177 +354,6 @@ describe("#SLP Utils", () => { }) }) - // describe("#decodeOpReturn", () => { - // it("should throw an error for a non-string input", async () => { - // try { - // const txid = 53423 // Not a string. - // - // await slp.Utils.decodeOpReturn(txid) - // - // assert2.equal(true, false, "Unexpected result.") - // } catch (err) { - // //console.log(`err: ${util.inspect(err)}`) - // assert2.include(err.message, `txid string must be included`) - // } - // }) - // - // it("should throw an error for non-SLP transaction", async () => { - // try { - // // Mock the call to the REST API - // if (process.env.TEST === "unit") { - // sandbox - // .stub(axios, "get") - // .resolves({ data: mockData.nonSLPTxDetailsWithoutOpReturn }) - // } - // - // const txid = - // "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec" - // - // await slp.Utils.decodeOpReturn(txid) - // - // assert2.equal(true, false, "Unexpected result.") - // } catch (err) { - // assert2.include(err.message, `Not an OP_RETURN`) - // //console.log(`err: ${util.inspect(err)}`) - // } - // }) - // - // it("should throw an error for non-SLP transaction with OP_RETURN", async () => { - // try { - // // Mock the call to the REST API - // if (process.env.TEST === "unit") { - // sandbox - // .stub(axios, "get") - // .resolves({ data: mockData.nonSLPTxDetailsWithOpReturn }) - // } - // - // const txid = - // "2ff74c48a5d657cf45f699601990bffbbe7a2a516d5480674cbf6c6a4497908f" - // - // await slp.Utils.decodeOpReturn(txid) - // - // assert2.equal(true, false, "Unexpected result.") - // } catch (err) { - // assert2.include(err.message, `Not a SLP OP_RETURN`) - // //console.log(`err: ${util.inspect(err)}`) - // } - // }) - // - // it("should decode a genesis transaction", async () => { - // // Mock the call to the REST API - // if (process.env.TEST === "unit") { - // sandbox - // .stub(axios, "get") - // .resolves({ data: mockData.txDetailsSLPGenesis }) - // } - // - // const txid = - // "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90" - // - // const data = await slp.Utils.decodeOpReturn(txid) - // //console.log(`data: ${JSON.stringify(data, null, 2)}`) - // - // assert2.hasAnyKeys(data, [ - // "tokenType", - // "transactionType", - // "ticker", - // "name", - // "documentUrl", - // "documentHash", - // "decimals", - // "mintBatonVout", - // "initialQty", - // "tokensSentTo", - // "batonHolder" - // ]) - // }) - // - // it("should decode a mint transaction", async () => { - // // Mock the call to the REST API - // if (process.env.TEST === "unit") - // sandbox.stub(axios, "get").resolves({ data: mockData.txDetailsSLPMint }) - // - // const txid = - // "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4" - // - // const data = await slp.Utils.decodeOpReturn(txid) - // //console.log(`data: ${JSON.stringify(data, null, 2)}`) - // - // assert2.hasAnyKeys(data, [ - // "transactionType", - // "tokenType", - // "tokenId", - // "mintBatonVout", - // "batonStillExists", - // "quantity", - // "tokensSentTo", - // "batonHolder" - // ]) - // }) - // - // it("should decode a send transaction", async () => { - // // Mock the call to the REST API - // if (process.env.TEST === "unit") - // sandbox.stub(axios, "get").resolves({ data: mockData.txDetailsSLPSend }) - // - // const txid = - // "4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa" - // - // const data = await slp.Utils.decodeOpReturn(txid) - // //console.log(`data: ${JSON.stringify(data, null, 2)}`) - // - // assert2.hasAnyKeys(data, [ - // "transactionType", - // "tokenType", - // "tokenId", - // "spendData" - // ]) - // assert2.isArray(data.spendData) - // assert2.hasAnyKeys(data.spendData[0], ["quantity", "sentTo", "vout"]) - // }) - // - // it("should properly decode a Genesis transaction with no minting baton", async () => { - // // Mock the call to the REST API. - // if (process.env.TEST === "unit") { - // sandbox - // .stub(axios, "get") - // .resolves({ data: mockData.txDetailsSLPGenesisNoBaton }) - // } - // - // const txid = - // "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - // - // const data = await slp.Utils.decodeOpReturn(txid) - // //console.log(`data: ${JSON.stringify(data, null, 2)}`) - // - // assert2.include(data.batonHolder, "NEVER_CREATED") - // }) - // - // it("should decode a send transaction with alternate encoding", async () => { - // // Mock the call to rest.bitcoin.com - // if (process.env.TEST === "unit") { - // sandbox - // .stub(axios, "get") - // .resolves({ data: mockData.txDetailsSLPSendAlt }) - // } - // - // const txid = - // "d94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae" - // - // const data = await slp.Utils.decodeOpReturn(txid) - // //console.log(`data: ${JSON.stringify(data, null, 2)}`) - // - // assert2.hasAnyKeys(data, [ - // "transactionType", - // "tokenType", - // "tokenId", - // "spendData" - // ]) - // assert2.isArray(data.spendData) - // assert2.hasAnyKeys(data.spendData[0], ["quantity", "sentTo", "vout"]) - // }) - // }) - describe("#decodeOpReturn", () => { it("should throw an error for a non-string input", async () => { try { @@ -704,6 +533,62 @@ describe("#SLP Utils", () => { assert2.include(err.message, "amount string size not 8 bytes") } }) + + it("should decode a NFT Parent transaction", async () => { + // Mock the call to the REST API. + sandbox + .stub(axios, "get") + .resolves({ data: mockData.txDetailsSLPNftGenesis }) + + const txid = + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4" + + const data = await slp.Utils.decodeOpReturn(txid) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.property(data, "tokenType") + assert2.property(data, "txType") + assert2.property(data, "ticker") + assert2.property(data, "name") + assert2.property(data, "tokenId") + assert2.property(data, "documentUri") + assert2.property(data, "documentHash") + assert2.property(data, "decimals") + assert2.property(data, "mintBatonVout") + assert2.property(data, "qty") + + assert2.equal(data.tokenType, 129) + assert2.equal(data.mintBatonVout, 2) + assert2.equal(data.qty, 1) + }) + + // it("should decode a NFT Child transaction", async () => { + // // Mock the call to the REST API. + // // sandbox + // // .stub(axios, "get") + // // .resolves({ data: mockData.txDetailsSLPNftGenesis }) + // + // const txid = + // "3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76" + // + // const data = await slp.Utils.decodeOpReturn(txid) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // // assert2.property(data, "tokenType") + // // assert2.property(data, "txType") + // // assert2.property(data, "ticker") + // // assert2.property(data, "name") + // // assert2.property(data, "tokenId") + // // assert2.property(data, "documentUri") + // // assert2.property(data, "documentHash") + // // assert2.property(data, "decimals") + // // assert2.property(data, "mintBatonVout") + // // assert2.property(data, "qty") + // // + // // assert2.equal(data.tokenType, 129) + // // assert2.equal(data.mintBatonVout, 2) + // // assert2.equal(data.qty, 1) + // }) }) describe("#isTokenUtxo", () => { @@ -1604,6 +1489,96 @@ describe("#SLP Utils", () => { assert2.equal(data[1].utxoType, "minting-baton") assert2.equal(false, data[2]) }) + + it("should decode a NFT Parent transaction", async () => { + // Define stubbed data. + const slpData = { + tokenType: 129, + txType: "GENESIS", + ticker: "NFTTT", + name: "NFT Test Token", + tokenId: + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + documentUri: "https://FullStack.cash", + documentHash: "", + decimals: 0, + mintBatonVout: 2, + qty: "1" + } + + const stubValid = [ + { + txid: + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + valid: true + } + ] + + // Mock external dependencies. + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .resolves(slpData) + .onCall(1) + .resolves(slpData) + .onCall(2) + .resolves(slpData) + .onCall(3) + .resolves(slpData) + .onCall(4) + .resolves(slpData) + + // Stub the call to validateTxid + sandbox + .stub(slp.Utils, "validateTxid") + .resolves(stubValid) + .onCall(1) + .resolves(stubValid) + .onCall(2) + .resolves(stubValid) + + const utxos = [ + { + txid: + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + vout: 3, + value: "15620", + height: 638207, + confirmations: 3, + satoshis: 15620 + }, + { + txid: + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + vout: 2, + value: "546", + height: 638207, + confirmations: 3, + satoshis: 546 + }, + { + txid: + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + vout: 1, + value: "546", + height: 638207, + confirmations: 3, + satoshis: 546 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.isArray(data) + assert2.equal(data[0], false) + + assert2.equal(data[1].utxoType, "minting-baton") + assert2.equal(data[1].tokenType, 129) + + assert2.equal(data[2].utxoType, "token") + assert2.equal(data[2].tokenType, 129) + }) }) describe("#txDetails", () => {