Fetch data from a Map datatype on a Tezos Smart Contract
Fetch data from a BigMap datatype on a Tezos Smart Contract
Initialize Map data while originating a new contract to the Tezos Blockchain
Use Pairs as a key to access Map and BigMap values
Why Michelson Map and BigMap don't look like a Javascript Map
Taquito provides MichelsonMap to make it easy for developers to work with the native Michelson map datatypes. MichelsonMap supports initialization, get and set methods to Maps using primitive datatypes and pairs as keys.
Michelson offers two variants of Maps that are semantically the same but have different implementations and trade-offs in terms of gas and storage costs on a contract. A Map uses more storage but costs less gas, whereas a BigMap consumes less storage but has higher gas costs during the Smart Contract's execution.
Origination of the contract with an initial storage
This example builds on the Ligo Lang Taco Shop learning resources.
The storage of the contract used in the following example is a map where a key is a natural number (a nat), and a value is a pair composed of two values representing the quantity of stock and tez tokens, respectively. The contract's source code is available here. In the example, the contract is originated with initial values using the MichelsonMap class' set method.
Contract API
Wallet API
Live Editor
import{MichelsonMap}from'@taquito/taquito';
// import { TezosToolkit } from '@taquito/taquito';
// const Tezos = new TezosToolkit('https://YOUR_PREFERRED_RPC_URL');
The fromLiteral convenience method can be used instead of using set for each element. Here is the same origination operation but using fromLiteral to create our MichelsonMap.
Contract API
Wallet API
Live Editor
import{MichelsonMap}from'@taquito/taquito';
Tezos.contract
.originate({
code: contractMapTacoShop,
storage:MichelsonMap.fromLiteral({
1:{current_stock:'10000',max_price:'50'},
2:{current_stock:'120',max_price:'20'},
3:{current_stock:'50',max_price:'60'},
}),
})
.then((contractOriginated)=>{
console.log(`Waiting for confirmation of origination for ${contractOriginated.contractAddress}...`);
This example loads the same type of Taco Shop contract (we created this one earlier). Taquito provides a get method of the MichelsonMap on storage of type Map, and in this case, we access the value stored with a key of 1.
The example calls the Contracts main function of the contract using the key 1 as its parameter. Remember, we can only change contract storage by calling the function provided by the contract. The main function on this Smart Contract is decreasing the value of the current_stock associated with the key 1. We use the get method of the MichelsonMap class to see the difference in storage after the method call.
Contract API
Wallet API
Live Editor
Tezos.contract
.at('KT1B3SpFJ1iHagwdkd1utVYP18RyYgZXeGio')
.then((myContract)=>{
return myContract
.storage()
.then((myStorage)=>{
//We want to see the value of the key "1"
const value = myStorage.get('1');
console.log(
`The key "1" of the map has a current_stock of ${
value[Object.keys(value)[0]]
} and a max_price of ${value[Object.keys(value)[1]]}.`
);
//Calling the main method of the contract will modify the storage
A Contract with a Map using an unannotated pair/tuple as a key
Here we have the storage of our contract defined in Michelson.
It has a Map with the annotated name %theMap. This Map uses a pair consisting of a natural number and an address as its key (1, tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx). Its value is also a pair of values, consisting of an int (annotated as %quantity) and mutez (annotated as %amount).
The get method of the MichelsonMap class accesses values of the map for a specified key.
This example accesses the map using its theMap annotation. If the storage does not annotate its properties, the caller must use numeric indexes instead.
Recall that this contract does not annotate the pairs of the key pair either. We use numeric indexes for this also.
This contract schema has a key with eight nested pairs and the value of an int. This example type of key is impractical, but we offer it as an example to illustrate how to work with complex keys.
The Michelson storage schema with a map using eight pairs as a key:
The get method of the MichelsonMap class accesses values of the map for a specified key.
Reminder
Taquito will handle timestamps with the milliseconds as 2019-09-06T15:08:29.000Z rather than 2019-09-06T15:08:29Z which is crucial when accessing maps with complex keys
Map and BigMap are semantically the same except for everything you learned about Maps applies to working with BigMaps. The only difference is that when calling get on a bigMap will return a Javascript Promise, whereas get on a Map returns directly. In this example, the contract schema does not have map annotations, which means that each value needs to have an index as a property name.
The MichelsonMap class also supports the bigMap type. The following example uses a contract containing both a map and a bigMap in its storage. Here is the Michelson definition of storage for this example:
The get method of the MichelsonMap class accesses the values of the map and values of the bigMap. The difference is that the value gets returned directly for a map while the get method on a bigMap returns a promise.
Contract API
Wallet API
Live Editor
Tezos.contract
.at('KT1PZb8sEVvsKQGzLwusyQRkJpSCDQ7WFfny')
.then((myContract)=>{
return myContract
.storage()
.then((myStorage)=>{
//When called on a map, the get method returns the value directly
By default, a call to an RPC node is used to pack data when fetching values from a big map. Big map keys need to be serialized or packed and Taquito relies on the PACK functionality of a Tezos RPC node to pack the big map keys. This may be considered inefficient as it adds a request to a remote node to fetch data.
Now, Taquito allows you to pack the required data locally to fetch values from a big map. By relying on the local pack implementation, Taquito eliminates one RPC roundtrip when fetching big map values. This feature makes fetching big map values 50% faster.
Implementing this feature is a very easy 2 step process:
Importing the MichelCodecPacker class from @taquito/taquito
Creating an instance of the MichelCodecPacker class and passing it to the setPackerProvider method of the TezosToolkit instance.
Here is an example:
import{MichelCodecPacker}from'@taquito/taquito';
constTezos=newTezosToolkit(RPC_URL);
Tezos.setPackerProvider(newMichelCodecPacker());
After that, Taquito will automatically pack the keys locally when you want to fetch the values of a big map.
It is possible to fetch multiple big map values using Taquito with one call using the getMultipleValues method of the BigMapAbstraction class. Taquito will ensure that all fetched big maps come from the same block to ensure a consistent state.
The method takes an array of keys to query as a parameter and an optional block level and returns a MichelsonMap containing the keys and their value in a well-formatted JSON object format. The accepted types for the keys are string, number or object (the last one is used when the type of the keys in the big map is a Michelson pair).
In the following example, we will fetch 4 big map values at once. The Michelson type of the big map key is an address and the type of its value is a pair made of a nat and a map. We see in the example that the address tz1QZ6KY7d3BuZDT1d19dUxoQrtFPN2QJ3hn is not a key of the big map, so its value is set to undefined in the returned MichelsonMap.
Contract API
Wallet API
Live Editor
// import { TezosToolkit } from '@taquito/taquito';
// const Tezos = new TezosToolkit('https://ghostnet.tezos.ecadinfra.com');
Tezos.contract
.at('KT1LPdW47Aba3kVpNMpRt7sx5yM1M4A8XmAW')
.then((contract)=>{
console.log('Fetching the storage of the contract...');
return contract.storage();
})
.then((storage)=>{
console.log('Fetching the big map values...\n');
return storage['0'].getMultipleValues([
'tz3WXYtyDUNL91qfiCJtVUX746QpNv5i5ve5',
'tz1h3rQ8wBxFd8L9B3d7Jhaawu6Z568XU3xY',
'tz1bwsEWCwSEXdRvnJxvegQZKeX5dj6oKEys',
'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb',
]);
})
.then((values)=>{
values.forEach((value, key)=>{
console.log(`The value of the key ${key} is:\n${JSON.stringify(value,null,2)}.\n`);