mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-21 16:51:59 -07:00
fix(tokenUtxoDetails2): Added with unit and integration tests. Uses new decodeOpReturn2()
This commit is contained in:
@@ -1443,6 +1443,202 @@ class Utils {
|
||||
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.`)
|
||||
|
||||
// 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]
|
||||
|
||||
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.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.decodeOpReturn2(utxo.txid)
|
||||
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||
} catch (err) {
|
||||
// console.log(`error: `, err)
|
||||
// An error will be thrown if the txid is not SLP.
|
||||
// Mark as false and continue the loop.
|
||||
outAry.push(false)
|
||||
continue
|
||||
}
|
||||
|
||||
const txType = slpData.txType.toLowerCase()
|
||||
|
||||
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
||||
|
||||
// If there is an OP_RETURN, attempt to decode it.
|
||||
// Handle Genesis SLP transactions.
|
||||
if (txType === "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.documentUri
|
||||
utxo.tokenDocumentHash = slpData.documentHash
|
||||
utxo.decimals = slpData.decimals
|
||||
|
||||
// something
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Mint SLP transactions.
|
||||
if (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.
|
||||
outAry[i] = false
|
||||
}
|
||||
|
||||
// If UTXO passes validation, then return formatted token data.
|
||||
else {
|
||||
const genesisData = await this.decodeOpReturn2(slpData.tokenId)
|
||||
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
||||
|
||||
// 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.documentUri
|
||||
utxo.tokenDocumentHash = genesisData.documentHash
|
||||
utxo.decimals = genesisData.decimals
|
||||
|
||||
utxo.mintBatonVout = slpData.mintBatonVout
|
||||
|
||||
// Calculate the real token quantity.
|
||||
utxo.tokenQty =
|
||||
Number(slpData.qty) / Math.pow(10, genesisData.decimals)
|
||||
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Send SLP transactions.
|
||||
if (txType === "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
|
||||
// }
|
||||
|
||||
// Figure out what token quantity is represented by this utxo.
|
||||
const tokenQty = slpData.amounts[utxo.vout - 1]
|
||||
// console.log(`tokenQty: `, tokenQty)
|
||||
|
||||
if (!tokenQty) {
|
||||
outAry[i] = false
|
||||
}
|
||||
|
||||
// If UTXO passes validation, then return formatted token data.
|
||||
else {
|
||||
const genesisData = await this.decodeOpReturn2(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.documentUri
|
||||
utxo.tokenDocumentHash = genesisData.documentHash
|
||||
utxo.decimals = genesisData.decimals
|
||||
|
||||
// Calculate the real token quantity.
|
||||
utxo.tokenQty = tokenQty / Math.pow(10, genesisData.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Utils
|
||||
|
||||
+212
-14
@@ -635,6 +635,218 @@ describe(`#SLP`, () => {
|
||||
})
|
||||
})
|
||||
|
||||
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 () => {
|
||||
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.tokenUtxoDetails2(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 () => {
|
||||
// Mock the call to REST API
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76",
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 600297,
|
||||
confirmations: 76
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails2(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], "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.tokenUtxoDetails2(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.tokenUtxoDetails2(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.tokenUtxoDetails2(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.tokenUtxoDetails2(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data)
|
||||
assert.equal(false, data[0])
|
||||
assert.equal(false, data[1])
|
||||
})
|
||||
})
|
||||
|
||||
describe("#balancesForAddress", () => {
|
||||
it(`should throw an error if input is not a string or array of strings`, async () => {
|
||||
try {
|
||||
@@ -653,13 +865,6 @@ describe(`#SLP`, () => {
|
||||
})
|
||||
|
||||
it(`should fetch all balances for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9`, async () => {
|
||||
// Mock the call to rest.bitcoin.com
|
||||
if (process.env.TEST === "unit") {
|
||||
sandbox
|
||||
.stub(axios, "get")
|
||||
.resolves({ data: mockData.balancesForAddress })
|
||||
}
|
||||
|
||||
const balances = await bchjs.SLP.Utils.balancesForAddress(
|
||||
"simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9"
|
||||
)
|
||||
@@ -681,13 +886,6 @@ describe(`#SLP`, () => {
|
||||
"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57"
|
||||
]
|
||||
|
||||
// Mock the call to rest.bitcoin.com
|
||||
if (process.env.TEST === "unit") {
|
||||
sandbox
|
||||
.stub(axios, "post")
|
||||
.resolves({ data: mockData.balancesForAddresses })
|
||||
}
|
||||
|
||||
const balances = await bchjs.SLP.Utils.balancesForAddress(addresses)
|
||||
// console.log(`balances: ${JSON.stringify(balances, null, 2)}`)
|
||||
|
||||
|
||||
@@ -1521,6 +1521,479 @@ describe("#SLP Utils", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("#tokenUtxoDetails2", () => {
|
||||
it("should throw error if input is not an array.", async () => {
|
||||
try {
|
||||
await slp.Utils.tokenUtxoDetails2("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.tokenUtxoDetails2(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.tokenUtxoDetails2(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."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// // 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 () => {
|
||||
// Mock the call to REST API
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(slp.Utils, "validateTxid").resolves([
|
||||
{
|
||||
txid:
|
||||
"bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90",
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox.stub(slp.Utils, "decodeOpReturn2").resolves({
|
||||
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"
|
||||
})
|
||||
|
||||
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.tokenUtxoDetails2(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert2.equal(data[0], false, "Change UTXO marked as false.")
|
||||
|
||||
assert2.property(data[1], "txid")
|
||||
assert2.property(data[1], "vout")
|
||||
assert2.property(data[1], "amount")
|
||||
assert2.property(data[1], "satoshis")
|
||||
assert2.property(data[1], "height")
|
||||
assert2.property(data[1], "confirmations")
|
||||
assert2.property(data[1], "tokenType")
|
||||
assert2.property(data[1], "tokenId")
|
||||
assert2.property(data[1], "tokenTicker")
|
||||
assert2.property(data[1], "tokenName")
|
||||
assert2.property(data[1], "tokenDocumentUrl")
|
||||
assert2.property(data[1], "tokenDocumentHash")
|
||||
assert2.property(data[1], "decimals")
|
||||
assert2.property(data[1], "isValid")
|
||||
assert2.equal(data[1].isValid, true)
|
||||
})
|
||||
|
||||
it("should return details for a MINT token utxo", async () => {
|
||||
// Mock the call to REST API
|
||||
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
.stub(slp.Utils, "decodeOpReturn2")
|
||||
.onCall(0)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: "MINT",
|
||||
tokenId:
|
||||
"38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0",
|
||||
mintBatonVout: 2,
|
||||
qty: "1000000000000"
|
||||
})
|
||||
.onCall(1)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: "GENESIS",
|
||||
ticker: "PSF",
|
||||
name: "Permissionless Software Foundation",
|
||||
tokenId:
|
||||
"38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0",
|
||||
documentUri: "psfoundation.cash",
|
||||
documentHash: "",
|
||||
decimals: 8,
|
||||
mintBatonVout: 2,
|
||||
qty: "1988209163133"
|
||||
})
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(slp.Utils, "validateTxid").resolves([
|
||||
{
|
||||
txid:
|
||||
"cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76",
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76",
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 600297,
|
||||
confirmations: 76
|
||||
}
|
||||
]
|
||||
|
||||
const data = await slp.Utils.tokenUtxoDetails2(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert2.property(data[0], "txid")
|
||||
assert2.property(data[0], "vout")
|
||||
assert2.property(data[0], "amount")
|
||||
assert2.property(data[0], "satoshis")
|
||||
assert2.property(data[0], "height")
|
||||
assert2.property(data[0], "confirmations")
|
||||
assert2.property(data[0], "utxoType")
|
||||
assert2.property(data[0], "transactionType")
|
||||
assert2.property(data[0], "tokenId")
|
||||
assert2.property(data[0], "tokenTicker")
|
||||
assert2.property(data[0], "tokenName")
|
||||
assert2.property(data[0], "tokenDocumentUrl")
|
||||
assert2.property(data[0], "tokenDocumentHash")
|
||||
assert2.property(data[0], "decimals")
|
||||
assert2.property(data[0], "mintBatonVout")
|
||||
assert2.property(data[0], "tokenQty")
|
||||
assert2.property(data[0], "isValid")
|
||||
assert.equal(data[0].isValid, true)
|
||||
})
|
||||
|
||||
it("should return details for a simple SEND SLP token utxo", async () => {
|
||||
// Mock the call to REST API
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
.stub(slp.Utils, "decodeOpReturn2")
|
||||
.onCall(0)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: "SEND",
|
||||
tokenId:
|
||||
"497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7",
|
||||
amounts: ["200000000", "99887500000000"]
|
||||
})
|
||||
.onCall(1)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: "GENESIS",
|
||||
ticker: "TOK-CH",
|
||||
name: "TokyoCash",
|
||||
tokenId:
|
||||
"497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7",
|
||||
documentUri: "",
|
||||
documentHash: "",
|
||||
decimals: 8,
|
||||
mintBatonVout: 0,
|
||||
qty: "2100000000000000"
|
||||
})
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(slp.Utils, "validateTxid").resolves([
|
||||
{
|
||||
txid:
|
||||
"fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb",
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb",
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 596089,
|
||||
confirmations: 748
|
||||
}
|
||||
]
|
||||
|
||||
const data = await slp.Utils.tokenUtxoDetails2(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert2.property(data[0], "txid")
|
||||
assert2.property(data[0], "vout")
|
||||
assert2.property(data[0], "amount")
|
||||
assert2.property(data[0], "satoshis")
|
||||
assert2.property(data[0], "height")
|
||||
assert2.property(data[0], "confirmations")
|
||||
assert2.property(data[0], "utxoType")
|
||||
assert2.property(data[0], "tokenId")
|
||||
assert2.property(data[0], "tokenTicker")
|
||||
assert2.property(data[0], "tokenName")
|
||||
assert2.property(data[0], "tokenDocumentUrl")
|
||||
assert2.property(data[0], "tokenDocumentHash")
|
||||
assert2.property(data[0], "decimals")
|
||||
assert2.property(data[0], "tokenQty")
|
||||
assert2.property(data[0], "isValid")
|
||||
assert.equal(data[0].isValid, true)
|
||||
})
|
||||
|
||||
it("should handle BCH and SLP utxos in the same TX", async () => {
|
||||
// Mock external dependencies.
|
||||
sandbox
|
||||
.stub(slp.Utils, "validateTxid")
|
||||
.resolves(mockData.mockDualValidation)
|
||||
|
||||
sandbox
|
||||
.stub(slp.Utils, "decodeOpReturn2")
|
||||
.onCall(0)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: "SEND",
|
||||
tokenId:
|
||||
"dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d",
|
||||
amounts: ["1", "5"]
|
||||
})
|
||||
.onCall(1)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: "SEND",
|
||||
tokenId:
|
||||
"dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d",
|
||||
amounts: ["1", "5"]
|
||||
})
|
||||
.onCall(2)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: "GENESIS",
|
||||
ticker: "TAP",
|
||||
name: "Thoughts and Prayers",
|
||||
tokenId:
|
||||
"dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d",
|
||||
documentUri: "",
|
||||
documentHash: "",
|
||||
decimals: 0,
|
||||
mintBatonVout: 2,
|
||||
qty: "1000000"
|
||||
})
|
||||
|
||||
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 slp.Utils.tokenUtxoDetails2(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert2.isArray(result)
|
||||
assert2.equal(result.length, 2)
|
||||
assert2.equal(result[0], false)
|
||||
assert.equal(result[1].isValid, true)
|
||||
})
|
||||
|
||||
it("should handle problematic utxos", async () => {
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
.stub(slp.Utils, "decodeOpReturn2")
|
||||
.onCall(0)
|
||||
.throws({ message: "scriptpubkey not op_return" })
|
||||
.onCall(1)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: "SEND",
|
||||
tokenId:
|
||||
"f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f",
|
||||
amounts: ["5000000", "395010942"]
|
||||
})
|
||||
.onCall(2)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: "GENESIS",
|
||||
ticker: "AUDC",
|
||||
name: "AUD Coin",
|
||||
tokenId:
|
||||
"f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f",
|
||||
documentUri: "audcoino@gmail.com",
|
||||
documentHash: "",
|
||||
decimals: 6,
|
||||
mintBatonVout: 0,
|
||||
qty: "2000000000000000000"
|
||||
})
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(slp.Utils, "validateTxid").resolves([
|
||||
{
|
||||
txid:
|
||||
"67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19",
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
|
||||
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 slp.Utils.tokenUtxoDetails2(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert2.isArray(result)
|
||||
assert2.equal(result.length, 2)
|
||||
assert2.equal(result[0], false)
|
||||
assert2.equal(result[1].isValid, true)
|
||||
})
|
||||
|
||||
it("should return false for BCH-only UTXOs", async () => {
|
||||
// Mock live network calls
|
||||
if (process.env.TEST === "unit") {
|
||||
sandbox
|
||||
.stub(slp.Utils, "decodeOpReturn2")
|
||||
.throws(new Error("scriptpubkey not op_return"))
|
||||
}
|
||||
|
||||
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 slp.Utils.tokenUtxoDetails2(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert2.isArray(data)
|
||||
assert2.equal(false, data[0])
|
||||
assert2.equal(false, data[1])
|
||||
})
|
||||
})
|
||||
|
||||
describe("#txDetails", () => {
|
||||
it("should throw an error if txid is not included", async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user