Summary
We've met all the objectives set out in 300 lines of easy to read and understand Javascript and now have a form of primitive automation.
- Use zilliqa-js to deploy a contract.
- Use zilliqa-js to interact with and press the button.
- Use zilliqa-js to get the mutable state of our contract.
- Use zilliqa-js to listen for events in our contract.
const { BN, Long, bytes, units } = require('@zilliqa-js/util');const { Zilliqa } = require('@zilliqa-js/zilliqa');const { StatusType, MessageType } = require('@zilliqa-js/subscriptions');const { fromBech32Address, toBech32Address, getAddressFromPrivateKey} = require('@zilliqa-js/crypto');
// these values can change depending on what network you are connecting onconst zilliqa = new Zilliqa('https://dev-api.zilliqa.com');// webhook to listen to events emited back to clientsconst websocket = "wss://dev-ws.zilliqa.com"const chainId = 333;const msgVersion = 1; const VERSION = bytes.pack(chainId, msgVersion);
// use a dotenv file for storing private keys, do not commit this directly to your repoconst privateKey ='';// mutable state field name to monitorconst state_field_to_monitor = "total_count_clicks"
zilliqa.wallet.addByPrivateKey(privateKey);const address_from_pk = getAddressFromPrivateKey(privateKey);
// Application Definition// DEPLOY a contract, // SEND a transaction// LISTEN to events emitted// MONITOR mutable state async function DeploySendListenMonitor(){ deployed_contract_base_16 = await DeployButtonContract(); bech_32_bystr = toBech32Address(deployed_contract_base_16); console.log(`got ${bech_32_bystr} from ${deployed_contract_base_16}`)
await Promise.all([ ListenForEvents(deployed_contract_base_16), PressTheButton(bech_32_bystr) ]);}
// Runs ApplicationDeploySendListenMonitor();
// Deploys a contract using a private key, returns the deployed_contract_base_16 of the contract deployedasync function DeployButtonContract() { try { // Get Balance const balance = await zilliqa.blockchain.getBalance(address_from_pk); // Get Minimum Gas Price from blockchain const minGasPrice = await zilliqa.blockchain.getMinimumGasPrice(); // Account balance (See note 1) console.log(`Your account balance is:`); console.log(balance.result); console.log(`Current Minimum Gas Price: ${minGasPrice.result}`); const myGasPrice = units.toQa('2000', units.Units.Li); // Gas Price that will be used by all transactions console.log(`My Gas Price ${myGasPrice.toString()}`); const isGasSufficient = myGasPrice.gte(new BN(minGasPrice.result)); // Checks if your gas price is less than the minimum gas price console.log(`Is the gas price sufficient? ${isGasSufficient}`); // Deploy a contract console.log(`Deploying a new contract....`); const code = `scilla_version 0library MyFirstButton
let uint128_zero = Uint128 0let uint128_one = Uint128 1let empty_bystr = 0x1111111111111111111111111111111111111111
(* Error exception *)type Error =| NotContractOwner| NotUniqueClicker
let make_error =fun (result : Error) => let result_code = match result with | NotContractOwner => Int32 -1 | NotUniqueClicker => Int32 -2 end in { _exception : "Error"; code : result_code } (** Create a scilla contract which models a button that can be pressed by anyone. * If you are the recent button presser you may not press the button again.* When the button is pressed: Increment a counter and set the caller of the button press to be the most recent address. * The owner of the button has the ability to reset the counter to zero *)contract MyFirstButton(contract_owner: ByStr20)
field current_clicker : ByStr20 = empty_bystrfield total_count_clicks : Uint128 = uint128_zero
(* @Dev: Emit Errors *)procedure ThrowError(err : Error)e = make_error err;throw eend
(*@Dev : Throws an error if '_sender' is not 'contract_owner'*)procedure IsContractOwner()is_contract_owner = builtin eq contract_owner _sender;match is_contract_owner with| True => (* No Operation - Continue contract execution *)| False => err = NotContractOwner; ThrowError errendend
(* Dev: Increments 'current_clicker' by 'uint128_one'*)procedure IncrementCounter()previous_click_count <- total_count_clicks;new_click_count = builtin add previous_click_count uint128_one;total_count_clicks := new_click_count;
e = {_eventname : "IncrementCounterSuccess"; pcc:previous_click_count; ncc: new_click_count };event eend
(* Dev: Throws an error if the current '_sender' is the previous 'current_clicker'*)procedure IsPreviousClicker(new_clicker: ByStr20)previous_clicker <- current_clicker;is_previous_clicker = builtin eq previous_clicker _sender;match is_previous_clicker with| True => err = NotUniqueClicker; ThrowError err| False => (* No Operation - Continue contract execution *)endend
(* Dev: Sets 'new_clicker' as 'current_clicker'*)procedure SetNewClicker(new_clicker: ByStr20)current_clicker := new_clicker;
e = {_eventname : "NewClickerState"; nc:new_clicker};event eend
(* Dev: Resets 'current_clicker' to 'uint128_zero'*)procedure ContractOwnerResetButton()total_count_clicks := uint128_zeroend
(* Dev: Sets 'new_clicker' as current_clicker*)transition PressTheButton()IsPreviousClicker _sender;SetNewClicker _sender;IncrementCounter;
e = {_eventname : "PressTheButtonSuccess"; button_presser : _sender };event eend
(* Dev: Sets 'new_clicker' as current_clicker*)transition OwnerResetButton()IsContractOwner;ContractOwnerResetButtonend`; const init = [ // this parameter is mandatory for all init arrays { vname: '_scilla_version', type: 'Uint32', value: '0', }, { vname: 'contract_owner', type: 'ByStr20', value: `${address_from_pk}`, }, ]; const contract = zilliqa.contracts.new(code, init); // Deploy the contract. // Also notice here we have a default function parameter named toDs as mentioned above. // A contract can be deployed at either the shard or at the DS. Always set this value to false. const [deployTx, deployedContract] = await contract.deployWithoutConfirm( { version: VERSION, gasPrice: myGasPrice, gasLimit: Long.fromNumber(10000), }, false, ); // process confirm console.log(`The transaction id is:`, deployTx.id); console.log(`Waiting transaction be confirmed`); const confirmedTxn = await deployTx.confirm(deployTx.id); console.log(`The transaction status is:`); console.log(confirmedTxn.receipt); if (confirmedTxn.receipt.success === true) { console.log(`Contract address is: 0x${deployedContract.address}`); return "0x" + deployedContract.address; } } catch (err) { console.log(err); }}
// Listen for events from a contract - errors aren't caughtasync function ListenForEvents(deployed_contract_base_16) { const subscriber = zilliqa.subscriptionBuilder.buildEventLogSubscriptions( websocket, { addresses: [ deployed_contract_base_16 ], }, );
console.log("Listener started"); subscriber.emitter.on(MessageType.EVENT_LOG, async (event) => { console.log('get new event log: ', JSON.stringify(event)); // this will emit 2/3 times before event emitted
const current_button_click_count = await zilliqa.blockchain.getSmartContractSubState( deployed_contract_base_16, state_field_to_monitor ); console.log(current_button_click_count.result); console.log("") }); await subscriber.start();}
// Calls the previously deployed contract transitionasync function PressTheButton(bech_32_bystr) { try { const balance = await zilliqa.blockchain.getBalance(address_from_pk); const minGasPrice = await zilliqa.blockchain.getMinimumGasPrice(); const myGasPrice = units.toQa('2000', units.Units.Li); const isGasSufficient = myGasPrice.gte(new BN(minGasPrice.result)); const deployedContract = zilliqa.contracts.at(bech_32_bystr,);
console.log(`Pressing the button...`); const callTx = await deployedContract.callWithoutConfirm( 'PressTheButton', // transition name [], // no vnames { version: VERSION, amount: new BN(0), gasPrice: myGasPrice, gasLimit: Long.fromNumber(8000), }, false, );
console.log(`Waiting transaction be confirmed`); const confirmedTxn = await callTx.confirm(callTx.id);
//console.log(`The transaction status is:`); //console.log(confirmedTxn.receipt); if (confirmedTxn.receipt.success === true) { console.log(`Button pressed by : ${address_from_pk}`); } } catch (err) { console.log(err); }}
Your account balance is:{ balance: '1990534999999850', nonce: 307 }Current Minimum Gas Price: 2000000000My Gas Price 2000000000Is the gas price sufficient? trueDeploying a new contract....The transaction id is: 1548964c7d852fdb5c2b606a62aba83a5e89ce3ec047c3eac8986b0943e10da7Waiting transaction be confirmedThe transaction status is:{ cumulative_gas: 401, epoch_num: '3726858', success: true }Contract address is: 0x60d5edfe4bcff11bde238c153853bcf358654b41got zil1vr27mljtelc3hh3r3s2ns5au7dvx2j6pa6p9k0 from 0x60d5edfe4bcff11bde238c153853bcf358654b41Listener startedPressing the button...Waiting transaction be confirmed
get new event log: {"query":"EventLog"}{ total_count_clicks: '0' }
get new event log: {"query":"EventLog","value":[{"address":"60d5edfe4bcff11bde238c153853bcf358654b41","event_logs":[{"_eventname":"PressTheButtonSuccess","params":[{"type":"ByStr20","value":"0x428a2aa43456fe7fd2de66e48c1fbf372ec10eae","vname":"button_presser"}]},{"_eventname":"IncrementCounterSuccess","params":[{"type":"Uint128","value":"0","vname":"pcc"},{"type":"Uint128","value":"1","vname":"ncc"}]},{"_eventname":"NewClickerState","params":[{"type":"ByStr20","value":"0x428a2aa43456fe7fd2de66e48c1fbf372ec10eae","vname":"nc"}]}]}]}{ total_count_clicks: '1' }
Button pressed by : 0x428A2aA43456FE7fd2De66E48C1fBf372eC10eAEget new event log: {"query":"EventLog"}{ total_count_clicks: '1' }