Merge pull request #23 from christroutner/slp-mdm

decodeOpReturn2() - alternate implementation
This commit is contained in:
Chris Troutner
2020-05-19 21:14:03 -07:00
committed by GitHub
9 changed files with 650 additions and 3 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ wallet-info.txt
wallet.json
docs/
coverage/
# This paragraph comes last. Force includes specific files.
!docs/README.md
+93
View File
@@ -0,0 +1,93 @@
# Permissionless Software Foundation Community Contributing Guide 1.0
This document describes a very simple process suitable for most projects under
the PSF umbrella. It is based on [this Medium article](https://medium.com/the-node-js-collection/healthy-open-source-967fa8be7951) and the [Node.js Community Contribution Guide](https://github.com/nodejs/TSC/blob/master/BasePolicies/CONTRIBUTING.md).
Projects are encouraged to adopt this whether they
are hosted under the PSF or not.
The goal of this document is to create a contribution process that:
* Encourages new contributions.
* Encourages contributors to remain involved.
* Avoids unnecessary processes and bureaucracy whenever possible.
* Creates a transparent decision making process which makes it clear how
contributors can be involved in decision making.
This document is based on much prior art in the Node.js community, io.js,
and the Node.js project.
Additional guidance can be found at the [Permissionless Software Foundation Telegram Channel](https://t.me/permissionless_software).
## Vocabulary
* A **Contributor** is any individual creating or commenting on an issue or pull request.
* A **Committer** is a subset of contributors who have been given write access to the repository.
* A **TC (Technical Committee)** is a group of committers representing the required technical
expertise to resolve rare disputes.
# Logging Issues
Log an issue for any question or problem you might have. When in doubt, log an issue,
any additional policies about what to include will be provided in the responses. The only
exception is security disclosures which should be sent privately.
Committers may direct you to another repository, ask for additional clarifications, and
add appropriate metadata before the issue is addressed.
Please be courteous, respectful, and every participant is expected to follow the
project's Code of Conduct.
# Contributions
Any change to resources in this repository must be through pull requests. This applies to all changes
to documentation, code, binary files, etc. Even long term committers and TC members must use
pull requests.
No pull request can be merged without being reviewed.
For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that
contributors in other timezones have time to review. Consideration should also be given to
weekends and other holiday periods to ensure active committers all have reasonable time to
become involved in the discussion and review process if they wish.
The default for each contribution is that it is accepted once no committer has an objection.
During review committers may also request that a specific contributor who is most versed in a
particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off"
process for contributions to land. Once all issues brought by committers are addressed it can
be landed by any committer.
In the case of an objection being raised in a pull request by another committer, all involved
committers should seek to arrive at a consensus by way of addressing concerns being expressed
by discussion, compromise on the proposed change, or withdrawal of the proposed change.
If a contribution is controversial and committers cannot agree about how to get it to land
or if it should land then it should be escalated to the TC. TC members should regularly
discuss pending contributions in order to find a resolution. It is expected that only a
small minority of issues be brought to the TC for resolution and that discussion and
compromise among committers be the default resolution mechanism.
# Becoming a Committer
All contributors who land a non-trivial contribution should be on-boarded in a timely manner,
and added as a committer, and be given write access to the repository.
Committers are expected to follow this policy and continue to send pull requests, go through
proper review, and have other committers merge their pull requests.
# TC Process
The TC uses a "consensus seeking" process for issues that are escalated to the TC.
The group tries to find a resolution that has no open objections among TC members.
If a consensus cannot be reached that has no objections then a majority wins vote
is called. It is also expected that the majority of decisions made by the TC are via
a consensus seeking process and that voting is only used as a last-resort.
Resolution may involve returning the issue to committers with suggestions on how to
move forward towards a consensus. It is not expected that a meeting of the TC
will resolve all issues on its agenda during that meeting and may prefer to continue
the discussion happening among the committers.
Members can be added to the TC at any time. Any committer can nominate another committer
to the TC and the TC uses its standard consensus seeking process to evaluate whether or
not to add this new member. Members who do not participate consistently at the level of
a majority of the other members are expected to resign.
+16
View File
@@ -10130,6 +10130,22 @@
"is-fullwidth-code-point": "^2.0.0"
}
},
"slp-mdm": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/slp-mdm/-/slp-mdm-0.0.6.tgz",
"integrity": "sha512-fbjlIg/o8OtzgK2JydC6POJp3Qup/rLgy4yB5hoLgxWRlERyJyE29ScwS3r9TTwPxe12qK55pyivAdNOZZXL0A==",
"requires": {
"bignumber.js": "^9.0.0"
}
},
"slp-parser": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/slp-parser/-/slp-parser-0.0.4.tgz",
"integrity": "sha512-AvbslJumkzGfMGWNvuE2pWx2nyHEk/VgQ7l119kDKIFRTuRUWOkyOULLauw5laGRQsBRThg6NCx/TsR3grX6GA==",
"requires": {
"bignumber.js": "^9.0.0"
}
},
"socket.io": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz",
+3
View File
@@ -15,6 +15,7 @@
"test:integration:testnet:local": "RESTURL=http://localhost:4000/v3/ mocha --timeout 30000 test/integration/testnet",
"test:integration:api": "RESTURL=https://api.fullstack.cash/v3/ mocha --timeout 30000 test/integration",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs"
},
"license": "MIT",
@@ -61,6 +62,8 @@
"repl.history": "^0.1.4",
"safe-buffer": "^5.1.2",
"satoshi-bitcoin": "^1.0.4",
"slp-mdm": "0.0.6",
"slp-parser": "0.0.4",
"socket.io": "^2.1.1",
"socket.io-client": "^2.1.1",
"touch": "^3.1.0",
+105
View File
@@ -1,4 +1,5 @@
const axios = require("axios")
const slpParser = require("slp-parser")
const Script = require("../script")
const scriptLib = new Script()
@@ -11,6 +12,7 @@ class Utils {
constructor(config) {
this.restURL = config.restURL
this.apiToken = config.apiToken
this.slpParser = slpParser
// Add JWT token to the authorization header.
this.axiosOptions = {
@@ -740,6 +742,9 @@ class Utils {
*
* Throws an error if given a non-SLP txid.
*
* Note: At some point, this method will be deprecated in favor of decodeOpReturn2().
* At that time, decodeOpReturn2() will be rename to decodeOpReturn().
*
* @apiExample Example usage:
*
* (async () => {
@@ -939,6 +944,106 @@ class Utils {
}
}
/**
* @api SLP.Utils.decodeOpReturn2() decodeOpReturn2() - Read the OP_RETURN data from an SLP transaction.
* @apiName decodeOpReturn2
* @apiGroup SLP
* @apiDescription Retrieves transactions data from a txid and decodes the SLP OP_RETURN data.
*
* Similar to decodeOpReturn(), except decodeOpReturn2() uses the slp-parser
* library maintained by JT Freeman. Outputs have slightly different format.
*
* Throws an error if given a non-SLP txid.
*
* In a future version of bch-js, this method will replace the origonal
* decodeOpReturn() method.
*
* @apiExample Example usage:
*
* (async () => {
* try {
* const txid =
* "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1"
*
* const data = await slp.Utils.decodeOpReturn2(txid)
*
* console.log(`Decoded OP_RETURN data: ${JSON.stringify(data,null,2)}`)
* } catch (error) {
* console.error(error)
* }
* })()
*
* // returns
* {
* "tokenType": 1,
* "txType": "SEND",
* "tokenId": "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7"
* "amounts": [
* "100000000",
* "99883300000000"
* ]
* }
*/
// Reimplementation of decodeOpReturn() using slp-parser.
async decodeOpReturn2(txid) {
try {
// Validate the txid input.
if (!txid || txid === "" || typeof txid !== "string")
throw new Error(`txid string must be included.`)
// Retrieve the transaction object from the full node.
const path = `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=true`
const response = await axios.get(path, _this.axiosOptions)
const txDetails = response.data
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
// SLP spec expects OP_RETURN to be the first output of the transaction.
const opReturn = txDetails.vout[0].scriptPubKey.hex
// console.log(`opReturn hex: ${opReturn}`)
const parsedData = _this.slpParser.parseSLP(Buffer.from(opReturn, "hex"))
// console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
// Convert Buffer data to hex strings or utf8 strings.
let tokenData = {}
if (parsedData.transactionType === "SEND") {
tokenData = {
tokenType: parsedData.tokenType,
txType: parsedData.transactionType,
tokenId: parsedData.data.tokenId.toString("hex"),
amounts: parsedData.data.amounts
}
} else if (parsedData.transactionType === "GENESIS") {
tokenData = {
tokenType: parsedData.tokenType,
txType: parsedData.transactionType,
ticker: parsedData.data.ticker.toString(),
name: parsedData.data.name.toString(),
tokenId: txid,
documentUri: parsedData.data.documentUri.toString(),
documentHash: parsedData.data.documentHash.toString(),
decimals: parsedData.data.decimals,
mintBatonVout: parsedData.data.mintBatonVout,
qty: parsedData.data.qty
}
} else if (parsedData.transactionType === "MINT") {
tokenData = {
tokenType: parsedData.tokenType,
txType: parsedData.transactionType,
tokenId: parsedData.data.tokenId.toString("hex"),
mintBatonVout: parsedData.data.mintBatonVout,
qty: parsedData.data.qty
}
}
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
return tokenData
} catch (error) {
if (error.response && error.response.data) throw error.response.data
throw error
}
}
/**
* @api SLP.Utils.isTokenUtxo() isTokenUtxo() - Determine if UTXO belongs to an SLP transaction.
* @apiName isTokenUtxo
+150
View File
@@ -75,6 +75,156 @@ describe(`#SLP`, () => {
"spendData"
])
})
it("should decode the OP_RETURN for a GENESIS txid", async () => {
const txid =
"497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7"
const result = await bchjs.SLP.Utils.decodeOpReturn(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
"tokenType",
"transactionType",
"ticker",
"name",
"documentUrl",
"documentHash",
"decimals",
"mintBatonVout",
"initialQty",
"tokensSentTo",
"batonHolder"
])
})
it("should decode the OP_RETURN for a MINT txid", async () => {
const txid =
"65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4"
const result = await bchjs.SLP.Utils.decodeOpReturn(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
"tokenType",
"transactionType",
"tokenId",
"mintBatonVout",
"batonStillExists",
"quantity",
"tokensSentTo",
"batonHolder"
])
})
it("should throw an error for a non-SLP transaction", async () => {
try {
const txid =
"3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec"
await bchjs.SLP.Utils.decodeOpReturn(txid)
assert.equal(true, false, "Unexpected result")
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, "Not an OP_RETURN")
}
})
})
describe("#decodeOpReturn2", () => {
it("should decode the OP_RETURN for a SEND txid", async () => {
const txid =
"266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1"
const result = await bchjs.SLP.Utils.decodeOpReturn2(txid)
console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, ["tokenType", "txType", "tokenId", "amounts"])
assert.equal(
result.tokenId,
"497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7"
)
// Verify outputs
assert.equal(result.amounts.length, 2)
assert.equal(result.amounts[0], "100000000")
assert.equal(result.amounts[1], "99883300000000")
})
it("should decode the OP_RETURN for a GENESIS txid", async () => {
const txid =
"497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7"
const result = await bchjs.SLP.Utils.decodeOpReturn2(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
"tokenType",
"txType",
"tokenId",
"ticker",
"name",
"documentUri",
"documentHash",
"decimals",
"mintBatonVout",
"qty"
])
assert.equal(
result.tokenId,
"497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7"
)
assert.equal(result.txType, "GENESIS")
assert.equal(result.ticker, "TOK-CH")
assert.equal(result.name, "TokyoCash")
})
it("should decode the OP_RETURN for a MINT txid", async () => {
const txid =
"65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4"
const result = await bchjs.SLP.Utils.decodeOpReturn2(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
"tokenType",
"txType",
"tokenId",
"mintBatonVout",
"qty"
])
})
it("should throw an error for a non-SLP transaction", async () => {
try {
const txid =
"3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec"
await bchjs.SLP.Utils.decodeOpReturn2(txid)
assert.equal(true, false, "Unexpected result")
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, "scriptpubkey not op_return")
}
})
// Note: This TX is interpreted as valid by the original decodeOpReturn().
// Fixing this issue and related issues was the reason for creating the
// decodeOpReturn2() method using the slp-parser library.
it("should throw error for invalid SLP transaction", async () => {
try {
const txid =
"a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415"
await bchjs.SLP.Utils.decodeOpReturn2(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, "amount string size not 8 bytes")
}
})
})
describe("#tokenUtxoDetails", () => {
+89 -1
View File
@@ -944,6 +944,93 @@ const mockDualOpData = {
]
}
const mockInvalidSlpSend = {
txid: "a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415",
hash: "a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415",
version: 2,
size: 473,
locktime: 0,
vin: [
{
txid: "3ad621d46ddb7bdccb6e5e7b6505ee639d9327a2b2bdaa2937b8e3aa55c4f2a7",
vout: 0,
scriptSig: {
asm:
"3045022100f962fdc585261007f114f0470828bbe9ac6197cefc4d0897fe4a7dd1d4cb2f5402202e104b617a1c14fc293c0ace9d4fd5e1cfa819a68b4ff976b4c3c199feda5227[ALL|FORKID] 0351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fb",
hex:
"483045022100f962fdc585261007f114f0470828bbe9ac6197cefc4d0897fe4a7dd1d4cb2f5402202e104b617a1c14fc293c0ace9d4fd5e1cfa819a68b4ff976b4c3c199feda522741210351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fb"
},
sequence: 4294967295
},
{
txid: "a31c3167686f892d835727bc46b74bb11a46d74a8a6b08dc6cd82abb6e987b43",
vout: 1,
scriptSig: {
asm:
"3044022027dcd4ef752819ba4e61b892039ea0cb8d3ab0ffaeb78384dce9cf43dd76967e0220594fdf41e3d26cd207b6df939e3d655f6e09b01f05948edeb096aa644e1b6ec4[ALL|FORKID] 0351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fb",
hex:
"473044022027dcd4ef752819ba4e61b892039ea0cb8d3ab0ffaeb78384dce9cf43dd76967e0220594fdf41e3d26cd207b6df939e3d655f6e09b01f05948edeb096aa644e1b6ec441210351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fb"
},
sequence: 4294967295
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm:
"OP_RETURN 5262419 1 1145980243 091c80cee60cc3a6dd7b8c6c04cf0cf2d8103ba2d75daa105dcf1aaa53551fe8 1 fffffffffffffffe",
hex:
"6a04534c500001010453454e4420091c80cee60cc3a6dd7b8c6c04cf0cf2d8103ba2d75daa105dcf1aaa53551fe8010108fffffffffffffffe",
type: "nulldata"
}
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm:
"OP_DUP OP_HASH160 c0c53d84b5420c9bfe4015d1ae69017c3f1ddef8 OP_EQUALVERIFY OP_CHECKSIG",
hex: "76a914c0c53d84b5420c9bfe4015d1ae69017c3f1ddef888ac",
reqSigs: 1,
type: "pubkeyhash",
addresses: ["bitcoincash:qrqv20vyk4pqexl7gq2artnfq97r78w7lqj5548mny"]
}
},
{
value: 0.00000546,
n: 2,
scriptPubKey: {
asm:
"OP_DUP OP_HASH160 b505afc357a7e911b207332f13e8e674a72099c3 OP_EQUALVERIFY OP_CHECKSIG",
hex: "76a914b505afc357a7e911b207332f13e8e674a72099c388ac",
reqSigs: 1,
type: "pubkeyhash",
addresses: ["bitcoincash:qz6stt7r27n7jydjquej7ylgue62wgyecvs9zm4gff"]
}
},
{
value: 0.00044091,
n: 3,
scriptPubKey: {
asm:
"OP_DUP OP_HASH160 b505afc357a7e911b207332f13e8e674a72099c3 OP_EQUALVERIFY OP_CHECKSIG",
hex: "76a914b505afc357a7e911b207332f13e8e674a72099c388ac",
reqSigs: 1,
type: "pubkeyhash",
addresses: ["bitcoincash:qz6stt7r27n7jydjquej7ylgue62wgyecvs9zm4gff"]
}
}
],
hex:
"0200000002a7f2c455aae3b83729aabdb2a227939d63ee05657b5e6ecbdc7bdb6dd421d63a000000006b483045022100f962fdc585261007f114f0470828bbe9ac6197cefc4d0897fe4a7dd1d4cb2f5402202e104b617a1c14fc293c0ace9d4fd5e1cfa819a68b4ff976b4c3c199feda522741210351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fbffffffff437b986ebb2ad86cdc086b8a4ad7461ab14bb746bc2757832d896f6867311ca3010000006a473044022027dcd4ef752819ba4e61b892039ea0cb8d3ab0ffaeb78384dce9cf43dd76967e0220594fdf41e3d26cd207b6df939e3d655f6e09b01f05948edeb096aa644e1b6ec441210351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fbffffffff040000000000000000396a04534c500001010453454e4420091c80cee60cc3a6dd7b8c6c04cf0cf2d8103ba2d75daa105dcf1aaa53551fe8010108fffffffffffffffe22020000000000001976a914c0c53d84b5420c9bfe4015d1ae69017c3f1ddef888ac22020000000000001976a914b505afc357a7e911b207332f13e8e674a72099c388ac3bac0000000000001976a914b505afc357a7e911b207332f13e8e674a72099c388ac00000000",
blockhash: "000000000000000000915bd33a7241f34800b190f7cf51f90b42b2b05d2b7ed8",
confirmations: 90327,
time: 1535577007,
blocktime: 1535577007
}
module.exports = {
mockList,
mockToken,
@@ -966,5 +1053,6 @@ module.exports = {
txDetailsSLPSendAlt,
mockTxDetails,
mockDualValidation,
mockDualOpData
mockDualOpData,
mockInvalidSlpSend
}
+2 -1
View File
@@ -99,7 +99,8 @@ describe("#SLP TokenType1", () => {
5000
)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// console.log(`result.script: `, result.script)
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).
+191
View File
@@ -525,6 +525,187 @@ describe("#SLP Utils", () => {
})
})
describe("#decodeOpReturn2", () => {
it("should throw an error for a non-string input", async () => {
try {
const txid = 53423 // Not a string.
await slp.Utils.decodeOpReturn2(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.decodeOpReturn2(txid)
assert2.equal(true, false, "Unexpected result.")
} catch (err) {
// console.log(`err: ${util.inspect(err)}`)
assert2.include(err.message, `scriptpubkey not op_return`)
}
})
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.decodeOpReturn2(txid)
assert2.equal(true, false, "Unexpected result.")
} catch (err) {
// console.log(`err: ${util.inspect(err)}`)
assert2.include(err.message, `SLP not in first chunk`)
}
})
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 result = await slp.Utils.decodeOpReturn2(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert2.hasAllKeys(result, [
"tokenType",
"txType",
"tokenId",
"ticker",
"name",
"documentUri",
"documentHash",
"decimals",
"mintBatonVout",
"qty"
])
})
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 result = await slp.Utils.decodeOpReturn2(txid)
//console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert2.hasAllKeys(result, [
"tokenType",
"txType",
"tokenId",
"mintBatonVout",
"qty"
])
})
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 result = await slp.Utils.decodeOpReturn2(txid)
//console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert2.hasAllKeys(result, ["tokenType", "txType", "tokenId", "amounts"])
})
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.decodeOpReturn2(txid)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
assert2.equal(data.mintBatonVout, 0)
})
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.decodeOpReturn2(txid)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
assert2.hasAnyKeys(data, [
"transactionType",
"txType",
"tokenId",
"amounts"
])
})
// Note: This TX is interpreted as valid by the original decodeOpReturn().
// Fixing this issue and related issues was the reason for creating the
// decodeOpReturn2() method using the slp-parser library.
it("should throw error for invalid SLP transaction", async () => {
try {
// Mock the call to rest.bitcoin.com
if (process.env.TEST === "unit") {
sandbox
.stub(axios, "get")
.resolves({ data: mockData.mockInvalidSlpSend })
}
const txid =
"a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415"
await slp.Utils.decodeOpReturn2(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
} catch (err) {
// console.log(`err: `, err)
assert2.include(err.message, "amount string size not 8 bytes")
}
})
})
describe("#isTokenUtxo", () => {
it("should throw error if input is not an array.", async () => {
try {
@@ -1277,4 +1458,14 @@ describe("#SLP Utils", () => {
])
})
})
// describe("#decodeOpReturn2", () => {
// it("should do something", async () => {
// const txid =
// "4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa"
//
// await slp.Utils.decodeOpReturn2(txid)
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
// })
// })
})