mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-22 09:02:01 -07:00
Merge pull request #23 from christroutner/slp-mdm
decodeOpReturn2() - alternate implementation
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ wallet-info.txt
|
||||
wallet.json
|
||||
|
||||
docs/
|
||||
|
||||
coverage/
|
||||
|
||||
# This paragraph comes last. Force includes specific files.
|
||||
!docs/README.md
|
||||
|
||||
@@ -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.
|
||||
Generated
+16
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)}`)
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user