mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-22 09:02:01 -07:00
+156
-112
@@ -23,6 +23,80 @@ class TokenType1 {
|
||||
TransactionBuilder.setAddress(addy)
|
||||
}
|
||||
|
||||
// generateSendOpReturn(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 generateSendOpReturn()`)
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* @api SLP.TokenType1.generateSendOpReturn() generateSendOpReturn() - OP_RETURN code for SLP Send tx
|
||||
* @apiName generateSendOpReturn
|
||||
@@ -32,6 +106,40 @@ class TokenType1 {
|
||||
* Returns an object with two properties:
|
||||
* - script: an array of Bufers that is ready to fed into bchjs.Script.encode() 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.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
* (async () => {
|
||||
* try {
|
||||
* const addr = "bitcoincash:qq6xz6wwcy78uh79vgjvfyahj4arq269w5an8pcjak"
|
||||
* const utxos = await bchjs.Blockbook.utxos(addr)
|
||||
*
|
||||
* // Identify the SLP token UTXOs.
|
||||
* let tokenUtxos = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos);
|
||||
*
|
||||
* // Filter out the token UTXOs that match the user-provided token ID.
|
||||
* tokenUtxos = tokenUtxos.filter((utxo, index) => {
|
||||
* if (
|
||||
* utxo && // UTXO is associated with a token.
|
||||
* utxo.tokenId === TOKENID && // UTXO matches the token ID.
|
||||
* utxo.tokenType === "token" // UTXO is not a minting baton.
|
||||
* )
|
||||
* return true;
|
||||
* });
|
||||
*
|
||||
* // Generate the SEND OP_RETURN
|
||||
* const slpSendObj = bchjs.SLP.TokenType1.generateSendOpReturn(
|
||||
* tokenUtxos,
|
||||
* TOKENQTY
|
||||
* );
|
||||
* const slpData = slpSendObj.script;
|
||||
*
|
||||
* ...
|
||||
* // 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/send-token/send-token.js
|
||||
*/
|
||||
generateSendOpReturn(tokenUtxos, sendQty) {
|
||||
try {
|
||||
@@ -49,81 +157,6 @@ class TokenType1 {
|
||||
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 generateSendOpReturn()`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// New version using slp-mdm
|
||||
generateSendOpReturn2(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
|
||||
@@ -133,14 +166,14 @@ class TokenType1 {
|
||||
baseQty = baseQty.absoluteValue()
|
||||
baseQty = Math.floor(baseQty)
|
||||
baseQty = baseQty.toString()
|
||||
console.log(`baseQty: `, baseQty)
|
||||
// console.log(`baseQty: `, baseQty)
|
||||
|
||||
// Convert the change quantity to the format expected by slp-mdm.
|
||||
let baseChange = new BigNumber(change).times(10 ** decimals)
|
||||
baseChange = baseChange.absoluteValue()
|
||||
baseChange = Math.floor(baseChange)
|
||||
baseChange = baseChange.toString()
|
||||
console.log(`baseChange: `, baseChange)
|
||||
// console.log(`baseChange: `, baseChange)
|
||||
|
||||
// Generate the OP_RETURN as a Buffer.
|
||||
script = slpMdm.TokenType1.send(tokenId, [
|
||||
@@ -155,7 +188,7 @@ class TokenType1 {
|
||||
baseQty = baseQty.absoluteValue()
|
||||
baseQty = Math.floor(baseQty)
|
||||
baseQty = baseQty.toString()
|
||||
console.log(`baseQty: `, baseQty)
|
||||
// console.log(`baseQty: `, baseQty)
|
||||
|
||||
// console.log(`baseQty: ${baseQty.toString()}`)
|
||||
|
||||
@@ -165,7 +198,7 @@ class TokenType1 {
|
||||
|
||||
return { script, outputs }
|
||||
} catch (err) {
|
||||
console.log(`Error in generateSendOpReturn2()`)
|
||||
console.log(`Error in generateSendOpReturn()`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -198,24 +231,26 @@ class TokenType1 {
|
||||
let baseQty = new BigNumber(remainder).times(10 ** decimals)
|
||||
baseQty = baseQty.absoluteValue()
|
||||
baseQty = Math.floor(baseQty)
|
||||
let baseQtyHex = baseQty.toString(16)
|
||||
baseQtyHex = baseQtyHex.padStart(16, "0")
|
||||
baseQty = baseQty.toString()
|
||||
|
||||
// console.log(`baseQty: ${baseQty.toString()}`)
|
||||
|
||||
const 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")
|
||||
]
|
||||
// const 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")
|
||||
// ]
|
||||
|
||||
// Generate the OP_RETURN as a Buffer.
|
||||
const script = slpMdm.TokenType1.send(tokenId, [new slpMdm.BN(baseQty)])
|
||||
|
||||
return script
|
||||
} catch (err) {
|
||||
console.log(`Error in generateSendOpReturn()`)
|
||||
console.log(`Error in generateBurnOpReturn()`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -235,6 +270,7 @@ class TokenType1 {
|
||||
* ticker: (string) ticker symbol for the new token class.
|
||||
* name: (string) name of the token.
|
||||
* documentUrl: (string) a website url that you'd like to attach to the token.
|
||||
* documentHash: (string) optional.
|
||||
* }
|
||||
*
|
||||
* Note: document hash is currently not supported.
|
||||
@@ -243,34 +279,42 @@ class TokenType1 {
|
||||
try {
|
||||
// TODO: Add input validation.
|
||||
|
||||
let decimals = configObj.decimals.toString(16)
|
||||
decimals = decimals.padStart(2, "0")
|
||||
|
||||
let baseQty = new BigNumber(configObj.initialQty).times(
|
||||
10 ** configObj.decimals
|
||||
)
|
||||
baseQty = baseQty.absoluteValue()
|
||||
baseQty = Math.floor(baseQty)
|
||||
let baseQtyHex = baseQty.toString(16)
|
||||
baseQtyHex = baseQtyHex.padStart(16, "0")
|
||||
baseQty = baseQty.toString()
|
||||
// let baseQtyHex = baseQty.toString(16)
|
||||
// baseQtyHex = baseQtyHex.padStart(16, "0")
|
||||
|
||||
const script = [
|
||||
this.Script.opcodes.OP_RETURN,
|
||||
Buffer.from("534c5000", "hex"), // Lokad ID
|
||||
Buffer.from("01", "hex"), // Token Type 1
|
||||
Buffer.from(`GENESIS`),
|
||||
Buffer.from(configObj.ticker),
|
||||
Buffer.from(configObj.name),
|
||||
Buffer.from(configObj.documentUrl),
|
||||
const script = slpMdm.TokenType1.genesis(
|
||||
configObj.ticker,
|
||||
configObj.name,
|
||||
configObj.documentUrl,
|
||||
configObj.documentHash,
|
||||
configObj.decimals,
|
||||
null,
|
||||
new slpMdm.BN(baseQty)
|
||||
)
|
||||
|
||||
// Create an empty document hash.
|
||||
this.Script.opcodes.OP_PUSHDATA1, // Hex 4c
|
||||
this.Script.opcodes.OP_0, // Hex 00
|
||||
|
||||
Buffer.from(decimals, "hex"),
|
||||
Buffer.from("02", "hex"), // Mint baton vout
|
||||
Buffer.from(baseQtyHex, "hex")
|
||||
]
|
||||
// const script = [
|
||||
// this.Script.opcodes.OP_RETURN,
|
||||
// Buffer.from("534c5000", "hex"), // Lokad ID
|
||||
// Buffer.from("01", "hex"), // Token Type 1
|
||||
// Buffer.from(`GENESIS`),
|
||||
// Buffer.from(configObj.ticker),
|
||||
// Buffer.from(configObj.name),
|
||||
// Buffer.from(configObj.documentUrl),
|
||||
//
|
||||
// // Create an empty document hash.
|
||||
// this.Script.opcodes.OP_PUSHDATA1, // Hex 4c
|
||||
// this.Script.opcodes.OP_0, // Hex 00
|
||||
//
|
||||
// Buffer.from(decimals, "hex"),
|
||||
// Buffer.from("02", "hex"), // Mint baton vout
|
||||
// Buffer.from(baseQtyHex, "hex")
|
||||
// ]
|
||||
|
||||
return script
|
||||
} catch (err) {
|
||||
|
||||
+221
-387
@@ -731,225 +731,225 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @api SLP.Utils.decodeOpReturn() decodeOpReturn() - Read the OP_RETURN data from an SLP transaction.
|
||||
// * @apiName decodeOpReturn
|
||||
// * @apiGroup SLP
|
||||
// * @apiDescription Retrieves transactions data from a txid and decodes the SLP OP_RETURN data.
|
||||
// *
|
||||
// * Returns an object with properties corresponding to the SLP spec:
|
||||
// * https://github.com/simpleledger/slp-specifications/blob/master/slp-token-type-1.md
|
||||
// *
|
||||
// * 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 () => {
|
||||
// * try {
|
||||
// * const txid =
|
||||
// * "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90"
|
||||
// *
|
||||
// * const data = await slp.Utils.decodeOpReturn(txid)
|
||||
// *
|
||||
// * console.log(`Decoded OP_RETURN data: ${JSON.stringify(data,null,2)}`)
|
||||
// * } catch (error) {
|
||||
// * console.error(error)
|
||||
// * }
|
||||
// * })()
|
||||
// *
|
||||
// * // returns
|
||||
// * {
|
||||
// * "tokenType": 1,
|
||||
// * "transactionType": "genesis",
|
||||
// * "ticker": "SLPSDK",
|
||||
// * "name": "SLP SDK example using BITBOX",
|
||||
// * "documentUrl": "developer.bitcoin.com",
|
||||
// * "documentHash": "",
|
||||
// * "decimals": 8,
|
||||
// * "mintBatonVout": 2,
|
||||
// * "initialQty": 507,
|
||||
// * "tokensSentTo": "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05",
|
||||
// * "batonHolder": "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05"
|
||||
// * }
|
||||
// */
|
||||
// async decodeOpReturn(txid) {
|
||||
// try {
|
||||
// if (!txid || txid === "" || typeof txid !== "string")
|
||||
// throw new Error(`txid string must be included.`)
|
||||
//
|
||||
// const path = `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=true`
|
||||
// const lokadIdHex = "534c5000"
|
||||
//
|
||||
// // Create an empty output object that will be populated with metadata.
|
||||
// const outObj = {}
|
||||
//
|
||||
// // Retrieve the transaction object from the full node.
|
||||
// const response = await axios.get(path, _this.axiosOptions)
|
||||
// const txDetails = response.data
|
||||
// //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
//
|
||||
// // Retrieve the OP_RETURN data.
|
||||
// const script = scriptLib
|
||||
// .toASM(Buffer.from(txDetails.vout[0].scriptPubKey.hex, "hex"))
|
||||
// .split(" ")
|
||||
//
|
||||
// if (script[0] !== "OP_RETURN") throw new Error("Not an OP_RETURN")
|
||||
//
|
||||
// if (script[1] !== lokadIdHex) throw new Error("Not a SLP OP_RETURN")
|
||||
//
|
||||
// // Validate token type.
|
||||
// if (script[2] !== "OP_1" && script[2] !== "01" && script[2] !== "0001") {
|
||||
// // NOTE: bitcoincashlib-js converts hex 01 to OP_1 due to BIP62.3 enforcement
|
||||
// throw new Error("Unknown token type")
|
||||
// }
|
||||
// outObj.tokenType = 1
|
||||
//
|
||||
// const type = Buffer.from(script[3], "hex")
|
||||
// .toString("ascii")
|
||||
// .toLowerCase()
|
||||
// script[3] = type
|
||||
// //console.log(`type: ${type}`)
|
||||
//
|
||||
// // Decode a GENSIS SLP transaction.
|
||||
// if (type === "genesis") {
|
||||
// //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
// //console.log(`script: ${JSON.stringify(script, null, 2)}`)
|
||||
//
|
||||
// outObj.transactionType = "genesis"
|
||||
//
|
||||
// // Convert the next four entries into ascii.
|
||||
// for (let i = 4; i < 8; i++)
|
||||
// script[i] = Buffer.from(script[i], "hex").toString("ascii")
|
||||
// //.toLowerCase()
|
||||
//
|
||||
// outObj.ticker = script[4]
|
||||
// outObj.name = script[5]
|
||||
// outObj.documentUrl = script[6]
|
||||
// outObj.documentHash = script[7]
|
||||
//
|
||||
// // decimal precision of the token.
|
||||
// if (typeof script[8] === "string" && script[8].startsWith("OP_"))
|
||||
// script[8] = parseInt(script[8].slice(3)).toString(16)
|
||||
//
|
||||
// const decimals = Number(script[8])
|
||||
// outObj.decimals = decimals
|
||||
//
|
||||
// // Mint Baton vout.
|
||||
// if (typeof script[9] === "string" && script[9].startsWith("OP_"))
|
||||
// script[9] = parseInt(script[9].slice(3)).toString(16)
|
||||
// outObj.mintBatonVout = Number(script[9])
|
||||
//
|
||||
// // Initial quantity of tokens created.
|
||||
// let qty = new BigNumber(script[10], 16)
|
||||
// qty = qty / Math.pow(10, decimals)
|
||||
// script[10] = qty
|
||||
// outObj.initialQty = qty
|
||||
//
|
||||
// // Address initial tokens were sent to.
|
||||
// outObj.tokensSentTo = txDetails.vout[1].scriptPubKey.addresses[0]
|
||||
//
|
||||
// // Mint baton address holder.
|
||||
// if (!outObj.mintBatonVout) {
|
||||
// outObj.batonHolder = "NEVER_CREATED"
|
||||
// } else {
|
||||
// outObj.batonHolder =
|
||||
// txDetails.vout[outObj.mintBatonVout].scriptPubKey.addresses[0]
|
||||
// }
|
||||
//
|
||||
// // Mint type transaction
|
||||
// } else if (type === "mint") {
|
||||
// //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
//
|
||||
// outObj.transactionType = "mint"
|
||||
// outObj.tokenId = script[4]
|
||||
//
|
||||
// // Locate the vout UTXO containing the minting baton.
|
||||
// let mintBatonVout = 0
|
||||
// // Dev note: Haven't seen this if-statement in the wild. Copied from
|
||||
// // someone elses code.
|
||||
// if (typeof script[5] === "string" && script[5].startsWith("OP_"))
|
||||
// mintBatonVout = parseInt(script[5].slice(3))
|
||||
// // This is the common use case I see.
|
||||
// else mintBatonVout = parseInt(script[5])
|
||||
//
|
||||
// outObj.mintBatonVout = mintBatonVout
|
||||
//
|
||||
// // Check if baton was passed or destroyed.
|
||||
// // Dev Note: There should be some more extensive checking here. The most
|
||||
// // common way of 'burning' the minting baton is to set script[5] to a
|
||||
// // value of 0, but it could also point to a non-existant vout.
|
||||
// // TODO: Add checking if script[5] refers to a non-existant vout.
|
||||
// outObj.batonStillExists = false // false by default.
|
||||
// if (mintBatonVout > 1) outObj.batonStillExists = true
|
||||
//
|
||||
// // Parse the quantity generated in this minting operation.
|
||||
// // Returns a string. But without the decimals information included,
|
||||
// // I'm not sure how to properly represent the quantity.
|
||||
// if (typeof script[6] === "string" && script[6].startsWith("OP_"))
|
||||
// script[6] = parseInt(script[6].slice(3)).toString(16)
|
||||
//
|
||||
// outObj.quantity = new BigNumber(script[6], 16)
|
||||
//
|
||||
// // Report the reciever of the minted tokens.
|
||||
// outObj.tokensSentTo = txDetails.vout[1].scriptPubKey.addresses[0]
|
||||
//
|
||||
// // Report the address that controls the mint baton.
|
||||
// if (outObj.batonStillExists) {
|
||||
// outObj.batonHolder =
|
||||
// txDetails.vout[mintBatonVout].scriptPubKey.addresses[0]
|
||||
// }
|
||||
//
|
||||
// // Send tokens.
|
||||
// } else if (type === "send") {
|
||||
// //console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
// //console.log(`script: ${JSON.stringify(script,null,2)}`)
|
||||
//
|
||||
// if (script.length <= 4) throw new Error("Not a SLP txout")
|
||||
//
|
||||
// outObj.transactionType = "send"
|
||||
//
|
||||
// // Retrieve the token ID.
|
||||
// outObj.tokenId = script[4]
|
||||
//
|
||||
// // Loop through each output.
|
||||
// const spendData = []
|
||||
// for (let i = 5; i < script.length; i++) {
|
||||
// let thisScript = script[i]
|
||||
// const spendObj = {}
|
||||
//
|
||||
// if (typeof thisScript === "string" && thisScript.startsWith("OP_"))
|
||||
// thisScript = parseInt(thisScript.slice(3)).toString(16)
|
||||
//
|
||||
// // Compute the quantity of tokens.
|
||||
// spendObj.quantity = new BigNumber(thisScript, 16)
|
||||
//
|
||||
// // Calculate which vouts are SLP UTXOs.
|
||||
// const thisVout = i - 4
|
||||
// spendObj.sentTo = txDetails.vout[thisVout].scriptPubKey.addresses[0]
|
||||
// spendObj.vout = thisVout
|
||||
//
|
||||
// spendData.push(spendObj)
|
||||
// }
|
||||
//
|
||||
// outObj.spendData = spendData
|
||||
// }
|
||||
//
|
||||
// return outObj
|
||||
// } catch (error) {
|
||||
// if (error.response && error.response.data) throw error.response.data
|
||||
// throw error
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.decodeOpReturn() decodeOpReturn() - Read the OP_RETURN data from an SLP transaction.
|
||||
* @apiName decodeOpReturn
|
||||
* @apiGroup SLP
|
||||
* @apiDescription Retrieves transactions data from a txid and decodes the SLP OP_RETURN data.
|
||||
*
|
||||
* Returns an object with properties corresponding to the SLP spec:
|
||||
* https://github.com/simpleledger/slp-specifications/blob/master/slp-token-type-1.md
|
||||
*
|
||||
* 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 () => {
|
||||
* try {
|
||||
* const txid =
|
||||
* "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90"
|
||||
*
|
||||
* const data = await slp.Utils.decodeOpReturn(txid)
|
||||
*
|
||||
* console.log(`Decoded OP_RETURN data: ${JSON.stringify(data,null,2)}`)
|
||||
* } catch (error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // returns
|
||||
* {
|
||||
* "tokenType": 1,
|
||||
* "transactionType": "genesis",
|
||||
* "ticker": "SLPSDK",
|
||||
* "name": "SLP SDK example using BITBOX",
|
||||
* "documentUrl": "developer.bitcoin.com",
|
||||
* "documentHash": "",
|
||||
* "decimals": 8,
|
||||
* "mintBatonVout": 2,
|
||||
* "initialQty": 507,
|
||||
* "tokensSentTo": "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05",
|
||||
* "batonHolder": "bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05"
|
||||
* }
|
||||
*/
|
||||
async decodeOpReturn(txid) {
|
||||
try {
|
||||
if (!txid || txid === "" || typeof txid !== "string")
|
||||
throw new Error(`txid string must be included.`)
|
||||
|
||||
const path = `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=true`
|
||||
const lokadIdHex = "534c5000"
|
||||
|
||||
// Create an empty output object that will be populated with metadata.
|
||||
const outObj = {}
|
||||
|
||||
// Retrieve the transaction object from the full node.
|
||||
const response = await axios.get(path, _this.axiosOptions)
|
||||
const txDetails = response.data
|
||||
//console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
|
||||
// Retrieve the OP_RETURN data.
|
||||
const script = scriptLib
|
||||
.toASM(Buffer.from(txDetails.vout[0].scriptPubKey.hex, "hex"))
|
||||
.split(" ")
|
||||
|
||||
if (script[0] !== "OP_RETURN") throw new Error("Not an OP_RETURN")
|
||||
|
||||
if (script[1] !== lokadIdHex) throw new Error("Not a SLP OP_RETURN")
|
||||
|
||||
// Validate token type.
|
||||
if (script[2] !== "OP_1" && script[2] !== "01" && script[2] !== "0001") {
|
||||
// NOTE: bitcoincashlib-js converts hex 01 to OP_1 due to BIP62.3 enforcement
|
||||
throw new Error("Unknown token type")
|
||||
}
|
||||
outObj.tokenType = 1
|
||||
|
||||
const type = Buffer.from(script[3], "hex")
|
||||
.toString("ascii")
|
||||
.toLowerCase()
|
||||
script[3] = type
|
||||
//console.log(`type: ${type}`)
|
||||
|
||||
// Decode a GENSIS SLP transaction.
|
||||
if (type === "genesis") {
|
||||
//console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
//console.log(`script: ${JSON.stringify(script, null, 2)}`)
|
||||
|
||||
outObj.transactionType = "genesis"
|
||||
|
||||
// Convert the next four entries into ascii.
|
||||
for (let i = 4; i < 8; i++)
|
||||
script[i] = Buffer.from(script[i], "hex").toString("ascii")
|
||||
//.toLowerCase()
|
||||
|
||||
outObj.ticker = script[4]
|
||||
outObj.name = script[5]
|
||||
outObj.documentUrl = script[6]
|
||||
outObj.documentHash = script[7]
|
||||
|
||||
// decimal precision of the token.
|
||||
if (typeof script[8] === "string" && script[8].startsWith("OP_"))
|
||||
script[8] = parseInt(script[8].slice(3)).toString(16)
|
||||
|
||||
const decimals = Number(script[8])
|
||||
outObj.decimals = decimals
|
||||
|
||||
// Mint Baton vout.
|
||||
if (typeof script[9] === "string" && script[9].startsWith("OP_"))
|
||||
script[9] = parseInt(script[9].slice(3)).toString(16)
|
||||
outObj.mintBatonVout = Number(script[9])
|
||||
|
||||
// Initial quantity of tokens created.
|
||||
let qty = new BigNumber(script[10], 16)
|
||||
qty = qty / Math.pow(10, decimals)
|
||||
script[10] = qty
|
||||
outObj.initialQty = qty
|
||||
|
||||
// Address initial tokens were sent to.
|
||||
outObj.tokensSentTo = txDetails.vout[1].scriptPubKey.addresses[0]
|
||||
|
||||
// Mint baton address holder.
|
||||
if (!outObj.mintBatonVout) {
|
||||
outObj.batonHolder = "NEVER_CREATED"
|
||||
} else {
|
||||
outObj.batonHolder =
|
||||
txDetails.vout[outObj.mintBatonVout].scriptPubKey.addresses[0]
|
||||
}
|
||||
|
||||
// Mint type transaction
|
||||
} else if (type === "mint") {
|
||||
//console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
|
||||
outObj.transactionType = "mint"
|
||||
outObj.tokenId = script[4]
|
||||
|
||||
// Locate the vout UTXO containing the minting baton.
|
||||
let mintBatonVout = 0
|
||||
// Dev note: Haven't seen this if-statement in the wild. Copied from
|
||||
// someone elses code.
|
||||
if (typeof script[5] === "string" && script[5].startsWith("OP_"))
|
||||
mintBatonVout = parseInt(script[5].slice(3))
|
||||
// This is the common use case I see.
|
||||
else mintBatonVout = parseInt(script[5])
|
||||
|
||||
outObj.mintBatonVout = mintBatonVout
|
||||
|
||||
// Check if baton was passed or destroyed.
|
||||
// Dev Note: There should be some more extensive checking here. The most
|
||||
// common way of 'burning' the minting baton is to set script[5] to a
|
||||
// value of 0, but it could also point to a non-existant vout.
|
||||
// TODO: Add checking if script[5] refers to a non-existant vout.
|
||||
outObj.batonStillExists = false // false by default.
|
||||
if (mintBatonVout > 1) outObj.batonStillExists = true
|
||||
|
||||
// Parse the quantity generated in this minting operation.
|
||||
// Returns a string. But without the decimals information included,
|
||||
// I'm not sure how to properly represent the quantity.
|
||||
if (typeof script[6] === "string" && script[6].startsWith("OP_"))
|
||||
script[6] = parseInt(script[6].slice(3)).toString(16)
|
||||
|
||||
outObj.quantity = new BigNumber(script[6], 16)
|
||||
|
||||
// Report the reciever of the minted tokens.
|
||||
outObj.tokensSentTo = txDetails.vout[1].scriptPubKey.addresses[0]
|
||||
|
||||
// Report the address that controls the mint baton.
|
||||
if (outObj.batonStillExists) {
|
||||
outObj.batonHolder =
|
||||
txDetails.vout[mintBatonVout].scriptPubKey.addresses[0]
|
||||
}
|
||||
|
||||
// Send tokens.
|
||||
} else if (type === "send") {
|
||||
//console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
//console.log(`script: ${JSON.stringify(script,null,2)}`)
|
||||
|
||||
if (script.length <= 4) throw new Error("Not a SLP txout")
|
||||
|
||||
outObj.transactionType = "send"
|
||||
|
||||
// Retrieve the token ID.
|
||||
outObj.tokenId = script[4]
|
||||
|
||||
// Loop through each output.
|
||||
const spendData = []
|
||||
for (let i = 5; i < script.length; i++) {
|
||||
let thisScript = script[i]
|
||||
const spendObj = {}
|
||||
|
||||
if (typeof thisScript === "string" && thisScript.startsWith("OP_"))
|
||||
thisScript = parseInt(thisScript.slice(3)).toString(16)
|
||||
|
||||
// Compute the quantity of tokens.
|
||||
spendObj.quantity = new BigNumber(thisScript, 16)
|
||||
|
||||
// Calculate which vouts are SLP UTXOs.
|
||||
const thisVout = i - 4
|
||||
spendObj.sentTo = txDetails.vout[thisVout].scriptPubKey.addresses[0]
|
||||
spendObj.vout = thisVout
|
||||
|
||||
spendData.push(spendObj)
|
||||
}
|
||||
|
||||
outObj.spendData = spendData
|
||||
}
|
||||
|
||||
return outObj
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
@@ -985,7 +985,7 @@ class Utils {
|
||||
* }
|
||||
*/
|
||||
// Reimplementation of decodeOpReturn() using slp-parser.
|
||||
async decodeOpReturn2(txid) {
|
||||
async decodeOpReturn(txid) {
|
||||
try {
|
||||
// Validate the txid input.
|
||||
if (!txid || txid === "" || typeof txid !== "string")
|
||||
@@ -1168,7 +1168,7 @@ class Utils {
|
||||
// Decode the OP_RETURN.
|
||||
let slpData = {}
|
||||
try {
|
||||
slpData = await _this.decodeOpReturn2(utxo.txid)
|
||||
slpData = await _this.decodeOpReturn(utxo.txid)
|
||||
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||
} catch (err) {
|
||||
// console.log(`decodeOpReturn2 error: `, err)
|
||||
@@ -1244,8 +1244,7 @@ class Utils {
|
||||
*
|
||||
* (async () => {
|
||||
* try {
|
||||
* const u = await bchjs.Blockbook.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
|
||||
* const utxos = u.utxos
|
||||
* const utxos = await bchjs.Blockbook.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
|
||||
*
|
||||
* const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
*
|
||||
@@ -1277,174 +1276,9 @@ class Utils {
|
||||
|
||||
// CT 1/11/20: Refactored to comply with this GitHub Issue:
|
||||
// https://github.com/Bitcoin-com/slp-sdk/issues/84
|
||||
|
||||
// CT 5/31/20: Refactored to use slp-parse library.
|
||||
async tokenUtxoDetails(utxos) {
|
||||
try {
|
||||
// Throw error if input is not an array.
|
||||
if (!Array.isArray(utxos)) throw new Error(`Input must be an array.`)
|
||||
|
||||
// Loop through each element in the array and validate the input before
|
||||
// further processing.
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const utxo = utxos[i]
|
||||
|
||||
// Convert Blockbook UTXOs to Insight format.
|
||||
if (utxo.value) utxo.satoshis = Number(utxo.value)
|
||||
|
||||
// Throw error if utxo does not have a satoshis property.
|
||||
if (!utxo.satoshis)
|
||||
throw new Error(`utxo ${i} does not have a satoshis property.`)
|
||||
|
||||
// Throw error if utxo does not have a txid property.
|
||||
if (!utxo.txid)
|
||||
throw new Error(`utxo ${i} does not have a txid property.`)
|
||||
}
|
||||
|
||||
// Output Array
|
||||
const outAry = []
|
||||
|
||||
// Loop through each utxo
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const utxo = utxos[i]
|
||||
|
||||
// Get raw transaction data from the full node and attempt to decode
|
||||
// the OP_RETURN data.
|
||||
// If there is no OP_RETURN, mark the UTXO as false.
|
||||
let slpData = false
|
||||
try {
|
||||
slpData = await this.decodeOpReturn(utxo.txid)
|
||||
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||
} catch (err) {
|
||||
// console.log(`decodeOpReturn err: `, err)
|
||||
|
||||
if (
|
||||
err.message === "Not an OP_RETURN" ||
|
||||
err.message === "Not a SLP OP_RETURN"
|
||||
) {
|
||||
// An error will be thrown if the txid is not SLP.
|
||||
// Mark as false and continue the loop.
|
||||
outAry.push(false)
|
||||
|
||||
continue
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// If there is an OP_RETURN, attempt to decode it.
|
||||
// Handle Genesis SLP transactions.
|
||||
if (slpData.transactionType === "genesis") {
|
||||
if (
|
||||
utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output.
|
||||
utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
|
||||
) {
|
||||
// Can safely be marked as false.
|
||||
outAry[i] = false
|
||||
}
|
||||
|
||||
// If this is a valid SLP UTXO, then return the decoded OP_RETURN data.
|
||||
else {
|
||||
utxo.tokenType = "minting-baton"
|
||||
utxo.tokenId = utxo.txid
|
||||
utxo.tokenTicker = slpData.ticker
|
||||
utxo.tokenName = slpData.name
|
||||
utxo.tokenDocumentUrl = slpData.documentUrl
|
||||
utxo.tokenDocumentHash = slpData.documentHash
|
||||
utxo.decimals = slpData.decimals
|
||||
|
||||
// something
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Mint SLP transactions.
|
||||
if (slpData.transactionType === "mint") {
|
||||
if (
|
||||
utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output.
|
||||
utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
|
||||
) {
|
||||
// Can safely be marked as false.
|
||||
outAry[i] = false
|
||||
}
|
||||
|
||||
// If UTXO passes validation, then return formatted token data.
|
||||
else {
|
||||
const genesisData = await this.decodeOpReturn(slpData.tokenId)
|
||||
|
||||
// Hydrate the UTXO object with information about the SLP token.
|
||||
utxo.utxoType = "token"
|
||||
utxo.transactionType = "mint"
|
||||
utxo.tokenId = slpData.tokenId
|
||||
|
||||
utxo.tokenTicker = genesisData.ticker
|
||||
utxo.tokenName = genesisData.name
|
||||
utxo.tokenDocumentUrl = genesisData.documentUrl
|
||||
utxo.tokenDocumentHash = genesisData.documentHash
|
||||
utxo.decimals = genesisData.decimals
|
||||
|
||||
utxo.mintBatonVout = slpData.mintBatonVout
|
||||
utxo.batonStillExists = slpData.batonStillExists
|
||||
|
||||
// Calculate the real token quantity.
|
||||
utxo.tokenQty = slpData.quantity / Math.pow(10, utxo.decimals)
|
||||
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Send SLP transactions.
|
||||
if (slpData.transactionType === "send") {
|
||||
// Filter out any vouts that match.
|
||||
const voutMatch = slpData.spendData.filter(x => utxo.vout === x.vout)
|
||||
// console.log(`voutMatch: ${JSON.stringify(voutMatch, null, 2)}`)
|
||||
|
||||
// If there are no vout matches, the UTXO can safely be marked as false.
|
||||
if (voutMatch.length === 0) {
|
||||
outAry[i] = false
|
||||
}
|
||||
|
||||
// If UTXO passes validation, then return formatted token data.
|
||||
else {
|
||||
const genesisData = await this.decodeOpReturn(slpData.tokenId)
|
||||
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
||||
|
||||
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
||||
|
||||
// Hydrate the UTXO object with information about the SLP token.
|
||||
utxo.utxoType = "token"
|
||||
utxo.transactionType = "send"
|
||||
utxo.tokenId = slpData.tokenId
|
||||
utxo.tokenTicker = genesisData.ticker
|
||||
utxo.tokenName = genesisData.name
|
||||
utxo.tokenDocumentUrl = genesisData.documentUrl
|
||||
utxo.tokenDocumentHash = genesisData.documentHash
|
||||
utxo.decimals = genesisData.decimals
|
||||
|
||||
// Calculate the real token quantity.
|
||||
utxo.tokenQty = voutMatch[0].quantity / Math.pow(10, utxo.decimals)
|
||||
|
||||
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
||||
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, validate the SLP txid with SLPDB.
|
||||
if (outAry[i]) {
|
||||
const isValid = await this.validateTxid(utxo.txid)
|
||||
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
|
||||
|
||||
outAry[i].isValid = isValid[0].valid
|
||||
}
|
||||
}
|
||||
|
||||
return outAry
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async tokenUtxoDetails2(utxos) {
|
||||
try {
|
||||
// Throw error if input is not an array.
|
||||
if (!Array.isArray(utxos)) throw new Error(`Input must be an array.`)
|
||||
@@ -1503,7 +1337,7 @@ class Utils {
|
||||
// If there is no OP_RETURN, mark the UTXO as false.
|
||||
let slpData = false
|
||||
try {
|
||||
slpData = await this.decodeOpReturn2(utxo.txid)
|
||||
slpData = await this.decodeOpReturn(utxo.txid)
|
||||
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||
} catch (err) {
|
||||
// console.log(`error: `, err)
|
||||
@@ -1564,7 +1398,7 @@ class Utils {
|
||||
|
||||
// If UTXO passes validation, then return formatted token data.
|
||||
else {
|
||||
const genesisData = await this.decodeOpReturn2(slpData.tokenId)
|
||||
const genesisData = await this.decodeOpReturn(slpData.tokenId)
|
||||
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
||||
|
||||
// Hydrate the UTXO object with information about the SLP token.
|
||||
@@ -1609,7 +1443,7 @@ class Utils {
|
||||
|
||||
// If UTXO passes validation, then return formatted token data.
|
||||
else {
|
||||
const genesisData = await this.decodeOpReturn2(slpData.tokenId)
|
||||
const genesisData = await this.decodeOpReturn(slpData.tokenId)
|
||||
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
||||
|
||||
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
||||
|
||||
+395
-395
@@ -60,6 +60,78 @@ describe(`#SLP`, () => {
|
||||
})
|
||||
})
|
||||
|
||||
// describe("#decodeOpReturn", () => {
|
||||
// it("should decode the OP_RETURN for a SEND txid", async () => {
|
||||
// const txid =
|
||||
// "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1"
|
||||
//
|
||||
// const result = await bchjs.SLP.Utils.decodeOpReturn(txid)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.hasAllKeys(result, [
|
||||
// "tokenType",
|
||||
// "transactionType",
|
||||
// "tokenId",
|
||||
// "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("#decodeOpReturn", () => {
|
||||
it("should decode the OP_RETURN for a SEND txid", async () => {
|
||||
const txid =
|
||||
@@ -68,78 +140,6 @@ describe(`#SLP`, () => {
|
||||
const result = await bchjs.SLP.Utils.decodeOpReturn(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result, [
|
||||
"tokenType",
|
||||
"transactionType",
|
||||
"tokenId",
|
||||
"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,
|
||||
@@ -156,7 +156,7 @@ describe(`#SLP`, () => {
|
||||
const txid =
|
||||
"497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7"
|
||||
|
||||
const result = await bchjs.SLP.Utils.decodeOpReturn2(txid)
|
||||
const result = await bchjs.SLP.Utils.decodeOpReturn(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result, [
|
||||
@@ -184,7 +184,7 @@ describe(`#SLP`, () => {
|
||||
const txid =
|
||||
"65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4"
|
||||
|
||||
const result = await bchjs.SLP.Utils.decodeOpReturn2(txid)
|
||||
const result = await bchjs.SLP.Utils.decodeOpReturn(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result, [
|
||||
@@ -201,7 +201,7 @@ describe(`#SLP`, () => {
|
||||
const txid =
|
||||
"3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec"
|
||||
|
||||
await bchjs.SLP.Utils.decodeOpReturn2(txid)
|
||||
await bchjs.SLP.Utils.decodeOpReturn(txid)
|
||||
|
||||
assert.equal(true, false, "Unexpected result")
|
||||
} catch (err) {
|
||||
@@ -218,7 +218,7 @@ describe(`#SLP`, () => {
|
||||
const txid =
|
||||
"a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415"
|
||||
|
||||
await bchjs.SLP.Utils.decodeOpReturn2(txid)
|
||||
await bchjs.SLP.Utils.decodeOpReturn(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
} catch (err) {
|
||||
// console.log(`err: `, err)
|
||||
@@ -322,320 +322,320 @@ describe(`#SLP`, () => {
|
||||
})
|
||||
})
|
||||
|
||||
// describe("#tokenUtxoDetails", () => {
|
||||
// it("should return token details on a valid UTXO", async () => {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1",
|
||||
// vout: 1,
|
||||
// value: "546",
|
||||
// height: 597740,
|
||||
// confirmations: 1,
|
||||
// satoshis: 546
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.hasAllKeys(result[0], [
|
||||
// "txid",
|
||||
// "vout",
|
||||
// "value",
|
||||
// "height",
|
||||
// "confirmations",
|
||||
// "satoshis",
|
||||
// "utxoType",
|
||||
// "transactionType",
|
||||
// "tokenId",
|
||||
// "tokenTicker",
|
||||
// "tokenName",
|
||||
// "tokenDocumentUrl",
|
||||
// "tokenDocumentHash",
|
||||
// "decimals",
|
||||
// "tokenQty",
|
||||
// "isValid"
|
||||
// ])
|
||||
// })
|
||||
//
|
||||
// it("should not choke on this problematic utxo", async () => {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "a8eb788b8ddda6faea00e6e2756624b8feb97655363d0400dd66839ea619d36e",
|
||||
// vout: 1,
|
||||
// value: "546",
|
||||
// height: 603282,
|
||||
// confirmations: 156,
|
||||
// satoshis: 546
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.hasAllKeys(result[0], [
|
||||
// "txid",
|
||||
// "vout",
|
||||
// "value",
|
||||
// "height",
|
||||
// "confirmations",
|
||||
// "satoshis",
|
||||
// "utxoType",
|
||||
// "transactionType",
|
||||
// "tokenId",
|
||||
// "tokenTicker",
|
||||
// "tokenName",
|
||||
// "tokenDocumentUrl",
|
||||
// "tokenDocumentHash",
|
||||
// "decimals",
|
||||
// "tokenQty",
|
||||
// "isValid"
|
||||
// ])
|
||||
// })
|
||||
//
|
||||
// it("should handle BCH and SLP utxos in the same TX", async () => {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
||||
// vout: 3,
|
||||
// value: "6816",
|
||||
// height: 606848,
|
||||
// confirmations: 13,
|
||||
// satoshis: 6816
|
||||
// },
|
||||
// {
|
||||
// txid:
|
||||
// "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
||||
// vout: 2,
|
||||
// value: "546",
|
||||
// height: 606848,
|
||||
// confirmations: 13,
|
||||
// satoshis: 546
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.isArray(result)
|
||||
// assert.equal(result.length, 2)
|
||||
// assert.equal(result[0], false)
|
||||
// })
|
||||
//
|
||||
// // This captures an important corner-case. When an SLP token is created, the
|
||||
// // change UTXO will contain the same SLP txid, but it is not an SLP UTXO.
|
||||
// it("should return details on minting baton from genesis transaction", async () => {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90",
|
||||
// vout: 3,
|
||||
// amount: 0.00002015,
|
||||
// satoshis: 2015,
|
||||
// height: 594892,
|
||||
// confirmations: 5
|
||||
// },
|
||||
// {
|
||||
// txid:
|
||||
// "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90",
|
||||
// vout: 2,
|
||||
// amount: 0.00000546,
|
||||
// satoshis: 546,
|
||||
// height: 594892,
|
||||
// confirmations: 5
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// // console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// assert.equal(data[0], false, "Change UTXO marked as false.")
|
||||
//
|
||||
// assert.property(data[1], "txid")
|
||||
// assert.property(data[1], "vout")
|
||||
// assert.property(data[1], "amount")
|
||||
// assert.property(data[1], "satoshis")
|
||||
// assert.property(data[1], "height")
|
||||
// assert.property(data[1], "confirmations")
|
||||
// assert.property(data[1], "tokenType")
|
||||
// assert.property(data[1], "tokenId")
|
||||
// assert.property(data[1], "tokenTicker")
|
||||
// assert.property(data[1], "tokenName")
|
||||
// assert.property(data[1], "tokenDocumentUrl")
|
||||
// assert.property(data[1], "tokenDocumentHash")
|
||||
// assert.property(data[1], "decimals")
|
||||
// assert.property(data[1], "isValid")
|
||||
// assert.equal(data[1].isValid, true)
|
||||
// })
|
||||
//
|
||||
// it("should return details for a MINT token utxo", async () => {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76",
|
||||
// vout: 1,
|
||||
// amount: 0.00000546,
|
||||
// satoshis: 546,
|
||||
// height: 600297,
|
||||
// confirmations: 76
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// // console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// assert.property(data[0], "txid")
|
||||
// assert.property(data[0], "vout")
|
||||
// assert.property(data[0], "amount")
|
||||
// assert.property(data[0], "satoshis")
|
||||
// assert.property(data[0], "height")
|
||||
// assert.property(data[0], "confirmations")
|
||||
// assert.property(data[0], "utxoType")
|
||||
// assert.property(data[0], "transactionType")
|
||||
// assert.property(data[0], "tokenId")
|
||||
// assert.property(data[0], "tokenTicker")
|
||||
// assert.property(data[0], "tokenName")
|
||||
// assert.property(data[0], "tokenDocumentUrl")
|
||||
// assert.property(data[0], "tokenDocumentHash")
|
||||
// assert.property(data[0], "decimals")
|
||||
// assert.property(data[0], "mintBatonVout")
|
||||
// assert.property(data[0], "batonStillExists")
|
||||
// assert.property(data[0], "tokenQty")
|
||||
// assert.property(data[0], "isValid")
|
||||
// assert.equal(data[0].isValid, true)
|
||||
// })
|
||||
//
|
||||
// it("should return details for a simple SEND SLP token utxo", async () => {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb",
|
||||
// vout: 1,
|
||||
// amount: 0.00000546,
|
||||
// satoshis: 546,
|
||||
// height: 596089,
|
||||
// confirmations: 748
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// // console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// assert.property(data[0], "txid")
|
||||
// assert.property(data[0], "vout")
|
||||
// assert.property(data[0], "amount")
|
||||
// assert.property(data[0], "satoshis")
|
||||
// assert.property(data[0], "height")
|
||||
// assert.property(data[0], "confirmations")
|
||||
// assert.property(data[0], "utxoType")
|
||||
// assert.property(data[0], "tokenId")
|
||||
// assert.property(data[0], "tokenTicker")
|
||||
// assert.property(data[0], "tokenName")
|
||||
// assert.property(data[0], "tokenDocumentUrl")
|
||||
// assert.property(data[0], "tokenDocumentHash")
|
||||
// assert.property(data[0], "decimals")
|
||||
// assert.property(data[0], "tokenQty")
|
||||
// assert.property(data[0], "isValid")
|
||||
// assert.equal(data[0].isValid, true)
|
||||
// })
|
||||
//
|
||||
// it("should handle BCH and SLP utxos in the same TX", async () => {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
||||
// vout: 3,
|
||||
// value: "6816",
|
||||
// height: 606848,
|
||||
// confirmations: 13,
|
||||
// satoshis: 6816
|
||||
// },
|
||||
// {
|
||||
// txid:
|
||||
// "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
||||
// vout: 2,
|
||||
// value: "546",
|
||||
// height: 606848,
|
||||
// confirmations: 13,
|
||||
// satoshis: 546
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.isArray(result)
|
||||
// assert.equal(result.length, 2)
|
||||
// assert.equal(result[0], false)
|
||||
// assert.equal(result[1].isValid, true)
|
||||
// })
|
||||
//
|
||||
// it("should handle problematic utxos", async () => {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "0e3a217fc22612002031d317b4cecd9b692b66b52951a67b23c43041aefa3959",
|
||||
// vout: 0,
|
||||
// amount: 0.00018362,
|
||||
// satoshis: 18362,
|
||||
// height: 613483,
|
||||
// confirmations: 124
|
||||
// },
|
||||
// {
|
||||
// txid:
|
||||
// "67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19",
|
||||
// vout: 1,
|
||||
// amount: 0.00000546,
|
||||
// satoshis: 546,
|
||||
// height: 612075,
|
||||
// confirmations: 1532
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.isArray(result)
|
||||
// assert.equal(result.length, 2)
|
||||
// assert.equal(result[0], false)
|
||||
// assert.equal(result[1].isValid, true)
|
||||
// })
|
||||
//
|
||||
// it("should return false for BCH-only UTXOs", async () => {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "a937f792c7c9eb23b4f344ce5c233d1ac0909217d0a504d71e6b1e4efb864a3b",
|
||||
// vout: 0,
|
||||
// amount: 0.00001,
|
||||
// satoshis: 1000,
|
||||
// confirmations: 0,
|
||||
// ts: 1578424704
|
||||
// },
|
||||
// {
|
||||
// txid:
|
||||
// "53fd141c2e999e080a5860887441a2c45e9cbe262027e2bd2ac998fc76e43c44",
|
||||
// vout: 0,
|
||||
// amount: 0.00001,
|
||||
// satoshis: 1000,
|
||||
// confirmations: 0,
|
||||
// ts: 1578424634
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// // console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// assert.isArray(data)
|
||||
// assert.equal(false, data[0])
|
||||
// assert.equal(false, data[1])
|
||||
// })
|
||||
// })
|
||||
|
||||
describe("#tokenUtxoDetails", () => {
|
||||
it("should return token details on a valid UTXO", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1",
|
||||
vout: 1,
|
||||
value: "546",
|
||||
height: 597740,
|
||||
confirmations: 1,
|
||||
satoshis: 546
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result[0], [
|
||||
"txid",
|
||||
"vout",
|
||||
"value",
|
||||
"height",
|
||||
"confirmations",
|
||||
"satoshis",
|
||||
"utxoType",
|
||||
"transactionType",
|
||||
"tokenId",
|
||||
"tokenTicker",
|
||||
"tokenName",
|
||||
"tokenDocumentUrl",
|
||||
"tokenDocumentHash",
|
||||
"decimals",
|
||||
"tokenQty",
|
||||
"isValid"
|
||||
])
|
||||
})
|
||||
|
||||
it("should not choke on this problematic utxo", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"a8eb788b8ddda6faea00e6e2756624b8feb97655363d0400dd66839ea619d36e",
|
||||
vout: 1,
|
||||
value: "546",
|
||||
height: 603282,
|
||||
confirmations: 156,
|
||||
satoshis: 546
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result[0], [
|
||||
"txid",
|
||||
"vout",
|
||||
"value",
|
||||
"height",
|
||||
"confirmations",
|
||||
"satoshis",
|
||||
"utxoType",
|
||||
"transactionType",
|
||||
"tokenId",
|
||||
"tokenTicker",
|
||||
"tokenName",
|
||||
"tokenDocumentUrl",
|
||||
"tokenDocumentHash",
|
||||
"decimals",
|
||||
"tokenQty",
|
||||
"isValid"
|
||||
])
|
||||
})
|
||||
|
||||
it("should handle BCH and SLP utxos in the same TX", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
||||
vout: 3,
|
||||
value: "6816",
|
||||
height: 606848,
|
||||
confirmations: 13,
|
||||
satoshis: 6816
|
||||
},
|
||||
{
|
||||
txid:
|
||||
"d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
||||
vout: 2,
|
||||
value: "546",
|
||||
height: 606848,
|
||||
confirmations: 13,
|
||||
satoshis: 546
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.equal(result.length, 2)
|
||||
assert.equal(result[0], false)
|
||||
})
|
||||
|
||||
// This captures an important corner-case. When an SLP token is created, the
|
||||
// change UTXO will contain the same SLP txid, but it is not an SLP UTXO.
|
||||
it("should return details on minting baton from genesis transaction", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90",
|
||||
vout: 3,
|
||||
amount: 0.00002015,
|
||||
satoshis: 2015,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
},
|
||||
{
|
||||
txid:
|
||||
"bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90",
|
||||
vout: 2,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.equal(data[0], false, "Change UTXO marked as false.")
|
||||
|
||||
assert.property(data[1], "txid")
|
||||
assert.property(data[1], "vout")
|
||||
assert.property(data[1], "amount")
|
||||
assert.property(data[1], "satoshis")
|
||||
assert.property(data[1], "height")
|
||||
assert.property(data[1], "confirmations")
|
||||
assert.property(data[1], "tokenType")
|
||||
assert.property(data[1], "tokenId")
|
||||
assert.property(data[1], "tokenTicker")
|
||||
assert.property(data[1], "tokenName")
|
||||
assert.property(data[1], "tokenDocumentUrl")
|
||||
assert.property(data[1], "tokenDocumentHash")
|
||||
assert.property(data[1], "decimals")
|
||||
assert.property(data[1], "isValid")
|
||||
assert.equal(data[1].isValid, true)
|
||||
})
|
||||
|
||||
it("should return details for a MINT token utxo", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76",
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 600297,
|
||||
confirmations: 76
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data[0], "txid")
|
||||
assert.property(data[0], "vout")
|
||||
assert.property(data[0], "amount")
|
||||
assert.property(data[0], "satoshis")
|
||||
assert.property(data[0], "height")
|
||||
assert.property(data[0], "confirmations")
|
||||
assert.property(data[0], "utxoType")
|
||||
assert.property(data[0], "transactionType")
|
||||
assert.property(data[0], "tokenId")
|
||||
assert.property(data[0], "tokenTicker")
|
||||
assert.property(data[0], "tokenName")
|
||||
assert.property(data[0], "tokenDocumentUrl")
|
||||
assert.property(data[0], "tokenDocumentHash")
|
||||
assert.property(data[0], "decimals")
|
||||
assert.property(data[0], "mintBatonVout")
|
||||
assert.property(data[0], "batonStillExists")
|
||||
assert.property(data[0], "tokenQty")
|
||||
assert.property(data[0], "isValid")
|
||||
assert.equal(data[0].isValid, true)
|
||||
})
|
||||
|
||||
it("should return details for a simple SEND SLP token utxo", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb",
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 596089,
|
||||
confirmations: 748
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data[0], "txid")
|
||||
assert.property(data[0], "vout")
|
||||
assert.property(data[0], "amount")
|
||||
assert.property(data[0], "satoshis")
|
||||
assert.property(data[0], "height")
|
||||
assert.property(data[0], "confirmations")
|
||||
assert.property(data[0], "utxoType")
|
||||
assert.property(data[0], "tokenId")
|
||||
assert.property(data[0], "tokenTicker")
|
||||
assert.property(data[0], "tokenName")
|
||||
assert.property(data[0], "tokenDocumentUrl")
|
||||
assert.property(data[0], "tokenDocumentHash")
|
||||
assert.property(data[0], "decimals")
|
||||
assert.property(data[0], "tokenQty")
|
||||
assert.property(data[0], "isValid")
|
||||
assert.equal(data[0].isValid, true)
|
||||
})
|
||||
|
||||
it("should handle BCH and SLP utxos in the same TX", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
||||
vout: 3,
|
||||
value: "6816",
|
||||
height: 606848,
|
||||
confirmations: 13,
|
||||
satoshis: 6816
|
||||
},
|
||||
{
|
||||
txid:
|
||||
"d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56",
|
||||
vout: 2,
|
||||
value: "546",
|
||||
height: 606848,
|
||||
confirmations: 13,
|
||||
satoshis: 546
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.equal(result.length, 2)
|
||||
assert.equal(result[0], false)
|
||||
assert.equal(result[1].isValid, true)
|
||||
})
|
||||
|
||||
it("should handle problematic utxos", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"0e3a217fc22612002031d317b4cecd9b692b66b52951a67b23c43041aefa3959",
|
||||
vout: 0,
|
||||
amount: 0.00018362,
|
||||
satoshis: 18362,
|
||||
height: 613483,
|
||||
confirmations: 124
|
||||
},
|
||||
{
|
||||
txid:
|
||||
"67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19",
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 612075,
|
||||
confirmations: 1532
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.equal(result.length, 2)
|
||||
assert.equal(result[0], false)
|
||||
assert.equal(result[1].isValid, true)
|
||||
})
|
||||
|
||||
it("should return false for BCH-only UTXOs", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"a937f792c7c9eb23b4f344ce5c233d1ac0909217d0a504d71e6b1e4efb864a3b",
|
||||
vout: 0,
|
||||
amount: 0.00001,
|
||||
satoshis: 1000,
|
||||
confirmations: 0,
|
||||
ts: 1578424704
|
||||
},
|
||||
{
|
||||
txid:
|
||||
"53fd141c2e999e080a5860887441a2c45e9cbe262027e2bd2ac998fc76e43c44",
|
||||
vout: 0,
|
||||
amount: 0.00001,
|
||||
satoshis: 1000,
|
||||
confirmations: 0,
|
||||
ts: 1578424634
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data)
|
||||
assert.equal(false, data[0])
|
||||
assert.equal(false, data[1])
|
||||
})
|
||||
})
|
||||
|
||||
describe("#tokenUtxoDetails2", () => {
|
||||
// // This captures an important corner-case. When an SLP token is created, the
|
||||
// // change UTXO will contain the same SLP txid, but it is not an SLP UTXO.
|
||||
it("should return details on minting baton from genesis transaction", async () => {
|
||||
@@ -660,7 +660,7 @@ describe(`#SLP`, () => {
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos)
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.equal(data[0], false, "Change UTXO marked as false.")
|
||||
@@ -697,7 +697,7 @@ describe(`#SLP`, () => {
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos)
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data[0], "txid")
|
||||
@@ -733,7 +733,7 @@ describe(`#SLP`, () => {
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos)
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data[0], "txid")
|
||||
@@ -776,7 +776,7 @@ describe(`#SLP`, () => {
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos)
|
||||
const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
@@ -807,7 +807,7 @@ describe(`#SLP`, () => {
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos)
|
||||
const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
@@ -838,7 +838,7 @@ describe(`#SLP`, () => {
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails2(utxos)
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data)
|
||||
|
||||
+124
-119
@@ -38,6 +38,125 @@ 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 () => {
|
||||
// Mock UTXO.
|
||||
@@ -66,123 +185,7 @@ describe("#SLP TokenType1", () => {
|
||||
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("#generateSendOpReturn2", () => {
|
||||
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.generateSendOpReturn2(
|
||||
tokenUtxos,
|
||||
1
|
||||
)
|
||||
console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result, ["script", "outputs"])
|
||||
assert.isNumber(result.outputs)
|
||||
@@ -218,8 +221,9 @@ describe("#SLP TokenType1", () => {
|
||||
1
|
||||
)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.equal(Buffer.isBuffer(result), true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -229,6 +233,7 @@ describe("#SLP TokenType1", () => {
|
||||
name: "SLP Test Token",
|
||||
ticker: "SLPTEST",
|
||||
documentUrl: "https://bchjs.cash",
|
||||
documentHash: "",
|
||||
decimals: 8,
|
||||
initialQty: 10
|
||||
}
|
||||
@@ -236,9 +241,9 @@ describe("#SLP TokenType1", () => {
|
||||
const result = await bchjs.SLP.TokenType1.generateGenesisOpReturn(
|
||||
configObj
|
||||
)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.equal(Buffer.isBuffer(result[1]), true)
|
||||
assert.equal(Buffer.isBuffer(result), true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+684
-761
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user