This guide explains how to add a new token to our existing token holding amount Verifiable Credentials (VC) contract, using Bean token as an example. We’ll define the token’s configuration and add it to the TokenMapping contract.
Step 1: Define the Token Configuration
Create a new token configuration library for Bean. This will include range assertions and network information.
pragmasolidity ^0.8.8;import'../../libraries/Identities.sol';import'../Constants.sol';libraryBean { // Define a range array for assertion clause generation, e.g. $holding_amount > 0, $holding_amount && $holding_amount < 1500, $holding_amount > 50000.
functiongetTokenRanges() internalpurereturns (TokenInfoRangesmemory) {uint256[] memory ranges =newuint256[](5); ranges[0] =0; ranges[1] =1500; ranges[2] =5000; ranges[3] =10000; ranges[4] =50000;returnTokenInfoRanges(ranges,0); } // Define a token info network array to config token address, data provider and token decimals for current token in different networks.
functiongetTokenNetworks()internalpurereturns (TokenInfoNetwork[] memory) { TokenInfoNetwork[] memory networks =new TokenInfoNetwork[](2); networks[0] =TokenInfoNetwork( Web3Networks.Bsc,'0x07da81e9a684ab87fad7206b3bc8d0866f48cc7c',// Token address on BSC DataProviderTypes.NoderealClient,// Data provider18// Token decimals ); networks[1] =TokenInfoNetwork( Web3Networks.Combo,'0xba7b9936a965fac23bb7a8190364fa60622b3cff',// Token address on Combo DataProviderTypes.NoderealClient,18 );return networks; }}
Step 2: Add the Token to the TokenMapping Contract
import { Bean } from"./Bean.sol";contractTokenMappingisTokenQueryLogic {constructor() { ...// registers the Bean token along with its holding ranges and network details.setTokenInfo("bean", Bean.getTokenRanges(), Bean.getTokenNetworks()); }}
How to Add a New Data Provider for a Token?
This guide demonstrates how to add a new external data provider, Nodereal, via NoderealClient as an example. You’ll define the new provider and integrate it into the token balance query logic.
Step 1: Add the Data Provider Type
First, add a new constant for the data provider in the DataProviderTypes library located in Constants.sol.
Next, create the NoderealClient library to interact with the external API and retrieve token balances. The library should include methods to query balances. For different external API, they may support some of the blockchain network, in case a token exists in blockchain network A but the external API used doesn't support blockchain network A, then we can filter this network so we don't make unnecessary call to the external API for the blockchain network that it doesn't support. And in the generated Verifiable Credentials, we will included the blockchain network set that we calculated the token balance on so that the VC user will know the final result is based on what exact blockchain networks.
pragmasolidity ^0.8.8;import'@openzeppelin/contracts/utils/Strings.sol';import'../libraries/Http.sol';import'../libraries/Identities.sol';import'../libraries/Utils.sol';libraryNoderealClient {functiongetTokenBalance(uint32 network,stringmemory secret,stringmemory tokenContractAddress,stringmemory account ) internalreturns (bool,uint256) { HttpHeader[] memory headers =new HttpHeader[](0);stringmemory request;stringmemory encodePackedUrl =string( abi.encodePacked(getNetworkUrl(network), secret) ); // For Native Token, call the method: eth_getBalance, if one token is the native token for the current network, the token contract address will be config to `Native Token`.
if (Strings.equal(tokenContractAddress,'Native Token')) {// Use eth_getBalance method request =string( abi.encodePacked('{"jsonrpc": "2.0", "method": "eth_getBalance", "id": 1, "params": ["', account,'", "latest"]}' ) ); } elseif (bytes(tokenContractAddress).length ==42) {// For non-Native Token, call the method: nr_getTokenBalance20. request =string( abi.encodePacked('{"jsonrpc": "2.0", "method": "nr_getTokenBalance20", "id": 1, "params": ["', tokenContractAddress,'","', account,'", "latest"]}' ) ); } else {return (false,0); } (bool result,stringmemory balance) = Http.PostString( encodePackedUrl,'/result', request, headers );if (result) {// The result value is hex string, need to convert it to a number value.return Utils.hexToNumber(balance); } else {return (false,0); } }// Define which networks are support by this client.functionisSupportedNetwork(uint32 network) internalpurereturns (bool) {return network == Web3Networks.Bsc || network == Web3Networks.Ethereum || network == Web3Networks.Combo; }// For Nodereal, different networks have different API URLs.functiongetNetworkUrl(uint32 network ) internalpurereturns (stringmemory url) {if (network == Web3Networks.Bsc) { url ='https://bsc-mainnet.nodereal.io/v1/'; } elseif (network == Web3Networks.Ethereum) { url ='https://eth-mainnet.nodereal.io/v1/'; } elseif (network == Web3Networks.Combo) { url ='https://combo-mainnet.nodereal.io/v1/'; } }}
Step 3: Integrate the New Data Provider into TokenQueryLogic
Finally, use the newly added NoderealClient in the TokenQueryLogic contract to query balances for the data provider supported networks.
abstractcontractTokenQueryLogicisTokenHoldingAmount {functionqueryBalance(Identitymemory identity,uint32 network,string[] memory secrets,stringmemory tokenName,TokenInfomemory token ) internaloverridereturns (uint256) { (bool identityToStringSuccess,stringmemory identityString) = Utils .identityToString(network, identity.value);if (identityToStringSuccess) {uint256 totalBalance =0;// Retrieve token information for the specified network (stringmemory tokenContractAddress,uint8 dataProviderType,uint8 decimals ) =getTokenInfoNetwork(token, network);uint8 tokenDecimals = token.maxDecimals;uint8 tokenDecimalsDiff = tokenDecimals - decimals;// Use NoderealClient to query balances for supported networksif ( dataProviderType == DataProviderTypes.NoderealClient && NoderealClient.isSupportedNetwork(network) ) { (bool success,uint256 balance) = NoderealClient .getTokenBalance( network, secrets[1], tokenContractAddress, identityString );if (success) { // Nodereal returns balance without decimals, so need multiply by the diff between maxDecimals and decimals.
totalBalance += balance *10** tokenDecimalsDiff; } } ...return totalBalance; }return0; }}