mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-21 16:51:59 -07:00
+126
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
This library wraps the slp-mdm library to generate the OP_RETURN for NFT1 tokens.
|
||||
|
||||
NFT Group tokens (Parents) are generated and minted. They are like amorphous
|
||||
NFTs; like stem cells that haven't specialized yet.
|
||||
|
||||
NFT 'Children' are the 'real' NFTs. They are created by burning an NFT Group
|
||||
(Parent) token.
|
||||
*/
|
||||
|
||||
const Address = require("./address")
|
||||
|
||||
const BigNumber = require("bignumber.js")
|
||||
const slpMdm = require("slp-mdm")
|
||||
|
||||
// const addy = new Address()
|
||||
let addy
|
||||
const TransactionBuilder = require("../transaction-builder")
|
||||
|
||||
class TokenType1 {
|
||||
constructor(config) {
|
||||
this.restURL = config.restURL
|
||||
|
||||
addy = new Address(config)
|
||||
|
||||
// Instantiate the transaction builder.
|
||||
TransactionBuilder.setAddress(addy)
|
||||
}
|
||||
|
||||
// New NFT Group 'Parent'
|
||||
newNFTGroupOpReturn(configObj) {
|
||||
try {
|
||||
// TODO: Add input validation.
|
||||
|
||||
// Prevent error if user fails to add the document hash.
|
||||
if (!configObj.documentHash) configObj.documentHash = ""
|
||||
|
||||
// If mint baton is not specified, then replace it with null.
|
||||
if (!configObj.mintBatonVout) configObj.mintBatonVout = null
|
||||
|
||||
const script = slpMdm.NFT1.Group.genesis(
|
||||
configObj.ticker,
|
||||
configObj.name,
|
||||
configObj.documentUrl,
|
||||
configObj.documentHash,
|
||||
0,
|
||||
configObj.mintBatonVout,
|
||||
new slpMdm.BN("1")
|
||||
)
|
||||
|
||||
return script
|
||||
} catch (err) {
|
||||
console.log(`Error in generateNFTParentOpReturn()`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Mint additional NFT Group 'Parent' tokens.
|
||||
mintNFTGroupOpReturn(tokenUtxos, mintQty, destroyBaton = false) {
|
||||
try {
|
||||
// Throw error if input is not an array.
|
||||
if (!Array.isArray(tokenUtxos))
|
||||
throw new Error(`tokenUtxos must be an array.`)
|
||||
|
||||
// Loop through the tokenUtxos array and find the minting baton.
|
||||
let mintBatonUtxo
|
||||
for (let i = 0; i < tokenUtxos.length; i++) {
|
||||
if (tokenUtxos[i].utxoType === "minting-baton")
|
||||
mintBatonUtxo = tokenUtxos[i]
|
||||
}
|
||||
|
||||
// Throw an error if the minting baton could not be found.
|
||||
if (!mintBatonUtxo)
|
||||
throw new Error(`Minting baton could not be found in tokenUtxos array.`)
|
||||
|
||||
const tokenId = mintBatonUtxo.tokenId
|
||||
|
||||
if (!tokenId)
|
||||
throw new Error(`tokenId property not found in mint-baton UTXO.`)
|
||||
|
||||
// Signal that the baton should be passed or detroyed.
|
||||
let batonVout = 2
|
||||
if (destroyBaton) batonVout = null
|
||||
|
||||
const script = slpMdm.NFT1.Group.mint(
|
||||
tokenId,
|
||||
batonVout,
|
||||
new slpMdm.BN(mintQty)
|
||||
)
|
||||
|
||||
return script
|
||||
} catch (err) {
|
||||
// console.log(`Error in generateMintOpReturn()`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
generateNFTChildOpReturn(configObj) {
|
||||
try {
|
||||
// TODO: Add input validation.
|
||||
|
||||
// Prevent error if user fails to add the document hash.
|
||||
if (!configObj.documentHash) configObj.documentHash = ""
|
||||
|
||||
// If mint baton is not specified, then replace it with null.
|
||||
if (!configObj.mintBatonVout) configObj.mintBatonVout = null
|
||||
|
||||
const script = slpMdm.NFT1.Group.genesis(
|
||||
configObj.ticker,
|
||||
configObj.name,
|
||||
configObj.documentUrl,
|
||||
configObj.documentHash,
|
||||
0,
|
||||
configObj.mintBatonVout,
|
||||
new slpMdm.BN("1")
|
||||
)
|
||||
|
||||
return script
|
||||
} catch (err) {
|
||||
console.log(`Error in generateNFTChildOpReturn()`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TokenType1
|
||||
@@ -12,6 +12,7 @@ const Address = require("./address")
|
||||
const ECPair = require("./ecpair")
|
||||
// const HDNode = require("./hdnode")
|
||||
const TokenType1 = require("./tokentype1")
|
||||
const NFT1 = require("./nft1")
|
||||
const Utils = require("./utils")
|
||||
|
||||
// SLP is a superset of BITBOX
|
||||
@@ -31,6 +32,7 @@ class SLP {
|
||||
this.Address = new Address(tmp)
|
||||
this.ECPair = ECPair
|
||||
this.TokenType1 = new TokenType1(this.restURL)
|
||||
this.NFT1 = new NFT1(this.restURL)
|
||||
this.Utils = new Utils(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
//const bchjs = new BCHJS()
|
||||
|
||||
const Address = require("./address")
|
||||
const Script = require("../script")
|
||||
|
||||
const BigNumber = require("bignumber.js")
|
||||
const slpMdm = require("slp-mdm")
|
||||
@@ -15,8 +14,6 @@ class TokenType1 {
|
||||
constructor(config) {
|
||||
this.restURL = config.restURL
|
||||
|
||||
this.Script = new Script()
|
||||
|
||||
addy = new Address(config)
|
||||
|
||||
// Instantiate the transaction builder.
|
||||
@@ -361,61 +358,6 @@ class TokenType1 {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Parent NFT
|
||||
generateNFTGenesisOpReturn(configObj) {
|
||||
try {
|
||||
// TODO: Add input validation.
|
||||
|
||||
// Prevent error if user fails to add the document hash.
|
||||
if (!configObj.documentHash) configObj.documentHash = ""
|
||||
|
||||
// If mint baton is not specified, then replace it with null.
|
||||
if (!configObj.mintBatonVout) configObj.mintBatonVout = null
|
||||
|
||||
const script = slpMdm.NFT1.Group.genesis(
|
||||
configObj.ticker,
|
||||
configObj.name,
|
||||
configObj.documentUrl,
|
||||
configObj.documentHash,
|
||||
0,
|
||||
configObj.mintBatonVout,
|
||||
new slpMdm.BN("1")
|
||||
)
|
||||
|
||||
return script
|
||||
} catch (err) {
|
||||
console.log(`Error in generateNFTGenesisOpReturn()`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
generateNFTChildOpReturn(configObj) {
|
||||
try {
|
||||
// TODO: Add input validation.
|
||||
|
||||
// Prevent error if user fails to add the document hash.
|
||||
if (!configObj.documentHash) configObj.documentHash = ""
|
||||
|
||||
// If mint baton is not specified, then replace it with null.
|
||||
if (!configObj.mintBatonVout) configObj.mintBatonVout = null
|
||||
|
||||
const script = slpMdm.NFT1.Group.genesis(
|
||||
configObj.ticker,
|
||||
configObj.name,
|
||||
configObj.documentUrl,
|
||||
configObj.documentHash,
|
||||
0,
|
||||
configObj.mintBatonVout,
|
||||
new slpMdm.BN("1")
|
||||
)
|
||||
|
||||
return script
|
||||
} catch (err) {
|
||||
console.log(`Error in generateNFTChildOpReturn()`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TokenType1
|
||||
|
||||
@@ -1167,6 +1167,7 @@ class Utils {
|
||||
utxo.tokenDocumentUrl = slpData.documentUri
|
||||
utxo.tokenDocumentHash = slpData.documentHash
|
||||
utxo.decimals = slpData.decimals
|
||||
utxo.tokenType = slpData.tokenType
|
||||
|
||||
// something
|
||||
outAry[i] = utxo
|
||||
@@ -1201,6 +1202,7 @@ class Utils {
|
||||
// Hydrate the UTXO object with information about the SLP token.
|
||||
utxo.transactionType = "mint"
|
||||
utxo.tokenId = slpData.tokenId
|
||||
utxo.tokenType = slpData.tokenType
|
||||
|
||||
utxo.tokenTicker = genesisData.ticker
|
||||
utxo.tokenName = genesisData.name
|
||||
@@ -1249,6 +1251,7 @@ class Utils {
|
||||
utxo.tokenDocumentUrl = genesisData.documentUri
|
||||
utxo.tokenDocumentHash = genesisData.documentHash
|
||||
utxo.decimals = genesisData.decimals
|
||||
utxo.tokenType = slpData.tokenType
|
||||
|
||||
// Calculate the real token quantity.
|
||||
utxo.tokenQty = tokenQty / Math.pow(10, genesisData.decimals)
|
||||
|
||||
@@ -1031,6 +1031,82 @@ const mockInvalidSlpSend = {
|
||||
blocktime: 1535577007
|
||||
}
|
||||
|
||||
const txDetailsSLPNftGenesis = {
|
||||
txid: "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4",
|
||||
hash: "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4",
|
||||
version: 2,
|
||||
size: 344,
|
||||
locktime: 0,
|
||||
vin: [
|
||||
{
|
||||
txid: "4041bc842554b796de1dbb625bbb6994379bc5132a47522677bef7323f3e5051",
|
||||
vout: 2,
|
||||
scriptSig: {
|
||||
asm:
|
||||
"3045022100f8d678e7cd6a2fc317fe4c0c8b633f5941aa40bceb6d7472559955908869544b02206d82e45a6658bb9b5ad39734aa93832b1de2d3c9650e1b59c2ee2f15aa26f3bb[ALL|FORKID] 029547345d63f86ea89ea92ea4ed26386c4493ff60c78d871cc7b38f517f3fd72c",
|
||||
hex:
|
||||
"483045022100f8d678e7cd6a2fc317fe4c0c8b633f5941aa40bceb6d7472559955908869544b02206d82e45a6658bb9b5ad39734aa93832b1de2d3c9650e1b59c2ee2f15aa26f3bb4121029547345d63f86ea89ea92ea4ed26386c4493ff60c78d871cc7b38f517f3fd72c"
|
||||
},
|
||||
sequence: 4294967295
|
||||
}
|
||||
],
|
||||
vout: [
|
||||
{
|
||||
value: 0,
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
"OP_RETURN 5262419 -1 47454e45534953 4e46545454 4e4654205465737420546f6b656e 68747470733a2f2f46756c6c537461636b2e63617368 0 0 2 0000000000000001",
|
||||
hex:
|
||||
"6a04534c500001810747454e45534953054e465454540e4e4654205465737420546f6b656e1668747470733a2f2f46756c6c537461636b2e636173684c0001000102080000000000000001",
|
||||
type: "nulldata"
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.00000546,
|
||||
n: 1,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
"OP_DUP OP_HASH160 6011206cd60db8b634f85edf46da22a6d1351e54 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
hex: "76a9146011206cd60db8b634f85edf46da22a6d1351e5488ac",
|
||||
reqSigs: 1,
|
||||
type: "pubkeyhash",
|
||||
addresses: ["bitcoincash:qpspzgrv6cxm3d35lp0d73k6y2ndzdg72s2304ttr8"]
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.00000546,
|
||||
n: 2,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
"OP_DUP OP_HASH160 6011206cd60db8b634f85edf46da22a6d1351e54 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
hex: "76a9146011206cd60db8b634f85edf46da22a6d1351e5488ac",
|
||||
reqSigs: 1,
|
||||
type: "pubkeyhash",
|
||||
addresses: ["bitcoincash:qpspzgrv6cxm3d35lp0d73k6y2ndzdg72s2304ttr8"]
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.0001562,
|
||||
n: 3,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
"OP_DUP OP_HASH160 6011206cd60db8b634f85edf46da22a6d1351e54 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
hex: "76a9146011206cd60db8b634f85edf46da22a6d1351e5488ac",
|
||||
reqSigs: 1,
|
||||
type: "pubkeyhash",
|
||||
addresses: ["bitcoincash:qpspzgrv6cxm3d35lp0d73k6y2ndzdg72s2304ttr8"]
|
||||
}
|
||||
}
|
||||
],
|
||||
hex:
|
||||
"020000000151503e3f32f7be772652472a13c59b379469bb5b62bb1dde96b7542584bc4140020000006b483045022100f8d678e7cd6a2fc317fe4c0c8b633f5941aa40bceb6d7472559955908869544b02206d82e45a6658bb9b5ad39734aa93832b1de2d3c9650e1b59c2ee2f15aa26f3bb4121029547345d63f86ea89ea92ea4ed26386c4493ff60c78d871cc7b38f517f3fd72cffffffff0400000000000000004b6a04534c500001810747454e45534953054e465454540e4e4654205465737420546f6b656e1668747470733a2f2f46756c6c537461636b2e636173684c000100010208000000000000000122020000000000001976a9146011206cd60db8b634f85edf46da22a6d1351e5488ac22020000000000001976a9146011206cd60db8b634f85edf46da22a6d1351e5488ac043d0000000000001976a9146011206cd60db8b634f85edf46da22a6d1351e5488ac00000000",
|
||||
blockhash: "000000000000000002405a44302888bc29bf0e9f4d99b97303038bcc83c15e33",
|
||||
confirmations: 2,
|
||||
time: 1591329189,
|
||||
blocktime: 1591329189
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mockList,
|
||||
mockToken,
|
||||
@@ -1051,6 +1127,7 @@ module.exports = {
|
||||
txDetailsSLPSend,
|
||||
txDetailsSLPGenesisNoBaton,
|
||||
txDetailsSLPSendAlt,
|
||||
txDetailsSLPNftGenesis,
|
||||
mockTxDetails,
|
||||
mockDualValidation,
|
||||
mockDualOpData,
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
Unit tests for the TokenType1 library.
|
||||
*/
|
||||
|
||||
const assert = require("chai").assert
|
||||
const nock = require("nock") // http call mocking
|
||||
const sinon = require("sinon")
|
||||
// const axios = require("axios")
|
||||
|
||||
// Default to unit tests unless some other value for TEST is passed.
|
||||
if (!process.env.TEST) process.env.TEST = "unit"
|
||||
// const SERVER = bchjs.restURL
|
||||
|
||||
const BCHJS = require("../../src/bch-js")
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
// Mock data used for unit tests
|
||||
// const mockData = require("./fixtures/slp/mock-utils")
|
||||
|
||||
// Default to unit tests unless some other value for TEST is passed.
|
||||
if (!process.env.TEST) process.env.TEST = "unit"
|
||||
|
||||
describe("#SLP NFT1", () => {
|
||||
let sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
// Activate nock if it's inactive.
|
||||
if (!nock.isActive()) nock.activate()
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up HTTP mocks.
|
||||
nock.cleanAll() // clear interceptor list.
|
||||
nock.restore()
|
||||
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe("#newNFTGroupOpReturn", () => {
|
||||
it("should generate new NFT Group OP_RETURN code", () => {
|
||||
const configObj = {
|
||||
name: "SLP Test Token",
|
||||
ticker: "SLPTEST",
|
||||
documentUrl: "https://bchjs.cash",
|
||||
documentHash: ""
|
||||
}
|
||||
|
||||
const result = bchjs.SLP.NFT1.newNFTGroupOpReturn(configObj)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.equal(Buffer.isBuffer(result), true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#mintNFTGroupOpReturn", () => {
|
||||
it("should generate NFT Group Mint OP_RETURN code", () => {
|
||||
const tokenUtxoData = [
|
||||
{
|
||||
txid:
|
||||
"3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76",
|
||||
vout: 2,
|
||||
value: "546",
|
||||
height: 638207,
|
||||
confirmations: 63,
|
||||
satoshis: 546,
|
||||
utxoType: "minting-baton",
|
||||
transactionType: "mint",
|
||||
tokenId:
|
||||
"680967b3f6fe080dbca8dbd370665bd29742e3490db24e2f28d08b424511807e",
|
||||
tokenType: 129,
|
||||
tokenTicker: "NFTP",
|
||||
tokenName: "NFT Parent",
|
||||
tokenDocumentUrl: "FullStack.cash",
|
||||
tokenDocumentHash: "",
|
||||
decimals: 0,
|
||||
mintBatonVout: 2,
|
||||
isValid: true
|
||||
},
|
||||
{
|
||||
txid:
|
||||
"3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76",
|
||||
vout: 1,
|
||||
value: "546",
|
||||
height: 638207,
|
||||
confirmations: 63,
|
||||
satoshis: 546,
|
||||
utxoType: "token",
|
||||
tokenQty: 10,
|
||||
transactionType: "mint",
|
||||
tokenId:
|
||||
"680967b3f6fe080dbca8dbd370665bd29742e3490db24e2f28d08b424511807e",
|
||||
tokenType: 129,
|
||||
tokenTicker: "NFTP",
|
||||
tokenName: "NFT Parent",
|
||||
tokenDocumentUrl: "FullStack.cash",
|
||||
tokenDocumentHash: "",
|
||||
decimals: 0,
|
||||
mintBatonVout: 2,
|
||||
isValid: true
|
||||
}
|
||||
]
|
||||
|
||||
const result = bchjs.SLP.NFT1.mintNFTGroupOpReturn(tokenUtxoData, 10)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.equal(Buffer.isBuffer(result), true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#generateNFTChildOpReturn", () => {
|
||||
it("should generate NFT Genesis OP_RETURN code", () => {
|
||||
const configObj = {
|
||||
name: "SLP Test Token",
|
||||
ticker: "SLPTEST",
|
||||
documentUrl: "https://bchjs.cash",
|
||||
documentHash: ""
|
||||
}
|
||||
|
||||
const result = bchjs.SLP.NFT1.generateNFTChildOpReturn(configObj)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.equal(Buffer.isBuffer(result), true)
|
||||
})
|
||||
})
|
||||
})
|
||||
+18
-186
@@ -38,127 +38,8 @@ describe("#SLP TokenType1", () => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
// CT 5/31/2020 This is old code for the older version of generateSendOpReturn()
|
||||
// This code can be delete in a couple weeks once things are stable.
|
||||
//
|
||||
// describe("#generateSendOpReturn", () => {
|
||||
// it("should generate send OP_RETURN code", async () => {
|
||||
// // Mock UTXO.
|
||||
// const tokenUtxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "a8eb788b8ddda6faea00e6e2756624b8feb97655363d0400dd66839ea619d36e",
|
||||
// vout: 2,
|
||||
// value: "546",
|
||||
// confirmations: 0,
|
||||
// satoshis: 546,
|
||||
// utxoType: "token",
|
||||
// transactionType: "send",
|
||||
// tokenId:
|
||||
// "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7",
|
||||
// tokenTicker: "TOK-CH",
|
||||
// tokenName: "TokyoCash",
|
||||
// tokenDocumentUrl: "",
|
||||
// tokenDocumentHash: "",
|
||||
// decimals: 8,
|
||||
// tokenQty: 7
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const result = await bchjs.SLP.TokenType1.generateSendOpReturn(
|
||||
// tokenUtxos,
|
||||
// 1
|
||||
// )
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.hasAllKeys(result, ["script", "outputs"])
|
||||
// assert.isNumber(result.outputs)
|
||||
// })
|
||||
//
|
||||
// it("should handle problematic configuration", async () => {
|
||||
// // Mock UTXO.
|
||||
// const tokenUtxos = [
|
||||
// {
|
||||
// txid:
|
||||
// "0bd6b874246202b4cbc2f501419a5ce6f9b01e8ba9521298afd15c7e8eac5951",
|
||||
// vout: 2,
|
||||
// value: "546",
|
||||
// confirmations: 0,
|
||||
// satoshis: 546,
|
||||
// utxoType: "token",
|
||||
// transactionType: "send",
|
||||
// tokenId:
|
||||
// "155784a206873c98acc09e8dabcccf6abf13c4c14d8662190534138a16bb93ce",
|
||||
// tokenTicker: "PSF",
|
||||
// tokenName: "PSF Testnet Token",
|
||||
// tokenDocumentUrl: "",
|
||||
// tokenDocumentHash: "",
|
||||
// decimals: 8,
|
||||
// tokenQty: 18004.71169917
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// const result = await bchjs.SLP.TokenType1.generateSendOpReturn(
|
||||
// tokenUtxos,
|
||||
// 5000
|
||||
// )
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
// console.log(`result.script: `, result.script)
|
||||
// console.log(`result.script: `, result.script.toString("hex"))
|
||||
//
|
||||
// // This transaction failed due to a floating point error. This is expressed
|
||||
// // by the script[6] being length 2 (incorrect) instead of 8 (correct).
|
||||
// assert.equal(result.script[5].length, 8)
|
||||
// assert.equal(result.script[6].length, 8)
|
||||
// })
|
||||
//
|
||||
// it("should generate good OP_RETURN for problematic TX", () => {
|
||||
// const utxo1 = {
|
||||
// txid:
|
||||
// "35287aa2aa7d3f1954fbbcb8a748d8773359b4f2771a851959a4a6116bcb552c",
|
||||
// vout: 1,
|
||||
// amount: 0.00000546,
|
||||
// satoshis: 546,
|
||||
// legacyAddress: "1MuLZ9LzLSTKioBHoPzeSfFANTEN2CZhuS",
|
||||
// cashAddress: "bitcoincash:qrj5sala6z53v559q54mekexru2xe34yrquhmpy5kx",
|
||||
// tokenId:
|
||||
// "c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479",
|
||||
// decimals: 2,
|
||||
// tokenQty: 1371.6
|
||||
// }
|
||||
//
|
||||
// const utxo2 = {
|
||||
// txid:
|
||||
// "cb652ce62babc81936f0f1e1d5b80102cb44de99dece83f4bbd34cfaeef744b0",
|
||||
// vout: 2,
|
||||
// amount: 0.00000546,
|
||||
// satoshis: 546,
|
||||
// legacyAddress: "1LzjkvDVB16k6kcSz8hZodW9KPP2zxwmGh",
|
||||
// cashAddress: "bitcoincash:qrd4tj48lpf0wv36qt5mkd6c3n4h5xcfgqqw77s63d",
|
||||
// tokenId:
|
||||
// "c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479",
|
||||
// decimals: 2,
|
||||
// tokenQty: 3250.75
|
||||
// }
|
||||
//
|
||||
// const utxos = [utxo1, utxo2]
|
||||
//
|
||||
// const { script } = bchjs.SLP.TokenType1.generateSendOpReturn(utxos, 1400)
|
||||
//
|
||||
// // console.log(`script: `, script)
|
||||
// // console.log(`outputs: `, outputs)
|
||||
//
|
||||
// // console.log(`script[6]: ${script[6].toString("hex")}`)
|
||||
// // console.log(`script[6].length: ${script[6].length}`)
|
||||
//
|
||||
// // This transaction failed due to a floating point error. This is expressed
|
||||
// // by the script[6] being length 2 (incorrect) instead of 8 (correct).
|
||||
// assert.equal(script[6].length, 8)
|
||||
// })
|
||||
// })
|
||||
|
||||
describe("#generateSendOpReturn", () => {
|
||||
it("should generate send OP_RETURN code", async () => {
|
||||
it("should generate send OP_RETURN code", () => {
|
||||
// Mock UTXO.
|
||||
const tokenUtxos = [
|
||||
{
|
||||
@@ -181,10 +62,7 @@ describe("#SLP TokenType1", () => {
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.TokenType1.generateSendOpReturn(
|
||||
tokenUtxos,
|
||||
1
|
||||
)
|
||||
const result = bchjs.SLP.TokenType1.generateSendOpReturn(tokenUtxos, 1)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result, ["script", "outputs"])
|
||||
@@ -193,7 +71,7 @@ describe("#SLP TokenType1", () => {
|
||||
})
|
||||
|
||||
describe("#generateBurnOpReturn", () => {
|
||||
it("should generate burn OP_RETURN code", async () => {
|
||||
it("should generate burn OP_RETURN code", () => {
|
||||
// Mock UTXO.
|
||||
const tokenUtxos = [
|
||||
{
|
||||
@@ -216,10 +94,7 @@ describe("#SLP TokenType1", () => {
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.TokenType1.generateBurnOpReturn(
|
||||
tokenUtxos,
|
||||
1
|
||||
)
|
||||
const result = bchjs.SLP.TokenType1.generateBurnOpReturn(tokenUtxos, 1)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
@@ -228,7 +103,7 @@ describe("#SLP TokenType1", () => {
|
||||
})
|
||||
|
||||
describe("#generateGenesisOpReturn", () => {
|
||||
it("should generate genesis OP_RETURN code", async () => {
|
||||
it("should generate genesis OP_RETURN code", () => {
|
||||
const configObj = {
|
||||
name: "SLP Test Token",
|
||||
ticker: "SLPTEST",
|
||||
@@ -238,15 +113,13 @@ describe("#SLP TokenType1", () => {
|
||||
initialQty: 10
|
||||
}
|
||||
|
||||
const result = await bchjs.SLP.TokenType1.generateGenesisOpReturn(
|
||||
configObj
|
||||
)
|
||||
const result = bchjs.SLP.TokenType1.generateGenesisOpReturn(configObj)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.equal(Buffer.isBuffer(result), true)
|
||||
})
|
||||
|
||||
it("should work if user does not specify doc hash", async () => {
|
||||
it("should work if user does not specify doc hash", () => {
|
||||
const configObj = {
|
||||
name: "SLP Test Token",
|
||||
ticker: "SLPTEST",
|
||||
@@ -255,9 +128,7 @@ describe("#SLP TokenType1", () => {
|
||||
initialQty: 10
|
||||
}
|
||||
|
||||
const result = await bchjs.SLP.TokenType1.generateGenesisOpReturn(
|
||||
configObj
|
||||
)
|
||||
const result = bchjs.SLP.TokenType1.generateGenesisOpReturn(configObj)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.equal(Buffer.isBuffer(result), true)
|
||||
@@ -265,9 +136,9 @@ describe("#SLP TokenType1", () => {
|
||||
})
|
||||
|
||||
describe("#generateMintOpReturn", () => {
|
||||
it("should throw error if tokenUtxos is not an array.", async () => {
|
||||
it("should throw error if tokenUtxos is not an array.", () => {
|
||||
try {
|
||||
await bchjs.SLP.TokenType1.generateMintOpReturn({}, 100)
|
||||
bchjs.SLP.TokenType1.generateMintOpReturn({}, 100)
|
||||
|
||||
assert.equal(true, false, "Unexpected result.")
|
||||
} catch (err) {
|
||||
@@ -279,7 +150,7 @@ describe("#SLP TokenType1", () => {
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw error if minting baton is not in UTXOs.", async () => {
|
||||
it("should throw error if minting baton is not in UTXOs.", () => {
|
||||
try {
|
||||
const utxos = [
|
||||
{
|
||||
@@ -303,7 +174,7 @@ describe("#SLP TokenType1", () => {
|
||||
}
|
||||
]
|
||||
|
||||
await bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100)
|
||||
bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100)
|
||||
|
||||
assert.equal(true, false, "Unexpected result.")
|
||||
} catch (err) {
|
||||
@@ -315,7 +186,7 @@ describe("#SLP TokenType1", () => {
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw error if tokenId is not included in minting-baton UTXO.", async () => {
|
||||
it("should throw error if tokenId is not included in minting-baton UTXO.", () => {
|
||||
try {
|
||||
const utxos = [
|
||||
{
|
||||
@@ -336,7 +207,7 @@ describe("#SLP TokenType1", () => {
|
||||
}
|
||||
]
|
||||
|
||||
await bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100)
|
||||
bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100)
|
||||
|
||||
assert.equal(true, false, "Unexpected result.")
|
||||
} catch (err) {
|
||||
@@ -348,7 +219,7 @@ describe("#SLP TokenType1", () => {
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw error if decimals is not included in minting-baton UTXO.", async () => {
|
||||
it("should throw error if decimals is not included in minting-baton UTXO.", () => {
|
||||
try {
|
||||
const utxos = [
|
||||
{
|
||||
@@ -370,7 +241,7 @@ describe("#SLP TokenType1", () => {
|
||||
}
|
||||
]
|
||||
|
||||
await bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100)
|
||||
bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100)
|
||||
|
||||
assert.equal(true, false, "Unexpected result.")
|
||||
} catch (err) {
|
||||
@@ -382,7 +253,7 @@ describe("#SLP TokenType1", () => {
|
||||
}
|
||||
})
|
||||
|
||||
it("should generate genesis OP_RETURN code", async () => {
|
||||
it("should generate genesis OP_RETURN code", () => {
|
||||
tokenUtxo = [
|
||||
{
|
||||
txid:
|
||||
@@ -404,46 +275,7 @@ describe("#SLP TokenType1", () => {
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.TokenType1.generateMintOpReturn(
|
||||
tokenUtxo,
|
||||
100
|
||||
)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.equal(Buffer.isBuffer(result), true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#generateNFTOpReturn", () => {
|
||||
it("should generate NFT Genesis OP_RETURN code", async () => {
|
||||
const configObj = {
|
||||
name: "SLP Test Token",
|
||||
ticker: "SLPTEST",
|
||||
documentUrl: "https://bchjs.cash",
|
||||
documentHash: ""
|
||||
}
|
||||
|
||||
const result = await bchjs.SLP.TokenType1.generateNFTGenesisOpReturn(
|
||||
configObj
|
||||
)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.equal(Buffer.isBuffer(result), true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#generateNFTChildOpReturn", () => {
|
||||
it("should generate NFT Genesis OP_RETURN code", async () => {
|
||||
const configObj = {
|
||||
name: "SLP Test Token",
|
||||
ticker: "SLPTEST",
|
||||
documentUrl: "https://bchjs.cash",
|
||||
documentHash: ""
|
||||
}
|
||||
|
||||
const result = await bchjs.SLP.TokenType1.generateNFTChildOpReturn(
|
||||
configObj
|
||||
)
|
||||
const result = bchjs.SLP.TokenType1.generateMintOpReturn(tokenUtxo, 100)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.equal(Buffer.isBuffer(result), true)
|
||||
|
||||
+146
-171
@@ -354,177 +354,6 @@ describe("#SLP Utils", () => {
|
||||
})
|
||||
})
|
||||
|
||||
// describe("#decodeOpReturn", () => {
|
||||
// it("should throw an error for a non-string input", async () => {
|
||||
// try {
|
||||
// const txid = 53423 // Not a string.
|
||||
//
|
||||
// await slp.Utils.decodeOpReturn(txid)
|
||||
//
|
||||
// assert2.equal(true, false, "Unexpected result.")
|
||||
// } catch (err) {
|
||||
// //console.log(`err: ${util.inspect(err)}`)
|
||||
// assert2.include(err.message, `txid string must be included`)
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it("should throw an error for non-SLP transaction", async () => {
|
||||
// try {
|
||||
// // Mock the call to the REST API
|
||||
// if (process.env.TEST === "unit") {
|
||||
// sandbox
|
||||
// .stub(axios, "get")
|
||||
// .resolves({ data: mockData.nonSLPTxDetailsWithoutOpReturn })
|
||||
// }
|
||||
//
|
||||
// const txid =
|
||||
// "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec"
|
||||
//
|
||||
// await slp.Utils.decodeOpReturn(txid)
|
||||
//
|
||||
// assert2.equal(true, false, "Unexpected result.")
|
||||
// } catch (err) {
|
||||
// assert2.include(err.message, `Not an OP_RETURN`)
|
||||
// //console.log(`err: ${util.inspect(err)}`)
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it("should throw an error for non-SLP transaction with OP_RETURN", async () => {
|
||||
// try {
|
||||
// // Mock the call to the REST API
|
||||
// if (process.env.TEST === "unit") {
|
||||
// sandbox
|
||||
// .stub(axios, "get")
|
||||
// .resolves({ data: mockData.nonSLPTxDetailsWithOpReturn })
|
||||
// }
|
||||
//
|
||||
// const txid =
|
||||
// "2ff74c48a5d657cf45f699601990bffbbe7a2a516d5480674cbf6c6a4497908f"
|
||||
//
|
||||
// await slp.Utils.decodeOpReturn(txid)
|
||||
//
|
||||
// assert2.equal(true, false, "Unexpected result.")
|
||||
// } catch (err) {
|
||||
// assert2.include(err.message, `Not a SLP OP_RETURN`)
|
||||
// //console.log(`err: ${util.inspect(err)}`)
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it("should decode a genesis transaction", async () => {
|
||||
// // Mock the call to the REST API
|
||||
// if (process.env.TEST === "unit") {
|
||||
// sandbox
|
||||
// .stub(axios, "get")
|
||||
// .resolves({ data: mockData.txDetailsSLPGenesis })
|
||||
// }
|
||||
//
|
||||
// const txid =
|
||||
// "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90"
|
||||
//
|
||||
// const data = await slp.Utils.decodeOpReturn(txid)
|
||||
// //console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// assert2.hasAnyKeys(data, [
|
||||
// "tokenType",
|
||||
// "transactionType",
|
||||
// "ticker",
|
||||
// "name",
|
||||
// "documentUrl",
|
||||
// "documentHash",
|
||||
// "decimals",
|
||||
// "mintBatonVout",
|
||||
// "initialQty",
|
||||
// "tokensSentTo",
|
||||
// "batonHolder"
|
||||
// ])
|
||||
// })
|
||||
//
|
||||
// it("should decode a mint transaction", async () => {
|
||||
// // Mock the call to the REST API
|
||||
// if (process.env.TEST === "unit")
|
||||
// sandbox.stub(axios, "get").resolves({ data: mockData.txDetailsSLPMint })
|
||||
//
|
||||
// const txid =
|
||||
// "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4"
|
||||
//
|
||||
// const data = await slp.Utils.decodeOpReturn(txid)
|
||||
// //console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// assert2.hasAnyKeys(data, [
|
||||
// "transactionType",
|
||||
// "tokenType",
|
||||
// "tokenId",
|
||||
// "mintBatonVout",
|
||||
// "batonStillExists",
|
||||
// "quantity",
|
||||
// "tokensSentTo",
|
||||
// "batonHolder"
|
||||
// ])
|
||||
// })
|
||||
//
|
||||
// it("should decode a send transaction", async () => {
|
||||
// // Mock the call to the REST API
|
||||
// if (process.env.TEST === "unit")
|
||||
// sandbox.stub(axios, "get").resolves({ data: mockData.txDetailsSLPSend })
|
||||
//
|
||||
// const txid =
|
||||
// "4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa"
|
||||
//
|
||||
// const data = await slp.Utils.decodeOpReturn(txid)
|
||||
// //console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// assert2.hasAnyKeys(data, [
|
||||
// "transactionType",
|
||||
// "tokenType",
|
||||
// "tokenId",
|
||||
// "spendData"
|
||||
// ])
|
||||
// assert2.isArray(data.spendData)
|
||||
// assert2.hasAnyKeys(data.spendData[0], ["quantity", "sentTo", "vout"])
|
||||
// })
|
||||
//
|
||||
// it("should properly decode a Genesis transaction with no minting baton", async () => {
|
||||
// // Mock the call to the REST API.
|
||||
// if (process.env.TEST === "unit") {
|
||||
// sandbox
|
||||
// .stub(axios, "get")
|
||||
// .resolves({ data: mockData.txDetailsSLPGenesisNoBaton })
|
||||
// }
|
||||
//
|
||||
// const txid =
|
||||
// "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7"
|
||||
//
|
||||
// const data = await slp.Utils.decodeOpReturn(txid)
|
||||
// //console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// assert2.include(data.batonHolder, "NEVER_CREATED")
|
||||
// })
|
||||
//
|
||||
// it("should decode a send transaction with alternate encoding", async () => {
|
||||
// // Mock the call to rest.bitcoin.com
|
||||
// if (process.env.TEST === "unit") {
|
||||
// sandbox
|
||||
// .stub(axios, "get")
|
||||
// .resolves({ data: mockData.txDetailsSLPSendAlt })
|
||||
// }
|
||||
//
|
||||
// const txid =
|
||||
// "d94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae"
|
||||
//
|
||||
// const data = await slp.Utils.decodeOpReturn(txid)
|
||||
// //console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// assert2.hasAnyKeys(data, [
|
||||
// "transactionType",
|
||||
// "tokenType",
|
||||
// "tokenId",
|
||||
// "spendData"
|
||||
// ])
|
||||
// assert2.isArray(data.spendData)
|
||||
// assert2.hasAnyKeys(data.spendData[0], ["quantity", "sentTo", "vout"])
|
||||
// })
|
||||
// })
|
||||
|
||||
describe("#decodeOpReturn", () => {
|
||||
it("should throw an error for a non-string input", async () => {
|
||||
try {
|
||||
@@ -704,6 +533,62 @@ describe("#SLP Utils", () => {
|
||||
assert2.include(err.message, "amount string size not 8 bytes")
|
||||
}
|
||||
})
|
||||
|
||||
it("should decode a NFT Parent transaction", async () => {
|
||||
// Mock the call to the REST API.
|
||||
sandbox
|
||||
.stub(axios, "get")
|
||||
.resolves({ data: mockData.txDetailsSLPNftGenesis })
|
||||
|
||||
const txid =
|
||||
"4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4"
|
||||
|
||||
const data = await slp.Utils.decodeOpReturn(txid)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert2.property(data, "tokenType")
|
||||
assert2.property(data, "txType")
|
||||
assert2.property(data, "ticker")
|
||||
assert2.property(data, "name")
|
||||
assert2.property(data, "tokenId")
|
||||
assert2.property(data, "documentUri")
|
||||
assert2.property(data, "documentHash")
|
||||
assert2.property(data, "decimals")
|
||||
assert2.property(data, "mintBatonVout")
|
||||
assert2.property(data, "qty")
|
||||
|
||||
assert2.equal(data.tokenType, 129)
|
||||
assert2.equal(data.mintBatonVout, 2)
|
||||
assert2.equal(data.qty, 1)
|
||||
})
|
||||
|
||||
// it("should decode a NFT Child transaction", async () => {
|
||||
// // Mock the call to the REST API.
|
||||
// // sandbox
|
||||
// // .stub(axios, "get")
|
||||
// // .resolves({ data: mockData.txDetailsSLPNftGenesis })
|
||||
//
|
||||
// const txid =
|
||||
// "3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76"
|
||||
//
|
||||
// const data = await slp.Utils.decodeOpReturn(txid)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// // assert2.property(data, "tokenType")
|
||||
// // assert2.property(data, "txType")
|
||||
// // assert2.property(data, "ticker")
|
||||
// // assert2.property(data, "name")
|
||||
// // assert2.property(data, "tokenId")
|
||||
// // assert2.property(data, "documentUri")
|
||||
// // assert2.property(data, "documentHash")
|
||||
// // assert2.property(data, "decimals")
|
||||
// // assert2.property(data, "mintBatonVout")
|
||||
// // assert2.property(data, "qty")
|
||||
// //
|
||||
// // assert2.equal(data.tokenType, 129)
|
||||
// // assert2.equal(data.mintBatonVout, 2)
|
||||
// // assert2.equal(data.qty, 1)
|
||||
// })
|
||||
})
|
||||
|
||||
describe("#isTokenUtxo", () => {
|
||||
@@ -1604,6 +1489,96 @@ describe("#SLP Utils", () => {
|
||||
assert2.equal(data[1].utxoType, "minting-baton")
|
||||
assert2.equal(false, data[2])
|
||||
})
|
||||
|
||||
it("should decode a NFT Parent transaction", async () => {
|
||||
// Define stubbed data.
|
||||
const slpData = {
|
||||
tokenType: 129,
|
||||
txType: "GENESIS",
|
||||
ticker: "NFTTT",
|
||||
name: "NFT Test Token",
|
||||
tokenId:
|
||||
"4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4",
|
||||
documentUri: "https://FullStack.cash",
|
||||
documentHash: "",
|
||||
decimals: 0,
|
||||
mintBatonVout: 2,
|
||||
qty: "1"
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
"4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4",
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
.stub(slp.Utils, "decodeOpReturn")
|
||||
.resolves(slpData)
|
||||
.onCall(1)
|
||||
.resolves(slpData)
|
||||
.onCall(2)
|
||||
.resolves(slpData)
|
||||
.onCall(3)
|
||||
.resolves(slpData)
|
||||
.onCall(4)
|
||||
.resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox
|
||||
.stub(slp.Utils, "validateTxid")
|
||||
.resolves(stubValid)
|
||||
.onCall(1)
|
||||
.resolves(stubValid)
|
||||
.onCall(2)
|
||||
.resolves(stubValid)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
"4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4",
|
||||
vout: 3,
|
||||
value: "15620",
|
||||
height: 638207,
|
||||
confirmations: 3,
|
||||
satoshis: 15620
|
||||
},
|
||||
{
|
||||
txid:
|
||||
"4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4",
|
||||
vout: 2,
|
||||
value: "546",
|
||||
height: 638207,
|
||||
confirmations: 3,
|
||||
satoshis: 546
|
||||
},
|
||||
{
|
||||
txid:
|
||||
"4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4",
|
||||
vout: 1,
|
||||
value: "546",
|
||||
height: 638207,
|
||||
confirmations: 3,
|
||||
satoshis: 546
|
||||
}
|
||||
]
|
||||
|
||||
const data = await slp.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert2.isArray(data)
|
||||
assert2.equal(data[0], false)
|
||||
|
||||
assert2.equal(data[1].utxoType, "minting-baton")
|
||||
assert2.equal(data[1].tokenType, 129)
|
||||
|
||||
assert2.equal(data[2].utxoType, "token")
|
||||
assert2.equal(data[2].tokenType, 129)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#txDetails", () => {
|
||||
|
||||
Reference in New Issue
Block a user