Hey guys, in the previous article we have created our own in-browser bitcoin cash wallet using the mainnet.cash library. Today we will add the Simple Ledger Protocol (SLP) support to it.
SLP tokens in BCH are similar to ERC-20 tokens in Ethereum. These can be treated as valuable bits of information - very similar to Bitcoin Cash itself - floating on top of the blockchain, forming its second layer. If you are interested in what the SLP tokens are and how exactly do they work, consider visiting the https://simpleledger.cash and http://slp.dev.
So let's start programming right away and duplicae the UI we have created for BCH and rename the HTML element ids and titles to have slp
in their names, so we have no ambiguity. The result is this snippet, placed right after the BCH wallet's closing div
:
<div style="margin: 30px; flex: 50%;">
<h1>Simple ledger tokens</h1>
<div>Wallet balance: <span id="slpBalance"></span></div>
<div>Wallet balance USD: <span id="slpBalanceUSD"></span></div>
<div>Deposit address: <span id="slpDepositAddr"></span></div>
<div>Deposit QR: <img id="slpDepositQr"/></div>
<div>
<button type="button" id="getSlp">Get testnet slp tokens</button>
</div>
<div>
Send to slp address
<input id="slpSendAddr"></input>
<input type="button" id="slpSend" value="Send SLP"></input>
<input type="button" id="slpSendMax" value="Send All SLP Tokens"></input>
</div>
</div>
Now let's render the missing information in our SLP wallet by calling its SLP functions.
For this we need to declare the tokenId
of the token we will be working with.
const tokenId = "132731d90ac4c88a79d55eae2ad92709b415de886329e958cf35fdd81ba34c15";
tokenId
is a 64 character long string. In fact it is a transaction hash in which the token first appeared on the blockchain. The tokenId = "1327..."
you see here is the tokenId
of our Mainnet coin
which we created for testing purposes beforehand. Working with SLP tokens requires precise knowledge of the token id. Knowing the token name Mainnet coin
or its ticker MNC
is not enough, since anyone can create a token with the same name and ticker and fool us about its identity.
Now let’s duplicate the code in the run()
function and start adapting it. First let's get the token balance of our wallet and update the UI:
const slpBalance = await wallet.slp.getBalance(tokenId);
document.querySelector('#slpBalance').innerText = `${slpBalance.value} ${slpBalance.ticker}`;
document.querySelector('#slpBalanceUSD').innerText = `???`;
This code looks familiar, doesn’t it? We access all the functions of an SLP wallet with a wallet.slp
accessor. We have designed both wallet interfaces to be as close as it can get. Most notable difference is that you have to supply the tokenId
everywhere in the function calls. Note, that we do not know the USD-MNC conversion rate, hence the ???
USD value.
Watching the balance is similar to the BCH code:
wallet.slp.watchBalance((slpBalance) => {
document.querySelector('#slpBalance').innerText = `${slpBalance.value} ${slpBalance.ticker}`;
document.querySelector('#slpBalanceUSD').innerText = `???`;
}, tokenId);
Let's now get the deposit address and deposit QR Code in the SLP format with the slptest:
prefix (simpleledger:
on the main network):
const slpAddr = await wallet.slp.getDepositAddress();
document.querySelector('#slpDepositAddr').innerText = slpAddr;
const slpQr = await wallet.slp.getDepositQr();
document.querySelector('#slpDepositQr').src = slpQr.src;
This is how the UI of our SLP wallet looks like now. Very similar to that of the BCH wallet.
Now lets animate the last three buttons remaining: "Get testnet slp tokens", "Send SLP" and "Send All SLP Tokens".
Getting the testnet Mainnet coin
tokens from the faucet is very similar to BCH:
document.querySelector('#getSlp').addEventListener("click", async () => {
await wallet.getTestnetSlp(tokenId);
});
Sending the tokens requires the knowledge of an SLP address of your counterparty and the tokenId
which you want to send.
// send some
document.querySelector('#slpSend').addEventListener("click", async () => {
const addr = document.querySelector('#slpSendAddr').value;
await wallet.slp.send([{slpaddr: addr, value: 1, tokenId: tokenId}]);
alert('Sent 1 slp to ' + addr);
});
// send all
document.querySelector('#slpSendMax').addEventListener("click", async () => {
const addr = document.querySelector('#slpSendAddr').value;
await wallet.slp.sendMax(addr);
alert('Sent all slp funds to ' + addr);
});
Important notice: getting the tokens from the faucet does not cost you anything. But the tokens will turn to be a dead weight if you don't have any spare BCH in your wallet. This marginal amount of BCH is required to pay the miners to move your tokens: compare to the Ethereum gas. So before trying to move the tokens, ensure you have claimed some BCH into your wallet too!
Our resulting UI will look like this after claiming from both faucets and moving the tokens:
Note that our BCH balance is down by about 1000 sats. This is about ~4x larger than your usual transaction fee. Reason for that lays in fact that SLP transactions require a special non-monetary output which contains the information about the token movements. But don't be worried about all these complex matters - mainnet.cash library takes care of building these outputs and ensuring that your tokens will not get burned.
This concludes our second tutorial article. Next time we will port our wallets to use the mainnet.cash REST server which will do the weightlifting for us, making our web page leaner. Be sure to check out our web site and library documentation. You can ask any questions in our telegram group. The source code of this tutorial is available on github.
Cheers,
pat for mainnet.cash team.
Today I tested sending and receiving SLP. It works well. I have many tokens on the memo wallet that I cannot send (e.g. BCH tokens or MIS tokens). I get an error message in memo. I tried to send BCH tokens with mainnet.cash. Something amazing happened. Instead of BCH tokens, WSB tokens were sent. Then I tried the same thing with MIS tokens. The same thing happened. WSB tokens were sent.