Basic Tutorial

We are going to create an application that uses the stas-js SDK package to generate some tokens and send them to a few friends.

The steps we need in order to do this are create a contract transaction then issue it by spending the contract UTXO.

The stas-js SDK code is available on Github.

The SDK does not communicate with the blockchain so we'll be using WhatsOnChain (WoC) endpoints to write to and read from the blockchain.

First create a folder

mkdir tokenExample

Now let's make a node project

npm init and answer in the questions.

We want to add a dependency to our stas-js package

npm install stas-js

We also need bsv library versoin 1.5.*

npm install bsv@1.5.4

Now we're ready to start coding. Create a main.js file in the root of the tokenExample folder using your editor or IDE of choice.

We need to add the following require statements

const bsv = require('bsv')

const {
  contract,
  issue,
  transfer,
  split,
  merge,
  mergeSplit,
  redeem
} = require('stas-js')

const {
  getTransaction,
  getFundsFromFaucet,
  broadcast,
  SATS_PER_BITCOIN
} = require('stas-js').utils

Now we shall add an asynchronos main function which will execute when the program runs. The rest of the code goes inside this function.

; (async () => {

})()

The first step in issuing the tokens is to create a contract. The contract contains the Satoshis we will use to issue our token and also data containing the contract JSON which describes the token and can be thought of as an on-chain contract between the issuer and the token owners.

We will be working on TAAL's private test network "taalnet". This allows us to use a faucet to get funds.

We need to create a couple of key pairs. One for the funder who will pay the fees for the transactions. Another for the token issuer. We are using the bsv.js library legacy version 1.5.4 to generate 2 cryptographically secure random private keys.

; (async () => {
  const issuerPrivateKey = bsv.PrivateKey()
  const fundingPrivateKey = bsv.PrivateKey()
})()

Now we get BSV from the faucet by providing the addresses

const NETWORK = 'testnet'

const contractUtxos = await getFundsFromFaucet(issuerPrivateKey.toAddress(NETWORK).toString())
const fundingUtxos = await getFundsFromFaucet(fundingPrivateKey.toAddress(NETWORK).toString())

We need the public key hash of the issuer key to uses as the redeem address. This is where the Satoshis will be sent when the token is redeemed. It is part of the token Id.

const publicKeyHash = bsv.crypto.Hash.sha256ripemd160(issuerPrivateKey.publicKey.toBuffer()).toString('hex')

We create the JSON document we'll use as our contract. Some of these fields come from the ERC20 standard

const supply = 10000
const symbol = 'EXA'

const schema = {
  name: 'Example Token',
  tokenId: `${publicKeyHash}`,
  protocolId: 'To be decided',
  symbol: symbol,
  description: 'Example token on private Taalnet',
  image: 'https://www.taal.com/wp-content/themes/taal_v2/img/favicon/favicon-96x96.png',
  totalSupply: supply,
  decimals: 0,
  satsPerToken: 1,
  properties: {
    legal: {
      terms: '© 2020 TAAL TECHNOLOGIES SEZC\nALL RIGHTS RESERVED. ANY USE OF THIS SOFTWARE IS SUBJECT TO TERMS AND CONDITIONS OF LICENSE. USE OF THIS SOFTWARE WITHOUT LICENSE CONSTITUTES INFRINGEMENT OF INTELLECTUAL PROPERTY. FOR LICENSE DETAILS OF THE SOFTWARE, PLEASE REFER TO: www.taal.com/stas-token-license-agreement',
      licenceId: '1234'
    },
    issuer: {
      organisation: 'Blocksteed Tutorials',
      legalForm: 'Limited Liability Public Company',
      governingLaw: 'UK',
      mailingAddress: '1 Industry Way, Purham',
      issuerCountry: 'UK',
      jurisdiction: '',
      email: 'info@example.com'
    },
    meta: {
      schemaId: 'token1',
      website: 'website of the issuer',
      legal: {
        terms: 'the terms of the issuer'
      }
    }
  }
}

Now we have a schema we can create a contract transaction using the SDK's contract method

const contractHex = await contract(
  issuerPrivateKey,
  contractUtxos,
  fundingUtxos,
  fundingPrivateKey,
  schema,
  supply
)

The parameters, not in order, are:

The issuer and funding private keys that we created earlier. The contract and funding utxos we got from the faucet. The contract format is:

{
  txid,
  vout,
  scriptPubKey (hex encoded),
  satoshis
}

The JSON schema as discussed above. The token supply in Satoshis, in this case 10 000

The issue transaction has the following inputs and outputs

IndexInputOutput

0

Contract UTXO

Contract

1

Funding UTXO

Funding Change

The method returns the hex of the newly created transactions. It's not yet been broadcast to the blockchain yet. You can submit it anyway you like. In this example we'll use the utils.broadcast method from the SDK which calls the appropriate the WhatsOnChain endpoint and returns the transaction Id if successful which we print out.

const contractTxid = await broadcast(contractHex)
console.log(`Contract TX:     ${contractTxid}`)

You can run the code at any time by typing or using your IDE

node main.js

Now we have created the contract transaction on chain. The next step is to issue the token by spending the contract UTXO. That means we need the contract transaction object. We can either create this or get it from WoC. We'll use the latter method

const contractTx = await getTransaction(contractTxid)

When we issue a token we specify an array of addresses to which we wish to send the newly issued tokens and the amounts we wish to send. We can issue them to one person or as many as we like. There's also the option to add data to each issue. This data will appear at the end of the token script and is immutable.

So we need to create some addresses we can send the tokens to

const alicePrivateKey = bsv.PrivateKey()
const bobPrivateKey = bsv.PrivateKey()
const aliceAddr = alicePrivateKey.toAddress(NETWORK).toString()
const bobAddr = bobPrivateKey.toAddress(NETWORK).toString()

Then the issue info array

const issueInfo = [
  {
    addr: aliceAddr,
    satoshis: 7000,
    data: 'one'
  },
  {
    addr: bobAddr,
    satoshis: 3000,
    data: 'two'
  }
]

Remember the supply we set in the contract transaction was 10 000 Satoshis. In the issueInfo above we're saying we want to send 7000 to Alice with the data 'one' and 3000 to Bob with data 'two'.

Now create the issue transaction hex.

The issuerPrivateKey is needed to sign the contract UTXO which we pass as the 3rd parameter. This is the first output of the contract transaction that contains the 10 000 Satoshis followed by the issue info object.

Following that comes the payment UTXO and fundingPrivateKey that can sign it.

The payment UTXO is the second (change) output of the contract transaction.

Then a boolean flag that indicates if the token is splittable. NFT tokens are non-splittable which means if you issue one 7000 Satoshi token output you can never split or merge it but it remains 7000 Satoshis. A splittable token could be split into smaller amounts or merged with another token of the same type into a larger amount.

The symbol needs to be the same string we set in the contract document above.

const issueHex = await issue(
  issuerPrivateKey,
  issueInfo,
  {
    txid: contractTxid,
    vout: 0,
    scriptPubKey: contractTx.vout[0].scriptPubKey.hex,
    satoshis: contractTx.vout[0].value (convert to satoshis)
  },
  {
    txid: contractTxid,
    vout: 1,
    scriptPubKey: contractTx.vout[1].scriptPubKey.hex,
    satoshis: contractTx.vout[1].value (convert to satoshis)
  },
  fundingPrivateKey,
  true, // isSplittable
  symbol // EXA
)

As before we then submit the transaction to the blockchain

const issueTxid = await broadcast(issueHex)
console.log(`Issue TX:        ${issueTxid}`)

At this stage we have issued 10 000 Satoshis of tokens, 7000 to Alice and 3000 to Bob. We can look at these tokens using the WoC token endpoints. A token is identified by it's redeem address and symbol, both of which we specified in the contract. Confusingly there the redeem address has the name "tokenId". To find the redeem address view the transaction on WoC. Use the contract txId we printed out after submitting it. I can see the contract tx I created here:

https://taalnet.whatsonchain.com/tx/762a39870133bdd9fb8aa5663cec192317fd41882a2e60e2482cd41d66796dc7

I can see in the contract document that my redeem address (tokenId in the contract document) is b3504922776e5d0516425b3bef1ae6fcd847406d The Woc URL to get token information is

https://taalnet.whatsonchain.com/v1/bsv/taalnet/token/{redeem address}/{symbol}

My token URL looks like this. Your redeem address will be different.

https://taalnet.whatsonchain.com/v1/bsv/taalnet/token/b3504922776e5d0516425b3bef1ae6fcd847406d/EXA

As you can see, we've achieved what we set out to do: We generated some tokens and sent them to a few friends. On the way we've had our first taste of the STAS SDK and WoC APIs.

I thought that was the end of this short tutorial but now Bob decides to transfer all his tokens to Alice We need the issue transaction which we can get from WoC as before

  const issueTx = await getTransaction(issueTxid)

The issue transaction has the following inputs and outputs

IndexInputOutput

0

Contract UTXO

7000 -> Alice

1

Funding UTXO

3000 -> Bob

2

Funding Change

For Bob to send his tokens to Alice he needs to create a transaction spending output 1. For simplicity we're going to use the change, output 2, to fund the next transaction. Transfer moves one token UTXO to another address. The first parameter is the private key of the token owner Then we have the token UTXO we want to move. The third parameter is the address we want to move the tokens to. Finally we pass in the funding UTXO and private key.

const issueOutFundingVout = issueTx.vout.length - 1

const transferHex = transfer(
  bobPrivateKey,
  {
    txid: issueTxid,
    vout: 1,
    scriptPubKey: issueTx.vout[1].scriptPubKey.hex,
    satoshis: issueTx.vout[1].value (convert to satoshis)
  },
  aliceAddr,
  {
    txid: issueTxid,
    vout: issueOutFundingVout,
    scriptPubKey: issueTx.vout[issueOutFundingVout].scriptPubKey.hex,
    satoshis: issueTx.vout[issueOutFundingVout].value (convert to satoshis)
  },
  fundingPrivateKey
)
const transferTxid = await broadcast(transferHex)

const transferTx = await getTransaction(transferTxid)

This is the pattern for all other methods in the SDK. Pass in the appropriate UTXOs, keys and addresses.

Further methods are:

split takes a token UTXO and splits it into 2-4 outputs that can be sent to different addresses.

merge takes 2 token UTXOs from the same address and merges them into one output that can be sent to any address

mergeSplit is a merge and a split in one transaction. It merges 2 token inputs and splits them into 2-4 outputs.

redeem transforms a token output back into a normal UTXO and send it to the redeem address

redeemSplit splits token UTXO and redeems the amount left unaccounted for.

swap swaps one UTXO for another. The UTXO's involved can be tokens or standard BSV UTXOs. This requires a bit more explanation.

Last updated