# 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 thedesc
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 theMask
(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
andaux_fee
fields cannot be updated. In this case, the right value for capabilityflags iscapabilityflags = 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 forMask
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.
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:
Once confirmation is given, the Promise is still pending and needs to resolve by you confirming the details. A second pop-up should appear.
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
andreceiver
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
andisToken = false
. - If you are sending a token, then
isToken = true
andtoken
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 theassets:
tab.
- If you wish to send SYS then
- 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
andmaxsupply
are not used when calling this function. There is a straightforward explanation for this: One can understand NFTs simply as SPTs but withmaxsupply
equal to 1 andprecision
determined byprecision
.
# 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.
After confirmation, NFT information is displayed.
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
After confirmation, details are showed and confirmation is necessary once more time
# 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:
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.