SLPJS is a JavaScript Library for validating and building Simple Ledger Protocol (SLP) token transactions. GENESIS, MINT, and SEND token functions are supported. See change log for updates.
NOTE: Using SLPJS has peer dependencies bitbox-sdk and bitcore-lib-cash, so these also need to be installed.
npm install slpjs bitbox-sdk bitcore-lib-cash
<script src='https://unpkg.com/slpjs'></script>
NOTE: The latest version of slpjs package will be refactored to fix this problem.
The following code snippet examples can be copy/pasted directly into the node.js CLI. See the examples directory for example files written in TypeScript than can be run using tsc & node <filename>.
Wallets utilizing this library will want to write their own methods in place of the methods found in TransactionHelpers and BitboxNetwork classes.
NOTES:
- The BigNumber.js library is used to avoid precision issues with numbers having more than 15 significant digits.
- For the fastest validation performance all of the following transaction examples show how to use SLPJS using default SLP validation via
rest.bitcoin.com. See the Local Validation section for instructions on how to validate SLP locally with your own full node. - All SLPJS methods require token quantities to be expressed in the smallest possible unit of account for the token (i.e., token satoshis). This requires the token's precision to be used to calculate the quantity. For example, token having a decimal precision of 9 sending an amount of 1.01 tokens would need to first calculate the sending amount using
1.01 x 10^9 => 1010000000.
Get all balances for a given example. See also the TypeScript example.
// Install BITBOX-SDK v8.1+ for blockchain access// For more information visit: https://www.npmjs.com/package/bitbox-sdkconstBITBOXSDK=require('bitbox-sdk')constslpjs=require('slpjs');// FOR MAINNET UNCOMMENTletaddr="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";constBITBOX=newBITBOXSDK.BITBOX({restURL: 'https://rest.bitcoin.com/v2/'});// FOR TESTNET UNCOMMENT// let addr = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l";// const BITBOX = new BITBOXSDK.BITBOX({ restURL: 'https://trest.bitcoin.com/v2/' });constbitboxNetwork=newslpjs.BitboxNetwork(BITBOX);letbalances;(asyncfunction(){balances=awaitbitboxNetwork.getAllSlpBalancesAndUtxos(addr);console.log("balances: ",balances);})();// RETURNS ALL BALANCES & UTXOs: // { satoshis_available_bch: 190889,// satoshis_locked_in_slp_baton: 546,// satoshis_locked_in_slp_token: 1092,// satoshis_in_invalid_token_dag: 0,// satoshis_in_invalid_baton_dag: 0,// slpTokenBalances: {// '1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125': BigNumber { s: 1, e: 3, c: [ 1000 ] },// '047918c612e94cce03876f1ad2bd6c9da43b586026811d9b0d02c3c3e910f972': BigNumber { s: 1, e: 2, c: [ 100 ] } // },// nftParentChildBalances: {// 'parentId1': {// 'childId1': BigNumber// 'childId2': BigNumber// }// 'parentId2': {// 'childId1': BigNumber// 'childId2': BigNumber// }// }// slpTokenUtxos: [ ... ],// slpBatonUtxos: [ ... ],// invalidTokenUtxos: [ ... ],// invalidBatonUtxos: [ ... ],// nonSlpUtxos: [ ... ]// unknownTokenTypeUtxos: [ ... ]// }GENESIS is the most simple type of SLP transaction since no special inputs are required. The following example shows how to create a fungible token. Also see the TypeScript examples for:
// Install BITBOX-SDK v8.1+ for blockchain access// For more information visit: https://www.npmjs.com/package/bitbox-sdkconstBITBOXSDK=require('bitbox-sdk')constBigNumber=require('bignumber.js');constslpjs=require('slpjs');// FOR MAINNET UNCOMMENTconstBITBOX=newBITBOXSDK.BITBOX({restURL: 'https://rest.bitcoin.com/v2/'});constfundingAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// <-- must be simpleledger formatconstfundingWif="L3gngkDg1HW5P9v5GdWWiCi3DWwvw5XnzjSPwNwVPN5DSck3AaiF";// <-- compressed WIF formatconsttokenReceiverAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// <-- must be simpleledger formatconstbchChangeReceiverAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// <-- cashAddr or slpAddr format// For unlimited issuance provide a "batonReceiverAddress"constbatonReceiverAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// FOR TESTNET UNCOMMENT// const BITBOX = new BITBOXSDK.BITBOX({ restURL: 'https://trest.bitcoin.com/v2/' });// const fundingAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l";// const fundingWif = "cVjzvdHGfQDtBEq7oddDRcpzpYuvNtPbWdi8tKQLcZae65G4zGgy";// const tokenReceiverAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l";// const bchChangeReceiverAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l";// // For unlimited issuance provide a "batonReceiverAddress"// const batonReceiverAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l";constbitboxNetwork=newslpjs.BitboxNetwork(BITBOX);// 1) Get all balances at the funding address.letbalances;(asyncfunction(){balances=awaitbitboxNetwork.getAllSlpBalancesAndUtxos(fundingAddress);console.log('BCH balance:',balances.satoshis_available_bch);})();// WAIT FOR NETWORK RESPONSE...// 2) Select decimal precision for this new tokenletdecimals=2;letname="Awesome SLPJS README Token";letticker="SLPJS";letdocumentUri="info@simpleledger.io";letdocumentHash=nullletinitialTokenQty=1000000// 3) Calculate the token quantity with decimal precision includedinitialTokenQty=(newBigNumber(initialTokenQty)).times(10**decimals);// 4) Set private keysbalances.nonSlpUtxos.forEach(txo=>txo.wif=fundingWif)// 5) Use "simpleTokenGenesis()" helper methodletgenesisTxid;(asyncfunction(){genesisTxid=awaitbitboxNetwork.simpleTokenGenesis(name,ticker,initialTokenQty,documentUri,documentHash,decimals,tokenReceiverAddress,batonReceiverAddress,bchChangeReceiverAddress,balances.nonSlpUtxos)console.log("GENESIS txn complete:",genesisTxid)})();Adding additional tokens for a token that already exists is possible if you are in control of the minting "baton". This minting baton is a special UTXO that gives authority to add to the token's circulating supply. Also see the TypeScript example.
// Install BITBOX-SDK v8.1+ for blockchain access// For more information visit: https://www.npmjs.com/package/bitbox-sdkconstBITBOXSDK=require('bitbox-sdk')constBigNumber=require('bignumber.js');constslpjs=require('slpjs');// FOR MAINNET UNCOMMENTconstBITBOX=newBITBOXSDK.BITBOX({restURL: 'https://rest.bitcoin.com/v2/'});constfundingAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// <-- must be simpleledger formatconstfundingWif="L3gngkDg1HW5P9v5GdWWiCi3DWwvw5XnzjSPwNwVPN5DSck3AaiF";// <-- compressed WIF formatconsttokenReceiverAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// <-- must be simpleledger formatconstbatonReceiverAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";constbchChangeReceiverAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// <-- cashAddr or slpAddr formatconsttokenIdHexToMint="adcf120f51d45056bc79353a2831ecd1843922b3d9fac5f109160bd2d49d3f4c";letadditionalTokenQty=1000// FOR TESTNET UNCOMMENT// const BITBOX = new BITBOXSDK.BITBOX({ restURL: 'https://trest.bitcoin.com/v2/' });// const fundingAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l";// const fundingWif = "cVjzvdHGfQDtBEq7oddDRcpzpYuvNtPbWdi8tKQLcZae65G4zGgy";// const tokenReceiverAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l";// const batonReceiverAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l";// const bchChangeReceiverAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l";// const tokenIdHexToMint = "a67e2abb2fcfaa605c6a3b0dfb642cc830b63138d85b5e95eee523fdbded4d74";// let additionalTokenQty = 1000constbitboxNetwork=newslpjs.BitboxNetwork(BITBOX);// 1) Get all balances at the funding address.letbalances;(asyncfunction(){balances=awaitbitboxNetwork.getAllSlpBalancesAndUtxos(fundingAddress);if(balances.slpBatonUtxos[tokenIdHexToMint])console.log("You have the minting baton for this token");elsethrowError("You don't have the minting baton for this token");})();// 2) Fetch critical token decimals information using bitdblettokenDecimals;(asyncfunction(){consttokenInfo=awaitbitboxNetwork.getTokenInformation(tokenIdHexToMint);tokenDecimals=tokenInfo.decimals;console.log("Token precision: "+tokenDecimals.toString());})();// WAIT FOR ASYNC METHOD TO COMPLETE// 3) Multiply the specified token quantity by 10^(token decimal precision)letmintQty=(newBigNumber(additionalTokenQty)).times(10**tokenDecimals)// 4) Filter the list to choose ONLY the baton of interest // NOTE: (spending other batons for other tokens will result in losing ability to mint those tokens)letinputUtxos=balances.slpBatonUtxos[tokenIdHexToMint]// 5) Simply sweep our BCH (non-SLP) utxos to fuel the transactioninputUtxos=inputUtxos.concat(balances.nonSlpUtxos);// 6) Set the proper private key for each UtxoinputUtxos.forEach(txo=>txo.wif=fundingWif)// 7) MINT token using simple functionletmintTxid;(asyncfunction(){mintTxid=awaitbitboxNetwork.simpleTokenMint(tokenIdHexToMint,mintQty,inputUtxos,tokenReceiverAddress,batonReceiverAddress,bchChangeReceiverAddress)console.log("MINT txn complete:",mintTxid);})();This example shows the general workflow for sending an existing token. Also see the TypeScript example.
// Install BITBOX-SDK v8.1+ for blockchain access// For more information visit: https://www.npmjs.com/package/bitbox-sdkconstBITBOXSDK=require('bitbox-sdk');constBigNumber=require('bignumber.js');constslpjs=require('slpjs');// FOR MAINNET UNCOMMENTconstBITBOX=newBITBOXSDK.BITBOX({restURL: 'https://rest.bitcoin.com/v2/'});constfundingAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// <-- must be simpleledger formatconstfundingWif="L3gngkDg1HW5P9v5GdWWiCi3DWwvw5XnzjSPwNwVPN5DSck3AaiF";// <-- compressed WIF formatconsttokenReceiverAddress=["simpleledger:qplrqmjgpug2qrfx4epuknvwaf7vxpnuevyswakrq9"];// <-- must be simpleledger formatconstbchChangeReceiverAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// <-- must be simpleledger formatlettokenId="d32b4191d3f78909f43a3f5853ba59e9f2d137925f28e7780e717f4b4bfd4a3f";letsendAmounts=[1];// FOR TESTNET UNCOMMENT// const BITBOX = new BITBOXSDK.BITBOX({ restURL: 'https://trest.bitcoin.com/v2/' });// const fundingAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l"; // <-- must be simpleledger format// const fundingWif = "cVjzvdHGfQDtBEq7oddDRcpzpYuvNtPbWdi8tKQLcZae65G4zGgy"; // <-- compressed WIF format// const tokenReceiverAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l"; // <-- must be simpleledger format// const bchChangeReceiverAddress = "slptest:qpwyc9jnwckntlpuslg7ncmhe2n423304ueqcyw80l"; // <-- must be simpleledger format// let tokenId = "78d57a82a0dd9930cc17843d9d06677f267777dd6b25055bad0ae43f1b884091";// let sendAmounts = [ 10 ];constbitboxNetwork=newslpjs.BitboxNetwork(BITBOX);// 1) Fetch critical token informationlettokenDecimals;(asyncfunction(){consttokenInfo=awaitbitboxNetwork.getTokenInformation(tokenId);tokenDecimals=tokenInfo.decimals;console.log("Token precision: "+tokenDecimals.toString());})();// Wait for network responses...// 2) Check that token balance is greater than our desired sendAmountletbalances;(asyncfunction(){balances=awaitbitboxNetwork.getAllSlpBalancesAndUtxos(fundingAddress);console.log(balances);if(balances.slpTokenBalances[tokenId]===undefined)console.log("You need to fund the addresses provided in this example with tokens and BCH. Change the tokenId as required.")console.log("Token balance:",balances.slpTokenBalances[tokenId].toFixed()/10**tokenDecimals);})();// Wait for network responses...// 3) Calculate send amount in "Token Satoshis". In this example we want to just send 1 token unit to someone...sendAmounts=sendAmounts.map(a=>(newBigNumber(a)).times(10**tokenDecimals));// Don't forget to account for token precision// 4) Get all of our token's UTXOsletinputUtxos=balances.slpTokenUtxos[tokenId];// 5) Simply sweep our BCH utxos to fuel the transactioninputUtxos=inputUtxos.concat(balances.nonSlpUtxos);// 6) Set the proper private key for each UtxoinputUtxos.forEach(txo=>txo.wif=fundingWif);// 7) Send tokenletsendTxid;(asyncfunction(){sendTxid=awaitbitboxNetwork.simpleTokenSend(tokenId,sendAmounts,inputUtxos,tokenReceiverAddress,bchChangeReceiverAddress)console.log("SEND txn complete:",sendTxid);})();This example demonstrates how to send BCH from a SLP enabled wallet. This API ensures the BCH transaction does not use SLP UTXOs which can cause token loss for the wallet.
// Install BITBOX-SDK v8.1+ for blockchain access// For more information visit: https://www.npmjs.com/package/bitbox-sdkconstBITBOXSDK=require('bitbox-sdk');constBigNumber=require('bignumber.js');constslpjs=require('slpjs');// FOR MAINNETconstBITBOX=newBITBOXSDK.BITBOX({restURL: 'https://rest.bitcoin.com/v2/'});constfundingAddress="bitcoincash:qzq6g2dzadew2ctt6cfhthwcc2q9m60mj56ld2hj5l";// <-- must be CashAddr format// can use BITBOX.HDNode.toWIF() to generate thisconstfundingWif="KzzMBS6twjSLAjH3a1wkd7rWs3PpiHq4eQzqSEfHuxbXfxYFUBiL";// <-- compressed WIF formatconstbchReceiverAddress=["bitcoincash:qz42xz5y2hfltsa94mwm36pnl3ew8u72cc9l038x8m"];// <-- must be CashAddr formatconstbchChangeReceiverAddress="bitcoincash:qzd5hqnlu2gprdxphqt6jvft33s3m2hegcqtu6mktg";// <-- must be CashAddr formatletsendAmountsInSatoshi=1000;constbitboxNetwork=newslpjs.BitboxNetwork(BITBOX);// 1) Check that token balance is greater than our desired sendAmountletbalances;(asyncfunction(){balances=awaitbitboxNetwork.getAllSlpBalancesAndUtxos(fundingAddress);if(balances.satoshis_available_bch<sendAmountsInSatoshi){thrownewError("You need to fund the addresses provided in this example with BCH.");}console.log("BCH balance in sats:",balances.satoshis_available_bch);})();// Wait for network responses...// 2) construct sendAmounts as an array of BigIntletsendAmounts=[newBigInt(sendAmountsInSatoshi)];// 3) Get all of non-SLP UTXOsletinputUtxos=balances.nonSlpUtxos;// 4) Set the proper private key for each UtxoinputUtxos=inputUtxos.map(utxo=>({ ...utxo,wif: fundingWif}));// 5) Send tokenletsendTxId;(async()=>{sendTxId=awaitbitboxNetwork.simpleBchSend(sendAmounts,inputUtxos,bchReceiverAddress,bchChangeReceiverAddress);console.log("SEND txn complete:",sendTxId);})();This example shows how to freeze funds until a future time using OP_CLTV. Also see the TypeScript example. First, the address is calculated based on a user-defined public key and locktime. After the locktime has elapsed the user can proceed to spend those funds as demonstrated in this example:
redeemScript (locking script) = <locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <pubkey> OP_CHECKSIG
unlocking script = <signature>
constBITBOXSDK=require('bitbox-sdk');constBigNumber=require('bignumber.js');constslpjs=require('slpjs');constBITBOX=newBITBOXSDK.BITBOX({restURL: 'https://trest.bitcoin.com/v2/'});constslp=newslpjs.Slp(BITBOX);consthelpers=newslpjs.TransactionHelpers(slp);constopcodes=BITBOX.Script.opcodes;constpubkey="0286d74c6fb92cb7b70b817094f48bf8fd398e64663bc3ddd7acc0a709212b9f69";constwif="cPamRLmPyuuwgRAbB6JHhXvrGwvHtmw9LpVi8QnUZYBubCeyjgs1";consttokenReceiverAddress=["slptest:prk685k6r508xkj7u9g8v9p3f97hrmgr2qp7r4safs"];// <-- must be simpleledger formatconstbchChangeReceiverAddress="slptest:prk685k6r508xkj7u9g8v9p3f97hrmgr2qp7r4safs";// <-- must be simpleledger formatlettokenId="f0c7a8a7addc29edbc193212057d91c3eb004678e15e4662773146bdd51f8d7a";letsendAmounts=[1];// Set our BIP-113 time lock (subtract an hour to acount for MTP-11)consttime_delay_seconds=0;// let's just set it to 0 so we can redeem it immediately.letlocktime,locktimeBip62;(asyncfunction(){locktime=(awaitBITBOX.Blockchain.getBlockchainInfo()).mediantime+time_delay_seconds-3600;// NOTE: the following locktime is hard-coded so that we can reuse the same P2SH address.locktimeBip62='c808f05c'//slpjs.Utils.get_BIP62_locktime_hex(locktime); })();// Wait for network response...letredeemScript=BITBOX.Script.encode([Buffer.from(locktimeBip62,'hex'),opcodes.OP_CHECKLOCKTIMEVERIFY,opcodes.OP_DROP,Buffer.from(pubkey,'hex'),opcodes.OP_CHECKSIG])// Calculate the address for this script contract // We need to send some token and BCH to it before we can spend it!letfundingAddress=slpjs.Utils.slpAddressFromHash160(BITBOX.Crypto.hash160(redeemScript),'testnet','p2sh');// gives us: slptest:prk685k6r508xkj7u9g8v9p3f97hrmgr2qp7r4safsconstbitboxNetwork=newslpjs.BitboxNetwork(BITBOX);// Fetch critical token informationlettokenDecimals;(asyncfunction(){consttokenInfo=awaitbitboxNetwork.getTokenInformation(tokenId);tokenDecimals=tokenInfo.decimals;console.log("Token precision: "+tokenDecimals.toString());})();// Wait for network response...// Check that token balance is greater than our desired sendAmountletbalances;(asyncfunction(){balances=awaitbitboxNetwork.getAllSlpBalancesAndUtxos(fundingAddress);console.log(balances);if(balances.slpTokenBalances[tokenId]===undefined)console.log("You need to fund the addresses provided in this example with tokens and BCH. Change the tokenId as required.")console.log("Token balance:",balances.slpTokenBalances[tokenId].toFixed()/10**tokenDecimals);})();// Wait for network response...// Calculate send amount in "Token Satoshis". In this example we want to just send 1 token unit to someone...sendAmounts=sendAmounts.map(a=>(newBigNumber(a)).times(10**tokenDecimals));// Don't forget to account for token precision// Get all of our token's UTXOsletinputUtxos=balances.slpTokenUtxos[tokenId];// Simply sweep our BCH utxos to fuel the transactioninputUtxos=inputUtxos.concat(balances.nonSlpUtxos);// Estimate the additional fee for our larger p2sh scriptSigsletextraFee=(8)*// for OP_CTLV and timelock data pushinputUtxos.length// this many times since we swept inputs from p2sh address// Build an unsigned transactionletunsignedTxnHex=helpers.simpleTokenSend(tokenId,sendAmounts,inputUtxos,tokenReceiverAddress,bchChangeReceiverAddress,[],extraFee);unsignedTxnHex=helpers.enableInputsCLTV(unsignedTxnHex);unsignedTxnHex=helpers.setTxnLocktime(unsignedTxnHex,locktime);// Build scriptSigs letscriptSigs=inputUtxos.map((txo,i)=>{letsigObj=helpers.get_transaction_sig_p2sh(unsignedTxnHex,wif,i,txo.satoshis,redeemScript)return{index: i,lockingScriptBuf: redeemScript,unlockingScriptBufArray: [sigObj.signatureBuf]}})letsignedTxn=helpers.addScriptSigs(unsignedTxnHex,scriptSigs);// 11) Send tokenletsendTxid;(asyncfunction(){sendTxid=awaitbitboxNetwork.sendTx(signedTxn)console.log("SEND txn complete:",sendTxid);})();This example shows the general workflow for sending tokens from a P2SH multisig address. Also see the TypeScript example. Electron Cash SLP edition 3.4.13 is compatible with signing the partially signed transactions generated from this example by using the insert_input_values_for_EC_signers helper method.
// Install BITBOX-SDK v8.1+ for blockchain access// For more information visit: https://www.npmjs.com/package/bitbox-sdkconstBITBOXSDK=require('bitbox-sdk');constBigNumber=require('bignumber.js');constslpjs=require('slpjs');constBITBOX=newBITBOXSDK.BITBOX({restURL: 'https://rest.bitcoin.com/v2/'});constslp=newslpjs.Slp(BITBOX);consthelpers=newslpjs.TransactionHelpers(slp);constpubkey_signer_1="02471e07bcf7d47afd40e0bf4f806347f9e8af4dfbbb3c45691bbaefd4ea926307";// Signer #1constpubkey_signer_2="03472cfca5da3bf06a85c5fd860ffe911ef374cf2a9b754fd861d1ead668b15a32";// Signer #2// we have two signers for this 2-of-2 multisig address (so for the missing key we just put "null")constwifs=[null,"L2AdfmxwsYu3KnRASZ51C3UEnduUDy1b21sSF9JbLNVEPzsxEZib"]//[ "Ky6iiLSL2K9stMd4G5dLeXfpVKu5YRB6dhjCsHyof3eaB2cDngSr", null ];// to keep this example alive we will just send everything to the same addressconsttokenReceiverAddress=["simpleledger:pphnuh7dx24rcwjkj0sl6xqfyfzf23aj7udr0837gn"];// <-- must be simpleledger formatconstbchChangeReceiverAddress="simpleledger:pphnuh7dx24rcwjkj0sl6xqfyfzf23aj7udr0837gn";// <-- must be simpleledger formatlettokenId="497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7";letsendAmounts=[1];constbitboxNetwork=newslpjs.BitboxNetwork(BITBOX);// 1) Fetch critical token informationlettokenDecimals;(asyncfunction(){consttokenInfo=awaitbitboxNetwork.getTokenInformation(tokenId);tokenDecimals=tokenInfo.decimals;console.log("Token precision: "+tokenDecimals.toString());})();// Wait for network responses...// 2) Check that token balance is greater than our desired sendAmountletfundingAddress="simpleledger:pphnuh7dx24rcwjkj0sl6xqfyfzf23aj7udr0837gn";letbalances;(asyncfunction(){balances=awaitbitboxNetwork.getAllSlpBalancesAndUtxos(fundingAddress);console.log(balances);if(balances.slpTokenBalances[tokenId]===undefined)console.log("You need to fund the addresses provided in this example with tokens and BCH. Change the tokenId as required.")console.log("Token balance:",balances.slpTokenBalances[tokenId].toFixed()/10**tokenDecimals);})();// Wait for network responses...// 3) Calculate send amount in "Token Satoshis". In this example we want to just send 1 token unit to someone...sendAmounts=sendAmounts.map(a=>(newBigNumber(a)).times(10**tokenDecimals));// Don't forget to account for token precision// 4) Get all of our token's UTXOsletinputUtxos=balances.slpTokenUtxos[tokenId];// 5) Simply sweep our BCH utxos to fuel the transactioninputUtxos=inputUtxos.concat(balances.nonSlpUtxos);// 6) Estimate the additional fee for our larger p2sh scriptSigsletextraFee=(2*33+// two pub keys in each redeemScript2*72+// two signatures in scriptSig10)*// for OP_CMS and various length bytesinputUtxos.length// this many times since we swept inputs from p2sh address// 7) Build an unsigned transactionletunsignedTxnHex=helpers.simpleTokenSend(tokenId,sendAmounts,inputUtxos,tokenReceiverAddress,bchChangeReceiverAddress,[],extraFee);// 8) Build scriptSigs for all intputsletredeemData=helpers.build_P2SH_multisig_redeem_data(2,[pubkey_signer_1,pubkey_signer_2]);letscriptSigs=inputUtxos.map((txo,i)=>{letsigData=redeemData.pubKeys.map((pk,j)=>{if(wifs[j]){returnhelpers.get_transaction_sig_p2sh(unsignedTxnHex,wifs[j],i,txo.satoshis,redeemData.lockingScript)}else{returnhelpers.get_transaction_sig_filler(i,pk)}})returnhelpers.build_P2SH_multisig_scriptSig(redeemData,i,sigData)})// 9) apply our scriptSigs to the unsigned transactionletsignedTxn=helpers.addScriptSigs(unsignedTxnHex,scriptSigs);// 10) Update transaction hex with input values to allow for our second signer who is using Electron Cash SLP edition (https://simpleledger.cash/project/electron-cash-slp-edition/)letinput_values=inputUtxos.map(txo=>txo.satoshis)signedTxn=helpers.insert_input_values_for_EC_signers(signedTxn,input_values)// 11) Send tokenletsendTxid;(asyncfunction(){sendTxid=awaitbitboxNetwork.sendTx(signedTxn)console.log("SEND txn complete:",sendTxid);})();This example shows the general workflow for sending an existing token. Also see the TypeScript example.
// Install BITBOX-SDK v8.1+ for blockchain access// For more information visit: https://www.npmjs.com/package/bitbox-sdkconstBITBOXSDK=require('bitbox-sdk')constBigNumber=require('bignumber.js');constslpjs=require('slpjs');constBITBOX=newBITBOXSDK.BITBOX({restURL: 'https://rest.bitcoin.com/v2/'});constfundingAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// <-- must be simpleledger formatconstfundingWif="L3gngkDg1HW5P9v5GdWWiCi3DWwvw5XnzjSPwNwVPN5DSck3AaiF";// <-- compressed WIF formatconstbchChangeReceiverAddress="simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu";// <-- must be simpleledger formatlettokenId="495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a";letburnAmount=102;constbitboxNetwork=newslpjs.BitboxNetwork(BITBOX);// 1) Fetch critical token informationlettokenDecimals;(asyncfunction(){consttokenInfo=awaitbitboxNetwork.getTokenInformation(tokenId);tokenDecimals=tokenInfo.decimals;console.log('Token precision:',tokenDecimals.toString());})();// 2) Check that token balance is greater than our desired sendAmountletbalances;(asyncfunction(){balances=awaitbitboxNetwork.getAllSlpBalancesAndUtxos(fundingAddress);console.log('Token balance:',balances.slpTokenBalances[tokenId].toFixed()/10**tokenDecimals)})();// Wait for network responses...// 3) Calculate send amount in "Token Satoshis". In this example we want to just send 1 token unit to someone...letamount=(newBigNumber(burnAmount)).times(10**tokenDecimals);// Don't forget to account for token precision// 4) Get all of our token's UTXOsletinputUtxos=balances.slpTokenUtxos[tokenId];// 5) Simply sweep our BCH utxos to fuel the transactioninputUtxos=inputUtxos.concat(balances.nonSlpUtxos);// 6) Set the proper private key for each UtxoinputUtxos.forEach(txo=>txo.wif=fundingWif)// 7) Send tokenletsendTxid;(asyncfunction(){sendTxid=awaitbitboxNetwork.simpleTokenBurn(tokenId,amount,inputUtxos,bchChangeReceiverAddress)console.log("BURN txn complete:",sendTxid);})();letUtils=require('slpjs').Utils;letslpAddr=Utils.toSlpAddress("bitcoincash:qzat5lfxt86mtph2fdmp96stxdmmw8hchyxrcmuhqf");console.log(slpAddr);// simpleledger:qzat5lfxt86mtph2fdmp96stxdmmw8hchy2cnqfh7hletcashAddr=Utils.toCashAddress(slpAddr);console.log(cashAddr);// bitcoincash:qzat5lfxt86mtph2fdmp96stxdmmw8hchyxrcmuhqfThe following examples show three different ways how you can use this library to validate SLP transactions. The validation techniques include:
- Local Validator with a JSON RPC full node connection
- Local Validation with a remote full node (using
rest.bitcoin.com) - Remote Validation (using
rest.bitcoin.com)
Validate SLP transaction locally with a local full node.
constBITBOXSDK=require('bitbox-sdk')constBITBOX=newBITBOXSDK.BITBOX();constslpjs=require('slpjs');constlogger=console;constRpcClient=require('bitcoin-rpc-promise');constconnectionString='http://bitcoin:password@localhost:8332'constrpc=newRpcClient(connectionString);constslpValidator=newslpjs.LocalValidator(BITBOX,async(txids)=>[awaitrpc.getRawTransaction(txids[0])],logger)// Result = false//let txid = "903432f451049357d51c19eb529478621272e7572b05179f89bcb7be31e55aa7";// Result = truelettxid="4a3829d6da924a16bbc0cc43d5d62b40996648a0c8f74725c15ec56ee930d0fa";letisValid;(asyncfunction(){console.log("Validating:",txid);console.log("This may take a several seconds...");isValid=awaitslpValidator.isValidSlpTxid(txid);console.log("Final Result:",isValid);})();Validate SLP transaction locally with a remote full node (i.e., rest.bitcoin.com).
constBITBOXSDK=require('bitbox-sdk')constBITBOX=newBITBOXSDK.BITBOX({restURL: 'https://rest.bitcoin.com/v2/'});constslpjs=require('slpjs');constlogger=console;constgetRawTransactions=asyncfunction(txids){returnawaitBITBOX.RawTransactions.getRawTransaction(txids)}constslpValidator=newslpjs.LocalValidator(BITBOX,getRawTransactions,logger);// Result = false//let txid = "903432f451049357d51c19eb529478621272e7572b05179f89bcb7be31e55aa7";// Result = truelettxid="4a3829d6da924a16bbc0cc43d5d62b40996648a0c8f74725c15ec56ee930d0fa";letisValid;(asyncfunction(){console.log("Validating:",txid);console.log("This may take a several seconds...");isValid=awaitslpValidator.isValidSlpTxid(txid);console.log("Final Result:",isValid);})();Validate SLP transaction using rest.bitcoin.com.
constBITBOXSDK=require('bitbox-sdk')constBITBOX=newBITBOXSDK.BITBOX({restURL: 'https://rest.bitcoin.com/v2/'});constslpjs=require('slpjs');constlogger=console;constslpValidator=newslpjs.BitboxNetwork(BITBOX,undefined,logger);// Result = false//let txid = "903432f451049357d51c19eb529478621272e7572b05179f89bcb7be31e55aa7";// Result = truelettxid="ab1550876e217d68bfac55e50b4a82535bb20842f976bdfbc07cca19e8028f13";letisValid;(asyncfunction(){console.log("Validating:",txid);console.log("This may take a several seconds...");isValid=awaitslpValidator.isValidSlpTxid(txid);console.log("Final Result:",isValid);})();Building this project creates lib/*.js files and then creates browserified versions in the dist folder.
Running the unit tests require node.js v8.15+.
npm run build
npm run test
- Updates for creating and sending NFT1 group and children token types
- Simplify BchdValidator behavior and API
- Add BchdValidator class for leveraging BCHD SLP indexer
- Update BchdNetwork to use an interface for gRPC client
- Note: 0.27.5 was skipped due to an error during publishing
- Fixed false positive edge case for NFT child GENESIS validation using new SLP unit test vectors
- Update packages
- Minor updates to token send example
- Remove extraneous regex checks
- Add flag to disable validator transaction cache in LocalValidator
- Fixed false positive case for MINT validation
- Updated for trusted validator
- Specify
- Update slp.ts internals to accept validator instead of network
- Add TrustedValidator class and example
- Utilizing simpleledger/slp-mdm package, update associated unit tests
- Update BigNumber library
- Update unit tests and examples for bchd based network
- Patch missing tokenIdHex in getTokenInformation after recent refactoring
- Typings fixes
- Typings update for bitcore-cash-lib
- Update mocha version and settings
- Update typings for bitcore-cash-lib
- Update typings for BchdNetwork
- Make bitcore-lib-cash a peer dependency to prevent conflicting version issues
- Breaking change: processUtxosForSlpAbstract now has getRawTransaction method parameter
- Add TrustedValidator class for remote validation
- Add BchdNetwork class, as an alternative to BitboxNetwork class
- Include extra baton/receiver sats in mint/genesis change calculation
- breaking change: use destructured parameters in txn helpers
- Truncate all decimals in satoshis value
- Update retrieveRawTransaction method in slp validator
- (breaking change) Enable "esModuleInterop" in tsconfig.json to align with default
tsc --initsettings - Update bitbox-sdk to 8.11.1
- Upgrade TransactionHelpers.simpleTokenMint and Slp.buildRawMintTx for p2sh compatibility
- Publish *.d.ts files instead of *.ts files
- Update types for bitbox-sdk
- Update bitbox-sdk to latest version
- Add toRegtestAddress method in Utils
- Added bitbox-sdk to peerDependency list in package.json
- Added missing typings modules to vendors.d.ts (for bitbox)
- Update bchaddr-slp package (adds "regtest:" compatibility)
- Add TransactionOutput interface to primatives.ts
- Updated unit tests with "allow_inconclusive" on missing tranasaction
- More linting
- (breaking change) Update applyInitialSlpJudgement (to be able to work with non-BITBOX sources of utxo data)
- Fixed a critical security vulnerability in the validation message parser
- Add ts linting / allow json comments
- Judge NFT1 child created directly from valid NFT1 Parent Genesis as valid
- Removed map files from npm package module
- Export bitcore
- Fix local validator cache for SLPDB (bug should have only impacted SLPDB application performance)
- Updated examples with more validation options
- Added new code examples for NFT1 Genesis Parent and Child (see examples directory)
- Updated Genesis/Mint/Send methods for handling new token types appropriately
- Breaking: Removed old NFT1 methods from v0.15.12, these were likely never used by anyone but bumping the version just in case.
- Fix accounting for unknown token type UTXOs in
SlpBalancesResultby addingsatoshis_in_unknown_token_typeandunknownTokenTypeUtxosobjects.
- Now NFT Parents/Children are readily visible when using
getAllSlpBalancesAndUtxos(<address>)- Add
nftParentChildBalancesdict/map toSlpBalancesResult. - Add
nftParentIdproperty to each SLP UTXO object (in typeSlpAddressUtxoResult).
- Add
- Created an examples directory, starting to mirror/migrate README examples to this folder. This will allow easier execution of the examples when they are in TypeScript
- Bump version for npm issue with previous version
- Export Primatives namespace
- Add 'Primatives.parseFromBuffer()' method
- Remove unused typing from vendors.d.ts
- Update dependency versions to address security flags
- Minor typings update
- Added full NFT1 validation support with updates to both the validator and parser.
- Critical Issue Fixed: This version fixed a critical bug associated with unsupported token types. All previous versions will allow unsupported token types to be burned because they are treated as if they are non-SLP UTXOs. This version includes a new type of UTXO judgement for unsupported token types (
UNSUPPORTED_TYPE), and they any UTXO receiving this judgement is prevented from being spent in the built-in transaction methods. - Added more descriptive code commenting to localvalidator.ts.
- Breaking Change: Added initial NFT1 validation support (not yet activated in parser). Change is breaking due to new
tokenTypeFilterparameter in methodisValidSlpTxid()
- Tweaked type for ScriptSigP2SH.unlockingScriptBufArray
- Added simpleTokenGenesis to bitbox network class
- Added example for freezing tokens
- Added Locktime and CLTV helper methods to TransactionHelpers
- Added get_BIP62_locktime_hex method to Utils
- Breaking Change: Updated Slp.buildSendOpReturn, Slp.buildMintOpReturn, and Slp.buildGenesisOpReturn methods to static
- Added generic support for P2SH
- Added specific helper methods for multisig compatible with Electron Cash signing
- Added README example for multisig w/ Electron Cash SLP co-signer
- Added
simpleledger:URI scheme parser & builder to Utils class per spec - Removed unused remote proxy validator code
- Bumped Bitbox dep to v8.1 with TypeScript updates
- Breaking changes:
- Dev dependency BITBOX updated to latestest version 8.0.1 from 3.0.11
- Throws on network error instead of returning null/false
- Non-breaking changes:
- Added tests for BitboxNetwork class
- added "decimalConversion" parameter to getTransactionDetails() and getTokenInformation() methods in BitboxNetwork, default is false
- Added slp address to getTransactionDetails() response
- Added optional logger to LocalValidator & BitboxNetwork classes
- Fixed bug in default remote validation for BitboxNetwork.isValidSlpTxid()
- Added comments warning users about two rate limited methods
- Improved error messages for insufficient inputs and fee too low
- Breaking changes:
- For all types of SEND transactions the change address must be provided, and the change address must be in simpleledger address format since it may contain token change.
- Non-breaking changes:
- Added new
voutproperty to validation parents inLocalValidatorclass - Added change log to
readme.md - Refactored transaction builder methods into a new class called
TransactionHelpers
- Added new
- Fixed issue in
isSlpAddresswhere it would throw instead of return false on some inputs. - Added
isLegacyAddress,toLegacy, andslpAddressFromHash160methods toUtilsclass.
- Add transaction helper methods for NFT1
- validate chunks of 20 with bitcoin.com validator endpoint
- handle array object type response from
sendRawTransactionmethod inBitboxNetworkclass
- Add default remote validation for BitboxNetwork
- Simplified all README examples to use default validator
- Add description for how to override the default validator
