# Key-Value
This guide describes how to create contract that allows to store, add, modify and remove data in blockchain using XPR Network
Let's start!
# Pre-requisites
- NodeJS 16 Installation Guide (opens new window)
- NPM
- Git
- XPR Network CLI (opens new window)
npm install -g @proton/cli
# Steps
- Generate a contract by providing a contract name, as shown here: - proton generate:contract kv- Note: the contract name must be 1-12 chars, only lowercase a-z and numbers 1-5 are possible. 
- The - proton generate:contractcommand prompts you for information about the action of the contact: name and parameters. Let's add- updatevaluesaction with 2 parameters:- with actorparameter that isName
- valuesparameter that is- stringarray
 - Let's add some actions to the class ? Enter new action name: updatevalues ? Do you want to add parameters to the action? Yes ? Enter new parameter name: actor ? Choose parameter type: Name ? Is the parameter an array? No ? Can the parameter be nullable? No ———————————— ? Do you want to add one more parameter? Yes ? Enter new parameter name: values ? Choose parameter type: string ? Is the parameter an array? Yes ? Can the parameter be nullable? No ———————————— ? Do you want to add one more parameter? No ———————————— ? Do you want to add one more action? No
- with 
- The command will prompt you to select your favorite Node.Js package manager if you have both - npmand- yarninstalled. Feel free to select the one you like.
- After the contract is ready navigate to - kvfolder. The folder will have the following structure:- Files - Details - kv.contract.ts- The contract code, written in XPR Network - playground.ts- The code to try a contract 
- Now let's add a table to our contract using - proton generate:tablecommand. The table will be used to store data related to the actor we will pass to the contract.- proton generate:table kvs --class=AccountKV- The table will have name - kvs. XPR Network class name will be- AccountKV.- Also the table will have 2 parameters: - Primary accountthat isName
- valuesthat is- stringarray The command prompt should look like this:
 - proton generate:table kvs --class=AccountKV ? Is the table singleton? No Let's add a primary parameter for the table ? Enter new primary parameter name: account ? Choose parameter type: Name ———————————— ? Do you want to one more parameter? Yes ? Enter new parameter name: values ? Choose parameter type: string ? Is the parameter an array? Yes ? Can the parameter be nullable? No ———————————— ? Do you want to add one more parameter? No Table kvs successfully created Adding the table to the contract kv
- Primary 
- After the table is ready new file - kv.tables.tswill appear in the folder. It should look like this:- import { Name, Table } from "proton-tsc"; @table("kvs") export class AccountKV extends Table { constructor( public account: Name = new Name(), public values: string[] = [] ) { super(); } @primary get primary(): u64 { return this.account.N; } }
- Let's modify this file to add possibility to store key-value. First we need to add new - KVclass to describe key-value type:- @packer export class KV { constructor ( public key: string = "", public value: string = "", ) {} }
- After that need to modify - AccountKVconstructor. Need to change- valuesparameter type from- string[]to- KV[]:- constructor( public account: Name = new Name(), public values: KV[] = [] ) { super(); }
- The result should look like this: - import { Name, Table } from "proton-tsc"; @packer export class KV { constructor ( public key: string = "", public value: string = "", ) {} } @table("kvs") export class AccountKV extends Table { constructor( public account: Name = new Name(), public values: KV[] = [] ) { super(); } @primary get primary(): u64 { return this.account.N; } }
- Now let's implement method to store data in blockchain. Open - kv.contract.tsand modify- updatevaluesmethod the following way:- @action("updatevalues") updatevalues( actor: Name, values: KV[] ): void { // Require authentication for the account we want to store data for requireAuth(actor) // Values should be passed check(values.length > 0, "Must provide at least one value") for (let i = 0; i < values.length; i++) { // The max key length should be less than 255 symbols check(values[i].key.length < 255, "The max key length is 255") // The max value length should be less than 255 symbols check(values[i].value.length < 255, "The max value length is 255") } // Check if there are any previously saved data for the account let kv = this.accountkvTableStore.get(actor.N) if (kv == null) { // Creating new key-value object for saving in blockchain kv = new AccountKV(actor, values) } else { // Adding or updating keys in existing data const existingKeys = kv.values.map<string>(value => value.key) for (let i = 0; i < values.length; i++) { const keyMatchIndex = existingKeys.indexOf(values[i].key) if (keyMatchIndex == -1) { kv.values.push(values[i]) } else { kv.values[keyMatchIndex].value = values[i].value } } } // Save data in table this.accountkvTableStore.set(kv, actor) }
- Also need to update imports by adding - check,- requireAuthfunctions and- KVclass:- import { Contract, Name, TableStore, check, requireAuth } from "proton-tsc"; import { AccountKV, KV } from "./kv.tables";
- Now let's check if our contract works and stores data. To do it let's modify - playground.tsfile the following way:- import { Blockchain } from "@proton/vert"; async function wait(ms: number) { return new Promise(resolve => { setTimeout(resolve, ms); }); } async function main() { const blockchain = new Blockchain(); const contract = blockchain.createContract('kv', 'target/kv.contract'); await wait(0); // First, we will check if there is no data in table store before the contract executed console.log('------ BEFORE ------'); console.log(contract.tables.kvs().getTableRows()); console.log('--------------------'); // Let's save webpage address for kv account await contract.actions.updatevalues(['kv', [{ key: 'webpage', value: 'www.proton.org' }]]).send('kv@active'); // And after all we will check if the data was properly saved to table store console.log('------ AFTER ------'); const data = contract.tables.kvs().getTableRows() console.log(JSON.stringify(data)); console.log('--------------------'); } main();
- And then we need to run - npm run playgroundcommand. The output should be similar to:- ------ BEFORE ------ [] -------------------- ... ------ AFTER ------ [{"account":"kv","values":[{"key":"webpage","value":"www.proton.org"}]}] --------------------- As you see in the - AFTERblock, the data was properly saved and we are able to read it.
- Now let's try to deploy our contract to the blockchain and check how it will work inside the chain. It is easy to do using XPR Network CLI (more detailed info can be found here): - Create an account in proton-testchain using these commands (account can contain 12 characters max using charset a-z and 1-5):proton chain:set proton-test proton account:create <ACCOUNT_NAME>
- Deploy the contract using the following commands:proton faucet:claim XPR <ACCOUNT_NAME> proton ram:buy <ACCOUNT_NAME> <ACCOUNT_NAME> 300000 proton contract:set <ACCOUNT_NAME> ./target
 
- Create an account in 
- After the contract is deployed, we can read the data from - kvstable using:- proton table <ACCOUNT_NAME> kvs- The output should be the following: - { "rows": [], "more": false, "next_key": "" }
- Now let's add data using our contract in blockchain. The command is the following: - proton action <ACCOUNT_NAME> updatevalues '{"actor":"<ACCOUNT_NAME>","values":[{"key":"webpage","value":"www.proton.org"}]}' <ACCOUNT_NAME>
- And check that the data was successfully added to the store: - proton table <ACCOUNT_NAME> kvs- The output should be the following: - { "rows": [ { "account": "<ACCOUNT_NAME>", "values": [ { "key": "webpage", "value": "www.proton.org" } ] } ], "more": false, "next_key": "" }
- There is one more way how you can get access to the data inside the blockchain - using XPR Network API for this. Let's try it. - Install XPR Network Api package using NPM - npm install --save @proton/api
- Create a new file api.tsand edit it the following way:import { ApiClass } from '@proton/api' // Creating new API instance for proton-test chain const protonApi = new ApiClass('proton-test') // perform request for data from kvs table from ACCOUNT_NAME contract protonApi.rpc.get_table_rows({ table: 'kvs', code: '<ACCOUNT_NAME>', scope: '<ACCOUNT_NAME>' }).then((res) => { console.log(JSON.stringify(res, null, 2)); });
- Execute the code using the command: npx ts-node ./api.ts. The result should be the following:It is the same with CLI result as you see.{ "rows": [ { "account": "ACCOUNT_NAME", "values": [ { "key": "webpage", "value": "www.proton.org" } ] } ], "more": false, "next_key": "" }
 
- Install XPR Network Api package using NPM - 
- Now we know how to store data in the contract and how to check the the data is properly saved. You can play with the contract and check the result: - Add one more key to the storageproton action <ACCOUNT_NAME> updatevalues '{"actor":"<ACCOUNT_NAME>","values":[{"key":"twitter","value":"https://twitter.com/protonxpr/"}]}' <ACCOUNT_NAME>
- Update one of the values by key:proton action <ACCOUNT_NAME> updatevalues '{"actor":"<ACCOUNT_NAME>","values":[{"key":"webpage","value":"protonchain.com"}]}' <ACCOUNT_NAME>
 
- Add one more key to the storage
- The only thing we cannot do using the contract is data removal. We can only add or update values. So let's implement this feature in our contract. Let's add new action to KV contract using CLI command - proton generate:action. The action name will be- removekeys. It will have 2 parameters:- with actorparameter that isName
- keysparameter that is- stringarray
 - ? Enter new action name: removekeys ? Do you want to add parameters to the action? Yes ? Enter new parameter name: actor ? Choose parameter type: Name ? Is the parameter an array? No ? Can the parameter be nullable? No ———————————— ? Do you want to add one more parameter? Yes ? Enter new parameter name: keys ? Choose parameter type: string ? Is the parameter an array? Yes ? Can the parameter be nullable? No ———————————— ? Do you want to add one more parameter? No ———————————— ? Do you want to add one more action? No Actions were successfully added
- with 
- Now let's implement method to remove data from blockchain. Open - kv.contract.tsand modify- removekeysmethod the following way:- @action("removekeys") removekeys( actor: Name, keys: string[] ): void { // Require authentication for the account we want to remove data for requireAuth(actor) // Get previously saved data for the account const kv = this.accountkvTableStore.requireGet(actor.N, `no kv found with name ${actor}`) // Find keys to remove let filteredValues: KV[] = [] for (let i = 0; i < kv.values.length; i++) { if (keys.indexOf(kv.values[i].key) == -1) { filteredValues.push(kv.values[i]) } } kv.values = filteredValues if (kv.values.length > 0) { // Save data for actor without keys passed to the method this.accountkvTableStore.update(kv, actor) } else { // Remove the key at all this.accountkvTableStore.remove(kv) } }
- After the code is in place need to modify - playground.tsto check if removal really works. Add the following lines to the end of- mainfunction:- await contract.actions.updatevalues(['kv', [{ key: 'twitter', value: 'https://twitter.com/protonxpr' }]]).send('kv@active'); // Check if there is any data in the store console.log('------ BEFORE REMOVE ------'); const data_before_remove = contract.tables.kvs().getTableRows() console.log(JSON.stringify(data_before_remove)); console.log('--------------------'); await contract.actions.removekeys(['kv', ['webpage']]).send('kv@active'); console.log('------ AFTER WEBPAGE REMOVE ------'); const data_without_webpage = contract.tables.kvs().getTableRows() console.log(JSON.stringify(data_without_webpage)); console.log('--------------------'); await contract.actions.removekeys(['kv', ['twitter']]).send('kv@active'); console.log('------ AFTER REMOVING ALL ------'); const data_clean = contract.tables.kvs().getTableRows() console.log(JSON.stringify(data_clean)); console.log('--------------------');
- And then run - npm run playgroundcommand. The output should be similar to:- ------ BEFORE ------ [] -------------------- ... ------ AFTER ------ [{ "account":"kv", "values":[ {"key":"webpage","value":"www.proton.org"} ] }] -------------------- ... ------ BEFORE REMOVE ------ [{ "account":"kv", "values":[ {"key":"webpage","value":"www.proton.org"}, {"key":"twitter","value":"https://twitter.com/protonxpr"} ] }] -------------------- ... ------ AFTER WEBPAGE REMOVE ------ [{ "account":"kv", "values":[ {"key":"twitter","value":"https://twitter.com/protonxpr"} ] }] -------------------- ... ------ AFTER REMOVING ALL ------ [] --------------------- So you see, that we started with a clean storage and finished with the clean storage. Now we are sure that remove feature is in place. 
- Now let's re-deploy the contract with command - proton contract:set <ACCOUNT_NAME> ./targetand check if the feature works in a real blockchain:- Either with CLI or api.tscheck if the data we added previously to the store is still in place.
- Now let's try to remove webpagekey from store:proton action <ACCOUNT_NAME> removekeys '{"actor":"<ACCOUNT_NAME>","keys":["webpage"]}' <ACCOUNT_NAME>
- And check the result with CLI. The result should be the following:{ "rows": [ { "account": "<ACCOUNT_NAME>", "values": [ { "key": "twitter", "value": "https://twitter.com/protonxpr/" } ] } ], "more": false, "next_key": "" }
- And now let's remove the twitterkey to get clean store:proton action <ACCOUNT_NAME> removekeys '{"actor":"<ACCOUNT_NAME>","keys":["twitter"]}' <ACCOUNT_NAME>
- The check using CLI should give the following result:{ "rows": [], "more": false, "next_key": "" }
 
- Either with CLI or 
Perfect! You created a contract that allows to store, modify and remove data in blockchain. This will help you to create more complex and amazing contract in the future.