diff --git a/examples/README.md b/examples/README.md index a2915da..61bd121 100644 --- a/examples/README.md +++ b/examples/README.md @@ -31,6 +31,8 @@ You can run each example script by entering its directory and executing `npm sta - [send-token](send-token) - Send tokens from your address to another address. +- [burn-all](burn-all) - Burn all tokens for an address by tokenId. + - [lookup-token](lookup-token) - Lookup token information based on the token `id`. - [list-all-tokens](list-all-tokens) - List every token in existence. diff --git a/examples/burn-all/burn-all.js b/examples/burn-all/burn-all.js new file mode 100644 index 0000000..8c49b4b --- /dev/null +++ b/examples/burn-all/burn-all.js @@ -0,0 +1,68 @@ +/* + Burn all tokens for an address by tokenId +*/ +"use strict" + +// Set NETWORK to either testnet or mainnet +const NETWORK = `testnet` + +const SLPSDK = require("../../lib/SLP").default + +// Instantiate SLP based on the network. +let SLP +if (NETWORK === `mainnet`) + SLP = new SLPSDK({ restURL: `https://rest.bitcoin.com/v2/` }) +else SLP = new SLPSDK({ restURL: `https://trest.bitcoin.com/v2/` }) + +// Open the wallet generated with create-wallet. +let walletInfo +try { + walletInfo = require(`../create-wallet/wallet.json`) +} catch (err) { + console.log( + `Could not open wallet.json. Generate a wallet with create-wallet first.` + ) + process.exit(0) +} + +async function burnAll() { + try { + const mnemonic = walletInfo.mnemonic + + // root seed buffer + const rootSeed = SLP.Mnemonic.toSeed(mnemonic) + // master HDNode + let masterHDNode + if (NETWORK === `mainnet`) masterHDNode = SLP.HDNode.fromSeed(rootSeed) + else masterHDNode = SLP.HDNode.fromSeed(rootSeed, "testnet") // Testnet + + // HDNode of BIP44 account + const account = SLP.HDNode.derivePath(masterHDNode, "m/44'/145'/0'") + + const change = SLP.HDNode.derivePath(account, "0/0") + + // get the cash address + const cashAddress = SLP.HDNode.toCashAddress(change) + + const tokenId = "" + + // get token balances + try { + const iBurnAllConfig = { + fundingAddress: cashAddress, + fundingWif: SLP.HDNode.toWIF(change), + tokenId: tokenId, + bchChangeReceiverAddress: cashAddress + } + const burnAll = await SLP.TokenType1.burnAll(iBurnAllConfig) + console.log("TXID: ", burnAll) + } catch (error) { + console.log(error.message) + } + } catch (err) { + console.error(`Error in burnAll: `, err) + console.log(`Error message: ${err.message}`) + throw err + } +} +burnAll() diff --git a/examples/burn-all/package.json b/examples/burn-all/package.json new file mode 100644 index 0000000..0b3d344 --- /dev/null +++ b/examples/burn-all/package.json @@ -0,0 +1,24 @@ +{ + "name": "burn-all", + "version": "1.0.0", + "description": "Burn all tokens for an address by tokenId", + "main": "burn-all.js", + "scripts": { + "test": "echo no tests yet", + "start": "node burn-all.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Bitcoin-com/slp-sdk.git" + }, + "keywords": [ + "slp-sdk", + "example" + ], + "author": "Gabriel Cardona ", + "license": "MIT", + "bugs": { + "url": "https://github.com/Bitcoin-com/slp-sdk/issues" + }, + "homepage": "https://github.com/Bitcoin-com/slp-sdk#readme" +} diff --git a/index.js b/index.js index 06ef7c1..9708852 100755 --- a/index.js +++ b/index.js @@ -15,7 +15,7 @@ const repl = require("repl") const SLP = require("./lib/SLP").default const clone = require("git-clone") -program.version("1.2.2", "-v, --version") +program.version("1.3.0", "-v, --version") program .command("new ") diff --git a/lib/SLP.js b/lib/SLP.js index 6aa253c..bfe7a07 100644 --- a/lib/SLP.js +++ b/lib/SLP.js @@ -31,7 +31,7 @@ var SLP = /** @class */ (function (_super) { else restURL = "https://rest.bitcoin.com/v2/"; _this.Address = new Address_1.default(restURL); - _this.TokenType1 = new TokenType1_1.default(); + _this.TokenType1 = new TokenType1_1.default(restURL); _this.Utils = new Utils_1.default(restURL); return _this; } diff --git a/lib/TokenType1.js b/lib/TokenType1.js index 0005fb8..c52fb4c 100644 --- a/lib/TokenType1.js +++ b/lib/TokenType1.js @@ -43,22 +43,17 @@ var slpjs = require("slpjs"); var Address_1 = require("./Address"); var addy = new Address_1.default(); var TokenType1 = /** @class */ (function () { - function TokenType1() { + function TokenType1(restURL) { + this.restURL = restURL; } TokenType1.prototype.create = function (createConfig) { return __awaiter(this, void 0, void 0, function () { - var network, tmpBITBOX, getRawTransactions, slpValidator, bitboxNetwork, fundingAddress, fundingWif, tokenReceiverAddress, batonReceiverAddress, bchChangeReceiverAddress, balances, decimals, name, symbol, documentUri, documentHash, initialTokenQty, genesisTxid; + var tmpBITBOX, getRawTransactions, slpValidator, bitboxNetwork, fundingAddress, fundingWif, tokenReceiverAddress, batonReceiverAddress, bchChangeReceiverAddress, balances, decimals, name, symbol, documentUri, documentHash, initialTokenQty, genesisTxid; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: - network = addy.detectAddressNetwork(createConfig.fundingAddress); - if (network === "mainnet") { - tmpBITBOX = new BITBOXSDK({ restURL: "https://rest.bitcoin.com/v2/" }); - } - else { - tmpBITBOX = new BITBOXSDK({ restURL: "https://trest.bitcoin.com/v2/" }); - } + tmpBITBOX = this.returnBITBOXInstance(createConfig.fundingAddress); getRawTransactions = function (txids) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { @@ -102,18 +97,12 @@ var TokenType1 = /** @class */ (function () { }; TokenType1.prototype.mint = function (mintConfig) { return __awaiter(this, void 0, void 0, function () { - var network, tmpBITBOX, getRawTransactions, slpValidator, bitboxNetwork, fundingAddress, fundingWif, tokenReceiverAddress, batonReceiverAddress, bchChangeReceiverAddress, tokenId, additionalTokenQty, balances, tokenInfo, tokenDecimals, mintQty, inputUtxos, mintTxid; + var tmpBITBOX, getRawTransactions, slpValidator, bitboxNetwork, fundingAddress, fundingWif, tokenReceiverAddress, batonReceiverAddress, bchChangeReceiverAddress, tokenId, additionalTokenQty, balances, tokenInfo, tokenDecimals, mintQty, inputUtxos, mintTxid; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: - network = addy.detectAddressNetwork(mintConfig.fundingAddress); - if (network === "mainnet") { - tmpBITBOX = new BITBOXSDK({ restURL: "https://rest.bitcoin.com/v2/" }); - } - else { - tmpBITBOX = new BITBOXSDK({ restURL: "https://trest.bitcoin.com/v2/" }); - } + tmpBITBOX = this.returnBITBOXInstance(mintConfig.fundingAddress); getRawTransactions = function (txids) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { @@ -156,18 +145,12 @@ var TokenType1 = /** @class */ (function () { }; TokenType1.prototype.send = function (sendConfig) { return __awaiter(this, void 0, void 0, function () { - var network, tmpBITBOX, getRawTransactions, slpValidator, bitboxNetwork, fundingAddress, fundingWif, tokenReceiverAddress, bchChangeReceiverAddress, tokenId, amount, tokenInfo, tokenDecimals, balances, inputUtxos, sendTxid; + var tmpBITBOX, getRawTransactions, slpValidator, bitboxNetwork, fundingAddress, fundingWif, tokenReceiverAddress, bchChangeReceiverAddress, tokenId, amount, tokenInfo, tokenDecimals, balances, inputUtxos, sendTxid; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: - network = addy.detectAddressNetwork(sendConfig.fundingAddress); - if (network === "mainnet") { - tmpBITBOX = new BITBOXSDK({ restURL: "https://rest.bitcoin.com/v2/" }); - } - else { - tmpBITBOX = new BITBOXSDK({ restURL: "https://trest.bitcoin.com/v2/" }); - } + tmpBITBOX = this.returnBITBOXInstance(sendConfig.fundingAddress); getRawTransactions = function (txids) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { @@ -208,6 +191,87 @@ var TokenType1 = /** @class */ (function () { }); }); }; + TokenType1.prototype.burnAll = function (burnAllConfig) { + return __awaiter(this, void 0, void 0, function () { + var tmpBITBOX_1, getRawTransactions, slpValidator, bitboxNetwork, tokenInfo, tokenDecimals, balances, inputUtxos, network, transactionBuilder_1, originalAmount_1, byteCount, sendAmount, keyPair_1, redeemScript_1, tx, hex, txid, error_1; + var _this = this; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 4, , 5]); + tmpBITBOX_1 = this.returnBITBOXInstance(burnAllConfig.fundingAddress); + getRawTransactions = function (txids) { return __awaiter(_this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, tmpBITBOX_1.RawTransactions.getRawTransaction(txids)]; + case 1: return [2 /*return*/, _a.sent()]; + } + }); + }); }; + slpValidator = new slpjs.LocalValidator(tmpBITBOX_1, getRawTransactions); + bitboxNetwork = new slpjs.BitboxNetwork(tmpBITBOX_1, slpValidator); + return [4 /*yield*/, bitboxNetwork.getTokenInformation(burnAllConfig.tokenId)]; + case 1: + tokenInfo = _a.sent(); + tokenDecimals = tokenInfo.decimals; + return [4 /*yield*/, bitboxNetwork.getAllSlpBalancesAndUtxos(burnAllConfig.fundingAddress)]; + case 2: + balances = _a.sent(); + inputUtxos = balances.slpTokenUtxos[burnAllConfig.tokenId]; + inputUtxos = inputUtxos.concat(balances.nonSlpUtxos); + inputUtxos.forEach(function (txo) { return (txo.wif = burnAllConfig.fundingWif); }); + network = this.returnNetwork(burnAllConfig.fundingAddress); + if (network === "mainnet") { + transactionBuilder_1 = new tmpBITBOX_1.TransactionBuilder("mainnet"); + } + else { + transactionBuilder_1 = new tmpBITBOX_1.TransactionBuilder("testnet"); + } + originalAmount_1 = 0; + inputUtxos.forEach(function (utxo) { + // index of vout + var vout = utxo.vout; + // txid of vout + var txid = utxo.txid; + // add input with txid and index of vout + transactionBuilder_1.addInput(txid, vout); + originalAmount_1 += utxo.satoshis; + }); + byteCount = tmpBITBOX_1.BitcoinCash.getByteCount({ P2PKH: inputUtxos.length }, { P2PKH: 1 }); + sendAmount = originalAmount_1 - byteCount; + transactionBuilder_1.addOutput(addy.toCashAddress(burnAllConfig.bchChangeReceiverAddress), sendAmount); + keyPair_1 = tmpBITBOX_1.ECPair.fromWIF(burnAllConfig.fundingWif); + inputUtxos.forEach(function (utxo, index) { + transactionBuilder_1.sign(index, keyPair_1, redeemScript_1, transactionBuilder_1.hashTypes.SIGHASH_ALL, utxo.satoshis); + }); + tx = transactionBuilder_1.build(); + hex = tx.toHex(); + return [4 /*yield*/, tmpBITBOX_1.RawTransactions.sendRawTransaction(hex)]; + case 3: + txid = _a.sent(); + return [2 /*return*/, txid[0]]; + case 4: + error_1 = _a.sent(); + return [2 /*return*/, error_1]; + case 5: return [2 /*return*/]; + } + }); + }); + }; + TokenType1.prototype.returnNetwork = function (address) { + return addy.detectAddressNetwork(address); + }; + TokenType1.prototype.returnBITBOXInstance = function (address) { + var network = this.returnNetwork(address); + var tmpBITBOX; + if (network === "mainnet") { + tmpBITBOX = new BITBOXSDK({ restURL: "https://rest.bitcoin.com/v2/" }); + } + else { + tmpBITBOX = new BITBOXSDK({ restURL: "https://trest.bitcoin.com/v2/" }); + } + return tmpBITBOX; + }; return TokenType1; }()); exports.default = TokenType1; diff --git a/package-lock.json b/package-lock.json index 1200ea3..6f50c2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "slp-sdk", - "version": "1.2.2", + "version": "1.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index db38f1e..a747e6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "slp-sdk", - "version": "1.2.2", + "version": "1.3.0", "description": "SLP SDK powered by BITBOX", "main": "index.js", "scripts": { diff --git a/src/SLP.ts b/src/SLP.ts index 67d9133..782a753 100644 --- a/src/SLP.ts +++ b/src/SLP.ts @@ -22,7 +22,7 @@ class SLP extends BITBOXSDK { else restURL = "https://rest.bitcoin.com/v2/" this.Address = new Address(restURL) - this.TokenType1 = new TokenType1() + this.TokenType1 = new TokenType1(restURL) this.Utils = new Utils(restURL) } } diff --git a/src/TokenType1.ts b/src/TokenType1.ts index 5bef41c..3c247ee 100644 --- a/src/TokenType1.ts +++ b/src/TokenType1.ts @@ -4,24 +4,25 @@ const BigNumber: any = require("bignumber.js") const slpjs: any = require("slpjs") // import interfaces -import { ICreateConfig } from "./interfaces/SLPInterfaces" -import { IMintConfig } from "./interfaces/SLPInterfaces" -import { ISendConfig } from "./interfaces/SLPInterfaces" +import { + ICreateConfig, + IMintConfig, + ISendConfig, + IBurnAllConfig +} from "./interfaces/SLPInterfaces" // import classes import Address from "./Address" let addy: any = new Address() class TokenType1 { - async create(createConfig: ICreateConfig) { - let network: string = addy.detectAddressNetwork(createConfig.fundingAddress) - let tmpBITBOX: any + restURL: string + constructor(restURL?: string) { + this.restURL = restURL + } - if (network === "mainnet") { - tmpBITBOX = new BITBOXSDK({ restURL: "https://rest.bitcoin.com/v2/" }) - } else { - tmpBITBOX = new BITBOXSDK({ restURL: "https://trest.bitcoin.com/v2/" }) - } + async create(createConfig: ICreateConfig) { + let tmpBITBOX: any = this.returnBITBOXInstance(createConfig.fundingAddress) const getRawTransactions = async (txids: any) => { return await tmpBITBOX.RawTransactions.getRawTransaction(txids) @@ -85,14 +86,7 @@ class TokenType1 { } async mint(mintConfig: IMintConfig) { - let network: string = addy.detectAddressNetwork(mintConfig.fundingAddress) - let tmpBITBOX: any - - if (network === "mainnet") { - tmpBITBOX = new BITBOXSDK({ restURL: "https://rest.bitcoin.com/v2/" }) - } else { - tmpBITBOX = new BITBOXSDK({ restURL: "https://trest.bitcoin.com/v2/" }) - } + let tmpBITBOX: any = this.returnBITBOXInstance(mintConfig.fundingAddress) const getRawTransactions = async (txids: any) => { return await tmpBITBOX.RawTransactions.getRawTransaction(txids) @@ -150,14 +144,7 @@ class TokenType1 { } async send(sendConfig: ISendConfig) { - let network = addy.detectAddressNetwork(sendConfig.fundingAddress) - let tmpBITBOX: any - - if (network === "mainnet") { - tmpBITBOX = new BITBOXSDK({ restURL: "https://rest.bitcoin.com/v2/" }) - } else { - tmpBITBOX = new BITBOXSDK({ restURL: "https://trest.bitcoin.com/v2/" }) - } + let tmpBITBOX: any = this.returnBITBOXInstance(sendConfig.fundingAddress) const getRawTransactions = async (txids: any) => { return await tmpBITBOX.RawTransactions.getRawTransaction(txids) @@ -208,6 +195,105 @@ class TokenType1 { ) return sendTxid[0] } + + async burnAll(burnAllConfig: IBurnAllConfig) { + try { + let tmpBITBOX: any = this.returnBITBOXInstance( + burnAllConfig.fundingAddress + ) + + const getRawTransactions = async (txids: any) => { + return await tmpBITBOX.RawTransactions.getRawTransaction(txids) + } + + const slpValidator: any = new slpjs.LocalValidator( + tmpBITBOX, + getRawTransactions + ) + const bitboxNetwork = new slpjs.BitboxNetwork(tmpBITBOX, slpValidator) + const tokenInfo = await bitboxNetwork.getTokenInformation( + burnAllConfig.tokenId + ) + let tokenDecimals = tokenInfo.decimals + + let balances = await bitboxNetwork.getAllSlpBalancesAndUtxos( + burnAllConfig.fundingAddress + ) + let inputUtxos = balances.slpTokenUtxos[burnAllConfig.tokenId] + inputUtxos = inputUtxos.concat(balances.nonSlpUtxos) + + inputUtxos.forEach((txo: any) => (txo.wif = burnAllConfig.fundingWif)) + let network: string = this.returnNetwork(burnAllConfig.fundingAddress) + let transactionBuilder: any + if (network === "mainnet") { + transactionBuilder = new tmpBITBOX.TransactionBuilder("mainnet") + } else { + transactionBuilder = new tmpBITBOX.TransactionBuilder("testnet") + } + + let originalAmount: number = 0 + inputUtxos.forEach((utxo: any) => { + // index of vout + let vout: string = utxo.vout + + // txid of vout + let txid: string = utxo.txid + + // add input with txid and index of vout + transactionBuilder.addInput(txid, vout) + + originalAmount += utxo.satoshis + }) + + let byteCount = tmpBITBOX.BitcoinCash.getByteCount( + { P2PKH: inputUtxos.length }, + { P2PKH: 1 } + ) + let sendAmount = originalAmount - byteCount + + transactionBuilder.addOutput( + addy.toCashAddress(burnAllConfig.bchChangeReceiverAddress), + sendAmount + ) + + let keyPair = tmpBITBOX.ECPair.fromWIF(burnAllConfig.fundingWif) + + let redeemScript: void + inputUtxos.forEach((utxo: any, index: number) => { + transactionBuilder.sign( + index, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + utxo.satoshis + ) + }) + + let tx = transactionBuilder.build() + let hex = tx.toHex() + let txid = await tmpBITBOX.RawTransactions.sendRawTransaction(hex) + return txid[0] + } catch (error) { + return error + } + } + + returnNetwork(address: string): string { + return addy.detectAddressNetwork(address) + } + + returnBITBOXInstance(address: string): any { + let network: string = this.returnNetwork(address) + let tmpBITBOX: any + + if (network === "mainnet") { + tmpBITBOX = new BITBOXSDK({ restURL: "https://rest.bitcoin.com/v2/" }) + } else { + tmpBITBOX = new BITBOXSDK({ restURL: "https://trest.bitcoin.com/v2/" }) + } + + return tmpBITBOX + } } export default TokenType1 diff --git a/src/interfaces/SLPInterfaces.ts b/src/interfaces/SLPInterfaces.ts index 4c5a787..de8914f 100644 --- a/src/interfaces/SLPInterfaces.ts +++ b/src/interfaces/SLPInterfaces.ts @@ -34,3 +34,10 @@ export interface ISendConfig { tokenId: string amount: number } + +export interface IBurnAllConfig { + fundingAddress: string + fundingWif: string + tokenId: string + bchChangeReceiverAddress: string +}