mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-22 00:52:01 -07:00
feat(tokenUtxoDetailsWL): tokenUtxoDetails that use whitelist SLPDB
This commit is contained in:
+2
-2
@@ -16,8 +16,8 @@
|
||||
"test:integration:local": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration",
|
||||
"test:integration:local:bchn": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/testnet",
|
||||
"test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#tokenUtxoDetails' test/integration/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#waterfallValidateTxid' test/unit/",
|
||||
"test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#tokenUtxoDetailsWL' test/integration/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#tokenUtxoDetailsWL' test/unit/",
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
|
||||
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
||||
|
||||
@@ -1120,6 +1120,95 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.tokenUtxoDetailsWL() tokenUtxoDetailsWL()
|
||||
* @apiName tokenUtxoDetailsWL
|
||||
* @apiGroup SLP Utils
|
||||
* @apiDescription
|
||||
*
|
||||
* Same as tokenUtxoDetails(), but it only uses the whitelist SLPDB to
|
||||
* validate UTXOs. This will result in a lot of `isValid: null` values,
|
||||
* but much more performant handling of SLP tokens. Some wallet apps prefer
|
||||
* the scaling performance over the breadth of supported tokens.
|
||||
*
|
||||
*/
|
||||
async tokenUtxoDetailsWL (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]
|
||||
|
||||
// Ensure the UTXO has a txid or tx_hash property.
|
||||
if (!utxo.txid) {
|
||||
// If Electrumx, convert the tx_hash property to txid.
|
||||
if (utxo.tx_hash) {
|
||||
utxo.txid = utxo.tx_hash
|
||||
} else {
|
||||
// If there is neither a txid or tx_hash property, throw an error.
|
||||
throw new Error(
|
||||
`utxo ${i} does not have a txid or tx_hash property.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the UTXO has a vout or tx_pos 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.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hydrate each UTXO with data from SLP OP_REUTRNs.
|
||||
const outAry = await this._hydrateUtxo(utxos)
|
||||
// console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
|
||||
|
||||
// *After* each UTXO has been hydrated with SLP data,
|
||||
// validate the TXID with SLPDB.
|
||||
for (let i = 0; i < outAry.length; i++) {
|
||||
const utxo = outAry[i]
|
||||
|
||||
// *After* the UTXO has been hydrated with SLP data,
|
||||
// validate the TXID with SLPDB.
|
||||
if (utxo.tokenType) {
|
||||
// Only execute this block if the current UTXO has a 'tokenType'
|
||||
// property. i.e. it has been successfully hydrated with SLP
|
||||
// information.
|
||||
|
||||
// Validate against the whitelist SLPDB.
|
||||
const whitelistResult = await this.validateTxid3(utxo.txid)
|
||||
// console.log(
|
||||
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
|
||||
// whitelistResult,
|
||||
// null,
|
||||
// 2
|
||||
// )}`
|
||||
// )
|
||||
|
||||
let isValid = null
|
||||
|
||||
// Safely retrieve the returned value.
|
||||
if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid
|
||||
|
||||
utxo.isValid = isValid
|
||||
}
|
||||
}
|
||||
|
||||
return outAry
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// This is a private function that is called by tokenUtxoDetails().
|
||||
// It loops through an array of UTXOs and tries to hydrate them with SLP
|
||||
// token information from the OP_RETURN data.
|
||||
|
||||
+114
-2
@@ -32,7 +32,8 @@ describe('#SLP', () => {
|
||||
describe('#util', () => {
|
||||
describe('#list', () => {
|
||||
it('should get information on the Spice token', async () => {
|
||||
const tokenId = '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
|
||||
const tokenId =
|
||||
'4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
|
||||
|
||||
const result = await bchjs.SLP.Utils.list(tokenId)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -490,6 +491,116 @@ describe('#SLP', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokenUtxoDetailsWL', () => {
|
||||
it('should return details for a simple SEND SLP token utxo', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 660554,
|
||||
tx_hash:
|
||||
'89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data[0], 'txid')
|
||||
assert.property(data[0], 'vout')
|
||||
assert.property(data[0], 'height')
|
||||
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 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.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data)
|
||||
assert.equal(data[0].isValid, false)
|
||||
assert.equal(data[1].isValid, false)
|
||||
})
|
||||
|
||||
it('should handle a dust attack', async () => {
|
||||
// it("#dustattack", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 655965,
|
||||
tx_hash:
|
||||
'a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e',
|
||||
tx_pos: 151,
|
||||
value: 547,
|
||||
satoshis: 547,
|
||||
txid:
|
||||
'a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e',
|
||||
vout: 151,
|
||||
address: 'bitcoincash:qq4dw3sm8qvglspy6w2qg0u2ugsy9zcfcqrpeflwww',
|
||||
hdIndex: 11
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.equal(data[0].isValid, false)
|
||||
})
|
||||
|
||||
it('should handle null SLPDB validations', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 665577,
|
||||
tx_hash:
|
||||
'4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 665577,
|
||||
tx_hash:
|
||||
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data)
|
||||
// assert.equal(data[0].isValid, null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#balancesForAddress', () => {
|
||||
it('should fetch all balances for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', async () => {
|
||||
const balances = await bchjs.SLP.Utils.balancesForAddress(
|
||||
@@ -738,7 +849,8 @@ describe('#SLP', () => {
|
||||
|
||||
describe('#waterfallValidateTxid', () => {
|
||||
it('should validate known good txid not in whitelist', async () => {
|
||||
const txid = '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
|
||||
const txid =
|
||||
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
|
||||
|
||||
const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
+69
-35
@@ -497,41 +497,6 @@ describe('#SLP Utils', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// CT 1/9/21: This test can be removed after 2/1/21
|
||||
// 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 uut.Utils.tokenUtxoDetails(utxos)
|
||||
//
|
||||
// assert.equal(true, false, 'Unexpected result.')
|
||||
// } catch (err) {
|
||||
// assert.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 = [
|
||||
@@ -1596,6 +1561,75 @@ describe('#SLP Utils', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokenUtxoDetailsWL', () => {
|
||||
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(uut.Utils, 'decodeOpReturn')
|
||||
.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(uut.Utils, 'validateTxid3').resolves([
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 596089,
|
||||
confirmations: 748
|
||||
}
|
||||
]
|
||||
|
||||
const data = await uut.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data[0], 'txid')
|
||||
assert.property(data[0], 'vout')
|
||||
assert.property(data[0], 'height')
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#txDetails', () => {
|
||||
it('should throw an error if txid is not included', async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user