fix(SLP.Utils): Replacing old functions with new ones

This commit is contained in:
Chris Troutner
2020-05-31 18:38:02 -07:00
parent 7526b4a09d
commit 119ef1b459
5 changed files with 1531 additions and 1738 deletions
+112 -79
View File
@@ -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
}
}
+221 -387
View File
@@ -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
View File
@@ -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)
+119 -116
View File
@@ -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)
+684 -761
View File
File diff suppressed because it is too large Load Diff