Merge pull request #39 from christroutner/unstable

fix(NFT): Additional NFT functionality, send Group and Child
This commit is contained in:
Chris Troutner
2020-06-05 16:18:43 -07:00
committed by GitHub
4 changed files with 411 additions and 9 deletions
+103 -3
View File
@@ -45,7 +45,7 @@ class TokenType1 {
configObj.documentHash,
0,
configObj.mintBatonVout,
new slpMdm.BN("1")
new slpMdm.BN(configObj.initialQty)
)
return script
@@ -95,7 +95,7 @@ class TokenType1 {
}
}
generateNFTChildOpReturn(configObj) {
generateNFTChildGenesisOpReturn(configObj) {
try {
// TODO: Add input validation.
@@ -117,7 +117,107 @@ class TokenType1 {
return script
} catch (err) {
console.log(`Error in generateNFTChildOpReturn()`)
console.log(`Error in generateNFTChildGenesisOpReturn()`)
throw err
}
}
// Generate the OP_RETURN for sending an NFT Child token.
// Assumes all tokenUtxos have the same tokenId in common. It does not filter
// the input.
generateNFTChildSendOpReturn(tokenUtxos, sendQty) {
try {
// TODO: Add input validation.
const tokenId = tokenUtxos[0].tokenId
// Calculate the total amount of tokens owned by the wallet.
let totalTokens = 0
for (let i = 0; i < tokenUtxos.length; i++)
totalTokens += tokenUtxos[i].tokenQty
const change = totalTokens - sendQty
let script
let outputs = 1
// The normal case, when there is token change to return to sender.
if (change > 0) {
outputs = 2
// Convert to integer string.
const sendStr = Math.floor(sendQty).toString()
const changeStr = Math.floor(change).toString()
// Generate the OP_RETURN as a Buffer.
script = slpMdm.NFT1.Child.send(tokenId, [
new slpMdm.BN(sendStr),
new slpMdm.BN(changeStr)
])
//
// Corner case, when there is no token change to send back.
} else {
// Convert to integer string.
const sendStr = Math.floor(sendQty).toString()
// Generate the OP_RETURN as a Buffer.
script = slpMdm.NFT1.Child.send(tokenId, [new slpMdm.BN(sendStr)])
}
return { script, outputs }
} catch (err) {
console.log(`Error in generateNFTChildSendOpReturn()`)
throw err
}
}
// Generate the OP_RETURN for sending an NFT Group token.
// Assumes all tokenUtxos have the same tokenId in common, it does not filter
// the input.
generateNFTGroupSendOpReturn(tokenUtxos, sendQty) {
try {
// TODO: Add input validation.
const tokenId = tokenUtxos[0].tokenId
// Calculate the total amount of tokens owned by the wallet.
let totalTokens = 0
for (let i = 0; i < tokenUtxos.length; i++)
totalTokens += tokenUtxos[i].tokenQty
const change = totalTokens - sendQty
let script
let outputs = 1
// The normal case, when there is token change to return to sender.
if (change > 0) {
outputs = 2
// Convert to integer string.
const sendStr = Math.floor(sendQty).toString()
const changeStr = Math.floor(change).toString()
// Generate the OP_RETURN as a Buffer.
script = slpMdm.NFT1.Group.send(tokenId, [
new slpMdm.BN(sendStr),
new slpMdm.BN(changeStr)
])
//
// Corner case, when there is no token change to send back.
} else {
// Convert to integer string.
const sendStr = Math.floor(sendQty).toString()
// Generate the OP_RETURN as a Buffer.
script = slpMdm.NFT1.Group.send(tokenId, [new slpMdm.BN(sendStr)])
}
return { script, outputs }
} catch (err) {
console.log(`Error in generateNFTGroupSendOpReturn()`)
throw err
}
}
+3 -3
View File
@@ -1125,7 +1125,7 @@ class Utils {
let slpData = false
try {
slpData = await this.decodeOpReturn(utxo.txid)
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
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.
@@ -1187,7 +1187,7 @@ class Utils {
// 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(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
// Minting Baton
if (utxo.vout === slpData.mintBatonVout) {
@@ -1265,7 +1265,7 @@ class Utils {
// 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)}`)
console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
outAry[i].isValid = isValid[0].valid
}
+139 -2
View File
@@ -44,7 +44,8 @@ describe("#SLP NFT1", () => {
name: "SLP Test Token",
ticker: "SLPTEST",
documentUrl: "https://bchjs.cash",
documentHash: ""
documentHash: "",
initialQty: 5
}
const result = bchjs.SLP.NFT1.newNFTGroupOpReturn(configObj)
@@ -118,10 +119,146 @@ describe("#SLP NFT1", () => {
documentHash: ""
}
const result = bchjs.SLP.NFT1.generateNFTChildOpReturn(configObj)
const result = bchjs.SLP.NFT1.generateNFTChildGenesisOpReturn(configObj)
// console.log(`result: `, result)
assert.equal(Buffer.isBuffer(result), true)
})
})
describe("#generateNFTChildSendOpReturn", () => {
it("should generate send OP_RETURN code for no change", () => {
// Mock UTXO.
const tokenUtxos = [
{
txid:
"81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3",
vout: 1,
value: "546",
height: 638273,
confirmations: 42,
satoshis: 546,
utxoType: "token",
tokenQty: 1,
tokenId:
"81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3",
tokenTicker: "NFTC",
tokenName: "NFT Child",
tokenDocumentUrl: "https://FullStack.cash",
tokenDocumentHash: "",
decimals: 0,
tokenType: 129,
isValid: true
}
]
const result = bchjs.SLP.NFT1.generateNFTChildSendOpReturn(tokenUtxos, 1)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, ["script", "outputs"])
assert.isNumber(result.outputs)
})
it("should generate send OP_RETURN code with change", () => {
// Mock UTXO.
const tokenUtxos = [
{
txid:
"81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3",
vout: 1,
value: "546",
height: 638273,
confirmations: 42,
satoshis: 546,
utxoType: "token",
tokenQty: 4,
tokenId:
"81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3",
tokenTicker: "NFTC",
tokenName: "NFT Child",
tokenDocumentUrl: "https://FullStack.cash",
tokenDocumentHash: "",
decimals: 0,
tokenType: 129,
isValid: true
}
]
const result = bchjs.SLP.NFT1.generateNFTChildSendOpReturn(tokenUtxos, 1)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, ["script", "outputs"])
assert.isNumber(result.outputs)
})
})
describe("#generateNFTGroupSendOpReturn", () => {
it("should generate send OP_RETURN with change", () => {
// Mock UTXO.
const tokenUtxos = [
{
txid:
"35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919",
vout: 1,
value: "546",
height: 638207,
confirmations: 3,
satoshis: 546,
utxoType: "token",
tokenQty: 10,
transactionType: "mint",
tokenId:
"eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981",
tokenType: 129,
tokenTicker: "NFTTT",
tokenName: "NFT Test Token",
tokenDocumentUrl: "https://FullStack.cash",
tokenDocumentHash: "",
decimals: 0,
mintBatonVout: 2,
isValid: true
}
]
const result = bchjs.SLP.NFT1.generateNFTGroupSendOpReturn(tokenUtxos, 1)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, ["script", "outputs"])
assert.isNumber(result.outputs)
})
it("should generate send OP_RETURN with no change", () => {
// Mock UTXO.
const tokenUtxos = [
{
txid:
"35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919",
vout: 1,
value: "546",
height: 638207,
confirmations: 3,
satoshis: 546,
utxoType: "token",
tokenQty: 10,
transactionType: "mint",
tokenId:
"eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981",
tokenType: 129,
tokenTicker: "NFTTT",
tokenName: "NFT Test Token",
tokenDocumentUrl: "https://FullStack.cash",
tokenDocumentHash: "",
decimals: 0,
mintBatonVout: 2,
isValid: true
}
]
const result = bchjs.SLP.NFT1.generateNFTGroupSendOpReturn(tokenUtxos, 10)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, ["script", "outputs"])
assert.isNumber(result.outputs)
})
})
})
+166 -1
View File
@@ -1490,7 +1490,7 @@ describe("#SLP Utils", () => {
assert2.equal(false, data[2])
})
it("should decode a NFT Parent transaction", async () => {
it("should decode a NFT Group Genesis transaction", async () => {
// Define stubbed data.
const slpData = {
tokenType: 129,
@@ -1579,6 +1579,171 @@ describe("#SLP Utils", () => {
assert2.equal(data[2].utxoType, "token")
assert2.equal(data[2].tokenType, 129)
})
it("should decode a NFT Group Mint transaction", async () => {
// Define stubbed data.
const slpData = {
tokenType: 129,
txType: "MINT",
tokenId:
"eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981",
mintBatonVout: 2,
qty: "10"
}
const genesisData = {
tokenType: 129,
txType: "GENESIS",
ticker: "NFTTT",
name: "NFT Test Token",
tokenId:
"eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981",
documentUri: "https://FullStack.cash",
documentHash: "",
decimals: 0,
mintBatonVout: 2,
qty: "1"
}
const stubValid = [
{
txid:
"35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919",
valid: true
}
]
// Mock external dependencies.
// Stub the calls to decodeOpReturn.
sandbox
.stub(slp.Utils, "decodeOpReturn")
.resolves(slpData)
.onCall(1)
.resolves(slpData)
.onCall(2)
.resolves(genesisData)
.onCall(3)
.resolves(slpData)
.onCall(4)
.resolves(genesisData)
// Stub the call to validateTxid
sandbox
.stub(slp.Utils, "validateTxid")
.resolves(stubValid)
.onCall(1)
.resolves(stubValid)
.onCall(2)
.resolves(stubValid)
const utxos = [
{
txid:
"35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919",
vout: 3,
value: "15620",
height: 638207,
confirmations: 3,
satoshis: 15620
},
{
txid:
"35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919",
vout: 2,
value: "546",
height: 638207,
confirmations: 3,
satoshis: 546
},
{
txid:
"35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919",
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)
})
it("should decode a NFT Child Genesis transaction", async () => {
// Define stubbed data.
const slpData = {
tokenType: 129,
txType: "GENESIS",
ticker: "NFTC",
name: "NFT Child",
tokenId:
"81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3",
documentUri: "https://FullStack.cash",
documentHash: "",
decimals: 0,
mintBatonVout: 0,
qty: "1"
}
const stubValid = [
{
txid:
"81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3",
valid: true
}
]
// Mock external dependencies.
// Stub the calls to decodeOpReturn.
sandbox
.stub(slp.Utils, "decodeOpReturn")
.resolves(slpData)
.onCall(1)
.resolves(slpData)
// Stub the call to validateTxid
sandbox.stub(slp.Utils, "validateTxid").resolves(stubValid)
const utxos = [
{
txid:
"81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3",
vout: 2,
value: "16712",
height: 638273,
confirmations: 42,
satoshis: 16712
},
{
txid:
"81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3",
vout: 1,
value: "546",
height: 638273,
confirmations: 42,
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, "token")
assert2.equal(data[1].tokenType, 129)
})
})
describe("#txDetails", () => {