mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-21 16:51:59 -07:00
feat(isTokenUtxo): Deprecating and removing isTokenUtxo() as tokenUtxoDetails() can be used instead
This commit is contained in:
@@ -831,188 +831,6 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.isTokenUtxo() isTokenUtxo()
|
||||
* @apiName isTokenUtxo
|
||||
* @apiGroup SLP Utils
|
||||
* @apiDescription Determine if UTXO belongs to an SLP transaction.
|
||||
*
|
||||
* Expects an array of UTXO objects as input. Returns the same array with
|
||||
* additional properties for each UTXO, including an isSlp property which
|
||||
* will be true or false indicating if the UTXO belongs to a valid SLP
|
||||
* transaction.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
* (async () => {
|
||||
* try {
|
||||
* const addr = 'bitcoincash:qqkpnx2rff4q5jranf3wk9jsprhae9unxckq07gph4'
|
||||
* const utxos = await bchjs.Blockbook.utxo(addr)
|
||||
*
|
||||
* const isSLPUtxo = await bchjs.SLP.Utils.isTokenUtxo(utxos)
|
||||
*
|
||||
* console.log(`${JSON.stringify(isSLPUtxo,null,2)}`)
|
||||
* } catch (error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // returns
|
||||
* [
|
||||
* {
|
||||
* "txid": "99093e8a19e0a649bf943dbc33d926feb09c02e61258c1bdaf2caffa7183c730",
|
||||
* "vout": 1,
|
||||
* "value": "546",
|
||||
* "height": 636885,
|
||||
* "confirmations": 123,
|
||||
* "satoshis": 546,
|
||||
* "isSlp": true
|
||||
* },
|
||||
* {
|
||||
* "txid": "2069e99a90499693e42cd1db82147e3e0acfe5e7315c6cc2f0252432f45300d7",
|
||||
* "vout": 0,
|
||||
* "value": "600",
|
||||
* "height": 636885,
|
||||
* "confirmations": 123,
|
||||
* "satoshis": 600,
|
||||
* "isSlp": false
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
|
||||
// Note: There is no way to validate SLP UTXOs without inspecting the OP_RETURN.
|
||||
// If a UTXO returns false when its txid is passed to validateTxid(), then the
|
||||
// UTXO is not associated with SLP. This is a fast and quick check.
|
||||
// If a UTXO returns true though, the OP_RETURN has to be inspected to determine
|
||||
// for sure that it *is* associated with an SLP transaction not just change.
|
||||
async isTokenUtxo(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 spot check the required
|
||||
// properties.
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const utxo = utxos[i]
|
||||
|
||||
if (!utxo.satoshis) {
|
||||
// If Electrumx, convert the value to satoshis.
|
||||
if (utxo.value) {
|
||||
utxo.satoshis = utxo.value
|
||||
}
|
||||
// If there is neither a satoshis or value property, throw an error.
|
||||
else {
|
||||
throw new Error(
|
||||
`utxo ${i} does not have a satoshis or value property.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!utxo.txid) {
|
||||
// If Electrumx, convert the tx_hash property to txid.
|
||||
if (utxo.tx_hash) {
|
||||
utxo.txid = utxo.tx_hash
|
||||
}
|
||||
// If there is neither a txid or tx_hash property, throw an error.
|
||||
else {
|
||||
throw new Error(
|
||||
`utxo ${i} does not have a txid or tx_hash property.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!Number.isInteger(utxo.vout)) {
|
||||
if (Number.isInteger(utxo.tx_pos)) {
|
||||
utxo.vout = utxo.tx_pos
|
||||
} else {
|
||||
throw new Error(
|
||||
`utxo ${i} does not have a vout or tx_pos property.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validatedSlpUtxos = []
|
||||
|
||||
// Loop through each utxo.
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const utxo = utxos[i]
|
||||
|
||||
// console.log(`thisValidation: `, thisValidation)
|
||||
// console.log(`utxo: `, utxo)
|
||||
|
||||
// Validate the txid for the UTXO using SLPDB.
|
||||
const isValid = await _this.validateTxid(utxo.txid)
|
||||
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
|
||||
|
||||
// If SLPDB says the UTXOs TXID is invalid, mark the UTXO and move on.
|
||||
if (!isValid) {
|
||||
utxo.isSlp = false
|
||||
validatedSlpUtxos.push(utxo)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the OP_RETURN.
|
||||
let slpData = {}
|
||||
try {
|
||||
slpData = await _this.decodeOpReturn(utxo.txid)
|
||||
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||
} catch (err) {
|
||||
// console.log(`decodeOpReturn2 error: `, err)
|
||||
// If that function throws an error, then mark the UTXO as false.
|
||||
utxo.isSlp = false
|
||||
validatedSlpUtxos.push(utxo)
|
||||
continue
|
||||
}
|
||||
|
||||
const txType = slpData.txType.toLowerCase()
|
||||
|
||||
// Handle Genesis and Mint SLP transactions.
|
||||
if (txType === "genesis" || txType === "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.
|
||||
utxo.isSlp = false
|
||||
validatedSlpUtxos.push(utxo)
|
||||
continue
|
||||
}
|
||||
//
|
||||
|
||||
// Handle Send transactions.
|
||||
} else if (txType === "send") {
|
||||
const slpOutputs = slpData.amounts.length
|
||||
|
||||
// If the UTXO vout value does not line up with an SLP output in the
|
||||
// OP_RETURN, then it is not an SLP UTXO.
|
||||
if (utxo.vout > slpOutputs) {
|
||||
utxo.isSlp = false
|
||||
validatedSlpUtxos.push(utxo)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If code path gets to this point and it hasn't been marked false,
|
||||
// then it can safely be marked as true.
|
||||
utxo.isSlp = true
|
||||
validatedSlpUtxos.push(utxo)
|
||||
|
||||
// Below is deprecated, but kept for posterity:
|
||||
// Invalidate the utxo if it contains more than dust, since SLP token
|
||||
// UTXOs only contain dust values. <--- NOT TRUE
|
||||
// Note: This is not a very accurate way to make a determination.
|
||||
// See https://gist.github.com/christroutner/434ae0c710001b57e33a4fa8abb7d478
|
||||
//if (utxo.satoshis > 546) validations[i] = false
|
||||
}
|
||||
|
||||
return validatedSlpUtxos
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.tokenUtxoDetails() tokenUtxoDetails()
|
||||
* @apiName tokenUtxoDetails
|
||||
|
||||
@@ -591,278 +591,6 @@ describe("#SLP Utils", () => {
|
||||
// })
|
||||
})
|
||||
|
||||
describe("#isTokenUtxo", () => {
|
||||
it("should throw error if input is not an array.", async () => {
|
||||
try {
|
||||
await slp.Utils.isTokenUtxo("test")
|
||||
|
||||
assert2.equal(true, false, "Unexpected result.")
|
||||
} catch (err) {
|
||||
assert2.include(
|
||||
err.message,
|
||||
`Input must be an array`,
|
||||
"Expected error message."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw error if utxo does not have satoshis or value property.", async () => {
|
||||
try {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90",
|
||||
vout: 3,
|
||||
amount: 0.00002015,
|
||||
satoshis: 2015,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
},
|
||||
{
|
||||
txid:
|
||||
"bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90",
|
||||
vout: 2,
|
||||
amount: 0.00000546,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
}
|
||||
]
|
||||
|
||||
await slp.Utils.isTokenUtxo(utxos)
|
||||
|
||||
assert2.equal(true, false, "Unexpected result.")
|
||||
} catch (err) {
|
||||
assert2.include(
|
||||
err.message,
|
||||
`utxo 1 does not have a satoshis or value property`,
|
||||
"Expected error message."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw error if utxo does not have txid or tx_hash property.", async () => {
|
||||
try {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90",
|
||||
vout: 3,
|
||||
amount: 0.00002015,
|
||||
satoshis: 2015,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
},
|
||||
{
|
||||
vout: 2,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
}
|
||||
]
|
||||
|
||||
await slp.Utils.isTokenUtxo(utxos)
|
||||
|
||||
assert2.equal(true, false, "Unexpected result.")
|
||||
} catch (err) {
|
||||
assert2.include(
|
||||
err.message,
|
||||
`utxo 1 does not have a txid or tx_hash property`,
|
||||
"Expected error message."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should return false for change in an SLP token creation transaction", async () => {
|
||||
// Mock the call to the REST API
|
||||
// Stub the call to validateTxid
|
||||
const validateStub = [
|
||||
{
|
||||
txid:
|
||||
"bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90",
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
sandbox
|
||||
.stub(slp.Utils, "validateTxid")
|
||||
.resolves(validateStub)
|
||||
.onCall(1)
|
||||
.resolves(validateStub)
|
||||
|
||||
// Stub the calls to decodeOpReturn.
|
||||
const decodeStub = {
|
||||
tokenType: 1,
|
||||
txType: "GENESIS",
|
||||
ticker: "SLPSDK",
|
||||
name: "SLP SDK example using BITBOX",
|
||||
tokenId:
|
||||
"bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90",
|
||||
documentUri: "developer.bitcoin.com",
|
||||
documentHash: "",
|
||||
decimals: 8,
|
||||
mintBatonVout: 2,
|
||||
qty: "50700000000"
|
||||
}
|
||||
sandbox
|
||||
.stub(slp.Utils, "decodeOpReturn")
|
||||
.resolves(decodeStub)
|
||||
.onCall(1)
|
||||
.resolves(decodeStub)
|
||||
|
||||
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 slp.Utils.isTokenUtxo(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.equal(
|
||||
data[0].isSlp,
|
||||
false,
|
||||
"Change should not be identified as SLP utxo."
|
||||
)
|
||||
assert.equal(data[1].isSlp, true, "SLP UTXO correctly identified.")
|
||||
})
|
||||
|
||||
it("should return true for a simple SEND SLP token utxo", async () => {
|
||||
// Mock the call to the REST API
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(slp.Utils, "validateTxid").resolves([
|
||||
{
|
||||
txid:
|
||||
"fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb",
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox.stub(slp.Utils, "decodeOpReturn").resolves({
|
||||
tokenType: 1,
|
||||
txType: "SEND",
|
||||
tokenId:
|
||||
"497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7",
|
||||
amounts: ["200000000", "99887500000000"]
|
||||
})
|
||||
|
||||
// Input object.
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb",
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 596089,
|
||||
confirmations: 748
|
||||
}
|
||||
]
|
||||
|
||||
const data = await slp.Utils.isTokenUtxo(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.equal(data[0].isSlp, true, "Simple send UTXO correctly identified")
|
||||
})
|
||||
|
||||
it("should accurately analyze non-slp UTXO from Electrumx", async () => {
|
||||
try {
|
||||
// Mock the call to the REST API
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(slp.Utils, "validateTxid").resolves([
|
||||
{
|
||||
txid:
|
||||
"2069e99a90499693e42cd1db82147e3e0acfe5e7315c6cc2f0252432f45300d7",
|
||||
valid: false
|
||||
}
|
||||
])
|
||||
|
||||
// Simulate the utxos array returned by Electrumx.
|
||||
const utxos = [
|
||||
{
|
||||
height: 636885,
|
||||
tx_hash:
|
||||
"2069e99a90499693e42cd1db82147e3e0acfe5e7315c6cc2f0252432f45300d7",
|
||||
tx_pos: 0,
|
||||
value: 600
|
||||
}
|
||||
]
|
||||
|
||||
const isTokenUtxos = await slp.Utils.isTokenUtxo(utxos)
|
||||
// console.log(`isTokenUtxos: ${JSON.stringify(isTokenUtxos, null, 2)}`)
|
||||
|
||||
assert.equal(
|
||||
isTokenUtxos[0].isSlp,
|
||||
false,
|
||||
"Non-slp UTXO correctly identified"
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(`Error: `, err)
|
||||
}
|
||||
})
|
||||
|
||||
it("should accurately analyze slp UTXO from Electrumx", async () => {
|
||||
try {
|
||||
// Mock the call to the REST API
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(slp.Utils, "validateTxid").resolves([
|
||||
{
|
||||
txid:
|
||||
"99093e8a19e0a649bf943dbc33d926feb09c02e61258c1bdaf2caffa7183c730",
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox.stub(slp.Utils, "decodeOpReturn").resolves({
|
||||
tokenType: 1,
|
||||
txType: "SEND",
|
||||
tokenId:
|
||||
"a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2",
|
||||
amounts: ["100", "198999900"]
|
||||
})
|
||||
|
||||
// Simulate the utxos array returned by Electrumx.
|
||||
const utxos = [
|
||||
{
|
||||
height: 636885,
|
||||
tx_hash:
|
||||
"99093e8a19e0a649bf943dbc33d926feb09c02e61258c1bdaf2caffa7183c730",
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
const isTokenUtxos = await slp.Utils.isTokenUtxo(utxos)
|
||||
// console.log(`isTokenUtxos: ${JSON.stringify(isTokenUtxos, null, 2)}`)
|
||||
|
||||
assert.equal(
|
||||
isTokenUtxos[0].isSlp,
|
||||
true,
|
||||
"Slp UTXO correctly identified"
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(`Error: `, err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("#tokenUtxoDetails", () => {
|
||||
it("should throw error if input is not an array.", async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user