# Tokens and Transactions

Once you have installed Pali Wallet and retrieved the controller object you can make function calls. If you have not completed those steps, go to Getting Started.

await controller.function()
>> output

It will also help you to be familiar with some of the available Wallet functions before proceeding.

TIP

When using Syscoin Testnet, you can view assets, transactions and statuses with Blockbook (opens new window).

# handleCreateToken()

This is an asynchronous functions, thus it returns a Promise.

Token properties:

When creating a token, there are some optional properties. These are marked with ?.

handleCreateToken({
  precision: number || 8, 
  symbol: 'string', 
  maxsupply: number, 
  description?: 'string', 
  receiver?: 'string',
  capabilityflags?: number, 
  notarydetails?: { endpoint?: string, instanttransfers?: boolean, hdrequired?: boolean }, 
  auxfeedetails?: { auxfeekeyid: string, auxfees: [{ bound: any | 0, percent: any | 0 }] },
  notaryAddress?: string, 
  payoutAddress?: string
)

# Required inputs

Some details need explaining. Let's start with precision. "Precision" represents how many decimals places your token provides. According to the Syscoin documentation (opens new window)

Precision of balances. Must be an integer; min 0 and max 8. The lower it is the higher possible maxsupply is available since the supply is represented as a 64-bit integer. With a precision of 8, the highest possible max supply is 9,999,999,999.99999999. See the Glossary for a precision/maxSupply correlation table.

maxsupply (integer or decimal) is the max quantity of this token that can ever be issued.

symbol (string) can have a length up to 8 characters in ASCII.

Refer to the the Glossary for token terminology.

# Optional inputs

  • description (string) will be encoded via JSON and stored in the asset's pubdata field at the desc key in the JSON object.

  • receiver (string) is the address that will receive ownership of the token spec and issuer rights (usually it is your own wallet).

  • capabilityflags (see table below) should be an integer representing the sum of the Mask (bitmask) integers representing the permissions you wish the grant the issuer (usually yourself). Default is 127 (all flags enabled). Declaring the value when creating the token explicits the limits to which the token spec owner/issuer can alter the asset's underlying information. See the list below:

Mask flag Permissions
0 no flag
1 ASSET_UPDATE_DATA Update public data field
2 ASSET_UPDATE_CONTRACT Update smart contract field
4 ASSET_UPDATE_SUPPLY Issue and distribute supply via assetsend
8 ASSET_UPDATE_NOTARY_KEY Update notary address
16 ASSET_UPDATE_NOTARY_DETAILS Update notary details
32 ASSET_UPDATE_AUXFEE Update aux fees
64 ASSET_UPDATE_CAPABILITYFLAGS Update capability flags
127 ASSET_CAPABILITY_ALL All flags enabled

# Obs:

Please note that the Mask flags are negatively defined. That means that adding Masks restricts user capabilities.

Let's see an Example:

Supose you wish to create a token such that its contract, public data and aux_fee fields cannot be updated. In this case, the right value for capabilityflags is capabilityflags = 92, in more details:

public_data = 1, contract = 2, aux_fee = 32, All = 127
>> Mask = 127 - (1 + 2 + 32)
>> Mask = 92

So, if all flags are enabled, then the only flag selected is no flag = 0, therefore, the value for Mask is

>> Mask = 127 - 0 = 127

which is consistent with our negative definition for capabilityflags.

# Interacting with an external API (Notary/Business Rules)

If you are interacting our API from your own software (or even another API), you may want to specify its details. If you are not pipelining, please skip this section.

  • The (Optional) object notarydetails should be specified according to the following values descriptions
Object Entry Required Details
endpoint YES Requires a fully qualified URL of the notary endpoint. The endpoint will receive POST requests of all transaction attempts made with your asset, with transaction hex and some other details in a JSON object. Your API at the endpoint then executes your logic to determine whether to approve (sign) the user's transaction.
instanttransfers NO. Default is zero (false) This indicates your Notary API guarantees protection against double-spending. Since notarization happens via the API at the endpoint, it can track and block attempts to double-spend, making safe instant transactions possible.
hdrequired NO. Default is zero (false) If HD account XPUB and HD path information is required by the notary to verify, change addresses to the ones belonging to the sender account.

You can also declare the following

  • auxfeedetails(Optional): Prescribe auxiliary fees to the transactions of the current asset.
    • auxfee (Required): An Array of auxiliary fees, which is based on the total value being sent.

Where the Object auxfee should be detailed as

Input Object Entry Required Details
auxfee bound YES A lower bound threshold (in satoshis), of the total output value for this asset, defining if a percentage fee will be applied on the transaction.
percent YES Percentage of the applied fee, based on the total output value. Obs: this value is multiplied by 1000 to avoid floating point precision. E.g. if one wishes to apply a 0.001% fee, then percent = 1

# Example

Here is a typical example of a token creation call:

const handleMakeSomething = async () => {
  await controller.handleCreateToken({
    precision: 2,
    symbol: 'Coffees',
    amount: 10,
    receiver: 'tsys1q0t9l60hxql6f4f7m47j682m7ncrd5xs4yz00dc'
  });
}

The extension should pop up and ask for your confirmation, since it creates a new EventListener and hence a Promise which must be resolved on the extension by either rejecting or confirming the token creation.

Confirmation

Confirmation

WARNING

The Creation of a token is a process that takes about two blocks to be completed, since more than one transaction is necessary to validate the token. This process may take a while (from 2 to 5 minutes). If you are trying to mint tokens without success, please try to verify if the token is already settled on-chain before minting.

# handleIssueSPT()

Once you have created a token you can now mint a quantity of it, either as standard (fungible) SPT or as an NFT (below)

TIP

"What is the difference between minting and creating?"
To create a token is to make a space for it on the blockchain and define its details. When doing so, one has to define its max_supply, but not necessarily all its supply will be readily available. It is possible to mint them on the fly, either manually or through a DApp.

Minting/Issuing comes after creation. This is defined as actually supplying the tokens; generating and putting them into circulation.

A useful analogy is made with creating a wooden mold. In this analogy, creating the token is to actually carve the wood - the base from which one can pour (mint) instances of that shape. Like a wooden mold, the printing supply is limited (due to material abrasion), and every item cast is a representation of the original mold. The artist then sells the prints and keeps the mold.

Being an asynchronous function, issuing tokens looks like this normally:

const handleMakeSomething = async () => {
  await controller.handleIssueSPT({ amount: 3, assetGuid: '837966484' });
}

as stated previously, assetGuid can be found within the asset list of the wallet you used to create the token. Running this command should pop-up a wallet request for confirmation.

This function adds an EventListener asking for confirmation with the following pop-up window:

Confirmation

Once confirmation is given, the Promise is still pending and needs to resolve by you confirming the details. A second pop-up should appear.

Confirmation

Once you provide confirmation, you will see that the token is being minted/issued on your Activity tab within Pali Wallet. This process should take a couple of minutes to finalize.

The new tokens will be minted to the address that owns the token spec (usually the same address you used when Creating the token). From there you can begin sending/circulating them.

# handleSendToken()

handleSendToken() is our main function for a transaction of SYS or SPTs (tokens).

This asynchronous function asks for an object as input with the following keys

const input = {
  sender: 'string', 
  receiver: 'string', 
  amount: number, 
  fee: number, 
  token: {...} || null, 
  isToken: boolean, 
  rbf: boolean
}

So one can then call it with

const handleMakeSomething = async () => {
  await controller.handleSendToken({
    sender: "tsys1qvpjfee87n83d4kfhdwcw3fq76mteh92e4vdr8v",
    receiver: "tsys1q4g67h0rx8j7pzlxfqystemeclkp3shuh2np065",
    amount: 13,
    fee: 0.00001,
    token: {
      assetGuid: "2681611140",
      balance: 100000,
      decimals: 2,
      symbol: "Coffees",
      type: "SPTAllocated"
    },
    isToken: true,
    rbf: true
  });
}

Lets clarify some of them:

  • sender and receiver should be the addresses. The "sender" is the address from which the value is being sent, and should be an address within your wallet. The "receiver" is the recipient of the value you are sending.
  • token : can be two things:
    • If you wish to send SYS then token = null and isToken = false.
    • If you are sending a token, then isToken = true and token is an Object containing key:value pairs associated with the asset such as:
    token = {
      type: "SPTAllocated", 
      assetGuid: "402608430",
      symbol: "Coffee Token",
      balance: 123, 
      decimals: 3
    }
    
    • You should recognize these as the outputs of getWalletState() from the assets: tab.
  • RBF stands for Replace-By-Fee. Setting this true enables you to replace an old unconfirmed transaction that is lingering in the mempool with a new transaction paying a higher fee for faster fulfillment by miners. The trade-off of enabling this is that Z-DAG will not be used for this transaction.

This is an async() function, so, when making a transaction, you should expect the extension to pop up and ask for confirmation (to fulfill a Promise). Once you confirm, the transaction will be broadcasted to the blockchain network.

# handleCreateNFT()

We suggest creating NFTs directly with this function rather than handleCreateToken() because this function streamlines the NFT creation process and makes it less prone to user mistakes. Since most of the details here are very much similar to the ones for creating SPTs, you can cross-reference handleCreateToken() to answer questions you might have.

Let's examine some of the inputs in our function

handleCreateNFT ({
  symbol: 'string',
  issuer: 'string',
  precision: number || 8, 
  description?: 'string', 
  notary?: boolean, 
  notarydetails?: { endpoint?: 'string', instanttransfers?: boolean, hdrequired?: boolean }, 
  auxfee?: boolean, 
  auxfeedetails?: { auxfeekeyid: 'string', auxfees: [{ bound: any | 0, percent: any | 0 }] }, 
  notaryAddress?: 'string', 
  payoutAddress?: 'string'
})

All fields here are the same as handleCreateToken(), with two exceptions:

issuer is the owner/Issuer address that will receiving the NFT spec. If you are issuing it yourself, then it should probably equal an address in your current connected wallet. You can also make your NFT a fracionary one, so multiple addresses may have a fraction of its ownership. In such a case,

precision serves to make your NFT fractional, meaning multiple addresses can own a fraction of the NFT. This value stands for how many fractions your NFT can be separated into. This field defaults to 1 if undefined, meaning one share (non-fractional). NFT fractionality is related to precision on the backend. For this feature to work, you must choose one of the following as your number of shares:

precision
1
10
100
1000
10000
100000
1000000
10000000
100000000

e.g. with 100 shares, your NFT will be created with a precision value of 2 (1.00), giving it 100 units of divisibility.

NOTE: precision is a unique and efficient approach to NFT fractionality in the industry. Typical NFT platforms do not provide this. If you wish to keep things simple and easy for users of other platforms to understand, you may choose to stick with a value of 1 (non-fractional). A more typical approach to fractional NFTs will become available when Syscoin releases NEVM smart contract functionality.

You may be wondering why the fields precision and maxsupply are not used when calling this function. There is a straightforward explanation for this: One can understand NFTs simply as SPTs but with maxsupply equal to 1 and precision determined by precision.

# Example :

You can call the function like this

const handleMakeSomething = async () => {
  await controller.handleCreateNFT({
    symbol: 'Coffe4two', 
    issuer: 'tsys1qz7c7mm39vzj9ca43st8ejxee8trty6jgm7aykv', 
    precision: 1, 
    description: 'More than enough Coffee for one person'
  });
}

This sends a message to the extension, which creates a Promise. Pali Wallet requests confirmation from the user.

Confirmation

After confirmation, NFT information is displayed.

Confirmation

once confirmed, the Promise resolves and the Token is created. You may then see it on the blockbook. In this case you should see that a total of 1 Coffee4two has been minted.

If something goes wrong, the function will print "Can't create and issue NFT. Try again later"

# handleIssueNFT()

This function is very similar to handleIssueSPT(). In fact, NFTs are just SPTs with precision determined by precision and maxSupply equal to one. NFTs possess their own distinct assetGuid on the blockchain. Therefore, when running this function, another auxiliary one, namely isNFT(), is called which checks if the NFT hash fits by returning a boolean value.

So this function is actually creating an SPT (called the Parent SPT) and then issuing/minting the NFT and sending it to the token creator address. The Parent SPT then receives new properties and is blocked from being updated (capabilityFlags = 0: this guarantees non-fungibility and trustlessness of the token spec). You can also reference the Parent SPT from the NFT by looking for your minted NFT's baseAssetID value which is the assetGuid of the Parent SPT used to issue it.

const handleMakeSomething = async () => {
  await controller.handleIssueNFT({ amount: number, assetGuid: 'string' });
};

This asynchronous function sends a message to the extension. Pali Wallet should notify you with the same type of information as in handleIssueSPT()

# getDataAsset()

This asynchronous function comes in handy when you need details about a specific asset. The only input is the assetGuid of the asset you are inquiring about. Use as follows:

const handleMakeSomething = async () => {
  await controller.getDataAsset(assetGuid: 'string');
};

This returns an object with the token's parameters in the following format

{ 
  assetGuid: "5609075964"
  contract: "0x"
  decimals: 8
  maxSupply: "100000000"
  pubData: {desc: "aXBmcyB1cmw="}
  symbol: "VGhlVEZO"
  totalSupply: "0"
  updateCapabilityFlags: 127
}

As stated in the handleCreateToken() tab, the pubData field is an object that has the token description (encoded via JSON) at the desc key. Moreover, you may remember that the updateCapabilityFlags carries the value 127 as the default. This is a bitmask value representing which permissions the token spec owner has in managing the token. Furthermore, contract here represents a smart contract address (i.e. an Ethereum or Syscoin NEVM contract) that translates SYS tokens (both SPTs and NFTs) to smart contract tokens on another blockchain. This field/feature should be useful again once Syscoin NEVM is released.

# handleUpdateAsset()

Fields such as maxsupply, precision, etc, are immutable and cannot be changed after creation.

However, some other fields can be updated depending on the token's capabilityflags value.

If you own a token spec and wish to update certain of the token's fields/characteristics that happen to be changeable, then handleUpdateAsset() can be called. The example below shows which fields can potentially be changed, again, depending on whether the token's capabilityflags value permits.

const handleMakeSomething = async () => {
  await controller.handleUpdateAsset({
    assetGuid: string, 
    contract?: string | null, 
    capabilityflags?: string | '0', 
    description?: string | null, 
    notarydetails?: {
      endpoint?: string, 
      instanttransfers?: boolean, 
      hdrequired?: boolean 
    } | null, 
    auxfeedetails?: {
      auxfeekeyid?: any, 
      auxfees?: [{ bound?: any | 0, percent?: any | 0 }] 
    } | null,
    notaryAddress?: string | null, 
    payoutAddress?: string | null
  });
};

This async() should generate a Promise and add an EventListener waiting for the information to be fulfilled. Once the asset is updated, it should resolve and remove the EventListener.

Updating fields of an asset is a restricted action, defined by the capabilityflags variable. Details on possible values are here.

# isNFT()

You can verify if an asset is an NFT by passing an assetGuid through isNFT(). This function returns a boolean value.

controller.isNFT(14156257357);
>> true

# handleTransferOwnership()

You can transfer token ownership with this asynchronous function, like so

const handleMakeSomething = async () => {
  await controller.handleTransferOwnership({ assetGuid: '2681611140', newOwner: 'tsys1q4g67h0rx8j7pzlxfqystemeclkp3shuh2np065' });
};

The following window should pop-up

Ownership

After confirmation, details are showed and confirmation is necessary once more time

Ownership2

# signAndSend()

Although named signAndSend this function actually sings and sends a PSBT (i.e. a Partially Signed Bitcoin Transaction). For a function that only signs, please head to signPSBT().

This feature is particularly useful for transactions that depend on multiple wallets or authorizations (e.g. corporate decisions that request multiple verifications before sending large sums), called multisigs. The specifics on psbts, for advanced users, can be found on the bitcoin code for the BIP 0174 (opens new window) and BIP 0370 (opens new window).

The sole input is the actual psbt hash:

const handleMakeSomething = async () => {
  await controller.signAndSend(PSBT);
};

After running, the wallet should ask you for confirmation:

Confirmation

You should notice that the PSBT is serialized, this is due to the hashing of information on the blockchain (a feature for minimizing bit usage), in future releases the PSBT will be a normal transaction hash (on the wallet interface).

Note: However, there are some issues due to how React works together with the syscoin-libjs and makes it a bit hard to workaround. That said, when testing your App on the test network, you can converse your app with another server to get direct access to the syscoin-libjs. To facilitate this, we built an example App for signTransaction (opens new window) on Github. Check it, if you will.

Such issues will probably be resolved in future versions of the Pali Wallet.

# signPSBT()

This function is very similar to signAndSend(). The difference is that the previous one signs and sends, while this one only signs.

const handleMakeSomething = async () => {
  await controller.signPSBT(PSBT);
};

When dealing with multisig, only the last signer sends the transaction, otherwise, it may go unsigned and be rejected.