ZRC-2 Wallet Support


This guide is meant for wallet developers looking to add support for ZRC-2 tokens in their wallet. Since most of the wallet front-ends are built using a JavaScript framework, most developers would find the code found in the js tab to be relevant to them. We have included code snippets for some other languages as well in case you want to handle these functionalities at the backend.

note

It is assumed that you have already installed language-specific Zilliqa SDKs, e.g., zilliqa-js, laksaj, pyzil, gozilliqa.

ZRC-2 Specification

ZRC-2 fungible tokens contract consists of the following components in smart contracts as per the specification.

Error Codes

NameTypeCodeMandatory?
CodeIsSenderInt32-1
✔️
CodeInsufficientFundsInt32-2
✔️
CodeInsufficientAllowanceInt32-3
✔️
CodeNotOwnerInt32-4
CodeNotApprovedOperatorInt32-5

Immutable Variables

NameTypeMandatory?
contract_ownerByStr20
✔️
nameString
✔️
symbolString
✔️
decimalsUint32
✔️
init_supplyUint128
✔️
default_operatorsList ByStr20

Mutable Variables

NameTypeMandatory?
total_supplyUint128
✔️
balancesMap ByStr20 Uint128
✔️
allowancesMap ByStr20 (Map ByStr20 Uint128)
✔️
operatorsMap ByStr20 (Map ByStr20 Unit)
revoked_default_operatorsMap ByStr20 (Map ByStr20 Unit)

Transitions and Events

NameEventsMandatory?
IsOperatorFor()
Mint()Minted, Error
Burn()Burnt, Error
AuthorizeOperator()AuthorizeOperatorSuccess, Error
RevokeOperator()RevokeOperatorSuccess, Error
IncreaseAllowance()IncreasedAllowance, Error
✔️
DecreaseAllowance()DecreasedAllowance, Error
✔️
Transfer()TransferSuccess, Error
✔️
TransferFrom()TransferFromSuccess, Error
✔️
OperatorSend()OperatorSendSuccess, Error

Checking if Contract is Compliant with ZRC-2 Standard

In order to check if the address entered by the user is a ZRC-2 compliant contract, we need to look at the contract code and check if it has the required properties.

Check Immutable Variables

For immutable variables, we'll use the GetSmartContractInit method of the Zilliqa API to get the immutable variables of the contract.

Example Request

curl -d '{
"id": "1",
"jsonrpc": "2.0",
"method": "GetSmartContractInit",
"params": ["173Ca6770Aa56EB00511Dac8e6E13B3D7f16a5a5"]
}' -H "Content-Type: application/json" -X POST "https://api.zilliqa.com/"

Example Response

{
"id": "1",
"jsonrpc": "2.0",
"result": [
{
"type": "Uint32",
"value": "0",
"vname": "_scilla_version"
},
{
"type": "ByStr20",
"value": "0x0f8167a0CBFfb8AB1d1919E31f83DC26C863D0F9",
"vname": "contract_owner"
},
{
"type": "String",
"value": "XSGD",
"vname": "name"
},
{
"type": "String",
"value": "XSGD",
"vname": "symbol"
},
{
"type": "Uint32",
"value": "6",
"vname": "decimals"
},
{
"type": "Uint128",
"value": "0",
"vname": "init_supply"
},
{
"type": "ByStr20",
"value": "0x0f8167a0CBFfb8AB1d1919E31f83DC26C863D0F9",
"vname": "init_implementation"
},
{
"type": "ByStr20",
"value": "0x0f8167a0CBFfb8AB1d1919E31f83DC26C863D0F9",
"vname": "init_admin"
},
{
"type": "BNum",
"value": "732529",
"vname": "_creation_block"
},
{
"type": "ByStr20",
"value": "0x173ca6770aa56eb00511dac8e6e13b3d7f16a5a5",
"vname": "_this_address"
}
]
}

The response received consists of the type, value, and vname for each immutable variable.

To check whether a contract is compliant with the ZRC-2 standard's immutable variables requirement, see if the contract implements the mandatory immutable variables.

note

This code snippet is in JavaScript but the same logic can be applied in other languages.

let vNameArray = []; //Array to store immutable variable names
for(let i=0; i< smartContractInit.result.length; i++){
vNameArray[i] = smartContractInit.result[i].vname;
}
//check if the immutable variables: name, symbol, decimals & init_supply exist in the vName array.
let isZRC2 = vNameArray.includes("name") && vNameArray.includes("symbol") && vNameArray.includes("decimals") && vNameArray.includes("init_supply");
console.log(isZRC2);

Check Mutable Variables

For mutable variables, we'll use the GetSmartContractState method of the Zilliqa API to get the mutable variables of the contract.

Example Request

curl -d '{
"id": "1",
"jsonrpc": "2.0",
"method": "GetSmartContractState",
"params": ["173Ca6770Aa56EB00511Dac8e6E13B3D7f16a5a5"]
}' -H "Content-Type: application/json" -X POST "https://api.zilliqa.com/"

Example Response

{
"id": "1",
"jsonrpc": "2.0",
"result": {
"_balance": "0",
"admin": "0x2f604cbd408e2c8b7442b1b629a1288c44945130",
"allowances": {},
"balances": {
"0x05d087623bc636108d450e0550ddbfc03da99fa9": "10000000",
"0x0f8167a0cbffb8ab1d1919e31f83dc26c863d0f9": "24250000",
"0x18c241a5f0d6cf380f721618880f2c2b7aa5ea97": "9975750000",
"0x5abf71d798ca594b7317b04f52ad5a31fae62170": "10000000",
"0x8c3de413a9d8d602a1757210ab539853103e08d8": "250000000000",
"0x93eb1d0cb7ba3962fcc86cd28aa45b241c888277": "20000",
"0xab61c57a9a4b2742a4a325ecd9e77b5da67f663a": "980000",
"0xabe1e844c776e97beb619f3ca64faa2b3edc2840": "400000",
"0xc48565c853fe4ffa5d7eac33e255141134640ceb": "600000",
"0xf5f9e1ad8ea6439f625e12b8ef57e1e99ac2d383": "7000000"
},
"implementation": "0x3bd9ad6fee7bfdf5b5875828b555e4f702e427cd",
"total_supply": "260029000000"
}
}

The response received above consists of the mutable variables total_supply, balances, and allowances.

To check whether a contract is compliant with the ZRC-2 standard's mutable variables requirement, see if the contract implements the mandatory mutable variables.

note

This code snippet is in JavaScript but the same logic can be applied in other languages.

let vNameArray = []; //Array to store mutable variable names
for(let i=0; i< smartContractState.result.length; i++){
vNameArray[i] = smartContractState.result[i].vname;
}
//check if the mutable variables: total_supply, balances & allowances exist in the vName array.
let isZRC2 = vNameArray.includes("total_supply") && vNameArray.includes("balances") && vNameArray.includes("allowances");
console.log(isZRC2);

Check Transitions, Events, and Error Codes

Currently, you need to look at the smart contract code manually and check for transitions, events, and error codes in a contract. To check whether a contract is compliant with the ZRC-2 standard's error codes - as well transitions and events - requirement, see if the contract implements the mandatory error codes and mandatory transitions and events.

Integrating with ZRC-2 Fungible Tokens Contract

Fetch Token Balance

In a ZRC-2 fungible tokens contract, the mutable field balances (with data type Map) stores the mapping between addresses and their corresponding token balances. We'll use the GetSmartContractState method of the Zilliqa API to get the mutable variables of the contract.

Example Request

curl -d '{
"id": "1",
"jsonrpc": "2.0",
"method": "GetSmartContractState",
"params": ["173Ca6770Aa56EB00511Dac8e6E13B3D7f16a5a5"]
}' -H "Content-Type: application/json" -X POST "https://api.zilliqa.com/"

Example Response

{
"id": "1",
"jsonrpc": "2.0",
"result": {
"_balance": "0",
"admin": "0x2f604cbd408e2c8b7442b1b629a1288c44945130",
"allowances": {},
"balances": {
"0x05d087623bc636108d450e0550ddbfc03da99fa9": "10000000",
"0x0f8167a0cbffb8ab1d1919e31f83dc26c863d0f9": "24250000",
"0x18c241a5f0d6cf380f721618880f2c2b7aa5ea97": "9975750000",
"0x5abf71d798ca594b7317b04f52ad5a31fae62170": "10000000",
"0x8c3de413a9d8d602a1757210ab539853103e08d8": "250000000000",
"0x93eb1d0cb7ba3962fcc86cd28aa45b241c888277": "20000",
"0xab61c57a9a4b2742a4a325ecd9e77b5da67f663a": "980000",
"0xabe1e844c776e97beb619f3ca64faa2b3edc2840": "400000",
"0xc48565c853fe4ffa5d7eac33e255141134640ceb": "600000",
"0xf5f9e1ad8ea6439f625e12b8ef57e1e99ac2d383": "7000000"
},
"implementation": "0x3bd9ad6fee7bfdf5b5875828b555e4f702e427cd",
"total_supply": "260029000000"
}
}

If the value of user's address is stored in the variable userAddress, and the response json received above is stored in the variable smartContractState, a user's token balance would then be:

smartContractState.result.balances[userAddress]

Transfer Token

ZRC-2 contracts have the transfer transition that allows users to transfer tokens to another address by specifying the receiving address and amount.

The code snippet below illustrates how one can transfer ZRC-2 tokens to another address.

//zilliqa, privateKey, bytes, units, recipientAddress, sendingAmount are defined in the codebase before
zilliqa.wallet.addByPrivateKey(privateKey);
const CHAIN_ID = 1;
const MSG_VERSION = 1;
const VERSION = bytes.pack(CHAIN_ID, MSG_VERSION);
const myGasPrice = units.toQa('1000', units.Units.Li); // Gas Price that will be used by all transactions
try {
const contract = zilliqa.contracts.at(contractAddress);
const callTx = await contract.call(
'Transfer',
[
{
vname: 'to',
type: 'ByStr20',
value: recipientAddress,
},
{
vname: 'amount',
type: 'Uint128',
value: sendingAmount,
}
],
{
// amount, gasPrice and gasLimit must be explicitly provided
version: VERSION,
amount: new BN(0),
gasPrice: myGasPrice,
gasLimit: Long.fromNumber(10000),
}
);
console.log(JSON.stringify(callTx.TranID));
} catch (err) {
console.log(err);
}