mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-21 16:51:59 -07:00
1280 lines
39 KiB
JavaScript
1280 lines
39 KiB
JavaScript
const axios = require("axios")
|
|
|
|
const Script = require("../script")
|
|
const scriptLib = new Script()
|
|
|
|
const BigNumber = require("bignumber.js")
|
|
|
|
let _this
|
|
|
|
class Utils {
|
|
constructor(config) {
|
|
this.restURL = config.restURL
|
|
this.apiToken = config.apiToken
|
|
|
|
// Add JWT token to the authorization header.
|
|
this.axiosOptions = {
|
|
headers: {
|
|
authorization: `Token ${this.apiToken}`
|
|
}
|
|
}
|
|
|
|
_this = this
|
|
}
|
|
|
|
/**
|
|
* @api SLP.Utils.list() list() - List all tokens or list single token by id.
|
|
* @apiName list
|
|
* @apiGroup SLP
|
|
* @apiDescription List all tokens or list single token by id.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* // List all tokens
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* let list = await bchjs.SLP.Utils.list();
|
|
* console.log(list);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* [ { decimals: 5,
|
|
* timestamp: '2019-04-20 05:03',
|
|
* versionType: 1,
|
|
* documentUri: 'developer.bitcoin.com',
|
|
* symbol: 'MYSTERY',
|
|
* name: 'Mystery',
|
|
* containsBaton: true,
|
|
* id:
|
|
* '10528f22fc20422f7c1075a87ed7270c0a17bc17ea79c6e2f426c6cc14bb25f2',
|
|
* documentHash:
|
|
* '1010101010101010101010101010101010101010101010101010101010101010',
|
|
* initialTokenQty: 500,
|
|
* blockCreated: 579041,
|
|
* blockLastActiveSend: null,
|
|
* blockLastActiveMint: null,
|
|
* txnsSinceGenesis: 1,
|
|
* validAddresses: 1,
|
|
* totalMinted: 500,
|
|
* totalBurned: 0,
|
|
* circulatingSupply: 500,
|
|
* mintingBatonStatus: 'ALIVE' },
|
|
* { decimals: 8,
|
|
* timestamp: '2019-04-20 04:54',
|
|
* versionType: 1,
|
|
* documentUri: 'developer.bitcoin.com',
|
|
* symbol: 'ENIGMA',
|
|
* name: 'Enigma',
|
|
* containsBaton: true,
|
|
* id:
|
|
* '113c55921fe29919ff84e53a6d5af39ed9d983a1c3b3000f27125688489935fa',
|
|
* documentHash:
|
|
* '1010101010101010101010101010101010101010101010101010101010101010',
|
|
* initialTokenQty: 1234,
|
|
* blockCreated: 579040,
|
|
* blockLastActiveSend: null,
|
|
* blockLastActiveMint: 579040,
|
|
* txnsSinceGenesis: 2,
|
|
* validAddresses: 2,
|
|
* totalMinted: 1334,
|
|
* totalBurned: 0,
|
|
* circulatingSupply: 1334,
|
|
* mintingBatonStatus: 'ALIVE' }
|
|
* ]
|
|
*
|
|
* // List single token
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* let list = await bchjs.SLP.Utils.list(
|
|
* "b3f4f132dc3b9c8c96316346993a8d54d729715147b7b11aa6c8cd909e955313"
|
|
* );
|
|
* console.log(list);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* { decimals: 8,
|
|
* timestamp: '2019-04-20 04:54',
|
|
* versionType: 1,
|
|
* documentUri: 'developer.bitcoin.com',
|
|
* symbol: 'ENIGMA',
|
|
* name: 'Enigma',
|
|
* containsBaton: true,
|
|
* id:
|
|
* '113c55921fe29919ff84e53a6d5af39ed9d983a1c3b3000f27125688489935fa',
|
|
* documentHash:
|
|
* '1010101010101010101010101010101010101010101010101010101010101010',
|
|
* initialTokenQty: 1234,
|
|
* blockCreated: 579040,
|
|
* blockLastActiveSend: null,
|
|
* blockLastActiveMint: 579040,
|
|
* txnsSinceGenesis: 2,
|
|
* validAddresses: 2,
|
|
* totalMinted: 1334,
|
|
* totalBurned: 0,
|
|
* circulatingSupply: 1334,
|
|
* mintingBatonStatus: 'ALIVE' }
|
|
*
|
|
* // List multiple tokens by tokenIds
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* let list = await bchjs.SLP.Utils.list([
|
|
* "fa6c74c52450fc164e17402a46645ce494a8a8e93b1383fa27460086931ef59f",
|
|
* "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0"
|
|
* ]);
|
|
* console.log(list);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* [ { decimals: 0,
|
|
* timestamp: '2019-02-18 14:47',
|
|
* versionType: 1,
|
|
* documentUri: 'https://simpleledger.cash',
|
|
* symbol: 'SLP',
|
|
* name: 'Official SLP Token',
|
|
* containsBaton: true,
|
|
* id:
|
|
* 'fa6c74c52450fc164e17402a46645ce494a8a8e93b1383fa27460086931ef59f',
|
|
* documentHash: null,
|
|
* initialTokenQty: 18446744073709552000,
|
|
* blockCreated: 570305,
|
|
* blockLastActiveSend: 580275,
|
|
* blockLastActiveMint: 575914,
|
|
* txnsSinceGenesis: 4537,
|
|
* validAddresses: 164,
|
|
* totalMinted: 19414628793626410000,
|
|
* totalBurned: 18446568350267302000,
|
|
* circulatingSupply: 968060443359109600,
|
|
* mintingBatonStatus: 'ALIVE' },
|
|
* { decimals: 8,
|
|
* timestamp: '2019-02-14 03:11',
|
|
* versionType: 1,
|
|
* documentUri: 'psfoundation.cash',
|
|
* symbol: 'PSF',
|
|
* name: 'Permissionless Software Foundation',
|
|
* containsBaton: true,
|
|
* id:
|
|
* '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
|
* documentHash: null,
|
|
* initialTokenQty: 19882.09163133,
|
|
* blockCreated: 569658,
|
|
* blockLastActiveSend: 580153,
|
|
* blockLastActiveMint: null,
|
|
* txnsSinceGenesis: 51,
|
|
* validAddresses: 9,
|
|
* totalMinted: 19882.09163133,
|
|
* totalBurned: 0.0534241,
|
|
* circulatingSupply: 19882.03820723,
|
|
* mintingBatonStatus: 'ALIVE' } ]
|
|
*/
|
|
async list(id) {
|
|
let path
|
|
let method
|
|
if (!id) {
|
|
method = "get"
|
|
path = `${this.restURL}slp/list`
|
|
} else if (typeof id === "string") {
|
|
method = "get"
|
|
path = `${this.restURL}slp/list/${id}`
|
|
} else if (typeof id === "object") {
|
|
method = "post"
|
|
path = `${this.restURL}slp/list`
|
|
}
|
|
|
|
//console.log(`path: ${path}`)
|
|
|
|
try {
|
|
let response
|
|
if (method === "get") {
|
|
response = await axios.get(path, _this.axiosOptions)
|
|
} else {
|
|
response = await axios.post(
|
|
path,
|
|
{
|
|
tokenIds: id
|
|
},
|
|
_this.axiosOptions
|
|
)
|
|
}
|
|
return response.data
|
|
} catch (error) {
|
|
if (error.response && error.response.data) throw error.response.data
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @api SLP.Utils.balancesForAddress() balancesForAddress() - Return all balances for an address.
|
|
* @apiName balancesForAddress
|
|
* @apiGroup SLP
|
|
* @apiDescription Return all balances for an address or array of addresses.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* let balances = await bchjs.SLP.Utils.balancesForAddress('simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk');
|
|
* console.log(balances);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* // [ { tokenId:
|
|
* // '968ff0cc4c93864001e03e9524e351250b94ec56150fa4897f65b0b6477d44d4',
|
|
* // balance: '9980',
|
|
* // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
|
* // decimalCount: 9 },
|
|
* // { tokenId:
|
|
* // 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
* // balance: '617',
|
|
* // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
|
* // decimalCount: 8 },
|
|
* // { tokenId:
|
|
* // 'b96304d12f1bbc2196df582516410e55a9b34e13c7b4585bf5c1770af30d034f',
|
|
* // balance: '1',
|
|
* // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
|
* // decimalCount: 0 },
|
|
* // { tokenId:
|
|
* // 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
|
|
* // balance: '776',
|
|
* // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
|
* // decimalCount: 7 } ]
|
|
*
|
|
* // balances for Cash Address
|
|
* (async () => {
|
|
* try {
|
|
* let balances = await bchjs.SLP.Utils.balancesForAddress('bitcoincash:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3g4hl47n9y');
|
|
* console.log(balances);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* // [ { tokenId:
|
|
* // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5',
|
|
* // balance: '507',
|
|
* // decimalCount: 2 } ]
|
|
*
|
|
* // balances for Legacy Address
|
|
* (async () => {
|
|
* try {
|
|
* let balances = await bchjs.SLP.Utils.balancesForAddress('1NM2ozrXVSnMRm66ua6aGeXgMsU7yqwqLS');
|
|
* console.log(balances);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* // [ { tokenId:
|
|
* // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5',
|
|
* // balance: '507',
|
|
* // decimalCount: 2 } ]
|
|
*
|
|
* Note: Balances for multiple addresses can be retrieves by passing in an
|
|
* array of addresses.
|
|
*/
|
|
// Retrieve token balances for a given address.
|
|
async balancesForAddress(address) {
|
|
try {
|
|
// Single address.
|
|
if (typeof address === "string") {
|
|
const path = `${this.restURL}slp/balancesForAddress/${address}`
|
|
|
|
const response = await axios.get(path, _this.axiosOptions)
|
|
return response.data
|
|
|
|
// Array of addresses.
|
|
} else if (Array.isArray(address)) {
|
|
const path = `${this.restURL}slp/balancesForAddress`
|
|
|
|
// Dev note: must use axios.post for unit test stubbing.
|
|
const response = await axios.post(
|
|
path,
|
|
{
|
|
addresses: address
|
|
},
|
|
_this.axiosOptions
|
|
)
|
|
|
|
return response.data
|
|
}
|
|
|
|
throw new Error(`Input address must be a string or array of strings.`)
|
|
} catch (error) {
|
|
if (error.response && error.response.data) throw error.response.data
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @api SLP.Utils.balancesForToken() balancesForToken() - List all balances for tokenId.
|
|
* @apiName balancesForToken
|
|
* @apiGroup SLP
|
|
* @apiDescription List all balances for tokenId.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* let balances = await bchjs.SLP.Utils.balancesForToken(
|
|
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
* )
|
|
* console.log(balances)
|
|
* } catch (error) {
|
|
* console.error(error)
|
|
* }
|
|
* })()
|
|
*
|
|
* // returns
|
|
* [
|
|
* {
|
|
* tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
|
|
* tokenBalance: 20,
|
|
* slpAddress: 'simpleledger:qp4g0q97tq53pasnxk2rs570c6573qvylunsf5gy9e'
|
|
* },
|
|
* {
|
|
* tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
|
|
* tokenBalance: 335.55,
|
|
* slpAddress: 'simpleledger:qqcraw7q0ys3kg4z6f2zd267fhg2093c5c0spfk03f'
|
|
* }
|
|
* ]
|
|
*
|
|
*/
|
|
// Retrieve token balances for a given tokenId.
|
|
async balancesForToken(tokenId) {
|
|
const path = `${this.restURL}slp/balancesForToken/${tokenId}`
|
|
|
|
try {
|
|
const response = await axios.get(path, _this.axiosOptions)
|
|
return response.data
|
|
} catch (error) {
|
|
if (error.response && error.response.data) throw error.response.data
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @api SLP.Utils.balance() balance() - Return single balance for an address by token id.
|
|
* @apiName balance
|
|
* @apiGroup SLP
|
|
* @apiDescription Return single balance for an address by token id.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* // single balance for SLP Address
|
|
* (async () => {
|
|
* try {
|
|
* let balance = await bchjs.SLP.Utils.balance(
|
|
* "simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk",
|
|
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
* );
|
|
* console.log(balance);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* // { tokenId:
|
|
* // 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
* // balance: '617',
|
|
* // decimalCount: 8 }
|
|
*
|
|
* // single balance for Cash Address
|
|
* (async () => {
|
|
* try {
|
|
* let balance = await bchjs.SLP.Utils.balance(
|
|
* "bitcoincash:qr5agtachyxvrwxu76vzszan5pnvuzy8dumh7ynrwg",
|
|
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
* );
|
|
* console.log(balance);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* // { tokenId:
|
|
* // 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
* // balance: '617',
|
|
* // decimalCount: 8 }
|
|
*
|
|
* // single balance for Legacy Address
|
|
* (async () => {
|
|
* try {
|
|
* let balance = await bchjs.SLP.Utils.balance(
|
|
* "1DwQqpWc8pzaRydCmiJsPdoqCzmjSQQbp8",
|
|
* "467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5"
|
|
* );
|
|
* console.log(balance);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* // { tokenId:
|
|
* // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5',
|
|
* // balance: '1234',
|
|
* // decimalCount: 2 }
|
|
*/
|
|
// Retrieve a balance for a specific address and token ID
|
|
async balance(address, tokenId) {
|
|
const path = `${this.restURL}slp/balance/${address}/${tokenId}`
|
|
|
|
try {
|
|
const response = await axios.get(path, _this.axiosOptions)
|
|
return response.data
|
|
} catch (error) {
|
|
if (error.response && error.response.data) throw error.response.data
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @api SLP.Utils.validateTxid() validateTxid() - Validate that txid is an SLP transaction.
|
|
* @apiName validateTxid
|
|
* @apiGroup SLP
|
|
* @apiDescription Validate that txid is an SLP transaction.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* // validate single SLP txid
|
|
* (async () => {
|
|
* try {
|
|
* let validated = await bchjs.SLP.Utils.validateTxid(
|
|
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
* );
|
|
* console.log(validated);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* [ { txid:
|
|
* 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
* valid: true } ]
|
|
*
|
|
* // validate multiple SLP txids
|
|
* (async () => {
|
|
* try {
|
|
* let validated = await bchjs.SLP.Utils.validateTxid([
|
|
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb",
|
|
* "00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476"
|
|
* ]);
|
|
* console.log(validated);
|
|
* } catch (error) {
|
|
* console.error(error);
|
|
* }
|
|
* })();
|
|
*
|
|
* // returns
|
|
* [ { txid:
|
|
* 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
* valid: true },
|
|
* { txid:
|
|
* '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476',
|
|
* valid: true } ]
|
|
*/
|
|
async validateTxid(txid) {
|
|
const path = `${this.restURL}slp/validateTxid`
|
|
|
|
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
|
|
|
|
// Handle a single TXID or an array of TXIDs.
|
|
let txids
|
|
if (typeof txid === "string") txids = [txid]
|
|
else txids = txid
|
|
|
|
try {
|
|
const response = await axios.post(
|
|
path,
|
|
{
|
|
txids: txids
|
|
},
|
|
_this.axiosOptions
|
|
)
|
|
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
|
|
|
const validatedTxids = response.data
|
|
|
|
// Handle any null values
|
|
for (let i = 0; i < validatedTxids.length; i++) {
|
|
if (validatedTxids[i] === null) {
|
|
validatedTxids[i] = {
|
|
txid: txids[i],
|
|
valid: false
|
|
}
|
|
}
|
|
}
|
|
|
|
return validatedTxids
|
|
} catch (error) {
|
|
if (error.response && error.response.data) throw error.response.data
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @api SLP.Utils.tokenStats() tokenStats() - Stats for token by tokenId.
|
|
* @apiName tokenStats
|
|
* @apiGroup SLP
|
|
* @apiDescription Stats for token by tokenId.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* let stats = await bchjs.SLP.Utils.tokenStats(
|
|
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
|
* )
|
|
* console.log(stats)
|
|
* } catch (error) {
|
|
* console.error(error)
|
|
* }
|
|
* })()
|
|
*
|
|
* // returns
|
|
* { tokenId:
|
|
* 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
|
* documentUri: '',
|
|
* symbol: 'NAKAMOTO',
|
|
* name: 'NAKAMOTO',
|
|
* decimals: 8,
|
|
* txnsSinceGenesis: 367,
|
|
* validUtxos: 248,
|
|
* validAddresses: 195,
|
|
* circulatingSupply: 20995990,
|
|
* totalBurned: 4010,
|
|
* totalMinted: 21000000,
|
|
* satoshisLockedUp: 135408
|
|
* }
|
|
*/
|
|
async tokenStats(tokenId) {
|
|
const path = `${this.restURL}slp/tokenStats/${tokenId}`
|
|
|
|
try {
|
|
const response = await axios.get(path, _this.axiosOptions)
|
|
return response.data
|
|
} catch (error) {
|
|
if (error.response && error.response.data) throw error.response.data
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @api SLP.Utils.transactions() transactions() - SLP Transactions by tokenId and address.
|
|
* @apiName transactions
|
|
* @apiGroup SLP
|
|
* @apiDescription SLP Transactions by tokenId and address.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* let transactions = await bchjs.SLP.Utils.transactions(
|
|
* "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a",
|
|
* "qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqlsy4gusz"
|
|
* )
|
|
* console.log(transactions)
|
|
* } catch (error) {
|
|
* console.error(error)
|
|
* }
|
|
* })()
|
|
*
|
|
* // returns
|
|
* [
|
|
* {
|
|
* "txid": "27e27170b546f05b2af69d6eddff8834038facf5d81302e9e562df09a5c4445f",
|
|
* "tokenDetails": {
|
|
* "valid": true,
|
|
* "detail": {
|
|
* "decimals": null,
|
|
* "tokenIdHex": "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a",
|
|
* "timestamp": null,
|
|
* "transactionType": "SEND",
|
|
* "versionType": 1,
|
|
* "documentUri": null,
|
|
* "documentSha256Hex": null,
|
|
* "symbol": null,
|
|
* "name": null,
|
|
* "batonVout": null,
|
|
* "containsBaton": null,
|
|
* "genesisOrMintQuantity": null,
|
|
* "sendOutputs": [
|
|
* {
|
|
* "$numberDecimal": "0"
|
|
* },
|
|
* {
|
|
* "$numberDecimal": "25"
|
|
* },
|
|
* {
|
|
* "$numberDecimal": "77"
|
|
* }
|
|
* ]
|
|
* },
|
|
* "invalidReason": null,
|
|
* "schema_version": 30
|
|
* }
|
|
* }
|
|
* ]
|
|
*/
|
|
// Retrieve token transactions for a given tokenId and address.
|
|
async transactions(tokenId, address) {
|
|
const path = `${this.restURL}slp/transactions/${tokenId}/${address}`
|
|
|
|
try {
|
|
const response = await axios.get(path, _this.axiosOptions)
|
|
return response.data
|
|
} catch (error) {
|
|
if (error.response && error.response.data) throw error.response.data
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @api SLP.Utils.burnTotal() burnTotal() - List input, output and burn total for slp transaction.
|
|
* @apiName burnTotal
|
|
* @apiGroup SLP
|
|
* @apiDescription List input, output and burn total for slp transaction.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* const burnTotal = await bchjs.SLP.Utils.burnTotal(
|
|
* "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de"
|
|
* )
|
|
* console.log(burnTotal)
|
|
* } catch (error) {
|
|
* console.error(error)
|
|
* }
|
|
* })()
|
|
*
|
|
* // returns
|
|
* {
|
|
* transactionId: 'c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de',
|
|
* inputTotal: 100000100,
|
|
* outputTotal: 100000000,
|
|
* burnTotal: 100
|
|
* }
|
|
*/
|
|
async burnTotal(transactionId) {
|
|
const path = `${this.restURL}slp/burnTotal/${transactionId}`
|
|
|
|
try {
|
|
const response = await axios.get(path, _this.axiosOptions)
|
|
return response.data
|
|
} catch (error) {
|
|
if (error.response && error.response.data) throw error.response.data
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @api SLP.Utils.txDetails() txDetails() - SLP transaction details
|
|
* @apiName txDetails
|
|
* @apiGroup SLP
|
|
* @apiDescription Transaction details on a token transfer.
|
|
* There is no bulk method for this endpoint. Can only get one tx at a time.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* const details = await bchjs.SLP.Utils.txDetails(
|
|
* "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de"
|
|
* )
|
|
* console.log(details)
|
|
* } catch (error) {
|
|
* console.error(error)
|
|
* }
|
|
* })()
|
|
*
|
|
*/
|
|
async txDetails(txid) {
|
|
try {
|
|
if (
|
|
!txid ||
|
|
txid === "" ||
|
|
typeof txid !== "string" ||
|
|
txid.length !== 64
|
|
)
|
|
throw new Error(`txid string must be included.`)
|
|
|
|
// console.log(`this.restURL: ${this.restURL}`)
|
|
const path = `${this.restURL}slp/txDetails/${txid}`
|
|
|
|
const response = await axios.get(path, _this.axiosOptions)
|
|
return response.data
|
|
} 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.
|
|
*
|
|
* @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.isTokenUtxo() isTokenUtxo() - Determine if UTXO belongs to an SLP transaction.
|
|
* @apiName isTokenUtxo
|
|
* @apiGroup SLP
|
|
* @apiDescription Determine if UTXO belongs to an SLP transaction.
|
|
*
|
|
* Expects an array of UTXO objects as input. Returns an array of Boolean
|
|
* values indicating if a UTXO is associated with SLP tokens (true) or not
|
|
* (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.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* const u = await bchjs.Insight.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
|
|
* const utxos = u.utxos
|
|
*
|
|
* const isSLPUtxo = await bchjs.SLP.Utils.isTokenUtxo(utxos)
|
|
*
|
|
* console.log(`isSLPUtxo: ${JSON.stringify(isSLPUtxo,null,2)}`)
|
|
* } catch (error) {
|
|
* console.error(error)
|
|
* }
|
|
* })()
|
|
*
|
|
* // returns
|
|
* {
|
|
* transactionId: 'c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de',
|
|
* inputTotal: 100000100,
|
|
* outputTotal: 100000000,
|
|
* burnTotal: 100
|
|
* }
|
|
*/
|
|
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]
|
|
|
|
// 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.`)
|
|
}
|
|
|
|
// Create an array of txid strings to feed to validateTxid
|
|
const txids = utxos.map(x => x.txid)
|
|
|
|
// Validate the array of txids.
|
|
let validations = await this.validateTxid(txids)
|
|
//console.log(`validations: ${JSON.stringify(validations, null, 2)}`)
|
|
|
|
// Extract the boolean result
|
|
validations = validations.map(x => x.valid)
|
|
|
|
// Loop through each element in the array and validate the input before
|
|
// further processing.
|
|
for (let i = 0; i < utxos.length; i++) {
|
|
const thisValidation = validations[i]
|
|
const utxo = utxos[i]
|
|
|
|
// Only need to worry about validations that are still true.
|
|
if (thisValidation) {
|
|
const slpData = await this.decodeOpReturn(utxo.txid)
|
|
//console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
|
|
|
// Handle Genesis and Mint SLP transactions.
|
|
if (
|
|
slpData.transactionType === "genesis" ||
|
|
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.
|
|
validations[i] = false
|
|
} else 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) validations[i] = false
|
|
}
|
|
}
|
|
|
|
// 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 validations
|
|
} catch (error) {
|
|
if (error.response && error.response.data) throw error.response.data
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @api SLP.Utils.tokenUtxoDetails() tokenUtxoDetails() - Get token details on a UTXO
|
|
* @apiName tokenUtxoDetails
|
|
* @apiGroup SLP
|
|
* @apiDescription Hydrate a UTXO with SLP token metadata.
|
|
*
|
|
* Expects an array of UTXO objects as input. Returns an array of equal size.
|
|
* If the
|
|
* UTXO does not belong to a SLP transaction, it will return false.
|
|
* If the UTXO is part of an SLP transaction, it will return the UTXO object
|
|
* with additional SLP information attached. An isValid property will be included.
|
|
* If its value is true, the UTXO is a valid SLP UTXO. If the value is null,
|
|
* then SLPDB has not yet processed that txid and validity has not been confirmed.
|
|
*
|
|
* @apiExample Example usage:
|
|
*
|
|
* (async () => {
|
|
* try {
|
|
* const u = await bchjs.Blockbook.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
|
|
* const utxos = u.utxos
|
|
*
|
|
* const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
|
*
|
|
* console.log(`utxoInfo: ${JSON.stringify(isSLPUtxo,null,2)}`)
|
|
* } catch (error) {
|
|
* console.error(error)
|
|
* }
|
|
* })()
|
|
*
|
|
* // returns
|
|
* {
|
|
* "txid": "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb",
|
|
* "vout": 1,
|
|
* "amount": 0.00000546,
|
|
* "satoshis": 546,
|
|
* "height": 596089,
|
|
* "confirmations": 748,
|
|
* "utxoType": "token",
|
|
* "tokenId": "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7",
|
|
* "tokenTicker": "TOK-CH",
|
|
* "tokenName": "TokyoCash",
|
|
* "tokenDocumentUrl": "",
|
|
* "tokenDocumentHash": "",
|
|
* "decimals": 8,
|
|
* "tokenQty": 2,
|
|
* "isValid": true
|
|
* }
|
|
*/
|
|
|
|
// CT 1/11/20: Refactored to comply with this GitHub Issue:
|
|
// https://github.com/Bitcoin-com/slp-sdk/issues/84
|
|
async tokenUtxoDetails(utxos) {
|
|
try {
|
|
console.log(`tokenUtxoDetails, apiToken: ${_this.apiToken}`)
|
|
|
|
// 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
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = Utils
|